본문 바로가기
Research/Django

[Django] 점프 투 장고 튜토리얼 - 02-4. 조회와 템플릿(template)

by RIEM 2021. 11. 11.

Django 점프 투 장고 정리

작성일 : 2021-11-09

문서버전 : 1.0

 

개요

이 문서는 점프 투 장고 사이트의 장고 튜토리얼 학습 내용을 정리한 내용입니다.

 

레퍼런스

점프 투 장고 https://wikidocs.net/72242

 

2-4. 조회와 템플릿

파이보의 핵심 기능이 될 ‘질문 목록’과 ‘질문 상세 기능’을 구현해보자. ‘질문 목록’은 등록 질문들을 리스트로 보여주는 기능이고, ‘질문 상세’는 게시물 목록 중 한 건의 데이터를 상세하게 조회하는 기능이다.

질문 목록

http://localhost:8000/pybo/ 에 접속하면 아래와 같이 단순 문구만 출력된다.

 

질문 목록이 출력되도록 pybo/views.py 파일의 index 함수를 다음과 같이 변경하자.

 
from django.http import HttpResponse  # 삭제
from django.shortcuts import render
from .models import Question


def index(request):
    """
    pybo 목록 출력
    """
    question_list = Question.objects.order_by('-create_date')
    context = {'question_list': question_list}
    return render(request, 'pybo/question_list.html', context)

 

질문 목록 데이터는 Question.objects.order_by(‘-create_date’)로 얻을 수 있다. Order_by는 조회 결과를 정렬하는 함수다. order_by(‘-create_date’)는 작성일시 역순으로 정렬하라는 의미인데, 여기서 ‘-’하이픈 기호는 역방향을 의미한다. 하이픈이 없으면 순방향이다. 게시물 조회 시 최신글 기준으로 보기 때문에 작성일시의 역순으로 정렬했다.

 

그렇다면 render 함수는 무슨 기능을 할까? Render 함수는 파이썬 데이터를 템플릿에 적용시킨 후에 HTML로 반환하는 함수다. 위의 경우 question_list 데이터를 pybo/question_list.html 파일에 적용하여 HTML을 리턴하는 것이다. 여기서 사용된 pybo/question_list.html과 같은 파일을 장고에서는 Template이라 한다. HTML 파일과 유사하지만 장고에서 사용하는 tag를 사용할 수 있는 HTML 파일이다. 

 

장고는 MVT(Model-View-Template) 패턴을 따르는 웹 프레임워크인데, 그중 하나가 template이다.

(ref : https://dev.to/sm0ke/django-tutorial-mvt-architecture-custom-commands-19nb)

 

템플릿 디렉토리

Render 함수에 사용한 pybo/question_list.html 템플릿 파일을 작성해야 한다. 그전에 템플릿 파일을 저장할 수 있는 디렉토리를 만들어 주자.

> ../projects/mysite/config/settings.py

TEMPLATES = [
  {
      'BACKEND': 'django.template.backends.django.DjangoTemplates',
      'DIRS': [BASE_DIR / 'templates'],
      'APP_DIRS': True,
      'OPTIONS': {
          'context_processors': [
              'django.template.context_processors.debug',
              'django.template.context_processors.request',
              'django.contrib.auth.context_processors.auth',
              'django.contrib.messages.context_processors.messages',
          ],
      },
  },
]

 

DIRS는 템플릿 디렉토리를 여러개 등록할 수 있도록 리스트화되어 있다. Pybo는 BASE_DIR/’templates’ 디렉토리만 등록하자. BASE_DIR / ‘templates’에서 BASE_DIR은 ../projects/mysite이다. 따라서 추가한 디렉터리 전체 경로는 아래와 같다.

 

../projects/mysite/templates

 

위 디렉토리는 아직 생성되지 않은 상태이므로 아래와 같이 만들어주자.

(mysite) c:\projects\mysite>mkdir templates

장고는 DIRS에 설정한 디렉터리 외에도 앱 디렉터리 바로 하위에 있는 templates 디렉터리도 템플릿 디렉터리로 인식한다. 즉, pybo 앱의 경우 다음의 디렉터리를 생성하면 별 다른 설정없이 템플릿 디렉터리로 인식한다.

C:\projects\mysite\pybo\templates

저자는 이 방법을 권장하지 않는다. 왜냐하면 하나의 사이트에서 여러 앱을 사용할 때 여러 앱의 화면을 구성하는 템플릿은 한 디렉터리에 모아 관리하는 것이 좋기 때문이다. 예를 들어 여러 앱이 공통으로 사용하는 공통 템플릿을 어디에 저장해야 할지 생각해 보면 왜 이런 방법을 선호하는지 이해될 것이다.

 

따라서 pybo 시스템은 ../projects/mysite/pybo/templates 보다는 ../projects/mysite/templates/pybo 디렉터리를 사용하자. 그리고 공통으로 사용하는 템플릿은 ../projects/mysite/templates 위치에 저장할 것이다. 정리하면 아래와 같다.

 

모든 앱이 공통으로 사용하는 템플릿 디렉터리 : ../projects/mysite/templates

Pybo 앱만 사용할 템플릿 디렉터리 : ../projects/mysite/templates/pybo

 

템플릿 파일

템플릿 파일을 만들어보자. Render 함수에서 사용한 템플릿 파일명은 ‘pybo/question_list.html’이었다. 따라서 해당 템플릿은 ‘../projects/mysite/templates/pybo/question_list.html’에 저장해야 한다.

> ../projects/mysite/templates/pybo/question_list.html

{% if question_list %}
    <ul>
    {% for question in question_list %}
        <li><a href="/pybo/{{ question.id }}/">{{ question.subject }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>질문이 없습니다.</p>
{% endif %}

 

템플릿을 보면 {% if question_list %} 처럼 {%, %}가 있다. 이 문장을 템플릿 태그라 한다.

 

{% if question_list %} : render 함수로 전달받은 질문 목록 데이터인 question_list가 있을 경우 조건문

 

{% for question in question_list %} : question_list 반복하여 question 대입. 파이썬 반복문 형식이다.

 

{{ question.id }} : for문에 의해 대입된 question 객체의 id 출력

 

{{ question.subject }} : for문에 의해 대입된 question 객체의 제목 출력



탬플릿 태그

장고에 사용되는 탬플릿 태그는 주로 3가지 유형을 쓰는데, 1)분기, 2)반복, 3)객체 출력이다. 하나씩 살펴보자.

 

