Содержание
- Схема URL: полезные приёмы
- Упорядочивание функций импорта
- Использование множества префиксов для представлений
- Специальные URL в режиме отладки
- Использование именованных групп
- Понимание алгоритма совпадения/группировки
- Передача дополнительных аргументов в функции представления
- Использование стандартных аргументов для представления
- Представления для особых случаев
- Обработка совпадающего текста
- Определение того, с чем совпадёт шаблон URL
- Высокий уровень абстракции в функциях представления
- Оборачивание функций представления
- Подключение других схем URL
Перевод © Попов Руслан <ruslan.popov • gmail • com>
Перевод © Дмитрий Косточно <alerion.um • gmail • com>
В главе «Представления и привязки URL» мы рассказали о функциях представлений Django и о схеме URL, в которой определено соответствие URL этим функциям. Эта глава остановится на этих вопросах более подробно.
В схеме URL нет ничего особенного, как и всё остальное в Django — это просто код на языке Python. Вы можете использовать это знание различными способами, как показано в разделах главы далее.
Рассмотрим нижеприведённый файл со схемой URL, созданой по примеру главы «Представления и привязки 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),
)
Как объяснялось в главе «Представления и привязки URL», каждый шаблон схемы URL ассоциирован с функцией представления, которая указывается в виде функционального объекта. Это означает, что необходимо импортировать функции представления вначале модуля.
Но, по мере усложнения Django приложения, его схема URL также растёт, управление этими импортами может быть утомительным. (Для каждой новой функции представления вам потребуется не забыть об её импорте, это скажется на размере оператора импорта.) Есть возможность избежать этого, просто импортируя сам модуль views . Следующий пример эквивалентен предыдущему:
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^hello/$', views.hello),
(r'^time/$', views.current_datetime),
(r'^time/plus/(d{1,2})/$', views.hours_ahead),
)
Django предоставляет другой способ указания функции представления для определённого шаблона в схеме URL. Вы можете передать строку, которая содержит имя модуля и имя функции, вместо передачи самого объекта. Продолжаем работу над нашим примером:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^hello/$', 'mysite.views.hello'),
(r'^time/$', 'mysite.views.current_datetime'),
(r'^time/plus/(d{1,2})/$', 'mysite.views.hours_ahead'),
)
Следует отметить кавычки вокруг имён представления! Мы используем 'mysite.views.current_datetime' с кавычками вместо mysite.views.current_datetime
Используя этот метод, больше нет необходимости импортировать функции представления. Django автоматически импортирует соответствующую функцию представления как только она понадобится, в соответствии с текстовым описанием её имени и пути.
Ещё сильнее код можно сократить, используя общий префикс для
пути к функциям представления. В нашем примере, каждая строка
начиналась с mysite.views, т.е. опять
дублирование. Мы можем исключить этот общий префикс и передать
его в качестве первого аргумента функции
patterns()
, например так:
from django.conf.urls.defaults import *
urlpatterns = patterns('mysite.views',
(r'^hello/$', 'hello'),
(r'^time/$', 'current_datetime'),
(r'^time/plus/(d{1,2})/$', 'hours_ahead'),
)
Следует заметить, что не надо ставить завершающую точку в префикс и не надо ставить точку перед именами функций представления. Всё это будет автоматически сделано Django.
Так какой из этих двух подходов лучше? Это зависит от вашего стиля написания кода и ваших нужд.
Преимущества использования строк вместо объектов:
Краткий код, т.к. не требуется импортировать функции представления.
Более читаемая и управляемая схема URL в случае, если ваши функции представления располагаются в нескольких различных модулях.
Преимущества использования объектов вместо строк:
Простая замена функций представления. Обратитесь к разделу «Оборачивание функций представления» в этой главе.
Стиль Python, сохраняются его традиции, такие как передача функции в виде объекта.
Оба подхода правильные. Вы можете смешивать их в одном файле, описывающем схему URL. Это ваш выбор.
На практике, при использовании строчной методики вы можете
столкнуться с ситуацией, когда функции представления в схеме
URL не будут иметь общего префикса. Однако, это не помешает
вам воспользоваться преимуществами префикса. Просто добавьте
нужное количество patterns()
, вот так:
# Раньше
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^hello/$', 'mysite.views.hello'),
(r'^time/$', 'mysite.views.current_datetime'),
(r'^time/plus/(\d{1,2})/$', 'mysite.views.hours_ahead'),
(r'^tag/(\w+)/$', 'weblog.views.tag'),
)
# Теперь
from django.conf.urls.defaults import *
urlpatterns = patterns('mysite.views',
(r'^hello/$', 'hello'),
(r'^time/$', 'current_datetime'),
(r'^time/plus/(\d{1,2})/$', 'hours_ahead'),
)
urlpatterns += patterns('weblog.views',
(r'^tag/(\w+)/$', 'tag'),
)
Всё, о чём заботится среда разработки — это о
существовании переменной urlpatterns
. Эта
переменная может быть создана динамически, как это показано в
примере. Следует отметить, что объекты, возвращаемые
patterns()
, могут быть объединены
оператором «+».
Говоря о динамическом построении
urlpatterns
, вы можете пожелать
использовать преимущества этой методики для изменения
поведения вашей схемы URL при работе Django в режиме
отладки. Чтобы осуществить это, просто проверяйте значение
параметра DEBUG во время работы приложения, вот
так:
from django.conf import settings
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^$', views.homepage),
(r'^(\d{4})/([a-z]{3})/$', views.archive_month),
)
if settings.DEBUG:
urlpatterns += patterns('',
(r'^debuginfo/$', views.debug),
)
В этом примере URL /debuginfo/ будет работать только в том случае, если параметр DEBUG установлен в True.
Ранее во всех примерах мы использовали простые, неименованные группы регулярных выражений. Мы ограничивали части URL круглыми скобками, которые требовалось обработать функцией представления, как аргументы. Существует возможность использования именованных групп регулярных выражений для получения кусков URL и последующей их передачи функции представления как именованных аргументов.
Именованные аргументы или позиционные аргументы
Функция в языке Python может быть вызвана с использованием именованных аргументов или с помощью обязательных аргументов, а в некоторых случаях, эти аргументы можно смешивать. При использовании именованных аргументов вы должны указать имена и значения аргументов. В противном случае вы просто передаёте значения, они будут назначены аргументам по порядку их определения в функции.
Например, рассмотрим эту простую функцию:
def sell(item, price, quantity):
print "Selling %s unit(s) of %s at %s" % (quantity, item, price)
Для вызова функции с помощью обязательных аргументов надо перечислить их в порядке, в котором они определены в функции:
sell('Socks', '$2.50', 6)
Для вызова функции с помощью именованных аргументов следует указать имена и значения аргументов. Следующие операторы эквивалентны предыдущему примеру:
sell(item='Socks', price='$2.50', quantity=6)
sell(item='Socks', quantity=6, price='$2.50')
sell(price='$2.50', item='Socks', quantity=6)
sell(price='$2.50', quantity=6, item='Socks')
sell(quantity=6, item='Socks', price='$2.50')
sell(quantity=6, price='$2.50', item='Socks')
Наконец, вы можете смешивать эти два подхода, пока обязательные аргументы находятся на своих местах. Следующие операторы эквивалентны предыдущим примерам:
sell('Socks', '$2.50', quantity=6)
sell('Socks', price='$2.50', quantity=6)
sell('Socks', quantity=6, price='$2.50')
В регулярных выражениях Python синтаксис для именованных групп регулярных выражений будет таким: (?P<name>pattern), где name является именем группы, а pattern — неким шаблоном.
Ниже дан пример файла со схемой URL, в котором используются неименованные группы:
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^articles/(\d{4})/$', views.year_archive),
(r'^articles/(\d{4})/(\d{2})/$', views.month_archive),
)
Теперь приведём тот же файл, который переписан с использованием именованных групп:
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^articles/(?P<year>\d{4})/$', views.year_archive),
(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', views.month_archive),
)
Этот пример аналогичен предыдущему за исключением одного тонкого различия: полученные значения передаются в функции представления в виде именованных аргументов.
Например, при использовании не именованныхгрупп запрос к /articles/2006/03/ будет преобразован к такому вызову функции:
month_archive(request, '2006', '03')
Однако при использовании именованных групп тот же самый запрос будет преобразован к такому вызову функции:
month_archive(request, year='2006', month='03')
На практике, использование именованных групп позволяет вашей схеме URL быть более очевидной и менее подверженной внесению ошибок. Вы можете менять порядок следования аргументов в ваших функциях представления. Следуя вышеописанным примерам, если нам потребуется так изменить URL, чтобы вставить месяц перед годом, при использовании не именованных групп, нам потребуется не забыть внести соответствующие изменения в порядок аргументов в представлении month_archive. При использовании именованных групп такое изменение порядка параметров в регулярном выражении никак не скажется на работе функции представления.
Конечно, выгода от использования именованных групп идёт от краткости. Некоторые разработчики найдут синтаксис именованных групп ужасным и слишком многословным. Другим преимуществом именованных групп является читаемость, особенно для людей, незнакомых с регулярными выражениями или с вашим Django приложением. Ведь проще всего понять, что происходит в приложении, просто заглянув в файл со схемой URL, который использует именованные функции.
Недостаток использования именованных групп в схеме URL в том, что один шаблон не может содержать не именованные и именованные группы. Если вы попробуете так сделать, Django не отобразит никакой ошибки, но, вероятно, вы обнаружите, что ваша схема перестала работать так как ожидалось. Проще говоря, существует алгоритм работы парсера схемы URL в отношении именованных и не именованных групп в регулярных выражениях:
Если есть хотя бы один именованный аргумент, то не именованные аргументы игнорируются.
Иначе, передаются все не именованные аргументы в качестве обязательных.
В обоих случаях передаётся дополнительная информация в виде именованного аргумента. Читайте далее для получения подробностей.
Случается, что иногда приходится писать незначительно различающиеся функции представления. Например, скажем у вас есть два представления, чьё содержимое идентично, исключая шаблоны, которые они используют:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^foo/$', views.foo_view),
(r'^bar/$', views.bar_view),
)
# views.py
from django.shortcuts import render_to_response
from mysite.models import MyModel
def foo_view(request):
m_list = MyModel.objects.filter(is_new=True)
return render_to_response('template1.html', {'m_list': m_list})
def bar_view(request):
m_list = MyModel.objects.filter(is_new=True)
return render_to_response('template2.html', {'m_list': m_list})
Мы сами дублируем код и это неэлегантно. Сначала вы можете решить убрать избыточность просто используя одно и то же представления для обоих URL, поставите скобки в URL для того, чтобы получить его и проверить, чтобы определить какой шаблон надо использовать, например так:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^(foo)/$', views.foobar_view),
(r'^(bar)/$', views.foobar_view),
)
# views.py
from django.shortcuts import render_to_response
from mysite.models import MyModel
def foobar_view(request, url):
m_list = MyModel.objects.filter(is_new=True)
if url == 'foo':
template_name = 'template1.html'
elif url == 'bar':
template_name = 'template2.html'
return render_to_response(template_name, {'m_list': m_list})
Проблема такого решения заключается в том, что URL переносятся в код. Если вы решите переименовать /foo/ в /fooey/, вам потребуется не забыть внести изменения в код функции представления.
Элегантное решение требует включить необязательный параметр в шаблон схемы URL. Каждый шаблон в схеме может включать в себя третий элемент: словарь именованных аргументов для передачи его в функцию представления.
Учитывая это, мы можем переписать наш предыдущий пример так:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^foo/$', views.foobar_view, {'template_name': 'template1.html'}),
(r'^bar/$', views.foobar_view, {'template_name': 'template2.html'}),
)
# views.py
from django.shortcuts import render_to_response
from mysite.models import MyModel
def foobar_view(request, template_name):
m_list = MyModel.objects.filter(is_new=True)
return render_to_response(template_name, {'m_list': m_list})
Как можно увидеть, шаблон в схеме URL имеет третий параметр
template_name
Такая методика является отличным способом передачи дополнительной информации в функции представления с минимальными затратами. Эта же методика используется в поставляемых с Django приложениях, в основном в его основной системе представлений, которую мы рассмотрим в главе «Генерация данных, отличных от HTML».
Следующие секции содержат ряд идей о том как вы можете использовать эту методику в своих проектах.
Допустим, у вас есть набор представлений, которые соответствуют шаблону, наряду с другим URL, который не совпадает с шаблоном, но логика его функции представления аналогична. В этом случае, вы можете «подделать» получение значений из URL с помощью дополнительной опции для обработки этого дополнительного URL той же функцией представления.
Например, у вас может быть приложение, которое отображает некие данные для определённого дня, вот с такими URL:
/mydata/jan/01/
/mydata/jan/02/
/mydata/jan/03/
# ...
/mydata/dec/30/
/mydata/dec/31/
Довольно просто это обработать, используя синтаксис именованных групп:
urlpatterns = patterns('',
(r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view),
)
А сигнатура функции представления станет такой:
def my_view(request, month, day):
# ....
Это прямолинейный подход, ничего подобного раньше вы не видели. Эта уловка пригодится когда понадобится добавить другой URL, который будет использовать my_view, но её URL не будет обрабатывать month и/или day.
Например, вы можете добавить другой URL, /mydata/birthday/, который будет аналогичен /mydata/jan/06/. В этом случае использование дополнительного параметра даст вам преимущество:
urlpatterns = patterns('',
(r'^mydata/birthday/$', views.my_view, {'month': 'jan', 'day': '06'}),
(r'^mydata/(?P<month>\w{3})/(?P<day>\d\d)/$', views.my_view),
)
Секрет в том, что не требуется вносить какие-либо изменения
в код функции представления. Функция должна
обрабатывать параметры
month
и day
, не важно
получены ли они из URL или через дополнительные параметры.
Снижение количества дублирующего кода является хорошим тоном разработки программного обеспечения. Например, эти функции, написанные на языке Python:
def say_hello(person_name):
print 'Hello, %s' % person_name
def say_goodbye(person_name):
print 'Goodbye, %s' % person_name
мы можем упростить, выделив приветствие в параметр функции:
def greet(person_name, greeting):
print '%s, %s' % (greeting, person_name)
Вы можете использовать эту философию применимо к представлениям Django, используя дополнительные параметры шаблонов схемы URL.
Учитывая это, вы можете начать создание высокоуровневых
абстракций для ваших представлений. Вместо того, чтобы
думать «Это представление отображает список объектов
Event
»
или «Этопредставление отображает список объектов
BlogEntry
», рассматривайте их
как два особых случая«Представление, которое отображает список объектов, где тип объекта является переменной».
Для примера рассмотрим следующий код:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^events/$', views.event_list),
(r'^blog/entries/$', views.entry_list),
)
# views.py
from django.shortcuts import render_to_response
from mysite.models import Event, BlogEntry
def event_list(request):
obj_list = Event.objects.all()
return render_to_response('mysite/event_list.html', {'event_list': obj_list})
def entry_list(request):
obj_list = BlogEntry.objects.all()
return render_to_response('mysite/blogentry_list.html', {'entry_list': obj_list})
Эти два представления делают то же самое: они отображают список объектов. Таким образом, можно упростить тип объекта, который они отображают:
# urls.py
from django.conf.urls.defaults import *
from mysite import models, views
urlpatterns = patterns('',
(r'^events/$', views.object_list, {'model': models.Event}),
(r'^blog/entries/$', views.object_list, {'model': models.BlogEntry}),
)
# views.py
from django.shortcuts import render_to_response
def object_list(request, model):
obj_list = model.objects.all()
template_name = 'mysite/%s_list.html' % model.__name__.lower()
return render_to_response(template_name, {'object_list': obj_list})
Внеся небольшие изменения, мы неожиданно получили образцовое представление, пригодное для многократного использования. Начиная с этого момента, как только нам понадобится представление, которое отображает набор объектов в виде списка, мы можем просто использовать представление object_list, вместо создания ещё одного представления. Теперь рассмотрим, что же мы сделали:
Мы напрямую передаём классы модели, используя параметр
model
. Словарь с дополнительными параметрами может передавать в представление объект Python любого типа, а не только строки.Строка model.objects.all() является примером утиного набора FIXME: «Если это ходит как утка и разговаривает как утка, мы можем считать это уткой.» Следует отметить, что код ничего не знает о типе объекта
model
, единственное требование —model
должна иметь атрибут objects, у которого как раз есть метод all().Мы используем model.__name__.lower() для того, чтобы определить имя шаблона. В Python каждый класс имеет атрибут __name__, который содержит имя класса. Эта особенность полезна в подобных случаях, когда мы не знаем имя класса во время работы приложения. Например, атрибут __name__ класса
BlogEntry
содержит строку «BlogEntry».Разница между примерами в том, что мы передаём обобщённую переменную
object_list
в шаблон. Мы могли легко изменить имя этой переменной наblogentry_list
илиevent_list
, но оставим это в качестве домашнего задания.
Так как сайты, хранящие свои данные в базах данных, используют одинаковые шаблоны, Django поставляется с набором «общие представления», который используется вышеописанную методику для сохранения вашего времени. Мы рассмотрим встроенные общие представления Django в главе «Генерация данных, отличных от HTML».
При распространении приложения для Django может так случиться, что ваши пользователи пожелают внести некоторые изменения в конфигурацию приложения. В таком случае, хорошей идеей будет добавить перехватчики к вашим представлениям для любого параметра конфигурации, который может подвергнуться такому изменению. Для этого опять надо использовать дополнительные параметры шаблона схемы URL.
Чаще всего изменению подвергаются имена шаблонов:
def my_view(request, template_name):
var = do_something()
return render_to_response(template_name, {'var': var})
При наличии конфликта параметров, дополнительные параметры шаблона схемы URL получают преимущество над передаваемыми в функцию параметрами. Другими словами, если ваш шаблон получил переменную с именованной группой, а дополнительный параметр включает в себя переменную с таким же именем, именно он и будет использован.
Например:
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
(r'^mydata/(?P<id>\d+)/$', views.my_view, {'id': 3}),
)
В данном примере параметр id
находится и
в регулярном выражении и в словаре дополнительных
параметров. Параметр из словаря получает преимущество. Это
означает, что любой запрос (т.е., /mydata/2/
или /mydata/432432/) будет интерпретирован
как-будто бы параметр id установлен в
3, независимо от значения в URL.
Проницательные читатели отметят, что в данном случае захват
переменной id
с помощью регулярного выражения — это просто трата времени, так как
полученное значение будет всегда перекрываться значением
параметра id
из словаря. Это правильное
поведение, мы напоминаем об этом лишь для того, чтобы вы не
сделали ошибку.
Другим удобным способом является указание стандартных значений для аргументов представления. Эти значения используются в случае, когда соответствующие параметры не определены при вызове функций.
Пример:
# urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^blog/$', views.page),
(r'^blog/page(?P<num>\d+)/$', views.page),
)
# views.py
def page(request, num="1"):
# Output the appropriate page of blog entries, according to num.
# ...
В данном примере оба шаблона URL указывают на одно и то же
представление, views.page , но первый шаблон не
получает параметров из URL. Если срабатывает первый шаблон, то
будет выполнена функция page()
будет
использовать стандартные значения
аргументов(num=1). Если срабатывает второй
шаблон, функция page()
будет использовать
значение для параметра num
полученное с
помощью регулярного выражения.
Заметьте, что в качестве значения по-умолчанию мы указали
строку "1", а не число
1. Это для согласованности т.к. любое полученное для
num
значение будет строкой.
Как объяснялось ранее, обычно используют данную методику
совместно с параметрами конфигурации. Этот пример немного
улучшает пример из раздела
«
Позволяем настройку представления
»,
предоставляя стандартное значение для template_name
:
def my_view(request, template_name='mysite/my_view.html'):
var = do_something()
return render_to_response(template_name, {'var': var})
Иногда так случается, что есть шаблон в вашей схеме URL, который обрабатывает большой набор URL, но вам необходимо реализовать для них особые случаи. Для этого надо просто описать эти случаи первыми.
Например, страницы «добавить объект» на сайте администратора Django представлены в схеме URL такой строкой:
urlpatterns = patterns('',
# ...
('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
# ...
)
Эта строка совпадает с URL такими как /myblog/entries/add/ и /auth/groups/add/. Однако страница добавления пользователей (/auth/user/add/) является особым случаем — оно не отображает все поля формы, она отображает два поля для ввода пароля и так далее. Мы можем решить эту проблему реализовав особое поведение представления, например так:
def add_stage(request, app_label, model_name):
if app_label == 'auth' and model_name == 'user':
# do special-case code
else:
# do normal code
Но такой подход не элегантен по причинам, которые мы обсуждали чуть ранее: этот подход помещает логику обработки URL в представление. В качестве элегантного решения мы можем воспользоваться тем фактом, что шаблоны URL обрабатываются в порядке их определения в файле:
urlpatterns = patterns('',
# ...
('^auth/user/add/$', 'django.contrib.admin.views.auth.user_add_stage'),
('^([^/]+)/([^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'),
# ...
)
Теперь вызов /auth/user/add/ будет обработан представлением user_add_stage. Несмотря на то, что этот URL совпадает со вторым шаблоном, он будет обработан первым.
Каждый аргумент, полученный из URL с помощью регулярного выражения передаётся представлению в виде обычной строки. Например:
(r'^articles/(?P<year>\d{4})/$', views.year_archive),
Аргумент year
в этой строке будет передан в
представление views.year_archive() в виде
строки, не в виде целого числа, несмотря на то, что выражение
\d{4} совпадает только со строкой чисел.
Эту особенность следует помнить при разработке
приложения. Множество встроенных функций Python принимают
объекты строго определённого типа. Общей ошибкой является
попытка создать объект datetime.date
,
используя строковое представление даты, а не целочисленное:
>>> import datetime
>>> datetime.date('1993', '7', '9')
Traceback (most recent call last):
...
TypeError: an integer is required
>>> datetime.date(1993, 7, 9)
datetime.date(1993, 7, 9)
С точки зрения схемы URL это ошибка выглядит так:
# urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^articles/(\d{4})/(\d{2})/(\d{2})/$', views.day_archive),
)
# views.py
import datetime
def day_archive(request, year, month, day)
# The following statement raises a TypeError!
date = datetime.date(year, month, day)
А правильно можно написать так:
def day_archive(request, year, month, day):
date = datetime.date(int(year), int(month), int(day))
Следует отметить, что сам метод int()
вызывает исключение ValueError при передаче ему
строки, в которой есть не только цифры. Но мы избегаем эту
ошибку в нашем случае, так как регулярное выражения в шаблоне
URL обеспечивает передачу только цифр.
При получении запроса Django пытается найти соответствующий шаблон в схеме URL как для обычной строки Python (не как для Unicode строки). При этом параметры GET и POST или доменное имя не принимаются во внимание. Также не рассматривается начальный слэш, потому что каждое URL имеет начальный слэш.
Например, в запросе http://www.example.com/myapp/ Django будет искать шаблон URL для myapp/. В запросе http://www.example.com/myapp/?page=3 Django будет искать шаблон для myapp/.
Метод запроса (т.е. GET, POST, HEAD) не принимается во внимание при обработке схемы URL. Другими словами, все методы будут обрабатываться теми же функциями. Ветвление обработки запроса в зависимости от использованного метода лежит полностью на функциях представления.
Говоря о разветвлении логики в функциях представления основанных на методе запроса, давайте посмотрим каким красивым способом мы можем это сделать:
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
# ...
(r'^somepage/$', views.some_page),
# ...
)
# views.py
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response
def some_page(request):
if request.method == 'POST':
do_something_for_post()
return HttpResponseRedirect('/someurl/')
elif request.method == 'GET':
do_something_for_get()
return render_to_response('page.html')
else:
raise Http404()
В этом примере, обработка методов POST и
GET функцией some_page()
совсем отличается. Все что у них общего это URL
/somepage/. Поэтому не очень красиво
обрабатывать их одной функцией. Было бы на много лучше, если
бы одна функция обрабатывала запрос GET, а
другая - POST - и вызывалась только одна,
которая необходима для данного типа запросов.
Мы можем реализовать это создав функцию, которая будет
вызывать другие функции, руководствуясь определённой
логикой. Этот пример покажет как этот подход поможет упростить
нашу функцию some_page()
.
# views.py
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response
def method_splitter(request, GET=None, POST=None):
if request.method == 'GET' and GET is not None:
return GET(request)
elif request.method == 'POST' and POST is not None:
return POST(request)
raise Http404
def some_page_get(request):
assert request.method == 'GET'
do_something_for_get()
return render_to_response('page.html')
def some_page_post(request):
assert request.method == 'POST'
do_something_for_post()
return HttpResponseRedirect('/someurl/')
# urls.py
from django.conf.urls.defaults import *
from mysite import views
urlpatterns = patterns('',
# ...
(r'^somepage/$', views.method_splitter, {'GET': views.some_page_get, 'POST': views.some_page_post}),
# ...
)
Давайте рассмотрим что она делает:
Мы создали новую функцию представления
method_splitter()
, которая вызывает другие функции в соответствии с request.method. Она принимает два именованных аргументаGET
иPOST
, которые должны быть функциями представления. Если request.method равен 'GET' - вызывается функцияGET
, если 'POST' -POST
. Если request.method равен чему-то другому(HEAD, например), илиGET
илиPOST
не указаны, тогда вызывается исключения Http404.В схеме URL /somepage/ указывает на
method_splitter()
и передает ей дополнительные параметры - функции представления дляGET
и POST.Разделяем функцию представления
some_page()
на две функции —some_page_get()
иsome_page_post()
. Это на много лучше, чем использовать всю эту логику в одной функции.Заметьте что теперь эти функции не должны проверять request.method, потому что
method_splitter()
делает это. (В тот момент, когдаsome_page_post()
будет вызвана, мы можем быть уверенны, что request.method равен 'POST'.) Все же, что бы быть уверенными, ну и для документирования, мы добавили assert, который проверяет что в request.method то, что мы ожидаем.
Теперь у нас есть прекрасная общая функция представления,
которая содержит логику делегирования представления по
request.method. Представление
method_splitter()
никакне привязано к
нашему приложению и, следовательноЮ, мы можем использовать его
в других проектах.
Но ещё есть способ улучшить
method_splitter()
. Код представления
подразумевает, что представления для GET и
POST не принимают никаких аргументов кроме
request. Что произойдёт, если нам потребуется
использовать method_splitter()
с
представлениями, которые получают, например, текст из URL или
принимают необязательные именованные аргументы?
Для решения этой задаче, мы можем воспользоваться интересной особенностью языка Python: переменные аргументы со звёздочками. Сначала рассмотрим пример, а потом разберёмся с ним:
def method_splitter(request, *args, **kwargs):
get_view = kwargs.pop('GET', None)
post_view = kwargs.pop('POST', None)
if request.method == 'GET' and get_view is not None:
return get_view(request, *args, **kwargs)
elif request.method == 'POST' and post_view is not None:
return post_view(request, *args, **kwargs)
raise Http404
Здесь мы переписали method_splitter()
так, чтобы избавиться от неименованных аргументов в
представлениях GET и POST в
пользу *args и **kwargs
(обратите внимание на звёздочки). Это особенность языка
Python, которая позволяет функции принимать разное количество
аргументов, имена которых не известны до момента исполнения
кода. Если поместить одну звёздочку перед параметром в
определении функции, то любые
неименованные аргументы будут развёрнуты в единый кортеж. Если поместить две звёздочки — любые
именованные аргументы будут развёрнуты в
словарь.
Например, для этой функции:
def foo(*args, **kwargs):
print "Positional arguments are:"
print args
print "Keyword arguments are:"
print kwargs
Вот результаты:
>>> foo(1, 2, 3)
Positional arguments are:
(1, 2, 3)
Keyword arguments are:
{}
>>> foo(1, 2, name='Adrian', framework='Django')
Positional arguments are:
(1, 2)
Keyword arguments are:
{'framework': 'Django', 'name': 'Adrian'}
Возвращаясь к method_splitter()
, вы
можете увидить, что мы используем *args и
**kwargs для приёма любых
аргументов в функцию и передачи их соответствующему
представлению. Но перед этим, мы выполняем
kwargs.pop() дл получения GET и
POST аргументов, если они есть. (Мы используем
pop()
с значением по-умолчанию
None, чтобы избежать KeyError
при отсутствии аргументов.)
Наша последняя уловка также основана на особенностях языка Python. Скажем, вы обнаружили, что постоянно повторяете кусок кода в своих представлениях, например:
def my_view1(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
# ...
return render_to_response('template1.html')
def my_view2(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
# ...
return render_to_response('template2.html')
def my_view3(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
# ...
return render_to_response('template3.html')
Здесь, каждое представление начинается с проверки аутентификации request.user (залогинился ли пользователь на сайте или нет) и последующего перенаправления на /accounts/login/, если аутентификация не пройдена. (Следует отметить, что мы ещё не касались request.user — обратитесь к главе «Средства от других разработчиков», но как вы можете представить, объект request.user представляет текущего пользователя (анонимного или аутентифицировавшегося).
Будет неплохо убрать этот повторяющийся код из каждого представления и просто пометить их как нуждающихся в аутентифицировании пользователя. Мы можем реализовать это с помощью обёртывания представления. Изучите следующий код:
def requires_login(view):
def new_view(request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
return view(request, *args, **kwargs)
return new_view
Эта функция, requires_login
, принимает
функцию представления (view) и возвращает новую
функцию представления (new_view). Новая функция
определена внутри
requires_login
и обрабатывает логику
проверки request.user.is_authenticated(), а
затем передаёт управление функции представления
(view).
Теперь мы можем удалить проверку if not request.user.is_authenticated() из наших функций
представления и просто обернуть их с помощью
requires_login
в нашей схеме URL:
from django.conf.urls.defaults import *
from mysite.views import requires_login, my_view1, my_view2, my_view3
urlpatterns = patterns('',
(r'^view1/$', requires_login(my_view1)),
(r'^view2/$', requires_login(my_view2)),
(r'^view3/$', requires_login(my_view3)),
)
Всё будет работать как и раньше, но код будет менее избыточным. Сейчас мы создали общую функцию —
requires_login
, с помощью которой мы
можем легко добавлять функционал проверки аутентификации
пользователя к любой функции представления.
11 комментариев | Оставьте комментарий
а как будет выглядеть функции представления?
def my_view1(request):
# ...
return render_to_response('template1.html')
так я правильно понял? я иммею в виду они только request принимают?
Ответ на Nelly_lucky
а как будет выглядеть функции представления?
def my_view1(request):
# ...
return render_to_response('template1.html')
так я правильно понял? я иммею в виду они только request принимают?
Нет, еще параметры спарсеные регулярным выражением, которым задан URL и еще в urlpatterns можно указать, какие параметры будут передавать, кроме request.
утиного набора FIXME:
утиной типизации http://ru.wikipedia.org/wiki/Утиная_типизация
Не совсем понял, зачем нужна обёртка в последнем примере. У неё ведь нет своей логики, она тупо передаёт управление другой функции. Разве нельзя было сделать без обёртки ?:
def requires_login(view):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
return view(request, *args, **kwargs)
Ответ на Krest
Не совсем понял, зачем нужна обёртка в последнем примере. У неё ведь нет своей логики, она тупо передаёт управление другой функции. Разве нельзя было сделать без обёртки ?:
def requires_login(view):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
return view(request, *args, **kwargs)
Ну попробуйте. Тот пример так же немного кривой. Там нужно еще wraps применить из functools
тут если внимательно присмотреться то >>>def new_view..... это обёртка а >>>def requires_login.... это обёртка обёртки. Зачем такая инкапсуляция не совсем ясно.
Модератор удали мои посты пожалуйста. Сори за флуд....
Ответ на Krest
тут если внимательно присмотреться то >>>def new_view..... это обёртка а >>>def requires_login.... это обёртка обёртки. Зачем такая инкапсуляция не совсем ясно.
Модератор удали мои посты пожалуйста. Сори за флуд....
Почитайте про декораторы. Ваш вариант не будет работать т.к. request передается в представление в момент его вызова (запроса), обертка(декоратор) запускается при импорте модуля.
Ответ на alerion
Почитайте про декораторы. Ваш вариант не будет работать т.к. request передается в представление в момент его вызова (запроса), обертка(декоратор) запускается при импорте модуля.
в python-не при импорте модуля ничего вроде бы не запускается, а только лишь передаётся пространство имён, пока не будет прямого обращения к имортируемой функции(в данном случае обёртки). Это в принципе и к локальным функциям относится... Но наша обёртка при обращении, моментально распаковывает вторую функцию которая в свою очередь распаковывает 3-тию функцию. Непонятно зачем она нужна....
Наша функция-обёртка к декораторам не относится, так как декоратор вылавливает вызов функции и переопределяет(изменяет) её логику.
а если так ?
def requires_login(request, view):
if not request.user.is_authenticated():
return HttpResponseRedirect('/accounts/login/')
return view(request, *args, **kwargs)
Ответ на Krest
в python-не при импорте модуля ничего вроде бы не запускается, а только лишь передаётся пространство имён, пока не будет прямого обращения к имортируемой функции(в данном случае обёртки). Это в принципе и к локальным функциям относится... Но наша обёртка при обращении, моментально распаковывает вторую функцию которая в свою очередь распаковывает 3-тию функцию. Непонятно зачем она нужна....
Наша функция-обёртка к декораторам не относится, так как декоратор вылавливает вызов функции и переопределяет(изменяет) её логику.
Ну это смотря как импортировать. Если вы добавить в модуль или в __init__.py `print 1`, то вы увидите это в консоли. Про пространство имен не понял. Просто выполняется код импортируемого модуля, создаются все определенные функции, классы и тд. так они в пространство имен и попадают. Не понятно как вы собираетесь использовать вашу функцию. Вот вы используете request в функции. А как вы собираетесь его передавать при добавлении представления в urlpatterns, если request создается при запросе на сервер.
И это все же декоратор, просто используется как функция.
Вопрос такой возник по поводу патернов:
в файле url.py
url(r'^$', home, {'page': 1}, name='home'),
url(r'^(?P<page>\d+)$', home, name='home'),
работает как '~/' так и '~/n' но такая проблема - когда на вюхе генерирую список ссылок
{% for page in pages %}
{% url home page %}"
{% endfor %}
то первая страница отображается как '~/1' можно ли как-то побороть ето поведение(чтобы первая страница которая дефолтная вела на '~/') без проверки на то что это первая страница?