본문 바로가기
Research/Django

[Django] 공식 튜토리얼 3 정리

by RIEM 2021. 11. 5.

Django 공식 튜토리얼 3 정리

작성일 : 2021-11-05

문서버전 : 1.0

레퍼런스

개요

튜토리얼3에서는 웹 투표 앱을 하고 public interface인 “views”를 만들어보자.

 

View란?

View는 일종의 장고 앱의 웹페이지를 말한다. 일반적으로 특수한 기능과 템플릿을 수행한다. 

 

블로그 앱의 경우를 예로 들었을 때의 views들은 아래와 같을 것이다.

  • 블로그 홈페이지 : 몇개의 항목들 보여준다
  • 입구 상세페이지 : 각 항목에 연결하는 영구 페이지를 제공한다
  • 연 단위 아카이브 페이지 : 해당 연도의 월별 항목들을 모두 보여준다
  • 월 단위 아카이브 페이지 : 해당 월의 일별 항목들을 모두 보여준다
  • 일 단위 아카이브 페이지 : 그날의 모든 항목들을 보여준다
  • 댓글 : 주어진 항목에 댓글을 작성할 수 있도록 돕는다

 

우리가 만들고 있는 투표 앱에서는, 아래와 같은 view들을 만들어야 할 것이다.

  • 질문 인덱스 페이지 : 최근 질문들을 보여준다
  • 질문 상세 페이지 : 질문의 내용을 보여준다. 투표 결과는 보여주지 않는다.
  • 질문 결과 페이지 : 특정 질문의 투표 결과를 나타낸다
  • 투표 페이지 : 특정 질문에 대한 특정 응답을 투표할 수 있도록 돕는다.

 

View의 역할

장고에서 웹페이지와 다른 내용은 view로 인해 전달된다. 각 view는 파이썬 함수에 의해 표현된다. 장고는 요청받은 URL을 확인함으로써 view를 선택하게 된다.

 

View 추가하기

polls/views.py에 view를 추가해보자. 아래 View들은 argument를 가지고 있기 때문에 조금씩 다르다.

> polls/views.py : view 추가하기

기존 index에 더해 detail, results, vote 함수를 추가해보자

from django.shortcuts import render

from django.http import HttpResponse



def index(request):

    return HttpResponse("Hello, world. 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() 호출 추가

아래 path() 호출을 추가함으로써 위에서 위에서 새로 추가한 view들을 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')

]



새로 추가한 View를 polls.urls모듈과 연결하는 것을 구조도로 표현하면 아래와 같다.

polls/views.py에 detail, results, vote 함수를 추가해주었다. 추가된 view들은 polls/urls.py의 urlpatterns의 path 리스트에 추가하여 urls 모듈과 연동해준다.


> webpage 확인

http://127.0.0.1:8000/polls/34/ 에 들어가서 어떤 화면이 뜨는지 확인해보자. 

You're looking at question 34.

 

위와 같은 문구가 뜰 것이다. 주소창에 /polls/34/는 detail() 메소드와 연동된다. URL에 입력한 아이디(34)가 표시된다. 위 urls.py와 정의한 views.detail의 path를 보면 이해가 된다. 

 

    # 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/’와 같은 명령어를 주소창에 입력해서 페이지를 request하면, 장고는 mysite.urls 파이썬 모듈을 실행시킨다. 왜냐하면 ROOT_URLCONF가 이를 지정해줬기 때문이다. 이것이 urlpatterns라는 변수를 찾아내고 순서대로 해당 패턴들을 훑어본다. 만약 ‘polls/’에 해당하는 것을 찾을경우, 매칭되는 텍스트(“polls/”)를 strip하고 남은 텍스트 “34/”를 ‘polls.urls’ URLconf로 보낸다. 그다음 ‘<int:question_id>/’와 매칭되어서 궁극적으로 detail() view를 호출하게 된다. 호출할 때는 아래와 같이 호출한다고 보면 된다.

 

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

 

question_id=34<int:question_id>에서 온 것이다. ‘< >’괄호를 사용해서 URL 부분을 포착하고 이를 해당 view 함수에 keyword argument로 전송한다.. Question_id 문자열 부분은 매칭된 패턴을 확인하는데 사용되는 이름을 정의한다. int 부분은 일종의 변환기인데, 어떤 패턴이 이 URL path와 매치되어야 하는지 결정한다. 콜론 :은 컨버터와 패턴명을 구분하기 위해 사용한다.

 

아래 Views.py의 detail() 함수를 다시 확인해보자

Views.py

from django.shortcuts import render

from django.http import HttpResponse



