본문 바로가기

Python/Django

Django 예제 / Writing your first Django app, part 4 / Froms and generic views

Django 공식 문서 v3.0 Tutorial

https://docs.djangoproject.com/en/3.0/intro/tutorial04/

 

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

https://github.com/JisunParkRea/django-tutorial-mypractice

 

JisunParkRea/django-tutorial-mypractice

This is django-tutorial practice code referencing django official documentation. - JisunParkRea/django-tutorial-mypractice

github.com

 

Minimal form 쓰기

우선, admin page에 Choice를 관리부분을 넣기 위해

polls/admin.py를 다음과 같이 고치자

from django.contrib import admin

from .models import Question
from .models import Choice

admin.site.register(Question)
admin.site.register(Choice)

결과:

 

그리고 Template가 HTML <form> element를 포함하도록

poll detail template(polls/detail.html) 코드를 다음과 같이 수정하자.

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

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% 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 %}
<input type="submit" value="Vote">
</form>

- choice를 radio button의 형태로 보여준다.

- 해당 radio button을 선택할 시 POST data choice=# 를 전달하게 된다.(#는 선택된 choice의 ID)

- action은 {% url 'polls:vote' question.id %} 로 해놓고 method="post"로 해놓는다.

- 이는 server-side의 data를 바꾸는 form이다.

- 모든 POST forms(internal URLs를 사용하는)는 {% csrf_token %} template tag를 사용해야 한다.

 

결과:

이제는 submit된 data를 다루는 view를 만들어야 한다.

전에 만든 polls application의 URLconf와 vote() function을 실제로 기능하게 해보자.

 

polls/views.py을 다음과 같이 고쳐보자.

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

from .models import Choice, Question
# ...
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 form.
        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. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

- request.POST['choice']선택된 choice의 ID(항상 string으로)를 return한다. 만약 choice가 제공되지 않으면 KeyError을 발생시킨다. 위의 코드는 이를 체크하는 기능까지 포함시킨 것이다.

- choice count를 증가시킨 후에, 보통의 HttpResponse가 아닌 HttpResponseRedirect를 return하게 한다. HttpResponse는 single argument를 받는데, 이는 user이 redirect될 URL이다.

- POST data를 성공적으로 다루고 난 뒤엔 항상 HttpResponseRedirect를 return해야한다. 이는 단지 Django에만 국한된 것이 아닌 일반적으로 'good Web development practice'이다.

- HttpResponseRedirect 생성자에서 사용된 reverse() 함수는 view 함수에서 URL을 hardcode해야하는 상황을 피하게 한다. 통제를 전달할 view의 이름과 view를 가리키는 URL 패턴의 변수 부분이 주어져야 한다.

 

vote() view가 result 페이지로 redirect할 수 있도록

polls/views.py를 다음과 같이 고쳐보자.

from django.shortcuts import get_object_or_404, render


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

 

그리고 polls/templates/polls/results.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>

투표 결과 페이지

* 그런데, 여기 vote() view에서 만약 2명 이상이 정확히 같은 시간에 vote를 하면 문제가 생긴다.

예를 들어, 2명이 vote를 하면 +2가 되어야 하는데 같은 시점에서 하면 +1밖에 안된다.

이를, race condition이라 한다,

 

generic view 사용하기: Less code is better

detail(), results(), 그리고 index() view들은 짧고 불필요하다.

이런 view들은 기본 웹 개발의 일반적인 경우를 나타내는데,

이는 URL로 패스되는 parameter에 따라 데이터베이스에서 데이터를 꺼내고, template을 load시키고 이를 return하는 것을 말한다.

이런 일들을 하는 shortcut으로, Django에서 이를 Generic view로 제공을 한다.

 

그럼 우리 poll app을 generics view system을 사용하도록 변환시켜보자.

1. URLconf 변환하기

2. 불필요한 view 삭제

3. Django의 generic view를 사용하여 새로운 view 만들기

 

1. URLconf 고치기

polls/urls.py를 다음과 같이 고쳐보자.

from django.urls import path

from . import views

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

<question_id> 에서 <pk>로 바꾼 것에 주의하기!

 

2, 3. View 고치기

polls/views.py를 다음과 같이 고치자.

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic

from .models import Choice, Question


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """Return the last five published questions."""
        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'


def vote(request, question_id):
    ... # same as above, no changes needed.

- 여기서 2개의 generic view를 사용했다:

   - ListView: display a list of objects

   - DetailView: display a detail page for a particular type of object

 

 

끝!