Loading

Django路由层

一、MVC和MTV框架

MVC

  • M 代表模型(Model)
  • V 代表视图(View)
  • C 代表控制器(Controller)

Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的、松耦合的方式连接在一起,模型负责业务对象与数据库的映射(ORM),视图负责与用户的交互(页面),控制器接受用户的输入调用模型和视图完成用户的请求。

MTV

Django 的 MTV 模式本质上和MVC是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同,Django 的MTV分别是值:

  • M 代表模型(Model): 负责业务对象和数据库的关系映射(ORM)。
  • T 代表模板 (Template):负责如何把页面展示给用户(html)。
  • V 代表视图(View): 负责业务逻辑,并在适当时候调用Model和Template。

除了以上三层之外,还需要一个URL分发器,它的作用是将一个个URL的页面请求分发给不同的View处理,View 再调用相应的 Model 和 Template

二、URL配置

URL配置:它的本质是URL与要为该URL调用的视图函数之间的映射表。

Django 1.x版本

url()方法:普通路径和正则路径均可使用,需要自己手动添加正则首位限制符号。

注意:

  1. urlpatterns中的元素按照书写顺序从上往下逐一匹配,一旦匹配成功则不在继续
  2. 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)。
# urls.py文件
from django.contrib import admin
from django.conf.urls import url		# 2.x之后,用 url 需要引用
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

# 新增视图函数
def index(request):		# request 中包含请求信息
    return render(request, 'index.html')	# 返回的 HTML 页面

Django 2.x之后版本

path:用于普通路径,不需要自己手动添加正则首位限制符号,底层已经添加。

re_path:用于正则路径,需要自己手动添加正则首位限制符号。

# urls.py文件
from django.contrib import admin
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index),                # 新增-普通路径
    re_path(r'^books/\d{4}/$', views.books)     # 新增-正则路径
]

# -------------------------------------------------------------------

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

def index(request):
    return render(request, 'index.html')		# 返回的 HTML 页面

def books(request):
    return HttpResponse('Hello world')			# 响应信息

三、分组

分组就是需要直接从路径中取出参数,这就用到了正则表达式的分组功能了。

分组分为两种:无名分组与有名分组

无名分组

无名分组按照位置传参,需要一 一对应。

捕获URL中的值并以位置参数形式传递给视图

views 中除了request,其他形参的数量要与urls中的分组数量一致。

# ruls.py文件
from django.contrib import admin
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    re_path(r'^admin/', admin.site.urls),
    re_path(r'^books/(\d{4})/', views.books),
]

# ------------------------------------------------------------------

# views.py文件
from django.shortcuts import render, HttpResponse

def books(request, year):
    print(year)			# 一个形参代表路径中一个分组的内容,按顺序匹配
    return HttpResponse(f'Hello world {year}')

有名分组

语法:

(?P<组名>正则表达式)

捕获URL中的值并以关键字参数形式传递给视图。

有几个有名分组就要有几个关键字参数

# urls.py文件
from django.contrib import admin
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    re_path(r'^admin/', admin.site.urls),
    # 匹配成功的分组部分会以关键字参数(name_id = 匹配成功的数字)的形式传给视图函数。
    re_path(r'^books/(?P<name_id>\d{2})/', views.books),
]

# ------------------------------------------------------------------

# views.py文件
from django.shortcuts import render, HttpResponse

# 需要额外增加一个形参,形参名必须为 name_id
def books(request, name_id):
    return HttpResponse(f'Hello world {name_id}')

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

四、路由分发(include)

存在的问题:Django项目里有多个app共用一个 urls 容易造成混淆,后期维护不方便。

解决:使用路由分发(include),让每个app目录都单独拥有自己的 urls。

步骤:

1、在各自 app 目录下的创建 urls.py文件, 并对 urls.py 文件和 views.py 文件中写各自的路由和视图函数。

app01下的 urls.py 文件和 views.py 文件

# urls.py文件
from django.urls import path, re_path
from app01 import views		# 导入app01的views


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

# ------------------------------------------------------------------

# views.py文件
from django.shortcuts import render, HttpResponse

def index(request):
    return HttpResponse('app01页面')

app02下的 urls.py 文件和 views.py 文件

# urls.py文件
from django.urls import path, re_path
from app02 import views		# 导入app02的views


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

# ------------------------------------------------------------------

# views.py文件
from django.shortcuts import render, HttpResponse

def index(request):
    return HttpResponse('app02页面')

2、在项目名称目录下的 urls 文件里面,统一将路径分发给各个 app 目录。

from django.contrib import admin
from django.urls import path, re_path, include

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

    # 新增两条路由,不能以$结尾
    # include函数就是做分发操作的。
    re_path(r'^app01/', include('app01.urls')),
    re_path(r'^app02/', include('app02.urls')),
]

五、反向解析

如果路由 层的 url 发生变化,就需要取更改对应的视图层和模板层的 url ,非常麻烦,不便于维护。

所以可以利用反向解析,当路由层 url 发生变化,在视图层和模块层动态反向解析出更改后的 url ,避免修改操作

反向解析一般用在模板中的超链接以及视图中的重定向。

普通反向解析

登录成功跳转到 index.html 页面:如果 urls.py 文件中的 index 路径有所变化,views.py 文件中不需要更改index路径。

# urls.py文件
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    # 路径 login/的别名为login_page
    re_path(r'^login/', views.login, name='login_page'),
    # 路径 index/的别名为index_page
    re_path(r'^index/', views.index, name='index_page'),
]

