URL调度器

一个干净、优雅的URL方案是高质量Web应用程序的重要细节。Django允许您随心所欲地设计URL,没有框架限制。

Cool URIs don't change ,作者是万维网创建者TimBerners-Lee,他对为什么URL应该是干净的和可用的进行了很好的论证。

概述

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

这个映射可以是短的,也可以是长的。它可以引用其他映射。而且,因为它是纯Python代码,所以可以动态构造它。

Django还提供了一种根据活动语言翻译URL的方法。见 internationalization documentation 更多信息。

Django如何处理请求

当用户从Django支持的站点请求页面时,系统将遵循以下算法来确定要执行的python代码:

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

  2. Django加载该python模块并查找变量 urlpatterns . 这应该是一个 sequence 属于 django.urls.path() 和/或 django.urls.re_path() 实例。

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

  4. 一旦其中一个URL模式匹配,Django导入并调用给定的视图,该视图是一个Python函数(或 class-based view )该视图将传递以下参数:

    • 的实例 HttpRequest .

    • 如果匹配的URL模式不包含命名组,则正则表达式中的匹配项将作为位置参数提供。

    • 关键字参数由与所提供的路径表达式匹配的任何命名部分组成,并由可选的 kwargs 参数 django.urls.path()django.urls.re_path() .

  5. 如果没有匹配的URL模式,或者在此过程中的任何一点期间引发异常,Django将调用适当的错误处理视图。见 Error handling 下面。

例子

以下是一个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. Feel free to exploit the ordering to insert special cases like this. Here, Django would call the function 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-site") .

路径转换器

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

  • str -匹配任何非空字符串,路径分隔符除外, '/' . 如果表达式中不包含转换器,则这是默认值。

  • int -匹配零或任何正整数。返回一个 int .

  • slug -匹配由ASCII字母或数字以及连字符和下划线字符组成的任何段塞字符串。例如, building-your-1st-django-site .

  • uuid -匹配格式化的UUID。为了防止多个URL映射到同一个页面,必须包括短划线和小写字母。例如, 075194d3-6885-417e-a8a8-6c931e272f00 . 返回A UUID 实例。

  • path -匹配任何非空字符串,包括路径分隔符, '/' . 这允许您匹配一个完整的URL路径,而不是像 str .

注册自定义路径转换器

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

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

  • A regex 类属性,作为字符串。

  • A to_python(self, value) 方法,它处理将匹配的字符串转换为应传递给View函数的类型。应该提高 ValueError 如果它不能转换给定的值。A ValueError 被解释为不匹配,因此除非另一个URL模式匹配,否则将向用户发送404响应。

  • A to_url(self, value) 方法,该方法处理将Python类型转换为要在URL中使用的字符串。它应该升高 ValueError 如果它不能转换给定的值。A ValueError 被解释为不匹配,因此 reverse() 将提高 NoReverseMatch 除非另一个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 path, register_converter

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(r"^articles/(?P<year>[0-9]{4})/$", views.year_archive),
    re_path(r"^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$", views.month_archive),
    re_path(
        r"^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$",
        views.article_detail,
    ),
]

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

  • 与之匹配的确切URL受到了稍微多一些的约束。例如,年份10000将不再匹配,因为年份整数的长度被限制为正好四位数。

  • 每个捕获的参数都作为字符串发送到视图中,而不管正则表达式的匹配类型如何。

从使用切换时 path()re_path() 反之亦然,特别重要的是要注意视图参数的类型可能会改变,因此您可能需要调整视图。

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

以及命名的组语法,例如 (?P<year>[0-9]{{4}}) ,也可以使用较短的未命名组,例如 ([0-9]{{4}}) .

建议不要特别使用此用法,因为这样更容易在匹配的预期含义和视图参数之间意外引入错误。

在这两种情况下,建议在给定的regex中只使用一种样式。两种样式混合后,将忽略所有未命名的组,并且只将命名的组传递给视图函数。

嵌套参数

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

from django.urls import re_path

