TIL | Django | 공식 문서 정리 | 첫번째 앱 PART.3

이도운·2022년 1월 7일
0

TIL

목록 보기
34/73
post-thumbnail

개요

우리가 만드는 poll 어플리케이션에서 다음과 같은 네개의 view를 만들어 보겠습니다.

  • 색인 페이지 - 최근 질문들을 표시합니다
  • 세부 페이지 - 질문 내용과 투표할 수 있는 서식을 표시합니다
  • 결과 페이지 - 특정 질문에 대한 결과를 표시합니다
  • 투표 기능 - 특정 질문에 대해 선턱을 할 수 있는 투표 기능을 제공합니다

URL로부터 뷰를 얻기 위해 Django는 URLconf라는 것을 이용합니다. URLconf는 URL 패턴을 뷰에 연결합니다.

이 튜토리얼은 URLconf를 사용하는 기초 지식을 제공하며, 좀 더 자세한 정보는 URL dispatcher를 참조하세요.


뷰 추가하기

이제 polls/views.py에 뷰를 추가해 봅시다. 이 뷰들은 인수를 받기 때문에 조금 모양이 다릅니다.

# polls/views.py
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

다음의 path() 호출을 추가하여 이러한 새로운 뷰를 polls.urls 모듈로 연결하세요.

# polls/urls.py
urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

브라우저에 /polls/34/를 입력해 보세요. 이 주소에 접속하면 detail() 함수를 호출하여 URL에 입력한 ID를 출력할 것입니다.

사용자가 웹사이트 페이즈를 요청할 때, 예를 들어 /polls/34/를 요청했다고 하면, Django는 mysite.urls 파이썬 모듈을 불러오게 됩니다. ROOT_URLCONF 설정에 의해 해당 모듈을 바라보도록 지정되어 있기 때문입니다. mysite.urls에서 urlpatterns라는 변수를 찾고, 순서대로 패턴을 따라갑니다. 'polls/'를 찾은 후엔 일치하는 텍스트를 버리고, 남은 텍스트인 "34/"를 polls.urls URLconf로 전달하여 남은 처리를 진행합니다. 거기에 '<int:question_id>/'와 일치하여 결과적으로 detail() 뷰 함수가 호출됩니다.

아래와 같은 형식으로 detail() 뷰 함수가 호출됩니다.

detail(request=<HttpRequest object>, question_id=34)

뷰가 실제로 뭔가를 하도록 만들기

각 뷰는 두 가지 중 하나를 하도록 되어 있습니다. 요청된 페이지의 내용이 담긴 HttpResponse 객체를 반환하더나, 혹은 Http404 같은 예외를 발생하게 해야합니다.

당신이 작성한 뷰는 데이터베이스의 레코드를 읽어올 수도 있습니다. 또한 뷰는 Django나 Python에서 서드파티로 제공되는 템플릿 시스템을 사용할 수도 있습니다. 뷰는 PDF를 생성하거나, XML을 출력하거나, 실시간으로 ZIP 파일을 만들 수도 있습니다. 뷰는 당신이 원하는 무엇이든, Python의 어떤 라이브러리도 사용할 수 있습니다.

Django에 필요한 것은 HttpResponse 객체 혹ㅇ은 예외입니다.

왜냐면, 그렇게 다루는게 편리하기 때문입니다. 튜토리얼 2장의 예제에서 다룬 Django 자체 데이터베이스 API를 사용해봅시다. 새로운 index() 뷰 하나를 호출했을 때, 시스템에 저장된 최소한 5개의 투표 질문이 콤마로 분리되어, 발행일에 따라 출력됩니다.

# polls/views.py
from django.http import HttpResponse

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

여기에는 몇가지 문제가 있습니다. 뷰에서 페이지 디자인이 하드코딩 되어 있다고 합시다. 만약 페이지가 보여지는 방싱을 바꾸고 싶다면, 이 Python 코드를 편집해야만 할 겁니다. 그렇다면 이제 뷰에서 사용할 수 있는 템플릿을 작성하여, Python 코드로부터 디자인을 분리하도록 Django 템플릿 시스템을 사용해 봅시다.

우선, polls 디렉토리에 templates라는 디렉토리를 만듭니다. Django는 여기서 템플릿을 찾게 될 것입니다.

프로젝트의 TEMPLATES 설정은 Django가 어떻게 템플릿을 불러오고 렌더링 할 것인지 기술합니다. 기본 설정 파일은 APP_DIRS 옵션이 True로 설정된 DjangoTemplates 백엔드를 구성합니다. 관례에 따라, DjangoTemplates은 각 INSTALLED_APPS 디렉토리의 templates 하위 디렉토리를 탐색합니다.

방금 만든 templates 디렉토리 내에 polls라는 다른 디렉토리를 만들고 그 안에 index.html이라는 파일을 만듭니다. 즉 templates는 polls/templates/polls/index.html과 같아야 합니다. 위에서 설명한 바와 같이 app_directories 템플릿 로더가 작동하는 방식 때문에 Django 내의 해당 템플릿을 polls/index.html이라는 방식으로 참조할 수 있습니다.

템플릿에 다음과 같은 코드를 입력합니다.

# polls/templates/polls/index
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

이제 템플릿을 이용하여 polls/views.py에 index 뷰를 업데이트 해보도록 하겠습니다.

# polls/views.py
from django.http import HttpResponse
from django.template import loader

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

이 코드는 polls/index.html 템플릿을 불러온 후, context를 전달합니다. context는 템플릿에서 쓰이는 변수명과 Python 객체를 연결하는 사전형 값입니다.

