Loading

day 61 路由层 (URL)

1 路由匹配

1.1 介绍

​ 路由即请求地址与视图函数的映射关系,如果把网站比喻为一本书,那路由就好比是这本书的目录,在Django中路由默认配置在urls.py中,如下图:

urls

​ URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表。你就是以这种方式告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码。

[官方文档](URL dispatcher | Django documentation | Django (djangoproject.com))

1.2 简单的路由配置

1.2.1 基本格式:

推导过程

# 路由匹配
url(r'test',views.test),
url(r'testadd',views.testadd)

"""
上面这种方式会让 asdfatest 和 testaddafhnbv 都匹配成功所以需要正则表达式
url方法第一个参数是正则表达式
	只要第一个参数正则表达式能够匹配到内容,那么就会立刻停止往下匹配
	直接执行对应的视图函数
"""

加上正则

url(r'^test/',views.test),
url(r'^testadd/',views.testadd)
"""
一般以/结尾
你在输入url的时候Django会让浏览器默认加斜杠
	django内部帮你做到重定向
		第一次匹配不行
		Django让浏览器在url后面加斜杠再来一次
"""

补充说明

# 如果你想取消自动加斜杠,是否开启URL访问地址后面不为/跳转至带有/的路径的配置项
APPEND_SLASH = False/True	# 默认是自动加斜杠的

​ Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加'/'。

其效果就是,我们定义了urls.py:

from django.conf.urls import url
from app01 import views

urlpatterns = [
        url(r'^blog/$', views.blog),
]

​ 访问 http://www.example.com/blog 时,默认将网址自动转换为 http://www.example/com/blog/

​ 如果在settings.py中设置了 APPEND_SLASH=False,此时我们再请求 http://www.example.com/blog 时就会提示找不到页面

urlpatterns = [
    url(r'^admin/', admin.site.urls),   # 首页:URL什么也没有匹配首页
    
    # 路由匹配
    url(r'^$',views.home),
    url(r'^test/$',views.test),
    url(r'^testadd/$',views.testadd),
    
    # 尾页(了解)
    url(r'',views.error),
]

1.2.2 正则表达式详解

1.2.2.1 基本使用

基本配置

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/([0-9]{4})/$', views.year_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
    url(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]

注意事项

  1. urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
  2. 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)。
  3. 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
  4. 每个正则表达式前面的 'r' 是可选的但是建议加上。

1.2.2.2 URLconf匹配的位置

​ URLconf 在请求的URL 上查找,将它当做一个普通的Python 字符串。不包括GET和POST参数以及域名。

​ 例如,http://www.example.com/myapp/ 请求中,URLconf 将查找myapp/。

​ 在http://www.example.com/myapp/?page=3 请求中,URLconf 仍将查找myapp/

​ URLconf 不检查请求的方法。换句话讲,所有的请求方法 —— 同一个URL的POSTGETHEAD等等 —— 都将路由到相同的函数。

捕获的参数永远都是字符串

​ 每个在URLconf中捕获的参数都作为一个普通的Python字符串传递给视图,无论正则表达式使用的是什么匹配方式。例如,下面这行URLconf 中:

url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),

传递到视图函数views.year_archive() 中的year 参数永远是一个字符串类型。

视图函数中指定默认值

# urls.py中
from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^blog/$', views.page),
    url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]

# views.py中,可以为num指定默认值
def page(request, num="1"):
    pass

​ 在上面的例子中,两个URL模式指向相同的view - views.page - 但是第一个模式并没有从URL中捕获任何东西。

​ 如果第一个模式匹配上了,page()函数将使用其默认参数num=“1”,如果第二个模式匹配,page()将使用正则表达式捕获到的num值。

1.2.3 案例:

urls.py文件

from django.conf.urls import url
from django.contrib import admin
from app01 import views # 导入模块views.py

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    url(r'^index/$',views.index), # 新增一条
]

views.py文件

from django.shortcuts import render
from django.shortcuts import HttpResponse # 导入HttpResponse,用来生成响应信息

# 新增视图函数index
def index(request):
    return HttpResponse('index page...')

测试:

python manage.py runserver 8001 # 在浏览器输入:http://127.0.0.1:8001/index/ 会看到 index page...
注意一:

​	刚刚我们在浏览器输入:http://127.0.0.1:8001/index/,Django会拿着路径部分index/去路由表中自上而下匹配正则表达式,一旦匹配成功,则立即执行其后的视图函数,不会继续往下匹配,此处匹配成功的正则表达式是 r'^index/$'。

注意二:

​	但是我们在浏览器输入:http://127.0.0.1:8001/index,Django同样会拿着路径部分index去路由表中自上而下匹配正则表达式,貌似并不会匹配成功任何正则表达式( r'^index/$'匹配的是必须以 / 结尾,所以不会匹配成功index),但实际上仍然会看到结果 index page...,原因如下:

​	在配置文件settings.py中有一个参数APPEND_SLASH,该参数有两个值True或False

​	当APPEND_SLASH=True(如果配置文件中没有该配置,APPEND_SLASH的默认值为True),并且用户请求的url地址的路径部分不是以 / 结尾,例如请求的url地址是 http://127.0.0.1:8001/index,Django会拿着路径部分(即index)去路由表中匹配正则表达式,发现匹配不成功,那么Django会把这条链接地址并让浏览器在路径后加 / (即index/)之后,再去路由表中匹配,如果匹配失败则会返回路径未找到,如果匹配成功,则会返回重定向信息给浏览器,要求浏览器重新向 http://127.0.0.1:8001/index/地址发送请求。

​	当APPEND_SLASH=False时,则不会执行上述过程,即一旦url地址的路径部分匹配失败就立即返回路径未找到,不会做任何的附加操作

​	注意!!!在末尾加/然后重新发起请求,这是浏览器的功能,如果是在终端直接执行curl http://127.0.0.1:8901/index,则没有该功能。

2 分组命名匹配

2.1 介绍

​ 上面的示例使用简单的正则表达式分组匹配(通过圆括号)来捕获URL中的值并以位置参数形式传递给视图。

​ 在更高级的用法中,可以使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图。什么是分组、为何要分组呢?比如我们开发了一个博客系统,当我们需要根据文章的id查看指定文章时,浏览器在发送请求时需要向后台传递参数(文章的id号),可以使用 http://127.0.0.1:8001/article/?id=3,也可以直接将参数放到路径中http://127.0.0.1:8001/article/3/

​ 针对后一种方式Django就需要直接从路径中取出参数,这就用到了正则表达式的分组功能了,分组分为两种:无名分组与有名分组

​ 在Python的正则表达式中,分组命名正则表达式组的语法是(?P<name>pattern),其中name是组的名称,pattern是要匹配的模式。

下面是以上URLconf 使用命名组的重写:

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^articles/2003/$', views.special_case_2003),
    url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]

​ 这个实现捕获的值作为关键字参数而不是位置参数传递给视图函数。例如,针对url /articles/2017/12/相当于按以下方式调用视图函数:

views.month_archive(request, year="2017", month="12")

​ 在实际应用中,使用分组命名匹配的方式可以让你的URLconf 更加明晰且不容易产生参数顺序问题的错误,但是有些开发人员则认为分组命名组语法太丑陋、繁琐。

​ 至于究竟应该使用哪一种,你可以根据自己的喜好来决定。

2.2 无名分组

分组:就是给某一段正则表达式用小括号扩起来

无名分组就是将括号内正则表达式匹配到的内容当作位置参数传递给后面的视图函数

url(r'^test/(\d+)/',views.test)

def test(request,xx):
    print(xx)
    return HttpResponse('test')

urls.py文件

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    # 下述正则表达式会匹配url地址的路径部分为:article/数字/,匹配成功的分组部分会以位置参数的形式传给视图函
数,有几个分组就传几个位置参数
    url(r'^aritcle/(\d+)/$',views.article), 
]

views.py文件

from django.shortcuts import render
from django.shortcuts import HttpResponse

# 需要额外增加一个形参用于接收传递过来的分组数据
def article(request,article_id):
    return HttpResponse('id为 %s 的文章内容...' %article_id)

测试:

python manage.py runserver 8001 
# 在浏览器输入:http://127.0.0.1:8001/article/3/ 会看到: id为 3 的文章内容...

2.3 有名分组

​ 可以给正则表达式起一个别名。

​ 有名分组就是将括号内正则表达式匹配到的内容当作关键字参数传递给后面的视图函数

url(r'^testadd/(?P<year>\d+)',views.testadd)

def testadd(request,year):
    print(year)
    return HttpResponse('testadd')

urls.py文件

from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    # 该正则会匹配url地址的路径部分为:article/数字/,匹配成功的分组部分会以关键字参数(article_id=匹配成功的数
字)的形式传给视图函数,有几个有名分组就会传几个关键字参数
    url(r'^aritcle/(?P<article_id>\d+)/$',views.article), 
]

views.py文件

from django.shortcuts import render
from django.shortcuts import HttpResponse

# 需要额外增加一个形参,形参名必须为article_id
def article(request,article_id):
    return HttpResponse('id为 %s 的文章内容...' %article_id)

