Spec-Zone.ru › Django 3.2

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

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

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

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

Напишите минимальную форму

Обновим шаблон детали опроса (“polls/detail.html”) из предыдущего урока, чтобы шаблон содержал HTML-элемент <form>.

polls/templates/polls/detail.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>

Краткое описание:

  • Вышеприведенный шаблон отображает радиокнопку для каждого варианта ответа. 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 прошёл через цикл.
  • Поскольку мы создаём форму POST (которая может влиять на изменение данных), нам нужно беспокоиться о межсайтовых поддельных запросах. К счастью, вам не нужно слишком беспокоиться, так как 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.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'] возвращает 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() перенаправляет на страницу результатов для вопроса. Давайте напишем это представление:

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, будет извлечено для votes. Затем для обоих пользователей вычисляется и сохраняется новое значение 43, но ожидаемое значение должно было быть 44.

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

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

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

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

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

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

  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/3.2/intro/tutorial04/

Spec-Zone.ru

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