첫 번째 장고 앱 작성하기, part 3
첫 번째 장고 앱 작성하기, part 3 | Django 문서
The web framework for perfectionists with deadlines.
docs.djangoproject.com
이번 부분에서는 Web-poll 애플리케이션에 공용 인터페이스인 views 를 만드는 데 초점을 맞춰서 공부해보겠습니다.
view는 일반적으로 특정한 기능을 제공하고 특정한 템플릿을 가진 Django 애플리케이션에 있는 웹 페이지의 “type”이다. 예를 들어, 블로그 애플리케이션에서는 아래와 같은 view를 가질 수 있습니다.
- Blog 홈페이지 – 가장 최근의 항목들을 보여줌
- 항목 “세부”(detail) 페이지 – 하나의 항목에 연결하는 영구적인 링크(permalink)를 제공
- 년도별 축적 페이지 – 주어진 연도의 모든 월별 항목들을 표시
- 월별 축적 페이지 – 주어진 월의 날짜별 항목들을 표시
- 날짜별 축적 페이지 – 주어진 날짜의 모든 항목들을 표시
- 댓글 기능 – 특정 항목의 댓글을 다룰 수 있는 기능
현재 만들고 있는 poll 어플리케이션에서 다음과 같은 네개의 view 를 만들어 보겠습니다.
- 질문 “색인” 페이지 – 최근의 질문들을 표시
- 질문 “세부” 페이지 – 질문 내용과, 투표할 수 있는 서식을 표시
- 질문 “결과” 페이지 – 특정 질문에 대한 결과를 표시
- 투표 기능 – 특정 질문에 대해 특정 선택을 할 수 있는 투표 기능을 제공
Django에서는 웹 페이지와 기타 콘텐츠가 view로 전달되며, 각 view는 Python 함수(또는 클래스 기반 view의 경우 메소드)로 표현됩니다.
Django는 요청한 URL(더 정확하게 말하면 도메인 이름 뒤의 URL 부분)을 확인하여 보기를 선택합니다.
지금 웹 상에서 ``ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B``와 같은 내용을 접했을 것인데. Django는 우리에게 그것보다 더 편리한 *URL 패턴*을 이용할 수 있게 해줍니다.
URL 패턴은 URL의 일반적인 형식(예: /newsarchive/<year>/<month>/).
URL로부터 뷰를 얻기 위해, Django는 ‘URLconfs’라는 것을 사용하며, URLconf는 URL 패턴을 뷰에 연결합니다.
이 튜토리얼은 URLconfs를 사용하는 기초 지식을 제공하며, 좀 더 자세한 정보는 URL dispatcher를 참조하면 됩니다.
https://docs.djangoproject.com/ko/5.1/topics/http/urls/
URL dispatcher | Django 문서
The web framework for perfectionists with deadlines.
docs.djangoproject.com
뷰 추가하기
이제, 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
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/34/"를 확인해 보니. 이 기능은 detail() 함수를 실행하고 URL에 제공된 ID를 표시합니다. "/polls/34/results/"와 "/polls/34/vote/"도 시도해 보세요. 이렇게 하면 다음과 같이 표시됩니다
누군가 당신의 웹사이트에 페이지 - 예를 들어 : “/polls/34” 요청하면, Django는 ROOT_URLCONF 설정이 가리키고 있는 mysite.urls Python 모듈을 로드하며, 이 모듈은 urlpatterns``라는 변수를 찾아서 패턴을 순서대로 순회합니다.
'polls/' 에서 일치하는 패턴을 찾은 후 일치하는 텍스트("polls/")를 제거하고 나머지 텍스트인 –"34/"–를 추가 처리를 위해 ‘polls.urls’ URLconf로 보냅니다.
거기에서 '1/'`와 일치하는 패턴을 찾아 다음과 같이 ``detail() 뷰를 호출합니다
detail(request=<HttpRequest object>, question_id=34)
question_id=34 부분은 <int:question_id> 에서 왔으며, 꺾쇠 괄호를 사용하면 URL의 일부가 “캡처”되어 키워드 인수로 view 함수에 전송되어 집니다.
문자열의 question_id 부분은 일치하는 패턴을 식별하는 데 사용할 이름을 정의하고, int 부분은 URL 경로의 이 부분과 일치하는 패턴을 결정하는 변환기입니다.
콜론(:)은 컨버터와 패턴 이름을 구분하는데 사용됩니다.
뷰가 실제로 뭔가를 하도록 만들기
각 뷰는 두 가지 중 하나를 하도록 되어 있습니다.
요청된 페이지의 내용이 담긴 HttpResponse 객체를 반환하거나, 혹은 Http404 같은 예외를 발생하게 해야합니다.
작성한된 뷰는 데이터베이스의 레코드를 읽을 수도 있습니다.
또한 뷰는 Django나 Python에서 서드파티로 제공되는 템플릿 시스템을 사용할 수도 있는데, 뷰는 PDF를 생성하거나, XML을 출력하거나, 실시간으로 ZIP 파일을 만들 수 있습니다.
뷰는 원하는 무엇이든, Python의 어떤 라이브러리라도 사용할 수 있는 것입니다.
Django에 필요한 것은 HttpResponse 객체 혹은 예외입니다.
왜냐면, 그렇게 다루는게 편리하기 때문입니다. 튜토리얼 2장의 예제에서 다룬 Django 자체 데이터베이스 API를 사용해보겠습니다.
새로운 index() 뷰 하나를 호출했을 때, 시스템에 저장된 최소한 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)
# Leave the rest of the views (detail, results, vote) unchanged
뷰에서 페이지의 디자인이 하드코딩 되어 있을때. 만약 페이지가 보여지는 방식을 바꾸고 싶다면, 이 Python 코드를 편집해야 될겁니다.
그럼, 뷰에서 사용할 수 있는 템플릿을 작성하여, Python 코드로부터 디자인을 분리하도록 Django의 템플릿 시스템을 사용해 봅시다.
우선, polls 디렉토리에 templates라는 디렉토리를 만들면. Django는 여기서 템플릿을 찾게 됩니다.
프로젝트의 TEMPLATES 설정은 Django가 어떻게 템플릿을 불러오고 렌더링 할 것인지 기술합니다.
기본 설정 파일은 APP_DIRS 옵션이 True로 설정된 DjangoTemplates 백엔드를 구성합니다.
관례에 따라, DjangoTemplates은 각 INSTALLED_APPS 디렉토리의 “templates” 하위 디렉토리를 탐색하게 됩니다.
방금 만든 templates 디렉터리 내에 polls 라는 다른 디렉터리를 만들고 그 안에 index.html 이라는 파일을 만들어 줍니다. 즉, 템플릿은 polls/templates/polls/index.html``이어야 하며. 위에서 설명한 것처럼 ``app_directories 템플릿 로더가 작동하는 방식 때문에 Django 내에 있는 이 템플릿을 ``polls/index.html``로 지칭할 수 있는 것입니다.
'''
템플릿 네임스페이싱
이제 템플릿을 polls/templates 에 직접 넣는 것에서 벗어날 수 있을지도 모르지만 (또 다른 polls 하위 디렉토리를 만드는 것보다는) 실제로는 좋은 생각이 아닙니다.
Django는 이름이 일치하는 첫 번째 템플릿을 선택하는데 만약 다른 응용 프로그램에 같은 이름의 템플릿이 있으면 Django는 이러한 템플릿을 구별할 수 없는점이 있습니다. Django에게 정확한 템플릿을 지정하기 위해서 가장 편리한 방법은 *이름공간*으로 구분짓는 것입니다. 이것은 애플리케이션의 이름으로 된 디렉토리에 이러한 템플릿들을 넣으면 되는 것 입니다.
'''
템플릿에 다음과 같은 코드를 입력하면 됩니다.
{% 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 %}
내용이길어지는 것을 방지하기 위해 불완전한 HTML 을 사용한 것이지 자신의 프로젝트에서는 `complete HTML documents`__를 사용해야 한다.
이제, 템플릿을 이용하여 polls/views.py에 index 뷰를 업데이트 해보도록 하겠습니다.
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는 이런 표현을 쉽게 표현할 수 있도록 단축 기능(shortcuts)을 제공하고 이쓰며. 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와 HttpResponse를 가져오지 않아도 됩니다. (만약 detail, results, vote에서 stub 메소드를 가지고 있다면, HttpResponse를 유지해야 할 것입니다.)
render() 함수는 request 객체를 첫번째 인수로 받고, 템플릿 이름을 두번째 인수로 받으며, context 사전형 객체를 세전째 선택적(optional) 인수로 받게되면. 인수로 지정된 context로 표현된 템플릿의 HttpResponse 객체가 반환되어 집니다.
404 에러 일으키기
이제, 질문의 상세 뷰에 에러를 일으켜 보겠습니다 상세 뷰는 지정된 설문조사의 질문 내용을 보여줍니다. 다음과 같습니다.
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가 없을 경우 HTTP484 예외를 발생시킵니다.
조금 후에 polls/detail.html 템플릿에 무엇을 넣을 수 있는지 논의하겠지만, 일단 위의 예제를 동작시키기 위해 아래 내용이 들어있는 파일을 작성해주어야 한다.
{{ question }}
get_object_or_404()
만약 객체가 존재하지 않을 때 get() 을 사용하여 Http404 예외를 발생시키는것은 자주 쓰이는 용법입니다. Django에서 이 기능에 대한 단축 기능을 제공합니다. 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 모델을 첫번째 인자로 받고, 몇개의 키워드 인수를 모델 관리자의 get() 함수에 넘깁니다. 만약 객체가 존재하지 않을 경우, Http404 예외가 발생하게 됩니다.
'''
상위 계층에서 ObjectDoesNotExist 예외를 자동으로 잡아 내는 대신 get_object_or_404() 도움 함수(helper functoin)를 사용하거나, ObjectDoesNotExist 예외를 사용하는 대신 Http404 를 사용하는 이유는 모델 계층을 뷰 계층에 연결하는 방법이기 때문입니다.
Django의 중요한 설계 목표는, 약결합(loose coupling)을 관리하는 데에 있습니다. 일부 제어된 결합이 django.shortcuts 모듈에서 도입되었습니다.
'''
또한, get_object_or_404() 함수처럼 동작하는 get_list_or_404() 함수가 있는데. get() 대신 filter() 를 쓴다는 것이 다른 점입니다. 리스트가 비어있을 경우, Http404 예외를 발생시킵니다.
템플릿 시스템 사용하기
투표 어플리케이션의 detail() 뷰로 이동해서.
context 변수 question이 주어졌을때, 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 %}에서 사용하기 좋습니다.
템플릿에 대한 더 많은 정보는 템플릿 지침서를 확인하면 된다.
https://docs.djangoproject.com/ko/5.1/topics/templates/
템플릿 | Django 문서
The web framework for perfectionists with deadlines.
docs.djangoproject.com
템플릿에서 하드코딩된 URL 제거하기
polls/index.html 템플릿에 링크를 적으면, 이 링크는 다음과 같이 부분적으로 하드코딩된다는 것을 기억하세요.
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
이 하드코딩된 결합된 접근 방식의 문제는 템플릿이 많은 프로젝트에서 URL을 변경이 어려워지는 것이다.
그러나 polls.urls 모듈에서 path() 함수에 이름 인수를 정의했기 때문에 {% url %} 템플릿 태그를 사용하여 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/specifics/12/로 바꾸고 싶다면, 템플릿에서 바꾸는 것이 아니라 polls/urls.py에서 바꿔야 합니다.
...
# added the word 'specifics'
path("specifics/<int:question_id>/", views.detail, name="detail"),
...
URL의 이름공간 정하기
튜토리얼의 프로젝트는 polls라는 앱 하나만 가지고 진행했습니다. 실제 Django 프로젝트는 앱이 몇개라도 올 수 있습니다. Django는 이 앱들의 URL을 어떻게 구별할까요??
예를 들어, polls 앱은 detail이라는 뷰를 가지고 있고, 동일한 프로젝트에 블로그를 위한 앱이 있을 수도 있는데, Django가 {% url %} 템플릿태그를 사용할 때, 어떤 앱의 뷰에서 URL을 생성하는지를 알까요?
정답은 URLconf에 이름공간(namespace)을 추가하는 것입니다. 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>
이번 공부를 통해 view 작성에 익숙해 졌고, 다음에는 폼 처리 및 재너릭 뷰 에 대한 기본 사항에 대해 알아보겠습니다.
'PYTHON-BACK' 카테고리의 다른 글
Django_tutorial_var5 (1) | 2025.01.31 |
---|---|
Django_tutorial_var4 (1) | 2025.01.22 |
Django_tutorial_var2 (1) | 2025.01.20 |
Django_tutorial_var1 (0) | 2025.01.20 |
# 24.11.28_영화추천 사이트 제작_협업 기반 필터링 (0) | 2024.11.28 |