Computer Science/BackEnd

장고 | 장고 공식 문서 내용 정리 #1-5 Getting started : writing your first Django app, part 4

토마토. 2022. 9. 3. 18:15

첫 번째 장고 앱 작성하기, Part 4 | 장고 문서 | 장고 (djangoproject.com)

 

Writing your first Django app, part 4 | Django documentation | Django

Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

docs.djangoproject.com

 

첫 장고 앱 만들기 4
최소한으로 form 만들기

detail.html을 업데이트해보자

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

 

애프터

<from action="{% url 'polls:vote' question.id %}" method="post">
    {%csrf_token%}
    <fieldset>
        <legend><h1>{{question.question_text}}</h1></legend>
        {%if error_message%}<p><strong>{{error_essage}}</strong></p>{%endif%}
        {%for choice in question.choice_set.all %}
            <input type="radio" name="choice" id="choice{{forloop.counter}}" value="{{choice.id}}">
            <label for="choice{{forloop.counter}}">{{choice.choice_text}}
            </label><br>
        {%endfor%}
        </fieldset>
    <input type="submit" value="Vote">
</form>

이게 radio 버튼. 

위 템플릿에서는 각 질문에 대해 라디오 버튼을 보여준다. 

라디오 버튼은 choice.id 즉, 응답 id를 보여준다. 

{% csrf_token %} 탬플릿 태그를 이용하면, 교차 사이트 요청 위조를 방지할 수 있다. 

[참고] Built-in template tags and filters | Django documentation | Django (djangoproject.com)

 

이제 데이터를 제출한 뒤에 데이터를 보여주는 Django view를 만들어보자. 

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

앞서 만들어둔 것

 

from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
from .models import Question, Choice
from django.template import loader
from django.urls import reverse

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # redisplay the question voting
        return render(request, 'polls/detail.html',{
            'question':question, 
            'error_message':"You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect
        # after successfully dealing with post data
        # 두번 post되는 것을 방지한다. 
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

코드는 이것. 

 

from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse, HttpResponseRedirect
from .models import Question, Choice
from django.urls import reverse

def vote(request, question_id):
	# 인수로 받은 question_id를 primary key로 넣어 Question 객체를 받아온다
    # 만약 객체가 없으면 404 return
    question = get_object_or_404(Question, pk=question_id)
    
    try:
    	# request.POST는 key name으로 데이터를 받아오는 딕셔너리 같은 객체
        # request.POST는 string으로 받아온다(여기서는 'choice'의 ID를 반환)
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
        
    # KeyError가 발생하는 경우는 POST 데이터가 없을 때
    except (KeyError, Choice.DoesNotExist):
        # redisplay the question voting
        return render(request, 'polls/detail.html',{
            'question':question, 
            'error_message':"You didn't select a choice.",
        })
    else:
    	# choice count를 1 올림
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect
        # after successfully dealing with post data
        # 두번 post되는 것을 방지한다. 
        # HttpResponseRedirect를 반환. 유일한 인수는 리다이렉트할 URL
        
        # reverse() 함수는 URL이름을 받아 직접 URL string을 반환해준다
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

 

마지막 라인에서 보이듯이, vote 함수는 polls:results url로 리디렉션한다. 

다시 view 함수부터 구성해보자. 

 

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

 

results 함수에서 반환한 result.html 탬플릿 코드를 완성한다. 

<h1>{{question.question_text}}</h1>

<ul>
    {%for choice in question.choice_set.all%}
    <li>{{choice.choice_text}} -- {{choice.votes}} vote{{choice.votes|pluralize}}</li>
    {%endfor%}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

 

generic view 사용하기

장고에서 view를 클래스로 구현하면, 제너릭 뷰(Generic view)를 사용할 수 있다. 

제너릭 뷰는 장고에서 기본으로 제공하는 뷰 클래스를 의미한다. 

장고 제너릭 뷰를 상속받아 메소드를 재정의하면 편리하게 작업할 수 있다. 

 

[참고] [Django] 제네릭 뷰(Generic View) 살펴보기 (velog.io)

 

제너릭 뷰를 사용하기 위해 해야할 것

- URLconf 수정

- views.py 수정

- generic view 클래스 도입

 

우선, URL에 매핑된 views 함수를 수정해준다.

from django.urls import path
from . import views

app_name = 'polls'

urlpatterns = [
    # /polls/
    path('', views.IndexView.as_view(), name='index'),
    # /polls/5
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    # /polls/5/results/
    path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
    # /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

 

그 다음 views.py 함수에서 구식 부분을 삭제하고 generic view를 도입해보자. 

from django.views import generic

# Create your views here.

class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'
    
    def get_queryset(self):
        return Question.objects.order_by('-pub_date')[:5]

class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'

class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'

굉장히 깔끔해졌다. 

 

여기서 상속받은 뷰는 generic.ListView와 generic.DetailView다. 

ListView는 객체 리스트를 보여주는 걸 추상화한 클래스이고, 

DetailView는 객체의 디테일을 보여주는 페이지를 추상화한 클래스이다. 

 

이때 DetailView는 호출 URL 인자에 꼭 pk가 들어가있어야 한다. 

 

[추가 참고] Class-based views | Django documentation | Django (djangoproject.com)