Skip to main content

网址调度员

一个干净,优雅的URL方案是高质量Web应用程序中的一个重要细节。 Django让你设计你想要的URL,没有框架的限制。

没有 .php.cgi 需要,当然没有一个 0,2097,1-1-1928,00 废话。

请参阅 Cool URIs don’t change,由万维网创作者Tim Berners-Lee,关于为什么URL应该干净和可用的优秀论点。

概述

要设计应用程序的URL,您可以非正式地创建一个称为 URLconf (URL配置)的Python模块。这个模块是纯Python代码,是一个简单的URL模式(简单正则表达式)到Python函数(你的视图)之间的映射。

该映射可以短或者根据需要长。它可以引用其他映射。并且,因为它是纯Python代码,它可以动态构造。

Django还提供了一种根据活动语言翻译URL的方法。有关详细信息,请参阅 国际化文档

Django如何处理请求

当用户从您的Django供电站点请求页面时,系统遵循的算法是确定要执行哪个Python代码:

  1. Django确定要使用的根URLconf模块。通常,这是 ROOT_URLCONF 设置的值,但如果传入的 HttpRequest 对象具有 urlconf 属性(由中间件设置),则其值将用于代替 ROOT_URLCONF 设置。

  2. Django加载Python模块并查找变量 urlpatterns。这应该是 django.conf.urls.url() 实例的Python列表。

  3. Django按顺序运行每个URL模式,并停止在与请求的URL匹配的第一个模式。

  4. 一旦一个正则表达式匹配,Django导入并调用给定视图,这是一个简单的Python函数(或 基于类的视图)。视图将传递以下参数:

    • HttpRequest 的实例。

    • 如果匹配的正则表达式没有返回任何命名组,则来自正则表达式的匹配项将作为位置参数提供。

    • 关键字参数由与正则表达式匹配的任何命名组组成,由在 django.conf.urls.url() 的可选 kwargs 参数中指定的任何参数覆盖。

  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/ 将不匹配任何网址格式,因为列表中的第三个条目需要该月的两位数字。

  • /articles/2003/ 将匹配列表中的第一个模式,而不匹配第二个模式,因为模式按顺序测试,第一个是第一个测试通过。随意利用排序插入这种特殊情况。这里,Django会调用函数 views.special_case_2003(request)

  • /articles/2003 将不匹配任何这些模式,因为每个模式都要求URL以斜杠结尾。

  • /articles/2003/03/03/ 将匹配最终模式。 Django会调用函数 views.article_detail(request, '2003', '03', '03')

命名组

上面的例子使用简单的 non-named 正则表达式组(通过括号)来捕获URL的位,并将它们作为 positional 参数传递给视图。在更高级的用法中,可以使用 named 正则表达式组来捕获URL位,并将它们作为 keyword 参数传递给视图。

在Python正则表达式中,命名的正则表达式组的语法是 (?P<name>pattern),其中 name 是组的名称,pattern 是要匹配的模式。

下面是上面的示例URLconf,重写为使用命名组:

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')

实际上,这意味着您的URLconfs稍微更加明确,并且不太容易出现参数顺序错误 - 并且您可以重新排序视图的函数定义中的参数。当然,这些好处是以简洁为代价的;一些开发人员发现命名组语法丑陋和太详细。

匹配/分组算法

这里是URLconf解析器遵循的算法,关于正则表达式中的命名组与非命名组:

  1. 如果有任何命名参数,它将使用那些,忽略未命名的参数。

  2. 否则,它将所有未命名的参数作为位置参数传递。

在这两种情况下,根据 Passing extra options to view functions (下面)给出的任何额外的关键字参数也将被传递给视图。

URLconf搜索什么

URLconf作为正常的Python字符串搜索请求的URL。这不包括GET或POST参数或域名。

例如,在对 https://www.example.com/myapp/ 的请求中,URLconf将查找 myapp/

https://www.example.com/myapp/?page=3 的请求中,URLconf将查找 myapp/

