본문 바로가기
파이썬/장고(django)

[Django] 튜토리얼3. 뷰와 템플릿(Views and templates)

by 책 읽는 개발자_테드 2022. 6. 12.
반응형

 이 글은 장고 공식 홈페이지의 튜토리얼을 번역하고, 직접 실습하는 과정을 정리합니다.

두 번째 튜토리얼(https://scshim.tistory.com/593)과 이어지는 세 번째 글입니다.

 

목차

· 뷰 작성하기

· 실제로 무언가 하는 뷰 작성하기

· 404 에러 발생시키기

· 템플릿 시스템 사용하기

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

· URL 이름 네임스페이스


 

해당 튜토리얼에서는 기본 설문 조사 응용 프로그램을 만드는 과정을 통해 학습을 진행한다. 해당 튜토리얼은 Python 3.8 이상을 지원하는 Django 4.0을 사용하여 작성되었다. 아래 명령어를 통해 Django 버전을 확인할 수 있다.

$ python -m django --version

 

뷰(View)란?


· 뷰는 일반적으로 특정 기능을 제공하고 특정 템플릿이 있는 장고 애플리케이션의 웹 페이지 타입이다.

ex) 블로그 홈페이지, Question 리스트 페이지, Question 세부사항 페이지

 

· 장고에서 웹 페이지 및 기타 콘텐츠는 view에 의해 전달된다.

· 각 View는 파이썬 함수(클래스 기반 View의 경우 메서드)로 표시된다. 

· 장고는 요청된 도메인 이름 뒤의 URL 부분을 검사하여 View를 선택한다.

· 장고는 URL에서 View로 이동하기 위해 URLconfs을 사용한다. URLconf는 URL 패턴을 View에 매핑한다.

- 더 자세한 내용: URL dispatcher

 

뷰 작성하기


polls/views.py 모듈에 더 많은 뷰를 더한 후,

from django.http import HttpResponse


def index(request):
    return HttpResponse("You're at the polls index.")


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)

 

polls/urls.py 모듈에 path() 호출을 추가하여 새로운 View를 연결한다.

from django.urls import path

from . import views

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'),
]

 

· 작동 원리:

1. "/polls/34"와 같이 웹사이트에서 페이지를 요청한다.

2. 장고는 ROOT_URLCONF 설정이 가리키는 mysite.urls 파이썬 모듈을 로드하고,  urlpatterns라는 변수를 찾아 순서대로 패턴을 탐색한다.

3. 'polls/'에서 일치 항목을 찾은 후 일치하는 텍스트 'polls/'를 제거하고, 나머지 텍스트 '34/'를 추가 처리하기 위해 'polls.url' URLconf로 보낸다.

4. '34/' 텍스트는 'polls.url'의 '<int:question_id>/'와 일치하여 다음과 같이 detail() View를 호출한다.

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

- question_id=34 부분은 <int:question_id>에서 가져온다.

- 꺾쇠 괄호를 사용하면 URL의 일부분을 캡처하고, 이를 View 함수에 키워드 인수로 보낸다.

- 문자열의 question_id 부분은 일치하는 패턴을 식별하는 데 사용할 이름을 정의하고, int 부분은 URL 경로의 해당 부분과 일치해야 하는 패턴을 결정하는 변환기다.

- 콜론(:)은 변환기와 패턴 이름을 구분한다.

http://127.0.0.1:8000/polls/34/ 요청 결과

 

실제로 무언가 하는 뷰 작성하기


· 각 View는 요청된 페이지의 컨텐츠가 포함된 HttpResponse 객체를 반환하거나 Http404와 같은 예외를 발생시키는 두 가지 작업 중 하나를 수행한다. 

 

· View는 데이터베이스에서 레코드를 읽을 수 있을 수도 있고, 읽지 않을 수도 있다. 장고와 같은 템플릿 시스템 또는 타사 파이썬 템플릿 시스템을 사용할 수도 있고, 사용하지 않을 수도 있다. 원하는 파이썬 라이브러리를 사용하여 PDF 파일을 생성하고, XML을 출력하고 ZIP 파일을 생성할 수도 있다.

 

