Ваше первое Django-приложение, часть 4

Начнём с того места, где мы остановились в 3 части. Мы продолжаем написание приложения Web-опроса и сосредоточимся на простой форме обработки и сокращения нашего кода.

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

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

<h1>{{ poll.question }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="/polls/{{ poll.id }}/vote/" method="post">
{% for choice in poll.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>

Краткое содержание:

  • вышеупомянутый шаблон отображает кнопку с зависимой фиксацией для каждого выбора опроса. value каждой такой кнопки является ID выбора опроса. name каждой такой кнопки - это "choice". Это означает, что после совершения выбора и подачи формы, отправляются POST-данные choice=3. Это HTML-формы 101.
  • мы настраиваем action в /polls/{{ poll.id }}/vote/, и устанавливаем method="post". Использование method="post" (в отличие от method="get") очень важно, потому что действие подачи этой формы изменит данные на сервере. Всякий раз, когда Вы создаете форму, которая изменяет данные на сервере, используйте method="post". Этот совет относится не только к  Django; это полезно для создания Web-приложений.
  • forloop.counter указывает сколько раз выполнился цикл в тэге  for.

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

(r'^(?P<poll_id>\d+)/vote/$', 'vote'),

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

from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from mysite.polls.models import Choice, Poll
# ...
def vote(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    try:
        selected_choice = p.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the poll voting form.
        return render_to_response('polls/detail.html', {
            'poll': 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('mysite.polls.views.results', args=(p.id,)))

Этот код включает в себя несколько моментов, о которых мы ещё не говорили в этом руководстве:

  • request.POST iэто объект, типа словаря,который позволяет получить доступ к переданным данным по ключевому имени. В этом случае, request.POST['choice'] возвращает ID-выбор, как строку.  Значения request.POST всегда  являются строками.

    Обратите внимание, что Django также предоставляет request.GET для доступа к GET-данным таким же способом - но мы явно используем в нашем коде request.POST чтобы гарантировать, что данные будут изменены только через POST-запрос.

  • request.POST['choice'] выбросит KeyError если choice не был предоставлен в POST-данные. TВышеупомянутый код проверяет KeyError и заново выводит форму Опроса с сообщением об ошибке, если не был сделан choice.

  • После увеличения количества выборов, код возвращает HttpResponseRedirect вместо нормального HttpResponse. HttpResponseRedirect принимает единственный аргумент: URL, к которому пользователь будет переадресован (о том, как мы будем создавать URL в этом случае, смотри следующий пункт).

    Так как Python комментирует выше указанные пункты, то вам необходимо всегда возвращать HttpResponseRedirect после успешной работы с POST-данными. Этот совет относится не только к Django; это полезно для Web-разработки.

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

    '/polls/3/results/'

    ... где 3 - это значение p.id. Затем этот переадресованный URL вызовет 'results' чтобы отобразить конечную страницу. Обратите внимание, что здесь вам необходимо использовать полное имя (включая префикс).

Как уже упоминалось в 3 части, request является объектом HttpRequest. Дополнительную информацию о HttpRequest объектах можно посмотреть здесь: request and response documentation.

После того, как кто - то проголосует в Опросе vote() view переадресовывает нас к странице результатов. Давайте напишем этот view:

def results(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    return render_to_response('polls/results.html', {'poll': p})

Это почти тоже самое, что и detail() view из Части 3. Единственное отличие состоит в название шаблона. Но мы исправим это позже.

Теперь, создайте шаблон results.html:

<h1>{{ poll.question }}</h1>

<ul>
{% for choice in poll.choice_set.all %}
    <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

Теперь, перейдите в /polls/1/ в вашем браузере и ответьте на вопросы Опросника. Вы увидите страницу с результатами, которая обновляется каждый раз, когда Вы голосуете. Если Вы подаете форму, не проголосовав, то Вы увидите сообщение об ошибке.

Использование generic views: Чем меньше кода, тем лучше

detail() из ((Части 3) и results() views очень просты - и, как упоминалось выше, избыточны. index() view (также из части 3), который отображает список опросов, является аналогичным.

Эти views представляют собой общий случай Web-разработки: получение информации из базы данных в соответствии с параметром, передаваемым в URL, загрузка шаблона и возвращение переданного шаблона. Потому что так часто бывает, что Django предоставляет ярлык, который называется система "generic views".

Generic views помогают абстрагироваться до такой степени, что вам даже не нужно писать Python-код  для создания приложения.

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

  1. Преобразуем URLconf.
  2. Переименуем некоторые шаблоны.
  3. Удалим некоторые старые, теперь ненужные views.
  4. Исправим обработку URL для новых views.

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

Почему код-Shuffle?

Обычно, при написании Django-приложения, Вы оцениваете, хорошо ли подходят для вашей проблемы generic views, и Вы будете использовать их с самого начала, вместо того, чтобы делать рефакторинг кода на полпути. Но это руководство до сих пор преднамеренно сосредоточило свое внимание на написании views "the hard way", чтобы сосредоточиться на основных понятиях.

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

Во-первых, откройте polls/urls.py URLconf. Это выглядит следующим образом:

from django.conf.urls.defaults import *

urlpatterns = patterns('mysite.polls.views',
    (r'^$', 'index'),
    (r'^(?P<poll_id>\d+)/$', 'detail'),
    (r'^(?P<poll_id>\d+)/results/$', 'results'),
    (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)

Измените его следующим образом:

from django.conf.urls.defaults import *
from mysite.polls.models import Poll

info_dict = {
    'queryset': Poll.objects.all(),
}

urlpatterns = patterns('',
    (r'^$', 'django.views.generic.list_detail.object_list', info_dict),
    (r'^(?P<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
    url(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'),
    (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)

Мы используем здесь два generic views: object_list() и object_detail(). Соответственно, эти два views абстрагируют понятия "отображение списка объектов" и "отображение страницы подробностей для конкретного типа объектов".

  • каждый generic view должен знать, в соответствии с какими данными он будет действовать. Эти данные приводятся в словаре. queryset в данном словаре указывает на список объектов, которыми будет управлять generic view.
  • object_detail() ожидает ID-значение, взятое из URL, чтобы называться "object_id", так что мы изменили poll_id на object_id для generic views.
  • Мы добавили название, poll_results, затем, чтобы иметь возможность обратиться к его URL позже (для дополнительной информации смотри документацию naming URL patterns). Мы также используем здесь функцию url() из django.conf.urls.defaults. Это хорошая привычка использовать URL (), когда Вы предоставляете имя шаблона.

По умолчанию object_detail() generic view использует шаблон <app name>/<model name>_detail.html. В нашем случае будет использован шаблон "polls/poll_detail.html". Таким образом, переименуйте свой шаблон polls/detail.html на polls/poll_detail.html, и измените строку render_to_response() в vote().

Кроме того object_list() generic view использует шаблон под названием <app name>/<model name>_list.html. Таким образом, переименуйте polls/index.html на polls/poll_list.html.

Поскольку у нас больше одной записи в URLconf, которая использует object_detail() для нашего приложения, мы вручную указываем имя шаблона для results view: template_name='polls/results.html'. В противном случае, оба views использовали бы один и тот же шаблон. Обратите внимание, что мы используем dict() чтобы вернуть измененный словарь на место.

Примечание

django.db.models.QuerySet.all() ленив

Видеть использование Poll.objects.all() в detail view, для которого необходим лишь один Poll-объект, может выглядеть немного пугающе, но не волнуйтесь; Poll.objects.all() является фактически специальным объектом, под названием  QuerySet, который является "ленивым" и не касается вашей базы данных, до тех пор, пока это не надо. К тому времени, когда произойдет запрос к базе данных object_detail() generic view сузит свои возможности до одного объекта, так что в конечном итоге запрос выберет только одну строку из базы данных.

Если Вы хотите узнать больше о том, как это работает, смотри здесь: explains the lazy nature of QuerySet objects.

В предыдущих частях руководства, шаблонам был предоставлен контекст, который содержит poll и latest_poll_list Однако, в качестве контекста generic views  предоставляет переменные object и object_list Таким образом, вам необходимо изменить шаблоны в соответствии с новыми переменными контекста. Пробегитесь по шаблонам, и измените ссылки к latest_poll_list на object_list, и измените ссылку к poll на object.

Вы можете теперь удалить index(), detail() и results() views из polls/views.py. Они нам больше не нужны - их заменили generic views.

vote() view по-прежнему необходим. Но его надо изменить в соответствии с новыми переменными контекста. В запросе render_to_response() переименуйте переменную контекста poll на object.

И последнее, что надо сделать - это исправить обработку URL, чтобы вычислить использование generic views. В указанном выше vote view мы использовали функцию reverse() чтобы избежать жесткого кодирования URLs. Теперь, когда мы перешли на generic view, нам надо изменить запрос reverse() чтобы указать на наш новый generic view. Мы не можем больше просто использовать функцию view - generic views могут быть используемы много раз - но мы можем использовать имя, которое мы дали:

return HttpResponseRedirect(reverse('poll_results', args=(p.id,)))

Запустите сервер, и используйте ваше новое приложение Опроса, основанное на generic views.

Полную информацию о generic views смотри здесь generic views documentation.

 

Оригинал статьи на  www.docs.djangoproject.com

Перевод хостинг КОМТЕТ komtet.ru

Вам также может помочь