URLconf不查看请求方法。换句话说,所有请求方法(POSTGETHEAD 等)将被路由到相同URL的相同函数。

捕获的参数总是字符串

每个捕获的参数作为纯Python字符串发送到视图,不管正则表达式匹配什么类型。例如,在这个URLconf行:

url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
...传递给 views.year_archive()year 参数将是一个字符串,

不是一个整数,即使 [0-9]{4} 只匹配整数字符串。

为视图参数指定默认值

一个方便的技巧是为视图的参数指定默认参数。这里有一个URLconf和view的例子:

# 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.
    ...

在上面的示例中,两个网址模式都指向同一个视图 - views.page - 但第一个模式不会从网址捕获任何内容。如果第一个模式匹配,page() 函数将使用其默认参数 num"1"。如果第二个模式匹配,page() 将使用正则表达式捕获的任何 num 值。

性能

urlpatterns 中的每个正则表达式在第一次访问时被编译。这使得系统速度飞快。

urlpatterns 变量的语法

urlpatterns 应该是 url() 实例的Python列表。

错误处理

当Django找不到与请求的URL匹配的正则表达式时,或者当引发异常时,Django将调用错误处理视图。

用于这些情况的视图由四个变量指定。它们的默认值应该足以满足大多数项目,但是可以通过覆盖它们的默认值来进一步定制。

有关详细信息,请参阅 自定义错误视图 上的文档。

这些值可以在根URLconf中设置。在任何其他URLconf中设置这些变量将不起作用。

值必须是可调用项,或表示应调用的视图的完整Python导入路径的字符串,以处理手头的错误条件。

变量是:

包括其他URLconfs

在任何时候,您的 urlpatterns 可以“包括”其他URLconf模块。这本质上“根”一组URL低于其他。

例如,下面是 Django website 本身的URLconf的摘录。它包括许多其他URLconfs:

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模式。例如,考虑这个URLconf:

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)),
]

在此示例中,/credit/reports/ URL将由 credit_views.report() Django视图处理。

这可用于从URLconfs中删除冗余,其中重复使用单个模式前缀。例如,考虑这个URLconf:

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
]

这两个模式使用嵌套参数并将解析:例如,blog/page-2/ 将导致与 blog_articles 的匹配,具有两个位置参数:page-2/2comments 的第二个模式将匹配 comments/page-2/,关键字参数 page_number 设置为2.这种情况下的外部参数是非捕获参数 (?:...)

在这种情况下,blog_articles 视图需要反转最外面捕获的参数,page-2/ 或无参数,而 comments 可以与无参数或 page_number 的值相反。

嵌套捕获的参数创建视图参数和URL之间的强耦合,如 blog_articles 所示:视图接收URL(page-2/)的一部分,而不是仅视图感兴趣的值。这种耦合在反转时更加明显,因为要反转视图,我们需要传递一段URL而不是页面编号。

作为经验法则,只捕获视图需要使用的值,并在正则表达式需要参数但是视图忽略它时使用非捕获参数。

传递额外选项以查看函数

URLconfs有一个钩子,让你传递额外的参数到你的视图函数,作为一个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')

此技术在 联合框架 中用于将元数据和选项传递到视图。

处理冲突

它可以有一个URL模式捕获命名的关键字参数,并传递参数在其额外的参数的字典相同的名称。当这种情况发生时,将使用字典中的参数,而不是URL中捕获的参数。

include() 传递额外选项

同样,您可以向 include() 传递额外的选项。当你向 include() 传递额外的选项时,所包含的URLconf中的 each 行将被传递额外的选项。

例如,这两个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}),
]

注意,额外的选项将 always 传递到包含的URLconf中的 every 行,而不管行的视图是否实际接受那些选项为有效。为此,这种技术只有在你确定包含的URLconf中的每个视图都接受你传递的额外选项时才有用。

URL的反向解析