测试:

python manage.py runserver 8001 
# 在浏览器输入:http://127.0.0.1:8001/article/3/ 会看到: id为 3 的文章内容...

在视图函数中指定默认值

在urls配置中
urlpatterns = [
    url(r'^jjzz/', views.Index.as_view()),
    url(r'^jjzz/(?P<year>[0-9]{4})/$', views.Index.as_view()),
]

在views配置中
class Index(View):
    jjzz = 'jjzz on the world'
    def get(self, request, year='2019'):
        return JsonResponse({'jjzz': self.jjzz})

​ 在上述的配置前提下,两个URL模式指向相同的view views.Index.as_view()但是第一个模式并没有从URL中捕获任何东西。如果第一个模式匹配上了,gte()函数将使用其默认参数year='2019',如果第二个模式匹配,page()将使用正则表达式捕获到的year的值。

​ 总结:有名分组和无名分组都是为了获取路径中的参数,并传递给视图函数,区别在于无名分组是以位置参数的形式传递,有名分组是以关键字参数的形式传递。

强调:无名分组和有名分组不要混合使用

2.4 无名有名是否可以混合使用

不能混用,但是同一个分组可以使用N多次

# 单个的分组可以使用多次
url(r'^index/(\d+)/(\d+)/(\d+)/',views.index),
url(r'^index/(?P<year>\d+)/(?P<age>\d+)/(?P<month>\d+)/',views.index),

3 反向解析

3.1 介绍

​ 在软件开发初期,url地址的路径设计可能并不完美,后期需要进行调整,如果项目中很多地方使用了该路径,一旦该路径发生变化,就意味着所有使用该路径的地方都需要进行修改,这是一个非常繁琐的操作。

​ 解决方案就是在编写一条url(regex, view, kwargs=None, name=None)时,可以通过参数name为url地址的路径部分起一个别名,项目中就可以通过别名来获取这个路径。以后无论路径如何变化别名与路径始终保持一致。

​ 上述方案中通过别名获取路径的过程称为反向解析

​ 咱们简单来说就是可以给我们的URL匹配规则起个名字,一个URL匹配模式起一个名字。这样我们以后就不需要写死URL代码了,只需要通过名字来调用当前的URL。

# 通过一些方法得到一个结果,该结果可以直接访问对应的url触发视图函数

# 先给路由与视图函数起一个别名
url(r'^func_kkk/',views.func,name='ooo')
    
# 反向解析
# 后端反向解析
  from django.shortcuts import render,HttpResponse,redirect,reverse
  reverse('ooo')
    
  # 前端反向解析
  <a href="{% url 'ooo' %}">111</a>

3.2 案例

案例1:登录成功跳转到index.html页面

在urls.py文件中
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    url(r'^login/$', views.login,name='login_page'), # 路径login/的别名为login_page
    url(r'^index/$', views.index,name='index_page'), # 路径index/的别名为index_page
]
在views.py中
from django.shortcuts import render 
from django.shortcuts import reverse # 用于反向解析
from django.shortcuts import redirect #用于重定向页面
from django.shortcuts import HttpResponse

def login(request):
        if request.method == 'GET':
                # 当为get请求时,返回login.html页面,页面中的{% url 'login_page' %}会被反向解析成路径:/login/
                return render(request, 'login.html')

        # 当为post请求时,可以从request.POST中取出请求体的数据
        name = request.POST.get('name')
        pwd = request.POST.get('pwd')
        if name == 'kevin' and pwd == '123':
                url = reverse('index_page')  # reverse会将别名'index_page'反向解析成路径:/index/       
                return redirect(url) # 重定向到/index/
        else:
                return HttpResponse('用户名或密码错误')


def index(request):
        return render(request, 'index.html')
login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<!--强调:login_page必须加引号-->
<form action="{% url 'login_page' %}" method="post">
    {% csrf_token %} <!--强调:必须加上这一行,后续我们会详细介绍-->
    <p>用户名:<input type="text" name="name"></p>
    <p>密码:<input type="password" name="pwd"></p>
    <p><input type="submit" value="提交"></p>

</form>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h3>我是index页面...</h3>
</body>
</html>
测试
python manage.py runserver 8001  
# 在浏览器输入:http://127.0.0.1:8001/login/ 会看到登录页面,输入正确的用户名密码会跳转到index.html
# 当我们修改路由表中匹配路径的正则表达式时,程序其余部分均无需修改

总结:

在views.py中,反向解析的使用:
url = reverse('index_page')
    
在模版login.html文件中,反向解析的使用
{% url 'login_page' %}