1.분기

파이썬의 if문과 비슷하다. 마지막에 {% endif %}로 닫아주어야 한다는 것이 핵심이다.

{% if 조건문1 %}
    <p>조건문1에 해당되는 경우</p>
{% elif 조건문2 %}
    <p>조건문2에 해당되는 경우</p>
{% else %}
    <p>조건문1, 2에 모두 해당되지 않는 경우</p>
{% endif %}

2.반복

파이썬의 for문과 비슷하다. 분기문처럼 마지막에는 {% endfor %} 태그로 닫아주어야 한다.

{% for item in list %}
    <p>순서: {{ forloop.counter }} </p>
    <p>{{ item }}</p>
{% endfor %}



템플릿 for문 내에서 forloop 객체를 사용할 수 있는데, 속성은 아래와 같다.

Forloop 속성 설명
forloop.counter 루프내 순서 1부터 표시
forloop.counter() 루프내 순서 0부터 표시
forloop.first 루프의 첫번째 순서인 경우 True
forloop.last 루프 마지막 순서인 경우 True

 

3.객체 출력

‘{{ 객체명 }}’으로 객체를 출력한다. 예를 들어 ‘{{ item }}’등이 있다. 파이썬과 마찬가지로 객체 속성은 객체 뒤에 ‘.’을 붙여 사용한다. ‘{{ 객체.속성 }}’, {{question.id}}, {{question.subject}}와 같은 양식을 가졌다. 자세한 내용은 아래를 확인하면 된다. https://docs.djangoproject.com/en/3.0/topics/templates/

 

테스트

서버를 재부팅후 localhost:8000/pybo/ 사이트를 재접속하면 아래와 같은 화면이 뜬다.

 

질문 상세

위 질문 한개를 클릭하면 아래와 같이 오류가 표시된다.

아직 http://localhost:8000/pybo/2 와 같은 페이지에 대한 URL 매핑이 되지 않았기 때문이다.

 

Urls.py

http://localhost:8000/pybo/2/ URL의 의도는 id값이 2인 Question을 상세 조회하기 위함이다. URL 동작을 위해 pybo/urls.py 파일을 다음과 같이 수정하자.

> pybo/urls.py

 
from django.urls import path

from . import views

urlpatterns = [
    path('', views.index),
    path('<int:question_id>/', views.detail),
]

 

path(‘<int:question_id>/’, views.detail)라는 URL 매핑을 추가했다. 이제 http://localhost:8000/pybo/2 페이지가 요청되면 위의 매핑 룰에 의해 http://localhost:8000/pybo/<int:question_id>/ 가 적용되어 question_id에 2가 저장되고 views.detail 함수가 실행된다. 

 

views.py

위 URL 매핑에 의해 views.detail 함수를 생성해야 한다. 다음처럼 pybo/views.py 파일에 detail 함수를 추가하자.

> ../projects/mysite/pybo/views.py

위 URL 매핑에 대한 Views.detail 함수를 생성해야 한다.

...

