Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Django_-_podrobnoe_rukovodstvo

.pdf
Скачиваний:
308
Добавлен:
01.03.2016
Размер:
4.88 Mб
Скачать

50

Глава 3. Представления и конфигурирование URL

•• Во второй строчке функции конструируется HTML-ответ с помощью встроенных в Python средств форматирования строк. Последовательность %s внутри строки – это спецификатор, а знак процента после строки означает «заменить спецификатор %s в предшествующей строке значением переменной now». Технически переменная now – объект класса datetime.datetime, а не строка, но спецификатор %s преобразует его в строковое представление, которое выглядит примерно так: “2008-12-13 14:09:39.002731”. В результате получается HTML-разметка вида “<html><body>Сейчас 2008-12-13 14:09:39.002731.</ body></html>”.

•• Да, эта HTML-разметка некорректна, но мы хотим, чтобы пример был простым и кратким.

•• Наконец, представление возвращает объект HttpResponse, который содержит сгенерированный ответ, – точно так же, как было в hello.

После того как этот код будет помещен в файл views.py, необходимо добавить соответствующий шаблон­ URL в файл urls.py, чтобы Django знал, какому URL соответствует новое представление. URL /time/ вполне подойдет:

from django.conf.urls.defaults import *

from mysite.views import hello, current_datetime

urlpatterns = patterns(‘’, (‘^hello/$’, hello), (‘^time/$’, current_datetime),

)

Итак, произведено два изменения. Во-первых, мы в самом начале импортировали функцию current_datetime. Во-вторых, и это более важно, мы добавили шаблон­ URL, который отображает URL /time/ на новое представление. Начинаете понимать?

Теперь, когда представление написано и конфигурация URL обновлена, запустите сервер разработки командой runserver и введите адрес http://127.0.0.1:8000/time/ в своем броузере. Вы должны увидеть текущие дату и время.

Часовые пояса в Django

В зависимости от настроек вашего компьютера дата и время могут отличаться на несколько часов. Дело в том, что Django знает о часовых поясах и по умолчанию настроен на часовой пояс Чикаго в США. (Какое-то значение по умолчанию должно быть, и разработчики выбрали свое место проживания.) Если вы живете в другом месте, то, наверное, захотите изменить часовой пояс в файле settings.py. В этом файле есть комментарий со ссылкой на актуальный список мировых часовых поясов.

Конфигурация URL и слабая связанность

51

Конфигурация URL и слабая связанность

Теперь самое время остановиться на методологическом принципе, стоящем за идеей конфигурации URL и Django в целом, – принципе слабой связанности. Говоря попросту, слабая связанность – это подход к разработке программного обеспечения, в котором на первое место выдвигается взаимозаменяемость составных частей. Если две части кода слабо связаны, то изменения в одной из них почти или совсем не отразятся на другой.

Идея конфигурации URL в Django – хороший пример практического применения этого принципа. В приложении Django определения URL и ассоциированные с ними функции представления слабо связаны, то есть решение о том, какой URL сопоставить данной функции, и реализация самой функции располагаются в разных местах. Это позволяет подменить одну часть, не затрагивая другую.

Рассмотрим, к примеру, представление current_datetime. Если возникнет желание изменить URL этой страницы, например, с /time/ на /current-time/, то достаточно будет внести изменение в конфигурацию URL, не трогая само представление. Наоборот, если требуется как-то изменить логику работы функции представления, то это можно сделать, не затрагивая URL, с которым эта функция ассоциирована.

А если бы вы захотели сопоставить с функциональностью вывода текущей даты несколько URL, то это тоже можно было бы легко сделать путем редактирования конфигурации URL, не внося никаких изменений в код представления. В примере ниже к функции current_datetime можно обратиться по любому из двух URL-адресов. Конечно, этот пример надуманный, но иногда такая техника оказывается полезной:

urlpatterns = patterns(‘’, (‘^hello/$’, hello), (‘^time/$’, current_datetime),

(‘^another-time-page/$’, current_datetime),

)

Конфигурация URL и представления – пример слабой связанности в действии. Мы еще не раз встретимся с проявлениями этого важного принципа на страницах этой книги.

