Spec-Zone.ru › Django 1.9

Создание вашего первого приложения Django, часть 3

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

Обзор

Представление — это «тип» веб-страницы в вашем приложении Django, которое обычно выполняет определённую функцию и имеет определённый шаблон. Например, в приложении блога у вас могут быть следующие представления:

  • Главная страница блога — отображает последние несколько записей.
  • Страница «детали» записи — страница постоянной ссылки на отдельную запись.
  • Страница архива по годам — отображает все месяцы с записями в данном году.
  • Страница архива по месяцам — отображает все дни с записями в данном месяце.
  • Страница архива по дням — отображает все записи в данный день.
  • Действие комментария — обрабатывает отправку комментариев к данной записи.

В нашем приложении опросов у нас будут следующие четыре представления:

  • Страница «индекса» вопроса — отображает последние несколько вопросов.
  • Страница «деталей» вопроса — отображает текст вопроса без результатов, но с формой для голосования.
  • Страница «результатов» вопроса — отображает результаты по конкретному вопросу.
  • Действие голосования — обрабатывает голосование за конкретный выбор в конкретном вопросе.

В Django веб-страницы и другое содержимое предоставляются представлениями. Каждое представление представлено простой функцией Python (или методом в случае представлений на основе классов). Django выберет представление, проанализировав запрашиваемый URL (точнее, часть URL после доменного имени).

Теперь в ваше время в интернете вы, возможно, сталкивались с такими чудесами, как «ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B». Вы будете рады узнать, что Django позволяет нам намного более элегантные URL-шаблоны, чем это.

URL-шаблон — это просто общий вид URL-адреса — например: /newsarchive/<year>/<month>/.

Для перехода от URL-адреса к представлению Django использует то, что известно как «URLconf». URLconf сопоставляет URL-шаблоны (описанные как регулярные выражения) с представлениями.

Этот учебник предоставляет основные инструкции по использованию URLconf, и вы можете обратиться к django.core.urlresolvers за дополнительной информацией.

Написание дополнительных представлений

Теперь добавим несколько дополнительных представлений к polls/views.py. Эти представления немного отличаются, потому что они принимают аргумент:

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

Подключите эти новые представления к модулю polls.urls, добавив следующие вызовы url():

from django.conf.urls import url

from . import views

