Django : python으로 웹서비스 개발하기(9) - view 작성 및 template 연동하기

harry jang·2023년 7월 16일
0

Django

목록 보기
9/12
post-thumbnail

Django의 디자인 패턴은 MTV(Model - Template - View)패턴입니다. Model은 DB에 저장되는 데이터, Template은 유저에게 보여지는 화면, View는 요청에 따라 적절한 로직을 수행하여 결과를 템플릿으로 렌더링하여 응답하는 것을 의미합니다.

오늘은 지금까지 작성했던 테스트코드를 바탕으로 아래와 같은 View를 작성하고, 사용자에게 보여질 Template연동까지 해보도록 하겠습니다.

  • 도서 색인 페이지 : 최근에 등록된 도서목록을 최근 5개까지 보여줍니다.
  • 도서 상세 페이지 : 도서의 상세정보를 보여 줍니다.
  • 도서 분류 페이지 : 카테고리별로 대상 카테고리에 속하는 도서 목록을 보여줍니다.

View 적용하기

먼저 도서 색인 페이지를 추가해보도록 하겠습니다.
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")
]

Template 연동하기

이제 템플릿을 연동하여 사용자에게 보여줄 화면을 그리는 작업을 진행하겠습니다.

먼저 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.htmlcontext 데이터를 넣어 화면을 그려서 응답을 주도록 코드를 작성했습니다.

다음으로 템플릿으로 사용할 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를 사용하도록 설정되어 있습니다.

Shortcut 모듈을 활용하여 코드 간결화하기

템플릿에 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)

모든 뷰에 적용한다면 더 이상 loaderHttpResponse를 별도로 임포트하지 않아도 되기 때문에 앞으로 나올 코드들은 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에러를 던지도록 코드를 작성했습니다.

Shortcut 모듈을 활용하여 코드 간결화하기

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에 각각 생성한 후 템플릿을 다음과 같이 작성합니다.

/book/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>

/category/detail.html

<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 %}을 이용하여 반복분도 사용할 수 있습니다.


템플릿에서 하드코딩된 url 제거하기

앞서 /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 %}

URL의 네임스페이스 지정하기

실제로 Django 프로젝트는 앱이 여러개가 될 수 있습니다. 그렇게 되면 이 앱들간의 URL을 구분하는 방법이 필요해지게 됩니다.

예를 들어 우리가 만든 test_appindex라는 이름의 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 %}
profile
software engineer

2개의 댓글

comment-user-thumbnail
2023년 7월 16일

잘봤습니다.

답글 달기
comment-user-thumbnail
2023년 7월 17일

저도 개발자인데 같이 교류 많이 해봐요 ㅎㅎ! 서로 화이팅합시다!

답글 달기