Spec-Zone.ru › Django 4.2

Написание вашего первого приложения Django, часть 4

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

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

Если у вас возникли трудности с этим учебником, перейдите к разделу Получение помощи раздела часто задаваемых вопросов.

Создание минимальной формы

Обновим шаблон детали опроса (“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 (которая может привести к изменению данных), нам нужно позаботиться о межсайтовых поддельных запросах. К счастью, вам не нужно слишком сильно беспокоиться, так как Django поставляется с полезной системой защиты от неё. Короче говоря, все формы POST, направленные на внутренние URL-адреса, должны использовать тег шаблона {% csrf_token %}.

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

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

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

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 — это объект, подобный словарю, который позволяет вам получать доступ к отправленным данным по имени ключа. В этом случае request.POST['choice'] возвращает идентификатор выбранного варианта в виде строки. Значения 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() перенаправляет на страницу результатов для этого вопроса. Давайте напишем это представление:

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/ в вашем браузере и проголосуйте за вопрос. Вы должны увидеть страницу результатов, которая обновляется каждый раз, когда вы голосуете. Если вы отправите форму, не выбрав вариант ответа, вы увидите сообщение об ошибке.

Примечание

Код нашего представления vote() имеет небольшую проблему. Он сначала получает объект selected_choice из базы данных, затем вычисляет новое значение votes, а затем сохраняет его обратно в базу данных. Если два пользователя вашего сайта попытаются проголосовать в одинаковое время, это может привести к ошибке: для обоих пользователей будет получено одинаковое значение, скажем, 42. Затем для обоих пользователей будет вычислено и сохранено новое значение 43, но ожидалось бы значение 44.

Это называется голосовой гонкой. Если вас это интересует, вы можете прочитать Избежание гонок с помощью F(), чтобы узнать, как можно решить эту проблему.

Использование общих представлений: меньше кода — лучше

Представления detail() (из Урока 3) и results() очень короткие — и, как упоминалось выше, избыточные. Аналогично представление index(), отображающее список опросов.

Эти представления представляют собой распространённый случай базовой разработки веб-приложений: получение данных из базы данных в соответствии с параметром, переданным в URL, загрузка шаблона и возвращение отрендеренного шаблона. Поскольку это очень распространённый случай, Django предоставляет сокращение, называемое системой «общих представлений».

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

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

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

Подробности см. ниже.

Почему такая перестановка кода?

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

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

Изменение URLconf

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

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 и измените его следующим образом:

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):
    ...  # 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/4.2/intro/tutorial04/

Spec-Zone.ru

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