Spec-Zone.ru › Django 1.8

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

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

Написание простой формы

Обновим шаблон деталей опроса (“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>

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

  • Вышеупомянутый шаблон отображает радиокнопку для каждого варианта ответа. Идентификатор каждой радиокнопки — это связанный идентификатор варианта ответа. Значение каждой радиокнопки — "choice". Это означает, что когда кто-то выбирает одну из радиокнопок и отправляет форму, она отправит данные POST choice=#, где # — это идентификатор выбранного варианта. Это базовая концепция HTML-форм.
  • Мы установили метод отправки формы на {% 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, которое включает эту строку:

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.core.urlresolvers import reverse

from .models import Choice, Question
# ...
def vote(request, question_id):
    p = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', {
            'question': p,
            '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=(p.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 — это значение p.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/ в вашем браузере и проголосуйте за вопрос. Вы должны увидеть страницу результатов, которая обновляется каждый раз, когда вы голосуете. Если вы отправите форму, не выбрав вариант, вы увидите сообщение об ошибке.

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

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

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

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

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

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

Подробнее об этом ниже.

Почему перестановка кода?

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

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

Изменение URLconf

Сначала откройте URLconf polls/urls.py и измените его следующим образом:

from django.conf.urls import url

from . import views

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.core.urlresolvers 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

Здесь мы используем два универсальных представления: 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.8/intro/tutorial04/

Spec-Zone.ru

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