Создание сайта на Python/Django: создаем простую форму

Мы уже знаем как происходит маршрутизация URL в Django и что такое пространство имен. Пришла пора создать первую простую форму в нашем приложении. Для этого добавим в файл 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>

Приведенный выше шаблон отображает переключатель для каждого вопроса. Значение каждого переключателя – это идентификатор соответствующего вопроса. Название name каждой радиокнопки «choice». Это означает, что когда кто-то выбирает одну из переключателей и отправляет форму, он отправляет выбор данных POST choice=#, где # – идентификатор выбранного варианта. Это основная концепция HTML-форм.

Действие при отправке формы устанавливается {% url ‘polls: voice’ question.id%} method = “post”. Использование method = “post” (в отличие от method = “get”) очень важно, потому что отправка этой формы изменит данные на стороне сервера. Всякий раз, когда создается форма, которая изменяет данные на стороне сервера, используйте method=”post”. Этот совет не является специфическим для Django; это хорошая практика веб-разработки.

forloop.counter указывает, сколько раз тег for прошел свой цикл.

Так как мы создаем форму POST (которая может повлиять на изменение данных), нам нужно беспокоиться о подделке межсайтовых запросов. К счастью, вам не нужно слишком сильно беспокоиться, потому что Django поставляется с очень простой в использовании системой для защиты от него. Короче говоря, все формы POST, предназначенные для внутренних URL-адресов, должны использовать тег {% csrf_token %}.

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

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

Мы ранее создали фиктивную реализацию функции vote(). Давайте создадим рабочую версию. Добавьляем в файл 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-адрес, на который будет перенаправлен пользователь. Необходимо всегда возвращать HttpResponseRedirect после успешной обработки данных POST. Этот совет не является специфическим для Django. Это признак хорошей практики веб-разработки.

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

'/polls/1/results/'

где 1 – это значение question.id. Этот перенаправленный URL-адрес затем вызовет представление results для отображения финальной страницы.

После того, как кто-то дал ответ на вопрос, представление 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(). Единственное отличие – это имя шаблона. Мы исправим эту избыточность позже. Теперь создадим шаблон 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 из базы данных, затем вычисляет новое значение количества голосов и после сохраняет его обратно в БД. Например, если два пользователя вашего веб-сайта одновременно попытаются проголосовать, то это может привести к некорректности результатов. Голос одного из них будет потерян. Это называется состоянием гонки (race condition). Если интересно, то можете ознакомиться с тем как избеганить состояния гонки, используя F().

На этом пока прервемся, а в следующей части серии будем рассматривать возможности общих представлений, почему их стоит использовать и какие преимущества при этом получаем.

Приятного кодинга!

Добавить комментарий

Ваш адрес email не будет опубликован.

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.