polls/views.py 모듈의 index() view를 수정한다. 게시 날짜에 따라 쉼포로 구분하여 5개의 최신 투표 질문을 표시한다.

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)

 

 

위 코드는 페이지의 디자인이 view에 하드 코딩되어 있는 문제가 있다. 페이지가 표시되는 방식을 변경하려면 해당 파이썬 코드를 편집해야 한다. 장고의 템플릿 시스템을 사용하여 view에서 사용할 수 있는 템플릿을 만들어 파이썬과 디자인을 분리하자.

 

polls 디렉터리에 templates라는 디렉터리를 만든다. 장고는 여기에서 템플릿을 찾는다. 

 

프로젝트의 TEMPLATES 설정은 장고가 템플릿을 로드하고 렌더링하는 방법을 설명한다. 기본 설정 파일은 APP_DIRS 옵션이 True로 설정된 DjangoTemplates 백엔드를 구성한다. 관례에 따라 DjangoTemplates는 각 INSTALLED_APPS에서 'templates' 하위 디렉터리를 찾는다.

 

방금 만든 templates 디렉터리에  polls라는 다른 디렉터리를 만들고 그 안에 index.html이라는 파일을 만든다.  

 

polls/templates/polls/index.html

{% 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 %}

 

· Template namespacing

- app_directories 템플릿 로더가 작동하는 방식 때문에 장고 내에서 이 템플릿을 polls/index.html로 참조할 수 있다. 하지만 이렇게 polls/templates 디렉터리에 파일을 직접 생성하는 방식은 문제가 있다.

- 장고는 이름이 일치하는 첫 번째 템플릿을 선택하고, 다른 응용 프로그램에 같은 이름의 템플릿이 있는 경우 이를 구분할 수 없다. 장고가 올바른 것을 가리킬 수 있어야 하며 이를 보장하는 가장 좋은 방법은 네임 스페이스를 지정하는 것이다.

- 즉, 위에 코드 처럼 해당 템플릿을 으용 프로그램 자체에 대해 명명된 다른 디렉터리에 넣는다.

 

다음으로 템플릿을 사용하여 polls/view.py 모듈의 index view을 수정하자.

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이라는 템플릿을 로드하고 컨텍스트를 전달한다. 컨텍스트는 템플릿 변수 이름을 파이썬 객체에 매핑하는 dictionary다.

 

브라우저에서 "/polls/" 지정하여 페이지를 로드하면 앞서 저장한 "What's up" 질문이 포함된 글머리 기호 목록이 표시된다.

 

링크를 클릭하면, 세부 정보 페이지로 이동한다.

 

A shortcut: render()

템플릿을 로드하고, 컨텍스트를 채우고, 렌더링된 템플릿의 결과로 HttpRespone 객체를 반환하는 것은 매우 일반적인 관용구다. 장고는 이에 대한 shortcut을 제공한다. 다음은 다시 작성한 index() view다.

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)

  

· 한 번 view에서 이 작업을 수행하면 더 이상 로더와 HttpResponse를 가져올 필요가 없다.

- 단, 세부 사항, 결과 및 투표에 대한 스텁 메서드가 여전히 있는 경우 HttpResponse를 유지해야하는 경우도 있다.

· render() 함수는 request 객체를 첫 번째 인수로, 템플릿 이름을 두 번째 인수로, dictionary를 선택적 세 번째 인수로 갖는다.

 

A shortcut: get_object_or_404()

get() 을 사용하고 객체가 존재하지 않는 경우 Http404를 발생시키는 것은 매우 일반적인 구용구다. 장고는 이에 대한 shortcut을 제공한다. 다음은 다시 작성한 detail() view다.

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() 함수는 장고 모델을 첫 번쨰 인수로 갖고, 임의의 수의 키워드 인수 모델 관리자의 get() 함수에 전달한다. 객체가 존재하지 않으면 Http404를 발생시킨다.

 

