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

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

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

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

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

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

polls/templates/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>

Краткое изложение:

  • В приведенном выше шаблоне для каждого варианта вопроса отображается переключатель. 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 шаблон:

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 объект из базы данных, затем вычисляет новое значение votes и затем сохраняет его обратно в базу данных. Если два пользователя вашего веб-сайта попытаются проголосовать в одно и то же время , это может пойти не так: будет получено одно и то же значение, скажем 42 votes . Затем для обоих пользователей вычисляется и сохраняется новое значение 43, но 44 будет ожидаемым значением.

Это называется состоянием гонки . Если вам интересно, вы можете прочитать Избегание условий гонки с помощью F (), чтобы узнать, как вы можете решить эту проблему.

Используйте общие представления: меньше кода - лучше

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

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

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

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

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

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

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

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

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

Изменить URLconf

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

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_name template_name results DetailView

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

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

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

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

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

Copyright ©2020 All rights reserved