URL 是 web 服务的入口, 用户通过浏览器发送过来的任何请求, 都是发送到一个指定的 URL 地址, 然后被响应.
在 Django 项目中编写路由, 就是向外暴露我们接收哪些 URL 的请求, 除此之外的任何 URL 都不被处理, 也没有返回. 通俗地理解, 不恰当的形容, URL 路由是你的 Web 服务对外暴露的 API.
Django 奉行 DRY 主义, 提倡使用简洁, 优雅的 URL, 没有. PHP 或. CGI 这种后缀, 更不会单独使用 0,2097,1-1-1928,00 这样无意义的东西, 让你随心所欲设计你的 URL, 不受框架束缚.
1.Django 如何处理请求
当用户请求一个页面时, Django 根据下面的逻辑执行操作:
决定要使用的根 URLconf 模块. 通常, 这是 ROOT_URLCONF 设置的值, 但是如果传入的 HttpRequest 对象具有 urlconf 属性 (由中间件设置), 则其值将被用于代替 ROOT_URLCONF 设置. 通俗的讲, 就是你可以自定义项目入口 url 是哪个文件!
加载该模块并寻找可用的 urlpatterns. 它是 django.urls.path() 或者
django.urls.re_path()
实例的一个列表.
依次匹配每个 URL 模式, 在与请求的 URL 相匹配的第一个模式停下来. 也就是说, url 匹配是从上往下的短路操作, 所以 url 在列表中的位置非常关键.
导入并调用匹配行中给定的视图, 该视图是一个简单的 Python 函数 (被称为视图函数), 或基于类的视图. 视图将获得如下参数:
一个 HttpRequest 实例.
如果匹配的表达式返回了未命名的组, 那么匹配的内容将作为位置参数提供给视图.
关键字参数由表达式匹配的命名组组成, 但是可以被 django.urls.path() 的可选参数 kwargs 覆盖.
如果没有匹配到任何表达式, 或者过程中抛出异常, 将调用一个适当的错误处理视图.
2.URL 的正向解析
上官方文档代码
- 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),
- ]
注意:
代码中的尖括号, 在 Django 中尖括号的作用是捕获值,":" 之前式转换器的类型, 后面是转换器捕获值的名称. 此处应当提前说明一下, 此处捕获的参数会被传入对应的 views 函数里面, 假使函数没有声名这个参数, 就会报错;
可以转换捕获到的值为指定类型, 比如例子中的 int. 默认情况下, 捕获到的结果保存为字符串类型, 不包含 / 这个特殊字符;
匹配模式的最开头不需要添加 /, 因为默认情况下, 每个 url 都带一个最前面的 /, 既然大家都有的部分, 就不用浪费时间特别写一个了.
2.1 匹配例子
/articles/2005/03/ 将匹配第三条, 并调用 views.month_archive(request, year=2005, month=3);
/articles/2003 / 匹配第一条, 并调用 views.special_case_2003(request);
/articles/2003 将一条都匹配不上, 因为它最后少了一个斜杠, 而列表中的所有模式中都以斜杠结尾;
/articles/2003/03/building-a-django-site/ 将匹配最后一个, 并调用 views.article_detail(request, year=2003, month=3, slug="building-a-django-site"
2.2 路径转换器
默认情况下, Django 内置下面的路径转换器:
str: 匹配任何非空字符串, 但不含斜杠 /, 如果你没有专门指定转换器, 那么这个是默认使用的;
int: 匹配 0 和正整数, 返回一个 int 类型
slug: 可理解为注释, 后缀, 附属等概念, 是 url 拖在最后的一部分解释性字符. 该转换器匹配任何 ASCII 字符以及连接符和下划线, 比如'building-your-1st-django-site';
uuid: 匹配一个 uuid 格式的对象. 为了防止冲突, 规定必须使用破折号, 所有字母必须小写, 例如'075194d3-6885-417e-a8a8-6c931e272f00' . 返回一个 UUID 对象;
path: 匹配任何非空字符串, 重点是可以包含路径分隔符'/'. 这个转换器可以帮助你匹配整个 url 而不是一段一段的 url 字符串.
2.3 使用正则表达式
如果路径和转换器语法不足以定义 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),
- ]
这完成了与前一个示例大致相同的事情, 除了:
匹配的确切网址稍微受限制. 例如, 年份 10000 将不再匹配, 因为年份整数被限制为恰好四位数.
无论正则表达式匹配什么类型, 每个捕获的参数都将作为字符串发送到视图.
除了命名组语法之外, 例如 (?P<year>[0-9]{4}), 还可以使用较短的未命名组, 例如 ([0-9]{4}), 不推荐这种用法, 不再赘述.
2.4 指定视图参数的默认值
有一个小技巧, 可以指定视图参数的默认值. 下面是一个 URLconf 和视图的示例:
- # URLconf
- from django.urls import path
- from . import views
- urlpatterns = [
- path('blog/', views.page),
- path('blog/page<int:num>/', views.page),
- ]
- # 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 值.
2.5 路由转发 (include)
在一般情况下, 进行网站的编写, url 不可能简简单单的只有几个, 而应该是很多, 此时如果将需要匹配的 url 全部放到 urlpattern 里面, 肯那个就就会显得有些冗余. include 函数就应运而生了. 每当 Django 遇到时 include(), 它会去掉 URL 中已经匹配的部分, 并将剩余的字符串发送到包含的 URLconf 以进行进一步处理, 也就是转发到二级路由去.
被包含的 URLconf 会收到来自父 URLconf 捕获的任何参数 ##
- from django.urls import include, path
- urlpatterns = [
- # ...
- path('community/', include('aggregator.urls')),
- path('contact/', include('contact.urls')),
- # ...
- ]
上面的 url,community / 就会跳转到 aggregator.urls 内部进行继续匹配.
再看一个例子:
- from django.conf.urls import url
- from . import views
- urlpatterns = [
- re_path(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/history/$', views.history),
- re_path(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/edit/$', views.edit),
- re_path(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/discuss/$', views.discuss),
- re_path(r'^(?P<page_slug>[\w-]+)-(?P<page_id>\w+)/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),
- ])),
- ]
2.6 传递额外参数
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')
同样, 我们可以传递额外的选项, include() 并且包含的??URLconf 中的每一行都将传递额外的选项. 无论是传递给当前的 url 文件还是 include 指向的文件, 都是被允许的.
3.URL 的反向解析
3.1 反向解析 URL
反向解析 url 主要是为了解决 url 硬性编码的问题, 硬性编码不仅费时, 不可扩展, 难以维护, 而且很容易出错.
Django 提供了一种解决方案, 只需在 URL 中提供一个 name 参数.
通过这个 name 参数, 可以反向解析 URL, 反向 URL 匹配, 反向 URL 查询或者简单的 URL 反查.
在需要解析 URL 的地方, 对于不同层级, Django 提供了不同的工具用于 URL 反查:
在模板语言中: 使用 url 模板标签.(也就是写前端网页时)
在 Python 代码中: 使用 reverse() 函数.(也就是写视图函数等情况时)
在更高层的与处理 Django 模型实例相关的代码中: 使用 get_absolute_url() 方法.(也就是在模型 model 中)
搬个文档例子:
- # urls.py
- from django.urls import path
- from . import views
- urlpatterns = [
- #...
- path('articles/<int:year>/', views.year_archive, name='news-year-archive'),
- #...
- ]
在模板中
- <a href="{% url'news-year-archive'2012 %}">
- 2012 Archive
- </a>
- <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 = 2019
- # ...
- return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))
其中, 起到核心作用的是我们通过 name='news-year-archive'为那条 url 起了一个可以被引用的名称.
4.URL 的命名空间
先看下这个命名空间是干嘛用的?
上面我们解决了 url 的硬性编码问题, 就是给 url 起个别名, 我们知道一个项目可能会包含多个 App 应用, 所以除非你列一个很清晰的表格记录分别给每个 App 的每个 url 起了什么别名, 以确保他们的名字没有重复, 否则我们很难记得是不是已经有了一个叫 "张伟" 的. 如果不同 App 的 url 起了相同的名字, 那就要看谁排在前面了, 这种抽奖式方式显然不是我们需要的, 为了解决这个问题, 就有了命名空间.
URL 命名空间可以保证反查到唯一的 URL, 即使不同的 App 使用相同的 URL 名称.
实现命名空间的做法很简单, 在 urlconf 文件中添加 app_name = 'polls'和 namespace='author-polls'这种类似的定义.
简单例子:
project 的 urls.py
- urlpatterns = [
- path('admin/', admin.site.urls),
- path('app01/', include("app01.urls",namespace="app01")),
- path('app02/', include("app02.urls",namespace="app02")),
- ]
app01 的 urls.py
- urlpatterns = [
- path('index/', views.index,name="index"),
- ]
app02 的 urls.py
- urlpatterns = [
- path('index/', views.index,name="index"),
- ]
app01 的 views.py
- from django.core.urlresolvers import reverse
- def index(request):
- print('app01')
- return HttpResponse(reverse("app01:index"))
app02 的 views.py
- from django.core.urlresolvers import reverse
- def index(request):
- print('app02')
- return HttpResponse(reverse("app02:index"))
来源: http://www.bubuko.com/infodetail-3206323.html