urlpatterns = [
    re_path(r"^blog/(page-([0-9]+)/)?$", blog_articles),  # bad
    re_path(r"^comments/(?:page-(?P<page_number>[0-9]+)/)?$", 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不查看请求方法。换句话说,所有请求方法-- POSTGETHEAD 等等--将被路由到同一个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 . 如果第二种模式匹配, page() 会用任何东西 num 已捕获值。

性能

Django处理 urlpatterns 第一次访问时编译的列表。后续请求通过URL解析器使用缓存的配置。

语法 urlpatterns 变量

urlpatterns 应该是 sequence 属于 path() 和/或 re_path() 实例。

错误处理

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

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

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

这些值可以在根urlconf中设置。在任何其他urlconf中设置这些变量都不会有任何效果。

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

变量包括:

包括其他URLCONF

在任何时候,你的 urlpatterns 可以“包括”其他URLCONF模块。这基本上是“根”在其他URL下面的一组URL。

例如,下面是 Django website 本身。它包括许多其他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() 实例。例如,考虑这个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视图。

这可用于从重复使用单个模式前缀的urlconfs中删除冗余。例如,考虑这个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。

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

urlconfs有一个钩子,允许您将额外的参数作为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, 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() 方法。

实例

再次考虑这个urlconf条目:

from django.urls import path

from . import views

urlpatterns = [
    # ...
    path("articles/<int:year>/", views.year_archive, name="news-year-archive"),
    # ...
]

根据此设计,对应年份的档案URL nnnn/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.http import HttpResponseRedirect
from django.urls import reverse


def redirect_to_year(request):
    # ...
    year = 2006
    # ...
    return HttpResponseRedirect(reverse("news-year-archive", args=(year,)))

如果出于某种原因,决定更改发布年度文章存档内容的URL,那么您只需要更改urlconf中的条目。

在某些视图具有通用性的场景中,URL和视图之间可能存在多对一关系。在这些情况下,当需要反转URL时,视图名并不是一个足够好的标识符。阅读下一节了解Django为此提供的解决方案。

命名URL模式

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

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

在URL名称上添加前缀,可能是从应用程序名称(例如 myapp-comment 而不是 comment )减少碰撞的可能性。

你可以故意选择 相同的URL名称 如果要重写视图,则作为另一个应用程序。例如,一个常见的用例是重写 LoginView . Django的部分应用程序和大多数第三方应用程序都假定此视图具有名称为的URL模式。 login . 如果您有一个自定义的登录视图,并给出其URL的名称 loginreverse() 只要您的自定义视图在 urlpatterns 之后 django.contrib.auth.urls 包括在内(如果包括在内的话)。

如果多个URL模式的参数不同,也可以使用相同的名称。除了URL名称之外, reverse() 匹配参数的数目和关键字参数的名称。路径转换器也可以提升 ValueError 要指示不匹配,请参见 注册自定义路径转换器 有关详细信息。

URL命名空间

介绍

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

正确使用URL名称间距的Django应用程序可以为特定站点部署多次。例如 django.contrib.admin 有一个 AdminSite 类,它允许您 deploy more than one instance of the admin . 在后面的示例中,我们将讨论在两个不同的位置部署教程中的polls应用程序的想法,这样我们就可以为两个不同的受众(作者和发布者)提供相同的功能。

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

应用程序命名空间

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

实例命名空间

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

使用指定的名称空间URL ':' 操作员。例如,使用 'admin:index' . 这表示的命名空间为 'admin' ,以及 'index' .

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

正在反转命名空间URL

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

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

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

    这个 url 模板标记将当前已解析视图的命名空间用作 RequestContext . 您可以通过在 request.current_app 属性。

  3. 如果没有当前应用程序,Django将查找默认应用程序实例。默认应用程序实例是具有 instance namespace 匹配 application namespace (在本例中, polls 调用 'polls'

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

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

如果存在嵌套的命名空间,则对命名空间的每个部分重复这些步骤,直到仅解析视图名称为止。然后,视图名称将解析为已找到的命名空间中的URL。

例子

要在实际中显示此解决策略,请考虑 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")),
]
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"),
    ...,
]

使用此设置,可以进行以下查找:

  • 如果其中一个实例是当前的,例如,如果我们在实例中呈现详细信息页 '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

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

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

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"),
    ...,
]
urls.py
from django.urls import include, path

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

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

其次,您可以包括一个包含嵌入名称空间数据的对象。如果你 include() 一份名单 path()re_path() 实例时,该对象中包含的URL将被添加到全局命名空间。但是,您也可以 include() 包含以下内容的2元组:

(<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模式包含到给定的应用程序名称空间中。

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