在Understand Django系列的最后一篇文章中,我们看到了用户的浏览器请求是如何从浏览器转到Django的“前门”的,现在是时候看看Django如何处理这些请求了。
来自浏览器的HTTP请求包含一个URL,用来描述Django应该提供哪个资源。由于url可以有多种形式,我们必须告诉Django我们的web应用程序可以处理的url类型。这就是URL配置的目的。在Django文档中,URL配置简称为URLconf。
URLconf在哪里?URLconf位于项目设配置文件中的ROOT_URLCONF所设置的模块路径。如果运行startproject命令,则该设置将命名为类似project.url,其中“project”是命令中参数给定的名称。换句话说,URLconf就放在project/urls.py中的settings.py文件旁边。
这说明了文件所在的位置,但并没有告诉我们它是如何工作的。让我们再深入一些。
URLconf实战
尝试将URL配置看作是Django提供的从上到下匹配的URL路径列表。当Django找到匹配的路径时,HTTP请求将路由到与该路径关联的Python代码块。这个“Python代码块”被称为视图,我们将在稍后对其进行更多的探讨。目前,请相信视图知道如何处理HTTP请求。
我们可以使用一个URLconf示例来展示这一点。
这里的内容与我上面描述相匹配:Django将尝试从上到下匹配URL路径列表。此列表的关键点是urlpatterns。Django将把urlpatterns变量列表视为URLconf。
名单上的顺序也很重要。该示例没有显示路径之间的任何冲突,但是可以创建两个不同的路径,它们可以匹配用户提交的同一个URL。在我们看到路径的另一个方面之后,我将举一个这样的例子。
我们可以通过这个例子来了解www.acme.com的工作原理。考虑URLconf中的URL时,Django不使用(https://)、域(www.acme.com)和前导斜杠进行匹配。其他一切都是URLconf将要匹配的。
对https://www.acme.com/about/请求的匹配过程的将看起来像“about/”,并匹配第二个路径。该请求将路由到views.about视图。
对https://www.acme.com/的请求在模式匹配过程中看起来像“”并匹配第一个路径。该请求将路由到views.home视图。
旁白:您可能注意到Django url以斜线字符结尾。事实上,如果您尝试访问像https://www.acme.com/about这样的URL,Django会将请求重定向到附加斜杠的同一URL,因为附加斜杠是默认设置。这种行为是由Django的设计理念决定的。
我们面前的道路
如果我给你的只是上面的例子,然后你认为“哇,Django是个笨蛋。为什么urlpatterns不是下面这样的字典?”,我不会责怪你。
path 比我最初揭示的更强大。大部分能力都包含在传递给函数的第一个字符串参数中。路径的字符串部分(例如,“about/”)称为路由。
正如您所看到的,路由是一个简单的字符串,但是它可以包含其他特殊结构和一个称为转换器的特性。使用转换器时,可以从视图的URL中提取信息。想想这样一条路径:
此路径中的两个转换器是:
尖括号和一些保留名称会使Django尝试对URL进行额外的解析。每个转换器都有一些预期的规则要遵循。
int转换器必须与整数匹配。Slug转换器必须与Slug匹配。Slug是一种出现在Django的报刊术语,因为Django是从堪萨斯州的一家报刊开始的。slug是一个字符串,可以包含字符、数字、破折号和下划线。
给定了这些转换器定义,让我们与一些url进行比较!
https://www.acme.com/blog/2020/urls-lead-way/ - 匹配!
https://www.acme.com/blog/twenty-twenty/urls-lead-way/ - 不行.
https://www.acme.com/blog/0/life-in-rome/ - 匹配!可能不是我们想的那样,我们等下再解释。
现在我们可以重新审视一下之前的顺序问题。以不同的顺序考虑这两条路径:
在第一次中,转换器将匹配blog/之后的任何整数,包括https://www.acme.com/blog/2020/。这意味着第一次排序将永远不会调用blog_for_twenty_twenty视图,因为Django按顺序匹配。
相反,在第二个顺序中,blog/2020/将正确地路由到blog_for_twenty_twenty,因为它首先匹配。这意味着要记住:
当包含与转换器匹配的路径项时,请确保将它们放在更具体的项之后。
简略的观点
转换器如何处理这些额外的数据?这很难在不涉及视图的情况下解释。下一篇文章将更深入地讨论这些观点,但这里有一个入门。
视图是接受请求并返回响应的代码。使用Python的可选类型检查,下面是一个发送Hello World响应的示例。
HTTP request是Django转换的HTTP请求格式,包装在一个方便的容器类中。同样,我们可以使用HTTP response,以便Django将我们的响应数据转换为格式正确的HTTP响应,并将其发送回用户的浏览器。
现在我们可以再看一个转换器。
使用这个转换器,blog-by-year会是什么样子?
Django开始在这里展示一些不错的品质!转换器为我们做了一堆乏味的工作。Django设置的year参数已经是一个整数,因为Django进行了字符串解析和转换。
如果有人提交/blog/not_a_number/,Django将返回not Found响应,因为not_a_number不能转换成整数。这样做的好处是,我们不必在 blog_by_year中添加额外的检查逻辑来处理不合法的输入年份。这种功能可以节省时间!它使代码更干净,处理更精确。
我们之前在/blog/0/life in rome/上看到的另一个奇怪的例子呢?这与前面部分中的模式相匹配,但假设我们希望匹配四位数的年份,我们怎么能做到?我们可以使用正则表达式。
正则表达式路径
正则表达式是一种编程特性,常常被比作电锯:它们功能强大得令人难以置信,但如果不小心的话,你可以砍掉你的脚。
正则表达式可以以非常简洁的方式表示复杂的关系和匹配模式。这种简洁性常常给正则表达式带来难以理解的坏名声。但如果仔细使用,它们可能是一个很好用的工具。
正则表达式(通常缩写为“regex”)适合的一项工作是匹配字符串中的复杂模式。这听起来像是我们blog year的问题!在我们的问题中,我们只想匹配一个四位数的整数。让我们看看Django的解决方案,然后阐释它的含义。
作为提醒,此解决方案将匹配某些URL路径,如blog/2020/URL lead way/。
这个疯狂的字符串的作用与前面的例子完全一样,只是它更精确地限制了四位数的年份。疯狂的弦也有名字。它被称为regex模式。当Django代码运行时,它将根据此模式中定义的规则测试URL路径。
要了解它是如何工作的,我们必须知道模式的各个部分意味着什么。我们可以一次解释一块。
插入符号,^,意思是“模式必须从这里开始”。因为插入符号,一个像myblog/一样开始的路径。。。不会起作用。
blog/是字面上的解释。这些字符必须完全匹配。
圆括号内的部分(?P[0-9]{4})是一个捕获组。这个?P是与捕获组关联的名称,类似于这样的转换器中的冒号右侧。该名称允许Django将名为year的参数中的内容传递给视图。
捕获组[0-9]{4}的另一部分是模式实际匹配的部分。[0-9]是一个字符类,意思是“匹配从0到9的任意数字,{4}表示它必须精确匹配四次。这是re_path给出的int转换器不能提供的特性!
捕获组之间的斜杠/,是另一个要匹配的文本字符。
第二个捕捉组,(?P[\w-]+),将匹配的内容放入名为slug的参数中。[\w-]的字符类包含两种类型的字符。\w是指自然语言中的任何单词字符。另一种类型的字符是文字破折号,-,字符。最后,+,字符表示字符类必须匹配1次或多次。
最后一个斜杠也是文字字符匹配。
为了完成这个模式,美元符号$的作用与插入符号相反,表示“模式必须在这里结束”。因此,blog/2020/some slug/another slug/将不匹配。
祝贺你!这绝对是本文最难的部分。如果你明白我们对re_path做了什么,剩下的应该会感觉很舒服。如果没有,请不要担心!如果您想了解有关正则表达式的更多信息,请明白我在模式中描述的所有内容都不是特定于Django的。相反,这是Python的内置行为。您可以从Python的正则表达式HOWTO了解更多关于正则表达式的信息。
知道了re_path的力量,即使你今天不需要它,也可以帮助你以后的Django之旅。
为相关的URL分组
到目前为止,我们已经研究了可以在URLconf中映射的各个路由。当相关的视图组应该共享一条公共路径时,我们可以做什么?我们为什么要这么做?
让我们想象一下你正在建设一个教育项目。在您的项目中,有学校、学生和其他与教育相关的概念。你可以这样做:
这种方法可以很好地工作,但是它迫使根URLconf了解每个应用程序、学校和学生中定义的所有视图。作为替代,我们可以使用include来更好地处理这个问题。
然后,在每个应用程序中,我们都会得到如下内容:
include使每个Django应用程序在需要定义的视图中拥有自主权。这个项目可以很幸福地“不知道”应用程序正在做什么。
此外,从第一个例子中删除了schools/或students/的重复。当Django处理一个路由时,它将匹配路由的第一部分,并将其余部分传递到在单个应用程序中定义的URLconf。通过这种方式,URL配置可以形成一个树,根URLconf是所有请求开始的地方,但是当请求路由到适当的应用程序时,各个应用程序可以处理细节。
命名URL
我们已经研究了用path、re-path和include定义url的主要方法。还有另外一个方面需要考虑。我们如何引用代码中其他地方的url?想想这个(相当愚蠢的)视图:
重定向是指用户试图访问一个页面,并被浏览器发送到其他地方。处理重定向的方法比这个示例显示的要好得多,但是这个视图说明了一个不同的观点。如果您希望重新构造项目,以便将博客类别从/blog/categories/to/marketing/blog/categories/移动到/marketing/blog/categories/,会发生什么情况?在当前表单中,我们必须修复此视图和直接引用路由的任何其他视图。
真是浪费时间!Django为我们提供了一些工具来提供独立于显式路由的路径名。我们使用path的name关键字参数来实现这一点。
这使我们的blog_categories在/marketing/blog/categories/路径中独立命名。要使用这个名称,我们需要reverse 作为其对应项。我们修改后的视图如下:
reverse的任务是查找任何路径名并返回其路由。这意味着:
至少在你想再换一次之前。
当名字冲突时
如果您有多个要给出相同名称的url,会发生什么情况?例如,index或detail是可能要应用的公用名称。我们可以向The Zen of Python 寻求建议。
提姆·彼得斯的《The Zen of Python》
美胜于丑。…命名空间是一个很好的主意-让我们做更多!
如果编程时间不长,命名空间对您来说可能是全新的概念。它们是命名共享的空间。也许这很清楚,但我记得当我第一次开始写软件的时候,我还在为这个概念而挣扎。
为了与现实世界中的事物进行类比,让我们使用可信的桶。假设你有两个红球和两个蓝球。把一个颜色的球放在两个标有“A”和“B”的桶里。如果我想要一个特定的蓝球,我不能说“请把蓝球给我”,因为那样会模棱两可。相反,为了得到一个特定的球,我需要说“请给我bucket B中的蓝色球。”在这个场景中,bucket是命名空间。
我们在学校和学生中使用的示例可以帮助在代码中说明这个想法。两个应用程序都有一个index视图来表示项目各个部分的根(即学校/和学生/)。如果我们想关联到这些视图,我们会尽量选择最简单的index作为名称。不幸的是,如果选择index,那么Django无法判断哪个视图是index的正确视图。名称不明确。
一种解决方案是通过在名称前面加上一些常见的东西(如schools)来创建自己的命名空间。这种方法的问题在于URLconf会自我重复。
Django提供了一个选项,可以让您保留一个较短的名称。
通过添加app_name,我们向Django发出信号,这些视图位于一个名称空间中。现在,当我们想要获得一个URL时,我们使用名称空间名称和URL名称,并用冒号将它们连接起来。
这是Django为简化我们的应用程序开发提供的另一个便利。
这就结束了url的话题。现在,我们已经看到了如何:
通过创建包含urlpatterns列表的模块来进行URL配置。
使用path 和re_path创建URL。
使用转换器为视图提取信息。
使用正则表达式来表示更复杂的URL数据。
将相关的url与include一起分组。
按名称引用URL。
将相关名称放在命名空间中。
在下一篇文章中,我们将深入探讨各种观点。这篇文章只对什么是视图给出了最简单的定义。Django在处理视图时为我们提供了非常丰富的选项。我们将探索:
视图函数
视图类
一些内置的视图
视图装饰器。
如果你想继续这个系列,请随时订阅,在那里我宣布我的所有新内容。如果你还有其他问题,可以在Twitter上@mblayman。
英文原文:https://www.mattlayman.com/understand-django/urls-lead-way/
译者:QL