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

Начнём с того места, где мы остановились во 2 части. Мы продолжаем написание приложения Web-опроса и сосредоточимся теперь на создании интерфейса пользователя - "views".

Философия

view - это "тип" Web-страницы в вашем приложении Django, который как правило выполняет определенную функцию и имеет конкретный шаблон. Например, в weblog-приложении, у вас могут быть следующие views:

  • домашняя страница блога - отображает последние несколько посещений блога.
  • Страница "детализации" посещений - ссылка на страницу с информацией об одном посещении.

  • страница архива за год - отображает помесячно все посещения за текущий год.

  • страница архива за месяц - отображает посещения по дням за текущий месяц.

  • страница архива за день - отображает все посещения за текущий день.

  • Комментарии - комментарии, публикуемые вручную за текущее посещение.

В нашем приложении Опроса, будет четыре views:

  • страница "архива" - отображает последние несколько опросов.
  • страница "детализации" - отображает вопрос с формой для голосования.
  • страница "результатов" - отображает результаты определённого опроса.
  • голосование - голосование за определенный вариант конкретного опроса.

В Django, каждый view представлен простой функцией Python.

Дизайн ваших URLs

Первый шаг к написанию views, это дизайн структуры вашего URL. Это можно сделать, создав Python-модуль, называющийся URLconf. URLconfs - это то, как Django связывает данный URL с данным Python-кодом.

Когда пользователь запрашивает страницу Django, система просматривает параметр ROOT_URLCONF который содержит строку точечного синтаксиса Python. Django загружает этот модуль и ищет переменную urlpatterns, которая является последовательностью кортежей в следующем формате:

(regular expression, Python callback function [, optional dictionary])

Django начинает с первого регулярного выражения и далее вниз по списку, сравнивая требуемый URL с каждым регулярным выражением, пока не найдёт подходящий.

После этого, Django вызывает callback-функцию Python, с первым аргументом в качестве HttpRequest-объекта, любые "зафиксированные" значения из регулярного выражения в качестве ключевых аргументов, и, дополнительно, произвольные ключевые аргументы из словаря (дополнительный третий элемент в кортеже).

Дополнительную информацию о HttpRequest-объектах, смотри здесь: Request and response objects. Более детальную информацию о URLconfs, смотри здесь: URL dispatcher.

Команда django-admin.py startproject mysite, aвыполненная в начале 1 части руководства, создала по умолчанию URLconf в mysite/urls.py. Это также автоматически настроило ROOT_URLCONFsettings.py) чтобы указать на этот файл:

ROOT_URLCONF = 'mysite.urls'

Пора привести пример. Измените mysite/urls.py так, чтобы он выглядел следующим образом:

from django.conf.urls.defaults import *

from django.contrib import admin
admin.autodiscover()

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

Это стоит посмотреть! Если кто - то запрашивает страницу с вашего сайта - скажем, "/polls/23 / ", то Django загрузит этот Python-модуль, потому что так прописано в настройках ROOT_URLCONF. Он находит переменную urlpatterns и просматривает по очереди все регулярные выражения. Когда он находит подходящее регулярное выражение - r'^polls/(?P<poll_id>\d+)/$' - то он загружает функцию detail() из mysite/polls/views.py. И наконец, он вызывает эту функцию detail() вот таким образом:

detail(request=<HttpRequest object>, poll_id='23')

poll_id получает значение '23' после обработки регулярного выражения (?P\d+). Использование круглых скобок вокруг шаблона "фиксирует" текст, соответствующий этому шаблону и отправляет его функции view в качестве аргумента; ?P<poll_id> определяет имя, которое будет использоваться, для определения соответствующего шаблона; а \d+ iявляется регулярным выражением для сопоставления последовательности цифр (т. е. число).

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

(r'^polls/latest\.php$', 'mysite.polls.views.index'),

Не делайте так. Это глупость.

Обратите внимание, что эти регулярные выражения не ищут параметры GET и POST, или имя домена. Например, в запросе к http://www.example.com/myapp/, URLconf будет искать myapp/. В запросе к http://www.example.com/myapp/?page=3, URLconf будет искать myapp/.

Если вам нужна помощь по использованию регулярных выражений, то смотри здесь: Wikipedia's entry и здесь: Python documentation. Также, на мой взгляд, книга O'Reilly "Mastering Regular Expressions" Джеффри Фридлома является прекрасным помощником по данной теме.

Примечание: эти регулярные выражения компилируются, когда загружается в первый раз модуль URLconf. Они супер быстрые.

Пишем своё первое view

Ок, мы ещё не создали ни одного views - у нас есть только URLconf. Но давайте убедимся, что Django правильно следует за URLconf.

Запустите сервер Web-разработки Django:

python manage.py runserver

Теперь перейдите на "http://localhost:8000/polls/" на свой домен в вашем Web-браузере. Вы должны увидеть страницу с сообщением об ошибке:

