Django的路由系统
【1】什么是URL配置(URLconf)
- URL调度器 | Django 文档 | Django (djangoproject.com)
- URL配置(URLconf)就像Django 所支撑网站的目录。
- 它的本质是URL与要为该URL调用的视图函数之间的映射表。
- 你就是以这种方式告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码。
【2】基本语法
(1)Django1.x语法
from django.conf.urls import url
urlpatterns = [
url(正则表达式, views视图函数,参数,别名),
]
from django.conf.urls import url
urlpatterns = [
url(r"^index/", views.index,参数,别名),
]
(2)Django2.x+
from django.urls import path
from django.urls import re_path
from . import views
urlpatterns = [
re_path(r"^index/",views.index),
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),
]
- 注意
- 要从 URL 中取值,使用尖括号。
- 捕获的值可以选择性地包含转换器类型。比如,使用
<int:name>
来捕获整型参数。如果不包含转换器,则会匹配除了/
外的任何字符。 - 这里不需要添加反斜杠,因为每个 URL 都有。比如,应该是
articles
而不是/articles
。
(3)参数说明
from django.conf.urls import url
urlpatterns = [
path(路径名, views视图函数,参数,别名),
]
- 路径名:
- 在浏览器端口后请求的路径名
- views视图函数:
- 一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串
- 参数:
- 可选的要传递给视图函数的默认参数(字典形式)
- 别名:
- 一个可选的name参数
【3】请求示例
/articles/2005/03/
会匹配 URL 列表中的第三项。Django 会调用函数views.month_archive(request, year=2005, month=3)
。/articles/2003/
将匹配列表中的第一个模式不是第二个,因为模式按顺序匹配,第一个会首先测试是否匹配。请像这样自由插入一些特殊的情况来探测匹配的次序。在这里 Django 会调用函数views.special_case_2003(request)
/articles/2003
不匹配任何一个模式,因为每个模式要求 URL 以一个斜线结尾。/articles/2003/03/building-a-django-site/
会匹配 URL 列表中的最后一项。Django 会调用函数views.article_detail(request, year=2003, month=3, slug="building-a-django-site")
。
【4】使用正则表达式
- 如果路径和转化器语法不能很好的定义你的 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),
]
- 这实现了与前面示例大致相同的功能,除了:
- 将要匹配的 URLs 将稍受限制。比如,10000 年将不在匹配,因为 year 被限制长度为4。
- 无论正则表达式进行哪种匹配,每个捕获的参数都作为字符串发送到视图。
- 当从使用
path()
切换到re_path()
(反之亦然),要特别注意,视图参数类型可能发生变化,你可能需要调整你的视图。
【一】路由匹配
【1】路径参数相似
# 路由匹配
path('test',views.test),
path('testadd',views.testadd),
无法跳转到
testadd
- url方法第一个参数是路径参数
- 只要第一个参数能够匹配到内容,就会立刻停止匹配,执行视图函数
【2】解决路径参数相似问题
# 路由匹配
path('test'/,views.test),
path('testadd/',views.testadd),
- 在尾部加一个/
- 在输入url的时候会默认加一个/
- 一次匹配不行,那就加一个/ 再尝试一次
【3】路由系统自动添加 /
- 在配置文件中,有一个参数可以帮助我们干这件事
APPEND_SLASH = True
- 当设置为
True
时,如果请求的 URL 不符合 URLconf 中的任何模式,并且不以斜线结尾,则会发出一个 HTTP 重定向到相同的URL,并附加一个斜线。 - 注意,重定向可能会导致 POST 请求中提交的任何数据丢失。
APPEND_SLASH
的配置只有在安装了CommonMiddleware
的情况下才会使用(参见 中间件)。也请参见PREPEND_WWW
。
【4】完整的路由匹配
- 这才是完整版的路由匹配格式
path('test/', views.test),
- 匹配首页的路由格式
path('', views.test),
【二】无名分组
- 分组就是将某段正则表达式用
()
括起来
# 无名分组
re_path(r'^text/(\d+)/', views.test),
def test(request,data):
print(data)
return HttpResponse('test')
- 无名分组就是将括号内正则表达式匹配到的内容当做位置参数传给后面的视图函数
【三】有名分组
- 可以给正则表达式起一个别名
# 有名分组
re_path(r'^testadd/(?P<year>)\d+',views.testadd),
def testadd(request,year):
print(year)
return HttpResponse("testadd")
- 有名分组就是将括号内正则表达式匹配到的内容当做关键字参数传给后面的视图函数
【四】无名有名混用
- 无名分组和有名分组不能混用
# 无名有名混合使用
re_path(r'^index/(\d+)/(?P<year>\d+)/', views.index),
# 访问路由获取不到 (\d+) 的内容
- 但是同一个分组可以使用多次
re_path(r'^test/(\d+)/(\d+)/',views.test),
re_path(r'^test/(?P<xxx>\d+)/(?P<year>\d+)/',views(r'^test/(?P<xxx>\d+)/(?P<year>\d+)/',viewsre_path.test),
def index(request,args,year):
return HttpResponse("index")
【五】反向解析
【0】反向解析的本质
-
先给路由起一个别名,然后,通过一些方法去解析别名,可以得到这个别名对应的路由地址
-
先给路由与视图函数起一个别名
re_path(r'index/(\d+)/(\d+)/', views.func,name="index"),
【1】后端反向解析
from django.shortcuts import render, HttpResponse,reverse
def home(request):
reverse('index', args=(1, 2)))
【2】前端反向解析
<a href="{% url 'index' 123 234 %}">111</a>
【3】无名分组的反向解析
- 有名分组和无名分组的反向解析是一样的
# 后端
reverse("路由映射的别名",args=(额外的参数,))
# 前端
<a href="{% url '路由映射的别名' 额外的参数 %}">跳转链接</a>
【六】路由分发
【1】前言
- Django每一个应用都可以拥有属于自己的
- templates文件夹
- urls.py
- static文件夹
- 正是基于上述的特点,Django可以很好的做到自己的分组开发(每个人只写自己的app)
- 最后只需要将所有的app拷贝到新的文件,并将这些APP注册到配置文件中,然后再利用路由分发的特点,将所有的APP整合起来
- 当一个项目中的URL特别多的时候,总路由urls.py的代码非常冗余而且不好维护,这个时候就可以利用路由分发来减轻总路由的压力
- 利用路由分发之后,总路由不再干预路由与视图函数的直接对应关系
- 而是做一个分发处理
- 识别当前的url是属于哪个应用下的,直接分发给对应的应用去处理
【2】语法
- 通常,我们会在每个app里,各自创建一个urls.py路由模块,然后从根路由出发,将app所属的url请求,全部转发到相应的urls.py模块中。
(1)方式一:转发app下的urls
- 例如,下面是Django网站本身的URLconf节选。 它包含许多其它URLconf:
from django.urls import include, path
urlpatterns = [
path('community/', include('aggregator.urls')),
path('contact/', include('contact.urls')),
]
- 路由转发使用的是
include()
方法,需要提前导入,它的参数是转发目的地路径的字符串,路径以圆点分割。 - 每当Django 遇到
include()
时,它会去掉URL中匹配的部分并将剩下的字符串发送给include的URLconf做进一步处理,也就是转发到二级路由去。
(2)方式二:转发url列表
- 另外一种转发其它URLconf的方式是使用一个path()实例的列表。 例如,下面的URLconf:
from django.urls import include, path
from apps.main import views as main_views
from credit import views as credit_views
extra_patterns = [
path('reports/', credit_views.report),
path('reports/<int:id>/', credit_views.report),
path('charge/', credit_views.charge),
]
urlpatterns = [
path('', main_views.homepage),
path('help/', include('apps.help.urls')),
path('credit/', include(extra_patterns)),
]
- 在此示例中,
/credit/reports/
URL将由credit_views.report()
视图处理。 - 这种做法,相当于把二级路由模块内的代码写到根路由模块里一起了,不是很推荐。
【3】示例
(1)总路由
from django.contrib import admin
from django.urls import path, re_path, include
import app01
import app02
urlpatterns = [
path('admin/', admin.site.urls),
path('app01/', include(app01.urls)),
path('app02/', include(app02.urls)),
]
(2)子路由
# 自己的逻辑路径
from django.contrib import admin
from django.urls import path, re_path, include
from app01 import login
urlpatterns = [
path('login/', views.login),
]
【4】传递额外的参数给include()
- 类似上面,也可以传递额外的参数给include()。参数会传递给include指向的urlconf中的每一行。
- 例如,下面两种URLconf配置方式在功能上完全相同:
django/urls.py
# 方式一:将所有的视图函数都放在一个urls.py文件下
from django.contrib import admin
from django.urls import path, include
from app01.views import register
from app01.views import register_two as register_two_01
from app02.views import register_two as register_two_02
urlpatterns = [
path('admin/', admin.site.urls),
# 注册路由和视图函数的映射关系
path('register/', register),
path('app01/register_two',register_two_01),
path('app02/register_two',register_two_02)
]
方式二:
from django.urls.conf import include
urlpatterns = [
path('app01/', include('app01.urls', namespace='app01')),
path('app02/', include('app02.urls', namespace='app02')),
]
# app01
app_name ='app01'
urlpatterns =[
path('register_two/',views.register_two)
]
# app02
app_name ='app02'
urlpatterns =[
path('register_two/',views.register_two)
]
【七】名称空间
【1】引入
- 当多个应用出现相同的别名,反向解析不会自动识别应用前缀
- 正常情况下的反向解析是不能识别前缀的
【2】使用
# 在主路由上注册
path("app01/", include(("app01.urls", "app01"), namespace="app01")),
path("app02/", include(("app02.urls", "app02"), namespace="app02"))
# 在子路由 app01
path("login/", views.login, name="login")
# app01/login/
# app02/login/
# 在前端解析路由地址的时候
{% url "app01:login" %}
{% url "app02:login" %}
【八】伪静态
- 静态网页
- 数据是写死的
- 伪静态
- 将一个动态网页伪装成静态网页
- 伪装的目的在于增大本网站的seo查询力度
- 并且增加搜索引擎收藏本网页的概率
- 搜索引擎本质上就是一个巨大的爬虫程序
总结:无论怎么优化,怎么处理,始终还是干不过RMB玩家
【九】虚拟环境
- 在正常开发中,我们会给每一个项目独有的解释器环境
- 该环境内只有该项目用到的模块,用不到的一概不装
【1】虚拟环境
- 每创建一个虚拟环境就类似于重新下载了一个纯净的python解释器
- 但是虚拟器不建议下载太多,创建虚拟环境是需要消耗磁盘空间的
【2】模块管理文件
- 每个项目都需要用到很多模块,并且每个模块的版本可能是不一样的
- 这种情况下我们会给每一个项目配备一个requirements.txt文件,里面存放着我们这个项目所安装的所有模块及版本
- 只需要一条命令即可安装所有模块及版本
【3】模块文件导出和安装
(1)导出项目模块文件
pip freeze > requirements.txt
- 这行命令的含义是 "freeze"(冻结)当前环境中的所有已安装包及其版本信息,并将其输出重定向到
requirements.txt
文件中。 - 执行此命令后,
requirements.txt
将列出你项目所需的全部依赖及其对应版本号。
(2)安装项目模块文件
pip install -r requirements.txt
- 这将按照
requirements.txt
中列出的顺序及版本要求安装相应的 Python 包。
【十】Django版本的区别
【1】路由匹配规则
- Django1.x路由层使用的是url方法
- 在Django2.x版本以后在路由层使用的是path方法
- url() 第一个参数支持正则
- path() 第一个参数不支持正则,写什么就匹配到什么
【2】正则匹配规则
- 在Django2.x以后也可以使用正则表单式,但是使用的方法是re_path
from django.urls import path, re_path
re_path(r'^fuc/(?P<year>\d+)', views.func)
# 等价于
url(r'^fuc/(?P<year>\d+)', views.func)
【3】路径转换器
(1)初识
- 虽然path不支持正则,但是其内部支持五种转换器
path('index/<int:id>/',views.index)
# 将第二个路由里面的内容先转成整型,然后以关键字的形式传递给后面的视图函数
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),
]
(2)五种转换器
- str
- 匹配除了 '/' 之外的非空字符串。
- 如果表达式内不包含转换器,则会默认匹配字符串。
- int
- 匹配 0 或任何正整数。返回一个 int 。
- slug
- 匹配任意由 ASCII 字母或数字以及连字符和下划线组成的短标签。
- 比如,building-your-1st-django-site 。
- uuid
- 匹配一个格式化的 UUID 。为了防止多个 URL 映射到同一个页面,必须包含破折号并且字符都为小写。
- 比如,075194d3-6885-417e-a8a8-6c931e272f00。返回一个 UUID 实例。
- path
- 匹配非空字段,包括路径分隔符 '/' 。
- 它允许你匹配完整的 URL 路径而不是像 str 那样匹配 URL 的一部分。
【4】自定义转换器
- Django支持自定义转换器
(1)初识
- 转换器是一个类,包含如下内容:
- 字符串形式的
regex
类属性。 to_python(self, value)
方法,用来处理匹配的字符串转换为传递到函数的类型。如果没有转换为给定的值,它应该会引发ValueError
。ValueError
说明没有匹配成功,因此除非另一个 URL 模式匹配成功,否则会向用户发送404响应。- 一个
to_url(self, value)
方法,它将处理 Python 类型转换为字符串以用于 URL 中。如果不能转换给定的值,它应该引发ValueError
。ValueError
被解释为无匹配项,因此reverse()
将引发NoReverseMatch
,除非有其他 URL 模式匹配。
- 字符串形式的
Changed in Django 3.1:
支持引发
ValueError
以表示没有匹配项被添加。
(2)自定义转换器示例
-
在app01下新建文件
path_ converters.py
(文件名可以随意命名)class FourDigitYearConverter: # 此属性名为正则表达式(regex),用于匹配四位数的年份字符串 regex = r'[0-9]{4}' def to_python(self, value): """ 将接收到的字符串值解析为Python整数类型表示四位数的年份。 示例:输入 "2024" 会转换为 2024 """ return int(value) def to_url(self, value): """ 根据给定四位数年份(value)将其格式化为URL安全的四位数字形式,例如 "2024" -> "2024". 注意这里的 "匹配的regex是四个数字" 应更改为 "此方法针对四位数的年份字符串" 例如:输入 "2024" 会返回 "2024", 保持四位数且无前导零。 """ return '%04d' % value
(3)使用自定义转换器
-
在urls.py中,使用
register_converter()
将其注册到URL配置中:# 引入所需的模块和从当前目录下的views.py导入视图函数 from django.urls import path, register_converter from . import converters, views # 在Django应用中注册自定义的日期转换器类,即位于converters文件中的FourDigitYearConverter # 并为其指定别名 'yyyy', 这个别名可以方便地在URL模式中引用该转换器 register_converter(converters.FourDigitYearConverter, 'yyyy') # 配置URL模式,其中: # path('/articles/2003/', views.special_case_2003) - 特殊情况处理,当访问 '/articles/2003/' 时调用 special_case_2003 视图函数 # path('articles/<yyyy:year>/', views.year_archive) - 处理常规年份归档页面请求, # 其中 '<yyyy:year>' 表示 URL 模板中 '<year>' 前面带有 'yyyy' 转换器别名, # Django将在实际匹配到的 URL 字符串中找到四位数的年份并传递给 views.year_archive 视图函数作为参数 urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<yyyy:year>/', views.year_archive), ... ]
(4)转换器使用示例
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是两个数字,返回的结果也必须是两个数字
urls.py
from django.contrib import admin
from django.urls import path
from app01 import views
register_converter(converters.FourDigitYearConverter, 'yyyy')
urlpatterns = [
path('admin/', admin.site.urls),
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
]
-
app01/views.py
from django.shortcuts import render,HttpResponse,reverse from django.urls import reverse def article_detail(request, year, month, other): print(year, type(year)) print(month, type(month)) print(other, type(other)) print(reverse('article_detail', args=(1988, 12, 'hello'))) # 反向解析结果/articles/1988/12/hello/ ''' 2009 <class 'int'> 12 <class 'int'> hello <class 'str'> /articles/1988/12/hello/ ''' return HttpResponse('xxxx')
-
测试
# 在浏览器输入http://127.0.0.1:8000/articles/2009/12/hello/ # path会成功匹配出参数year=2009,month=12,other='hello'传递给函数article_detail
# 在浏览器输入http://127.0.0.1:8000/articles/2009/123/hello/ # path会匹配失败,因为我们自定义的转换器mon只匹配两位数字,而对应位置的123超过了2位
【十一】Django返回JSON格式的数据两种方式
【1】第一种方式json模块序列化
def register_two(request):
data = {"username": "zhangsan", "password": "123321"}
# 先用json序列化
data = json.dumps(data)
return HttpResponse(data)
【2】第二种
def register_two(request):
data = {"username": "zhangsan", "password": "123321"}
resource = JsonResponse(data)
return resource
【十二】form表单上传下载文件数据
form表单上传数据以及后端如何获取
【1】数据准备
- 路由urls.py
from app01.views import register, register_two
urlpatterns = [
path('admin/', admin.site.urls),
# 注册路由和视图函数的映射关系
path('register/', register),
path('app01/register_two/', register_two),
]
- views
def register_two(request):
if request.method == "POST":
user_data = request.POST
print(user_data)
avatar = user_data.get("avatar")
print(avatar,type(avatar))
return HttpResponse('ok')
return render(request,"register_two.html")
- 前端register_two.py
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这是主页面</h1>
<form action="" method="post">
<div>username: <input type="text" name="username"></div>
<div>password: <input type="password" name="password"></div>
<div>avatar: <input type="file" name="avatar"></div>
<div><input type="submit"></div>
</form>
</body>
</html>
# 优化-添加form表单参数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这是主页面</h1>
<form action="" method="post" enctype="multipart/form-data">
<div>username: <input type="text" name="username"></div>
<div>password: <input type="password" name="password"></div>
<div>avatar: <input type="file" name="avatar"></div>
<div><input type="submit"></div>
</form>
</body>
</html>
- 后端没有了avatar对应的键和值
【2】优化-后端获取form的表单文件数据
def register_two(request):
if request.method == "POST":
user_data = request.POST
# file = request.FILES
file_obj = request.FILES.get("avatar")
# <QueryDict: {'username': ['zhang'], 'password': ['1']}>
print(file_obj,type(file_obj)) # <MultiValueDict: {'avatar': [<TemporaryUploadedFile: 1709436763121.jpg (image/jpeg)>]}>
# 1709436763121.jpg <class 'django.core.files.uploadedfile.TemporaryUploadedFile'>
file_name = file_obj.name
print(file_name)
# 1709436763121.jpg
# 保存文件到本地
with open(f'{file_name}',mode='wb') as fp:
for line in file_obj.chunks():
fp.write(line)
# 获取文件名
# file_name = file.name
# print(user_data)
avatar = user_data.get("avatar")
return HttpResponse('ok')
return render(request, "register_two.html")
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这是主页面</h1>
<form action="" method="post" enctype="multipart/form-data">
<div>username: <input type="text" name="username"></div>
<div>password: <input type="password" name="password"></div>
<div>avatar: <input type="file" name="avatar"></div>
<div><input type="submit"></div>
</form>
</body>
</html>
【十三】request对象方法补充
【1】request.method
request.method
:该方法返回客户端用于发起请求的HTTP方法。- 例如,可以是'GET'、'POST'、'PUT'、'DELETE'等。
- 您可以使用该方法来确定请求的类型,并相应地执行特定操作。
【2】request.POST
request.POST
:该属性是一个类似字典的对象,包含了请求中通过POST方法发送的所有参数。- 这些参数通常是通过HTML表单发送的。
- 您可以使用参数的名字作为键来访问单个参数,例如
request.POST['username']
。
【3】request.GET
request.GET
:类似于request.POST
,该属性包含了请求中通过GET方法发送的所有参数。- 这些参数通常会附加在URL之后,以问号分隔。
- 您可以使用参数的名字作为键来访问单个参数,例如
request.GET['page']
。
【4】request.FILES
request.FILES
:该属性是一个类似字典的对象,包含了请求中通过文件上传组件发送的所有文件。- 当表单中包含文件上传字段时,通过
request.FILES
可以访问上传的文件。 - 您可以使用文件的名字作为键来访问单个文件,例如
request.FILES['file']
。
【5】request.path
只能获取到路由地址,无法获取到参数
request.path
:该属性表示请求URL中的路径部分。- 它包含在域名之后,在任何查询参数之前。
- 例如,如果请求的URL是"http://example.com/foo/bar/",那么
request.path
将为"/foo/bar/"。
【6】request.path_info
只能获取到路由地址,无法获取到参数
- 用于表示请求URL的路径部分,不包括域名和查询参数。
- 与
request.path
相比,request.path_info
更加原始和未经解析。 - 它保留了URL路径中的任何编码、特殊字符或斜杠等信息。
- 例如,对于以下请求URL:"http://example.com/foo/bar/?page=2",
request.path_info
的值将是 "/foo/bar/"。 - 通常情况下,您可以使用
request.path
来获取丢弃域名后的路径,而使用request.path_info
来获取原始的、未解析的路径。这在某些情况下非常有用,例如当您需要对URL进行一些自定义操作或路由处理时。
【7】request.get_full_path()
即能获取到路由地址又能获取到完整的路由地址后面的参数
request.get_full_path()
:该方法返回请求URL的完整路径,包括路径部分和任何查询参数。- 当您需要将完整URL作为字符串使用时,这个方法非常有用。
- 例如,如果请求的URL是"http://example.com/foo/bar/?page=2",
request.get_full_path()
将返回"/foo/bar/?page=2"。
【十四】CBV和FBV
- 视图函数既可以是函数也可以是类
- 我们之前写过的都是基于函数的view,就叫FBV。
- 还可以把view写成基于类的。
【1】FBV
# FBV版添加班级
# 导入模块
from django.shortcuts import render, redirect
from . import models
# FBV版添加班级
def add_class(request):
# 处理 POST 请求
if request.method == "POST":
# 从表单中获取班级名称
class_name = request.POST.get("class_name")
# 使用 Django 模型创建新的班级对象并保存到数据库
models.Classes.objects.create(name=class_name)
# 重定向到班级列表页面
return redirect("/class_list/")
# 处理 GET 请求,渲染添加班级的表单页面
return render(request, "add_class.html")
【2】CBV
- class类和路由之间的映射关系
(1)路由
- 在视图函数引入一个类
- 在路由中注册路由和视图类之间的映射关系
# CBV 路由 - 根据请求方式的不同选择不同的入口动作
path('login/', views.MyLogin.as_view())
(2)视图
# 需要导入类
from django.views import View
# 重写一个类,继承View
class Register(View):
# get 请求
def get(self, request, *args, **kwargs):
print(request.GET)
# 重写get请求逻辑
return render(request,'register_two.html')
# post 请求
def post(self, request, *args, **kwargs):
# 处理POST请求携带的请求体数据
print(request.POST)
return HttpResponse("ok")
(3)小结
- 选择使用 FBV 还是 CBV 取决于具体的需求和个人偏好。
- FBV 相对简单直观,适合编写简单的视图逻辑;
- 而 CBV 可以通过继承和重写类来实现代码复用和可扩展性,适用于复杂的视图处理场景。
- 在实际开发中,可以根据需求选择适合的方式来编写视图处理函数或类。
【十五】CBV源码剖析
class View:
# http_method_names : 存放了我们常用的请求方式
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
# 绑定给类的函数,绑定方法
@classonlymethod
def as_view(cls, **initkwargs):
# **initkwargs : 可变长关键字参数
for key in initkwargs:
# print(f"key :>>>> {key}") # key :>>>> pattern_name
# 可变长关键字参数中的每一个键值对
# 我们自己写视图类中没有定义过 http_method_names ,只能从父类 View 里面找
if key in cls.http_method_names:
#
raise TypeError(
'The method name %s is not accepted as a keyword argument '
'to %s().' % (key, cls.__name__)
)
# hasattr : 获取到当前对象中的属性
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
# 上面都没有报错,走到了 view 函数里面
def view(request, *args, **kwargs):
# 获取到当前类的对象 这个 cls ---> Register
self = cls(**initkwargs)
# 获取到了一个 当前的get方法
self.setup(request, *args, **kwargs)
# 必须接受一个位置参数叫request
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
# 触发了 dispatch 方法 ---> 获取到了get函数的返回值
# render
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
def setup(self, request, *args, **kwargs):
# self : 当前Register实例化出来的对象
# 有GET方法 并且 没有 head属性
if hasattr(self, 'get') and not hasattr(self, 'head'):
# self.head 变成了我自己写的 GET 方法
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
# 走到了dispatch方法
def dispatch(self, request, *args, **kwargs):
# request.method.lower() :当前请求方法转全小写
if request.method.lower() in self.http_method_names:
# 获取到了当前对象中的get方法的函数内存地址
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
# get方法的函数内存地址调用
return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs):
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())
def options(self, request, *args, **kwargs):
"""Handle responding to requests for the OPTIONS HTTP verb."""
response = HttpResponse()
response.headers['Allow'] = ', '.join(self._allowed_methods())
response.headers['Content-Length'] = '0'
return response
def _allowed_methods(self):
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
- 当我们启动Django项目时
- 会自动触发路由中的方法,调用 as_view 方法并自执行
- 在执行后我们查看 as_view 方法的源码 发现
- 在依次给我们的对象赋值后,最终返回了一个自执行的 dispatch 方法
- 于是我们又去查看了 dispatch 方法
- 在 dispatch 内部 ,先是将请求方式转换并进行校验
- 然后开始校验需要调用的方法的调用位置,校验成功并拿到需要执行的方法执行
- 在自己写的类中如果有相关的方法,会首先调用我们重写的类方法,并返回执行结果
- 如果自己的类里面没有该方法 会去自己的父类中调用 父类的方法
- 如果父类 以及 基类 都找不到则报错,抛出异常
- 如果自己的类里面没有该方法 会去自己的父类中调用 父类的方法
【十六】CBV添加装饰器
【1】给FBV加装饰器
- FBV本身就是一个函数,所以和给普通的函数加装饰器无差:
def timer(func):
def inner(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
print(f"总耗时:>>>>{time.time() - start_time}s")
return res
return inner
@timer
def register(request):
time.sleep(2)
return HttpResponse("ok")
【2】CBV添加装饰器的三种方式
- 类中的方法与独立函数不完全相同,因此不能直接将函数装饰器应用于类中的方法 ,我们需要先将其转换为方法装饰器。
- Django中提供了method_decorator装饰器用于将函数装饰器转换为方法装饰器。
(1)第一种
from django.utils.decorators import method_decorator
def timer(func):
def inner(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
print(f"总耗时:>>>>{time.time() - start_time}s")
return res
return inner
@timer
def register(request):
time.sleep(2)
return HttpResponse("ok")
# 第一种
class Login(View):
@timer
def get(self, request, *args, **kwargs):
time.sleep(2)
return HttpResponse("Login")
(2)第二种
from django.utils.decorators import method_decorator
# 第二种方式 : 借助Django内置的公共函数 method_decorator
class Login(View):
@method_decorator(timer)
def get(self, request, *args, **kwargs):
time.sleep(2)
return HttpResponse("Login")
(3)第三种
from django.utils.decorators import method_decorator
def timer(func):
def inner(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
print(f"总耗时:>>>>{time.time() - start_time}s")
return res
return inner
class Login(View):
@timer
def register(request):
time.sleep(2)
return HttpResponse("ok")
# 第三种方式:重写 dispatch 方法做拦截
def get(self,request,*args,**kwargs):
time.sleep(2)
return HttpResponse("login")
def dispatch(self,request,*args,**kwargs):
start_time = time.time()
# 可以什么都不写
obj = super().dispatch(request,*args,**kwargs)
# 可以放自己的类名和自己的对象 self
obj = super(Login,self).dispatch(request, *args, **kwargs)
print(f"总耗时 :>>>> {time.time() - start_time} s ")
return obj
标签:进阶,views,self,request,视图,Django,import,path,路由
From: https://www.cnblogs.com/Fredette/p/18064694