# ------------------------------------------------------------------

# views.py文件
from django.shortcuts import render, reverse, redirect, HttpResponse

def login(request):
    # 当为GET请求时就返回登录页面
    if request.method == 'GET':
        return render(request, 'login.html')
    else:
        # post 请求时,就提取出请求数据。
        user = request.POST.get('username')
        pwd = request.POST.get('password')
        if user == 'xiaoyang' and pwd == '123':
            # 会对别名反向解析成路径/index/
            url = reverse('index_page')
            # 然后哦重定向到别名解析的路径
            return redirect(url)
        else:
            return HttpResponse('登录失败')

def index(request):
    return HttpResponse('登录成功!!!')

登录成功 HTML页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>登录页面</h1>

<!-- 这里的 "{% url 'login_page' %}" 相当于路径/login/-->
<form action="{% url 'login_page' %}" method="post">
    {% csrf_token %}    <!-- post提交需要做CSRF验证 -->
    用户名:<input type="text" name="username">
    密码:<input type="password" name="password">
    <input type="submit">
</form>

</body>
</html>

分组反向解析

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

无名分组:reverse ( "路由别名", arges = ( 符合正则匹配的参数 ) )

有名分组:reverse ( "路由别名", arges = { "分组名":"符合正则匹配的参数" } )

# urls.py文件
from django.urls import path, re_path
from app01 import views


urlpatterns = [
    # 无名分组
    re_path(r'^books/(\d{2})/', views.books, name='books_page'),
    # 有名分组
    re_path(r'^years/(?P<year>\d{4})/', views.years, name='years_page'),
]

# ------------------------------------------------------------------

# views.py文件
from django.shortcuts import render, reverse, redirect, HttpResponse

# 有名分组的反向解析
def years(request, year):
    url = reverse('years_page', kwargs={'year': year})
    
    # HTML 文件中:{% url 'years_page' 'year'=1234 %}
    return HttpResponse(url)

# 无名分组的反向解析
def books(request, ret):
    url = reverse('books_page', args=(ret,))
    
    # HTML 文件中:{% url 'books_page' 34 %}
    return HttpResponse(url)

六、名称空间

Django项目里有多个app,当在不同的 app 目录下的 urls.py 文件中定义了相同的路由别名 name 时,那么在反向解析时则会出现覆盖。

例如:

不管输入:http://127.0.0.1:8000/app01/index/ 还是 http://127.0.0.1:8000/app02/index/ 都得到的是 /app02/index/ app02路径的别名覆盖了app01 路径的别名

app01下的 urls.py 文件和 views.py 文件

# urls.py文件
from django.urls import path, re_path
from app01 import views

urlpatterns = [
    re_path(r'^index/', views.index, name='index_page'),
]

# ------------------------------------------------------------------

# views.py文件
from django.shortcuts import render, reverse, HttpResponse

def index(request):
    url = reverse('index_page')
    return HttpResponse(url)

app02下的 urls.py 文件和 views.py 文件

# urls.py文件
from django.urls import path, re_path
from app02 import views

urlpatterns = [
    re_path(r'^index/', views.index, name='index_page'),
]

# ------------------------------------------------------------------

# views.py文件
from django.shortcuts import render, reverse, HttpResponse

def index(request):
    url = reverse('index_page')
    return HttpResponse(url)

项目名称目录下的 urls 文件里面,统一将路径分发给各个 app 目录。

# urls.py文件
from django.urls import path, re_path, include

# 总路由表
urlpatterns = [
    re_path(r'^app01/', include('app01.urls')),
    re_path(r'^app02/', include('app02.urls')),
]

对于这种问题的解决方法就是避免使用相同的别名,如果要使用相同的别名,那就需要将别名放到不同的名称空间中去,这样就避免了即使出现了重复,彼此也不会冲突。

解决方法:

格式:

# 视图中的名称空间的方向解析
url=reverse('名称空间的名字:待解析的别名')
# 模板中的名称空间的反向解析
<a href="{% url '名称空间的名字:待解析的别名'%}">小杨</a>

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

项目名称目录下的 urls 文件里面,统一将路径分发给各个 app 目录。

# url.py文件
from django.urls import path, re_path, include

# 总路由表
urlpatterns = [
    # 给include传递一个元组,第一个是路由分发的地址,第二个是我们自定义的名称空间名字
    re_path(r'^app01/', include(('app01.urls', 'app01'))),
    re_path(r'^app02/', include(('app02.urls', 'app02'))),
]

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

app01下的 urls.py 文件和 views.py 文件

# urls.py文件
from django.urls import path, re_path
from app01 import views

urlpatterns = [
        re_path(r'^index/', views.index, name='index_page'),
]

# ------------------------------------------------------------------

# views.py文件
from django.shortcuts import render, HttpResponse, reverse

def index(request):
    # 解析的是app01下的别名‘index_page’
    url = reverse('app01:index_page')
    return HttpResponse(url)

app02下的 urls.py 文件和 views.py 文件

# urls.py文件
from django.urls import path, re_path
from app02 import views

urlpatterns = [
    re_path(r'^index', views.index, name='index_page'),
]

# ------------------------------------------------------------------

# views.py文件
from django.shortcuts import render, reverse, HttpResponse

def index(request):
    # 解析的是app02下的别名‘index_page’
    url = reverse('app02:index_page')
    return HttpResponse(url)

posted @ 2021-06-08 23:16  Mr-Yang`  阅读(126)  评论(0编辑  收藏  举报