在Django项目上工作时的常见需求是获得最终形式的URL的可能性,用于嵌入生成的内容(视图和资产URL,向用户显示的URL等)或处理服务器上的导航流侧(重定向等)

强烈希望避免硬编码这些URL(繁琐,不可扩展和容易出错的策略)。同样危险的是设计ad-hoc机制来生成与URLconf描述的设计并行的URL,这可能导致生成随时间变得过时的URL。

换句话说,需要的是DRY机制。除了其他优点,它将允许URL设计的演变,而不必经过所有的项目源代码来搜索和替换过时的URL。

我们可用于获取URL的主要信息是负责处理它的视图的标识(例如,名称)。必须参与正确URL的查找的其他信息片段是视图参数的类型(位置,关键字)和值。

Django提供了一个解决方案,使得URL映射器是URL设计的唯一存储库。您用您的URLconf提供它,然后它可以在两个方向使用:

  • 从用户/浏览器请求的URL开始,它调用正确的Django视图,提供它可能需要的参数以及从URL中提取的值。

  • 从识别对应的Django视图加上将传递给它的参数的值开始,获取相关的URL。

第一个是我们在前面的章节中讨论的用法。第二个是所谓的 反向解析URL反向URL匹配反向URL查找 或简称 网址反转

Django提供了用于执行网址反转的工具,以匹配需要网址的不同图层:

  • 在模板中:使用 url 模板标记。

  • 在Python代码中:使用 reverse() 函数。

  • 在与处理Django模型实例的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.urls 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的时间。阅读下一节,了解Django为此提供的解决方案。

命名网址格式

为了执行网址颠倒,您需要使用 命名的网址格式,如上面的示例中所做的。用于URL名称的字符串可以包含任何您喜欢的字符。您不限于有效的Python名称。

当您为网址格式命名时,请确保使用不可能与任何其他应用程序选择的名称冲突的名称。如果您调用您的网址格式 comment,而另一个应用程序做同样的事情,则不能保证在您使用此名称时,哪个网址会插入您的模板。

在您的URL名称上添加前缀(可能源自应用程序名称)会降低冲突的可能性。我们推荐类似 myapp-comment 而不是 comment

URL名称空间

介绍

URL名称空间允许您唯一地反转 命名的网址格式,即使不同的应用程序使用相同的URL名称。第三方应用程序始终使用命名空间网址(如我们在教程中所做的)是一个很好的做法。同样,如果部署了应用程序的多个实例,也可以反向URL。换句话说,由于单个应用程序的多个实例将共享命名的URL,命名空间提供了一种方法来区分这些命名的URL。

正确使用URL命名空间的Django应用程序可以针对特定站点多次部署。例如 django.contrib.admin 有一个 AdminSite 类,它允许你轻松地 部署多个管理员实例。在后面的示例中,我们将讨论在两个不同位置部署来自教程的polls应用程序的想法,以便我们可以向两个不同的受众(作者和发布者)提供相同的功能。

URL命名空间分为两部分,两者都是字符串:

应用程序命名空间

它描述了正在部署的应用程序的名称。单个应用程序的每个实例都将具有相同的应用程序命名空间。例如,Django的管理应用程序具有可预测的 'admin' 的应用程序命名空间。

实例命名空间

这标识应用程序的特定实例。实例命名空间在整个项目中应该是唯一的。但是,实例命名空间可以与应用程序命名空间相同。这用于指定应用程序的默认实例。例如,默认的Django admin实例具有 'admin' 的实例命名空间。

使用 ':' 运算符指定命名空间URL。例如,使用 'admin:index' 引用管理应用程序的主索引页。这表示 'admin' 的命名空间,以及 'index' 的命名URL。

命名空间也可以嵌套。命名URL 'sports:polls:index' 将在命名空间 'polls' 中查找名为 'index' 的模式,该模式本身在顶级命名空间 'sports' 中定义。

颠倒命名空间网址

