앱 모듈 추가
python manage.py startapp photo
셋팅
INSTALLED_APPS = [
...
'photo.apps.PhotoConfig' # 사진 앱 추가
]
Model
photo/models.py
from django.db import models
from django.urls import reverse
# 사진에 대한 원본 이미지와 썸네일 이미지를 모두 저장할 수 있는 커스텀 필드
from photo.fields import ThumbnailImageField
# Album 과 Photo 는 1 : N
class Album(models.Model):
name = models.CharField(max_length=30)
description = models.CharField('One Line Description', max_length=100, blank=True)
class Meta:
ordering = ('name',) # 객체 리스트를 출력할때 정렬 기준
def __str__(self):
return self.name
# 이 메소드가 정의된 객체를 지칭하는 URL을 반환
# /photo/album/99
def get_absolute_url(self):
return reverse('photo:album_detail', args=(self.id,))
class Photo(models.Model):
# 외래키
album = models.ForeignKey(Album,on_delete=models.CASCADE)
title = models.CharField('TITLE',max_length=30)
description = models.TextField('Photo Description',blank=True)
image = ThumbnailImageField(upload_to='photo/%Y/%m')
upload_dt = models.DateTimeField('Upload Date', auto_now=True)
class Meta:
ordering = ('title',)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('photo:photo_detail', args=(self.id,))
Admin 사이트
photo/admin.py
from django.contrib import admin
from photo.models import Album, Photo
# StackedInline : 세로로 나열
class PhotoInline(admin.StackedInline):
model = Photo
extra = 2 # 이미 존재하는 객체 외에 추가로 입력할 수 있는 photo 테이블 객체 수는 2개
@admin.register(Album)
class AlbumAdmin(admin.ModelAdmin):
inlines = (PhotoInline,)
list_display = ('id','name','description')
@admin.register(Photo)
class PhotoAdmin(admin.ModelAdmin):
list_display = ('id','title','upload_dt')
커스텀 필드
photo/field.py
import os
from PIL import Image
from django.db.models.fields.files import ImageField, ImageFieldFile
class TumbnailImageFieldFile(ImageFieldFile):
# 확장자 수정 메소드
def _add_thumb(self,file_name):
parts = file_name.split(".")
parts.insert(-1, "thumb")
# 확장자 부분 'jpg' 로 수정
if parts[-1].lower() not in ['jpeg','jpg']:
parts[-1] = 'jpg'
return ".".join(parts)
# 데코레이터를 사용하여 메소드를 멤버 변수처럼 사용할 수 있다.
@property
def thumb_path(self):
return self._add_thumb(self.path) # 함수를 리턴
@property
def thumb_url(self):
return self._add_thumb(self.url)
def save(self,name, content, save=True):
super().save(name,content,save) # 부모 메소드 실행, 원본 이미지 저장
img = Image.open(self.path)
# 디폴트 128 x 128
size = (self.field.thumb_width, self.field.thumb_height)
img.thumbnail(size)
background = Image.new('RGB', size, (255,255,255))
# 썸네일 박스에 붙이기
box = (int((size[0] - img.size[0])/3), int((size[1] - img.size[1])/ 3))
background.paste(img,box)
background.save(self.thumb_path, 'JPEG')
def delete(self, save=True):
if os.path.exists(self.thumb_path):
os.remove(self.thumb_path)
super().delete(save)
class ThumbnailImageField(ImageField):
attr_class = TumbnailImageFieldFile
def __init__(self, verbose_name=None, thumb_width=128, thumb_height=128, **kwargs):
self.thumb_width, self.thumb_height = thumb_width, thumb_height
super().__init__(verbose_name,**kwargs)
데이터베이스 반영
python manage.py makemigrations photo
python manage.py migrate
mysite/urls.py
from django.contrib import admin
from django.urls import path , include # include 추가
from django.conf.urls.static import static
from django.conf import settings
from bookmark.views import BookmarkLV, BookmarkDV
from mysite.views import HomeView # 홈페이지
urlpatterns = [
path('admin/', admin.site.urls),
# path('bookmark/',BookmarkLV.as_view(), name='index'),
# path('bookmark/<int:pk>',BookmarkDV.as_view(), name='detail'),
path('bookmark/', include('bookmark.urls')),
# include 를 통해 APP 단위의 URLconf 로 위임
path('blog/',include('blog.urls')),
path('photo/',include('photo.urls')),
# 홈페이지
path('',HomeView.as_view(), name='home'), # 루트 URL , 패턴명 = 'home'
] + static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
path('photo/',include('photo.urls')), // 를 추가
photo/urls.py
from django.urls import path
from photo import views
app_name = 'photo'
urlpatterns=[
# /photo/
path('', views.AlbumLV.as_view(),name='index'),
# path('', views.AlbumLV.as_view(model=Album), name='index'), # 이렇게 정의하면 views.py 에 따로 코딩안해도 된다.
path('album',views.AlbumLV.as_view(), name='album_list'),
path('album/<int:pk>/', views.AlbumDV.as_view(), name='album_detail'),
path('photo/<int:pk>/', views.PhotoDV.as_view(), name='photo_detail')
]
photo/views.py
from django.shortcuts import render
from django.views.generic import ListView, DetailView
from photo.models import Album, Photo
class AlbumLV(ListView):
model = Album
# {{ object }}
class AlbumDV(DetailView):
model = Album
# {{ object_list }}
class PhotoDV(DetailView):
model = Photo
템플릿
photo/templates/photo/album_list.html
{% extends 'base.html' %}
{% block title %}album_list.html{% endblock %}
{% block extra-style %}
<style>
thumbnail{
border: 3px solid #ccc
}
</style>
{% endblock %}
{% block content %}
{% for item in object_list %}
<div class="mt-5"> <!-- margin top -->
<a class="h2" href="{% url 'photo:album_detail' item.id %}">
{{ item.name }}
</a>   <!-- 큰 스페이스 -->
<span class="font-italic h5">{{ item.description }}</span>
</div>
<hr style="margin: 0 0 20px 0;"> <!-- 동 서 남 북 -->
<div class="row">
{% for photo in item.photo_set.all|slice:":5" %} <!-- 리스트에서 앞에서부터 5개 객체 추출, slice는 슬라이싱과 유사 -->
<div class="ml-5"> <!-- margin left -->
<div class="thumbnail">
<a href="{{ photo.get_absolute_url }}">
<img src="{{ photo.image.thumb_url }}" style="width: 30%;">
</a>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
{% endblock %}
album_detail.html
{% extends 'base.html' %}
{% block title %}album_detail.html{% endblock %}
{% block extra-style %}
<style>
thumbnail{
border: 5px solid #ccc
}
</style>
{% endblock %}
{% block content %}
<div class="mt-5">
<span class="h2">{{ object.name }} </span>
<span class="h5 font-italic">{{ object.description }}</span>
</div>
<hr style="margin: 0 0 20px 0;">
<div class="row">
{% for photo in object.photo_set.all %}
<div class="col-md-3 mb-5">
<div class="thumbnail">
<a href="{{ photo.get_absolute_url }}">
<img src="{{ photo.image.thumb_url }}" style="width: 100%;">
</a>
</div>
<ul>
<li class="font-italic">{{ photo.title }}</li>
<li class="font-italic">{{ photo.upload_dt|date:"Y-m-d" }}</li>
</ul>
</div>
{% endfor %}
</div>
{% endblock %}
photo_detail.html
{% extends "base.html" %}
{% block title %}photo_detail.html{% endblock %}
{% block content %}
<h2 class="mt-5">{{ object.title }}</h2>
<div class="row">
<div class="col-md-9"> <!-- 너비 9 영역 -->
<a href="{{ object.image.url }}">
<img src="{{ object.image.url }}" style="width: 100%;">
</a>
</div>
<ul class="col-md-3 mt-3">
<li class="h5">Photo 설명</li>
{% if object.description %}<p>{{ object.description|linebreaks }}</p>
{% else %}<p>(blank)</p>{% endif %}
<li class="h5">Date Uploaded</li>
<p class="font-italic">{{ object.upload_dt }}</p>
<li class="h5">Album Name</li>
<p class="font-italic">
<a href="{% url 'photo:album_detail' object.album.id %}">{{ object.album.name }}</a>
</p>
</ul>
</div>
{% endblock %}
상위 템플릿
base.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<title>{% block title %} JJango Web Programming {% endblock %}</title>
{% block extra-style %}{% endblock %}
</head>
<body style="padding-top:90px">
<!-- home html -->
<nav class="navbar navbar-expand-lg bg-primary navbar-fixed-top navbar-dark" >
<span class="navbar-brand mx-5 mb-0 font-weight-bold font-italic">JJango - Python Web Programming</span>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="nav navbar-nav">
<li class="nav-item mx-1 btn btn-primary">
<a href="{% url 'home' %}" class="nav-link btn-primary" >Home </a>
</li>
<li class="nav-item mx-1 btn btn-primary">
<a href="{% url 'bookmark:index' %}" class="nav-link btn-primary" >Bookmark </a>
</li>
<li class="nav-item mx-1 btn btn-primary">
<a href="{% url 'blog:index' %}" class="nav-link btn-primary" >Blog </a>
</li>
<li class="nav-item mx-1 btn btn-primary">
<a href="{% url 'photo:index' %}" class="nav-link" text-white>Photo </a>
</li>
<li class="nav-item dropdown mx-1 btn btn-primary">
<a href="#" class="nav-link dropdown-toggle btn-primary" data-toggle="dropdown" role="button" aria-expanded="false">Util <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="{% url 'admin:index' %}" class="dropdown-item">Admin</a></li>
<li class="divider"></li>
<li><a href="{% url 'blog:post_archive' %}" class="dropdown-item">Archive</a></li>
<li><a href="{% url 'blog:search' %}" class="dropdown-item">Search</a></li>
</ul>
<!-- <a href="#" class="nav-link dropdown-toggle btn-primary" data-toggle="dropdown" role="button" aria-expanded="false">Util <span class="caret"></span></a>-->
<!-- <div class="dropdown-menu">-->
<!-- <a href="{% url 'admin:index' %}" class="dropdown-item">Admin</a>-->
<!-- <div class="divider"></div>-->
<!-- <a href="{% url 'blog:post_archive' %}" class="dropdown-item">Archive</a>-->
<!-- <a href="" class="dropdown-item">Search</a>-->
<!-- </div>-->
</li>
</ul>
<form class="form-inline my-2" action="" method="post" role="search">
{% csrf_token %}
<input class="form-control mr-sm-2" type="search" placeholder="global search" name="search_word">
</form>
</div><!-- /.navbar-collapse -->
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
<!-- 하위 html 파일에서 변경하거나 추가할 가능성이 있는 부분에는 % block % 태그를 추가 -->
{% block footer %}{% endblock %}
{% block extra-script %}{% endblock %}
</body>
</html>
에러 처리
django.urls.exceptions.NoReverseMatch: Reverse for 'photo_detail' not found. 'photo_detail' is not a valid view function or pattern name.
http://127.0.0.1:8000/photo/
해당 url 접근 중 에러 발생
photo_detail 이라는 이름을 가진 템플릿 파일을 찾지 못하고 있다.
그렇게 삽질 결과
해당 이름을 잘못 정의 했었고
photo_detail
으로 수정 했다.
수정 결과
http://127.0.0.1:8000/photo/
다른 url 접근할때 확인 사항
ex)
URL 패턴을 확인
photo/urls.py
app_name = 'photo'
urlpatterns=[
# /photo/
path('', views.AlbumLV.as_view(),name='index'),
# path('', views.AlbumLV.as_view(model=Album), name='index'), # 이렇게 정의하면 views.py 에 따로 코딩안해도 된다.
path('album',views.AlbumLV.as_view(), name='album_list'),
path('album/<int:pk>/', views.AlbumDV.as_view(), name='album_detail'),
path('photo/<int:pk>/', views.PhotoDV.as_view(), name='photo_detail')
]
http://127.0.0.1:8000/photo/photo/2/
의 url은 photo_detail.html 이 담당
템플릿
album_list.html
{% for photo in item.photo_set.all|slice:":5" %}
<a href="{{ photo.get_absolute_url }}">
...
클릭시 photo.get_absolute_url 메소드가 실행됨을 알 수 있다.
photo/views.py
class Photo(models.Model):
...
def get_absolute_url(self):
return reverse('photo:photo_detail', args=(self.id,))
...
photo 클래스 에서 get_absolute_url 메소드는
photo_detail 템플릿을 호출한다.
그래서 위의 링크를 클릭하면 photo_detail.html 이 실행된다.
'Language > 파이썬' 카테고리의 다른 글
장고(django) 로그인 기능 - 파이썬 장고를 활용한 쉽고 빠른 웹 개발 프로그래밍 (0) | 2022.02.02 |
---|---|
장고(django) 블로그 글 검색(Search) - 파이썬 장고를 활용한 쉽고 빠른 웹 개발 프로그래밍 (0) | 2022.01.23 |
장고(django) 태그 taggit - 파이썬 장고를 활용한 쉽고 빠른 웹 개발 프로그래밍 (0) | 2022.01.17 |
장고(django) 프로젝트 첫 페이지, 템플릿 상속 - 파이썬 장고를 활용한 쉽고 빠른 웹 개발 프로그래밍 (0) | 2022.01.11 |
장고(django)blog app(+아카이브) - 파이썬 장고를 활용한 쉽고 빠른 웹 개발 프로그래밍 (0) | 2022.01.05 |
댓글