· 상위 수준에서 ObjectDoesNotExist 예외를 자동으로 포착하거나, 모델 API가 ObjectDoesNotExist 대신 Http404를 발생시키는 대신 helper 함수 get_object_or_404()를 사용하는 이유는?

- 이것이 모델 레이어를 뷰 레이어에 연결하기 때문이다. 장고의 가장 중요한 설계 목표 중 하나는 느슨한 결합을 유지하는 것이다. 일부 제어된 결합이 django.shorcuts 모듈에 도입되었다.

 

· get_list_or_404(): 목록이 비어 있으면 Http404가 발생하는 함수다. get() 대신 filter()를 사용하는 것을 제외하고, get_object_or_404()와 똑같이 작동한다.

 

 

404 에러 발생시키기


주어진 설문 조사에 대한 질문 텍스트를 표시하는 페이지인 질문 세부 정보 view를 만들어보자.

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})

 

polls/detail.html 템플릿에 다음 코드를 입력한다.

polls/templates/polls/detail.html

{{ question }}

 

요청된 ID를 가진 질문이 존재하지 않으면 view에서 Http404 예외가 발생한다.

 

템플릿 시스템 사용하기


설문조사 애플리케이션을 위한 detail() view로 돌아가서, 컨텍스트 변수로 질문이 주어지면 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) 구문을 사용하여 변수 속성에 접근한다. 

ex) {{ question.question_text }}의 예로 보면, 첫 번째로 장고는 question 객체를 사전 검색(dictionary lookup)한다. 실패하면, 속성 조회(attribute lookup)를 시도한다. 만약 속성 조회가 실패하면, 목록 색인 조회(list-index lookup)를 시도한다.

 

· 메서드 호출은 {% for %} 루프에서 발생한다. question.choice_set.all은 파이썬 코드 question_choice_set.all()로 해석되며, 이는 {% for %} 태그에서 사용하기에 적합한 literable한 Choice 객체를 반환한다.

 

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


 polls/index.html 템플릿에서 질문에 대한 링크를 작성할 때, 해당 링크는 부분적으로 하드코딩 되었다.

 

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

 

문제: 하드코딩되고, 밀접하게 연결된 방식으로 템플릿이 많은 프로젝트에서 URL을 변경하기 어려워진다.

해결: polls.urls 모듈에서 path() 함수 name 인수를 정의했기 때문에 {% url %} 템플릿 태그를 사용하여 URL 구성에 정의된 특정 URL 경로에 대한 의존도를 제거할 수 있다.

- 작동 방식: polls.urls 모듈에 지정된 URL 정의를 찾는다. 아래 ‘detail’의 URL 이름이 정확이 어디에 정의되어 있는지 확인할 수 있다.

 

# {% url %} 템플릿 태그에 의해 호출되는 ‘name’ 값
path('<int:question_id>/', views.detail, name='detail'),

 

polls detail view의 URL을 변경하려면, 템플릿에서 변경하는 대신 polls/urls.py에서 변경해야한다. ex) polls/specifics/12

 

path('specifics/<int:question_id>/', views.detail, name='detail'),

 

URL 이름 네임스페이스


현재 진행하는 튜토리얼 프로젝트는 polls 앱 하나만 가지고 있지만, 실제 장고 프로젝트에는 훨씬 더 많은 앱들이 있을 것이다. 

 

장고는 이러한 앱들의 URL을 어떻게 구별할까? URLconf에 네임스페이스를 추가하면 된다. polls/urls.py 파일에서 app_name을 추가하여 애플리케이션 네임스페이스를 설정해보자.   

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/index.html 템플릿을 변경해보자. 아래 코드를

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

 

다음과 같이 변경한다.

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

 

출처

https://docs.djangoproject.com/en/4.0/intro/tutorial03/

 

반응형

댓글