3.3 无名有名分组反向解析

如果路径存在分组的反向解析使用:

示例1
from django.conf.urls import url
from django.contrib import admin
from app01 import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    url(r'^aritcle/(\d+)/$',views.article,name='article_page'), # 无名分组
    url(r'^user/(?P<uid>\d+)/$',views.article,name='user_page'), # 有名分组
]

# 1 针对无名分组,比如我们要反向解析出:/aritcle/1/ 这种路径,写法如下
在views.py中,反向解析的使用:
url = reverse('article_page',args=(1,)) 
在模版login.html文件中,反向解析的使用
{% url 'article_page' 1 %}

    
# 2 针对有名分组,比如我们要反向解析出:/user/1/ 这种路径,写法如下
在views.py中,反向解析的使用:
url = reverse('user_page',kwargs={'uid':1}) 
在模版login.html文件中,反向解析的使用
{% url 'user_page' uid=1 %}
示例2
# 无名分组反向解析基本使用:
url(r'^index/(\d+)/',views.index,name='xxx')

# 前端
{% url 'xxx' 123 %}   # 这里这个数字只是一个占位符
# 后端
reverse('xxx', args=(1,))

-----------------------------------------------------------------
#这个数字写代码的时候应该放什么
数字一般情况下放的是数据的主键值,数据的编辑和删除
url(r'^edit/(\d+)/',views.edit,name='xxx')
--------------------------------------------------
def edit(request,edit_id):
reverse('xxx',args=(edit_id,))
--------------------------------------------------
{%for user_obj in user_queryset%}
<a href="{% url 'xxx' user_obj.id %}">编辑</a>
{%endfor%}
--------------------------------------------------

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

# 有名分组反向解析
   url(r'^func/(?P<year>\d+)/',views.func,name='ooo')
# 前端
<a href="{% url 'ooo' year=123 %}">111</a>   # 形式1
<a href="{% url 'ooo' 123 %}">222</a>  # 形式2,和无名分组一样
-------------------------------------------------------------------
# 后端
 # 有名分组反向解析
   print(reverse('ooo',kwargs={'year':123}))

   # 简便的写法,减少你的脑容量消耗,记跟无名一样的操作即可
   print(reverse('ooo',args=(111,)))

4 路由分发

4.1 路由分发

​ 随着项目功能的增加,app会越来越多,路由也越来越多,每个app都会有属于自己的路由,如果再将所有的路由都放到一张路由表中,会导致结构不清晰,不便于管理,所以我们应该将app自己的路由交由自己管理,然后在总路由表中做分发,具体做法如下

1 创建两个app

# 新建项目mystie2
E:\git>django-admin startproject mysite2
    
# 切换到项目目录下
E:\git>cd mysite2
    
# 创建app01和app02
E:\git\mysite2>python3 manage.py startapp app01
E:\git\mysite2>python3 manage.py startapp app02

2 在每个app下手动创建urls.py来存放自己的路由,如下:

app01下的urls.py文件

from django.conf.urls import url
# 导入app01的views
from app01 import views

urlpatterns = [
    url(r'^index/$',views.index), 
]

app01下的views.py

from django.shortcuts import render
from django.shortcuts import HttpResponse

def index(request):
    return HttpResponse('我是app01的index页面...')

app02下的urls.py文件

from django.conf.urls import url
# 导入app02的views
from app02 import views

urlpatterns = [
    url(r'^index/$',views.index), 
]

app02下的views.py

from django.shortcuts import render
from django.shortcuts import HttpResponse

def index(request):
    return HttpResponse('我是app02的index页面...')

3 在总的urls.py文件中(mysite2文件夹下的urls.py)

from django.conf.urls import url,include
from django.contrib import admin

# 总路由表
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    # 新增两条路由,注意不能以$结尾
    # include函数就是做分发操作的,当在浏览器输入http://127.0.0.1:8001/app01/index/时,会先进入到总路由表中进行匹配,正则表达式r'^app01/'会先匹配成功路径app01/,然后include功能会去app01下的urls.py中继续匹配剩余的路径部分
    url(r'^app01/', include('app01.urls')),
    url(r'^app02/', include('app02.urls')),
]

------------------------------------------------------------------------------------
# 总路由
from app01 import urls as app01_urls
from app02 import urls as app02_urls
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 1.路由分发
    url(r'^app01/',include(app01_urls)),  # 只要url前缀是app01开头 全部交给app01处理
    url(r'^app02/',include(app02_urls))   # 只要url前缀是app02开头 全部交给app02处理

]
------------------------------------------
urlpatterns = [  "第二种写法不用导入from app02 import urls as app02_urls"
    url(r'^admin/', admin.site.urls),
   
    # 2.终极写法  推荐使用
    url(r'^app01/',include('app01.urls')),
    url(r'^app02/',include('app02.urls'))
    # 注意事项:总路由里面的url千万不能加$结尾
]

