Менеджер URL-ов

Чистая, элегантная схема 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-сайта, используется такой алгоритм для определения какой код выполнить:

  1. Django определяет какой корневой модуль URLconf использовать. Обычно, это значение настройки ROOT_URLCONF, но, если объект запроса HttpRequest содержит атрибут urlconf (установленный request middleware), его значение будет использоваться вместо ROOT_URLCONF.

  2. Django загружает модуль конфигурации URL и ищет переменную urlpatterns. Это должен быть список экземпляров django.conf.urls.url().

  3. Django перебирает каждый URL-шаблон по порядку, и останавливается при первом совпадении с запрошенным URL-ом.

  4. Если одно из регулярных выражений соответствует URL-у, Django импортирует и вызывает соответствующее представление, которое является просто функцией Python(или представление-класс). При вызове передаются следующие аргументы:

    • Объект HttpRequest.

    • Если в результате применения регулярного выражения получили именованные совпадения, они будут переданы как позиционные аргументы.

    • Именованные аргументы создаются из именованных совпадений. Они могут быть перезаписаны значениями из аргумента kwargs, переданного в django.conf.urls.url().

  5. Если ни одно регулярное выражение не соответствует, или возникла ошибка на любом из этапов, 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/ соответствует первому выражению, не второму, потому что шаблоны проверяются по порядку. Не стесняйтесь использовать порядок для обработки различных ситуаций, таких как эта.

  • /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, для определения использовать значения именованных или не именованных совпадений:

  1. Если существует именованный аргумент, он будет использован вместо позиционного аргумента.

  2. Иначе все неименованные параметры будут переданы как позиционные аргументы.

В любом случае дополнительные именованные аргументы будут переданы как именованные. Смотрите `Passing extra options to view functions`_ ниже.

Что использует URLconf при поиске нужного шаблона URL

URLconf использует запрашиваемый URL как обычную строку Python. Он не учитывает параметры GET, POST и имя домена.

Например, при запросе к http://www.example.com/myapp/, URLconf возьмет myapp/.

При запросе к http://www.example.com/myapp/?page=3myapp/.

URLconf не учитывает тип запроса. Другими словами, все типы запросов – POST, GET, HEAD, и др. – будут обработаны одним представлением при одинаковом URL.

Найденные аргументы – всегда строки

Каждый найденный аргумент передается в представление как строка, независимо от того, какое “совпадение” определено в регулярном выражении. Например, URLconf содержит такую строку:

url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),

...аргумент year для 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 будет скомпилировано при первом использовании. Это делает систему невероятно быстрой.

Синтаксис переменной urlpatterns

urlpatterns должен быть списком экземпляров url().

Обработчики ошибок

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

Эти представления определены в четырёх переменных. Их значения по-умолчанию должны подойти для большинства проектов, но вы можете их поменять при необходимости.

Подробности в разделе о переопределении обработчика ошибок.

Эти значения должны быть определены в главном URLconf.

Значение это функции, или полный путь для импорта, которая будет вызвана, если не был найден подходящий URL-шаблон.

Есть следующие переменные:

Комбинирование URLconfs

В любой момент, ваш 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/(?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),
    ])),
]

Нахождение аргументов в URL

Включенный 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()

Аналогично вы можете передать дополнительные аргументы в 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-а по его определению для отображения пользователю или для редиректа.

Очень важно не “хардкодить” URL-ы (трудоемкая и плохо поддерживаемая стратегия). Иногда необходимо менять структуру URL-ов, при этом важно не оставить где-то URL в старом формате.

В общем необходимо придерживаться принципа 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 шаблоны, как это показано в примерах выше. Строка, использованная для наименования URL, может содержать любые символы. Вы не ограничены только теми именами, что позволяет Python.

При выборе названия для URL-шаблона, убедитесь что оно достаточно уникально. Если вы назовете URL-шаблон comment, и другое приложение сделает аналогичное, нет гарантии что в шаблон будет вставлен правильный URL.

Добавление префикса к названию URL-шаблона, возможного состоящего из названия приложения, уменьшит шанс конфликта. Мы советуем использовать myapp-comment вместо comment.

Пространства имен в конфигурации URL-ов

Описание

Пространства имен позволяют получить URL по названию URL-шаблона даже, если несколько приложений используют одинаковые названия. Для сторонних приложений использование пространств имен – хорошая практика (как мы и делали в учебнике). Аналогично можно получить URL, если несколько экземпляров одного приложения подключены в конфигурацию URL-ов.

