URL调度器

一个干净优雅的URL方案是高质量Web应用程序中的一个重要细节。 Django可以让你设计网址,但不受框架的限制。

没有.php.cgi需要,当然没有那个0,2097,1-1-1928,00无意义。

请参阅万维网创建者Tim Berners-Lee的酷的URIs不会改变,为什么URL应该是干净的和可用的。

概述¶ T0>

要为应用程序设计URL,您可以非正式地创建一个名为URLconf(URL配置)的Python模块。 这个模块是纯Python代码,并且是URL路径表达式与Python函数(您的视图)之间的映射。

这种映射可以尽可能短或只要需要。 它可以引用其他映射。 而且,因为它是纯Python代码,所以可以动态地构建它。

Django还提供了根据活动语言翻译URL的方法。 有关更多信息,请参阅internationalization documentation

Django如何处理请求

当用户从Django支持的站点请求页面时,系统会遵循这个算法来确定要执行的Python代码:

  1. Django确定要使用的根URLconf模块。 Ordinarily, this is the value of the ROOT_URLCONF setting, but if the incoming HttpRequest object has a urlconf attribute (set by middleware), its value will be used in place of the ROOT_URLCONF setting.
  2. Django加载Python模块并查找变量urlpatterns 这应该是django.urls.path()和/或django.urls.re_path()实例的Python列表。
  3. Django按顺序遍历每个URL模式,并停在与请求的URL匹配的第一个URL模式。
  4. Once one of the URL patterns matches, Django imports and calls the given view, which is a simple Python function (or a class-based view). 该视图通过了以下参数:
    • 一个HttpRequest的实例。
    • 如果匹配的URL模式不返回任何命名组,则来自正则表达式的匹配作为位置参数提供。
    • 关键字参数是由路径表达式匹配的任何指定部件组成的,在可选的kwargs参数中指定的任何参数覆盖到django.urls.path()django.urls.re_path()
  5. 如果没有URL模式匹配,或者在这个过程中的任何一点引发异常,Django调用适当的错误处理视图。 请参阅下面的错误处理

实施例¶ T0>

以下是一个URLconf示例:

from django.urls import path

from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    path('articles/<int:year>/', views.year_archive),
    path('articles/<int:year>/<int:month>/', views.month_archive),
    path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
]

笔记:

  • 要从URL中捕获值,请使用尖括号。
  • 捕获的值可以选择包含一个转换器类型。 例如,使用<int:name>来捕获整数参数。 如果不包含转换器,则匹配除/字符之外的任何字符串。
  • 没有必要添加一个前导斜杠,因为每个URL都有。 例如,它是articles,而不是/articles

示例请求:

  • /articles/2005/03/的请求将与列表中的第三个条目匹配。 Django会调用函数views.month_archive(request, year = 2005, month = 3)
  • /articles/2003/ would match the first pattern in the list, not the second one, because the patterns are tested in order, and the first one is the first test to pass. 随意利用排序来插入这样的特殊情况。 在这里,Django会调用函数views.special_case_2003(request)
  • /articles/2003不符合任何这些模式,因为每个模式都要求URL以斜杠结尾。
  • /articles/2003/03/building-a-django-site/将匹配最终模式。 Django会调用函数views.article_detail(request, year = 2003, month = 3, slug =“building -a-django的位点“) T4> T0>。

路径转换器

以下路径转换器默认可用:

  • str - 匹配任何非空字符串,不包括路径分隔符'/' 如果转换器不包含在表达式中,这是默认值。
  • int - Matches zero or any positive integer. 返回一个int
  • slug - 匹配任何由ASCII字母或数字组成的字符串,加上连字符和下划线字符。 例如,building-your-1st-django-site
  • uuid - 匹配格式化的UUID。 为了防止多个URL映射到同一页面,必须包含破折号,并且字母必须是小写。 例如,075194d3-6885-417e-a8a8-6c931e272f00 返回一个UUID实例。
  • path - 匹配任何非空字符串,包括路径分隔符'/' 这允许您匹配一个完整的URL路径,而不仅仅是一个URL路径的一部分,就像str一样。

注册自定义路径转换器

对于更复杂的匹配要求,您可以定义自己的路径转换器。

一个转换器是一个包括以下内容的类:

  • 一个regex类属性,作为一个字符串。
  • 一个to_python(self, value)方法,它处理将匹配的字符串转换为应该传递给view函数的类型。 如果它不能转换给定的值,它应该引发ValueError
  • 一个to_url(self, value)方法,它处理将Python类型转换为在URL中使用的字符串。

例如:

