Содержание
Пишем простую форму
Давайте обновим шаблон деталей вопроса в нашем приложении голосования (файл 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.
Запустите ваш сервер разработки — и используйте ваше новое приложение, основанное для общих шаблонах.






