django框架:路由分发、名称空间、虚拟环境、视图层之必会三板斧、JsonResponse对象、视图层之request对象获取文件、视图层之FBV与CBV、CBV源码剖析(重要)、模板层
一、路由分发
- django支持每个应用都可以有自己独立的路由层、静态文件、模板层。基于该特性多人开发项目就可以完全解耦合,之后利用路由分发还可以整合到一起
- 多个应用都有很多路由与视图函数的对应关系 这个时候可以拆分到各自的路由层中
- 说的直白一点,路由分发就是把Django项目中的路由信息分配到每个应用中,然后在项目的路由文件中导入应用中的路文件即可正常使用。
使用路由分发之前 总路由直接干路由与视图函数的匹配
path('index/', index)
使用路由分发之后 总路由只按照应用名分配匹配方向
path('app01/',include('app01.urls')),
path('app02/',include('app02.urls')),
应用中的子路由:
path('after/',views.after) # app01
path('after/',views.after) # app02
总结:
- 路由分发解决的就是项目的总路由匹配关系过多的情况
- 利用路由分发之后 总路由不再处理路由与视图函数的直接对应关系
- 总路由而是做一个分发处理(识别当前url是属于那个应用下的 直接分发对应的应用去处理)
二、名称空间
路由分发之后,针对相同的别名能否自动反向解析出不同的应用前缀?
经过测试,默认情况下是无法直接识别应用前缀的。
如果想要正常识别区分有两种方式
方式1:名称空间
在总路由中增加一个名称空间(在Django1和Django2及以上版本有区别,Django1用的是url)
Django2及以上版本
path('app01/', include(('app01.urls', 'app01'), namespace='app01')),
path('app02/', include(('app02.urls', 'app02'), namespace='app02')),
反向解析中的使用方式
reverse('app01:index_view')
reverse('app02:index_view')
Django1中的使用方式
# 1.总路由增加一个名称空间
url(r'^app01/',include('app01.urls',namespace='app01')), # 创建名称空间app01
url(r'^app02/',include('app02.urls',namespace='app02')) # 创建名称空间app02
# 2.子路由app01
urlpatterns = [
url(r'^reg/',views.reg,name='reg')
]
# 后端
def reg(request):
print(reverse('app01:reg'))
return HttpResponse('app01: reg')
# 前端
{% url 'app01:reg' %}
# 3.子路由app02
urlpatterns = [
url(r'^reg/',views.reg,name='reg')
]
# 后端
def reg(request):
print(reverse('app02:reg'))
return HttpResponse('app02: reg')
# 前端
{% url 'app02:reg' %}
方式2:别名不冲突即可
多个应用别名不冲突可以用应用名作为别名的前缀
path('index/', views.index, name='app01_index_view')
path('index/', views.index, name='app02_index_view')
三、虚拟环境
通过具体情况导入概念
项目1需要使用:
django1.11
python38
项目2需要使用:
django2.22 pymysql requests
python38
项目3需要使用:
django3.22 request_html flask urllib3
python38
实际开发项目中我们会使用虚拟环境进行解释器的配置,让每个项目所使用的虚拟环境只配备所需的环境,不需要的一概不配!!!
虚拟环境:能够针对相同版本的解释器创建多个分身,每个分身可以有自己独立的环境。
pycharm创建虚拟环境和cmd中使用代码创建虚拟环境
pycharm创建虚拟环境
ps:每创建一个虚拟环境就相当于重新下载了一个全新的解释器
步骤一:打开pycharm后新建一个项目
步骤二:按图示进行操作
ps:创建虚拟环境会比较慢,电脑不行的话还会出现进度条卡住的情况。
虚拟环境在pycharm是黄色的:
使用虚拟环境下载模块:
命令行的方式创建虚拟环境:
注意事项:python3.3 之后venv已经作为标准库嵌入到了python中,而之前的版本需要借助virtualenv这个第三方库来实现。
创建虚拟环境的命令
ps:当我们创建虚拟环境的时候,在那个路径下执行的代码,虚拟环境 的文件就会创建到哪个目录下。
注意:python命令此处不支持多版本共存的操作(如果需要调整版本,要在环境变量中调整路径的上下位置,达到调整顺序的目的):python27、python36、python38
激活与关闭虚拟环境
当我们在cmd中创建了虚拟环境之后并不能直接使用,我们需要在命令窗口中输入激活命令来使用他,当我们不想使用的时候要使用关闭命令,关闭虚拟环境才能使用之前的解释器。
我们需要进入虚拟环境的文件夹中,进入内部的Scripts文件夹,再在内部运行指令,其中激活指令有三个,分别对应三种不同的操作系统。
激活
activate
激活了虚拟环境之后,cmd窗口的左边会出现一个套着python文字的框。
关闭
deactivate
当我们在激活了虚拟环境的情况下就可以给虚拟环境安装需要的模块
pip install --index-url http://mirrors.aliyun.com/pypi/simple/ django==1.11.11 --trusted-host mirrors.aliyun.com
四、视图层之必会三板斧
之前我们简单学习了三板斧的使用,这里我们进行一些深入的了解。
视图函数不写返回值会报错,报错信息如下:
这里就引出了今天的话题。这里我们需要记住一个定论:用来处理请求的视图函数都必须返回HttpResponse对象
在之前学习的时候我们对HttpResponse的作用仅限于知道他是用来给网页传递字符数据的。
从正面不容易证明上面的定论,我们可以从三板斧的源码中进行确认。
render
def render(request, template_name, context=None, content_type=None, status=None, using=None):
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
这里是render的内部源码,在他的返回值中我们可以发现他的返回值也是用HttpResponse类产生的一个对象。
redirect
def redirect(to, *args, permanent=False, **kwargs):
redirect_class = HttpResponsePermanentRedirect if permanent else HttpResponseRedirect
return redirect_class(resolve_url(to, *args, **kwargs))
这里我们可以直接的看到返回的结果时redirect_class这个变量,而这个变量的值是有两种情况,用三元表达式进行简写了,依旧是用ctrl+鼠标左键,我们点击他们,会发现他两分别是两个不同的类,但是同时继承了一个名叫HttpResponseRedirectBase的父类
class HttpResponseRedirect(HttpResponseRedirectBase):
status_code = 302
class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
status_code = 301
接下来我们去看这个父类有什么花头。
我们可以在这两个类的源代码上方找到这个父类,咋们也不用管他干了什么,首先我们就会发现这个类他的父类是HttpResponse
class HttpResponseRedirectBase(HttpResponse):
allowed_schemes = ['http', 'https', 'ftp']
def __init__(self, redirect_to, *args, **kwargs):
super().__init__(*args, **kwargs)
self['Location'] = iri_to_uri(redirect_to)
parsed = urlparse(str(redirect_to))
if parsed.scheme and parsed.scheme not in self.allowed_schemes:
raise DisallowedRedirect("Unsafe redirect to URL with protocol '%s'" % parsed.scheme)
url = property(lambda self: self['Location'])
def __repr__(self):
return '<%(cls)s status_code=%(status_code)d%(content_type)s, url="%(url)s">' % {
'cls': self.__class__.__name__,
'status_code': self.status_code,
'content_type': self._content_type_for_repr,
'url': self.url,
}
当一个类的爷爷继承了HttpResponse,那么这个类产生的对象其实就是由HttpResponse产生的(如果不理解的可以回头看看类的双下new方法产生对象的过程)。
HttpResponse
他就是用类名加括号的形式返回值,相当于是返回了一个对象,符合上面的定论。
通过上面的一圈论证,我们就可以证明我们的定论是正确的。
五、JsonResponse对象
当我们给前端页面传递数据的时候,有些时候可能会初夏跨语言交互数据你情况,这时候我们需要使用json模块来序列化数据,让数据实现跨语言传输。
在html网页中我们使用js语言中的JSON对象进行序列化和反序列化
JSON.stringify()
JSON.parse()
python中我们使用json模块进行序列化和反序列化
json.dumps()
json.loads()
两种序列化数据的方式
方式一:使用json模块
def index_func(request):
# 返回给浏览器一个json格式的字符串
user_dict = {'name': 'jason老师', 'age': 18}
import json
user_json = json.dumps(user_dict, ensure_ascii=False)
return HttpResponse(user_json)
使用json序列化数据需要先转换格式,再交给HttpResponse返回给浏览器
方式二:使用JsonResponse对象
from django.http import JsonResponse
def index_func(request):
user_dict = {'name': 'jason老师', 'age': 18}
return JsonResponse(user_dict)
使用JsonResponse对象,直接输出数据就好了,他的源代码中已经用json模块处理过数据了。同时也省去了调用HttpResponse返回给浏览器数据的步骤。
ps:以后写代码很多时候可能需要参考源码及所学知识扩展功能
使用JsonResponse对象序列化除字典外的数据类型
接下去当我们尝试使用不同数据类型的数据,通过JsonResponse对象返回给浏览器的时候,除了字典类型的数据都会出现报错,通过查看源码我们得知是safe参数导致的主动报错。
这里是JsonResponse部分源码
class JsonResponse(HttpResponse):
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.'
)
因为JsonResponse主要序列化字典,所以针对非字典的其他可以被序列化的数据需要修改safe参数为False
如果给JsonResponse对象内部的json代码传参
解决了数据类型的传递问题,我们又想到针对字符编码需要使用ensure_ascii=False让中文字符不变成二进制。通过观察源码,我们发现如果想要传参,是通过json_dumps_params这个变量来传递其他参数的。通过查看源码的运行,我们发现直接用关键字参数的形式在JsonResponse对象的括号内传参即可。
完整源码如下
class JsonResponse(HttpResponse):
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().__init__(content=data, **kwargs)
六、视图层之request对象获取文件
我们使用表单标签中的input标签接收用户的输入以及其他数据
form表单携带文件类型的数据需要做到以下几点
1.method必须是post
2.enctype必须是multipart/form-data
<form action="" method="post" enctype="multipart/form-data">
<p>file:
<input type="file" name="file">
</p>
<input type="submit" value="选我">
<button>点我</button>
</form>
django后端需要通过request.FILES获取文件类型的数据
def index_func(request):
if request.method == 'POST':
# print(request.POST) # 获取普通数据(输入、选择)
# print(request.FILES) # 获取文件数据(上传)
file_obj = request.FILES.get('file')
print(file_obj.name) # 获取文件名称
with open(r'%s' % file_obj.name, 'wb') as f:
for line in file_obj: # 文件对象支持for循环一行行读取内容
f.write(line)
return render(request, 'indexPage.html')
七、视图层之FBV与CBV
概念介绍
视图是可调用的,用来处理请求(request)并且返回响应(response),django的视图有两种形式:FBV和CBV
1、FBV基于函数的视图(Function base views),我们之前一直介绍的都是FBV
2、CBV基于类的视图(Class base views),我们本节主要介绍它
视图函数可以是函数也可以是类
代码展示:
FBV
# urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('fbv/', views.test_fbv),
]
# views.py
def index(request):return HttpResponse对象
CBV
# urls.py
from django import views
urlpatterns = [
path('login/', views.MyLoginView.as_view())
]
这里就是CBV形式,也就是类的路由导入,这时候我们就不需要写对应的对象内的方法了,会自动匹配并执行
# views.py
"""只要是处理业务逻辑的视图函数 形参里面肯定要有request"""
from django import views
class MyLoginView(views.View):
def get(self, request):
return HttpResponse('from CBV get function')
def post(self, request):
return HttpResponse('from CBV post function')
八、CBV源码剖析(重要)
1.从CBV的路由匹配切入
path('login/', views.MyLoginView.as_view())
1.类名点名字(名字的查找问题)
2.类名点名字并加括号调用(静态方法、绑定给类的方法)
2.函数名加括号执行优先级最高 项目一启动就会自动执行as_view方法
部分View源码
class View:
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process."""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
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))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
我们可以看到在源码中as_view返回的结果时view,而view是as_view内部的一个闭包函数。因此CBV路由导入的结果其实和FBV的路由导入代码一样,相当于FBV中的路由导入:
path('login/', views.view)
# as_view 可以看成view
因此可以得出结论:CBV路由本质还是FBV
3.浏览器地址栏访问login路由需要执行view函数
1.产生我们自己编写类的对象
部分源码
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.setup(request, *args, **kwargs)
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
return self.dispatch(request, *args, **kwargs)
首先是开头的self变量,他相当于产生了一个对象,而这个对象使用我们自己定义的那个类来产生的,接着中间的都不重要,在末尾return的位置我们发现产生的这个对象调用了dispatch方法。
2.对象调用dispatch方法(注意查找顺序)
4.研究父类中的dispatch方法
获取当前请求方法并转小写,之后利用反射获取类中对应的方法并执行。
因为我们没有定义这个dispatch方法,所以会调用父类中的dispatch方法,第一个if语句判断的是我们的请求方法,判断他是否在view类中定义的第一行的变量名内。如果有这个方法名称就用反射获取结果,没有结果就返回报错信息。
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
九、模板层
常用语法:
{{}}:主要与数据值相关
{%%}:主要与逻辑相关
django的模板语法是自己写的 跟jinja2不一样
1.针对需要加括号调用的名字 django模板语法会自动加括号调用你只需要写名字就行
2.模板语法的注释前端浏览器是无法查看的 {##}
3.模版语法因为不能加括号,所以不能传参数
1.模板语法传值
return render(request, 'demo02.html', {'n1': name, 'a1': age}) # 传值方式1:精准传值 不浪费资源 针对多资源的传递书写麻烦
return render(request,'demo02.html', locals()) # 传值方式2:将函数名称空间中所有的名字全部传递 名字过多并且不使用的情况下比较浪费资源
2.模板语法传值特性
1.基本数据类型正常展示
2.文件对象也可以展示并调用方法(file_obj.read)
3.函数名会自动加括号执行并将返回值展示到页面上(不支持额外传参)
4.类名也会自动加括号调用
5.对象则不会
ps:针对可以加括号调用的名字,模板语法都会自动加括号调用
我们在使用模版语法传值的时候,python基本数据类型的数据会被渲染(也就是加点样式,但是不多)
# python基本数据类型
f = 1.11
i = 666
s = 'hello jason! say hello big baby ha ha ha'
l = [11, 22, 33, 44]
d = {'name': 'jason', 'age': 18}
t = (11, 22, 33, 44)
se = {11, 22, 33, 44}
b = True
3.模板语法之过滤器(内置函数)
在Django的模板语言中,通过使用过滤器(类似python的内置方法)来改变变量的显示。
过滤器的语法: {{ value|filter_name:参数 }}
使用管道符"|"来应用过滤器。
例如:{{ name|lower }}会将name变量应用lower过滤器之后再显示它的值。lower在这里的作用是将文本全都变成小写。
注意事项:
- 过滤器支持“链式”操作。即一个过滤器的输出作为另一个过滤器的输入。
- 过滤器可以接受参数,例如:{{ sss|truncatewords:30 }},这将显示sss的前30个词。
- 过滤器参数包含空格的话,必须用引号包裹起来。比如使用逗号和空格去连接一个列表中的元素,如:{{ list|join:', ' }}
- '|'左右没有空格没有空格没有空格
Django的模板语言中提供了大约六十个内置过滤器。
length
返回值的长度,作用于字符串和列表。
<p>{{ value|length }}</p>
返回value的长度,如 value=['a', 'b', 'c', 'd']的话,就显示4.
slice
切片
<p>{{ s|slice:'1:4' }}</p>
add
添加和拼接(可以在源码内观看原理)
如果是两个数字用add进行操作,会把值进行相加,如果是两个字符串就会把两个字符串进行拼接
<p>{{ i|add:10 }}</p>
<p>{{ s|add:'baby' }}</p>
filesizeformat
文件大小
可以将文件大小转换成合适的格式
<p>{{ file_size|filesizeformat }}</p>
date
日期格式
将时间信息转换成格式化时间(Y年-m月-d日 H时:i分:s秒),这里没有大小写的X进行格式替换。
<p>{{ ctime|date:'Y-m-d H:i:s ' }}</p>
truncatechars
节选字符,后面的参数设置为几,就会显示几个字符,剩余部分用三个点表示
<p>{{ s|truncatechars:5 }}</p>
truncatewords
节选单词
类似truncatechars,参数设置为几,就会显示几个单词,剩余部分用三个点表示
<p>{{ s|truncatewords:3 }}</p>
safe
对变量内的字符串进行html转义(默认情况下是不转义)
h1 = '<h1>哈哈哈哈</h1>'
s1 = "<script>confirm(123)</script>"
<p>{{ h1|safe }}</p>
<p>{{ s1|safe }}</p>