Третье представление: динамические URL-адреса

В представлении current_datetime динамическим было содержимое страницы – текущие дата и время, но ее URL-адрес (/time/) оставался статическим. Однако в большинстве динамических веб-приложений URL содержит параметры, которые влияют на содержимое результирующей

52

Глава 3. Представления и конфигурирование URL

страницы. Так, в книжном интернет-магазине каждой книге может быть сопоставлен отдельный URL (например, /books/243/ и /books/81196/).

Сейчас мы создадим третье представление, которое будет выводить текущие дату и время со сдвигом на заданное количество часов. Наша цель – создать сайт, в котором на странице /time/plus/1/ выводятся дата

ивремя, сдвинутые вперед на один час, на странице /time/plus/2/ – дата

ивремя, сдвинутые вперед на два часа, на странице /time/plus/3/ – дата

ивремя, сдвинутые вперед на три часа, и т. д.

Неопытный разработчик, возможно, начал бы писать отдельные функции представления для каждой величины сдвига. В результате получилась бы такая конфигурация URL:

urlpatterns = patterns(‘’, (‘^time/$’, current_datetime),

(‘^time/plus/1/$’, one_hour_ahead), (‘^time/plus/2/$’, two_hours_ahead), (‘^time/plus/3/$’, three_hours_ahead), (‘^time/plus/4/$’, four_hours_ahead),

)

Очевидно, такой подход порочен. Мало того что образуются лишние функции представления, так еще приложение содержит фундаментальное ограничение: оно поддерживает только четыре предопределенных сдвига – на один, два, три и четыре часа. Если бы мы захотели создать страницу со сдвигом времени на пять часов, то пришлось бы написать отдельное представление и добавить в конфигурацию URL еще одну строку, отчего дублирование только увеличилось бы. Здесь необходима какая-то новая идея.

О красивых URL-адресах

Если вы работали с какой-нибудь другой платформой веб-раз­ работки,­ например PHP или Java, то может возникнуть жела-

ние воспользоваться параметром в строке запроса – что-то вроде /time/plus?hours=3, где сдвиг обозначается параметром hours в строке запроса URL-адреса (так называется часть после знака ?).

В Django это возможно (и мы объясним, как это сделать, в главе 8), но одна из ключевых философских идей Django заключается в том, что URL-адреса должны быть красивыми. URL /time/ plus/3/ гораздо элегантнее, проще читается и проще произносится вслух – в общем, с какой стороны ни глянь, он красивее эквивалентного адреса с параметром в строке запроса. Красивые URL – одна из характеристик качественного веб-приложения.

Механизм конфигурации URL в Django поощряет придумывание красивых URL-адресов просто потому, что использовать та-

кие адреса проще, чем не использовать.

Третье представление: динамические URL-адреса

53

Так как же спроектировать приложение, чтобы оно могло обрабатывать произвольный сдвиг? Идея в том, чтобы воспользоваться параметрическими шаблонами­ URL (wildcard URLpatterns). Выше уже отмечалось, что шаблон­ URL – это регулярное выражение, поэтому для сопоставления с одной или несколькими цифрами мы можем использовать регулярное выражение \d+:

urlpatterns = patterns(‘’,

# ...

(r’^time/plus/\d+/$’, hours_ahead),

# ...

)

