Spec-Zone.ru › Django 1.10

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

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

Напишите простую форму

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

<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 для приложения опросов, который включает эту строку:

url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),

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

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
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() перенаправляет на страницу результатов для этого вопроса. Давайте напишем это представление:

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.

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

from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
    url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

Обратите внимание, что имя сопоставленного шаблона в регулярных выражениях второго и третьего шаблонов изменилось с <question_id> на <pk>.

Изменение представлений

Далее мы удалим старые представления index, detail, и results и воспользуемся универсальными представлениями Django вместо них. Для этого откройте файл polls/views.py и измените его следующим образом:

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
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/1.10/intro/tutorial04/

Spec-Zone.ru

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