Введение
Этот туториал начинается там, где закончился раздел Создание представлений для общедоступного интерфейса. Мы продолжаем работу над веб-опросом и сосредоточимся на обработке форм и сокращении кода.
💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал
Этот туториал начинается там, где закончился раздел Создание представлений для общедоступного интерфейса. Мы продолжаем работу над веб-опросом и сосредоточимся на обработке форм и сокращении кода.
Обновим шаблон деталей опроса (polls/detail.html
) из предыдущего туториала, чтобы он содержал HTML-элемент <form>
:
<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
каждой радиокнопки — это ID соответствующего варианта ответа на вопрос. name
каждой радиокнопки — "choice"
. Это означает, что когда кто-то выбирает одну из радиокнопок и отправляет форму, она отправит POST-данные choice=#
, где ## — это ID выбранного варианта. Это основная концепция HTML-форм.action
формы на {% url 'polls:vote' question.id %}
, и установили method="post"
. Использование method="post"
(в отличие от method="get"
) очень важно, потому что отправка этой формы изменит данные на стороне сервера. Когда вы создаете форму, которая изменяет данные на стороне сервера, используйте method="post"
. Этот совет не специфичен для Django; это хорошее правило веб-разработки в целом.forloop.counter
показывает, сколько раз тег for
прошел по циклу{% csrf_token %}<csrf_token>
.Теперь создадим представление Django, которое обрабатывает отправленные данные и делает с ними что-то. Помните, в разделе Создание представлений для общедоступного интерфейса мы создали URL-конфигурацию для приложения опросов, которая включает в себя эту строку:
path("<int:question_id>/vote/", views.vote, name="vote"),
Мы также создали заглушку реализации функции vote()
. Создадим настоящую версию. Добавьте следующее в 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 <django.http.HttpRequest.POST>
— это объект, похожий на словарь, который позволяет вам получать отправленные данные по имени ключа. В этом случае request.POST['choice']
возвращает ID выбранного варианта в виде строки. request.POST <django.http.HttpRequest.POST>
значения всегда являются строками.
Обратите внимание, что Django также предоставляет request.GET <django.http.HttpRequest.GET>
для доступа к GET-данным аналогичным образом — но мы явно используем request.POST <django.http.HttpRequest.POST>
в нашем коде, чтобы убедиться, что данные изменяются только через вызов POST.
request.POST['choice']
вызовет KeyError
, если choice
не был предоставлен в POST-данных. Код выше проверяет наличие KeyError
и переотображает форму вопроса с сообщением об ошибке, если choice
не указан.
После увеличения количества голосов код возвращает ~django.http.HttpResponseRedirect
, а не обычный ~django.http.HttpResponse
. ~django.http.HttpResponseRedirect
принимает один аргумент: URL-адрес, на который будет перенаправлен пользователь (см. следующий пункт, как мы строим URL-адрес в этом случае).
Как указывает комментарий на Python выше, вы должны всегда возвращать ~django.http.HttpResponseRedirect
после успешной обработки POST-данных. Этот совет не специфичен для Django; это хорошее правило веб-разработки в целом.
В этом примере мы используем функцию ~django.urls.reverse
в конструкторе ~django.http.HttpResponseRedirect
. Эта функция помогает избежать жесткой кодировки URL-адреса в функции представления. Ей передается имя представления, на которое мы хотим передать управление, и переменная часть URL-шаблона, которая указывает на это представление. В этом случае, используя URL-конфигурацию, которую мы установили в разделе Создание представлений для общедоступного интерфейса, вызов ~django.urls.reverse
вернет строку, похожую на:
"/polls/3/results/"
где 3
— это значение question.id
. Затем этот перенаправленный URL-адрес вызовет представление 'results'
, чтобы отобразить конечную страницу.
Как упоминалось в разделе Создание представлений для общедоступного интерфейса, request
— это объект ~django.http.HttpRequest
. Для получения дополнительной информации о объектах ~django.http.HttpRequest
см. документацию по запросам и ответам </ref/request-response>.
После того, как кто-то голосует за вопрос, представление 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()
из раздела Создание представлений для общедоступного интерфейса. Единственная разница — это имя шаблона. Мы исправим эту избыточность позже.
Теперь создайте шаблон 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/
в вашем браузере и голосуйте за вопрос. Вы должны увидеть страницу результатов, которая обновляется каждый раз, когда вы голосуете. Если вы отправите форму, не выбрав вариант ответа, вы должны увидеть сообщение об ошибке.
cd ~/project/mysite
python manage.py runserver 0.0.0.0:8080
Примечание:
В коде нашего представления vote()
есть небольшая проблема. Он сначала получает объект selected_choice
из базы данных, затем вычисляет новое значение votes
, а затем сохраняет его обратно в базу данных. Если два пользователя вашего сайта попытаются проголосовать ровно одновременно, это может пойти не так: для votes
будет получено одно и то же значение, скажем 42. Затем для обоих пользователей вычисляется новое значение 43 и сохраняется, но ожидаемым значением было бы 44.
Это называется условием гонки. Если вы заинтересованы, вы можете прочитать статью avoiding-race-conditions-using-f
, чтобы узнать, как можно решить эту проблему.
Представления detail()
(из раздела Создание представлений для общедоступного интерфейса) и results()
очень короткие — и, как упоминалось выше, избыточны. Аналогично выглядит представление index()
, которое отображает список опросов.
Эти представления представляют собой общий случай базовой веб-разработки: получение данных из базы данных в соответствии с параметром, переданным в URL, загрузка шаблона и возврат отрендеренного шаблона. Поскольку это так часто встречается, Django предоставляет сокращение, называемое системой «общих представлений».
Общие представления абстрагируют общие шаблоны настолько, что вам даже не нужно писать Python-код для создания приложения.
Преобразуем наше приложение с опросами для использования системы общих представлений, чтобы можно было удалить кучу нашего собственного кода. Для этого нам придется предпринять несколько шагов:
Дальше вы найдете подробности.
Почему перемешивать код?
一般来说,在编写Django应用程序时,您会评估通用视图是否适合您的问题,并从一开始就使用它们,而不是在中途重构代码。但本教程到目前为止有意专注于“艰难地”编写视图,以专注于核心概念。
在开始使用计算器之前,您应该先了解基本数学。
首先,打开URL-конфигурацию 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>
。
接下来,我们将删除旧的 index
、detail
和 results
视图,并改用Django的通用视图。为此,打开文件 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):
... ## то же, что и выше, изменения не требуются.
我们在这里使用了两个通用视图:~django.views.generic.list.ListView
和 ~django.views.generic.detail.DetailView
。相应地,这两个视图分别抽象了“显示对象列表”和“显示特定类型对象的详细页面”的概念。
model
属性提供。~django.views.generic.detail.DetailView
期望从URL捕获的主键值称为 "pk"
,因此我们将通用视图的 question_id
更改为 pk
。默认情况下,通用视图 ~django.views.generic.detail.DetailView
使用名为 <应用名称>/<模型名称>_detail.html
的模板。在我们的例子中,它将使用模板 "polls/question_detail.html"
。template_name
属性用于告诉Django使用特定的模板名称,而不是自动生成的默认模板名称。我们还为结果列表视图指定了 template_name
—— 这确保了结果视图和详细视图在渲染时具有不同的外观,尽管它们在幕后都是 ~django.views.generic.detail.DetailView
。
类似地,通用视图 ~django.views.generic.list.ListView
使用默认模板 <应用名称>/<模型名称>_list.html
;我们使用 template_name
告诉 ~django.views.generic.list.ListView
使用我们现有的 "polls/index.html"
模板。
在本教程的前几部分中,模板已提供了一个上下文,其中包含 question
和 latest_question_list
上下文变量。对于 DetailView
,question
变量会自动提供 —— 因为我们使用的是Django模型 (Question
),Django能够为上下文变量确定合适的名称。但是,对于 ListView
,自动生成的上下文变量是 question_list
。为了覆盖此设置,我们提供了 context_object_name
属性,指定我们希望使用 latest_question_list
代替。作为另一种方法,您可以更改模板以匹配新的默认上下文变量 —— 但告诉Django使用您想要的变量要容易得多。
运行服务器,并使用基于通用视图的新投票应用程序。
Поздравляем! Вы завершили лабораторную работу по обработке форм и сокращению кода. Вы можете практиковаться в других лабораторных работах в LabEx, чтобы улучшить свои навыки.