Django приложения, правильно используя пространство имен для URL, могут использоваться в нескольких экземплярах на проекте. Например, django.contrib.admin содержит класс AdminSite, который легко позволяет подключить несколько интерфейсов администратора. Ниже мы опишем как сделать аналогично для нашего приложения опросов, чтобы можно было создать несколько интерфейсов (для авторов и издателей).

Пространство имен состоит из двух частей, каждая из которых это строка:

application namespace

Указывает название установленного приложения. Все экземпляры одного приложения будет иметь одно название. Например, название приложения администратора Django – admin.

instance namespace

Идентифицирует конкретный экземпляр приложения. Должно быть уникальным для проекта. Однако, название экземпляра может быть равным названию приложения. Оно используется по-умолчанию при создании приложения. Например, пространство имен приложения администратора Django – admin.

Пространство имен определяется с помощью оператора ':'. Например, главная страница интерфейса администратора определяется как 'admin:index'. Мы видим пространство имен 'admin', и название URL-шаблона 'index'.

Пространства имен могут быть вложенными. Название URL-а 'sports:polls:index' означает именованный URL-шаблон с названием 'index' в пространстве имен 'polls', которое было определенно в другом пространстве имен - 'sports'.

Поиск URL-а по шаблону с пространством имен

Если необходимо найти URL по названию с пространством имен (например, 'polls:index'), Django разбивает название на части и следует такому алгоритму:

  1. Первым делом, Django проверяет название(пространсву имен) приложения (например, polls). Django получает список экземпляров приложения.

  2. Если указан текущий экземпляр приложения, Django найдёт и вернет “URL resolver” для этого экземпляра. Текущий экземпляр приложения можно указать в качестве атрибута запроса. Приложения, которые должны иметь несколько установок должны определять атрибут current_app для request при обработке.

    Изменено в Django 1.8:

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

    Текущей экземпляр приложения можно также определить используя аргумент при вызове функции reverse().

  3. Если текущий экземпляр приложения не найден, Django попытается использовать экземпляр по-умолчанию. Экземпляр по-умолчанию – это экземпляр, у которого instance namespace и application namespace совпадают (в нашем примере это экземпляр polls с названием polls).

  4. Если экземпляр по-умолчанию не найден, Django возьмет последний установленный экземпляр приложения, не обращая внимание на его название.

  5. Если на первом шаге не было найдено приложение по указанному пространству имен, Django попытается найти экземпляр приложения по его названию, используя пространство имен как название экземпляра.

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

Например

Разберем небольшой пример. У нас есть два экземпляра приложения polls: один назван 'author-polls', другой - 'publisher-polls'. Предположим, что мы уже изменили код приложения и оно учитывает текущее пространство имен при создании страниц.

urls.py
from django.conf.urls import include, url

urlpatterns = [
    url(r'^author-polls/', include('polls.urls', namespace='author-polls', app_name='polls')),
    url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls', app_name='polls')),
]
polls/urls.py
from django.conf.urls import url

from . import views

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-а в шаблоне требует, чтобы мы определили current_app для request следующим образом:

    def render_to_response(self, context, **response_kwargs):
        self.request.current_app = self.request.resolver_match.namespace
        return super(DetailView, self).render_to_response(context, **response_kwargs)
    
  • Если текущий экземпляр приложения не указан - например, мы ищем 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.

Пространства имен в URL -ах и include

Пространство имен URL можно определить двумя путями.

Первый, передав названия приложения и экземпляра аргументами в include(). Например:

url(r'^polls/', include('polls.urls', namespace='author-polls', app_name='polls')),

Этот код добавляет URL-шаблоны определенные в polls.urls, используя application namespace 'polls' и instance namespace 'author-polls'.

Второй, вы можете добавить объект, который содержит все необходимые данные. Если добавить через include() список url(), они будет добавлены в глобальное пространство имен. Однако, в include() можно передать 3-элементный кортеж, который содержит:

(<list of url() instances>, <application namespace>, <instance 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'),
]

url(r'^polls/', include((polls_patterns, 'polls', 'author-polls'))),

Этот код добавляет URL-шаблоны, используя название приложения bar и название экземпляра foo.

Например, для подключения интерфейса администратора используется экземпляр AdminSite, каждый из которых содержит свойство urls: 3-х элементный кортеж, который содержит все URL-шаблоны, название приложения 'admin' и название экземпляра приложения. Это то свойство, которое вы добавляете, используя include(), в urlpatterns вашего проекта при установке приложения администратора.

Убедитесь, что вы передаете кортеж в include(). Если вы просто передадите три аргумента include(polls_patterns, 'polls', 'author-polls'), Django не вызовет исключения из-за сигнатуры include(), но 'polls' будет названием экземпляра, а 'author-polls' – названием приложения, а не наоборот.