ViewDoesNotExist at /polls/

Tried index in module mysite.polls.views. Error was: 'module'
object has no attribute 'index'

Эта ошибка произошла из-за того, что Вы не прописали функцию index() в модуле mysite/polls/views.py.

Попробуйте "/polls/23 / ", "/polls/23/results / " и "/polls/23/vote / ". Из сообщения об ошибке Вы узнаете, какой view ищет Django (и не может найти, потому что Вы не написали еще никаких view).

Пора создать свой первый view. Откройте файл mysite/polls/views.py и запишите в него следующий Python-код:

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the poll index.")

Это самый простой view. Зайдите в " / polls / " через свой браузер, чтобы увидеть текст.


Теперь давайте добавим ещё несколько view. Они будут немного отличаться, потому что они принимают аргумент:

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

def results(request, poll_id):
    return HttpResponse("You're looking at the results of poll %s." % poll_id)

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

Зайдите в своём браузере в "/polls/34 / ". Он запустит метод деталь () и отобразит любой ID, который Вы предоставите в URL. Попробуйте также "/polls/34/results / " и "/polls/34/vote / " - они отобразят результаты указателя места заполнения и страниц голосования.

Пишем views, которые на самом деле могут что-то делать

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

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

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

Так как это удобно, давайте использовать собственные API-базы данных Django, о которых шла речь в 1 части. Вот пример index() view, который отображает последние 5 вопросов вопросника в системе, отделенные запятыми, согласно даты публикации:

from mysite.polls.models import Poll
from django.http import HttpResponse

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    output = ', '.join([p.question for p in latest_poll_list])
    return HttpResponse(output)

Но здесь существует одна проблема: дизайн страницы жестко закодирован в view. Если Вы хотите изменить способ представления страницы, вам придется изменить код Python. Так что, давайте с помощью системы шаблонов Django, отделим дизайн от Python:

from django.template import Context, loader
from mysite.polls.models import Poll
from django.http import HttpResponse

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    t = loader.get_template('polls/index.html')
    c = Context({
        'latest_poll_list': latest_poll_list,
    })
    return HttpResponse(t.render(c))

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

Перезагрузите страницу. Вы увидите следующую ошибку:

TemplateDoesNotExist at /polls/
polls/index.html

А шаблона всё ещё нет. Сначала, создайте каталог в любом месте вашей файловой системы, к контенту которого может обратиться Django (Django запускается, как только пользователь заходит на ваш сервер). Но не создавайте его в корне. Вам также не следует его делать общедоступным, по причине безопасности. Затем измените TEMPLATE_DIRS в settings.py чтобы сообщить Django где искать шаблоны - также, как Вы это делали в разделе "Настройка внешнего вида интерфейса администратора" во второй части руководства.

Когда Вы это сделали, создайте каталог polls iв директории шаблона. Создайте в нём файл index.html. Обратите внимание, что наш вышеприведённый код loader.get_template('polls/index.html') отображается в "template_directory]/polls/index.html" в файловой системе.

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

{% if latest_poll_list %}
    <ul>
    {% for poll in latest_poll_list %}
        <li>{{ poll.question }}</li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Загрузите страницу в вашем Web-браузере, и Вы увидите маркированный список, содержащий опрос "What's up" из первой части руководства.

Ярлык: render_to_response()

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

from django.shortcuts import render_to_response
from mysite.polls.models import Poll

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
    return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})

Обратите внимание, что, как только мы сделали это во всех views, нам нет больше необходимости импортировать loader, Context и HttpResponse.

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

Выброс исключения 404

Теперь, давайте займемся страницей, отображающей вопрос для данного опроса. Вот её view:

from django.http import Http404
# ...
def detail(request, poll_id):
    try:
        p = Poll.objects.get(pk=poll_id)
    except Poll.DoesNotExist:
        raise Http404
    return render_to_response('polls/detail.html', {'poll': p})

Вот новая концепция: view выбрасывает исключение Http404 если опрос с требуемым ID не существует.

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

{{ poll }}

даст вам возможность начать прямо сейчас.

Ярлык: get_object_or_404()

Часто бывает при использовании get() выбрасывается Http404 если объект не существует. Django предоставляет ярлык. Вот detail() view:

from django.shortcuts import render_to_response, get_object_or_404
# ...
def detail(request, poll_id):
    p = get_object_or_404(Poll, pk=poll_id)
    return render_to_response('polls/detail.html', {'poll': p})

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

Философия

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

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

Есть ещё функция get_list_or_404(), которая работает так же, как get_object_or_404() -- за исключением использования filter() вместо get(). Но если список пуст, то выбрасывается Http404.

Пишем view исключения 404  (страница, не найдена)

 

