이번 포스팅에서는 장고의 기본 요소 중 하나인 조회와 템플릿에 대하여 공부를 해보고자 한다.
템플릿(Template)
Django에서의 View가 다른 MVC Framework에서의 Controller와 유사한 역할을 한다면, Django에서의 템플릿은 MVC Framework에서의 View와 비슷한 역할을 한다.
템플릿은 View로부터 전달된 데이터를 템플릿에 적용하여 Dynamic 한 웹페이지를 만드는 데 사용된다.
템플릿은 HTML 파일로서 Django App 폴더 밑에 "templates" 라는 서브 폴더를 만들고 그 안에 템플릿 파일(*.html)을 생성한다.
이는 단일 App이거나 동일 템플릿명이 없는 경우 사용할 수 있다.
하지만, Django 개발 가이드라인은 "App폴더/templates/App명/템플릿파일" 처럼, 각 App 폴더 밑에 templates 서브 폴더를 만들고 다시 그 안에 App명을 사용하여 서브 폴더를 만든 후 템플릿 파일을 그 안에 넣기를 권장한다. (예: /home/templates/home/index.html )
질문 목록
다음 페이지 요청 시 등록한 질문들을 조회할 수 있도록 구현할 경우, http://localhost:8000/pybo/ 를 요청하면 "안녕하세요 pybo에 오신 것을 환영합니다."라는 문구가 출력된다.
질문 목록이 출력되도록 하려면 pybo/views.py 파일의 index 함수를 변경해야 한다.
[파일명: projects/mysite/pybo/views.py]
from django.http import HttpResponse # 삭제
from django.shortcuts import render
from .models import Question
def index(request):
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 함수는 파이썬 데이터를 템플릿에 적용하여 HTML로 반환하는 함수이다.
즉, 위에서 사용한 render 함수는 질문 목록으로 조회한 question_list 데이터를 pybo/question_list.html 파일에 적용하여 HTML을 생성한 후 리턴한다.
여기서 사용된 pybo/question_list.html과 같은 파일을 템플릿(Template)이라고 부른다.
템플릿 파일은 HTML 파일과 비슷하지만 파이썬 데이터를 읽어서 사용할 수 있는 HTML 파일이다.
템플릿 디렉터리
render 함수에서 사용한 pybo/question_list.html 템플릿 파일을 작성해야 한다.
템플릿 파일을 작성하기 전에 템플릿 파일을 저장할 디렉터리를 먼저 만들어야 한다.
템플릿을 저장할 디렉터리는 config/settings.py 파일의 TEMPLATES 항목에 설정해야 한다.
[파일명: 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는 템플릿 디렉터리를 여러 개 등록할 수 있도록 리스트로 되어 있다.
파이보는 BASE_DIR / 'templates' 디렉터리 한 개만 등록한다.
BASE_DIR / 'templates'에서 BASE_DIR은 c:\projects\mysite이므로 추가한 디렉터리의 전체 경로는 c:\projects\mysite\templates 이다.
이 디렉터리는 없으므로 따로 생성해야 한다.
(mysite) c:\projects\mysite> mkdir templates
장고는 DIRS에 설정한 디렉터리 외에도 앱 디렉터리 바로 하위에 있는 templates 디렉터리도 템플릿 디렉터리로 인식한다.
즉, 앱의 경우 다음의 디렉터리를 생성하면 별다른 설정 없이 템플릿 디렉터리로 인식한다.
projects/mysite/pybo/templates
앱(App) 디렉터리 하위에 템플릿 디렉터리를 두는 방법은 권장하지 않는다.
하나의 웹 사이트에서 여러 앱을 사용할 때 여러 앱의 화면을 구성하는 템플릿은 한 디렉터리에 모아 관리하는 편이 여러모로 좋기 때문이다.
예를 들어 여러 앱이 공통으로 사용하는 공통 템플릿을 어디에 저장해야 할지 생각해 보면 왜 이런 방법을 선호하는지 쉽게 이해할 수 있다.
따라서 앱은 템플릿 디렉터리로 projects/mysite/pybo/templates이 아닌 /projects/mysite/templates/pybo 디렉터리를 사용한다.
그리고 공통으로 사용하는 템플릿은 projects/mysite/templates 위치에 저장한다.
- 모든 앱이 공통으로 사용할 템플릿 디렉터리 - projects/mysite/templates
- 앱이 사용할 템플릿 디렉터리 - projects/mysite/templates/pybo
- common 앱이 사용할 템플릿 디렉터리 - projects/mysite/templates/common
템플릿 파일
render 함수에서 사용한 템플릿 파일명은 pybo/question_list.html 이다.
따라서 question_list.html 파일은 projects/mysite/templates/pybo/question_list.html 경로에 저장해야 한다.
그리고 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 %} 처럼 {% 와 %} 로 둘러싸인 문장들을 볼 수 있는데 이러한 것들을 템플릿 태그라고 한다.
[question_list.html에 사용된 템플릿 태그]
태그 | 설명 |
{% if question_list %} | question_list가 있다면 |
{% for question in question_list %} | question_list를 순회하며 순차적으로 하나씩 question에 대입 |
{{ question.id }} | for문에 의해 대입된 question 객체의 id 번호를 출력 |
{{ question.subject }} | for문에 의해 대입된 question 객체의 제목을 출력 |
템플릿에서 사용한 question_list는 render 함수로 전달한 "질문 목록" 데이터이다.
템플릿 태그
장고에서 사용하는 템플릿 태그는 다음 3가지 유형만 알면 된다.
1. 분기
분기문 태그의 사용법
{% if 조건문1 %}
<p>조건문1에 해당되는 경우</p>
{% elif 조건문2 %}
<p>조건문2에 해당되는 경우</p>
{% else %}
<p>조건문1, 2에 모두 해당되지 않는 경우</p>
{% endif %}
파이썬의 if 문과 다를 바가 없다.
다만 항상 {% endif %} 태그로 닫아주어야 한다.
2. 반복
반복문 태그의 사용법
{% for item in list %}
<p>순서: {{ forloop.counter }} </p>
<p>{{ item }}</p>
{% endfor %}
파이썬의 for 문과 다를 게 없다.
마지막은 항상 {% endfor %} 태그로 닫아주어야 한다.
그리고 템플릿 for문 안에서는 다음과 같은 forloop 객체를 사용할 수 있다.
forloop 속성 | 설명 |
forloop.counter | 루프내의 순서로 1부터 표시 |
forloop.counter0 | 루프내의 순서로 0부터 표시 |
forloop.first | 루프의 첫번째 순서인 경우 True |
forloop.last | 루프의 마지막 순서인 경우 True |
3. 객체 출력
객체를 출력하기 위한 태그의 사용법
{{ 객체 }}
ex) {{ item }}
객체에 속성이 있는 경우는 파이썬과 동일한 방법으로 도트(.) 문자를 이용하여 표시하면 된다.
{{ 객체.속성 }}
ex) {{question.id}}, {{question.subject}}
테스트
여기까지 수정하고 http://localhost:8000/pybo/ 페이지를 요청하면 다음과 같은 화면이 출력된다.
이전에 장고 셸과 어드민에서 등록한 질문 2건이 조회되었다.
템플릿 디렉터리 추가 후에는 로컬 서버를 재시작해야 한다.
재 시작하지 않으면 템플릿을 찾을 수 없다는 오류가 표시된다.
질문 상세
질문 목록 중 한 개를 선택하여 클릭하면 다음과 같은 오류가 표시된다.
http://localhost:8000/pybo/2/와 같은 페이지에 대한 URL매핑이 아직 없기 때문이다.
urls.py
질문 목록 화면에서 링크를 클릭하여 요청한 질문 상세 URL은 http://localhost:8000/pybo/3/ 이다.
이 URL의 의도는 id 값이 3인 Question을 상세 조회하는 것이다.
이 URL이 동작할 수 있도록 pybo/urls.py 파일을 다음과 같이 수정한다.
[파일명: projects/mysite/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/3/ 페이지가 요청되면 여기에 등록한 매핑 룰에 의해해 http://localhost:8000/pybo/<int:question_id>/ 가 적용되어 question_id 에 3가 저장되고 views.detail 함수도 실행된다.
<int:question_id> 에서 int는 숫자가 매핑됨을 의미한다.
views.py
이제 위 URL 매핑 규칙에 의해 실행되는 views.detail 함수를 만들어야 한다.
pybo/views.py 파일에 detail 함수를 추가한다.
[파일명: projects/mysite/pybo/views.py]
(... 생략 ...)
def detail(request, question_id):
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/3/ 페이지가 요청되면 매개변수 question_id에 3가 세팅되어 detail 함수가 실행된다.
question_detail.html
detail 함수에서 사용할 pybo/question_detail.html 템플릿을 작성한다.
[파일명: projects/mysite/templates/pybo/question_detail.html]
<h1>{{ question.subject }}</h1>
<div>
{{ question.content }}
</div>
{{ question.subject }}과 {{ question.content }}의 question은 detail 함수에서 템플릿에 context 변수로 전달한 Question 모델 객체이다.
다시 http://localhost:8000/pybo/3/ 페이지를 요청하면 질문 상세 기능이 만들어져 있다.
오류 페이지
http://localhost:8000/pybo/30/ 페이지를 요청하면 DoesNotExist 오류가 발생한다.
이 오류는 전달된 question_id가 30이기 때문에 Question.object.get(id=30)이 호출되어 발생한 오류이다.
이때 브라우저에 전달되는 오류코드는 500이다.
이렇게 없는 데이터를 요청할 경우 500 오류 페이지보다는 "Not Found (404)" 페이지를 리턴하는 것이 바람직하다.
[HTTP 주요 응답 코드의 종류]
오류코드 | 설명 |
200 | 성공 (OK) |
500 | 서버오류 (Internal Server Error ) |
404 | 서버가 요청한 페이지를 찾을 수 없음 (Not Found) |
http://localhost:8000/pybo/30/ 처럼 없는 데이터를 요청할 경우 500 페이지 대신 404 페이지를 출력하도록 detail 함수를 수정한다.
[파일명: projects\mysite\pybo\views.py]
from django.shortcuts import render, get_object_or_404
from .models import Question
(... 생략 ...)
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)
Question.objects.get(id=question_id)를 get_object_or_404(Question, pk=question_id)로 바꾸었다.
여기서 사용한 pk는 Question 모델의 기본키(Primary Key)에 해당하는 값을 의미한다.
이렇게 수정하고 다시 http://localhost:8000/pybo/30/ 페이지를 요청하면 500 대신 404 오류 페이지가 출력된다.
장고 제네릭 뷰(Generic View)
목록 조회나 상세 조회 같은 특정한 패턴이 있는 뷰를 작성할 때 늘 비슷한 내용을 입력하기 때문에 이것을 패턴화 하여 간략하게 만든 것이 바로 제네릭 뷰이다.
만약 views.py에 작성한 index나 detail함수를 제네릭 뷰로 변경하면 다음처럼 간략하게 작성할 수 있다.
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
app_name = 'pybo'
urlpatterns = [
path('', views.IndexView.as_view()),
path('<int:pk>/', views.DetailView.as_view()),
]
모델의 목록 조회나 상세 조회는 제네릭 뷰를 사용하는 것이 매우 간편하다.
제네릭 뷰는 매우 편리하지만 내부적으로 어떻게 동작하는지 이해하기 쉽지 않고, 제네릭 뷰를 사용할 경우에는 복잡한 케이스에서 더 어렵게 작성되는 경우가 종종 있으니 주의하여 사용해야 한다.
'Framework > Django' 카테고리의 다른 글
[Django] 장고(Django) 기본요소 - 스태틱(static) (0) | 2022.11.29 |
---|---|
[Django] 장고(Django) 기본요소 - URL 별칭 (0) | 2022.11.27 |
[Django] 장고(Django) 기본요소 - 장고 관리자(Admin) (0) | 2022.11.25 |
[Django] 장고(Django) 기본요소 - 모델(Model) (4) | 2022.11.24 |
[Django] 장고(Django) 기본요소 - URL과 View (2) | 2022.11.23 |