Django——路由基础

URL是Web服务的入口,用户通过浏览器发送过来的任何请求,都是发送到一个指定的URL地址,然后被响应。

在Django项目中编写路由,就是向外暴露我们接收哪些URL的请求,除此之外的任何URL都不被处理,也没有返回。通俗地理解,不恰当的形容,URL路由是你的Web服务对外暴露的API。

Django奉行DRY主义,提倡使用简洁、优雅的URL,没有.php.cgi这种后缀,更不会单独使用0、2097、1-1-1928、00这样无意义的东西,让你随心所欲设计你的URL,不受框架束缚。

1.Django如何处理请求

当用户请求一个页面时,Django根据下面的逻辑执行操作:

  1. 决定要使用的根URLconf模块。通常,这是ROOT_URLCONF设置的值,但是如果传入的HttpRequest对象具有urlconf属性(由中间件设置),则其值将被用于代替ROOT_URLCONF设置。通俗的讲,就是你可以自定义项目入口url是哪个文件!
  2. 加载该模块并寻找可用的urlpatterns。 它是django.urls.path()或者django.urls.re_path()实例的一个列表。
  3. 依次匹配每个URL模式,在与请求的URL相匹配的第一个模式停下来。也就是说,url匹配是从上往下的短路操作,所以url在列表中的位置非常关键。
  4. 导入并调用匹配行中给定的视图,该视图是一个简单的Python函数(被称为视图函数),或基于类的视图。 视图将获得如下参数:
    1. 一个HttpRequest 实例。
    2. 如果匹配的表达式返回了未命名的组,那么匹配的内容将作为位置参数提供给视图。
    3. 关键字参数由表达式匹配的命名组组成,但是可以被django.urls.path()的可选参数kwargs覆盖。
  5. 如果没有匹配到任何表达式,或者过程中抛出异常,将调用一个适当的错误处理视图。

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

注意:

  1. 代码中的尖括号,在Django中尖括号的作用是捕获值,“”之前式转换器的类型,后面是转换器捕获值的名称。此处应当提前说明一下,此处捕获的参数会被传入对应的views函数里面,假使函数没有声名这个参数,就会报错;
  2. 可以转换捕获到的值为指定类型,比如例子中的int。默认情况下,捕获到的结果保存为字符串类型,不包含/这个特殊字符;
  3. 匹配模式的最开头不需要添加/,因为默认情况下,每个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"))
终日不为以思,无益,不如学也
原文地址:https://www.cnblogs.com/lymlike/p/11560057.html