Spec-Zone.ru › Django 6.0

Создание первого приложения Django, часть 4

Это руководство начинается там, где завершилась часть 3 руководства. Мы продолжим работу над веб-приложением для опросов и сосредоточимся на обработке форм и сокращении кода.

Где получить помощь:

Если у вас возникли трудности при прохождении этого руководства, обратитесь к разделу Получение помощи в 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". Это означает, что когда пользователь выберет один из переключателей и отправит форму, будут отправлены POST-данные choice=#, где # — идентификатор выбранного варианта ответа. Так работают HTML-формы.
  • Мы задали для формы значение action равным {% url 'polls:vote' question.id %} и указали method="post". Использовать method="post" (а не method="get") очень важно, поскольку отправка этой формы изменит данные на сервере. Всегда используйте method="post" при создании форм, изменяющих данные на сервере. Этот совет относится не только к Django — это хорошая практика веб-разработки в целом.
  • forloop.counter показывает, сколько раз тег for выполнил цикл
  • Поскольку мы создаём POST-форму (которая может изменить данные), нужно учитывать возможность межсайтовой подделки запросов (CSRF). К счастью, Django предоставляет удобную систему защиты от таких атак. Если коротко, во всех POST-формах, отправляемых на внутренние URL-адреса, следует использовать тег шаблона {% csrf_token %}.

Теперь создадим представление Django, которое обработает отправленные данные и выполнит необходимые действия. Помните, что в части 3 руководства мы создали URLconf приложения polls, включающий следующую строку:

polls/urls.py
path("<int:question_id>/vote/", views.vote, name="vote"),

Мы также создали заглушку для функции vote(). Давайте создадим её настоящую версию. Добавьте следующий код в polls/views.py:

polls/views.py
from 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'] возвращает идентификатор выбранного варианта ответа в виде строки. Значения 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 выше, после успешной обработки POST-данных всегда следует возвращать HttpResponseRedirect. Этот совет относится не только к Django — это хорошая практика веб-разработки в целом.

  • В этом примере в конструкторе HttpResponseRedirect мы используем функцию reverse(). Эта функция позволяет не прописывать URL-адрес непосредственно в функции представления. Ей передаются имя представления, которому нужно передать управление, и переменная часть шаблона URL-адреса, указывающего на это представление. В данном случае, при использовании URLconf, настроенного в части 3 руководства, вызов reverse() вернёт строку вида

    "/polls/3/results/"
    

    где 3 — это значение question.id. Затем по этому URL-адресу будет вызвано представление 'results', которое отобразит итоговую страницу.

Как упоминалось в части 3 руководства, request — это объект HttpRequest. Подробнее об объектах HttpRequest читайте в документации по запросам и ответам.

После того как пользователь проголосует в опросе, представление vote() перенаправит его на страницу результатов опроса. Давайте напишем это представление:

polls/views.py
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:

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 абстрагируют понятия «отобразить список объектов» и «отобразить страницу с подробной информацией об объекте определённого типа» соответственно.

Давайте переведём наше приложение для опросов на систему универсальных представлений, чтобы удалить значительную часть собственного кода. Для этого нужно выполнить несколько шагов:

  1. Изменить URLconf.
  2. Удалить некоторые старые, ненужные представления.
  3. Добавить новые представления на основе универсальных представлений Django.

Подробности — далее.

Зачем переставлять код?

Обычно при разработке приложения Django вы оцениваете, подходят ли универсальные представления для вашей задачи, и используете их с самого начала, а не переписываете код на полпути. Однако в этом руководстве мы намеренно до этого момента создавали представления «сложным способом», чтобы сосредоточиться на основных концепциях.

Прежде чем пользоваться калькулятором, нужно знать основы математики.

Изменение URLconf

Сначала откройте URLconf приложения polls/urls.py и измените его следующим образом:

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>. Это необходимо, поскольку мы заменим представления detail() и results() универсальным представлением DetailView, которое ожидает, что значение первичного ключа, извлечённое из URL-адреса, будет называться "pk".

Изменение представлений

Теперь мы удалим старые представления index, detail и results и заменим их универсальными представлениями Django. Для этого откройте файл polls/views.py и измените его следующим образом:

polls/views.py
from 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 этого руководства, чтобы узнать, как тестировать приложение polls.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/intro/tutorial04/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API