class FourDigitYearConverter:
    regex = '[0-9]{4}'

    def to_python(self, value):
        return int(value)

    def to_url(self, value):
        return '%04d' % value

在您的URLconf中使用register_converter()注册自定义转换器类:

from django.urls import register_converter, path

from . import converters, views

register_converter(converters.FourDigitYearConverter, 'yyyy')

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    path('articles/<yyyy:year>/', views.year_archive),
    ...
]

使用正则表达式

如果路径和转换器语法不足以定义您的URL模式,您还可以使用正则表达式。 为此,请使用re_path()来代替path()

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

以下是使用正则表达式重写的示例URLconf:

from django.urls import path, re_path

from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    re_path('articles/(?P<year>[0-9]{4})/', views.year_archive),
    re_path('articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/', views.month_archive),
    re_path('articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-_]+)/', views.article_detail),
]

这与前面的例子大致相同,除了:

  • 匹配的确切URL会受到一些限制。 例如,年份10000将不再匹配,因为年份整数限制为四位数字。
  • 无论正则表达式匹配什么类型,每个捕获的参数都以字符串的形式发送到视图。

path()切换到re_path()时,反过来也是如此,请注意视图参数的类型可能会改变,所以您可能需要适应你的观点。

使用未命名的正则表达式组

As well as the named group syntax, e.g. (?P<year>[0-9]{4}), you can also use the shorter unnamed group, e.g. ([0-9]{4}).

这个用法不是特别推荐的,因为它更容易意外地在匹配的意图和视图的参数之间引入错误。

无论哪种情况,建议在给定的正则表达式中只使用一种样式。 当两种样式混合使用时,任何未命名的组都被忽略,只有命名组被传递给视图函数。

嵌套参数

正则表达式允许嵌套参数,Django将解析它们并将它们传递给视图。 当反转时,Django将尝试填充所有外部捕获的参数,忽略任何嵌套的捕获参数。 考虑以下URL模式,可以选择使用页面参数:

from django.urls import re_path

urlpatterns = [
    re_path(r'blog/(page-(\d+)/)?$', blog_articles),                  # bad
    re_path(r'comments/(?:page-(?P<page_number>\d+)/)?$', comments),  # good
]

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

在这种情况下,blog_articles视图需要将最外面的捕获参数颠倒过来,page-2/或者没有参数,而comments或者没有参数或者page_number的值。

嵌套的捕获参数在视图参数和URL之间建立了强大的耦合,如blog_articles所示:视图接收部分URL(page-2/),价值观是有兴趣的。 这种耦合在倒转时更为明显,因为要颠倒视图,我们需要传递一段URL而不是页码。

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

什么URLconf搜索

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

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

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

URLconf不查看请求方法。 In other words, all request methods – POST, GET, HEAD, etc. – will be routed to the same function for the same URL.

指定视图参数的默认值

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

# URLconf
from django.urls import path

from . import views

