Содержание
Пишем простую форму
Давайте обновим шаблон деталей вопроса в нашем приложении голосования (файл polls/detail.html
) из предыдущей части.
Добавим в него HTML элемент <form>
:
<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>
Краткое описание:
- этот шаблон отображает переключатель для каждого варианта ответа.
value
каждого переключателя связана с ID варианта ответа вопроса.name
каждого переключателя= "choice"
. Это значит, что когда кто-то выбирает один из вариантов, и подтверждает действие (type=submit
) – форма отправляет POST-запрос с даннымиchoice=#
, где#
– это ID выбранногоchoice
. Это базовая концепция HTML-форм. - мы установили
action
формы{% url 'polls:vote' question.id %}
, и метод POST. Использование метода POST (в отличи от метода GET) очень важно, так как действие подтверждения формы затрагивает сервер. Каждый раз, когда вы создаёте форму, которая имеет какое-то влияние на сервер – используйтеmethod="post"
. Это касается не только Django – а вообще хорошая практика в веб-разработке. forloop.counter
подсчитывает, сколько раз сработал тегfor
;- так как мы создаём форму POST (которая выполняет модификацию данных) – нам необходимо побеспокоиться о Межсайтовой Подделке Запросов (Cross Site Request Forgeries, XSRF). К счастью – нам не надо переживать об этом сильно много, так как Django имеет встроенную систему защиты от неё. Вкратце – все формы с POST, которые направлены на внутренние URL-ы должны использовать тег
{% csrf_token %}
.
Теперь – давайте создадим представление, которое будет обрабатывать переданные данные, и что-то с ними делать. В предыдущей части мы уже создали URLconf
для нашего приложения polls
, который включает в себя такую строку:
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
Так же – мы создали простую функцию vote()
. Давайте её приведём к более полезному виду:
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect, HttpResponse from django.core.urlresolvers import reverse from .models import Choice, Question # ... def vote(request, question_id): p = get_object_or_404(Question, pk=question_id) try: selected_choice = p.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): # отобразить форму голосования заново return render(request, 'polls/detail.html', { 'question': p, 'error_message': "You didn't select a choice.", }) else: selected_choice.votes += 1 selected_choice.save() # всегда выполняйте возврат HttpResponseRedirect после успешной обработки POST # Это предотвращает двойное использование данных # если пользователь нажимает кнопку Назад return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))
В этом коде встречаются вещи, с которыми мы ещё не встречались:
request.POST
– это объект-словарь, который позволяет вам обращаться к переданным послеsubmit
данным по имени:ключу. В данном случае –request.POST['choice']
возвращает ID выбранного голоса в виде строки; помните, что Django также предоставляетrequest.GET
для обращения к данным GET;request.POST['choice']
вызоветKeyError
, если в данных POST не былоchoice
; код выше проверяет на предметKeyError
и отображает форму голосования заново с сообщением об ошибке, еслиchoice
не был передан;- после увеличения счётчика голосов – код возвращает
HttpResponseRedirect
, а не обычныйHttpResponse
.HttpResponseRedirect
принимает один аргумент – URL, на который пользователь будет перенаправлен (смотрите ниже – как именно мы сконструировали URL в данном случае); - мы используем функцию
reverse()
в конструктореHttpResponseRedirect
; эта функция помогает избежать использования URL-ов, вписанных непосредственно в код представления; ему передаётся имя функции, которой мы передаём управление, а часть переменной шаблона URL-а, который указывает на это представление; в данном случае – используяURLconf
, который мы настроили в предыдущей части, вызовreverse()
вернёт строку вида ‘/polls/3/results/‘, где “3” – это значениеp.id
; этот вызов переадресации вызовет представлениеresult()
, что бы отобразить результаты
Как уже упоминалось в четвёртой части – request
это объект HttpRequest
. Больше информации о HttpRequest
можно получить на странице request and response documentation.
После того, как кто-то проголосует в вопросе, представление vote()
перенаправит его на страницу результатов голосования по этому вопросу.
Давайте перепишем представление results()
так:
... 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}) ...
Теперь оно выглядит практически так же, как и функция detail()
из предыдущей части. Разница только в названии файла шаблона.
Далее – создайте шаблон 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>
А теперь – перейдите на страницу /polls/1/ и проголосуйте в вашем вопросе. Вы должны увидеть страницу с результатами голосования. Если вы нажмёте на “Голосовать” без выбора варианта ответа – должна отобразиться ошибка:
Использование общих представлений: меньше кода – лучше
Функции detail()
и result()
очень простые и избыточные. То же касается и функции index()
, которая отображает список вопросов.
Эти представления являются распространёнными случаем в веб-разработке: получение данных из базы данных в соответствии с параметром, переданным в URL-е, загрузка шаблона и возврат его отрендеренного варианта. Так как эти задачи часто используются – Django предоставляет систему, которая называется “общие представления” (generic views).
Общие представления являются некими шаблонами, при использовании которых вам иногда можно вообще не писать код на Python.
Давайте изменим наше приложение-голосование на использование таких общих шаблонов – так мы сможем удалить значительную часть кода.
Для этого нам потребуется выполнить всего несколько шагов. Наши действия:
- изменим
URLconf
; - удалим некоторые старые, уже не нужные, представления;
- добавим новые представления, основанные на общих представлениях Django.
Меняем URLconf
Для начала – откройте файл polls/urls.py
, и измените URLconf
так:
from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'), url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]
Обратите внимание, что некоторые шаблоны совпадений в регулярных выражениях на второй и третьей строке изменились с <question_id>
на <pk>
.
Меняем представления
Теперь – давайте удалим старые представления index
, detail
и results
и используем вместо них общие представления Django. Что бы сделать это – откройте файл polls/views.py
и измените его следующим образом:
from django.shortcuts import get_object_or_404, render from django.http import HttpResponseRedirect from django.core.urlresolvers 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): # оставьте без изменений
Тут мы использовали два общих представления – ListView
и DetailView
. Эти два представления являются соответственно концепциями задач “отобразить список объектов” и “отобразить страницу деталей для определённого типа объекта“.
- каждое общее представление должно знать над какой моделью ему предстоит выполнять действия; это достигается с помощью атрибута
model
; - представление
DetailView
ожидает получения первичного ключа, захваченного из URL-а, с именем “pk
” – поэтому мы изменилиquestion_id
наpk
.
По умолчанию – DetailView
использует шаблон с именем <app name>/<model name>_detail.html
. В нашем случае – мы явно указали файл polls/question_detail.html
. Атрибут template_name
используется, что бы указать Django на использование конкретного шаблона вместо шаблона по умолчанию. Мы так же указали template_name
для results
– так мы можем быть уверены, что представления results
и detail
будут использовать различный вид после рендеринга.
Аналогично представление ListView
по умолчанию использует шаблон <app name>/<model name>_list.html
. Мы использовали template_name
и тут, что бы указать ListView
на использование уже имеющегося шаблона polls/index.html
.
В предыдущих частях руководства шаблоны использовали контекст, который содержал переменные question
и latest_question_list
. Для DetailView
переменная question
создаётся автоматически: так как мы используем модель Django (Question
) – Django в состоянии определить наиболее подходящее имя для переменной контекста. Однако, для ListView
автоматически сгенерированная переменная будет question_list
. Что бы изменить это – мы указали атрибут context_object_name
, в котором передали имя переменной latest_question_list
.
Запустите ваш сервер разработки – и используйте ваше новое приложение, основанное для общих шаблонах.