본문 바로가기
Language/파이썬

장고(django) 앨범, 사진 조회 기능 - 파이썬 장고를 활용한 쉽고 빠른 웹 개발 프로그래밍

by javapp 자바앱 2022. 1. 29.
728x90


 

 

앨범 리스트
앨범 디테일
사진 디테일

 


 

 

  •  

앱 모듈 추가

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> &emsp;                                                     <!-- 큰 스페이스 -->
            <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 }}&emsp;</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)

album_detail --> photo_detail

 

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 이 실행된다.

 

 

댓글