测试:

python manage.py runserver 8001  
# 在浏览器输入:http://127.0.0.1:8001/app01/index/ 会看到:我是app01的index页面...
# 在浏览器输入:http://127.0.0.1:8001/app02/index/ 会看到:我是app02的index页面...
示例
"""
django的每一个应用都可以有自己的templates文件夹,urls.py static文件夹,正是基于上述的特点,django能够非常好的做到分组开发(每个人只写自己的app)。
作为组长,只需要将手下书写的app全部拷贝到一个新的django项目中,然后在配置文件里面注册所有的app再利用路由分发的特点将所有的app整合起来。
当一个django项目中的url特别多的时候,总路由urls.py代码非常冗余不好维护,这个时候也可以利用路由分发来减轻总路由的压力,利用路由分发之后,总路由不再干路由与视图函数的直接对应关系,而是做一个分发处理,识别当前url是属于哪个应用下的,直接分发给对应的应用去处理

"""

----------------------------------------------------------------------
# 总路由
from app01 import urls as app01_urls
from app02 import urls as app02_urls
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # 1.路由分发
    url(r'^app01/',include(app01_urls)),  # 只要url前缀是app01开头 全部交给app01处理
    url(r'^app02/',include(app02_urls))   # 只要url前缀是app02开头 全部交给app02处理
  
    # 2.终极写法,推荐使用
    url(r'^app01/',include('app01.urls')),
    url(r'^app02/',include('app02.urls'))
    # 注意事项:总路由里面的url千万不能加$结尾
]
----------------------------------------------------------------------
# 子路由
  # app01 urls.py
  from django.conf.urls import url
  from app01 import views

  urlpatterns = [
      url(r'^reg/',views.reg)
  ]
  # app02 urls.py
  from django.conf.urls import url
  from app02 import views

  urlpatterns = [
      url(r'^reg/',views.reg)
  ]

4.2 名称空间

即使不同的APP使用相同的URL名称,URL的命名空间模式也可以让你唯一反转命名的URL。

4.2.1 引入

​ 当我们的项目下创建了多个app,并且每个app下都针对匹配的路径起了别名,如果别名存在重复,那么在反向解析时则会出现覆盖,如下

1 创建两个app

# 新建项目mystie2
E:\git>django-admin startproject mysite2
    
# 切换到项目目录下
E:\git>cd mysite2
    
# 创建app01和app02
E:\git\mysite2>python3 manage.py startapp app01
E:\git\mysite2>python3 manage.py startapp app02

2 在每个app下手动创建urls.py来存放自己的路由,并且为匹配的路径起别名

app01下的urls.py文件

from django.conf.urls import url
from app01 import views

urlpatterns = [
    # 为匹配的路径app01/index/起别名'index_page'
    url(r'^index/$',views.index,name='index_page'), 
]

app02下的urls.py文件

from django.conf.urls import url
from app02 import views

urlpatterns = [
    # 为匹配的路径app02/index/起别名'index_page',与app01中的别名相同
    url(r'^index/$',views.index,name='index_page'), 
]

3 在每个app下的view.py中编写视图函数,在视图函数中针对别名'index_page'做反向解析

app01下的views.py

from django.shortcuts import render
from django.shortcuts import HttpResponse
from django.shortcuts import reverse

def index(request):
    url=reverse('index_page')
    return HttpResponse('app01的index页面,反向解析结果为%s' %url)

app02下的views.py

from django.shortcuts import render
from django.shortcuts import HttpResponse
from django.shortcuts import reverse

def index(request):
    url=reverse('index_page')
    return HttpResponse('app02的index页面,反向解析结果为%s' %url)

4 在总的urls.py文件中(mysite2文件夹下的urls.py)

from django.conf.urls import url,include
from django.contrib import admin

# 总路由表
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    
    # 新增两条路由,注意不能以$结尾
    url(r'^app01/', include('app01.urls')),
    url(r'^app02/', include('app02.urls')),
]

5 测试:

python manage.py runserver 8001  

在测试时,无论在浏览器输入:http://127.0.0.1:8001/app01/index/还是输入http://127.0.0.1:8001/app02/index/ 针对别名'index_page'反向解析的结果都是/app02/index/,覆盖了app01下别名的解析。

4.2.2 名称空间

