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

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

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

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

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

Давайте обновим наш шаблон деталей опроса («polls / detail.html») из последнего руководства, чтобы он содержал <form>элемент HTML :

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

опросы / шаблоны / опросы / 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 для написания приложения.

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

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

Читайте подробности.

Зачем тасовать код?

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

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

Изменить URLconf

Сначала откройте polls/urls.pyURLconf и измените его так:

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общем представлении используется шаблон с именем . В нашем случае он будет использовать шаблон . Атрибут используется , чтобы указать Django использовать имя шаблона конкретного вместо сгенерированного имени шаблона по умолчанию. Мы также указываем для представления списка - это гарантирует, что представление результатов и представление подробностей будут иметь другой вид при рендеринге, даже если они оба скрыты за кулисами.<app name>/<model name>_detail.html"polls/question_detail.html"template_nametemplate_nameresultsDetailView

Точно так же ListViewобщее представление использует шаблон по умолчанию с именем ; мы используем, чтобы сказать использовать наш существующий шаблон.<app name>/<model name>_list.htmltemplate_nameListView"polls/index.html"

В предыдущих частях учебника шаблоны были снабжены контекстом, который содержит переменные контекста questionи latest_question_list. Для переменной обеспечивается автоматически - поскольку мы используем модель Django ( ), Django способен определить соответствующее имя для переменной контекста. Однако для ListView автоматически сгенерированная контекстная переменная - . Чтобы переопределить это, мы предоставляем атрибут, указав, что мы хотим использовать вместо него. В качестве альтернативного подхода вы можете изменить свои шаблоны, чтобы они соответствовали новым переменным контекста по умолчанию, но гораздо проще указать Django использовать нужную переменную.DetailViewquestionQuestionquestion_listcontext_object_namelatest_question_list

Запустите сервер и используйте новое приложение для опроса на основе общих представлений.

Для получения полной информации об общих представлениях см. Документацию по общим представлениям .

Когда вы освоитесь с формами и общими представлениями, прочитайте часть 5 этого руководства, чтобы узнать о тестировании нашего приложения для опросов.

Copyright ©2021 All rights reserved