def index(request):

    return HttpResponse("Hello, world. 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)



#질문

음. 점점 복잡해진다. 궁금한 점은 괄호가 위 호출 메시지에서 ‘request=<HttpRequest object>’를 의미하는 건지 아니면 <int:question_id>를 뜻하는걸까? 머리의 온도가 높아진다. 여러 모듈들이 상호작용하는 것을 선형적으로 정리하려니 직관적으로 파악되지 않는다. 프린트해서 파악해봐야겠다. 그전에 잠시 온도가 올라간 머리를 쿨다운하고 오자. 

 

본격적으로 View 활용해보기

각 view는 요청 페이지를 위한 데이터를 담은 HttpResponse 객체를 return하거나 또는 Http404와 같이 예외처리를 하는 등 기능들을 하는데 사용할 수 있다. 

 

우리의 View는 데이터베이스로부터 기록들을 읽을 수도 있고 아닐 수도 있다. 장고와 같이 template 시스템을 활용하거나 Python template 시스템과 같은 써드파티 솔루션을 사용할 수도 있다. PDF파일, XML 결과물을 만들 수도 있고 아니면 Zip파일을 만드는 등 Python 라이브러리를 활용해서 할 수 있는 일은 무궁무진하다.

 

장고가 요구하는 것은 HttpResponse 또는 exception이다.

 

튜토리얼2에서 다뤘던 장고 자체 DB AI를 이용해보자. 

아래 새로운 index() view가 있다. 최근 5개의 투표 질문을 나타낸다.

> polls/views.py

아래 새로운 index()를 만들어주자.

from django.shortcuts import render

from django.http import HttpResponse

from django.template import loader



from .models import Question



# 추가 수정한 index 함수

def index(request):

    latest_question_list = Question.objects.order_by('-pub_date')[:5]

    template = loader.get_template('polls/index.html')

    context = {

        'latest_question': latest_question_list,

    }

    return HttpResponse(template.render(context, request))    

   

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)



파이썬과 Template 영역 구분해주기

문제는 이 페이지의 디자인은 view에서 hard-coded 되어있다는 점이다. 만약 웹사이트의 화면 표시 방식을 바꾸고 싶다면, 우리는 파이썬 코드를 수정해야한다. 그럼 view가 사용하는 template를 만들어줌으로써 파이썬과 디자인 영역을 구분해주는 장고 template 시스템을 사용해보자.

> Templates 디렉토리 생성

첫째로, polls 디렉토리에 templates라는 디렉토리를 만들어줘야 한다. 장고가 그곳에서 template들을 찾을 것이다.

 

우리 프로젝트의 Template 세팅은 장고가 template들을 어떻게 load하고 render할 것인지를 알려준다. 기본 세팅은 APP_DIRS 옵션이 True로 되어있는 DjangoTemplates 백엔드로 설정되어있다. 관례적으로 DjangoTemplates는 각 INSTALLED_APPS의 template들 하위 디렉토리에서 찾는다.

 

Templates 디렉토리를 만든 뒤에, polls 디렉토리를 만들고 그 안에 index.html 파일을 생성한다. App_directories template loader가 위에서 방식처럼 작동하기 때문에, 장고 안에서 polls/index.html로 부를 수 있다. 그러니까 경로는 ‘polls/templates/polls/index.html’가 된다. 그런데 그냥 ‘polls/templates’로 바로 사용하면 안될까라고 생각할 수도 있겠지만 그렇게 하면 안된다고 한다. 장고는 매칭되는 첫번째 template를 찾는데, 만약 다른 다른 앱에 동일한 이름의 폴더가 있을 경우 장고는 이런 파일들을 구별할 수가 없다. 따라서 장고가 정확한 파일을 지칭할 수 있게 templates 안에 또 다른 어플리케이션의 이름의 폴더를 추가할 필요가 있다.


> polls/templates/polls/index.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 %}


> polls/views.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': latest_question_list,

    }

    return HttpResponse(template.render(context, request))

 

.

.

.

 

Index view함수를 했다. Quesion 오브젝트로 게시 날짜 기준으로 최근 5개 질문들을 가져오는 latest_question_list 변수는 그대로다. Template라는 변수가 새로 생겼는데, ‘polls/index.html’template를 로드한다. Context 변수도 새로 생겼는데, latest_question_list들을 딕셔너리 형태로 가져온다. 그리고 HttpResponse를 return하는데, template.render(context, request)를 파라미터로 가져온다. Render 문구는 처음 나온다.

 

장고 튜토리얼에 따르면 위 코드는 ‘polls/index.html’template를 호출한 뒤 최근 5개 질문들을 딕셔너리로 가진 context를 보낸다. Context는 template 변수를 파이썬 객체에 매핑한 딕셔너리다. 

 

