四:django 路由,高级,请求,响应,视图
day04---Django框架学习初阶(三)
有名分组与无名分组的反向解析
无名分组的反向解析
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index/(\d+)/$',views.index,name='app01_index'),
url(r'^home/(\d+)/$',views.home,name='app01_home')
]
针对无名分组:
我们在浏览器中输入http://127.0.0.1:9090/index/1/
# 在试图函数views中反向解析路径
from django.shortcuts import reverse
from django.shortcuts import redirect
def home(request):
return redirect(reverse('app01_index',args=(1,)))
# 在模板文件index.html中反向解析路径
<html>
<head>
...
</head>
<body>
<form action="{% url 'app01_index' 1 %}" method="post">
{% crsf_token %}
...
</form>
</body>
</html>
有名分组的反向解析
from django.conf.urls import url
from app02 import views
urlpatterns = [
url(r'^index/(?p<id>\d+)/$',views.index,name='app02_index'),
url(r'^home/(?p<id>\d+)/$',views.home,name='app02_home')
]
针对有名分组:
我们在浏览器中输入http://127.0.0.1:9090/index/1/
# 在视图函数views中反向解析路径
from django.shortcuts import reverse
from django.shortcuts import redirect
def home(request):
return redirect(reverse('app02_index',kwargs={'id':1}))
# 在模板文件中反向解析路径
<html>
<head>
...
</head>
<body>
<form action="{% url 'app02_index' id=1 %}" method="post">
{% crsf_token %}
...
</form>
</body>
</html>
路由分发
"""
django的每一个应用都可以有自己的templates文件夹urls.py static文件夹,
正是基于上述的特点,django能够非常好的做到分组开发(每个人只写自己的app)。
当一个django项目中的url特别多的时候 总路由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')),
url(r'^app02/',include('app02.urls')),
]
"""
注意:总路由里面的url千万不能以$结尾
"""
子路由app01/urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index/(\d+)/$',views.index,name='app01_index'),
url(r'^home/(\d+)/$',views.home,name='app01_home')
]
子路由app02/urls.py
from django.conf.urls import url
from app02 import views
urlpatterns = [
url(r'^index/(?p<id>\d+)/$',views.index,name='app02_index'),
url(r'^home/(?p<id>\d+)/$',views.home,name='app02_home')
]
名称空间(了解)
# 当我们在项目下创建多个app,并且每个app都针对匹配的路径设置了别名,当别名存在重复,那么反向解析时会存在覆盖现象。
"""
解决上述的问题,主要有两种思路:
(1)规避这种出现别名重复的风险,我们可以在别名前面加上app名称加以区别,如app01_index;
(2)在总路由中通过名称空间加以区别。
"""
方法一:
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index/(\d+)/$',views.index,name='app01_index'),
url(r'^home/(\d+)/$',views.home,name='app01_home')
]
方法二:
总路由
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','app01'))),
url(r'^app02/',include(('app02.urls','app02'))),
]
"""
传给include功能一个元组,第一个参数是路由分发地址,第二参数是指名称空间NameSpace
也可以直接这样写:
url(r'^app01/',include('app01.urls',namespace='app01'))
"""
# 在视图函数views解析时
from django.shortcuts import redirect
from django.shortcuts import reverse
def home(request):
return redirct(reverse('app01:index',args(1,)))
# 在模板文件app02_index.html解析时:
<html>
<head>
...
</head>
<body>
<form action="{% url 'app02:index' id=1 %}" method="post">
{% crsf_token %}
...
</form>
</body>
</html>
总结+补充
1.在视图函数中基于名称空间的反向解析,其用法如下
reverse('名称空间的名字:待解析的别名')
2.在模板中的基于名称空间的反向解析,其用法如下:
<a href="{% url '名称空间的名字:待解析的别名' %}">妹子图</a>
伪静态(了解)
伪静态:就是将一个动态网页伪装成静态网页,如https://www.cnblogs.com/surpass123/p/12969043.html
"""
伪装的目的:
(1)在于增大本网站的seo查询力度;
(2)并且增加搜索引擎收藏本网上的概率
"""
实现:
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index.html',views.index,name='app01_index'),
url(r'^home.html',views.home,name='app01_home')
]
虚拟环境(了解)
"""
【拓展】:
在正常开发中 我们会给每一个项目配备一个该项目独有的解释器环境,该环境内只有该项目用到的模块,其他用不到一概不装;
通常会给每个项目配置一个requirements.txt文件,里面书写了该项目所有的模块即版本,我们只需要直接输入一条命令即可一键安装所有模块即版本
"""
Django版本之间的区别
- 路由层的使用方法
"""
Django 1.x 使用的url()方法来正则匹配路径,Django 2.x和Django 3.x推荐使用path()方法来配置路径,同时也提供了re_path()方法来正则匹配路径。
url():第一个参数支持正则
path():第一个参数不支持正则,写什么就匹配什么
re_path就等价于Django 1.x 版本中的url
from 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'))),
]
"""
重要:path功能主要是用来解决数据类型转换问题与正则表达式冗余问题。
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),
]
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。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符
path虽然不支持正则,但是它内部支持5中转换器
str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int,匹配正整数,包含0。
slug,匹配字母、数字以及横杠、下划线组成的字符串。
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
除了原有默认的5个转换器外,还支持自定义的转换器
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')
在模型层里面1.X外键默认都是级联更新删除的,但是到了2.X和3.X中需要你自己手动配置参数。
1.x
models.ForeignKey(to='Publish')
2.x或3.x
models.ForeignKey(to='Publish',on_delete=models.CASCADE...)
视图层
三大神器
"""
HttpResponse:返回字符串类型
render: 返回html页面,并且在返回给浏览器之前,还可以html文件传值
redirect:重定向
"""
这三者返回的都是一个HttpResponse对象
# render简单内部实现原理
from django.template import Template,Context
res = Template('<h1>{{ user }}</h1>')
con = Context({'user':{'username':'jason','password':123}})
ret = res.render(con)
print(ret)
return HttpResponse(ret)
JsonResponse类
json格式的数据有什么用?
前后端数据的交互要使用json作为过渡,实现跨语言的传输数据
# 序列化与反序列化
前端 后端
JSON.stringify() json.dumps()
JSON.parse() json.loads()
示例:
import json
def test(request):
user_dict = {'username': '天王盖地虎', 'password': 'jason一米五'}
user_str = json.dumps(user_dict)
return HttpResponse(user_str)
可以看到,在页面上显示的是Unicode,我们可以通过设置dumps的ensure_ascii参数,设置其为False
user_str = json.dumps(user_dict,ensure_ascii=False)
# 刷新一下页面
django中提供了一种JsonResponse类帮我们实现上述的流程
from django.http import JsonResponse
def test(request):
user_dict = {'username': '天王盖地虎', 'password': 'jason一米五'}
return JsonResponse(user_dict)
发现又显示的unicode,这时候我们看JsonResponse源码
class JsonResponse(HttpResponse):
"""
An HTTP response class that consumes data to be serialized to JSON.
:param data: Data to be dumped into json. By default only ``dict`` objects
are allowed to be passed due to a security flaw before EcmaScript 5. See
the ``safe`` parameter for more information.
:param encoder: Should be an json encoder class. Defaults to
``django.core.serializers.json.DjangoJSONEncoder``.
:param safe: Controls if only ``dict`` objects may be serialized. Defaults
to ``True``.
:param json_dumps_params: A dictionary of kwargs passed to json.dumps().
"""
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
json_dumps_params=None, **kwargs):
if safe and not isinstance(data, dict):
raise TypeError(
'In order to allow non-dict objects to be serialized set the '
'safe parameter to False.'
)
if json_dumps_params is None:
json_dumps_params = {}
kwargs.setdefault('content_type', 'application/json')
data = json.dumps(data, cls=encoder, **json_dumps_params)
super(JsonResponse, self).__init__(content=data, **kwargs)
其中,
"""
param json_dumps_params: A dictionary of kwargs passed to json.dumps().
"""
data = json.dumps(data, cls=encoder, **json_dumps_params)
可以发现JsonResponse是内部是调用了json的dumps方法,它通过json_dumps_params来控制dunps内的传入参数,到这步,问题基本可以得到解决了。
return JsonResponse(user_dict,json_dumps_params={'ensure_ascii':False})
**json_dumps_params ==> ensure_ascii=False
那么,能否将列表序列化了?
如下列表,beast = ['姜春',' 李乾新','王林']
def test(request):
beast = ['姜春','李乾新','王林']
return JsonResponse(beast)
运行后报错了,提示如下:
TypeError: In order to allow non-dict objects to be serialized set the safe parameter to False.
为了使非字典对象(默认是字典对象)可以被序列化,请将safe参数设置为False
def test(request):
beast = ['姜春','李乾新','王林']
return JsonResponse(beast,safe=False,json_dumps_params={'ensure_ascii':False})
form表单上传文件
form表单上传文件
"""
1.method必须指定成post
2.enctype必须换成formdata
"""
def upload_files(request):
if request.method == 'POST':
file_obj = request.FILES.get('file') # 获取文件对象
with open(file_obj.name, mode='wb') as f:
for line in file_obj.chunks(): # 推荐加上chunks方法
f.write(line)
return HttpResponse(f'<h3>upload file {file_obj.name} success!</h3>')
return render(request, 'upload_files.html')
request对象方法
"""
request.method
request.POST
request.GET
request.FILES
request.body # 原生的浏览器发过来的二进制数据 后面详细的讲
request.path
request.path_info
request.get_full_path() 能过获取完整的url及问号后面的参数
"""
print(request.path) # /app01/ab_file/
print(request.path_info) # /app01/ab_file/
print(request.get_full_path()) # /app01/ab_file/?username=jason
FBV与CBV
# 视图函数既可以是函数也可以是类
def index(request):
return HttpResponse('index')
# CBV
# CBV路由
url(r'^login/$', views.MyLogin.as_view(),name='login')
from django.views import View
class MyLogin(View):
def get(self, request):
return render(request, 'login.html')
def post(self, request):
return HttpResponse('post方法')
"""
FBV和CBV各有千秋
CBV特点
能够直接根据请求方式的不同直接匹配到对应的方法执行
内部到底是怎么实现的?
CBV内部源码(******)
"""