본문 바로가기
Django

첫 번째 Django 앱 만들기 (Part 3: Views and templates)

by AlbertIm 2024. 8. 14.

시작

Part2를 이어서 간단한 설문조사(Polls) 앱을 만드는 과정을 통해 Django의 views와 templates을 알아보려고 합니다. 본 포스트에서는 macOS와 IntelliJ IDEA Ultimate을 사용합니다. View는 일반적으로 Django 앱에서 특정 기능을 제공하고 템플릿을 통해 웹 페이지의 "유형"을 정의입니다.

본문

URL dispatcher

Django에서 앱의 URL을 디자인하려면 비공식적으로 URLconf (URL 구성)이라고 불리는 Python 모듈을 만들어야 합니다. 이 모듈은 순수한 Python 코드로 작성되며 URL 경로 표현식과 Python 함수(뷰) 간의 매핑을 정의합니다.

Django는 또한 URL을 활성 언어에 따라 번역하는 방법도 제공합니다.

Internationalization: in URL patterns

Django는 URL 패턴을 국제화하기 위한 두 가지 메커니즘을 제공합니다:

  • LocaleMiddleware: 요청된 URL에서 활성화할 언어를 감지할 수 있도록 URL 패턴의 루트에 언어 접두사를 추가합니다.
  • django.utils.translation.gettext_lazy(): 이 함수를 통해 URL 패턴 자체를 번역 가능하게 만듭니다.

확인해 보면 이렇게 활성화된 언어 따라 URL이 달라집니다.

뷰 추가하기

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)

 

poll.urls 에 새로운 path()를 추가합니다.

 

from django.urls import path

from . import views

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

 

Intellij에서는 이렇게 URL이 표시됩니다.

 

서버를 실행하고 브라우저에서 /polls/1/,/polls/1/results/,/polls/1/vote/ 주소 요청을 보내면 모두 응답을 받을 수 있습니다.

 

*/polls/1*
**You're looking at question 1.**

