Написание вашего первого приложения Django, часть 4
Этот учебник начинается там, где закончился Урок 3. Мы продолжаем приложение Web-poll и сосредоточимся на простой обработке форм и сокращении кода.
Создайте простую форму
Обновим шаблон детали опроса («polls/detail.html») из предыдущего урока, чтобы шаблон содержал 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>
Краткое описание:
- Вышеприведенный шаблон отображает переключатель для каждого варианта ответа. Идентификатор каждого переключателя — это ID соответствующего варианта ответа. Значение каждого переключателя —
"choice". Это означает, что когда кто-то выбирает один из переключателей и отправляет форму, данные POSTchoice=#, где # — это ID выбранного варианта. Это базовый принцип HTML-форм. - Мы установили атрибут формы
actionв значение{% url 'polls:vote' question.id %}, и мы установилиmethod="post". Использованиеmethod="post"(в отличие отmethod="get") очень важно, потому что отправка этой формы изменит данные на стороне сервера. Всякий раз, когда вы создаете форму, изменяющую данные на стороне сервера, используйтеmethod="post". Этот совет не специфичен для Django; это просто хорошая практика веб-разработки. -
forloop.counterпоказывает, сколько раз тегforпрошел по своему циклу - Поскольку мы создаем форму POST (которая может изменить данные), нам нужно беспокоиться о межсайтовых поддельных запросах. К счастью, вам не нужно слишком беспокоиться, потому что Django поставляется с очень простым в использовании механизмом защиты от них. Короче говоря, все формы POST, которые направлены на внутренние URL-адреса, должны использовать тег шаблона
{% csrf_token %}.
Теперь давайте создадим представление Django, которое обрабатывает отправленные данные и делает с ними что-то. Помните, что в Уроке 3 мы создали URLconf для приложения polls, который включает эту строку:
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
Мы также создали фиктивную реализацию функции vote(). Давайте создадим реальную версию. Добавьте следующее в polls/views.py:
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):
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— это объект, похожий на словарь, который позволяет получать доступ к отправленным данным по имени ключа. В этом случаеrequest.POST['choice']возвращает ID выбранного варианта в виде строки. Значенияrequest.POSTвсегда являются строками.Обратите внимание, что Django также предоставляет
request.GETдля доступа к данным GET аналогичным образом — но мы явно используемrequest.POSTв нашем коде, чтобы убедиться, что данные изменяются только через вызов POST. -
request.POST['choice']будет подниматьKeyError, еслиchoiceне был передан в данных POST. Приведенный выше код проверяет наKeyErrorи повторно отображает форму вопроса с сообщением об ошибке, еслиchoiceне указано. -
После инкрементирования счетчика выбора код возвращает
HttpResponseRedirectвместо обычногоHttpResponse.HttpResponseRedirectпринимает один аргумент: URL-адрес, на который будет перенаправлен пользователь (см. следующий пункт, чтобы понять, как мы строим URL-адрес в этом случае).Как указано в комментарии Python выше, вы всегда должны возвращать
HttpResponseRedirectпосле успешной обработки данных POST. Этот совет не специфичен для Django; это просто хорошая практика веб-разработки. -
Мы используем функцию
reverse()в конструктореHttpResponseRedirectв этом примере. Эта функция помогает избежать необходимости жестко кодировать URL-адрес в функции представления. Ей передается имя представления, которому мы хотим передать управление, и переменная часть URL-шаблона, которая указывает на это представление. В данном случае, используя URLconf, который мы настроили в Уроке 3, этот вызовreverse()вернет строку типа'/polls/3/results/'
где
3— это значениеquestion.id. Этот перенаправленный URL-адрес затем вызовет представление'results'для отображения окончательной страницы.
Как упоминалось в Уроке 3, request — это объект HttpRequest. Дополнительную информацию об объектах HttpRequest см. в документации по запросам и ответам.
После того, как кто-то проголосует за вопрос, представление vote() перенаправит пользователя на страницу результатов для этого вопроса. Давайте напишем это представление:
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() из Урока 3. Единственное отличие — имя шаблона. Мы исправим эту избыточность позже.
Теперь создайте шаблон 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/ в вашем браузере и проголосуйте за вопрос. Вы должны увидеть страницу результатов, которая обновляется каждый раз, когда вы голосуете. Если вы отправите форму, не выбрав вариант, вы увидите сообщение об ошибке.
Примечание
Код нашего представления vote() имеет небольшую проблему. Он сначала получает объект selected_choice из базы данных, затем вычисляет новое значение votes, а затем сохраняет его обратно в базу данных. Если два пользователя вашего веб-сайта попробуют проголосовать в один и тот же момент времени, это может пойти не так: одно и то же значение, скажем, 42, будет извлечено для votes. Затем для обоих пользователей будет вычислено и сохранено новое значение 43, но ожидалось бы значение 44.
Это называется голосованием. Если вас интересует, вы можете прочитать Избегание гонок с использованием F(), чтобы узнать, как можно решить эту проблему.
Использование универсальных представлений: меньше кода — лучше
Представления detail() (из Урока 3) и results() очень простые — и, как упоминалось выше, избыточные. Представление index(), отображающее список опросов, похожее.
Эти представления представляют собой общий случай базовой веб-разработки: получение данных из базы данных в соответствии с параметром, переданным в URL, загрузка шаблона и возвращение рендерного шаблона. Поскольку это так часто встречается, Django предоставляет сокращение, называемое системой «универсальных представлений».
Универсальные представления абстрагируют общие шаблоны до такой степени, что вам даже не нужно писать код Python, чтобы написать приложение.
Давайте переведем наше приложение polls на систему универсальных представлений, чтобы мы могли удалить кучу собственного кода. Нам нужно будет предпринять несколько шагов для перевода. Мы будем:
- Преобразовать URLconf.
- Удалить некоторые старые ненужные представления.
- Ввести новые представления на основе универсальных представлений Django.
Подробнее об этом ниже.
Почему перестановка кода?
Как правило, при написании приложения Django вы оцениваете, подходят ли универсальные представления для вашей задачи, и используете их с самого начала, а не перерабатываете код на полпути. Но этот учебник намеренно сосредотачивался на написании представлений «сложным способом» до сих пор, чтобы сосредоточиться на основных концепциях.
Вам нужно знать основы математики, прежде чем вы начнете использовать калькулятор.
Изменить URLconf
Сначала откройте polls/urls.py URLconf и измените его следующим образом:
from django.conf.urls import url
from . import views
app_name = 'polls'
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):
... # same as above, no changes needed.
Здесь мы используем два универсальных представления: 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 – это гарантирует, что вид результатов и вид детали имеют разный вид при рендеринге, хотя они оба являются DetailView за кулисами.
Аналогично, обобщённый вид 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 вместо этого. В качестве альтернативного подхода, вы можете изменить свои шаблоны, чтобы соответствовать новым переменным контекста по умолчанию – но намного проще просто указать Django, какую переменную вы хотите использовать.
Запустите сервер и используйте ваше новое приложение опросов, основанное на обобщённых видах.
Подробную информацию об обобщённых видах см. в документации по обобщённым видам.
Когда вы будете уверены в формах и обобщённых видах, прочтите часть 5 этого учебника, чтобы узнать о тестировании нашего приложения опросов.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/intro/tutorial04/