본문 바로가기
DoItDJango

조회와 템플릿

by 자동매매 2023. 4. 13.

 

  • 질문 목록 - 등록한 질문들을 게시물 목록으로 조회하는 기능
  • 질문 상세 - 게시물 목록 중 한 건의 데이터를 상세하게 조회하는 기능
 

질문 목록

 

다음 페이지 요청시 등록한 질문들을 조회할 수 있도록 구현해 보자.

 

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 템플릿 파일을 작성해야 한다.

1. 템플릿 파일을 저장할 디렉터리를 먼저 만들어야 한다.

2. 템플릿을 저장할 디렉터리는 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' 디렉터리 한 개만 등록한다. ( c:\projects\mysite\templates )

 

사용할 템플릿 디렉터

  • 모든 앱이 공통으로 사용할 템플릿 디렉터리 - projects/mysite/templates
  • pybo 앱이 사용할 템플릿 디렉터리 - projects/mysite/templates/pybo
  • common 앱이 사용할 템플릿 디렉터리 - projects/mysite/templates/common

 

템플릿 파일

 

이제 템플릿 파일을 만들어 보자. render 함수에서 사용한 템플릿 파일명은 다음과 같았다.

 

[파일명: 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 객체의 제목을 출력

 

Test

http://localhost:8000/pybo/ 페이지를 요청하면 다음과 같이 변경된 화면을 볼 수 있을 것이다.

 

질문 상세

urls.py

질문 목록 화면에서 링크를 클릭하여 요청한 질문 상세 URL은 다음과 같다.

http://localhost:8000/pybo/2/

 

이 URL의 의도는 다음과 같다.

id 값이 2인 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/2/ 페이지가 요청되면 여기에 등록한 매핑 룰에 의해 http://localhost:8000/pybo/<int:question_id>/ 가 적용되어 question_id 에 2가 저장되고 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/2/ 페이지가 요청되면 매개변수 question_id에 2가 세팅되어 detail 함수가 실행된다.

 

question_detail.html

[파일명: projects/mysite/templates/pybo/question_detail.html]

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

 

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

 

 

오류 페이지

 

이번에는 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 서버가 요청한 페이지(Resource)를 찾을 수 없음 (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)

 

다시 http://localhost:8000/pybo/30/ 페이지를 요청해 보자. 이번에는 500 대신 404 오류 페이지가 출력되는 것을 확인할 수 있다.

 

 

템플릿 태그

 

장고에서 사용하는 템플릿 태그는 다음 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. 객체 출력

{{ 객체 }}

 

예) {{ item }}

객체에 속성이 있는 경우는 파이썬과 동일한 방법으로 도트(.) 문자를 이용하여 표시하면 된다.

 

장고 제네릭뷰 소개

 

이 책은 제네릭뷰를 사용하지 않는다. 왜냐하면 제네릭뷰는 매우 편리하지만 내부적으로 어떻게 동작하는지 이해하기 쉽지 않아 장고를 배우는 지금 이 시점에서는 오히려 혼란을 초래할 수 있기 때문이다. 여기 잠시 소개하였으니 눈으로만 살펴보고 현재 작성중인 코드를 수정하지는 말자.

장고에는 제네릭뷰(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()),
]

 

이렇듯 모델의 목록 조회나 상세 조회는 제네릭뷰를 사용하는것이 매우 간편함을 알 수 있다. 단, 제네릭 뷰를 사용할 경우에는 복잡한 케이스에서 더 어렵게 작성되는 경우가 종종 있으니 주의하여 사용해야 한다.

'DoItDJango' 카테고리의 다른 글

template 사용 - class기반 뷰어 생성  (0) 2023.04.13
데이터 저장  (0) 2023.04.13
URL 별칭  (0) 2023.04.13
superuser  (1) 2023.04.13
Model  (0) 2023.04.12

댓글