​ 解决这个问题的方法之一就是避免使用相同的别名,如果就想使用相同的别名,那就需要用到django中名称空间的概念,将别名放到不同的名称空间中,这样即便是出现重复,彼此也不会冲突,具体做法如下

1、总urls.py在路由分发时,指定名称空间

from django.conf.urls import url, include
from django.contrib import admin

# 总路由表
urlpatterns = [
    url(r'^admin/', admin.site.urls),

    # 传给include功能一个元组,元组的第一个值是路由分发的地址,第二个值则是我们为名称空间起的名字
    url(r'^app01/', include(('app01.urls','app01'))),
    url(r'^app02/', include(('app02.urls','app02'))),
]

2、修改每个app下的view.py中视图函数,针对不同名称空间中的别名'index_page'做反向解析

app01下的views.py

from django.shortcuts import render
from django.shortcuts import HttpResponse
from django.shortcuts import reverse

def index(request):
    url=reverse('app01:index_page') # 解析的是名称空间app01下的别名'index_page'
    return HttpResponse('app01的index页面,反向解析结果为%s' %url)

app02下的views.py

from django.shortcuts import render
from django.shortcuts import HttpResponse
from django.shortcuts import reverse

def index(request):
    url=reverse('app02:index_page') # 解析的是名称空间app02下的别名'index_page'
    return HttpResponse('app02的index页面,反向解析结果为%s' %url)

3、测试:

python manage.py runserver 8001  

浏览器输入:http://127.0.0.1:8001/app01/index/反向解析的结果是/app01/index/

在浏览器输入http://127.0.0.1:8001/app02/index/ 反向解析的结果是/app02/index/

总结+补充

Copy1、在视图函数中基于名称空间的反向解析,用法如下
url=reverse('名称空间的名字:待解析的别名')

2、在模版里基于名称空间的反向解析,用法如下
<a href="{% url '名称空间的名字:待解析的别名'%}">哈哈</a>
示例
# 当多个应用出现了相同的别名,我们研究反向解析会不会自动识别应用前缀,正常情况下的反向解析是没有办法自动识别
前缀的


# 名称空间
   # 总路由
    url(r'^app01/',include('app01.urls',namespace='app01')),
    url(r'^app02/',include('app02.urls',namespace='app02'))
    -----------------------------------------------------------------
  # 解析的时候
  # app01
    urlpatterns = [
    url(r'^reg/',views.reg, name='reg')
]
    -----------------------------------------------------------------
    # app02
    urlpatterns = [
    url(r'^reg/',views.reg,name='reg')
]
    -----------------------------------------------------------------
    reverse('app01:reg')
    reverse('app02:reg')
    -----------------------------------------------------------------
    {% url 'app01:reg' %}
    {% url 'app02:reg' %}
 
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

# 其实只要保证名字不冲突,就没有必要使用名称空间
"""
一般情况下,有多个app的时候我们在起别名的时候会加上app的前缀,这样的话就能够确保多个app之间名字不冲突的问题
"""

urlpatterns = [
    url(r'^reg/',views.reg,name='app01_reg')
]
urlpatterns = [
    url(r'^reg/',views.reg,name='app02_reg')
]

5 其他知识点

5.1 伪静态

静态网页
	数据是写死的 万年不变
	
伪静态
	将一个动态网页伪装成静态网页
	
	为什么要伪装呢?
		https://www.cnblogs.com/Dominic-Ji/p/9234099.html
		伪装的目的在于增大本网站的seo查询力度,并且增加搜索引擎收藏本网上的概率
	
	搜索引擎本质上就是一个巨大的爬虫程序
	
	总结:
		无论你怎么优化 怎么处理
		始终还是干不过RMB玩家
        
操作方法:
urlpatterns = [
    url(r'^reg.html',views.reg,name='app02_reg')
]

5.2 虚拟环境(了解)

在正常开发中,我们会给每一个项目配备一个该项目独有的解释器环境,该环境内只有该项目用到的模块,用不到一概不
装


虚拟环境
	你每创建一个虚拟环境就类似于重新下载了一个纯净的python解释器,但是虚拟环境不要创建太多,是需要消耗硬盘
    空间的

扩展:
	每一个项目都需要用到很多模块,并且每个模块版本可能还不一样,那我该如何安装呢?一个个看一个个装?
	解决办法:
            开发当中我们会给每一个项目配备一个requirements.txt文件,里面书写了该项目所有的模块即版本,你只需要直
        接输入一条命令即可一键安装所有模块即版本

5.3 django版本区别