(Здесь # ... означает, что могут быть и другие образцы URL, которые опущены для краткости.)

Такой шаблон­ URL соответствует любому URL вида /time/plus/2/, /time/ plus/25/ и даже /time/plus/100000000000/. Но давайте все же ограничим максимальный сдвиг 99 часами. Это означает, что допустимы только числа с одной и двумя цифрами. В терминах регулярных выражений это выглядит как \d{1,2}:

(r’^time/plus/\d{1,2}/$’, hours_ahead),

Примечание

При создании веб-приложений всегда необходимо рассматривать самые нелепые входные данные и решать, должно ли приложение их поддерживать. В данном случае мы решили ограничить экстравагантность, разрешив сдвиг не более чем на 99 часов.

Обратите внимание на символ r перед регулярным выражением. Он говорит интерпретатору Python, что далее следует r-строка, в которой не нужно обрабатывать знаки обратного слеша. Обычно обратный слеш служит для экранирования специальных символов; так, последовательность ‘\n’ интерпретируется как строка, содержащая единственный символ перевода строки. Если же поставить перед строкой r, то Python не станет экранировать символы, поэтому строка r’\n’ состоит из двух символов: обратного слеша и строчной буквы n. Существует естественный конфликт между использованием символа обратного слеша в строках Python и в регулярных выражениях, поэтому мы настоятельно рекомендуем при записи регулярных выражений пользоваться r-строками. Начиная с этого момента во всех шаблонах­ URL мы будем использовать только r-строки.

Итак, мы включили параметрическую группу в шаблон­ URL; теперь нужно как-то передать ее в функцию представления, чтобы она могла обрабатывать любой сдвиг. Для этого достаточно заключить в скобки ту часть шаблона­ URL, которая соответствует переменной части адреса. В данном случае нас интересует число, указанное в URL, поэтому заключим в скобки выражение \d{1,2}:

54

Глава 3. Представления и конфигурирование URL

(r’^time/plus/(\d{1,2})/$’, hours_ahead),

Если вы знакомы с регулярными выражениями, то эта конструкция не вызовет недоумения; круглые скобки служат для сохранения (capture) фрагмента текста, совпавшего с шаблоном­.

Окончательная конфигурация URL для последних двух представлений будет выглядеть так:

from django.conf.urls.defaults import *

from mysite.views import hello, current_datetime, hours_ahead

urlpatterns = patterns(‘’, (r’^hello/$’, hello), (r’^time/$’, current_datetime),

(r’^time/plus/(\d{1,2})/$’, hours_ahead),

)

Разобравшись с этим вопросом, напишем представление hours_ahead.

Порядок кодирования

В этом примере мы сначала составили шаблон­ URL, а потом пе- решли к написанию представления, но в предыдущих примерах порядок был противоположный. Какой подход лучше? Каждый разработчик решает сам.

Если вы предпочитаете сначала составить общую картину, то, наверное, в самом начале работы над проектом сразу выпишете все образцы URL для своего приложения, а потом начнете кодировать представления. Достоинство такого подхода в том, что имеется список того, что предстоит сделать, который по существу определяет требования к параметрам будущих функций представления.

Если же вам больше по душе разработка снизу вверх, то вы, скорее, сначала напишете представления, а потом свяжете их с шаб­ лонами URL. Это тоже нормально.

В конце концов лучший способ – тот, который больше соответствует вашему складу ума. А формально приемлемы оба подхода.

Представление hours_ahead очень похоже на current_datetime, но с одним существенным отличием: оно принимает дополнительный аргумент – количество часов сдвига. Вот как выглядит код представления:

from django.http import Http404, HttpResponse import datetime

def hours_ahead(request, offset): try:

offset = int(offset)

Третье представление: динамические URL-адреса

55

except ValueError: raise Http404()

dt = datetime.datetime.now() + datetime.timedelta(hours=offset)

html = “<html><body>Через %s часов будет %s.</body></html>” % (offset, dt) return HttpResponse(html)

Рассмотрим этот код построчно.

•• Функция представления hours_ahead принимает два параметра: request и offset.

•• request – это объект HttpRequest, так же как в представлениях hello и current_datetime. Повторим еще раз: любое представление в качестве первого параметра принимает объект HttpRequest.

•• offset – эта строка, сохраненная круглыми скобками в шаблоне­ URL. Например, при обращении к URL /time/plus/3/ параметр offset будет содержать строку ‘3’. При обращении к URL /time/plus/21/ параметр offset будет содержать строку ‘21’. Отметим, что сохраняемые значения – всегда строки, а не целые числа, даже если строка состоит из одних цифр, как, например, ‘21’.

Примечание

Строго говоря, сохраненные значения всегда являются Unicode-объектами, а не просто байтовыми строками Python, но сейчас нам это различие неважно.

•• Мы решили назвать переменную offset, но, вообще говоря, имя может быть произвольным допустимым идентификатором Python. Само имя переменной значения не имеет, важно лишь, что это второй аргумент функции после request. (В конфигурации URL можно также использовать именованные, а не позиционные аргументы, мы рассмотрим это в главе 8.)

•• Внутри функции мы первым делом вызываем int() с аргументом offset, чтобы преобразовать строку в число.

•• Если функция int() не сможет преобразовать свой аргумент (например, строку ‘foo’) в целое число, то Python возбудит исключение ValueError. В данном случае при возникновении исключения ValueError мы возбуждаем исключение django.http.Http404, которое, как нетрудно понять, приводит к появлению сообщения об ошибке 404 «Page not found».

•• Проницательный читатель удивится, как вообще мы можем попасть

вкод, где обрабатывается исключение ValueError, коль скоро регулярное выражение (\d{1,2}) в шаблоне­ URL сохраняет только цифры, и, значит, сдвиг offset может быть только строкой, составленной из цифр. Ответ такой – не можем, поскольку регулярное выражение

вшаблоне­ URL обеспечивает пусть умеренно строгий, но все же полезный контроль входных данных. Однако мы все же учитываем возможность исключения ValueError на случай, если эта функция

56

Глава 3. Представления и конфигурирование URL

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

•• В следующей строке мы вычисляем текущие дату и время и прибавляем нужное количество часов. С функцией datetime.datetime.now() вы уже знакомы по представлению current_datetime, а новое здесь – арифметические вычисления с датами, для чего мы создаем объект datetime.timedelta и прибавляем его к объекту datetime.datetime. Результат сохраняется в переменной dt.

•• Здесь же становится ясно, зачем мы вызывали функцию int() для сдвига offset, – функция datetime.timedelta требует, чтобы параметр hours был целым числом.

•• Далее конструируется HTML-разметка, которую выводит данная функция представления, – точно так же, как в current_datetime. Небольшое отличие от предыдущего примера состоит в том, что строка формата содержит два спецификатора %s вместо одного. Соответственно, мы передаем кортеж (offset, dt), содержащий подставляемые вместо спецификаторов значения.

•• И в самом конце мы возвращаем объект HttpResponse, инициализированный HTML-разметкой. Ну это вы уже знаете.

Теперь, имея эту функцию представления и обновленную конфигурацию URL, запустите сервер разработки 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/24/ и напоследок http://127.0.0.1:8000/time/plus/100/, чтобы убедиться, что заданный шаб­ лон URL принимает только однозначные и двузначные числа. В последнем случае Django должен вывести сообщение «Page not found» – точно такое же, как в разделе «Несколько слов об ошибке 404» выше. При обращении к URL http://127.0.0.1:8000/time/plus/ (вообще без указания сдвига) также должна быть выдана ошибка 404.

Красиво отформатированные страницы ошибок в Django

Полюбуйтесь на только что созданное вами замечательное веб-прило­ жение в последний раз, потому что сейчас мы его сломаем! Давайте сознательно внесем ошибку в файл views.py, закомментировав несколько строк в представлении hours_ahead:

def hours_ahead(request, offset):

#try:

#offset = int(offset)

#except ValueError:

Красиво отформатированные страницы ошибок в Django

57

#raise Http404()

dt = datetime.datetime.now() + datetime.timedelta(hours=offset)

html = “<html><body>Через %s часов будет %s.</body></html>” % (offset, dt) return HttpResponse(html)

Запустите сервер разработки и перейдите к URL /time/plus/3/. Вы увидите страницу ошибки, на которой присутствует очень много информации, в том числе сообщение об исключении TypeError в самом верху: «unsupported type for timedelta hours component: unicode» (неподдерживаемый тип для компонента timedelta hours: unicode).

Так что же произошло? Дело в том, что функция datetime.timedelta ожидает, что параметр hours – целое число, а мы закомментировали код, который преобразует offset в целое. Поэтому datetime.timedelta возбудила исключение TypeError. Это типичная ошибка, которую рано или поздно допускает любой программист.

Мы привели этот пример, чтобы продемонстрировать страницу ошибок в Django. Потратьте немного времени на изучение этой страницы и разберитесь в приведенной информации. Вот на что следует обратить внимание.

•• В начале страницы выводится основная информация об исключении: его тип, переданные исключению параметры (в данном случае сообщение «unsupported type»), файл, в котором оно возникло, и номер строки, содержащей ошибку.

•• Ниже основной информации отображается полная трассировка исключения. Она похожа на стандартную трассировку, которую выдает командный интерпретатор Python, но более интерактивна. Для каждого уровня стека (кадра) Django выводит имя файла, имя функции или метода, номер строки и исходный текст этой строки.

•• Щелкнув на строке исходного текста (темно-серого цвета), вы увидите контекст – несколько строк до и после той, где возникла ошибка.

•• Щелкните по любой из ссылок Local vars (Локальные переменные), расположенных под каждым кадром стека, чтобы посмотреть на таблицу всех локальных переменных и их значений в этом кадре в точке возникновения исключения. Эта отладочная информация может оказаться очень полезной.

•• Обратите внимание на ссылку Switch to copy-and-paste view (Переключение в режим копирования-вставки) ниже заголовка Traceback (Трассировка). Если щелкнуть по ней, то трассировка будет представлена в другом виде, упрощающем копирование и вставку. Это полезно, когда нужно передать информацию о трассировке исключения кому-то, кто может оказать техническую поддержку, например, ребятам в IRC-чате Django или подписчикам списка рассылки для пользователей Django.

•• Ниже находится кнопка Share this traceback on a public Web site (Отправить эту трассировку на открытый веб-сайт), которая позволяет сде-

58

Глава 3. Представления и конфигурирование URL

лать то же самое одним щелчком мыши. Щелкните по ней, чтобы отправить трассировку на сайт http://www.dpaste.com/. В ответ вы получите уникальный URL, который сможете сообщить другим пользователям.

•• В следующем разделе Request information (Информация о запросе) содержится много информации о веб-запросе, который привел к ошибке: данные для запросов типа GET и POST, значения cookie и различная метаинформация, например, заголовки общего шлюзового интерфейса CGI. В приложении G приведен полный перечень всего, что содержится в объекте запроса.

•• Под разделом Request information находится раздел Settings (Параметры), в котором перечислены все параметры данного экземпляра Django. (Мы уже упоминали параметр ROOT_URLCONF и по ходу изложения встретимся и с другими параметрами Django. Полный перечень параметров представлен в приложении D.)

В некоторых частных случаях страница ошибок Django может содержать и больше информации, например, если речь идет об ошибках в шаблоне­. Мы еще вернемся к этому вопросу, когда будем обсуждать систему­ шаблонов­ в Django. А пока раскомментируйте строки, относящиеся к offset = int(offset), чтобы восстановить работоспособность функции представления.

Вы относитесь к тем программистам, которые предпочитают выполнять отладку с помощью инструкций print, расставленных в стратегически важных местах? Аналогичный подход можно применять и в Django, используя страницы с сообщениями об ошибках вместо инструкций print. Если в любом месте представления вставить инструкцию assert False, то будет выдана страница ошибок. На ней вы сможете просмотреть значения локальных переменных и состояние программы. Этот прием продемонстрирован ниже, на примере представления hours_ahead:

def hours_ahead(request, offset): try:

offset = int(offset) except ValueError:

raise Http404()

dt = datetime.datetime.now() + datetime.timedelta(hours=offset) assert False

html = “<html><body>Через %s часов будет %s.</body></html>” % (offset, dt) return HttpResponse(html)

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

Что дальше?

59

мы расскажем в главе 12. А пока просто имейте в виду, что любой вновь созданный проект Django автоматически начинает работать в режиме отладки. (Звучит знакомо? Страница «Page not found», описанная выше в этой главе, работает точно так же.)

Что дальше?

До сих пор мы писали функции представления, встраивая HTMLразметку прямо в код на Python. Это было сделано специально, чтобы не усложнять изложение базовых концепций, однако в действующих приложениях этого следует избегать.

В состав Django входит простая, но весьма мощная система­ шаблонов,­ которая позволяет отделить дизайн страницы от ее кода. В следующей главе мы займемся шаблонами­ Django.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]