http://127.0.0.1:8000/polls/’를 주소창에 뛰어서 페이지를 로드하면, 튜토리얼2에서 만든 문구의 질문들이 담긴 꼭지들이 보일 것이다. 

 

질문을 클릭하면..

화면이 잘 뜬다.



render()에 대해

Template를 불러내고, context를 채워 rendered된 template의 결과와 함께 HttpResponse 객체를 리턴하는 것은 자주 사용하는 용법이다. 장고는 숏컷을 제공하는데 여기 index() view를 수정해보자.

> 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': latest_question_list,

    }

    return HttpResponse(template.render(context, request))    

   

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)

 

 

 

아래와 같이 수정해주자.

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': latest_question_list}

    return render(request, 'polls/index.html', context)    

   

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)

Loader, HttpResponse들을 import할 필요가 없어졌다. 만약 다른 detail, results, vote 함수를 사용하고 싶다면 HttpResponse를 import 해주면 된다.

 

404에러 발생시키기

이제 question detail view에 문제를 발생시켜보자. 해당 페이지는 주어진 투표값의 질문텍스트를 나타내는 페이지다. 

> polls/views.py

Http404를 import해주고, detail() 함수에 해당 모듈을 적용시켜보자.

from django.http import Http404

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': latest_question_list}

    return render(request, 'polls/index.html', context)    

   

def detail(request, question_id):

    try:

        question = Question.objects.get(pk=question_id)

    except Quesion.DoesNotExist:

        raise Http404("Question does not exist")

    return render(request, 'polls/detail.html', {'question':question})

 

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)

 

 

만약 request된아이디와 함께있는 질문이 존재하지 않을 경우, View가 Http404 예외를 발생시킬 것이다.

 

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() 함수는 장고 모델을 첫번째 argument(인자)로 받고 몇개의 키워드 인수를 모델관리자의 get() 함수에 넘긴다. 만약 객체가 존재하지 않을 경우 Http404를 발생한다. 

get_list_or_404() 함수도 있는데 get_object_or_404()와 유사하게 작동한다. get() 대신 filter()을 사용한다는 차이점이 있다. List가 비어있을 경우 Http404를 발생시킨다.

 

Template 시스템 사용하기

투표 앱의 detail() view로 돌아가보자. context variable question의 경우, polls/detail.html template는 아래와 같다.

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

 

Template 시스템은 변수의 속성에 접근하기 위해 점-탐색(dot-lookup) 문법을 사용한다. 예를 들어 {{ question.question_text }}의 경우, 우선 장고가 question 객체에 대해 dictionary형으로 탐색한다. 탐색에 실패할 경우, 속성형으로 탐색한다. 속성 탐색에 실패할 경우, 리스트-인덱스 형으로 탐색할 것이다.

 

{% for %} 반복 구문에서는 메소드 호출이 발생한다. Question.choice_set.all이 파이썬 코드 question.choice_set.all()로 해석되는데, 이것은 Choice 객체들의 반복자는 {% for %}에서 사용하기 적절하다. 추가 내용은 장고의 template guide를 참조해보자.

 

Template에서 하드코딩 된 URL 제거하기

기억해야 될 것은 polls/index.html template 내 question에 링크를 걸 때, 링크는 아래와 같이 부분적으로 하드코딩 되어있다.

> 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의 정의를 탐색하는 방식으로 동작한다. 아래 보면 URL 이름인 ‘detail’이 아래 정의되어있는 것을 확인할 수 있다.

 

Polls/urls.py

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

만약 polls detail view의 url 경로를 바꾸고 싶을 경우에는 어떻게 해야할까? 예를 들어서 polls/specifics/12/와 같이 말이다.그럴 경우 template 내에서 하드코딩하지 말고 아래와 같이 수정해주면 된다.

 

polls/urls.py

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

 

URL의 이름공간 정하기

우리는 튜토리얼 프로젝트에서 polls라는 앱 하나만 만들었었따. 실제 장고 프로젝트에서는 아마도 5, 10, 20 또는 그 이상의 앱들이 있을 것이다. 장고는 그 수많은 앱들의 URL 이름들을 어떻게 구분할까? 예를 들어서 polls 앱은 detail view를 가지고 있는데, 동일한 프로젝트에 블로그를 위한 앱도 있을 수도 있다. 그럴 경우 장고는 {% url %} template tag 할 때, 어떤 앱의 view에서 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'),

]

 

그렇다면 template의 내용도 바꿔보자.

> polls/templates/polls/index.html 

아래와 같이 기존 내용을

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

 

아래와 같이 이름공간으로 나눠진 view를 가르키도록 수정해보자.

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

 

댓글