Если выбрасывается исключение Http404 изнутри вьюхи, то Django загружает специальный view для обработки исключения 404. Он находит его, совершая поиск переменной handler404, которая является строкой, написанной в точечном синтаксисе Python - тот же самый формат, который использует обычный URLconf. Сам по себе view исключения 404 не имеет ничего особенного: Это просто обычный view.

Обычно вам не надо его писать. По умолчанию, URLconfs имеет следующую строку:

from django.conf.urls.defaults import *

Он занимается установкой handler404 в текущем модуле. Как Вы видите в django/conf/urls/defaults.py, handler404 установлен в django.views.defaults.page_not_found() по умолчанию.

Вот ещё дополнительные четыре пункта о views исключения 404:

  • Если DEBUG настроена на значение True ((в настройках модуля), то 404 view никогда не будет использоваться (и таким образом, шаблон 404.html никогда не будет отображаться), потому что вместо него будет отображён traceback.
  • Также 404 view вызывается, если Django не находит соответствия после проверки каждого регулярного выражения в URLconf.
  • Если Вы не задаёте 404 view, а используете значение по умолчанию, которое рекомендуется, то у вас есть ещё одно обязательство: создать шаблон 404.html в корне директории шаблонов. По умолчанию 404 view будет использовать этот шаблон для всех 404 ошибок.
  • Если DEBUG настроена на значение False (в настройках модуля) и если Вы не создали файл 404.html то вместо него будет выброшен Http500. Поэтому не забудьте создать 404.html.

Пишем view ошибки 500 (ошибка сервера)

Точно так же URLconfs могут назначить handler500, который указывает на view для вызова в случае ошибки сервера. Ошибки сервера случаются, когда у вас истекает время в view-коде.

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

Вернемся к detail() view нашего приложения опроса. Вот как может выглядеть шаблон "polls/detail.html":

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

Система шаблона использует синтаксис точечного поиска, чтобы получить доступ к атрибутам переменной. В примере {{ poll.question }}, сначала Django делает поиск по словарю у объекта poll. В случае неудачи, он совершает атрибутный поиск. Если данный поиск также закончится неудачей, то он пытается вызвать метод question() у объекта poll.

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

Для более подробной информации см. template guide.

Упрощение URLconfs

Найдите немного времени для работы с views и системой шаблонов. Как только Вы начнёте изменять URLconf, Вы заметите, что в нём есть немного лишнего:

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

А именно, в каждом повторном вызове находится mysite.polls.views.

Поскольку это распространённый случай, фреймворк URLconf предоставляет ярлык для общих префиксов. Вы можете вынести за скобки общие префиксы и добавить их в качестве первого аргумента к patterns(), например, так:

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

Это функционально похоже на предыдущее форматирование, только немного более чистое.

Декаплинг URLconfs

Раз мы этим занялись, давайте найдём немного времени на то, чтобы отделить URLs нашего приложения Опроса от конфигурации нашего Django-проекта. Приложения Django являются сменными (pluggable) - т. е. любое приложение должно открываться в другой инсталляции Django с минимальными усилиями.

Наше приложение опроса является почти "развязанным", с этой точки зрения, благодаря строгой структуре каталогов, которую создал Python python manage.py startapp но одна его часть является связанной с настройками Django:The URLconf.

Мы изменили URL в mysite/urls.py, но URL-дизайн  приложения заточен под него, а не под инсталляцию Django - поэтому, давайте переместим URLs в каталог приложения.

Скопируйте файл mysite/urls.py в mysite/polls/urls.py. Затем, измените mysite/urls.py чтобы удалить специальные URLs для Опроса и вставить include():

# ...
urlpatterns = patterns('',
    (r'^polls/', include('mysite.polls.urls')),
    # ...

include(), просто ссылается на другой URLconf. Обратите внимание, что регулярное выражение не имеет $ (символ конца строки), а имеет косую черту вправо. Всякий раз, когда Django сталкивается с include(), он обрубает совпадающую часть URL и отправляет оставшуюся строку в URLconf для дальнейшей обработки.

Вот что происходит, если пользователь заходит в "/polls/34 / " в этой системе:

  • Django находит совпадающий текст в '^polls/'
  • Затем удаляет его ("polls/") и отправляет оставшийся текст -- "34/" -- в 'mysite.polls.urls' URLconf для дальнейшей обработки.

Теперь, когда мы сделали декаплинг, мы должны сократить 'mysite.polls.urls' URLconf, удалив " опросы / " из каждой строки, и удалив строки, относящиеся к админке:

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'),
)

Идея,стоящая за декаплингом include() и URLconf состоит в том, чтобы облегчить подключение и работу с URLs. Теперь, когда опросы находятся в их собственном URLconf, они могут быть размещены в " / опросы / ", или в "/fun_polls / ", или в "/content/polls / ", или в любом другом корневом URL, и приложение будет ещё работать.

Для приложения Опроса важны его относительные, а не абсолютные URL-адреса.

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

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

.

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