urlpatterns = [
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

Посмотрите в вашем браузере на «/polls/34/». Он выполнит метод detail() и отобразит любой ID, который вы предоставите в URL. Попробуйте также «/polls/34/results/» и «/polls/34/vote/» — они отобразят страницы результатов и голосования по умолчанию.

Когда кто-то запрашивает страницу с вашего веб-сайта — например, «/polls/34/», Django загрузит модуль Python mysite.urls, потому что он указан в настройке ROOT_URLCONF. Он находит переменную с именем urlpatterns и последовательно проходит по регулярным выражениям. После нахождения совпадения в '^polls/', он удаляет совпадающий текст ("polls/") и отправляет оставшийся текст — "34/" — в URLconf «polls.urls» для дальнейшей обработки. Там он находит совпадение r'^(?P<question_id>[0-9]+)/$', что приводит к вызову представления detail() следующим образом:

detail(request=<HttpRequest object>, question_id='34')

Часть question_id='34' взята из (?P<question_id>[0-9]+). Использование скобок вокруг шаблона «захватывает» текст, соответствующий этому шаблону, и отправляет его в качестве аргумента в функцию представления; ?P<question_id> определяет имя, которое будет использоваться для идентификации соответствующего шаблона; и [0-9]+ — это регулярное выражение для сопоставления последовательности цифр (т. е. числа).

Поскольку URL-шаблоны являются регулярными выражениями, нет ограничений на то, что вы можете с ними сделать. И нет необходимости добавлять избыточные URL, такие как .html — если только вам это не нужно, в этом случае вы можете сделать что-то вроде этого:

url(r'^polls/latest\.html$', views.index),

Но не делайте этого. Это глупо.

Напишите представления, которые на самом деле что-то делают

Каждое представление отвечает за выполнение одного из двух действий: возвращение объекта HttpResponse, содержащего содержимое запрашиваемой страницы, или поднятие исключения, например Http404. Остальное зависит от вас.

Ваше представление может считывать записи из базы данных или нет. Оно может использовать систему шаблонов, такую как Django, или стороннюю систему шаблонов Python — или нет. Оно может генерировать файл PDF, выводить XML, создавать ZIP-архив на лету, всё, что вы хотите, используя любые библиотеки Python, которые вам нужны.

Всё, что нужно Django, это HttpResponse. Или исключение.

Для удобства давайте воспользуемся собственным API базы данных Django, который мы рассмотрели в Уроке 2. Вот одна попытка нового представления index(), которое отображает последние 5 вопросов опросов в системе, разделенные запятыми, в соответствии с датой публикации:

from django.http import HttpResponse

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

# Leave the rest of the views (detail, results, vote) unchanged

Однако здесь есть проблема: дизайн страницы жёстко закодирован в представлении. Если вы хотите изменить внешний вид страницы, вам нужно будет изменить этот код Python. Поэтому давайте воспользуемся системой шаблонов Django, чтобы разделить дизайн и Python, создав шаблон, который может использовать представление.

Сначала создайте каталог с именем templates в каталоге polls. Django будет искать шаблоны там.

Настройка вашего проекта TEMPLATES описывает, как Django будет загружать и рендерить шаблоны. Файл настроек по умолчанию настраивает бэкенд DjangoTemplates, чья настройка APP_DIRS установлена в True. По соглашению DjangoTemplates ищет подкаталог «templates» в каждом из INSTALLED_APPS.

Внутри каталога templates, который вы только что создали, создайте ещё один каталог с именем polls, а внутри него создайте файл с именем index.html. Иными словами, ваш шаблон должен находиться по адресу polls/templates/polls/index.html. Благодаря тому, как работает загрузчик шаблонов app_directories, как описано выше, вы можете указать на этот шаблон в Django просто как polls/index.html.

Пространство имён шаблонов

Сейчас мы, возможно, сможем обойтись без размещения шаблонов непосредственно в polls/templates (вместо создания другого подкаталога polls), но на самом деле это будет плохой идеей. Django выберет первый шаблон, имя которого совпадает, и если у вас есть шаблон с таким же именем в другом приложении, Django не сможет отличить их. Нам нужно уметь указывать Django на нужный, и самый простой способ гарантировать это — пространство имён. То есть, разместив эти шаблоны внутри ещё одного каталога, названного по имени самого приложения.

Вставьте следующий код в этот шаблон:

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Теперь обновим наше представление index в polls/views.py для использования шаблона:

from django.http import HttpResponse
from django.template import loader

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

Этот код загружает шаблон с именем polls/index.html и передаёт ему контекст. Контекст — это словарь, сопоставляющий имена переменных шаблона с объектами Python.

Загрузите страницу, указав в браузере адрес «/polls/», и вы должны увидеть список с пунктами, содержащий вопрос «Что нового» из Урока 2. Ссылка указывает на страницу с деталями вопроса.

Сокращение: render()

Очень часто используется способ загрузки шаблона, заполнения контекста и возврата объекта HttpResponse с результатом рендеринга шаблона. Django предоставляет сокращение. Вот полное представление index(), переписанное:

from django.shortcuts import render

from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

Обратите внимание, что после того, как мы выполнили это во всех этих представлениях, нам больше не нужно импортировать loader и HttpResponse (вам нужно будет сохранить HttpResponse если у вас всё ещё есть методы-заглушки для detail, results, и vote).

Функция render() принимает объект запроса в качестве первого аргумента, имя шаблона — в качестве второго аргумента и словарь в качестве необязательного третьего аргумента. Она возвращает объект HttpResponse заданного шаблона, отрисованного с заданным контекстом.

Поднятие ошибки 404

Теперь давайте рассмотрим представление деталей вопроса — страницу, на которой отображается текст вопроса для заданного опроса. Вот представление:

from django.http import Http404
from django.shortcuts import render

from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

Новая концепция здесь: представление поднимает исключение Http404, если вопрос с запрошенным ID не существует.

Мы обсудим, что вы могли бы поместить в этот polls/detail.html шаблон немного позже, но если вы хотите быстро запустить пример выше, файл, содержащий только:

{{ question }}

поможет вам начать работу на данный момент.

Сокращение: get_object_or_404()

Очень распространённый приём — использовать get() и поднимать Http404, если объект не существует. Django предоставляет сокращение. Вот detail() представление, переписанное:

from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

Функция get_object_or_404() принимает в качестве первого аргумента модель Django и произвольное количество именованных аргументов, которые передаются в функцию get() менеджера модели. Если объект не существует, поднимается Http404.

Философия

Почему мы используем вспомогательную функцию get_object_or_404() вместо автоматического перехвата исключений ObjectDoesNotExist на более высоком уровне или того, чтобы API модели поднимал Http404 вместо ObjectDoesNotExist?

Потому что это связало бы слой модели со слоем представления. Одна из основных целей проектирования Django — поддерживать слабую связь. Некоторая управляемая связь введена в модуле django.shortcuts.

Также есть функция get_list_or_404(), которая работает так же, как get_object_or_404(), — за исключением использования filter() вместо get(). Возникает Http404, если список пуст.

Использование системы шаблонов

Вернёмся к detail() представлению для нашего приложения опросов. Учитывая переменную контекста question, вот как может выглядеть polls/detail.html шаблон:

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

Система шаблонов использует синтаксис поиска по точкам для доступа к атрибутам переменных. В примере {{ question.question_text }}, Django сначала выполняет поиск в словаре по объекту question. Если это не удаётся, то пытается выполнить поиск по атрибуту — что в данном случае работает. Если поиск по атрибуту также не удаётся, то он пытается выполнить поиск по индексу списка.

Вызов метода происходит в цикле {% for %}: question.choice_set.all интерпретируется как код Python question.choice_set.all(), который возвращает итерируемый набор объектов Choice и подходит для использования в теге {% for %}.

См. руководство по шаблонам для получения дополнительной информации о шаблонах.

Удаление жёстко закодированных URL-адресов в шаблонах

Помните, когда мы писали ссылку на вопрос в polls/index.html шаблоне, ссылка частично была жёстко закодирована следующим образом:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

Проблема с этим жёстко закодированным, тесно связанным подходом заключается в том, что при изменении URL-адресов в проектах с большим количеством шаблонов возникают трудности. Однако, поскольку вы определили аргумент имени в функциях url() в модуле polls.urls, вы можете избавиться от зависимости от конкретных путей URL, определённых в конфигурации URL, используя тег шаблона {% url %}:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

Этот метод работает путём поиска определения URL-адреса, как указано в модуле polls.urls Вы можете увидеть, где определено имя URL «detail» ниже:

...
# the 'name' value as called by the {% url %} template tag
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

Если вы хотите изменить URL-адрес представления подробностей опросов на что-то другое, например, на что-то вроде polls/specifics/12/ вместо того, чтобы делать это в шаблоне (или шаблонах), вам нужно изменить его в polls/urls.py:

...
# added the word 'specifics'
url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
...

Именование URL-адресов

В проекте учебника всего одна приложение, polls. В реальных проектах Django может быть пять, десять, двадцать или более приложений. Как Django различает имена URL-адресов между ними? Например, приложение polls имеет представление detail, и то же самое может быть у приложения в том же проекте, предназначенном для блога. Как заставить Django определить, какое представление приложения создать для URL при использовании тега шаблона {% url %}?

Ответ заключается в добавлении пространств имён в вашу конфигурацию URL-адресов. В файле polls/urls.py добавьте app_name для установки пространства имён приложения:

from django.conf.urls import url

from . import views

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

Теперь измените свой шаблон polls/index.html с:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

на указание на пространство имён представления detail:

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/intro/tutorial03/

Spec-Zone.ru

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