点击查看代码
1.django1.X路由层使用的是url方法
	而在django2.Xhe3.X版本中路由层使用的是path方法
	url()第一个参数支持正则
	path()第一个参数是不支持正则的 写什么就匹配什么
	
	
	如果你习惯使用path那么也给你提供了另外一个方法
		from django.urls import path, re_path
		from django.conf.urls import url
		
		re_path(r'^index/',index),
		url(r'^login/',login)
		2.X和3.X里面的re_path就等价于1.X里面的url
 
 
2.虽然path不支持正则 但是它的内部支持五种转换器
	path('index/<int:id>/',index)
	# 将第二个路由里面的内容先转成整型然后以关键字的形式传递给后面的视图函数

	def index(request,id):
		print(id,type(id))
		return HttpResponse('index')
    
  
  
  str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
	int,匹配正整数,包含0。
	slug,匹配字母、数字以及横杠、下划线组成的字符串。
	uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
	path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
	
3.除了有默认的五个转换器之外 还支持自定义转换器(了解)
	class MonthConverter:
		regex='\d{2}' # 属性名必须为regex

		def to_python(self, value):
			return int(value)

		def to_url(self, value):
			return value # 匹配的regex是两个数字,返回的结果也必须是两个数字
	
	
	from django.urls import path,register_converter
	from app01.path_converts import MonthConverter

	# 先注册转换器
	register_converter(MonthConverter,'mon')

	from app01 import views


	urlpatterns = [
    path('articles/<int:year>/<mon:month>/<slug:other>/', 	views.article_detail, name='aaa'),

]


4.模型层里面1.X外键默认都是级联更新删除的
但是到了2.X和3.X中需要你自己手动配置参数
	models.ForeignKey(to='Publish')
	
	models.ForeignKey(to='Publish',on_delete=models.CASCADE...)

​ Django2.0中的re_path与django1.0的url一样,传入的第一个参数都是正则表达式

Copyfrom django.urls import re_path # django2.0中的re_path
from django.conf.urls import url # 在django2.0中同样可以导入1.0中的url

urlpatterns = [
    # 用法完全一致
    url(r'^app01/', include(('app01.urls','app01'))),
    re_path(r'^app02/', include(('app02.urls','app02'))),
]

​ 在Django2.0中新增了一个path功能,用来解决:数据类型转换问题与正则表达式冗余问题,如下

urls.py文件

from django.urls import re_path

from app01 import views

urlpatterns = [
    # 问题一:数据类型转换
    # 正则表达式会将请求路径中的年份匹配成功然后以str类型传递函数year_archive,在函数year_archive中如果想以int类型的格式处理年份,则必须进行数据类型转换
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),

    # 问题二:正则表达式冗余
    # 下述三个路由中匹配article_id采用了同样的正则表达式,重复编写了三遍,存在冗余问题,并且极不容易管理,因为一旦article_id规则需要改变,则必须同时修改三处代码
    
    re_path(r'^article/(?P<article_id>[a-zA-Z0-9]+)/detail/$', views.detail_view),
    re_path(r'^articles/(?P<article_id>[a-zA-Z0-9]+)/edit/$', views.edit_view),
    re_path(r'^articles/(?P<article_id>[a-zA-Z0-9]+)/delete/$', views.delete_view),
]

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.
def year_archive(request,year):
    print(year,type(year))
    return HttpResponse('year_archive page')

def detail_view(request,article_id):
    print(article_id, type(article_id))
    return HttpResponse('detail_view page')

def edit_view(request,article_id):
    print(article_id, type(article_id))
    return HttpResponse('edit_view page')

def delete_view(request,article_id):
    print(article_id, type(article_id))
    return HttpResponse('delete_view page')

Django2.0中的path如何解决上述两个问题的呢?请看示例

from django.urls import path,re_path

from app01 import views

urlpatterns = [
    # 问题一的解决方案:
    path('articles/<int:year>/', views.year_archive), # <int:year>相当于一个有名分组,其中int是django提供的转换器,相当于正则表达式,专门用于匹配数字类型,而year则是我们为有名分组命的名,并且int会将匹配成功的结果转换成整型后按照格式(year=整型值)传给函数year_archive


    # 问题二解决方法:用一个int转换器可以替代多处正则表达式
    path('articles/<int:article_id>/detail/', views.detail_view), 
    path('articles/<int:article_id>/edit/', views.edit_view),
    path('articles/<int:article_id>/delete/', views.delete_view),
]

强调:

#1、path与re_path或者1.0中的url的不同之处是,传给path的第一个参数不再是正则表达式,而是一个完全匹配的路径,相同之处是第一个参数中的匹配字符均无需加前导斜杠

