Django의 디자인 패턴은 MTV(Model - Template - View)
패턴입니다. Model
은 DB에 저장되는 데이터, Template
은 유저에게 보여지는 화면, View
는 요청에 따라 적절한 로직을 수행하여 결과를 템플릿으로 렌더링하여 응답하는 것을 의미합니다.
오늘은 지금까지 작성했던 테스트코드를 바탕으로 아래와 같은 View
를 작성하고, 사용자에게 보여질 Template
연동까지 해보도록 하겠습니다.
먼저 도서 색인 페이지를 추가해보도록 하겠습니다.
test_app/views.py 파일 내의 index()
뷰를 다음과 같이 변경합니다.
from django.http import HttpResponse
def index(request):
latest_book_list = TestBook.objects.order_by("-reg_datetime")[:5]
output = ", ".join([b.title for b in latest_book_list])
return HttpResponse(output)
코드를 간략히 설명하면 TestBook
테이블 오브젝트로 부터 reg_datetime 내림차순으로 정렬된 데이터(오름차순으로 정렬하려면 '-' 제거) 중 상위 5개만 가져와 latest_book_list
변수에 담아 응답을 내려주고 있습니다.
다음으로 test_app/urls.py 파일을 수정하여 각 페이지로 접속할 수 있는 url를 매핑합니다.
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
path("book/<int:book_id>/", views.book, name="book"),
path("book/category/<int:category_id>/", views.bookCategory, name="category")
]
이제 템플릿을 연동하여 사용자에게 보여줄 화면을 그리는 작업을 진행하겠습니다.
먼저 index()
뷰 로직을 다음과 같이 수정합니다.
from django.http import HttpResponse
def index(request):
latest_book_list = TestBook.objects.order_by("-reg_datetime")[:5]
template = loader.get_template("index.html")
context = {"latest_book_list" : latest_book_list,}
return HttpResponse(template.render(context, request))
기존 로직으로 latest_book_list
를 가져와서 context
변수에 담고 index.html 템플릿을 불러와서 동적으로 index.html에 context
데이터를 넣어 화면을 그려서 응답을 주도록 코드를 작성했습니다.
다음으로 템플릿으로 사용할 index.html 파일을 만들어봅시다.
기본적으로 django 템플릿은 /templates 디렉토리에서 스캔하게 되므로 test_app/templates 폴더를 하나 생성합니다.
그리고 나서 도서 색인페이지를 위한 index.html 파일을 아래 코드와 같이 생성합니다.
<h3>신간도서</h3>
{% if latest_book_list %}
<ul>
{% for book in latest_book_list %}
<li><a href="test/book/{{ book.id }}/">{{ book.title }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No books are available.</p>
{% endif %}
중괄호로 시작하는 부분들이 DjangoTemplates
언어로 작성된 코드들이며 HTML 마크업 사이에 끼어서 동적으로 데이터를 표시하여 유저에게 보여줄 수 있도록 합니다.
DjangoTemplates이란?
Django 웹 프레임워크에서 사용되는 템플릿 언어입니다. Template은 HTML과 Python 코드를 결합하여 동적으로 데이터를 표시하고 조작할 수 있도록 도와줍니다.
settings.py에서TEMPLATES
항목을 통해 템플릿 엔진을 설정할 수 있으며, 기본값으로DjangoTemplates
를 사용하도록 설정되어 있습니다.
템플릿에 context
를 채워넣어 표현한 결과를 HttpResponse
객체와 함께 돌려주는 구문은 자주 쓰는 용법입니다. Django는 이러한 작업을 쉽게 작성 할 수 있도록 단축 기능(shortcuts)을 제공합니다. index()
뷰를 단축 기능으로 작성하면 다음과 같습니다.
from django.shortcuts import render
def index(request):
latest_book_list = TestBook.objects.order_by("-reg_datetime")[:5]
context = {"latest_book_list" : latest_book_list,}
return render(request, "index.html", context)
모든 뷰에 적용한다면 더 이상 loader
와 HttpResponse
를 별도로 임포트하지 않아도 되기 때문에 앞으로 나올 코드들은 shortcuts
를 적용하여 코드를 작성하도록 하겠습니다.
이제 코드를 실행해서 인덱스 페이지에 접근하면 다음과 같은 화면이 노출됩니다. 책의 목록은 관리자페이지에서 임의로 생성하였으므로, 실습하실 때 여러개 임의로 추가해서 확인하면 됩니다.
이제 도서 상세 페이지와 도서 분류페이지도 작성하도록 하겠습니다. 코드를 작성하면서 존재하지 않는 상세 페이지나 분류페이지를 호출했을 때 에러를 노출하도록 해보겠습니다.
먼저 /test_app/views.py 파일에 다음 함수들을 추가합니다.
from django.http import Http404
//...
def book(request, book_id):
try:
book = TestBook.objects.get(pk=book_id)
except TestBook.DoesNotExist:
raise Http404("The Book does not exist")
return render(request, "book/detail.html", {"book": book})
def bookCategory(request, category_id):
try:
category = TestBookCategory.objects.get(pk=category_id)
books = TestBook.objects.filter(category_id=category_id)
except TestBookCategory.DoesNotExist:
raise Http404("The Category does not exist")
return render(request, template_name="category/detail.html", context={"category": category, "books":books})
Django는 Python의 try-except
문을 이용하여 예외를 잡아낼 수 있으며 이를 이용하여 해당 테이블 오브젝트에 요청된 데이터가 없을 시에 Http404에러를 던지도록 코드를 작성했습니다.
404에러를 반환하는 로직도 render 로직과 마찬가지로 Shortcut을 제공하여 간결하게 표현할 수 있습니다.
from django.shortcuts import get_object_or_404, render
//...
def book(request, book_id):
book = get_object_or_404(TestBook, pk=book_id)
return render(request, "book/detail.html", {"book": book})
def bookCategory(request, category_id):
category = get_object_or_404(TestBookCategory, pk=category_id)
books = TestBook.objects.filter(category_id=category_id)
return render(request, template_name="category/detail.html", context={"category": category, "books":books})
도서 상세 페이지와 도서 분류 페이지의 템플릿파일을 /test_app/templates/book/detail.html 과 /test_app/templates/category/detail.html에 각각 생성한 후 템플릿을 다음과 같이 작성합니다.
<h3>{{ book.title }}</h3>
<table border="1">
<th>도서번호</th>
<th>카테고리</th>
<th>가격</th>
<th>등록일자</th>
<tr>
<td>{{ book.id }}</td>
<td>{{ book.category.categoryName }}</td>
<td>{{ book.price }}</td>
<td>{{ book.reg_datetime }}</td>
</tr>
</table>
<h3>{{ category.categoryName }}</h3>
<table border="1">
<th>도서명</th>
{% for book in books %}
<tr>
<td>{{ book.title}}</td>
</tr>
{% endfor %}
</table>
/book/detail.html과 같이 템플릿 시스템에서 각 변수의 속성에 접근하기 위해서는 점-탐색(dot-lookup)문법을 사용해서 접근합니다. django는 먼저 book
객체에 대해 사전형으로 먼저 탐색을 시도하고 실패하면 속성값으로 탐색을 시도합니다. 속성으로도 탐색을 실패하면 리스트의 인덱스 탐색을 시도합니다.
/category/detail.html과 같이 {% for %}
을 이용하여 반복분도 사용할 수 있습니다.
앞서 /test/index.html에서 상세 페이지로 접근하는 링크가 하드코딩되어 있었습니다. 하드코딩된 URL은 코드가 강한 의존성을 갖게 되어 수많은 템플릿을 가질수록 수정하기 힘들어집니다.
이 부분을 해소하기 위해 템플릿에서 하드코딩 된 url을 제거해봅시다.
/test_app/urls.py에서 path() 함수에서 인수의 이름을 정의했으므로, {% url %} template 태그를 사용하여 url 설정에 정의된 특정한 URL 경로들의 의존성을 제거할 수 있습니다.
/test_app/templates/index.html
<h3>신간도서</h3>
{% if latest_book_list %}
<ul>
{% for book in latest_book_list %}
<li><a href="{% url 'book' book.id %}">{{ book.title }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No books are available.</p>
{% endif %}
실제로 Django 프로젝트는 앱이 여러개가 될 수 있습니다. 그렇게 되면 이 앱들간의 URL을 구분하는 방법이 필요해지게 됩니다.
예를 들어 우리가 만든 test_app의 index
라는 이름의 url이 있지만 다른 앱에서도 index
라는 url이 있을 경우 서로 URL을 구분할 수 있어야 합니다.
즉, Django가 {% url %}
템플릿태그를 사용할 때, 어떤 앱의 뷰에서 URL을 생성할지 알 수 있어야 합니다.
이것은 URLconf
에 네임스페이스(namespace)을 추가하여 해결할 수 있습니다.
test_app/urls.py파일에 다음과 같이 app_name
을 추가하여 어플리케이션의 네임스페이스를 설정할 수 있습니다.
test_app/urls.py
app_name = "bookstore"
urlpatterns = [
path("", views.index, name="index"),
path("book/<int:book_id>/", views.book, name="book"),
path("book/category/<int:category_id>/", views.bookCategory, name="category")
]
test_app/templates/index.html
<h3>신간도서</h3>
{% if latest_book_list %}
<ul>
{% for book in latest_book_list %}
<li><a href="{% url 'bookstore:book' book.id %}">{{ book.title }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No books are available.</p>
{% endif %}
잘봤습니다.