Написание вашего первого приложения Django, часть 4
Этот учебник начинается там, где закончился Учебник 3. Мы продолжаем работу над приложением web-опроса и сфокусируемся на обработке форм и сокращении кода.
Где получить помощь:
Если у вас возникли трудности с этим учебником, пожалуйста, обратитесь к разделу Получение помощи раздела FAQ.
Напишите минимальную форму
Давайте обновим шаблон подробностей опроса (“polls/detail.html”) из последнего учебника, чтобы шаблон содержал HTML <form> элемент:
polls/templates/polls/detail.html<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</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>
Краткое описание:
- Вышеприведенный шаблон отображает радиокнопку для каждого варианта ответа на вопрос.
valueкаждой радиокнопки — это идентификатор соответствующего варианта ответа на вопрос.nameкаждой радиокнопки —"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/urls.pypath("<int:question_id>/vote/", views.vote, name="vote"),
Мы также создали фиктивную реализацию функции vote(). Давайте создадим реальную версию. Добавьте следующее в polls/views.py:
polls/views.pyfrom django.db.models import F
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 = F("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не указан. -
F("votes") + 1инструктирует базу данных увеличить счетчик голосов на 1. -
После увеличения счетчика выбранного варианта код возвращает
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() перенаправит пользователя на страницу результатов для этого вопроса. Давайте напишем это представление:
polls/views.pyfrom 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:
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() (из Урока 3) и results() очень короткие — и, как упоминалось выше, избыточны. Представление index(), которое отображает список опросов, аналогично.
Эти представления представляют собой общий случай веб-разработки: получение данных из базы данных в соответствии с параметром, переданным в URL, загрузка шаблона и возврат рендеренного шаблона. Поскольку это так распространено, Django предоставляет сокращение, называемое системой «универсальных представлений».
Универсальные представления абстрагируют общие паттерны до такой степени, что вам даже не нужно писать код Python, чтобы написать приложение. Например, универсальные представления ListView и DetailView абстрагируют понятия «отображение списка объектов» и «отображение страницы подробностей для определённого типа объекта» соответственно.
Давайте переведём наше приложение опросов на использование универсальных представлений, чтобы удалить кучу собственного кода. Нам придётся сделать несколько шагов для конвертации.
- Преобразовать URLconf.
- Удалить некоторые старые, не нужные представления.
- Ввести новые представления, основанные на универсальных представлениях Django.
Подробности читайте далее.
Почему перестановка кода?
Как правило, при написании приложения Django вы оцените, подходят ли вам универсальные представления для вашей задачи, и будете использовать их с самого начала, а не переписывать код на полпути. Но этот учебник намеренно сосредоточился на написании представлений «трудным способом» до сих пор, чтобы сосредоточиться на основных понятиях.
Вы должны знать основы математики, прежде чем начать использовать калькулятор.
Изменение URLconf
Сначала откройте файл polls/urls.py URLconf и измените его следующим образом:
polls/urls.pyfrom 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>. Это необходимо, так как мы будем использовать универсальное представление DetailView для замены наших представлений detail() и results(), и оно ожидает, что значение первичного ключа, полученное из URL, будет называться "pk".
Изменение представлений
Далее, мы удалим наши старые представления index, detail и results и вместо этого будем использовать универсальные представления Django. Для этого откройте файл polls/views.py и измените его следующим образом:
polls/views.pyfrom django.db.models import F
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.
...
Каждому универсальному представлению нужно знать, к какой модели оно будет применяться. Это обеспечивается с помощью атрибута model (в этом примере model =
Question для DetailView и ResultsView) или путём определения метода get_queryset() (как показано в IndexView).
По умолчанию универсальное представление 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/5.2/intro/tutorial04/