#2、使用尖括号(<>)从url中捕获值,相当于有名分组

#3、<>中可以包含一个转化器类型(converter type),比如使用 <int:name> 使用了转换器int。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符

django默认支持一下5种转换器(Path converters)

str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int,匹配正整数,包含0。
slug,匹配字母、数字以及横杠、下划线组成的字符串。
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)

例如

path('articles/<int:year>/<int:month>/<slug:other>/', views.article_detail) 
# 针对路径http://127.0.0.1:8000/articles/2009/123/hello/,path会匹配出参数year=2009,month=123,other='hello'传递给函数article_detail
很明显针对月份month,转换器int是无法精准匹配的,如果我们只想匹配两个字符,那么转换器slug也无法满足需求,针对等等这一系列复杂的需要,我们可以定义自己的转化器。转化器是一个类或接口,它的要求有三点:
  • regex 类属性,字符串类型
  • to_python(self, value) 方法,value是由类属性 regex 所匹配到的字符串,返回具体的Python变量值,以供Django传递到对应的视图函数中。
  • to_url(self, value) 方法,和 to_python 相反,value是一个具体的Python变量值,返回其字符串,通常用于url反向引用。

自定义转换器示例:

  1. 在app01下新建文件path_ converters.py,文件名可以随意命名

    class MonthConverter:
        regex='\d{2}' # 属性名必须为regex
    
        def to_python(self, value):
            return int(value)
    
        def to_url(self, value):
            return value # 匹配的regex是两个数字,返回的结果也必须是两个数字
    
  2. 在urls.py中,使用register_converter 将其注册到URL配置中:

    from django.urls import path,register_converter
    from app01.path_converts import MonthConverter
    
    register_converter(MonthConverter,'mon')
    
    from app01 import views
    
    
    urlpatterns = [
        path('articles/<int:year>/<mon:month>/<slug:other>/', views.article_detail, name='aaa'),
    
    ]
    
  3. views.py中的视图函数article_detail

    from django.shortcuts import render,HttpResponse,reverse
    
    def article_detail(request,year,month,other):
        print(year,type(year))
        print(month,type(month))
        print(other,type(other))
        print(reverse('xxx',args=(1988,12,'hello'))) # 反向解析结果/articles/1988/12/hello/
        return HttpResponse('xxxx')
    
  4. 测试

    # 1、在浏览器输入http://127.0.0.1:8000/articles/2009/12/hello/,path会成功匹配出参数year=2009,month=12,other='hello'传递给函数article_detail
    # 2、在浏览器输入http://127.0.0.1:8000/articles/2009/123/hello/,path会匹配失败,因为我们自定义的转换器mon只匹配两位数字
    
总结
1.django1.X路由层使用的是url方法,而在django2.Xhe3.X版本中路由层使用的是path方法
	url()第一个参数支持正则
	path()第一个参数是不支持正则的 写什么就匹配什么
	
	如果你习惯使用path那么也给你提供了另外一个方法, 2.X和3.X里面的re_path就等价于1.X里面的url
		from django.urls import path, re_path
		from django.conf.urls import url
		
		re_path(r'^index/',index)
		url(r'^login/',login)
 
 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    
2.虽然path不支持正则,但是它的内部支持五种转换器
	path('index/<int:id>/',index)
	# 将第二个路由里面的内容先转成整型,然后以关键字的形式传递给后面的视图函数

	def index(request,id):
            print(id,type(id))
            return HttpResponse('index')
    
  
str:匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int:匹配正整数,包含0。
slug:匹配字母、数字以及横杠、下划线组成的字符串。
uuid:匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path:匹配任何非空字符串,包含了路径分隔符(/)(不能用?)

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>	

3.除了有默认的五个转换器之外,还支持自定义转换器
class MonthConverter:
        regex='\d{2}' 	# 属性名必须为regex

        def to_python(self, value):
            return int(value)

        def to_url(self, value):
            return value 	# 匹配的regex是两个数字,返回的结果也必须是两个数字
	
	
from django.urls import path,register_converter
from app01.path_converts import MonthConverter
    
# 先注册转换器
register_converter(MonthConverter,'mon')

from app01 import views


urlpatterns = [
    path('articles/<int:year>/<mon:month>/<slug:other>/', 	views.article_detail, name='aaa'),

]

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

4.模型层里面1.X外键默认都是级联更新删除的,但是到了2.X和3.X中需要你自己手动配置参数
	models.ForeignKey(to='Publish')
	
	models.ForeignKey(to='Publish',on_delete=models.CASCADE...)
posted @ 2021-12-10 10:56  maju  阅读(107)  评论(0)    收藏  举报