*/polls/1/results/*
**You're voting on question 1.**

*/polls/1/vote/*
**You're looking at the results of question 1.**

 

Django 서버에 요청을 보내면 ROOT_URLCONF 설정에 의해 지정한 mysite.urls Python 모듈을 로드합니다. urlpatterns라는 변수를 찾아 패턴 순서대로 탐색합니다. 예를 들어 /polls/34/ 로 요청했으면 ROOT_URLCONFpolls/와 일치하므로 polls/를 제거하고 너머지 텍스트 34/를 추가 탐색을 위해 polls.urls URLconf로 보냅니다. polls.urls에서 <int:question_id>와 일치하여 views.detail 뷰가 호출됩니다.

 

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

 

question_id<int:question_id> 에서 옵니다. <> 꺾쇠괄호를 사용하여 URL의 일부를 캡처하여 뷰 함수키워드 인수로 보냅니다. question_id와 일치하는 패턴을 식별하는 데 사용되는 이름을 정의하고 int 은 URL 경로의 꺾쇠 부분이 일치해야 하는 패턴의 유형을 결정하는 변환기입니다. 콜론(:)은 변환기와 패턴 이름을 구분합니다.

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

뷰의 역할은 두 가지 중 하나를 하도록 되어 있습니다.

  • 요청된 페이지의 내용이 담긴 HttpResponse 객체를 반환합니다.
  • Http404 같은 예외를 발생시킵니다.

뷰에서 데이터베이스 접근과 템플릿을 시스템 등 Python어떤 라이브러리라도 사용할 수 있습니다.

poㅣls/views.py 파일의 index() 함수를 수정합니다.

 

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)

 

뷰에서 페이지의 디자인을 하드코딩한다면 페이지의 디자인을 바꾸고 싶을 때 이 뷰 코드를 편집해야 합니다. 이를 해결하기 위해 Django의 템플릿 시스템을 사용하여 Python 코드와 디자인을 분리합니다.

Django 템플릿 시스템은 기본적으로 각 INSTALLED_APPS 디렉터리의 templates 하위 디렉터리를 탐색합니다.

polls/templates/polls/index.html 을 작성합니다. 실제 프로젝트이어서 HTML은 이렇게 작성하면 안 되고 완전한 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 %}

 

Django에게 정확한 템플릿을 지정하기 위해서 가장 편리한 방법은 애플리케이션이 사용하는 템플릿은 애플리케이션 이름으로 된 디렉터리에 넣는 것입니다.

이제 템플릿을 사용하도록 polls.views.pyindex 뷰를 업데이트합니다.

 

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이라는 템플릿을 로드하고 여기에 콘텍스트를 전달합니다. 콘텍스트는 템플릿 변수 이름을 Python 객체에 매핑하는 식별자입니다.

다시 polls/ 에 접속해 봅니다.

단축 기능: render()

템플렛에 콘텍스트를 채워 넣어 표현한 결과를 HttpResponse 객체와 함께 반환하는 구문으로 자주 사용됩니다. 따라서 Django는 이런 표현을 쉽게 하도록 shortcuts를 제공합니다. 여기서 shortcutrender()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)

 

render()함수는 첫 번째 인수는 request 객체를 두 번째 인수는 템플릿 이름을 세 번째 인수로는 선택적인 dictionary 인자이고 HttpRespnse객체를 반환합니다.

 

404 에러 일으키기

polls/views.pydetail뷰를 수정합니다.

 

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 템플릿을 만듭니다.

 

{{ question }}

 

서버를 실행하고 존재하지 않는 polldetail를 조회하면 아래와 같이 표시됩니다. (예: polls/1000)

단축 기능: get_object_or_404()

get()을 사용할 때 객체가 존재하지 않으면 Http404를 발생시키게 하는 것이 매우 일반적입니다. Django는 shortcutget_object_or_404()를 제공합니다.

detail() 를 이렇게 수정할 수 있습니다.

 

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 model을 임의 개수의 키워드 인수를 사용하여 model 관리자의 get()함수에 전달합니다. 객체가 존재하지 않으면 Http404를 발생시킵니다.

 


get_list_or_404() 함수도 있습니다.

 

템플릿 시스템 사용하기

detail() 뷰를 살펴보면 context 변수로 question이 제공되고 있습니다.

 

def detail(request, question_id):  
    question = get_object_or_404(Question, pk=question_id)  
    return render(request, "polls/detail.html", {"question": question})

 

이전에 shell을 사용하여 데이터를 생성할 필요가 있습니다.

  • Django shell 실행합니다.
  • python manage.py shell
  • Python 명령어 모드에 들어갑니다.
from polls.models import Question, Choice
from django.utils import timezone

# 새로운 Question 객체 생성
question = Question.objects.create(
    question_text="오늘의 날씨는?",
    pub_date=timezone.now()
)

# Choice 객체 생성
Choice.objects.create(question=question, choice_text="맑음", votes=0)
Choice.objects.create(question=question, choice_text="비", votes=0)
Choice.objects.create(question=question, choice_text="구름 많음", votes=0)
Choice.objects.create(question=question, choice_text="눈", votes=0)

# 생성된 Choice 객체 확인
choices = Choice.objects.filter(question=question) for choice in choices: print(choice.choice_text)

# 생성되 Question id 확인
print(question.id)

 


/polls/detail.html 수정전 코드입니다.

 

{{ question }}

 

웹 브라우저에 존재하는 polldetail을 요청하면 다음과 같이 표시됩니다. (예/polls/13/)

/polls/detail.html 수정합니다.

 

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

 

다시 웹 브라우저에 존재하는 polldetail를 요청하면 다음과 같이 표시됩니다. (예/polls/3/)


템플릿 시스템은 변수를 접근하기 위해 dot-lookup 문법을 사용합니다. 예제의 {{ question.question_text }} 에서는 Django은 먼저 question 객체에 대해 dictionary처럼 탐색합니다. 만약 탐색에 실패하게 되면 속성값에서 탐색이 완료됩니다. 만약 속성 탐색에도 실패한다면 리스트의 인덱스 탐색을 시도하게 됩니다. 이 예에서는 속성값에서 탐색이 완료됩니다.

{% for %} 는 루프 함수를 호출합니다.

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

polls/index.html 템플릿을 확인 시면 URL 부분이 하드코딩된 것을 확인할 수 있습니다.

polls.urls 모듈의 path() 함수에 정의된 name 인수를 사용하면 {% url %} 템플릿 태그를 활용하여 URL 구성에 정의된 특정 URL 경로에 대한 의존성을 제거할 수 있습니다.

 

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

 

이것은 polls.urls 모듈에 정의된 URL 패턴을 탐색하여 'detail'이라는 이름의 URL을 찾습니다.

 

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

 

만약 URL을 polls/specifics/12/로 변경하고 싶다면 템플릿수정하지 않고 polls/urls.py에서 변경하면 됩니다.

 

# 이렇게
urlpatterns = [
    path("specifics/<int:question_id>/", views.detail, name="detail"),
]

URL의 Namespacing 정하기

실제 Django 프로젝트는 여러 앱을 포함할 수 있습니다. Django에서 앱들의 URL을 구분하기 위해 URLconfnamespace 추가하면 됩니다. 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을 다음과 같이 변경하여 URL의 namespace를 지정할 수 있습니다.

 

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

마무리

이번 Part3에서는 Django의 기본적인 뷰와 템플릿 시스템을 사용하는 방법을 정리해 보았습니다. Django에서 코드의 유연성과 유지보수성을 높이기 위해 뷰와 템플릿을 분리하는 노력이 보입니다. 이러한 접근 방법은 Spring에서 Thymeleaf와 같은 템플릿 엔진을 사용하는 것과 비슷한 것 같습니다. 이번 Part에서는 모델 사용과 템플릿 문법이 다루어졌지만 내용이 많아 다루지 못한 부분도 있습니다. 나중에 단독 주제로 정리하면 좋을 것 같습니다. 이제 Part4로 넘어가겠습니다.

참고자료

댓글