urlpatterns = [
    path('blog/', views.page),
    path('blog/page<int:num>/', 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()函数将使用它的默认参数为num1 If the second pattern matches, page() will use whatever num value was captured.

性能¶ T0>

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

urlpatterns变量​​的语法

urlpatterns should be a Python list of path() and/or re_path() instances.

错误处理

当Django无法为请求的URL找到匹配项或引发异常时,Django会调用错误处理视图。

用于这些情况的视图由四个变量指定。 他们的默认值应该足够大多数项目,但进一步的自定义可能通过覆盖其默认值。

有关详细信息,请参阅customizing error views上的文档。

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

值必须是可调用的,或者是代表完整的Python导入路径的字符串,该路径应该被调用来处理当前的错误情况。

变量是:

包括其他URLconf

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

例如,以下是Django网站本身的URLconf摘录。 它包括一些其他的URLconf:

from django.urls import include, path

urlpatterns = [
    # ... snip ...
    path('community/', include('aggregator.urls')),
    path('contact/', include('contact.urls')),
    # ... snip ...
]

每当Django遇到include()时,它会截断与该点匹配的URL的任何部分,并将剩余的字符串发送到包含的URLconf以供进一步处理。

另一种可能性是通过使用path()实例的列表来包含额外的URL模式。 例如,考虑这个URLconf:

from django.urls import include, path

from apps.main import views as main_views
from credit import views as credit_views

extra_patterns = [
    path('reports/', credit_views.report),
    path('reports/<int:id>/', credit_views.report),
    path('charge/', credit_views.charge),
]

urlpatterns = [
    path('', main_views.homepage),
    path('help/', include('apps.help.urls')),
    path('credit/', include(extra_patterns)),
]

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

这可以用来消除重复使用单个模式前缀的URLconf中的冗余。 例如,考虑这个URLconf:

from django.urls import path
from . import views

urlpatterns = [
    path('<page_slug>-<page_id>/history/', views.history),
    path('<page_slug>-<page_id>/edit/', views.edit),
    path('<page_slug>-<page_id>/discuss/', views.discuss),
    path('<page_slug>-<page_id>/permissions/', views.permissions),
]

我们可以通过只声明一次公共路径前缀并对不同的后缀进行分组来改进:

from django.urls import include, path
from . import views

urlpatterns = [
    path('<page_slug>-<page_id>/', include([
        path('history/', views.history),
        path('edit/', views.edit),
        path('discuss/', views.discuss),
        path('permissions/', views.permissions),
    ])),
]

捕获的参数

包含的URLconf从父URLconf接收任何捕获的参数,所以下面的例子是有效的:

# In settings/urls/main.py
from django.urls import include, path

urlpatterns = [
    path('<username>/blog/', include('foo.urls.blog')),
]

# In foo/urls/blog.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.blog.index),
    path('archive/', views.blog.archive),
]

在上面的例子中,如预期的那样,捕获的"username"变量​​被传递给包含的URLconf。

传递额外的选项来查看函数

URLconf有一个钩子,可以让你传递额外的参数给你的视图函数,如Python字典。

path()函数可以接受一个可选的第三个参数,它应该是一个额外的关键字参数的字典传递给视图函数。

例如:

from django.urls import path
from . import views

urlpatterns = [
    path('blog/<int:year>/', views.year_archive, {'foo': 'bar'}),
]

在这个例子中,对于/blog/2005/的请求,Django会调用views.year_archive(request, year = 2005, t4 > foo ='bar')

syndication framework中使用这种技术将元数据和选项传递给视图。

处理冲突

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

将额外选项传递给include()

同样的,你可以传递额外的选项给include(),每个包含URLconf的行都会被传递给额外的选项。

例如,这两个URLconf集在功能上是相同的:

设置一个:

# main.py
from django.urls import include, path

urlpatterns = [
    path('blog/', include('inner'), {'blog_id': 3}),
]

# inner.py
from django.urls import path
from mysite import views

urlpatterns = [
    path('archive/', views.archive),
    path('about/', views.about),
]

设置二:

# main.py
from django.urls import include, path
from mysite import views

urlpatterns = [
    path('blog/', include('inner')),
]

# inner.py
from django.urls import path

urlpatterns = [
    path('archive/', views.archive, {'blog_id': 3}),
    path('about/', views.about, {'blog_id': 3}),
]

请注意,无论该行的视图是否实际接受这些选项都是有效的,无论该行的视图是否将总是都传递给包含的URLconf中的每一行​​。 出于这个原因,只有在确定包含的URLconf中的每个视图都接受了传递的额外选项时,该技术才有用。

反向解析URL

在Django项目上工作的一个共同需求是可以获得最终形式的URL,或者嵌入到生成的内容(视图和资产URL,显示给用户的URL等)中,或者用于处理服务器上的导航流方(重定向等)

强烈希望避免对这些URL进行硬编码(一种费力,不可扩展且容易出错的策略)。 同样危险的是设计特别的机制来生成与URLconf描述的设计并行的URL,这可能导致产生随着时间的推移而变旧的URL。

换句话说,需要的是一个干燥机制。 除了其他优势之外,它还允许进行URL设计,而无需查看所有项目源代码来搜索和替换过时的URL。

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

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

  • 从用户/浏览器请求的URL开始,它调用正确的Django视图,提供它可能需要的任何参数以及从URL中提取的值。
  • 从相应的Django视图的标识以及传递给它的参数的值开始,获取关联的URL。

第一个是我们前面讨论的用法。 第二个是所谓的反向解析URL反向URL匹配反向URL查找,或者简单地URL反向。

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

  • 在模板中:使用url模板标签。
  • 在Python代码中:使用reverse()函数。
  • 在与处理Django模型实例的URL相关的更高级代码中:get_absolute_url()方法。

实例¶ T0>

再考虑一下这个URLconf条目:

from django.urls import path

from . import views

urlpatterns = [
    #...
    path('articles/<int:year>/', 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和视图之间可能存在多对一的关系。 对于这些情况,视图名称不是一个足够好的标识符。 阅读下一节了解Django为此提供的解决方案。

命名URL模式

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

在命名URL模式时,请选择不太可能与其他应用程序的名称选择冲突的名称。 如果你调用你的URL模式comment而另一个应用程序做同样的事情,reverse()找到的URL取决于哪个模式是你项目的最后一个urlpatterns

在您的URL名称上加上一个前缀,可能是从应用程序名称(如myapp-comment而不是comment)派生的,从而降低了碰撞的可能性。

如果您想覆盖视图,您可以有意地选择相同的URL名称作为另一个应用程序。 例如,一个常见的用例就是覆盖LoginView 部分Django和大多数第三方应用程序假定这个视图有一个名为login的URL模式。 If you have a custom login view and give its URL the name login, reverse() will find your custom view as long as it’s in urlpatterns after django.contrib.auth.urls is included (if that’s included at all).

如果参数不同,也可以为多个URL模式使用相同的名称。 除了URL名称之外,reverse()匹配参数的数量和关键字参数的名称。

URL名称空间

引言¶ T0>

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

正确使用URL命名空间的Django应用程序可以为特定的站点多次部署。 For example django.contrib.admin has an AdminSite class which allows you to easily deploy more than one instance of the admin. 在后面的例子中,我们将讨论在两个不同的位置部署投票应用程序的想法,所以我们可以为两个不同的受众(作者和发行者)提供相同的功能。

一个URL命名空间有两部分,都是字符串:

application namespace
这描述了正在部署的应用程序的名称。 每个应用程序的每个实例将具有相同的应用程序名称空间。 例如,Django的管理应用程序有一些可预测的应用程序名称空间'admin'
instance namespace
这标识了应用程序的特定实例。 实例名称空间在整个项目中应该是唯一的。 但是,实例名称空间可以与应用程序名称空间相同。 这用于指定应用程序的默认实例。 例如,默认的Django管理实例有一个名为'admin'的实例名称空间。

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

名字空间也可以嵌套。 命名的URL 'sports:polls:index'会在命名空间'polls'中寻找名为'index'在顶级命名空间'sports'

反转名称空间的URL

当给定名称空间URL(例如'polls:index')来解决时,Django将完全限定的名字分割成部分,然后尝试以下查找:

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

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

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

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

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

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

如果存在嵌套命名空间,则对命名空间的每个部分重复这些步骤,直到只有视图名称未解析。 视图名称将被解析成已找到的命名空间中的URL。

实施例¶ T0>

为了显示这个解析策略的实际效果,请考虑一个来自本教程的polls应用程序的两个实例:一个名为'author-polls'和一个名为'publisher-polls' 假设我们已经增强了该应用程序,以便在创建和显示民意测验时考虑实例名称空间。

urls.py
from django.urls import include, path

urlpatterns = [
    path('author-polls/', include('polls.urls', namespace='author-polls')),
    path('publisher-polls/', include('polls.urls', namespace='publisher-polls')),
]
民调/ urls.py
from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    ...
]

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

  • If one of the instances is current - say, if we were rendering the detail page in the instance 'author-polls' - 'polls:index' will resolve to the index page of the 'author-polls' instance; i.e. both of the following will result in "/author-polls/".

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

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

    并在模板中:

    {% url 'polls:index' %}
    
  • If there is no current instance - say, if we were rendering a page somewhere else on the site - 'polls:index' will resolve to the last registered instance of polls. 由于没有默认实例('polls'),因此将使用已注册的polls的最后一个实例。 这将是'publisher-polls',因为它是在urlpatterns中声明的最后一个。

  • 'author-polls:index'将始终解析为实例'author-polls'的索引页(对于'publisher-polls'

如果还有一个默认的实例,即一个名为'polls'的实例,那么上面唯一的变化就是在没有当前实例的情况下(上面的第二个项目)。 In this case 'polls:index' would resolve to the index page of the default instance instead of the instance declared last in urlpatterns.

URL名称空间和包含的URLconf

包含的URLconf的应用程序名称空间可以用两种方式指定。

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

民调/ urls.py
from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    ...
]
urls.py
from django.urls import include, path

urlpatterns = [
    path('polls/', include('polls.urls')),
]

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

其次,你可以包含一个包含嵌入式命名空间数据的对象。 If you include() a list of path() or re_path() instances, the URLs contained in that object will be added to the global namespace. However, you can also include() a 2-tuple containing:

(<list of path()/re_path() instances>, <application namespace>)

例如:

from django.urls import include, path

from . import views

polls_patterns = ([
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
], 'polls')

urlpatterns = [
    path('polls/', include(polls_patterns)),
]

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

The instance namespace can be specified using the namespace argument to include(). 如果未指定实例名称空间,则默认为包含的URLconf的应用程序名称空间。 这意味着它也将是该名称空间的默认实例。