• 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"))
    终日不为以思,无益,不如学也
  • 相关阅读:
    Ubuntu:替换DASH图标
    使用 python 操作 mongodb 常用的操作
    boost Asio网络编程简介
    optional的使用
    boost中Function和Lambda的使用
    boost多线程入门介绍
    boost中bind的使用
    c++11新标准for循环和lambda表达式
    使用gcc命令编译多个文件
    编辑gif
  • 原文地址:https://www.cnblogs.com/lymlike/p/11560057.html
Copyright © 2020-2023  润新知