[Django] 첫 번째 장고 앱 작성하기, part3
링크 : https://docs.djangoproject.com/ko/4.1/intro/tutorial03
📖 개요
이 글은 장고 공식 문서를 보고 실습한 내용을 다시 한번 기억하기 위해 쓰면서 정리한 내용입니다.
Django에서는 웹 페이지와 기타 콘텐츠가 view로 전달됩니다. 각 view는 Python함수로 표현됩니다.
🎯 목표
- Web-poll 애플리케이션에 공동 인터페이스인 "views"를 만든다.
- 질문 "색인" 페이지 - 최근 질문들을 표시합니다.
- 질문 "세부" 페이지 - 질문 내용과, 투표할 수 있는 서식을 표시합니다.
- 질문 "결과" 페이지 - 특정 질문에 대한 결과를 표시합니다.
- 투표 기능 - 특정 질문에 대해 특정 선택을 할 수 있는 투표 기능을 제공합니다.
💡뷰 추가하기
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 모듈로 연결하기
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'),
]
브라우저에 "/polls/3/'를 입력하면 path() 구문을 통해 view.py 파일 안의 detail 함수가 작동된다. 마찬 가지로 "/polls/3/results'와 "/polls/3/vote' 역시 같은 원리로 동작한다. (숫자 3은 예를 들기 위한 임의의 숫자이다.)
💡 뷰가 실제로 뭔가 하도록 만들기
각 뷰는 두가지중 하나를 하도록 되어 있다. 요청된 페이지의 내용이 담긴 HttpResponse 객체를 반환하거나, Http404 같은 예외를 발생하게 해야 한다.
작성한 뷰는 데이터베이스의 레코드를 읽을 수도 있다. Python이나 Django에서 제공되는 템플릿 시스템을 사용할 수도 있고 PDF를 생성하거나, XML을 출력하거나, 실시간으로 ZIP 파일을 만들 수도 있다.
Django에 필요한 것은 HttpResponse 객체 혹은 예외이다. 그렇게 다루게 편하기 때문에 그렇게 다룬다.
Django의 자체 데이터베이스 API를 사용해서 새로운 index() 뷰 하나를 호출했을 때
polls/views.py 파일에 아래와 같이 작성하면
시스템에 저장된 최소한 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)
# 나머지 보기(세부 사항, 결과, 투표)는 변경하지 않고 그대로 둡니다.
💡 Django의 템플릿 시스템
위에서는 몇 가지의 문제가 있다. 뷰에서 페이지의 디자인이 하드코딩되어 있다고 가정하면 페이지가 보이는 방식을 바꾸려고 할 때, Python 코드를 편집해야만 한다. 그럼 뷰에서 사용할 수 있는 템플릿을 작성하여, Python 코드로부터 디자인을 분리하도록 Django의 템플릿 시스템을 사용하는 법이다.
- 한국어 번역이 조금 알아듣기 힘들게 되어 있어서 고생했던 부분이다.
- polls 디렉터리에 templates라는 디렉터리(폴더)를 만든다. (Django는 여기서 템플릿을 찾을 것이다.)
- 프로젝트의 TEMPLATES 설정은 Django가 어떻게 템플릿을 불러오고 렌더링을 할 것인지 기술한다. 기본 설정 파일은 APP_DIRS 옵션이 True로 설정된 DjangoTemplates 백엔드를 구성한다. 관례에 따라, DjangoTemplates은 각 INSTALLED_APPS 디렉터리의 "templates" 하위 디렉토리를 탐색한다.
- 방금 만든 templates 디렉토리 내의 templates라는 다른 디렉터리(폴더)를 만들고 그 안에 polls라는 폴더를 만들고 나서 index.html이라는 파일을 만든다. 즉 템플릿은 polls/templates/polls/index.html 이어야한다.
- app_directories 템플릿 로더가 작동하는 방식 때문에 Django내에 있는 이 템플릿을 polls/index.html로 지칭할 수 있다.
이후 polls/templates/polls/index.html 에 아래 코드를 입력한다.
- 자습서를 짧게 만들기 위해서 모든 템플릿 예제는 불완전한 html을 사용한다.
- 새로운 프로젝트에서는 완전한 THML을 사용해야 한다.
{% 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 %}
이제 템플릿을 사용하도록 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 컨텍스트에 전달된다. (컨텍스트는 템플릿 변수 이름을 Python 객체에 매핑한는 사전이다.)
이제 브라우저에서 "/polls/"를 지정하여 페이지를 로드하면 과거 질문으로 등록하였던 "What's up" 질문이 포함된 글머리 기호 목록이 표시된다. (링크는 질문의 세부사항 페이지와 연결되어 있다.)
💡바로가기 : render()
템플릿을 로드하고 컨텍스터를 채우고 HttpResponse 렌더링 된 템플릿의 결과로 개체를 반환하는 것은 매우 일반적인 관용구이다. Django는 바로가기를 제공한다.
index() 함수를 다시 작성해보면 아래와 같이도 작성할 수 있다.
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를 import 해줄 필요가 없다. 하지만 스텁 매서드가 여전히 있는 경우에는 HttpResponse를 유지하는 것이 더욱 좋다.
render() 함수는 요청 객체를 첫 번째 인수로, 템플릿 이름을 두 번째 인수로, 선택적으로 변수를 세 번째 인수로 사용한다.
💡 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})
위 코드에는 Http404라는 개념이 있는데, 요청된 ID가 있는 질문이 존재하지 않는 경우 보기에서 예외가 발생한다.
나중에 해당 템플릿에 무엇을 넣을 수 있는지 배우겠지만 일단, 위 예제를 작동시키려면 다음을 포함하는 파일이 필요하다.
polls/templates/polls/detail.html 파일 안에 아래와 같은 코드를 입력해준다.
{{ question }}
💡 바로가기 : get_object_or_404()
객체가 존재하지 않는 경우 사용하고 제기하는 매우 일반적인 관용구이다.
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는 객체가 존재하지 않으면 발생한다.
💡 템플릿 시스템 사용
vote 애플리케이션의 detail() 부분을 보면 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>
아래 부분은 잘 이해가 안 가지만 나중을 위해서 작성해 두었다.
템플릿 시스템은 변수의 속성에 접근하기 위해 점-탐색 문법을 사용한다. 예제의 {{ 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이 어떻게 정의되는지 확인할 수 있다.
...
# {% url %} 템플릿 태그에 의해 호출되는 'name' 값
path('<int:question_id>/', views.detail, name='detail'),
...
#만약 상세 뷰의 URL을 polls/specifics/12/로 바꾸고 싶다면,
#템플릿에서 바꾸는 것이 아니라 polls/urls.py에서 바꿔야 합니다.
...
path('specifics/<int:question_id>/', views.detail, name='detail'),
...
💡URL의 이름 공간 정하기
현재까지의 프로젝트에서는 polls라는 앱 하나만 가지고 진행하였지만 실제 Django 프로젝트는 앱을 몇 개라도 가지고 올 수 있습니다. Django는 이 앱들의 URL을 어떻게 구별할까?
예를 들어, polls앱은 detail이라는 뷰를 가지고 있고, 동일한 프로젝트에 블로그를 위한 앱이 있을 수도 있다.
만약 Django가 {% url %} 탬플릿 태그를 사용할 때, 어떤 앱의 뷰에서 URL을 생성할지 알 수 있을까?
정답은 URLconf에 이름 공간을 추가하는 것이다. 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/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>
'BackEnd > Django, DRF' 카테고리의 다른 글
[Django] 프로젝트 구조 설정, 만드는 순서 (0) | 2022.11.22 |
---|---|
[Django] static 파일 이미지 HTML에 추가하기 (0) | 2022.11.20 |
[Django] 첫 번째 장고 앱 작성하기, part4 (0) | 2022.11.17 |
[Django] 첫 번째 장고 앱 작성하기, part2 (0) | 2022.11.14 |
[Django] 첫 번째 장고 앱 작성하기, part1 (0) | 2022.11.14 |