본문 바로가기

Pyhon/Django

[Pyhon/Django] 장고 기초#013. 장고 애플리케이션 확장

포스트에 템플릿 링크 추가

이번에는 애플리케이션의 기능을 확장합니다. 포스트의 제목(Title)을 클릭하면 포스트의 세부 정보를 제공하는 기능을 추가합니다. 우선 post_list.html 문서를 다음과 같이 수정합니다.

{% extends 'blog/base.html' %}

{% block content %}
    {% for post in posts %}
        <div class="post">
            <div class="date">
                {{ post.published_date }}
            </div>
            <h1><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h1>
            <p>{{ post.text|linebreaksbr }}</p>
        </div>
    {% endfor %}
{% endblock %}

h1 태그 내부의 a 태그에서 href 어트리뷰트가 변경되었습니다. 장고의 템플릿 태그를 사용하여 클릭 시 이동 url을 제공하고 있습니다. 템플릿 태그에서 가리키는 post_detail은 뷰의 이름입니다. pk는 post.pk 필드를 의미하는데, Post 모델을 생성 할 때 기본 키(PK, Primary Key)를 지정하지 않았기 때문에 장고가 내부적으로 pk 필드를 추가합니다. 이렇게 디폴트로 생성 된 pk는 auto_increament 속성을 지니며, 최초 생성 된 데이터는 1의 값을 지니며 다음 데이터는 1씩 증가한 값을 갖게 됩니다. 결과적으로 링크(a href="...")를 클릭하면 post_detail 뷰를 호출하며, 인자로 pk 값을 전달하게 됩니다.

여기까지 진행하였다면 기존 http://127.0.0.1:8000/ URL으로의 요청이 실패(NoReverseMatch at /)하게 됩니다. 새로 추가된 뷰를 생성하고 url에 연결할 때까지는 페이지 이용이 불가능합니다.

urls.py 파일을 열어 post_detail 뷰를 볼 수 있도록 url을 연결합니다. 뷰 이름은 post_detail이고, views에 정의된 post_detail과 연결됩니다. url은 'http://127.0.0.1:8000/post/<int:pk>/'가 되도록 합니다.

from django.urls import path
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('post/<int:pk>/', views.post_detail, name='post_detail'),
]

url로 전달되는 <int:pk>는 정수형 데이터 타입을 갖고, 파라미터 이름은 pk인 파라미터를 의미합니다. 예를 들어 'http://127.0.0.1:8000/post/5/'으로 요청을 보내면, 해당 뷰에 pk가 5인 Post 모델 객체에 대해서 요청을 보내게 됩니다. 하지만 아직 뷰를 생성하지 않았기 때문에, 역시 페이지는 오류(AttributeError at /)를 출력합니다.

블로그 애플리케이션의 views.py를 열어 다음과 같이 수정합니다. 

from django.shortcuts import render, get_object_or_404
from django.utils import timezone

from .models import Post

def post_list(request):
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    return render(request, 'blog/post_list.html', { 'posts': posts })

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/post_detail.html', { 'post': post })

만약 인자로 전달 된 pk와 일치하는 Post 모델 객체를 찾지 못하는 경우 에러(DoesNotExist at /)가 발생하므로, 이에 대한 예외 처리를 위해서 get_object_or_404 모듈을 import 합니다. post_detail 뷰에서는 입력 파라미터로 pk를 받고 있습니다. 이는 url에서 입력한 <int:pk>와 맵핑됩니다. get_object_or_404는 장고 ORM에서 제공하는 쿼리셋으로, 객체를 get 쿼리셋으로 접근하되 객체를 찾지 못하는 경우 404 에러를 리턴합니다. 해당 함수의 구현 내용(shortcuts.py)을 살펴보면, queryset.model.DoesNotExist 예외를 catch하는 경우 Http404 에러를 리턴하는 코드를 확인 할 수 있습니다.

def get_object_or_404(klass, *args, **kwargs):
    """
    Use get() to return an object, or raise a Http404 exception if the object
    does not exist.

    klass may be a Model, Manager, or QuerySet object. All other passed
    arguments and keyword arguments are used in the get() query.

    Like with QuerySet.get(), MultipleObjectsReturned is raised if more than
    one object is found.
    """
    queryset = _get_queryset(klass)
    if not hasattr(queryset, 'get'):
        klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
        raise ValueError(
            "First argument to get_object_or_404() must be a Model, Manager, "
            "or QuerySet, not '%s'." % klass__name
        )
    try:
        return queryset.get(*args, **kwargs)
    except queryset.model.DoesNotExist:
        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)

뷰가 객체를 정상적으로 get하면 blog/post_detail.html 템플릿 코드를 렌더링합니다. 따라서 해당 템플릿 코드를 추가로 작성해야 합니다. 만약 post_detail.html 문서를 작성하지 않은 상태에서 링크를 누르면 마찬가지로 에러(TemplateDoesNotExist at /)가 발생합니다.

post_list.html 문서와 같은 위치에 post_detail.html 문서를 생성합니다. post_list.html과 마찬가지로 block content를 이용하여 blog/base.html의 내부를 구현합니다.

{% extends 'blog/base.html' %}

{% block content %}
    <div class="post">
        {% if post.published_date %}
            <div class="date">
                {{ post.published_date }}
            </div>
        {% endif %}
        <h1>{{ post.title }}</h1>
        <p>author : {{ post.author }}</p>
        <p>created date : {{ post.created_date }}</p>
        <p>primary key : {{ post.pk }}</p>
        <p>text : {{ post.text|linebreaksbr }}</p>
    </div>
{% endblock %}

block content를 이용하여 공통 html 문서(blog/base.html)에 접근하고, 뷰에서 컨텍스트로 전달한 post 객체를 이용하여 각 필드의 값을 출력하고 있습니다. 이 부분은 이전에 작업한 내용과 크게 다르지 않습니다.

이제 웹 페이지에서 각 포스트의 제목(링크)를 클릭하면 아래와 같은 페이지(blog/post_detail.html)로 이동합니다. 중요한 것은 새로 이동한 페이지의 url이며, 파라미터로 pk 값이 잘 전달 된 것을 확인 할 수 있습니다.