当给定要解析的命名空间URL(例如 'polls:index')时,Django将完全限定名称拆分为多个部分,然后尝试以下查找:

  1. 首先,Django寻找一个匹配的 application namespace (在这个例子中,'polls')。这将产生该应用程序的实例的列表。

  2. 如果定义了当前应用程序,Django会查找并返回该实例的URL解析器。可以使用 reverse() 函数的 current_app 参数指定当前应用程序。

    url 模板标签使用当前解析的视图的命名空间作为 RequestContext 中的当前应用程序。您可以通过在 request.current_app 属性上设置当前应用程序来覆盖此默认值。

    Changed in Django 1.9:

    以前,url 模板标记没有使用当前解析的视图的命名空间,您必须在请求上设置 current_app 属性。

  3. 如果没有当前应用程序。 Django寻找一个默认的应用程序实例。默认应用程序实例是具有与 application namespace 匹配的 instance namespace 的实例(在该示例中,称为 'polls'polls 的实例)。

  4. 如果没有默认应用程序实例,Django将选择应用程序的最后部署的实例,而不管其实例名称是什么。

  5. 如果在步骤1中提供的命名空间与 application namespace 不匹配,Django将尝试直接查找命名空间作为 instance namespace

如果有嵌套命名空间,则对命名空间的每个部分重复这些步骤,直到只有视图名称未解析。然后,视图名称将解析为已找到的命名空间中的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')),
    url(r'^publisher-polls/', include('polls.urls', namespace='publisher-polls')),
]
polls/urls.py
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'),
    ...
]

使用此设置,以下查找是可能的:

  • 如果其中一个实例是当前的 - 例如,如果我们在实例 'author-polls' - 'polls:index' 中呈现详细信息页面将解析到 'author-polls' 实例的索引页面;即以下两者将导致 "/author-polls/"

    在基于类的视图的方法中:

    reverse('polls:index', current_app=self.request.resolver_match.namespace)
    

    并在模板中:

    {% url 'polls:index' %}
    
  • 如果没有当前实例 - 例如,如果我们在网站的其他地方渲染一个页面 - 'polls:index' 将解析到最后一个注册的 polls 实例。由于没有默认实例('polls' 的实例命名空间),将使用注册的 polls 的最后一个实例。这将是 'publisher-polls',因为它是最后一次在 urlpatterns 中声明。

  • 'author-polls:index' 将始终解析到实例 'author-polls' 的索引页(同样对于 'publisher-polls')。

如果还有一个默认实例(即一个名为 'polls' 的实例),那么只有在没有当前实例(上面列表中的第二项)的情况下,上面的改变才会发生。在这种情况下,'polls:index' 将解析到默认实例的索引页面,而不是 urlpatterns 中最后声明的实例。

URL名称空间和包括的URLconfs

包含的URLconfs的应用程序命名空间可以通过两种方式指定。

首先,您可以在包含的URLconf模块中设置 app_name 属性,该属性与 urlpatterns 属性的级别相同。您必须将实际模块或对模块的字符串引用传递给 include(),而不是 urlpatterns 本身的列表。

polls/urls.py
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'),
    ...
]
urls.py
from django.conf.urls import include, url

urlpatterns = [
    url(r'^polls/', include('polls.urls')),
]

polls.urls 中定义的URL将具有应用程序命名空间 polls

其次,您可以包含一个包含嵌入命名空间数据的对象。如果 include() 列出了 url() 实例,则该对象中包含的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')

urlpatterns = [
    url(r'^polls/', include(polls_patterns)),
]

这将包括指定的URL模式到给定的应用程序命名空间。

可以使用 include()namespace 参数指定实例命名空间。如果未指定实例命名空间,则它将默认为包含的URLconf的应用程序命名空间。这意味着它也将是该命名空间的默认实例。

Changed in Django 1.9:

在以前的版本中,您必须在一个地方同时指定应用程序命名空间和实例命名空间,方法是将它们作为参数传递给 include(),或者通过包含一个包含 (<list of url() instances>, <application namespace>, <instance namespace>) 的3元组。