Предыдущая часть.
В нашем предыдущем представлении current_datetime содержимое страницы — текущие время и дата — были динамическими, но URL для обращения к ним был статическим. В очень многих веб-приложений URL содержит в себе параметры, которые влияют на выводимое содержимое страницы. Например, онлайн-магазин книг может хранить каждую книгу в одельном URL-е, таком как /books/243/ и /books/81196/.
Давайте создадим третье представление, которое будет отображать время и дату — но со смещением в несколько часов. Наша цель — создать веб-приложение, в котором страница /time/plus/1/ отображала бы время и дату со смещением на один час вперёд, страница /time/plus/2/ — на два часа, и так дале.
Можно было бы создать несколько функций для каждого смещения времени, и для каждой создать новую запись в URLconf-е, что-то вроде такого:
urlpatterns = patterns('',
url(r'^time/$', current_datetime),
url(r'^time/plus/1/$', one_hour_ahead),
url(r'^time/plus/2/$', two_hours_ahead),
url(r'^time/plus/3/$', three_hours_ahead),
url(r'^time/plus/4/$', four_hours_ahead),
)
Очевидно, что такой подход имеет недостатки. Мало того, что мы создаём лишние представления, но кроме того — мы ограничены несколькиими заранее определёнными периодами для смещения времени — 1, 2, 3 и 4 часа. Если мы захотим создать страницу, которая бы выводила время на 5 часов вперёд — нам придётся создавать новую функцию и добавлять строку в URLconf.
Кратко об URL-ах
Если вы ранее занимались разработкой под другие платформы, такие как PHP или Java, вы могли бы подумать «Давайте просто используем строку параметров в запросе!» — что-то вроде такого: /time/plus?hours=3, в котором количество часов передавался бы параметром hours прямо в URL-е.
Вы можете сделать это и с Django (мы рассмотрим такую возможность позже), но одна из ключевых особенностей философии Django — это «URL должен быть прекрасен». URL в виде /time/plus/3/ намного проще, более читаемый, его легче передать кому-то. Приятный URL — это показатель качества вашего приложения.
Модуль URLconf в Django поддерживает создание таких URL-ов, поэтому лучше пользоваться такой возможностью и использовать красивые адреса.
Итак, как нам настроить наше приложение, что бы обрабатывать произвольное смещение времени? Ключевым решением является использование групповых символов в URLpattern. Как мы уже упоминали, URLpattern — это регулярное выражение, поэтому — мы можем использовать регулярные выражения вида 'd+' что бы получить один или более цифр в URL-е:
urlpatterns = patterns('',
# ...
url(r'^time/plus/d+/$', hours_ahead),
# ...
)
(мы удалили весь предыдущий код, заменив его символами #)
Такой URLpattern будет перехватывать все адреса вида /time/plus/2/, /time/plus/25/ и даже /time/plus/100000000000/. Мы можем изменить его, что бы установить ограничение в 99 часов. Что бы реализовать это — нам необходимо установить лимит от одной до двух цифр в регулярном выражении, которое будет выглядеть как 'd{1,2}':
url(r'^time/plus/d{1,2}/$', hours_ahead),
Примечание
При разработке веб-приложения — всегда лучше предполагать получение самых необычных данных, и заранее решить — должно или нет приложение их обабатывать. Мы тут решили ограничить здесь такие данные 99-ю часами.
Теперь, когда мы создали групповой символ для URL-а, нам необходимо передать полученные из него данные в функцию представления, что бы мы могли использовать одну функцию для любого смещения времени. Мы можем сделать это разместив скобки () вокруг данных в URLpattern. В случае с нашим примером — мы хотим передать цифры, которые были переданы в URL-е, поэтому изменим наш код:
url(r'^time/plus/(d{1,2})/$', hours_ahead),
Если вы знакомы с регулярными выражениями — то в URLpattern вы будете себя чувствовать как дома. Мы используем скобки, что бы захватить текст, который попадает под шаблон.
В результате, мы получим такой URLconf, включая наши предыдущие представления:
from django.conf.urls.defaults import patterns, include, url
from example.views import hello, current_datetime, hours_ahead
urlpatterns = patterns('',
url(r'^$', hello),
url(r'^hello/$', hello),
url(r'^time/$', current_datetime),
url(r'^another-time-page/$', current_datetime),
url(r'^time/plus/(d{1,2})/$', hours_ahead),
)
С этим мы разобрались, теперь время заняться представлением hours_ahead.
hours_ahead весьма схожа с нашей предыдущей функций current_datetime, с одним ключевым отличием — новая фукнция будет принимать дополнительный аргумент, смещение времени в часах. Вот как это будет выглядеть:
def hours_ahead(request, offset):
try:
offset = int(offset)
except ValueError:
raise Http404()
dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
return HttpResponse(html)
А поностью файл views.py — так:
from django.http import HttpResponse, Http404
import datetime
def hello(request):
return HttpResponse("Hello world")
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
def hours_ahead(request, offset):
try:
offset = int(offset)
except ValueError:
raise Http404()
dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
html = "<html><body>In %s hour(s), it will be %s.</body></html>" % (offset, dt)
return HttpResponse(html)
Давайте пройдемся построчно по коду функции hours_ahead:
-
функция
hours_aheadпринимает два аргумента —requestиoffset:requestэто объектHttpRequest, точно такой же, как в наших примерах сhelloиcurrent_datetime. Повторим — каждое представление всегда должно принимать объектHttpRequestв качестве первого аргумента;offset— строка, захваченная скобками вURLpattern. Например, если переданный URL будет /time/plus/3/ — тогдаoffsetбудет строкой'3'. Если URL будет /time/plus/21/ — то offset будет'21'. Помните, что полученные изURLpatternданные всегда будут строкового типа, даже если они состоят из цифр;(технически — эти данные всегда будут объектом Unicode, а не Python-строкой, но пока об этом не беспокойтесь)Мы решли иназвать переменнуюoffset— но вы можете назвать её любым другим именем, главное — что бы это был второй аргумент функции, послеrequest. Так же — можно использовать ключевые слова вместо аргументов функции, но это мы рассмотрим позже).
- Первое действие нашей функции — это вызов
int()для переменнойoffset, что бы изменить типstr()вint().
Python должен вызвать исключениеValueError, если вы вызываете методint()для значения, которое не может быть приведено к типуint(), например — ‘foo‘. В нашем примере, если мы встречаемValueError— мы вызываем исключениеdjango.http.Http404, которое вернёт ошибку «404 — Страница не найдена».Внимательные читатели могут удивиться — как мы можем получитьValueError, если наше регулярное выражение (d{1,2}) перехватывает только цифры и, следовательно, полученные данные в переменнойoffsetдолжны быть только цифрами, хотя и в строковом виде? Ответ — верно, мы не должны получить другие данные, так какURLpatternпредоставляет нам простой и удобный инструмент проверки полученных данных. Но — мы должны проверять эти данные на предмет вызова исключенияValueErrorна тот случай, если эта функция будет вызываться каким-то другим способом. Вы ведь помните про «слабые связи«? - В следущей строке функции мы вычисляем время и дату, и добавляем полученное количесво часов. Вы уже видели
datetime.datetime.now()в функцииcurrent_datetime. Новое в данном случае — это то, что мы можем выполнить арифметические действия над временем и датой, создав объектdatetime.timedeltaи добавив результат к объектуdatetime.datetime. Результат сохраняется в переменнуюdt.
В этой строке так же становится понятно, почему мы приводим занчение переменнойoffsetк типуint()— функцияdatetime.timedeltaтребует, что бы параметрhoursбыл типаinteger. - Далее, мы создаём вывод HTML, так же, как мы делали это в функции
current_datetime. Небольшое отличие тут — что бы производим замещение данных с помощью%sдвух переменных, а не одной. Поэтому тут мы используем%sдва раза в строке, и передаём кортеж из двух переменных (offset, dt), которые надо в неё вставить. - >И последним действием — мы возвращаем
HttpResponse.
С новой функцией, и новым URLconf, запустите сервер разработки Django, если он ещё не запущен, и откройте страницу http://127.0.0.1:8000/time/plus/3/, что бы проверить как всё работает. Потом — попробуйте перейти на http://127.0.0.1:8000/time/plus/5/. Попробуйте зайти на http://127.0.0.1:8000/time/plus/100/, что бы убедиться что ваш URLconf принимает только две цифры — Django должен отобразить страницу 404. Ту же ошибку должна вернуть страница http://127.0.0.1:8000/time/plus/ — так как цифры в URL не переданы вообще.
Порядок программирования
В этом пример мы сначала написали наш URLpattern, а потом представление, но в предыдущем примере — наоборот, сначала представление, а потом URLpattern. Как правильно делать?
Процесс разработки очень разный.
Если вы человек, который может увидеть всю картину сразу — то есть смысл записать сначала все URLpatterns для вашего приложения, в начале работы над проектом, а затем приступать к программирвоанию представлений. Этот подход даёт преимущество в том, что у вас появляется готовый «список» того, с чем вам предстоит работать.
Если же вы разработчкк, который предпочитает работать «снизу вверх» — тогда, возможно, вам лучше сначала написать представления, а потом связывать их с URL-ами. Это тоже хороший подход к созданию вашего приложения.
В конце-концов — всё зависит то того, какой способ больше подходит вашей манере разработки. Оба они достойны использования.
Продолжение — в статье Django Book: страница ошибки Django.