Предыдущая часть.
В нашем предыдущем представлении 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.