Чистая, элегантная схема URL-ов – это важная часть качественного приложения. Django позволяет проектировать URL-адреса как вы пожелаете, без ограничений “фреймворка”.
В URL-ах не нужны ни .php, ни .cgi, ни всякая ерунда вроде 0,2097,1-1-1928,00.
Читайте Cool URIs don’t change, создателя World Wide Web, Тима Бернерса-Ли, чтобы узнать почему URL-ы должны быть красивыми и практичными.
Для определения URL-ов приложения, создайте модуль Python, неофициально названный URLconf (конфигурация URL-ов). Этот модуль содержит код Python, который отображает URL-шаблоны (регулярные выражения) и связанные функции Python (ваши представления).
Эта конфигурация может быть короткой или длинной настолько, насколько это нужно. Она может ссылаться на другие конфигурации. И, так как это код Python, может создаваться динамически.
Django также предоставляет метод для перевода URL на текущий язык. Обратитесь к документации на интернационализацию для подробностей.
При запросе к странице вашего Django-сайта, используется такой алгоритм для определения какой код выполнить:
Django определяет какой корневой модуль URLconf использовать. Обычно, это значение настройки ROOT_URLCONF, но, если объект запроса HttpRequest содержит атрибут urlconf (установленный request middleware), его значение будет использоваться вместо ROOT_URLCONF.
Django загружает модуль конфигурации URL и ищет переменную urlpatterns. Это должен быть список экземпляров django.conf.urls.url().
Django перебирает каждый URL-шаблон по порядку, и останавливается при первом совпадении с запрошенным URL-ом.
Если одно из регулярных выражений соответствует URL-у, Django импортирует и вызывает соответствующее представление, которое является просто функцией Python(или представление-класс). При вызове передаются следующие аргументы:
Объект HttpRequest.
Если в результате применения регулярного выражения получили именованные совпадения, они будут переданы как позиционные аргументы.
Именованные аргументы создаются из именованных совпадений. Они могут быть перезаписаны значениями из аргумента kwargs, переданного в django.conf.urls.url().
Если ни одно регулярное выражение не соответствует, или возникла ошибка на любом из этапов, Django вызывает соответствующий обработчик ошибок. Смотрите `Error handling`_ ниже.
Вот пример простого URLconf:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/([0-9]{4})/$', views.year_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]
Заметим:
Для получения совпадающего значения из URL, просто добавьте скобки вокруг него.
Не нужно добавлять косую черту в начале, потому что каждый URL содержит его. Например, используйте ^articles, вместо ^/articles.
Символ 'r' перед каждым регулярным выражением не обязателен, но рекомендуется. Он указывает Python что строка “сырая(raw)” и ничего в строке не должно быть экранировано. Смотрите Dive Into Python’s explanation.
Примеры запросов:
Запрос к``/articles/2005/03/`` будет обработан третьим элементом списка. Django вызовет функцию views.month_archive(request, '2005', '03').
/articles/2005/3/ не соответствует ни одному URL-шаблону, потому что третья запись требует две цифры в номере месяца.
/articles/2003/ соответствует первому выражению, не второму, потому что шаблоны проверяются по порядку и берется первый найденный. Не стесняйтесь использовать порядок для обработки различных ситуаций, таких как эта. В данном примере Django вызовет функцию views.special_case_2003(request).
/articles/2003 не соответствует ни одному регулярному выражению, потому что каждое ожидает, что URL оканчивается на косую черту.
/articles/2003/03/03/ соответствует последнему выражению. Django вызовет функцию views.article_detail(request, '2003', '03', '03').
Пример выше использует простые, не именованные группы совпадений в регулярных выражениях (то, что в скобках), чтобы получить значения из URL и передать в представление как позиционные аргументы. Но вы можете использовать имена для групп и значения будут передаваться как именованные аргументы.
Для регулярных выражений в Python синтаксис для именованных совпадений выглядит таким образом (?P<name>pattern), где name это название группы, а pattern – шаблон.
Вот пример конфигурации URL, переписанный с использованием именованных групп:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]
Он выполняет то же самое, что и предыдущий пример, с одним небольшим отличием: полученные значения передаются в представление как именованные аргументы, а не позиционные. Например:
Запрос к /articles/2005/03/ вызовет функцию views.month_archive(request, year='2005', month='03'), вместо views.month_archive(request, '2005', '03').
Запрос к /articles/2003/03/03/ вызовет views.article_detail(request, year='2003', month='03', day='03').
На практике это означает, что ваша конфигурация URL более понятна и меньше зависит от ошибки в порядке аргументов, также вы можете изменять порядок аргументов в представлении. Конечно, эти преимущества достигаются за счет краткости, некоторые разработчики считают именованные группы уродливыми и слишком многословными.
Вот алгоритм, которому следует синтаксический анализатор конфигурации URL, для определения использовать значения именованных или не именованных совпадений:
Если существует именованный аргумент, он будет использован вместо позиционного аргумента.
Иначе все неименованные параметры будут переданы как позиционные аргументы.
В любом случае дополнительные именованные аргументы будут переданы как именованные. Смотрите `Passing extra options to view functions`_ ниже.
URLconf использует запрашиваемый URL как обычную строку Python. Он не учитывает параметры GET, POST и имя домена.
Например, при запросе к https://www.example.com/myapp/, URLconf возьмет myapp/.
При запросе к https://www.example.com/myapp/?page=3 – myapp/.
URLconf не учитывает тип запроса. Другими словами, все типы запросов – POST, GET, HEAD, и др. – будут обработаны одним представлением при одинаковом URL.
Каждый найденный аргумент передается в представление как строка, независимо от того, какое “совпадение” определено в регулярном выражении. Например, URLconf содержит такую строку:
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
не числом, несмотря на то, что [0-9]{4} отлавливает только числа.
Принято указывать значения по-умолчанию для аргументов представления. Пример URLconf и представления:
# URLconf
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]
# View (in blog/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 из URL, которое найдет регулярное выражение.
Каждое регулярное выражение в urlpatterns будет скомпилировано при первом использовании. Это делает систему невероятно быстрой.
Если Django не может найти подходящий шаблон URL, или было вызвано исключение в процессе обработки запроса, Django вызовет соответствующее представление обрабатывающее ошибку.
Эти представления определены в четырёх переменных. Их значения по-умолчанию должны подойти для большинства проектов, но вы можете их поменять при необходимости.
Подробности в разделе о переопределении обработчика ошибок.
Эти значения должны быть определены в главном URLconf.
Значение это функции, или полный путь для импорта, которая будет вызвана, если не был найден подходящий URL-шаблон.
Есть следующие переменные:
handler400 – Смотрите django.conf.urls.handler400.
handler403 – Смотрите django.conf.urls.handler403.
handler404 – Смотрите django.conf.urls.handler404.
handler500 – Смотрите django.conf.urls.handler500.
В любой момент, ваш urlpatterns может “включать” другие модули URLconf.
Вот пример URLconf для сайта Django. Он включает множество других конфигураций URL:
from django.conf.urls import include, url
urlpatterns = [
# ... snip ...
url(r'^community/', include('django_website.aggregator.urls')),
url(r'^contact/', include('django_website.contact.urls')),
# ... snip ...
]
Заметим, что регулярные выражения не содержат $ (определитель конца строки), но содержит косую черту в конце. Каждый раз, когда Django встречает include() (django.conf.urls.include()), из URL обрезается уже совпавшая часть, остальное передается во включенный URLconf для дальнейшей обработки.
Другой возможностью будет добавление дополнительных URL-шаблонов с помощью списка экземпляров url(). Например, рассмотрим такую схему:
from django.conf.urls import include, url
from apps.main import views as main_views
from credit import views as credit_views
extra_patterns = [
url(r'^reports/$', credit_views.report),
url(r'^reports/(?P<id>[0-9]+)/$', credit_views.report),
url(r'^charge/$', credit_views.charge),
]
urlpatterns = [
url(r'^$', main_views.homepage),
url(r'^help/', include('apps.help.urls')),
url(r'^credit/', include(extra_patterns)),
]
В этом примере URL /credit/reports/ обработан представлением credit_views.report().
Такой подход может применяться для уменьшения дублирования кода в настройках URL, когда используется один и тот же шаблонный префикс. Например, возьмём такую конфигурацию URL:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/history/$', views.history),
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/edit/$', views.edit),
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/discuss/$', views.discuss),
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/permissions/$', views.permissions),
]
Мы можем сделать её проще, указав общий префикс только один раз и сгруппировав различающиеся суффиксы:
from django.conf.urls import include, url
from . import views
urlpatterns = [
url(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/', include([
url(r'^history/$', views.history),
url(r'^edit/$', views.edit),
url(r'^discuss/$', views.discuss),
url(r'^permissions/$', views.permissions),
])),
]
Включенный URLconf получает все аргументы найденные родительским URLconfs, поэтому этот пример работает:
# In settings/urls/main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^(?P<username>\w+)/blog/', include('foo.urls.blog')),
]
# In foo/urls/blog.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.blog.index),
url(r'^archive/$', views.blog.archive),
]
В примере выше, найденный аргумент "username" передается во включенный URLconf, как и ожидалось.
Регулярные выражения позволяют использовать вложенные аргументы, и Django может их найти и передать в представление. Во время поиска аргументов Django попытается получить самый внешний аргумент, игнорируя вложенные аргументы. Возьмем следующие шаблоны URL-ов, которые принимает необязательный номер страницы:
from django.conf.urls import url
urlpatterns = [
url(r'blog/(page-(\d+)/)?$', blog_articles), # bad
url(r'comments/(?:page-(?P<page_number>\d+)/)?$', comments), # good
]
Оба шаблона используют вложенные аргументы и могут обрабатывать URL-ы: например, для blog/page-2/ будет найдено представление blog_articles с двумя позиционными аргументами page-2/ и 2. Второй URL-шаблон для comments для comments/page-2/ найдет именованный аргумент page_number со значеним 2. Внешний аргумент в этом случае не захватываемый из-за (?:...).
При получении URL-а для представления blog_articles необходимо указать самый внешний аргумент(page-2/) или ни одного аргумента в данном случае. В то время как для comments необходимо передать значение page_number или не одного аргумента.
Вложенные захватываемые аргументы создают сильную связанность между URL и аргументами представления, как это показано для blog_articles: представление получает часть URL-а (page-2/) вместо значение, которое на самом деле необходимо представлению. Эта связанность особенно заметна при создании URL-а т.к. необходимо передать часть URL-а вместо номера страницы.
Как правило, URL-шаблон должен захватывать только необходимые для представления аргументы.
Конфигурация URL-ов позволяет определить дополнительные аргументы для функции представления, используя словарь Python.
Функция django.conf.urls.url() может принимать третий необязательный элемент. Этот элемент является словарем, который определяет дополнительные именованные аргументы для функции представления.
Например:
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'foo': 'bar'}),
]
Например, при запросе к /blog/2005/, Django вызовет views.year_archive(request, year='2005', foo='bar').
Такой подход используется в syndication framework для передачи параметров и дополнительных данных в представление.
Конфликты переменных
Если регулярное выражение URL-шаблона выделяет из URL-а аргумент с названием, которое уже используется в дополнительных именованных аргументах, будет использован аргумент из словаря дополнительных аргументов, вместо значения из URL.
Аналогично вы можете передать дополнительные аргументы в include(). При этом, каждый URL-шаблон включенного URLconf будет дополнен этими дополнительными аргументами.
Например, эти два URLconf работают идентично:
Первый:
# main.py
from django.conf.urls import include, url
urlpatterns = [
url(r'^blog/', include('inner'), {'blogid': 3}),
]
# inner.py
from django.conf.urls import url
from mysite import views
urlpatterns = [
url(r'^archive/$', views.archive),
url(r'^about/$', views.about),
]
Второй:
# main.py
from django.conf.urls import include, url
from mysite import views
urlpatterns = [
url(r'^blog/', include('inner')),
]
# inner.py
from django.conf.urls import url
urlpatterns = [
url(r'^archive/$', views.archive, {'blogid': 3}),
url(r'^about/$', views.about, {'blogid': 3}),
]
Дополнительные аргументы всегда передаются каждому представлению во включенном URLconf, независимо от того, принимает оно эти аргументы или нет. Поэтому, такой подход полезен только если вы уверенны, что каждое представление принимает передаваемые аргументы.
Обычной задачей является получение URL-а по его определению для отображения пользователю или для редиректа.
Очень важно не “хардкодить” URL-ы (трудоемкая и плохо поддерживаемая стратегия). Также не следует создавать “костыли” для генерации URL-ов, которые не следуют задокументированному дизайну URLconf.
В общем необходимо придерживаться принципа DRY. Немаловажно иметь возможность менять URL-ы в одном месте, а не выполнять поиск и замену по всему проекту.
Для получения URL-а нам необходим его идентификатор, то есть название URL-шаблона, и позиционные и именованные аргументы.
В Django для работы с URL-ами используется так называемый “URL mapper”. Ему передается URLconf, и теперь его можно использовать в два направления:
Получая запрошенный URL находит необходимое представление и предоставляет все необходимые аргументы полученные из URL-а.
Получая идентификатор представления и передаваемые ему аргументы, возвращает URL.
Первое это то, что мы уже рассмотрели в предыдущем разделе. Второе называется URL reversing, в общем получение URL-а по его названию.
Django предоставляет инструменты для получения URL-ов в различных компонентах фреймворка:
В шаблонах: Использование шаблонного тега url.
В Python коде: Использование функции django.core.urlresolvers.reverse().
На более высоком уровне для привязки URL-ов к моделям - метод get_absolute_url().
Рассмотрим следующий URLconf:
from django.conf.urls import url
from . import views
urlpatterns = [
#...
url(r'^articles/([0-9]{4})/$', views.year_archive, name='news-year-archive'),
#...
]
В соответствии с ним архиву nnnn года соответствует URL /articles/nnnn/.
Вы можете получить его в шаблоне следующим образом:
<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>
{# Or with the year in a template context variable: #}
<ul>
{% for yearvar in year_list %}
<li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>
{% endfor %}
</ul>
В Python коде:
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
def redirect_to_year(request):
# ...
year = 2006
# ...
return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
Если по каким-либо причинам необходимо будет изменить URL, достаточно будет изменить запись в вашем URLconf.
В некоторых случаях URL-ы и представления могут соотноситься как многое-к-одному. В таких случаях название представления не может идентифицировать конкретный URL. Как решить эту проблему читайте в следующем разделе.
Для того, чтобы выполнить обратное разрешение URL, вам потребуется использовать именованные URL шаблоны, как это показано в примерах выше. Строка, использованная для наименования URL, может содержать любые символы. Вы не ограничены только теми именами, что позволяет Python.
При выборе названия для URL-шаблона, убедитесь что оно достаточно уникально. Если вы назовете URL-шаблон comment, и другое приложение сделает аналогичное, нет гарантии что в шаблон будет вставлен правильный URL.
Добавление префикса к названию URL-шаблона, возможного состоящего из названия приложения, уменьшит шанс конфликта. Мы советуем использовать myapp-comment вместо comment.
Пространства имен позволяют получить URL по названию URL-шаблона даже, если несколько приложений используют одинаковые названия. Для сторонних приложений использование пространств имен – хорошая практика (как мы и делали в учебнике). Аналогично можно получить URL, если несколько экземпляров одного приложения подключены в конфигурацию URL-ов.
Django приложения, правильно используя пространство имен для URL, могут использоваться в нескольких экземплярах на проекте. Например, django.contrib.admin содержит класс AdminSite, который легко позволяет подключить несколько интерфейсов администратора. Ниже мы опишем как сделать аналогично для нашего приложения опросов, чтобы можно было создать несколько интерфейсов (для авторов и издателей).
Пространство имен состоит из двух частей, каждая из которых это строка:
Указывает название установленного приложения. Все экземпляры одного приложения будет иметь одно название. Например, название приложения администратора Django – admin.
Идентифицирует конкретный экземпляр приложения. Должно быть уникальным для проекта. Однако, название экземпляра может быть равным названию приложения. Оно используется по-умолчанию при создании приложения. Например, пространство имен приложения администратора Django – admin.
Пространство имен определяется с помощью оператора ':'. Например, главная страница интерфейса администратора определяется как 'admin:index'. Мы видим пространство имен 'admin', и название URL-шаблона 'index'.
Пространства имен могут быть вложенными. Название URL-а 'sports:polls:index' означает именованный URL-шаблон с названием 'index' в пространстве имен 'polls', которое было определенно в другом пространстве имен - 'sports'.
Если необходимо найти URL по названию с пространством имен (например, 'polls:index'), Django разбивает название на части и следует такому алгоритму:
Первым делом, Django проверяет название(пространсву имен) приложения (например, polls). Django получает список экземпляров приложения.
Если указан текущий экземпляр приложения, Django найдёт и вернет “URL resolver” для этого экземпляра. Текущее приложение можно указать с помощью аргумента current_app функции reverse().
Шаблонный тег url использует пространство имен представления как текущее приложение в RequestContext. Вы можете переопределить его, указав в атрибуте request.current_app.
В предыдущих версиях Django вам требовалось назначать атрибуту current_app класс Context или RequestContext, который использовался при рендеринге шаблона.
Ранее тег url не использовал пространство имен представления и вам приходилось устанавливать атрибут current_app запроса.
Если текущий экземпляр приложения не найден, Django попытается использовать экземпляр по-умолчанию. Экземпляр по-умолчанию – это экземпляр, у которого instance namespace и application namespace совпадают (в нашем примере это экземпляр polls с названием polls).
Если экземпляр по-умолчанию не найден, Django возьмет последний установленный экземпляр приложения, не обращая внимание на его название.
Если на первом шаге не было найдено приложение по указанному пространству имен, Django попытается найти экземпляр приложения по его названию, используя пространство имен как название экземпляра.
Если пространство имен вложенное, этот процесс будет повторен, пока неопределенным не останется только название представления. URL для названия представления будет искаться среди URL-шаблонов определенных в приложении, найденном через пространство имен.
Разберем небольшой пример. У нас есть два экземпляра приложения polls: один назван 'author-polls', другой - 'publisher-polls'. Предположим, что мы уже изменили код приложения и оно учитывает текущее пространство имен при создании страниц.
from django.conf.urls import include, url
urlpatterns = [
url(r'^author-polls/', include('polls.urls', namespace='author-polls')),
url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls')),
]
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
...
]
Для таких настроек URL-ов возможны следующие варианты поиска URL-а по названию:
Если один из экземпляров указан как текущий - например, мы выполняем шаблон в экземпляре 'author-polls' - поиск URL-а по 'polls:index' вернет URL на главную страницу экземпляра приложения 'author-polls'. То есть мы получим "/author-polls/" для двух, приведенных ниже, примеров.
В методе представления:
reverse('polls:index', current_app=self.request.resolver_match.namespace)
и в шаблоне:
{% url 'polls:index' %}
Если текущий экземпляр приложения не указан - например, мы ищем URL в другом приложении - поиск по 'polls:index' вернет URL для последнего добавленного экземпляра 'polls'. Т.к. у нас не определен экземпляр приложения по умолчанию (с instance namespace равным 'polls'), будет использоваться последний добавленный экземпляр polls. Это будет 'publisher-polls' т.к. он последний в urlpatterns.
Поиск по 'author-polls:index' всегда вернет ссылку на главную страницу экземпляра приложения 'author-polls' (аналогично и для 'publisher-polls').
Если бы у нас был экземпляр приложения по умолчанию – то есть с instance name 'polls' – у нас бы поменялся результат только для тех случаев, где не указан текущий экземпляр (второй пункт в списке выше). В этом случае для 'polls:index' мы бы получили ссылку на главную страницу экземпляра приложения по умолчанию, а не для последнего в urlpatterns.
Название приложения в URLconfs можно определить двумя путями.
Первый – вы можете указать атрибут app_name в модуле URLconf приложения, на том же уровне, что и атрибут urlpatterns. При этом вам необходимо передать сам модуль, или путь к импорту модуля, в функцию include(), а не список urlpatterns.
from django.conf.urls import url
from . import views
app_name = 'polls'
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
...
]
from django.conf.urls import include, url
urlpatterns = [
url(r'^polls/', include('polls.urls')),
]
URL-ы, определенные в polls.urls, содержат название приложения polls.
Второй, вы можете добавить объект, который содержит все необходимые данные. Если добавить через include() список url(), они будет добавлены в глобальное пространство имен. Однако, в include() можно передать 2-элементный кортеж, который содержит:
(<list of url() instances>, <application namespace>)
Например:
from django.conf.urls import include, url
from . import views
polls_patterns = ([
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<pk>\d+)/$', views.DetailView.as_view(), name='detail'),
], 'polls')
url(r'^polls/', include(polls_patterns)),
Этот код добавляет URL-шаблоны, используя указанное название приложения.
Имя конкретного экземпляра приложения можно указать с помощью аргумента namespace для include(). Если он не указан, будет использовать название приложения.
В предыдущих версиях требовалось указать название приложения и экземпляра приложения, передавая параметрами в include(), или подключая кортеж из 3-х элементов вида (<list of url() instances>, <application namespace>, <instance namespace>).
Mar 31, 2016