Django的View(视图层)
一、JsonResponse
为什么要给前端返回json格式字符串,后端就专门写接口 前端调用你这个接口 就能够拿到一个,json格式的字符串,然后前端利用序列化反序列转换成前端对应的数据类型
向前端返回一个json格式字符串的三种种方式,
JSON.stringify 序列化 >>> json.dumps
JSON.parse 反序列 >>> json.loads
方式一:template
# urls.py
from app import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^json/', views.jsons),
]
# views.py
from django.template import Template, Context
def jsons(request):
res = Template('<h1>{{ user }}</h1>')
con = Context({'user': {"username": "randysun", "pwd": "123"}})
ret = res.render(con)
return HttpResponse(ret)
# http://127.0.0.1:8000/json/ 结果 {'username': 'randysun', 'pwd': '123'}
方式二:json模块
# urls.py
from app import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^json/', views.jsons),
]
# views.py
import json
def jsons(request):
user_dic = {"username": "randy 你好!", 'pwd': "1234"}
user_dic = json.dumps(user_dic, ensure_ascii=False)
print(user_dic)
return HttpResponse(user_dic)
# http://127.0.0.1:8000/json/ 结果 {"username": "randy 你好!", 'pwd': "1234"}
补充:实现json意外的对象序列化
import json
from datetime import datetime
"""
TypeError: Object of type 'datetime' is not JSON serializable
"""
class MyJsonClass(json.JSONEncoder):
def default(self, o):
if isinstance(o, datetime): # 如果o不是json默认能够序列化 你就在该方法内给他处理成json能够转的类型
return o.strftime('%Y-%m-%d')
else:
super().default(self, o)
d = {'ctime': datetime.today()}
print(json.dumps(d, cls=MyJsonClass))
方式三:JsonResponse模块
# urls.py
from app import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^json/', views.jsons),
]
# views.py
from django.http import JsonResponse
def jsons(request):
user_dic = {"username": "randy 你好!", 'pwd': "1234"}
user_dic = JsonResponse(user_dic, safe=False, json_dumps_params={"ensure_ascii": False})
print(user_dic.content)
return user_dic
# lists = [1, 2, 3, 4]
# lists = JsonResponse(lists, safe=False, json_dumps_params={"ensure_ascii": False})
# return lists
# http://127.0.0.1:8000/json/ 结果 {"username": "randy 你好!", 'pwd': "1234"}
# http://127.0.0.1:8000/json/ 结果 [1, 2, 3, 4]
JsonResponse:JsonResponse默认只支持序列化字典如果你想序列化其他类型(json能够支持的类型)需要将safe参数由默认True改成False
二、后端接收前端的文件
# up_file.html
<body>
<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="up_file"> <br>
<input type="submit" value="上传文件">
</form>
注意事项:
1.提交方式必须是post;
2.enctype参数必须有默认的urlencoded变成formdata;
3.chunks可以设置大文件的传输,设置一次读取多少字节;
# urls.py
from app import views
urlpatterns = [
# 上传文件
url(r'^up_file/', views.up_file),
]
# views.py
def up_file(request):
if request.method == 'POST':
print(request.FILES)
file_obj = request.FILES.get('up_file') # 获取文件对象
print(file_obj)
file_name = file_obj.name # 获取文件名
with open(file_name, 'wb') as f:
# for line in file_obj:
for chunk in file_obj.chunks(): # file_obj你可以直接看成文件句柄
#
f.write(chunk)
return HttpResponse("文件上传成功!")
return render(request, 'up_file.html')
三、 FBV和CBV(源码分析)
django的视图层由两种形式构成:FBV和CBV
1、FBV基于函数的视图(Function base view),我们之前一直介绍的都是FBV
2、CBV基于类的视图(Class base view)
"""
你在类中写了两个方法 一个叫get一个叫post
为什么前端get请求来就会触发get方法
post请求来就会触发post方法 如何实现的???
"""
<!--cbv -->
<body>
<form action="" method="post">
<input type="text">
<input type="submit" value="cbv">
</form>
</body>
# urls.py
from app import views
from app import views
urlpatterns = [
# CBV
url(r'^cbv/', views.MyCbv.as_view()), # url(r'^up_file/', views.view),
]
# views.py
# CBV
class MyCbv(View):
def get(self, request):
return render(request, '02cbv.html')
def post(self, request):
return HttpResponse(f"我是MyCbv类中的Post方法{request}")
http://127.0.0.1:8000/cbv/----->进入cbv.html页面,当提交时转到 -----> 我是MyCbv类中的Post方法{request}
源码分析:
# views.py
from django.views import View
class MyCbv(View):
def get(self, request):
return render(request, '02cbv.html')
def post(self, request):
return HttpResponse(f"我是MyCbv类中的Post方法{request}")
# CBV路由
url(r'^cbv/', views.MyCbv.as_view()), # url(r'^up_file/', views.view),
@classonlymethod
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
self = cls(**initkwargs)
# self = MyCbv(**initkwargs) self是MyCbv类
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
# 上面的一通操作 就是给我们自己写的类的对象赋值
# 对象在查找属性或方法的时候 顺序是什么? 先从自己找 再从产生对象的类中找 再去类的父类中找...
"""也就意味着你在看源码的时候 你一定要牢记上面的话"""
return self.dispatch(request, *args, **kwargs)
return view
"""CBV最精髓的部分"""
def dispatch(self, request, *args, **kwargs):
if request.method.lower() in self.http_method_names: # 判断当前请求方式在不在默认的八个请求方式中
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
# handler = getattr(自己写的类产生的对象,'小写的请求方法(getpost)','获取不到对应的方法就报错')
# handler就是我们自己定义的跟请求方法相对应的方法的函数内存地址
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs) # 在调用获取到的方法
分析:
- 自定义一个类继承Views;
- 在路由层配置url('^cbv/', views.MyCbv.as_view()),as_view()是类绑定方法,程序启动时,他的请求地址,实际引用的是views.views地址;
- 当在浏览器请求cbv/时候,将会执行views.views();
- 在调用views.views()之后,方法里面会进行一些操作,最终执行self.dispathch(request, *arsg, **kwargs)函数,注意对象查找属性和方法的顺序;
- 在dispatcha方法中首先判断请求方式是否在默认八大请求里面,如果在,则通过反射到自定义类中,获取当前请求方法,请求方法不在自定义类中则报错,请求方式如果不在八大请求里面则会直接报错;
- 执行反射获取自定义类中对应的请求方法;
四、settings.py配置文件源码分析
在django的配置文件有两个,一个是暴露给用户可以自定义配置的, 一个是默认的全局配置文件, 当用户修改了settings.py中配置,就使用用户指定的配置, 用户没有指定就用默认的;
实现原理:将用户在settings.py配置的文件覆盖,Django底层配置文件,通过大写变量名
from django.conf import settings # 用户可以修改的配置文件模块实现
from django.conf import global_settings # django默认的配置文件
settings = LazySettings() # 通过from django.conf import settings 点进去
class LazySettings(LazyObject):
def _setup(self, name=None):
# os.environ你可以把它看成是一个全局的大字典
settings_module = os.environ.get(ENVIRONMENT_VARIABLE) # 从大字典中取值
# settings_module = '项目名.settings'
self._wrapped = Settings(settings_module) # Settings('项目名.settings')
class Settings(object):
def __init__(self, settings_module): # settings_module = 'day59.settings'
for setting in dir(global_settings): # 循环获取global_settings文件中所有的名字
if setting.isupper(): # 在判断名字是否是大写
# 如果是大写 利用反射 获取到大写的名字所对应的值 不停地添加到对象中
setattr(self, setting, getattr(global_settings, setting))
# store the settings module in case someone later cares
self.SETTINGS_MODULE = settings_module
mod = importlib.import_module(self.SETTINGS_MODULE) # '项目名.settings'
# from day59 import settings
# mod 指代的就是暴露给用户的配置文件模块名
for setting in dir(mod): # 循环获取暴露给用户配置文件中所有的名字
if setting.isupper(): # 判断是否是大写
setting_value = getattr(mod, setting) # 如果是大写 获取大写的变量名所对应的值
setattr(self, setting, setting_value) # 不停的给对象重新赋值, 设置新值
# 基于djangosettings源码实现自己的项目也有两个配置文件一个是暴露给用户的一个是项目默认用户配置了就用用户的用户没有配置就用默认的,
五、 请求对象(HttpRequest)
django将http协议请求报文中的请求行、首部信息、内容主体封装到了HttpRequest对象中(类似于我们自定义框架的environ参数)。 django会将HttpRequest对象当做参数传给视图函数的第一个参数request,在视图函数中,通过访问该对象的属性便可以提取http协议的请求数据
5.1、HttpRequest对象常用属性part1
一.HttpRequest.method
获取请求使用的方法(值为纯大写的字符串格式)。例如:"GET"、"POST"
应该通过该属性的值来判断请求方法
二.HttpRequest.GET
值为一个类似于字典的QueryDict对象,封装了GET请求的所有参数,可通过HttpRequest.GET.get('键')获取相对应的值
三.HttpRequest.POST
值为一个类似于字典的QueryDict对象,封装了POST请求所包含的表单数据,可通过HttpRequest.POST.get('键')获取相对应的值
针对表单中checkbox类型的input标签、select标签提交的数据,键对应的值为多个,需要用:HttpRequest.POST.getlist("hobbies")获取存有多个值的列表,同理也有HttpRequest.GET.getlist("键")
案例:
urls.py
from django.urls import re_path
from app01 import views
urlpatterns = [
re_path(r'^login/$',views.login),
]
Views.py
from django.shortcuts import render,HttpResponse
def login(request):
if request.method == 'GET':
# 当请求url为:http://127.0.0.1:8001/login/?a=1&b=2&c=3&c=4&c=5
# 请求方法是GET,?后的请求参数都存放于request.GET中
print(request.GET)
# 输出<QueryDict: {'a': ['1'], 'b': ['2'], 'c': ['3', '4', '5']}>
# 获取?后参数的方式为
a=request.GET.get('a') # 1
b=request.GET.get('b') # 2
c=request.GET.getlist('c') # ['3', '4', '5']
return render(request,'login.html')
elif request.method == 'POST':
# 在输入框内输入用户名randy、年龄18,选择爱好,点击提交
# 请求方法为POST,表单内的数据都会存放于request.POST中
print(request.POST)
# 输出<QueryDict: {..., 'name': ['randy'], 'age': ['18'], 'hobbies': ['music', 'read']}>
# 获取表单中数据的方式为
name=request.POST.get('name') # randy
age=request.POST.get('age') # 18
hobbies=request.POST.getlist('hobbies') # ['music', 'read']
return HttpResponse('提交成功')
在templates目录下新建login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<!--
method="post"代表在提交表单时会以POST方法提交表单数据
action="/login/" 代表表单数据的提交地址为http://127.0.0.1:8001/login/,可以简写为action="/login/",或者action=""
-->
<form action="http://127.0.0.1:8001/login/" method="post">
{% csrf_token %} <!--强调:必须加上这一行,验证,安全-->
<p>用户名:<input type="text" name="name"></p>
<p>年龄:<input type="text" name="age"></p>
<p>
爱好:
<input type="checkbox" name="hobbies" value="music">音乐
<input type="checkbox" name="hobbies" value="read">阅读
<input type="checkbox" name="hobbies" value="dancing">跳舞
</p>
<p><input type="submit" value="提交"></p>
</form>
</body>
</html>
5.2、HttpRequest对象常用属性part2
一.HttpRequest.body
当浏览器基于http协议的POST方法提交数据时,数据会被放到请求体中发送给django,django会将接收到的请求体数据存放于HttpRequest.body属性中,因为该属性的值为Bytes类型,所以通常情况下直接处理Bytes、并从中提取有用数据的操作是复杂而繁琐的,好在django会对它做进一步的处理与封装以便我们更为方便地提取数据,比如
对于form表单来说,提交数据的常用方法为GET与POST
1:如果表单属性method='GET',那么在提交表单时,表单内数据不会存放于请求体中,而是会将表单数据按照k1=v1&k2=v2&k3=v3的格式放到url中,然后发送给django,django会将这些数据封装到request.GET中,注意此时的request.body为空、无用
2:如果表单属性method='POST',那么在提交表单时,表单内的所有数据都会存放于请求体中,在发送给django后会封装到request.body里,此时django为了方便我们提取数据,会request.body的数据进行进一步的处理,具体如何处理呢,需要从form表单提交数据的编码格式说起:
form表单对提交的表单数据有两种常用的编码格式,可以通过属性enctype进行设置,如下
编码格式1(默认的编码格式):enctype="application/x-www-form-urlencoded"
编码格式2(使用form表单上传文件时只能用该编码):enctype="multipart/form-data"
如果form表单提交数据是按照编码格式1,那么request.body中数据的格式类似于GET方法的数据格式,如k1=v1&k2=v2,此时django会将request.body中的数据提取出来封装到request.POST中方便我们提取
如果form表单提交数据是按照编码格式2,那么request.body中数据的格式为b'------WebKitFormBoundaryKtcwuksQltpNprep
Content-Disposition: form-data;......',,此时django会将request.body中的数据提取出来封装到request.POST中,将上传的文件数据专门提取出来封装到request.FILES属性中
强调:毫无疑问,编码格式2的数据量要大于编码格式1,如果无需上传文件,还是推荐使用更为精简的编码格式1
我们除了可以采用form表单向django提交数据外,还可以采用ajax技术,ajax可以提交的数据格式有:1、编码格式1 2、编码格式2 3、json,当ajax采用POST方法提交前两种格式的数据时,django的处理方案同上,但是当ajax采用POST方法提交json格式的数据时,django会将接收到的数据存放于HttpRequest.body,此时需要我们自己对HttpRequest.body属性值做反序列化操作,
具体的,我们在讲解ajax时再做具体介绍
二.HttpRequest.FILES
如果使用form表单POST上传文件的话,文件数据将包含在HttpRequest.FILES属性中。
该属性值为一个类似于字典的对象,可以包含多组key:value(对应多个上传的文件),其中每个key为<input type="file" name="" /> 中name属性的值,而value则为对应的文件数据
强调:HttpRequest.FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象。
案例:form表单上传文件
urls.py
from django.urls import path,register_converter,re_path
from app01 import views
urlpatterns = [
re_path(r'^register/$',views.register),
]
views.py
from django.shortcuts import render,HttpResponse
def register(request):
if request.method == 'GET':
return render(request,'register.html')
elif request.method == 'POST':
print(request.body)
# 从request.POST中获取用户名
name=request.POST.get('name')
# 从request.FILES获取文件对象
file_obj=request.FILES.get('header_img')
# 上传的文件存放于templates文件夹下
with open('templates/header.png','wb') as f:
for line in file_obj:
f.write(line)
return HttpResponse('注册成功')
在templates目录下新建register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
</head>
<body>
<form action="" method="POST" enctype="multipart/form-data" >
{% csrf_token %}
<p>
用户名:<input type="text" name="name">
</p>
<p>
头像:<input type="file" name="header_img">
</p>
<p>
<input type="submit" value="提交">
</p>
</form>
</body>
</html>
2.3、HttpRequest对象常用属性part3
一.HttpRequest.path
获取url地址的路径部分,只包含路径部分
二.HttpRequest.get_full_path()
获取url地址的完整path,既包含路径又包含参数部分
如果请求地址是http://127.0.0.1:8001/order/?name=egon&age=10#_label3,
HttpRequest.path的值为"/order/"
HttpRequest.get_full_path()的值为"/order/?name=egon&age=10"
案例:
urls.py
from django.urls import path,register_converter,re_path
from app01 import views
urlpatterns = [
re_path(r'^order',views.order),
]
views.py
from django.shortcuts import render,HttpResponse
# 针对请求的url地址:http://127.0.0.1:8001/order/?name=egon&age=10#_label3
# 从域名后的最后一个“/”开始到“?”为止是路径部分,即/order/
# 从“?”开始到“#”为止之间的部分为参数部分,即name=egon&age=10
def order(request):
print(request.path) # 结果为“/order/”
print(request.get_full_path()) # 结果为"/order/?name=egon&age=10"
return HttpResponse('order page')
5.4、HttpRequest对象常用属性part4
一.HttpRequest.META
值为包含了HTTP协议的请求头数据的Python字典,字典中的key及期对应值的解释如下
CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
CONTENT_TYPE —— 请求的正文的MIME类型。
HTTP_ACCEPT —— 响应可接收的Content-Type。
HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
HTTP_HOST —— 客服端发送数据的目标主机与端口
HTTP_REFERER —— Referring 页面。
HTTP_USER_AGENT —— 客户端使用的软件版本信息
QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
REMOTE_ADDR —— 客户端的IP地址。
REMOTE_HOST —— 客户端的主机名。
REMOTE_USER —— 服务器认证后的用户。
REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。
SERVER_NAME —— 服务器的主机名。
SERVER_PORT —— 服务器的端口(是一个字符串)。
从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,HTTP协议的请求头数据转换为 META 的键时,
都会
1、将所有字母大写
2、将单词的连接符替换为下划线
3、加上前缀HTTP_。
所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。
注意:下述常用属性暂且了解即可,待我们讲到专门的知识点时再专门详细讲解
二.HttpRequest.COOKIES
一个标准的Python 字典,包含所有的cookie。键和值都为字符串。
三.HttpRequest.session
一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。
11.HttpRequest.user(用户认证组件下使用)
一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。
2.HttpRequest.is_ajax()
如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。
大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。
如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache middleware,
你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存。
六、 响应对象(HttpResponse)
响应可以是任何形式的内容,比如一个HTML文件的内容,一个重定向,一个404错误,一个XML文档,或者一张图片等。总之,无论视图本身包含什么逻辑,都要返回响应,具体的说,响应对象主要有三种形式:HttpResponse,render,redirect
from django.shortcuts import HttpResponse,render,redirect
6.1、HttpResponse()
括号内直接跟一个具体的字符串作为响应体,比较直接很简单,所以这里主要介绍后面两种形式。
6.2、render()
render(request, template_name[, context])
参数:
1、request:用于生成响应的请求对象,固定必须传入的第一个参数
2、template_name:要使用的模板的完整名称,必须传入,render默认会去templates目录下查找模板文件
3、context:可选参数,可以传入一个字典用来替换模块文件中的变量
综上,render的功能可以总结为:根据给定字典渲染模板文件,并返回一个渲染后的 HttpResponse对象。
6.3、redirect()
# 返回重定向信息
def my_view(request):
...
return redirect('/some/url/')
# 重定向的地址也可以是一个完整的URL:
def my_view(request):
...
return redirect('http://www.baidu.com/')
重定向转态码301与302的区别(了解):
一、301和302的异同。
1、相同之处:
301和302状态码都表示重定向,具体点说就是浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址(浏览器会从响应头Location中获取新地址),用户看到的效果都是输入地址A后瞬间跳转到了另一个地址B
2、不同之处:
301表示旧地址A的资源已经被永久地移除了,即这个资源不可访问了。搜索引擎在抓取新内容的同时也将旧的网址转换为重定向之后的地址;
302表示旧地址A的资源还在,即这个资源仍然可以访问,这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容、并且会保存旧的网址。 从SEO层面考虑,302要好于301
二、重定向原因:
1、网站调整(如改变网页目录结构);
2、网页被移到一个新地址;
3、网页扩展名改变(如应用需要把.php改成.Html或.shtml)。
这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。