브라우저에서 /polls/ 페이지를 불러오면, 튜토리얼 2장에서 작성한 What's up 질문이 포함된 리스트가 표시됩니다. 표시된 질문의 링크는 해당 질문에 대한 세부 페이지를 가리킵니다.


지름길: render()

템플릿에 context를 채워넣어 표현한 결과를 HttpResponse 객체와 함께 돌려주는 구문은 자주 쓰는 용법입니다. 따라서 Django는 이런 표현을 쉽게 표현할 수 있도록 단축 기능을 제공합니다. index() 뷰를 단축 기능으로 작서하면 다음과 같습니다.

# polls/views.py
from django.shortcuts import render

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

이런 방식을 모든 뷰에 적용한다면, 더 이상 loader와 HttpResponse를 임포트하지 않아도 됩니다.

render() 함수는 request 객체를 첫번째 인수로 받고, 템플릿 이름을 두번째 인수로 받으며, context 사전형 객체를 세번째 선택적 인수로 받습니다. 인수로 지정된 context가 들어있는 템플릿의 HttpResponse 객체가 반환됩니다.


404 에러 일으키기

# polls/views.py
from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

여기 새로운 내용이 추가되었습니다. 뷰는 요청된 질문의 ID가 없을 경우 Http404 예외를 발생시킵니다.

조금 후에 polls/detail.html 템플릿에 무엇을 넣을 수 있는지 이야기하겠지만, 일단 위의 예제를 동작시키기 위해 아래의 내용이 들어있느 파일을 작성하세요.

# polls/templates/polls/detail.html
{{ question }}

지름길: get_object_or_404()

객체가 존재하지 않을 때 get()을 사용하여 Http404 예외를 발생시키는 것은 자주 쓰이는 방법입니다. Django에서는 이 기능에 대한 단축 기능을 제공합니다. detail() 뷰를 단축 기능으로 작성하면 다음과 같습니다.

# polls/views.py
from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

get_object_or_404() 함수는 Django 모델을 첫번째 인자로 받고, 몇 개의 키워드 인수를 모델 관리자의 get() 함수에 넘깁니다. 만약 객체가 존재하지 않을 경우, Http404 예외가 발생합니다.

또한 get_object_or_404() 함수처럼 동작하는 get_list_or_404() 함수가 있습니다. get() 대신 filter()를 쓴다는 것이 다릅니다. 리슽트가 비어있을 경우, Http404 예외를 발생시킵니다.


템플릿 시스템 이용하기

투표 어플리케이션의 detail() 뷰로 되돌아가 봅시다. context 변수 question이 주어졌을 때, polls/detail.html이라는 템플릿이 어떻게 보이는지 봅시다.

# polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

템플릿 시스템은 변수의 속성에 접근하기 위해 dot-lookup 문법을 사용합니다. 예제의 {{ question.question_text }} 구문을 보면, Django는 먼저 question 객체에 대해 사전형으로 탐색합니다. 탐색에 실패하게 되면 속성값으로 탐색합니다. 만약 속성 탐색에도 실패한다면 리ㅣ스트의 인덱스 탐색을 시도하게 됩니다.

위 예에서는 속성값에서 탐색이 완료됩니다.

{% for %} 반복 구문에서 메소드 호출이 일어납니다. question.choice_set.all은 Python에서 question.choice_set.all() 코드로 해석되는데, 이때 반환된 Choice 객체의 반복자는 {% for %}에서 사용하기 적당합니다.

템플릿에 대한 더 많은 정보는 템플릿 지침서를 참고하세요.


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

polls/index.html 템플릿에 링크를 적으면, 이 링크는 다음과 같이 부분적으로 하듴코딩된다는 것을 기억하세요.

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

이런 강력하게 결합되고 하드코딩된 접근방식의 문제는 수 많은 템플릿을 가진 프로젝트들의 URL을 바꾸는 게 어려운 일이 된다는 점입니다. 그러나 polls.urls 모듈의 path() 함수에서 인수의 이름을 정으 했으므로, {% url %} template 태그를 이용하여 url 설정에 정의된 특정한 URL 경로들의 의존성을 제거할 수 있습니다.

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

이것이 polls.urls 모듈에 서술된 URL의 정의를 탐색하는 식으로 동작합니다. 다음과 같이 detail 이라는 이름의 URL이 어떻게 정으되어 있는지 확인할 수 있습니다.

# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),

URL의 이름공간 정하기

튜토리얼의 프로젝트는 polls라는 앱 하나만 가지고 진행했습니다. 실제 Django 프로젝트는 앱이 몇개라도 올 수 있습니다. Django는 이 앱들의 URL을 어떻게 구별해 낼까요? 예를 들어, polls 앱은 detail 이라는 뷰를 가지고 있고, 동일한 프로젝트에 블로그를 위한 앱이 있을 수도 있습니다. Django가 {% url %} 템플릿 태그를 사용할 때, 어떤 앱의 뷰에서 URL을 생성할지 알 수 있을까요?

정담은 URLconf에 이름공간(namespace)를 추가하는 것입니다. polls/urls.py 파일에 app_name을 추가하여 어플리케이션의 이름공간을 설정할 수 있습니다.

# polls/urls.py
from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.index, name='index'),
    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

아래와 같이 이름공간으로 나눠진 상세 뷰를 가리키도록 변경하세요.

# polls/templates/polls/index.html
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

참고

장고 공식 문서
https://docs.djangoproject.com/ko/4.0/intro/tutorial03/

profile
⌨️ 백엔드개발자 (컴퓨터공학과 졸업)

0개의 댓글