Spec-Zone.ru › Django 3.0

Написание вашего первого приложения 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 каждой кнопки радио — это идентификатор соответствующего варианта ответа. 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, а затем сохраняет его обратно в базу данных. Если два пользователя вашего веб-сайта попробуют проголосовать в абсолютно одно и то же время, это может пойти не так: для 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/3.0/intro/tutorial04/

Spec-Zone.ru

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