def detail(request, question_id):
    """
    pybo 내용 출력
    """
    question = Question.objects.get(id=question_id)
    context = {'question': question}
    return render(request, 'pybo/question_detail.html', context)

...

Index 함수와 유사한데, detail 함수 호출 시 전달되는 매개변수가 request 외 question_id가 추가된 점이다. 매개변수 question_id에는 URL 매핑 시 저장된 question_id가 전달된다. 

 

즉, http://localhost:8000/pybo/2/ 페이지가 요청되면 최종적으로 detail 함수의 매개변수 question_id에는 2라는 값이 전달된다.

 

구조를 보면 아래와 같다. 

 

Question_detail.html

pybo/question_detail.html 템플릿을 다음과 같이 작성해주자.

> projects/mysite/pybo/templates/pybo/question_detail.html

<h1>{{ question.subject }}</h1>

<div>
    {{ question.content }}
</div>

{{ question.subject }}과 {{ question.content }}의 question은 Detail() 함수에서 context로 넘겨온 데이터다. 

 

http://localhost:8000/pybo/2/ 페이지를 재요청해보자.

질문 상세 페이지 제작 성공!

 

오류 페이지

이번에는 http://localhost:8000/pybo/30/ 페이지를 요청해보자.

아마도 DoesNotExist 오류가 발생할 것이다. 이 오류는 전달된 question_id가 30이기 때문에 Question.object.get(id=30)이 호출되어 발생한 오류다. 이 때 브라우저에 전달되는 오류코드는 500이다. 응답코드의 유형별 설명은 아래와 같다.

 

HTTP 주요 응답코드의 종류

오류코드 설명
200 성공(OK)
500 서버오류(Internal Server Error)
404 서버가 요청한 페이지(Resource) 찾을 수 없음(Not Found)

 

> ../projects/mysite/pybo/views.py

http://localhost:8000/pybo/30/ 처럼 없는 데이터를 요청할 경우 404페이지를 출력하는 기능을 위해 detail 함수를 수정해보자.

from django.shortcuts import render, get_object_or_404
from .models import Question

(... 생략 ...)

def detail(request, question_id):
    """
    pybo 내용 출력
    """
    question = get_object_or_404(Question, pk=question_id)
    context = {'question': question}
    return render(request, 'pybo/question_detail.html', context)

 

Question.objects.get(id=question_id)를 -> get_object_or_404(Question, pk=question_id)로 바꾸어 주었다. pk는 Question 모델의 기본키(Primary Key)인 id다.

 

위와 같이 수정 후 http://localhost:8000/pybo/30/ 페이지를 요청해보자. 

 

이전에는 아래와 같은 페이지가 나왔지만..

 

현재는 404에러 페이지가 나온다.

 

장고 제네릭뷰 소개

장고에는 제네릭뷰(Generic View)라는 것이 있다. 목록 조회나 상세 조회 같은 특정 패턴이 있는 뷰 작성할 경우 비슷한 내용이 반복 입력되기 때문에 이를 패턴화시켜 간소화시킨 것이 제네릭뷰다.

 

이 튜토리얼에서는 제네릭뷰를 다루지 않는다. 사용이 편리하지만 내부적으로 어떻게 동작이 되는지 파악하기가 어렵기 때문이다.

 

제네릭뷰에 대해 잠깐 살펴보자.

Views.py 작성 내용을 제네릭뷰로 변경하면 아래와 같이 간략히 적을 수 있다.

 

기존

def index(request):
    question_list = Question.objects.order_by('-create_date')
    context = {'question_list': question_list}
    return render(request, 'pybo/question_list.html', context)

def detail(request, question_id):

    question = get_object_or_404(Question, pk=question_id)
    context = {'question': question}
    return render(request, 'pybo/question_detail.html', context)

 

제네릭뷰

class IndexView(generic.ListView):
def get_queryset(self):
Return Question.objects.order_by('-create_date')

class DetailView(generic.DetailView):
model = Question

 

IndexView 클래스가 index함수를 대체하고, DetailView클래스가 detail함수를 대체했다. IndexView는 템플릿 명이 명시적으로 지정되지 않은 경우에는 디폴트로 모델명_list.html을 템플릿으로 사용한다. 마찬가지로 DetailView는 모델명_detail.html을 디폴트 템플릿명으로 사용한다.

 

pybo/urls.py 파일은 아래와 같이 변경된다.

 

기존

from django.urls import path

from . import views

urlpatterns = [
  path('', views.index),
  path('<int:question_id>/', views.detail),
]

 

제네릭뷰

From django.urls import path

From . import views

App_name = 'pybo'
Urlpatterns = [
path('', views.IndexView.as_view()),
path('<int:pk>/', views.DetailView.as_view()),
]

 

댓글