28-django高级(一)
要求
- 每周必须交作业
- 开始自己做小项目作为以后的面试项目(可以结组完成)
1. django复习
- Http请求本质
浏览器(socket客户端):
2. DNS解析 + 三次握手 + socket.connect(ip,端口)
3. socket.send("http://www.xiaohuar.com/index.html....")
规则:http协议
GET请求:
"GET /index.html?k1=1&k2=2 Http/1.1\r\nhost:www.xiaohuar.com\r\ncontent-type:application/json\r\n\r\n"
请求头和请求体使用\r\n\r\n分割,前面头,后面是体,请求内容在请求头中
请求会被拼接成:www.xiaohuar.com/index.html?k1=1&k2=2
POST请求:
"POST /index.html Http/1.1\r\nhost:www.xiaohuar.com\r\ncontent-type:application/json\r\n\r\nusername=alex&pwd=123123
请求头和请求体使用\r\n\r\n分割,前面头,后面是体,请求内容在请求体中
6. 获取相应
响应头,响应体 = data.split('\r\n\r\n')
7. 断开连接
网站(socket服务端):
1. 服务端运行: ip,端口
4. 字符串 = server.recv()
头,体 = data.split("\r\n\r\n=")
request.POST.get() 就是从头中获取的信息
5. 服务端响应:
conn.send('......')
响应头:
响应体:
7. 断开连接
总结:
a. Http请求中本质都是字符串
b. Http请求短连接(请求,响应,然后断开连接)
c. 请求和响应都有:头、体
请求:
请求头
\r\n\r\n
请求体
响应:
响应头
\r\n\r\n
响应体
<html>
....
</html>
- Web框架
Django本质:
socket(wsgiref)PS:Django 本身不包含socket,而是封装了第三方的socketref
解析和封装http请求(*)
使用Django:
a. 安装
b.
projcet
django-admin startproject mysite
app
cd mysite
python manage.py startapp app01
写代码(****)
python manage.py runserver ip:port
- 写代码
- django运行流程:
模板(html、tpl) + DB数据渲染 = 字符串
- 路由系统 URL
/login/ -> funcname name='f1'
/login/\d+/ -> funcname name='f1'
/login/(?P<n>\d+)/ -> funcname name='f1'
/login/\d+/ -> include('app01.urls')
- 视图函数
def index(request):
request.GET #请求头中取信息
request.body # 原生的请求体
request.POST # 如果请求头中: content-type: urlencode-form。。。。,才将request.body转换称字典,默认请求头就是这个
- 可能有
- 可能没有【比如改了请求头中的content-type】
request.method
request.Meta
request.GET.get()
request.GET.getlist() ===前端用户选择checkbox、select,比如爱好、书籍的作者
request.POST.get()
request.POST.getlist() ===前端用户选择checkbox、select,比如爱好,书籍的作者
return HttpResponse('字符串/字节')
return render(request,"html路径",{})
return redirect('URL')##返回的也是字符串,字符串里有一个特殊的响应头location:www.baidu.com,会根据location的信息再次发送一个http请求
- 模板
- 继承
- 模板语言
for
if
- filter,sample_tag
- Models操作
- 创建表: 业务线
- models.xx.objects.create(name='欧美')
- models.xx.objects.create(**dic)
- models.xx.objects.filter(id__gt=1).delete()
- models.xx.objects.filter(id=1).delete()
- models.xx.objects.exclude(id=1).delete()
ps:过滤条件
id>5 filter(id__gt=5)
id=5 filter(id=5)
id不等于5的 exclude(id=5)
- models.xx.objects.filter(id=1).update(name='ddd')
- models.xx.objects.filter(id=1).update(**dic)
- 创建表:
业务线
主机表
id host port bs
# queryset = [对象,对象,...]
- objs = models.xx.objects.all()
for row in objs:
row.id
row.host
row.port
row.bs.name【通过对象的方式跨表,通过 . 的方式区对象的属性】
# queryset = [{},{},...]
- objs = models.xx.objects.all().values('id','host','port','bs__name') #集合对象是字典,在values中通过__(双下划线)的方式跨表
for row in objs:
row['id']
row['bs__name']
# queryset = [(1,1.1.11,80,'Web'),(),()...]
- objs = models.xx.objects.all().values_list('id','host','port','bs__name') #集合对象是元组,在values_list中通过__(双下划线)的方式跨表
for row in objs:
row[0]
row[1]
- 创建表:
用户表(id, user,pwd,email,mm)
业务线(id, name) # 用户表_set 和用户表的关系是多对多,和主机表的关系是一对多
主机表(id host port bs)
用户业务线关系表(id uid bid) ******
1 22 1
2 22 11
- obj = modes.userinfo.objects.filter(user='日语哥').first() #假设日语歌的uid是22
obj.mm.add(1)
obj.mm.add(11)
# 日语哥负责的所有业务线【多对多关系】-> [业务线对象,业务线对象,]
obj = models.userinfo.objects.filter(user='日语哥').first()
queryset = obj.mm.all()
for row in queryset:
row.id
row.name
- 二手车业务线是由那些人负责
obj = models.business_unit.objects.filter(name='二手车').first()
queryset = obj.userinfo_set.all() #[用户对象,用户对象,]
for row in queryset:
row.user
row.pwd
不能操作第三张表,因为没有这个表对象的类
通过其中一张表的mm字段来操作 表.mm (等价于第三张表)
obj.mm.add(1)
====通过对象的方式关联表记住两点
1,
定义了ManyToMany的表(userinfo),定义的字段为mm:userinfo.mm(定义了的就方便,通过.mm即获取第三张表)
没有定义ManyToMany的表(bussiness_unit):bussiness_unit.userinfo_set(没有定义mm的,得通过.关联表名_set的方式获取第三张表,关联表名得小写???)
解释:
没有定义mm的另一个表有一个隐藏的字段:关联的用户表名_set (等价于 定义了mtmt表的mtm字段)=====等价于第三张表
备注:
以上两种方式是从两个表开始取的,都取到的是第三张表
适用场景:
一个人负责那些业务线
一个业务线由那些人负责
2,
userinfo.mm.all()获取的是第三张表的所有内容
obj.mm.all() 获取的是和obj这个对象关联的第三张表的所有内容
作业:主机管理
用户表(id, user,pwd,email,mm)
业务线(id, name) # 用户表_set
主机表(id host port FK(业务线))
用户业务线关系表(id uid bid) ******
1. 登录(Ajax POST,密码加密)
2. 用户登录基于Session
3. 装饰器
4. 主机列表
host port <a>业务线名称</a>
host port 业务线名称
host port 业务线名称
host port 业务线名称
host port 业务线名称
分页
PS: 模态对话框编辑
5. 新页面: 当前业务线所有的管理员列表
提示:
编程if判断的时候,简单的逻辑放上面
字符串转字节的两种方法:
一、b'fsdfsaf'
二、'fsdfsf'.encode('utf-8')
timedetla的参数,看源码
2. FBV & CBV
FBV就是之前一直用的,今天主要介绍CBV,CBV 在类中自动做了get和post的判断,进而执行对应的方法(通过类其中的dispatch方法中的反射找到并返回的)。而FBV需要自己判断
CBV示例如下:
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login.html/$', views.LoginView.as_view()), #LoginViewl类的as_view方法 ]
views.py
from django.shortcuts import render, HttpResponse from django.views import View class LoginView(View): #继承Django自带的View类 # 执行父类的dispatch方法,***父类中的self指的不是父类self也是自身****,先找自身,再找父类 # 父类的dispatch方法通过反射找get,post请求 def dispatch(self, request, *args, **kwargs): print('before') response = super(LoginView,self).dispatch(request, *args, **kwargs) print('after') #CBV可以自定义 before 和after,这样,不管是get还是post都会执行before和after,可以用在验证的时候,下面有例子,但是DRF的dispatch不会执行after语句,因为DRF返回的是Response return response def get(self,request,*args,**kwargs): print('GET') return render(request,'login.html') def post(self,request,*args,**kwargs): print('POST') return HttpResponse('OK')
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="x-ua-compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> </head> <body> <form method="post" action=""> {% csrf_token%} <input type="text" name="user"> <input type="text" name="pwd"> <input type="submit" value="提交"> </form> </body> </html>
备注:form 表单只能提交get和post方法,但是as_view() 还支持其他的请求方式,但是ajax可以提交任何请求
效果就是通过CBV的方式来处理get和post请求
关于类继承方式寻找dispatch中的反射,补充知识如下
class B: def f1(self): self.f2() class A(B): def f2(self): print(a.f2) #本质要搞清楚 self到底是谁? 正确的方式 obj=A() obj.f1() ==等价于== B.f1(obj)【obj是A()产生的对象】,因为self这时候指的是A()的对象,所以在f1中可以调用f2【如果此时A中没有f2,才会到B中去找】,不会报错 错误的方式 obj=B() obj.f1() 报错,因为self这时候指的是B()的对象,这个对象中没有f2,所以报错。 总结:搞清楚self是谁,,,,如果没有要实用的方法,本身没有的时候,才会往父类找,但是绝对不会往下找======= eg1: class View: def dispach(self): fun = getattr(self,’get’) return func(……) class LoginView(View): def dispach(self): respond = super(LoginView,self).dispach() return respone obj = LoginView() obj.dispach() 执行结果:报错,因为在LoginView 和父类 View中都找不到get方法【优先在obj所属的类( LoginView)中找,如果找不到就去父类(View)中找】 eg2: class View: def dispach(self): fun = getattr(self,’get’) return func(……) def get(): print 1 class LoginView(View): def dispach(self): respond = super(LoginView,self).dispach() ###respone接收到的是父类返回的func() return respone def get(): print 2 obj = LoginView() obj.dispach() 正确执行 ====输出2
CBV使用场景【验证是否已登录】
实现【CBV结合session验证用户是否登陆】
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login.html$', views.LoginView.as_view()), url(r'^index.html$', views.IndexView.as_view()), ]
views.py
from django.shortcuts import render,HttpResponse, redirect from django.views import View class LoginView(View): def dispatch(self, request, *args, **kwargs): return super(LoginView,self).dispatch(request, *args, **kwargs) #注意这块要return,这块其实就是完全执行父类的dispatch方法 def get(self, request, *args,**kwargs): return render(request,'login.html') def post(self,request, *args,**kwargs): user = request.POST.get('user') pwd = request.POST.get('pwd') if user == 'alex' and pwd == '123': # session_id = asdfasdfasdfasdf # asdfasdfasdfasdf = {username: 'alex'} request.session['username'] = user # 这行代码做了如上两个操作 return redirect('/index.html') return render(request, 'login.html',{'msg': '去你的'}) class IndexView(View): def dispatch(self, request, *args, **kwargs): if request.session.get('username'): response = super(IndexView,self).dispatch(request, *args, **kwargs) return response else: return redirect('/login.html') # 如果认证不通过则重定向到登陆页面 def get(self,request,*args,**kwargs): # 通过get方式,如果认证通过,则显示认证的用户名 return HttpResponse(request.session['username'])
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- action不写,提交到当前所在的地址 --> <form method="POST" > <input type="text" name="user" /> <input type="text" name="pwd" /> <input type="submit" value="提交"/>{{ msg }} </form> </body> </html>
问题来了,如果有很多页面(类似index.html)都需要验证,怎么解决?
方式一:面向对象单继承的方式实现
urls.py 和 login.html 都同上
views.py
from django.shortcuts import render,HttpResponse, redirect from django.views import View # FBV、CBV class LoginView(View): def dispatch(self, request, *args, **kwargs): return super(LoginView,self).dispatch(request, *args, **kwargs) #注意这块要return,同时这块其实就是完全执行父类的dispatch def get(self, request, *args,**kwargs): return render(request,'login.html') def post(self,request, *args,**kwargs): user = request.POST.get('user') pwd = request.POST.get('pwd') if user == 'alex' and pwd == '123': # session_id = asdfasdfasdfasdf # asdfasdfasdfasdf = {username: 'alex'} request.session['username'] = user # 这行代码做了如上两个操作 return redirect('/index.html') return render(request, 'login.html',{'msg': '去你的'}) # 单继承方式: class BaseView(View): # 继承自View,并重写dispatch方法 def dispatch(self, request, *args, **kwargs): if request.session.get('username'): response = super(BaseView,self).dispatch(request, *args, **kwargs) return response else: return redirect('/login.html') class IndexView(BaseView): # 继承自自定义的BaseView def get(self,request,*args,**kwargs): return HttpResponse(request.session['username'])
如果需要做session验证,则CBV不要继承View,而是继承BaseView即可。
方式二:面向对象多继承的方式实现【推荐使用这种方式】
urls.py 和 login.html 都同上
views.py
from django.shortcuts import render,HttpResponse, redirect from django.views import View # FBV、CBV class LoginView(View): def dispatch(self, request, *args, **kwargs): return super(LoginView,self).dispatch(request, *args, **kwargs) def get(self, request, *args,**kwargs): return render(request,'login.html') def post(self,request, *args,**kwargs): user = request.POST.get('user') pwd = request.POST.get('pwd') if user == 'alex' and pwd == '123': # session_id = asdfasdfasdfasdf # asdfasdfasdfasdf = {username: 'alex'} request.session['username'] = user # 这行代码做了如上两个操作 return redirect('/index.html') return render(request, 'login.html',{'msg': '去你的'}) # 多继承方式: class BaseView(object): def dispatch(self, request, *args, **kwargs): if request.session.get('username'): response = super(BaseView,self).dispatch(request, *args, **kwargs)
# 这个self只的是继承的类(比如下面的IndexView类),这个dispatch最终还会找到另一个父类View中的dispatch return response else: return redirect('/login.html') class IndexView(BaseView,View): # 通过多继承的方式,不影响原来的,要需要验证则继承两个;如果不验证,继承一个View即可 # 为什么要继承View, 因为比如as_view这个方法在BaseView中没有定义,根据继承的原理,要使用的方法会按广度优先查找所有父类(super也是这个道理) # tornado中没有集成session,实现类似的功能时,推荐使用多继承的方式,原因一:第三方开发者自己写类的时候继承object即可,原因二:调用方如果需要使用,则多继承一个类即可。 def get(self,request,*args,**kwargs): return HttpResponse(request.session['username'])
方式三:通过装饰器实现
urls.py 和 login.html 都同上
views.py
from django.shortcuts import render,HttpResponse, redirect from django.views import View from django.utils.decorators import method_decorator # FBV、CBV class LoginView(View): def dispatch(self, request, *args, **kwargs): return super(LoginView,self).dispatch(request, *args, **kwargs) def get(self, request, *args,**kwargs): return render(request,'login.html') def post(self,request, *args,**kwargs): user = request.POST.get('user') pwd = request.POST.get('pwd') if user == 'alex' and pwd == '123': # session_id = asdfasdfasdfasdf # asdfasdfasdfasdf = {username: 'alex'} request.session['username'] = user # 这行代码做了如上两个操作 return redirect('/index.html') return render(request, 'login.html',{'msg': '去你的'}) # 定义装饰器函数 def auth(func): def inner(request,*args,**kwargs): if request.session.get('username'): obj = func(request,*args,**kwargs) return obj else: return redirect('/login.html') return inner # @method_decorator(auth,name='post') #写在类上,则需要通过name参数,指定修饰那个方法 《==等价于==》 写在类内部指定的方法上 # Django 规定必须通过method_decorator这种方式调用装饰器 class IndexView(View): @method_decorator(auth) # 写在类内部指定的方法上,则装饰指定方法 # Django 规定必须通过method_decorator这种方式调用装饰器 def get(self,request,*args,**kwargs): return HttpResponse(request.session['username'])
总结,可以定义在三个地方,定义在类上指定单个方法和定义在类内部单个方法效果一样,另一种是定义在dispatch上(下个例子中有,或者下面的小结中有)
【特殊情况】当时用csrf_exempt 装饰post方法的时候,只能写在dispatch上(定义在类上指定单个方法和定义在类内部单个方法都不可以),官方的一个bug
urls.py 和 login.html 都同上
views.py
from django.shortcuts import render,HttpResponse, redirect from django.views import View from django.views.decorators.csrf import csrf_exempt,csrf_protect from django.utils.decorators import method_decorator # FBV、CBV class LoginView(View): @method_decorator(csrf_exempt) # csrf_exempt 必须定义在dispatch方法上 def dispatch(self, request, *args, **kwargs): return super(LoginView,self).dispatch(request, *args, **kwargs) def get(self, request, *args,**kwargs): return render(request,'login.html') def post(self,request, *args,**kwargs): user = request.POST.get('user') pwd = request.POST.get('pwd') if user == 'alex' and pwd == '123': # session_id = asdfasdfasdfasdf # asdfasdfasdfasdf = {username: 'alex'} request.session['username'] = user # 这行代码做了如上两个操作 return redirect('/index.html') return render(request, 'login.html',{'msg': '去你的'})
FBV& CBV 小结:
1. FBV & CBV a. FBV -> 函数 CBV -> 类 - dispatch - get获取/post提交 b. 应用:登录验证 继承: 单继承: # class BaseView(View): # def dispatch(self, request, *args, **kwargs): # if request.session.get('username'): # response = super(BaseView,self).dispatch(request, *args, **kwargs) # return response # else: # return redirect('/login.html') # # class IndexView(BaseView): # # def get(self,request,*args,**kwargs): # return HttpResponse(request.session['username']) 多继承: # 多继承方式: # class BaseView(object): # def dispatch(self, request, *args, **kwargs): # if request.session.get('username'): # response = super(BaseView,self).dispatch(request, *args, **kwargs) # return response # else: # return redirect('/login.html') # # class IndexView(BaseView,View): # # def get(self,request,*args,**kwargs): # return HttpResponse(request.session['username']) 装饰器: def auth(func): def inner(request,*args,**kwargs): if request.session.get('username'): obj = func(request,*args,**kwargs) return obj else: return redirect('/login.html') return inner # @method_decorator(auth,name='get') class IndexView(View): @method_decorator(auth) def dispatch(self, request, *args, **kwargs): if request.session.get('username'): response = super(IndexView,self).dispatch(request, *args, **kwargs) return response else: return redirect('/login.html') @method_decorator(auth) def get(self,request,*args,**kwargs): return HttpResponse(request.session['username']) @method_decorator(csrf_exempt) # 无效 def post(self,request,*args,**kwargs): return HttpResponse(request.session['username']) 特殊:CSRF class IndexView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(LoginView,self).dispatch(request, *args, **kwargs) def get(self,request,*args,**kwargs): return HttpResponse(request.session['username']) def post(self,request,*args,**kwargs): return HttpResponse(request.session['username'])
3. 序列化
方式一:使用serializers
from django.core import serializers user_list = models.UserInfo.objects.all() data = serializers.serialize("json", user_list) data数据如下 [ {"model": "app01.userinfo", "pk": 1, "fields": {"username": "\u5174\u666e", "password": "123123"}}, {"model": "app01.userinfo", "pk": 2, "fields": {"username": "\u94f6\u79cb\u826f", "password": "666"}} ] 缺点:数据格式比较混乱
方式二:使用json.dump的方式序列化 (数据库取值的时候前提是使用values或者values_list)
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^users.html$', views.users), url(r'^get_users.html$', views.get_users), ]
app01/models.py
from django.db import models class UserInfo(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=64) #密码64位,因为MD5加密后的长度就是64
app01/views.py
from django.shortcuts import render,HttpResponse import json from app01 import models from django.core import serializers def users(request): return render(request,'users.html') def get_users(request): response = {'status': True, 'data':None, 'msg': None} # 利用这种方式,将后台是否执行成功,返回的数据和错误信息分别放在字典中 try: user_list = models.UserInfo.objects.values('id', 'username') response['data'] = list(user_list) # 设置返回的数据 except Exception as e: response['status'] = False # 设置执行结果状态 response['msg'] = str(e) # 设置错误信息 return HttpResponse(json.dumps(response))
templates/users.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul id="user_list_id"> </ul> <script src="/static/jquery-3.2.1.js"></script> <!-- 要放置在body的最下方 --> <script> $(function () { <!-- 页面加载完后自动执行 --> initData(); }); function initData() { $.ajax({ url: '/get_users.html', type: 'GET', dataType: 'JSON', // 等价于在success中执行 arg = JSON.parse(arg); success:function (arg) { if(arg.status){ // 判断后台是否执行成功 /* arg.data = [ {id: xxx,username: xxx} ] */ $.each(arg.data,function (index,row) { //循环arg.data的内容,index是索引,row是每行数据 // row = {id: xxx,username: xxx} // console.log(row.id,row.username) // var tag = document.createElement('li'); //对象的方式创建标签,还得设置innerHtml var tag = "<li>" + row.username + "</li>"; //字符串拼接的方式创建标签 $('#user_list_id').append(tag); }) }else{ // 后台执行失败,则返回错误信息给前端 alert(arg.msg); } } }) } </script> </body> </html>
备注,渲染模板有两种方式:
1,模板引擎渲染render
2,ajax 取数据,然后js或jquery来添加到页面上【上面的例子采用的是这种方式】
结果:
【特别注意】但是有一点json.dumps不能序列化时间类型的数据,这个时候需要对json.dumps做定制化,如下:
import json from datetime import date from datetime import datetime class JsonCustomEncoder(json.JSONEncoder): # 对json.JSONEncoder做了一些扩展 def default(self, field): if isinstance(field, datetime): return field.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(field, date): return field.strftime('%Y-%m-%d') else: return json.JSONEncoder.default(self, field) user_list = [ {'id': 1, 'name': 'alex', 'ctime': datetime.now()}, {'id': 2, 'name': 'eric', 'ctime': datetime.now()} ] data = json.dumps(user_list, cls=JsonCustomEncoder) # cls的默认值是JsonCustomEncoder继承的json.JSONEncoder print(data)
如果遇到json.dumps遇到其他不能处理的情况,也可以按照上述的方式处理
执行结果
[{"id": 1, "name": "alex", "ctime": "2017-09-11 07:48:51"}, {"id": 2, "name": "eric", "ctime": "2017-09-11 07:48:51"}]
序列化小结
方式一: from django.core import serializers user_list = models.UserInfo.objects.all() data = serializers.serialize("json", user_list) data数据如下 [ {"model": "app01.userinfo", "pk": 1, "fields": {"username": "\u5174\u666e", "password": "123123"}}, {"model": "app01.userinfo", "pk": 2, "fields": {"username": "\u94f6\u79cb\u826f", "password": "666"}} ] 缺点:数据格式比较混乱 方式二: 前提是使用values或者values_list从数据库中去数据 user_list = models.UserInfo.objects.values('id','username') user_list = list(user_list) 是QuerySet类型变成列表类型 data = json.dumps(user_list) data数据如下 [ {"username": "\u5174\u666e", "id": 1}, {"username": "\u94f6\u79cb\u826f", "id": 2} ] 方式二的问题是不能序列化时间类型数据,解决方案:对json.dumps做定制: import json from datetime import date from datetime import datetime class JsonCustomEncoder(json.JSONEncoder): def default(self, field): if isinstance(field, datetime): return field.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(field, date): return field.strftime('%Y-%m-%d') else: return json.JSONEncoder.default(self, field) user_list = [ {'id':1,'name':'alex','ctime': datetime.now()}, {'id':2,'name':'eric','ctime': datetime.now()} ] data = json.dumps(user_list,cls=JsonCustomEncoder) print(data) 总结: - 模板渲染 - Ajax - 后端json序列化内容传给前端 - 前端:js添加到页面
4. Django Form表单
一、django form实现用户增、删、改、查
仅包含一对多
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login.html', views.LoginView.as_view()), url(r'^users.html$', views.UsersView.as_view()), url(r'^add_user.html', views.AddUserView.as_view()), url(r'^edit_user_(\d+).html$', views.EditUserView.as_view()), url(r'^del_user_(\d+).html$', views.DelUserView.as_view()), ]
views.py
from django.shortcuts import render,HttpResponse, redirect from django.views import View from django.views.decorators.csrf import csrf_exempt,csrf_protect from django.utils.decorators import method_decorator from django.forms import Form from django.forms import fields from django.forms import widgets from app01 import models class AuthView(object): def dispatch(self, request, *args, **kwargs): if request.session.get('user_info'): response = super(AuthView,self).dispatch(request, *args, **kwargs) return response else: return redirect('/login.html') # FBV、CBV class LoginView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(LoginView,self).dispatch(request, *args, **kwargs) def get(self, request, *args,**kwargs): return render(request,'login.html') def post(self,request, *args,**kwargs): user = request.POST.get('user') pwd = request.POST.get('pwd') obj = models.UserInfo.objects.filter(username=user,password=pwd).first() if obj: request.session['user_info'] = {'id':obj.id,'username': obj.username} return redirect('/users.html') return render(request, 'login.html',{'msg': '去你的'}) class UsersView(AuthView,View): def get(self,request,*args,**kwargs): user_list = models.UserInfo.objects.all() return render(request,'users.html',{'user_list':user_list}) class UserForm(Form): ''' user = fields.CharField( required=True, error_messages={'required':'用户名不能为空'} ) # user 要数据库字段名字一致,前端input的name字段 email = fields.EmailField( required=True, error_messages={'required':'邮箱不能为空','invalid':'邮箱格式错误'} ) ip = fields.GenericIPAddressField( required=True, error_messages={'required': 'ip不能为空', 'invalid': 'ip格式错误'} ) user_type = fields.ChoiceField(choices=[(1,'上海'),(2,'北京'),(3,'广州')]) ''' username = fields.CharField( required=True, error_messages={'required': '用户名不能为空'} ) # user 要数据库字段名字一致,前端input的name字段 password = fields.CharField( required=True, error_messages={'required': '邮箱不能为空', 'invalid': '邮箱格式错误'} ) ut_id = fields.ChoiceField(choices=[]) # 每次类实例化的时候会执行__init__方法,但是其他字段(比如上面的ut_id)和方法不会执行,只是在加载的时候执行 # 所以需要将动态字段(比如ut_id的ChoiceField)放在__init__中 def __init__(self,*args,**kwargs): super(UserForm,self).__init__(*args,**kwargs) self.fields['ut_id'].choices = models.UserType.objects.values_list('id', 'title') # self.fields 指的是类中定义的所有字段【django会把定义的所有字段拷贝到self.fields中】 class AddUserView(AuthView,View): def get(self,request,*args,**kwargs): form = UserForm() return render(request,'add_user.html',{'form':form}) def post(self,request,*args,**kwargs): form = UserForm(data=request.POST) # 传入用户输入的数据 # 将用户提交的数据和UserForm中定义规则进行匹配 if form.is_valid(): # 把所有正确数据获取到 print(form.cleaned_data) models.UserInfo.objects.create(**form.cleaned_data) # 可以这样使用cleaned_data的前提是:用户提交的数据的input中的name【这个和自定义的UserForm类中的字段对应的】和数据库的字段名一致 return redirect('/users.html') else: print(form.errors) return render(request, 'add_user.html', {'form':form}) class EditUserView(AddUserView,View): def get(self, request, pk): obj = models.UserInfo.objects.filter(id=pk).first() form = UserForm(initial={'username':obj.username,'password':obj.password,'ut_id':obj.ut_id}) return render(request,'edit_user.html',{'form':form}) def post(self, request, pk): form = UserForm(data=request.POST) if form.is_valid(): models.UserInfo.objects.filter(id=pk).update(**form.cleaned_data) return redirect('/users.html') else: print(form.errors) return render(request, 'edit_user.html', {'form': form}) class DelUserView(AuthView, View): def get(self, request, pk): models.UserInfo.objects.filter(id=pk).delete() return redirect('/users.html')
add_user.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>添加用户</h1> <form method="post" novalidate> <!--novalidate不让浏览器做验证,这个验证功能只有个别浏览器才有--> {% csrf_token %} <p> 用户名:{{ form.username }} {{form.errors.username.0}} </p> <p> 密码:{{ form.password }} {{form.errors.password.0}} </p> <p> 用户类型:{{ form.ut_id }} {{form.errors.ut_id.0}} </p> <input type="submit" value="提交"> </form> </body> </html>
edit_user.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>编辑用户</h1> <form method="post" novalidate> <!--novalidate不让浏览器做验证,这个验证功能只有个别浏览器才有--> {% csrf_token %} <p> 用户名:{{ form.username }} {{form.errors.username.0}} </p> <p> 密码:{{ form.password }} {{form.errors.password.0}} </p> <p> 用户类型:{{ form.ut_id }} {{form.errors.ut_id.0}} </p> <input type="submit" value="提交"> </form> </body> </html>
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- action不写,提交到当前所在的地址 --> <form method="POST" > <input type="text" name="user" /> <input type="text" name="pwd" /> <input type="submit" value="提交"/>{{ msg }} </form> </body> </html>
users.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="/add_user.html">添加</a> <table border="1"> <thead> <tr> <th>ID</th> <th>用户名</th> <th>密码</th> <th>操作</th> </tr> </thead> <tbody> {% for row in user_list %} <tr> <td>{{ row.id }}</td> <td>{{ row.username }}</td> <td>{{ row.password }}</td> <td><a href="/edit_user_{{row.id}}.html">编辑</a> <a>删除</a></td> <!--<td><a href="/edit_user_" + {{row.id}} + ".html">编辑</a> <a>删除</a></td>--> <!--href="/edit_user_" + {{row.id}} + ".html" # 这种方式不可以,因为渲染后的不是a标签--> </tr> {% endfor %} </tbody> </table> </body> </html>
数据库表app01_userinfo
数据库表app01_type
最终效果如下【可添加、删除、修改】======【但是只有一对多的关系,比如用户和用户类型,没有多对多,下个例子介绍多对多】
添加页面
包含一对多和多对多
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^login.html$', views.LoginView.as_view()), url(r'^users.html$', views.UsersView.as_view()), url(r'^add_user.html$', views.AddUserView.as_view()), url(r'^edit_user_(\d+).html$', views.EditUserView.as_view()), url(r'^del_user_(\d+).html$', views.DelUserView.as_view()), url(r'^register.html$', views.register), ]
views.py
from django.shortcuts import render, HttpResponse, redirect from django.views import View from django.views.decorators.csrf import csrf_exempt,csrf_protect from django.utils.decorators import method_decorator from app01 import models from django.forms import Form from django.forms import fields from django.forms import widgets class AuthView(object): def dispatch(self, request, *args, **kwargs): if request.session.get('user_info'): response = super(AuthView, self).dispatch(request, *args, **kwargs) return response else: return redirect('/login.html') # CBV 方式 class LoginView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(LoginView, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return render(request, 'login.html') def post(self,request, *args, **kwargs): user = request.POST.get('user') pwd = request.POST.get('pwd') obj = models.UserInfo.objects.filter(username=user, password=pwd).first() if obj: request.session['user_info'] = {'id': obj.id, 'username': obj.username} return redirect('/users.html') return render(request, 'login.html', {'msg': '去你的'}) class UsersView(AuthView, View): def get(self, request, *args, **kwargs): user_list = models.UserInfo.objects.all() return render(request,'users.html',{'user_list':user_list}) class UserForm(Form): # 定义了一个类 username = fields.CharField( required=True, #默认 error_messages={'required':'用户名不能为空'}, # 自定义错误信息 widget=widgets.TextInput(attrs={'class':'form-control'}) # 通过这种方式可以将django自带的form和bootstrap的样式结合 ) password = fields.CharField( required=True, error_messages={'required': '邮箱不能为空','invalid':'邮箱格式错误'}, # 自定义错误信息,错误信息的key自己百度去 widget = widgets.TextInput(attrs={'class': 'form-control'}) # 通过这种方式可以将django自带的form和bootstrap的样式结合 ) # fields.EmailField() # fields.GenericIPAddressField(protocol='ipv4') ut_id = fields.ChoiceField( # ut_id 是ForeignKey自动生成的字段 choices=[], widget=widgets.Select(attrs={'class':'form-control'}) # widgets.Select和bootstrap的样式结合 ) role_id = fields.MultipleChoiceField( choices=[], widget=widgets.SelectMultiple(attrs={'class':'form-control'}) # widgets.SelectMultiple和bootstrap的样式结合 ) def __init__(self,*args,**kwargs): # __new__ 方法,在__init__ 方法之前执行 super(UserForm,self).__init__(*args,**kwargs) # self.fields已经有所有拷贝的字段,每次初始化的时候更新动态数据 self.fields['ut_id'].choices = models.UserType.objects.values_list('id','title') self.fields['role_id'].choices = models.Role.objects.values_list('id','caption') class AddUserView(AuthView, View): def get(self, request, *args, **kwargs): form = UserForm() # 创建类对象,并传递给前端 get的时候不传值 return render(request, 'add_user.html', {'form': form}) # 包含:form.对象和form.errors到前端 def post(self, request, *args, **kwargs): form = UserForm(data=request.POST) # post的时候传入请求数据 # 将用户提交的数据和UserForm中定义规则进行匹配: if form.is_valid(): # is_valid 拿到request.POST中的数据和类中定义的字段规则校验 # 把所有正确数据获取到 # {'username': 'xxxxx', 'password': 'xxxxx', 'ut_id': '1'} # print(form.cleaned_data) # {'username': 'xxxxx', 'password': 'xxxxx', 'ut_id': '1',role_id:} role_id_list = form.cleaned_data.pop('role_id') # [1,2] obj = models.UserInfo.objects.create(**form.cleaned_data) # UserInfo表中没有role_id这个字段,所以pop后才可以添加 obj.rl.add(*role_id_list) # obj.rl拿到的是和obj关联的第三张表的信息(role_id属于的是第三种表的信息) return redirect('/users.html') else: print(form.errors) return render(request, 'add_user.html', {'form': form}) # 包含:form.对象和form.errors到前端 class EditUserView(AuthView, View): def get(self, request, pk): obj = models.UserInfo.objects.filter(id=pk).first() role_id_list = obj.rl.values_list('id') # obj.rl是从UserInfo关联的第三种表中取数据 # print('======role_id_list-1',role_id_list) # <QuerySet [(2,), (3,)]> # print('======role_id_list-2',zip(*role_id_list)) # <zip object at 0x107fc2c48> # print('======role_id_list-3',list(zip(*role_id_list))) # [(2, 3)] # print('======role_id_list-4',list(zip(*role_id_list))[0]) # (2, 3) v = list(zip(*role_id_list))[0] if role_id_list else [] # 返回给前端的role_id应该是个列表,所以才有上面的zip操作 form = UserForm(initial={'username': obj.username, 'password': obj.password, 'ut_id': obj.ut_id, 'role_id': v}) return render(request, 'edit_user.html', {'form': form}) def post(self,request,pk): form = UserForm(data=request.POST) if form.is_valid(): # # {'username': 'xxxxx', 'password': 'xxxxx', 'ut_id': '1',role_id:} role_id = form.cleaned_data.pop('role_id') # 用户表更新 query = models.UserInfo.objects.filter(id=pk) query.update(**form.cleaned_data) # UserInfo表中没有role_id这个字段,所以pop后才可以update obj = query.first() obj.rl.set(role_id) # set == clear + add 操作 # obj.rl拿到的是和obj关联的第三张表的信息(role_id属于的是第三种表的信息) return redirect('/users.html') else: print(form.errors) return render(request, 'edit_user.html', {'form': form}) class DelUserView(AuthView,View): def get(self,request,pk): models.UserInfo.objects.filter(id=pk).delete() return redirect('/users.html') # ########################### 注册功能 ################################# class RegisterForm(Form): user = fields.CharField(required=True, min_length=6, max_length=18) email = fields.EmailField(required=True, min_length=6, max_length=18) password = fields.CharField(min_length=12) import json def register(request): if request.method == 'GET': form = RegisterForm() return render(request, 'register.html', {'form': form}) else: response = {'status': True, 'data': None,'msg': None} # 通过这种方式,将后台是否执行成功,执行结果和错误信息一起返回给前端 form = RegisterForm(request.POST) if form.is_valid(): print(form.cleaned_data) # 数据库中添加一条数据 # return redirect('/login.html') # ajax跳转,错!错!错!!,ajax 不认识 return redirect # return HttpResponse(json.dumps(response)) 而是应该用HttpResponse,写在了下面 else: response['status'] = False response['msg'] = form.errors # form.errors 是个对象,但是有__str__方法,所以打印出来是字符串 # return HttpResponse(json.dumps(response)) 而是应该用HttpResponse,写在了下面 return HttpResponse(json.dumps(response))
models.py
from django.db import models class UserType(models.Model): """ 用户类型 """ title = models.CharField(max_length=32) def __str__(self): # return:如果不定义这个str方法,前端显示外键ForeignKey的是对象Object return self.title class Role(models.Model): caption = models.CharField(max_length=32) def __str__(self): # return:如果不定义这个str方法,前端显示ManyToMany显示的是对象Object return self.caption class UserInfo(models.Model): """ 用户表 """ username = models.CharField(max_length=32) password = models.CharField(max_length=64) ut = models.ForeignKey(UserType,null=True,blank=True) rl = models.ManyToManyField(Role) # obj = UserInfo() # obj.r1.values('id','caption') # [Role,ROle]
templates/add_user.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>添加用户</h1> <form method="POST" novalidate> {% csrf_token %} <!--{{ form.as_p }} 生成form中所有的html标签,as_p 定制性差 --> <!-- 通过 {{from.user }}{{form.email}} 可以定制, 但是没有错误信息(form.errors是所有的错误信息) form.errors.users 返回的是一个列表,字段的一个规则对应一个错误信息,因为可以有多个规则,所以会有多个错误信息 form.errors.users.0 取第一个错误,只要有一个错误,即不可以通过 --> <p> 用户名: {{ form.username }} {{ form.errors.username.0 }} </p> <p> 密码: {{ form.password }} {{ form.errors.password.0 }} </p> <p> 用户类型: {{ form.ut_id }} {{ form.errors.ut_id.0 }} </p> <p> 角色: {{ form.role_id }} {{ form.errors.role_id.0 }} </p> <input type="submit" value="提交" /> </form> </body> </html>
templates/edit_user.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>编辑用户</h1> <form method="POST" novalidate> {% csrf_token %} <!--{{ form.as_p }} 生成form中所有的html标签 --> <p> 用户名: {{ form.username }} {{ form.errors.username.0 }} </p> <p> 密码: {{ form.password }} {{ form.errors.password.0 }} </p> <p> 用户类型: {{ form.ut_id }} {{ form.errors.ut_id.0 }} </p> <p> 角色: {{ form.role_id }} {{ form.errors.role_id.0 }} </p> <input type="submit" value="提交" /> </form> </body> </html>
templates/login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- action不写,提交到当前所在的地址 --> <form method="POST" > <input type="text" name="user" /> <input type="text" name="pwd" /> <input type="submit" value="提交"/>{{ msg }} </form> </body> </html>
templates/register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form id="f1"> {% csrf_token %} <p>用户名:{{ form.user }}</p> <!--不能再这块用{{form.errors}}渲染错误,因为ajax提交,页面不会被重新渲染{{form.errors.user.0}}永远为空--> <p>密码:{{ form.password }}</p> <p>邮箱:{{ form.email }}</p> <input type="button" value="提交" onclick="submitForm();" /> </form> <script src="/static/jquery-3.2.1.js"></script> <script> function submitForm() { $('#f1 .error').remove(); //将之前的错误信息清除,否则错误信息会多次追加在后面 $.ajax({ url: '/register.html', type: 'POST', data: $('#f1').serialize(), //serserialize 把所有数据拿到 ,cdrf_token 也拿到 dataType: 'JSON', success:function (arg) { if(arg.status){ location.href = "/login.html"; //ajax中通过【location.href跳转到新的页面】 }else{ /* arg.msg = { email: ['xxxxx',] password: ['xxxxx',] user: ['xxxxx',] } */ $.each(arg.msg,function (k,v) { var tag = document.createElement('span'); tag.innerHTML = v[0]; tag.className = "error"; //通过对象的方式创建标签【推荐】,当然也可以通过字符串拼接的方式 // <span class='error'>v[0]</span> $('#f1 input[name="'+k+'"]').after(tag); }) } } }) } </script> </body> </html>
templates/users.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="/add_user.html">添加</a> <table border="1"> <thead> <tr> <th>ID</th> <th>用户名</th> <th>密码</th> <th>类型</th> <th>角色</th> <th>操作</th> </tr> </thead> <tbody> {% for row in user_list %} <tr> <td>{{ row.id }}</td> <td>{{ row.username }}</td> <td>{{ row.password }}</td> <td>{{ row.ut }}</td> <!-- 显示外键ForeignKey,models中UserType定义__str__方法--> <td> {% for role in row.rl.all %} {{ role }} <!-- 显示ManyToMany,models中Role定义__str__方法--> {% endfor %} </td> <td><a href="/edit_user_{{ row.id }}.html">编辑</a> <a href="/del_user_{{ row.id }}.html">删除</a></td> </tr> {% endfor %} </tbody> </table> </body> </html>
最终效果:
展示:
添加:
编辑【注意用户id的传入方式】
删除,应该有个模态对话框的提醒,后续加上。
Django的Form表单小结
Form表单验证(功能1:用户请求验证 + 功能2:生成HTML标签) 示例:用户管理 a. 添加用户页面 - 显示HTML标签 - 提交:数据验证 - 成功之后保存 - 错误显示错误信息 总结: 1. 创建Form类(本质就是正则表达式的集合) from django.forms import Form from django.forms import fields from django.forms import widgets class UserForm(Form): username = fields.CharField( required=True, error_messages={'required':'用户名不能为空'}, widget=widgets.TextInput(attrs={'class':'form-control'}) ) password = fields.CharField( required=True, error_messages={'required': '邮箱不能为空','invalid':'邮箱格式错误'}, widget = widgets.TextInput(attrs={'class': 'form-control'}) ) # fields.EmailField() # fields.GenericIPAddressField(protocol='ipv4') ut_id = fields.ChoiceField( choices=[], widget=widgets.Select(attrs={'class':'form-control'}) ) role_id = fields.MultipleChoiceField( choices=[], widget=widgets.SelectMultiple(attrs={'class':'form-control'}) ) def __init__(self,*args,**kwargs): super(UserForm,self).__init__(*args,**kwargs) # self.fields已经有所有拷贝的字段 self.fields['ut_id'].choices = models.UserType.objects.values_list('id','title') self.fields['role_id'].choices = models.Role.objects.values_list('id','caption') 2. 只是生成HTML标签: 添加页面 form = MyForm() {{form.xx}} 3. 带默认值的HTML标签: 编辑页面 form = MyForm(initial={'xx': xxx}) {{form.xx}} 4. 提交数据 form = MyForm(data=request.POST) if form.is_valid(): print(form.cleaned_data) else: print(form.errors) 问题:下拉框数据无法实时更新 class UserForm(Form): username = fields.CharField( required=True, error_messages={'required':'用户名不能为空'} ) password = fields.CharField( required=True, error_messages={'required': '邮箱不能为空','invalid':'邮箱格式错误'} ) ut_id = fields.ChoiceField(choices=[]) def __init__(self,*args,**kwargs): super(UserForm,self).__init__(*args,**kwargs) self.fields['ut_id'].choices = models.UserType.objects.values_list('id','title') 示例:只用表单验证的功能(Ajax提交),注册&登录 定律: 【个数少,内容少】 页面摸态对话框:添加+删除+编辑 =》 ajax(无刷新) + Djaogo Form组件 - 用:验证 - 生成HTML(可用可不用,因为提交页面不刷新,所以即使是自己写的input,上次输入的数据也会保留) 【适用于:数据个数多;博客】 新URL方式:添加+删除+编辑 =》 Form标签提交(页面刷新) + + Djaogo Form组件 - 用:验证功能 - 用:生成HTML功能){{form.username}})(不要用自己输入input,无保留上次输入的内容) 个人: - 删除利用模态对话框,确认 - 添加+修改: 新URL方式
二、Form验证
forms中的CharField 和数据库中的CharField 没关系
forms中的CharField:前端验证的时候有用,要求用户提交的类型是字符串
models中的EmailField 只有在admin和modelForm,但是在数据库中都存放的是字符串
forms中的CharField 和 EmailField 就是django自带的默认的正则表达式方式验证
默认的验证规则
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^userinfo/', views.userinfo), url(r'^add_user/', views.add_user), url(r'^test/', views.test), ]
app01 views.py
from django.shortcuts import render, HttpResponse, redirect from app01 import models from app01.forms import UserInfoForm def userinfo(request): user_list = models.UserInfo.objects.all() return render(request, 'userinfo.html', {'user_list':user_list}) def add_user(request): if request.method == 'GET': form = UserInfoForm() return render(request, 'add_user.html', {'form':form}) else: form = UserInfoForm(data=request.POST) if form.is_valid(): models.UserInfo.objects.create(**form.cleaned_data) return redirect('/userinfo/') return render(request, 'add_user.html', {'form': form}) def test(request): print('test......') return HttpResponse('OK666')
app01 models.py
# -*- coding: utf-8 -*- from django.db import models class Depart(models.Model): """ 部门表 """ title = models.CharField(max_length=32) # 数据中的string类型 class UserInfo(models.Model): """ 用户表 """ name = models.CharField(max_length=32) email = models.CharField(max_length=32) # admin,ModelForm,数据中的string类型 phone = models.CharField(max_length=32) dp = models.ForeignKey(to='Depart', to_field='id')
app01 forms.py
# -*- coding:utf-8 -*- from django.forms import Form from django.forms import fields from app01 import models class UserInfoForm(Form): name = fields.CharField( required=True, # 默认必须要填写 min_length=6, # 长度最小为6 max_length=12 # 长度最大为12 ) # 要求用户提交数据是字符串 email = fields.EmailField() # 要求用户提交数据是字符串,且满足邮箱正则 phone = fields.CharField() dp_id = fields.ChoiceField( choices=[] ) def __init__(self,*args,**kwargs): # super的init的作用:找到类中的所有静态字段拷贝并且赋值给self.fields super(UserInfoForm,self).__init__(*args, **kwargs) # 在类的构造方法中更新动态字段 self.fields['dp_id'].choices = models.Depart.objects.values_list('id', 'title')
templates add_user.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="POST" novalidate> {% csrf_token %} <p> 用户名:{{ form.name }} {{ form.errors.name.0 }} </p> <p> 邮箱:{{ form.email }} {{ form.errors.email.0 }}</p> <p> 手机:{{ form.phone }} {{ form.errors.phone.0 }}</p> <p> 部门:{{ form.dp_id }} {{ form.errors.dp_id.0 }}</p> <input type="submit" value="提交" /> </form> </body> </html>
templates userinfo.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="/add_user/">添加</a> <ul> {% for item in user_list %} <li>{{ item }}</li> {% endfor %} </ul> </body> </html>
效果:
自定义验证规则
a. 对象
方式一:validators=[RegexValidator(正则表达式,错误信息),RegexValidator(正则表达式,错误信息)]
缺点:只能支持正则表达式
特点:可以支持多个正则表达式,从前到后一个个进行验证
app01 forms.py
# -*- coding:utf-8 -*- from django.forms import Form from django.forms import fields from app01 import models from django.core.validators import RegexValidator class UserInfoForm(Form): name = fields.CharField( required=True, min_length=6, max_length=12 ) # 要求用户提交数据是字符串 email = fields.EmailField() # 要求用户提交数据是字符串,且满足邮箱正则 phone = fields.CharField() # 方式一:RegexValidator对象 phone = fields.CharField( validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')] # 自定义验证规则进行验证 ) dp_id = fields.ChoiceField( choices=[] ) #使用dp_id更方便,而不要使用dp,因为models中的外键数据库中存放的是dp_id def __init__(self,*args,**kwargs): # 找到类中的所有静态字段拷贝并且赋值给self.fields super(UserInfoForm,self).__init__(*args,**kwargs) self.fields['dp_id'].choices = models.Depart.objects.values_list('id', 'title')
效果:
====
b. 函数方法(了解即可,不用)
方式二:validators=[函数,] ============》理解即可,不建议使用
原理:会把用户提交的数据传递给函数的形参,抛出异常内部会捕捉到并显示给前端页面
app01 forms.py
# -*- coding:utf-8 -*- from django.forms import Form from django.forms import fields from app01 import models import re from django.core.exceptions import ValidationError def mobile_validata(value): mobile_re = re.compile(r'(13[0-9])|15[0123456789]|17[678]|18[0-9]|14[57][0-9]{8}$') if not mobile_re.match(value): raise ValidationError('手机号格式错误') class UserInfoForm(Form): name = fields.CharField( required=True, min_length=6, max_length=12 ) # 要求用户提交数据是字符串 email = fields.EmailField() # 要求用户提交数据是字符串,且满足邮箱正则 # 方式二:函数 phone = fields.CharField( validators=[mobile_validata, ] ) dp_id = fields.ChoiceField( choices=[] ) # 使用dp_id更方便,而不要使用dp,因为models中的外键数据库中存放的是dp_id def __init__(self,*args,**kwargs): # 找到类中的所有静态字段拷贝并且赋值给self.fields super(UserInfoForm,self).__init__(*args,**kwargs) self.fields['dp_id'].choices = models.Depart.objects.values_list('id', 'title')
效果
c. clean_字段名称 方法
方法三:
优点:不仅支持正则表达式,还支持其他验证方式(比如去数据库验证手机号是否存在等)
特点:**********方法中只能取当前字段的值 **********
app01 forms.py
# -*- coding:utf-8 -*- from django.forms import Form from django.forms import fields from app01 import models import re from django.core.exceptions import ValidationError class UserInfoForm(Form): name = fields.CharField( required=True, min_length=6, max_length=12 ) # 要求用户提交数据是字符串 email = fields.EmailField() # 要求用户提交数据是字符串,且满足邮箱正则 # 方法三:当前类的方法中,方法名称要求: clean_phone方法 phone = fields.CharField() dp_id = fields.ChoiceField( choices=[] ) #使用dp_id更方便,而不要使用dp,因为models中的外键在数据库中存放的是dp_id def __init__(self,*args,**kwargs): # 找到类中的所有静态字段拷贝并且赋值给self.fields super(UserInfoForm,self).__init__(*args,**kwargs) self.fields['dp_id'].choices = models.Depart.objects.values_list('id', 'title') def clean_phone(self): """ 切勿瞎取值 :return: 必须有返回值, """ # 去取用户提交的其他值:可能是错误的,可能是正确,所以不要取,因为可能值还不存在, value = self.cleaned_data['phone'] # 此处的self代表的是form,所以可以去到cleaned_data,【钩子方法,只能对当前字段(phone)做操作】
# 但是只能取函数对应的字段!!只能取函数对应的字段!!只能取函数对应的字段 mobile_re = re.compile(r'(13[0-9])|15[0123456789]|17[678]|18[0-9]|14[57][0-9]{8}$') if not mobile_re.match(value): raise ValidationError('手机号格式错误666') if models.UserInfo.objects.filter(phone=value).count(): raise ValidationError('手机号码已经存在') return value #这个值被以后的clean_data 取到,【可以对返回值做自定义的操作,比如 return value + '6666'】
效果
备注:使用方法三,不要瞎取值(只能取当前字段的值),因为可能还没有存在(__dict__ 无序)
方法一和方法三可以同时存在,顺序:先走正则表达式验证,再走钩子方法【clean_字段名 方法】
if form.is_valid():
- 验证规则执行顺序
- 第一个字段的正则,钩子函数(方法中只能取当前字段的值)
- 第二个字段的正则,钩子函数
........
备注:第一个字段和第二个字段是哪个字段是无序的,这也是clean_字段名 这个钩子函数不能随意取值的原因。
整体验证【应用场景,确认两次输入的密码是否一致】
Django提供的一个兜底验证方法,即在每一个字段都验证过后,还会再对整体进行验证【钩子方法 :clean方法来实现(必须有返回值)】
重新定义clean方法【在源码(is_valid......error......full_clean....)中可以看到是clean函数是个Hook】,
所以Django整体的验证规则流程是@@@@@@@@@@@@@@@@@@@@@@@@
字段1 正则 + 钩子函数
字段2 正则 + 钩子函数
。。。
。。。
整体验证方法clean
eg:
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^register/', views.register), ]
app01 views.py
from django.shortcuts import render,HttpResponse,redirect from app01 import models from app01.forms import RegisterForm def register(request): if request.method == "GET": # form = RegisterForm(initial={'city':[1,2],'name':'alex'}) form = RegisterForm() return render(request,'register.html',{'form':form}) else: form = RegisterForm(request.POST) if form.is_valid(): print(form.cleaned_data['pwd']) print(form.cleaned_data['pwd_confirm']) # 当然这块也可以验证,两次输入的密码是否一致,但是比较后端,比较low逼,建议还是用clean验证 return render(request, 'register.html', {'form': form})
app01 forms.py
# -*- coding:utf-8 -*- from django.forms import Form from django.forms import fields from django.forms import widgets class RegisterForm(Form): name = fields.CharField() email = fields.EmailField() phone = fields.CharField() city = fields.ChoiceField( choices=[(0,"上海"),(1,'北京')], widget=widgets.Select(attrs={'class': 'c1'}) ) pwd = fields.CharField() pwd_confirm = fields.CharField() def clean(self): pwd = self.cleaned_data['pwd'] pwd_confirm = self.cleaned_data['pwd_confirm'] if pwd == pwd_confirm: ## 验证两次输入的密码是否一致 return self.cleaned_data else: from django.core.exceptions import ValidationError # self.add_error('pwd', ValidationError('密码输入不一致')) self.add_error('pwd_confirm', ValidationError('密码输入不一致')) # 给指定字段添加错误信息ValidationErrot() return self.cleaned_data
app01 models.py (这块的验证其实和后端数据库的表无关。。。。。)
from django.db import models class Depart(models.Model): """ 部门表 """ title = models.CharField(max_length=32) # 数据中的string类型 def __str__(self): return self.title class Meta: verbose_name_plural = "部门表" #在admin里显示的时候不显示Depart object 而显示部门表 class Role(models.Model): name = models.CharField(max_length=32) def __str__(self): return self.name class UserInfo(models.Model): """ 用户表 """ name = models.CharField(max_length=32) email = models.CharField(max_length=32) # admin,ModelForm,数据中的string类型 phone = models.CharField(max_length=32) pwd = models.CharField(max_length=64) dp = models.ForeignKey(to='Depart',to_field='id') roles = models.ManyToManyField("Role") def __str__(self): return self.name class Meta: verbose_name_plural = "用户表"
效果:
看源码参考(查找顺序为:is_valid......error......full_clean....)
_clean_fields
_clean_form
_clean_post(不用,因为它的功能,下面的clean都可以实现)
clean
三、Form常用组件
class RegisterForm(Form): name = fields.CharField( widget=widgets.TextInput(attrs={'class': 'c1'}) )
效果
email = fields.EmailField( widget=widgets.EmailInput(attrs={'class':'c1'}) )
效果
phone = fields.CharField( widget=widgets.Textarea(attrs={'class':'c1'}) )
效果
pwd = fields.CharField( widget=widgets.PasswordInput(attrs={'class':'c1'}) ) pwd_confirm = fields.CharField( widget=widgets.PasswordInput(attrs={'class': 'c1'}) )
效果
# 单选:select city = fields.ChoiceField( choices=[(0,"上海"),(1,'北京')], widget=widgets.Select(attrs={'class': 'c1'}) )
效果
# 多选:select city = fields.MultipleChoiceField( initial=[1,3], # #默认1,3被选中 choices=[(1,"上海"),(2,'北京'),(3,'广州'),(4,'深圳')], widget=widgets.SelectMultiple(attrs={'class': 'c1'}) )
效果
# 单选:checkbox city = fields.CharField( widget=widgets.CheckboxInput() )
效果
# 多选:checkbox city = fields.MultipleChoiceField( initial=[1, 3], # #默认1,3被选中 choices=[(1, "上海"), (2, '北京'), (3, '广州'), (4, '深圳')], widget=widgets.CheckboxSelectMultiple )
效果
# 单选:radio city = fields.CharField( initial=2, #默认哪个被选中 widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),)) )
效果
5. 中间件
1. 本质及应用
中间件的本质是类,类中包含函数。
- 中间件执行时机:请求到来,请求返回时 - 中间件是一个类: def process_request(self,request): print('m2.process_request') def process_response(self,request, response): print('m2.prcess_response') return response - 应用: - 请求日志(如记录所有请求所有请求的url,来源ip等) - 用户登录认证(这样就不用再每个视图函数中添加认证的装饰器了!!!)
Django中的http请求大致流程示意图如下:
备注
所有的请求的进出都会经过中间件(所以当要对所有请求都做某一处理的时候,可以使用中间件来实现)
request中的META信息记录所有请求数据信息
2. process_request和process_response
如果一个中间件类只定义process_request和process_response,那么大致流程如下:
例子1(正常流程处理):
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^test/', views.test), url(r'^login', views.login), ]
app01 views.py
from django.shortcuts import HttpResponse,render def test(request): int('fsfas') # 主动触发一个异常 print('test......') return HttpResponse('OK666') def login(request): if request.method == 'GET': return render(request,'login.html')
md middleware.py
#! /usr/bin/env python # -*- coding: utf-8 -*- # __author__ = "wuxiaoyu" # Date: 2017/9/17 from django.utils.deprecation import MiddlewareMixin # deprecation中的类表示即将被废弃,新版Django中如果下面报导入md模块报错,需要下面自己定义这个类 # 当使用上面的from ... import ...报错的时候,在自定义这个类 class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): # 新版本自定义类的话,这个名字可以随意,但是为了和老版本兼容,所以还这么起名字 response = self.process_response(request, response) return response class M1(MiddlewareMixin): # 自定义的中间件要继承自MiddlewareMixin def process_request(self,request): print('m1.process_request') def process_response(self,request,response): print('m1.process_respone') return response
settings.py
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'md.middleware.M1', # 注意每行后面都要加逗号 ]
当访问http://127.0.0.1:9000/test/的时候,后台打印的日志如下
说明了,请求处理流程是:中间件的process_request------> url路由映射------>视图函数-------->中间件的process_response
每个中间件至少包含两个方法是不对的(包含一个也是可以滴...),简单来说可以包含 process_request,process_respone【必须返回response】或者高级用法可以包含下面的process_view 、process_exception
也可以不写process_reponse,这样只进来的时候执行,出去的时候不执行,csrf就是只定义了process_request的方法,没有定义peocess_response
【特殊情况】下如果process_request也设置了返回值
process_request如果有返回值(默认返回None,继续交个下个中间件取处理),则会到此返回。。。。下图中的绿色箭头,所以process_request不能轻易加返回值
应用场景:
1,可以设置ip黑名单,如果访问来源是黑名单中的ip,中间件检测后返回。
2,设置登录验证,把不用登录就能访问的页面,要做一下特殊处理(否则会是死循环),见图下面的例子
例子2(process_request包含返回值的例子):
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^test/', views.test), ]
app01 views.py
from django.shortcuts import HttpResponse,render def test(request): print('test......') return HttpResponse('OK666')
md middleware.py
from django.shortcuts import HttpResponse,redirect class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response class M1(MiddlewareMixin): def process_request(self,request): print('m1.process_request') return HttpResponse('滚') def process_response(self,request, response): print('m1.prcess_response') return response
settings.py
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'md.middleware.M1', ]
效果
例子3(process_request 用做登陆验证的例子):
urls.py 和 views.py 同上
md middleware.py
from django.shortcuts import HttpResponse,redirect class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response class M1(MiddlewareMixin): def process_request(self,request): print('m1.process_request') if request.path_info == '/login': # 把不用登录就能访问的页面, return None user_info = request.session.get('user_info') if not user_info: return redirect('/login') # 重新发起一个请求,又经过M1中间件,导致死循环,解决方法是添加上面的判断 def process_response(self,request, response): print('m1.prcess_response') return response
效果
访问任何页面,都可以跳转到login页面,如果登陆后,可直接访问
当包含多个中间件的时候,安装settings.py中MIDDLEWARE定义的中间件顺序,依次向下处理,顺序为:
M1的process_request----->M2的process_request-----> url路由映射----->视图函数-----> M2的process_response ----->M1的process_response
例子3(包含多个中间件的时候):
urls.py 和 views.py同上
md middleware.py
from django.shortcuts import HttpResponse,redirect class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response class M1(MiddlewareMixin): def process_request(self,request): print('m1.process_request') # return HttpResponse('滚') def process_response(self,request, response): print('m1.prcess_response') return response class M2(MiddlewareMixin): def process_request(self,request): print('m2.process_request') def process_response(self,request, response): print('m2.prcess_response') return response
settings.py
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'md.middleware.M1', 'md.middleware.M2', ]
效果如下:
后台打印输出
如果此时 class M1 的process_request有返回值, 那么效果和例2一样。
3. process_view
当包含process_view函数的时候
process_request没有返回值的时候,流程图如下:
例子4(包含process_view且没有返回值):
urls.py 和views.py 同上
md middleware.py
from django.shortcuts import HttpResponse,redirect class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response class M1(MiddlewareMixin): def process_request(self,request): print('m1.process_request') # return HttpResponse('滚') def process_view(self, request, callback, callback_args, callback_kwargs): print('m1.process_view',callback) def process_response(self,request, response): print('m1.prcess_response') return response class M2(MiddlewareMixin): def process_request(self,request): print('m2.process_request') def process_view(self, request, callback, callback_args, callback_kwargs): print('m2.process_view',callback) def process_response(self,request, response): print('m2.prcess_response') return response
效果:
后台打印
如果process_view有返回值(下面的视图函数不再执行) =======》 蓝色箭头指向
例子5(包含process_view且有返回值):
urls.py 和views.py 和settings.py 同上
md middleware.py
from django.shortcuts import HttpResponse,redirect class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response class M1(MiddlewareMixin): def process_request(self,request): print('m1.process_request') # return HttpResponse('滚') def process_view(self, request, callback, callback_args, callback_kwargs): """ 如果有返回值,则不在继续执行,直接到最后一个中间件的response """ print('m1.process_view',callback) return HttpResponse('Process View返回值') def process_response(self,request, response): print('m1.prcess_response') return response class M2(MiddlewareMixin): def process_request(self,request): print('m2.process_request') def process_view(self, request, callback, callback_args, callback_kwargs): print('m2.process_view',callback) def process_response(self,request, response): print('m2.prcess_response') return response
效果
后台打印
4. process_exception
加了exception后的执行流程【可以定义多个exception,但是只要有一个exception捕获到了错误,则直接执行response】
例子6(包含process_exception且有返回值):
urls.py setting.py 同上
views.py
from django.shortcuts import HttpResponse,render def test(request): int('fsfas') # 主动触发一个异常 print('test......') return HttpResponse('OK666')
md middleware.py
from django.shortcuts import HttpResponse,redirect class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response class M1(MiddlewareMixin): def process_request(self,request): print('m1.process_request') # return HttpResponse('滚') def process_view(self, request, callback, callback_args, callback_kwargs): #callback 其实就是函数名func_name """ 如果有返回值,则不在继续执行,直接到最后一个中间件的response """ print('m1.process_view',callback) # return HttpResponse('Process View返回值') def process_exception(self,request,exception): print('m1.process_exception') def process_response(self,request, response): print('m1.prcess_response') return response class M2(MiddlewareMixin): def process_request(self,request): print('m2.process_request') def process_view(self, request, callback, callback_args, callback_kwargs): print('m2.process_view',callback) def process_response(self,request, response): print('m2.prcess_response') return response def process_exception(self,request,exception): print('m2.process_exception') return HttpResponse('500错误')
效果
可以自定义错误返回页面~~~~
中间件流程汇总(网上的图片)
用logging + Middleware记录Django API请求日志
自己写的同时记录请求日志和异常错误信息,参考:https://www.cnblogs.com/robinunix/articles/11477895.html
6. 缓存
Local-memory caching Note that each process will have its own private cache instance, which means no cross-process caching is possible. This obviously also means the local memory cache isn’t particularly memory-efficient, so it’s probably not a good choice for production environments. It’s nice for development. The low-level cache API To provide thread-safety, a different instance of the cache backend will be returned for each thread. 参考: https://docs.djangoproject.com/en/2.2/topics/cache/ https://djangobook.com/low-level-cache-api/ https://kite.com/python/docs/django.core.cache.backends.locmem.LocMemCache
由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者Redis中之前缓存的内容拿到,并返回。
1. 配置
Django中提供了6种缓存方式:
- 开发调试
- 内存
- 文件
- 数据库
- Memcache缓存(python-memcached模块)
- Memcache缓存(pylibmc模块)
a、开发调试
# 此为开始调试用,实际内部不做任何操作 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 引擎 'TIMEOUT': 300, # 缓存超时时间(默认300,None表示永不过期,0表示立即过期) 'OPTIONS':{ 'MAX_ENTRIES': 300, # 最大缓存个数(默认300) 'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3) }, 'KEY_PREFIX': '', # 缓存key的前缀(默认空) 'VERSION': 1, # 缓存key的版本(默认1) 'KEY_FUNCTION' 函数名 # 生成key的函数(默认函数会生成为:【前缀:版本:key】) } } # 自定义key def default_key_func(key, key_prefix, version): """ Default function to generate keys. Constructs the key used by all other methods. By default it prepends the `key_prefix'. KEY_FUNCTION can be used to specify an alternate function with custom key making behavior. """ return '%s:%s:%s' % (key_prefix, version, key) def get_key_func(key_func): """ Function to decide which key function to use. Defaults to ``default_key_func``. """ if key_func is not None: if callable(key_func): return key_func else: return import_string(key_func) return default_key_func
b、内存
# 此缓存将内容保存至内存的变量中 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-snowflake', } } # 注:其他配置同开发调试版本
本地缓存装饰器
import hashlib import time import ujson as json def loc_mem_cache(ttl): def decorator(func): def wrapper(*args, **kwargs): arg = json.dumps({'args': args, 'kwargs': kwargs}, sort_keys=True) chksum = hashlib.blake2b(arg.encode('utf-8')).hexdigest() cache_data_key = '__loc_mem_cache__data_%s__' % chksum cache_ts_key = '__loc_mem_cache__ts_%s__' % chksum now = time.time() data = getattr(func, cache_data_key, None) timestamp = getattr(func, cache_ts_key, 0) if not data or timestamp + ttl < now: data = func(*args, **kwargs) setattr(func, cache_data_key, data) setattr(func, cache_ts_key, now) return data return wrapper return decorator # usage @loc_mem_cache(10) #使用这个装饰器可以将返回结果缓存10s,注意这个的前提是在django的settings中开启LocMemCache def slow_function(): time.sleep(1.0) return 123
c、文件
# 此缓存将内容保存至文件 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/django_cache', } } # 注:其他配置同开发调试版本
d、数据库
# 此缓存将内容保存至数据库 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'my_cache_table', # 数据库表 } } # 注:执行创建表命令 python manage.py createcachetable
e、Memcache缓存(python-memcached模块)
# 此缓存使用python-memcached模块连接memcache CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': '127.0.0.1:11211', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'unix:/tmp/memcached.sock', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] } }
f、Memcache缓存(pylibmc模块)
# 此缓存使用pylibmc模块连接memcache CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '127.0.0.1:11211', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '/tmp/memcached.sock', } } CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] } }
django 默认不支持redis,如果需要使用redis去网上找第三方插件
2、应用
a. 全站使用
使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存 MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', # 其他中间件... 'django.middleware.cache.FetchFromCacheMiddleware', ] CACHE_MIDDLEWARE_ALIAS = "" CACHE_MIDDLEWARE_SECONDS = "" CACHE_MIDDLEWARE_KEY_PREFIX = ""
b. 单独视图缓存
方式一: from django.views.decorators.cache import cache_page @cache_page(60 * 15) def my_view(request): ... 方式二: from django.views.decorators.cache import cache_page urlpatterns = [ url(r'^foo/([0-9]{1,2})/$', cache_page(60 * 15)(my_view)), ###15*60s 参数是时间 ]
c、局部视图使用
a. 引入TemplateTag {% load cache %} b. 使用缓存 {% cache 5000 缓存key %} 缓存内容 {% endcache %}
更多:猛击这里
缓存使用总结:
- 使用 - 全局 MIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', # 其他中间件 'django.middleware.cache.FetchFromCacheMiddleware', ] - 视图函数 from django.views.decorators.cache import cache_page @cache_page(10) def test1(request): import time ctime = time.time() return render(request,'test1.html',{'ctime':ctime}) - 局部模板 {% load cache %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>TEST1 -> {{ ctime }}</h1> {% cache 10 "asdfasdfasdf" %} <h1>TEST1 -> {{ ctime }}</h1> {% endcache %} </body> </html>
应用举例
备注以下三者独立使用,不要混合使用(个人建议)
1,全局缓存(所有的页面都会被缓存)
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^test1/', views.test1), url(r'^test2/', views.test2), ]
views.py
from django.shortcuts import render,HttpResponse,redirect from app01 import models from django.views.decorators.cache import cache_page def test1(request): models.Depart.objects.create(title='管公布') import time ctime = time.time() return render(request,'test1.html',{'ctime':ctime}) def test2(request): import time ctime = time.time() return render(request,'test2.html',{'ctime':ctime})
settings.py
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
CACHE_MIDDLEWARE_SECONDS = 5 #这个的优先级要比下面的TIMEOUT的优先级高
....
....
....
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',# 引擎 'LOCATION': '/tmp/', 'TIMEOUT': 50, # 缓存超时时间(默认300,None表示永不过期,0表示立即过期) 'OPTIONS':{ 'MAX_ENTRIES': 300, # 最大缓存个数(默认300) 'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3) }, # 'KEY_PREFIX': '', # 缓存key的前缀(默认空) # 'VERSION': 1, # 缓存key的版本(默认1) # 'KEY_FUNCTION' 函数名 # 生成key的函数(默认函数会生成为:【前缀:版本:key】) } }
test1.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>TEST1 -> {{ ctime }}</h1> </body> </html>
test2.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>TEST2 - >{{ ctime }}</h1> </body> </html>
http://127.0.0.1:8001/test1/
http://127.0.0.1:8001/test2/
均被缓存5s
2,视图函数缓存
urls.py同上
views.py
from django.shortcuts import render,HttpResponse,redirect from app01 import models from django.views.decorators.cache import cache_page @cache_page(5) #缓存5s def test1(request): models.Depart.objects.create(title='管公布') import time ctime = time.time() return render(request,'test1.html',{'ctime':ctime}) @cache_page(10) #缓存10s def test2(request): import time ctime = time.time() return render(request,'test2.html',{'ctime':ctime})
settings.py(原生的不要加全局缓存的配置哦)
MIDDLEWARE = [ #'django.middleware.cache.UpdateCacheMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', #'django.middleware.cache.FetchFromCacheMiddleware', ]
访问
http://127.0.0.1:8001/test1/ 缓存5s
http://127.0.0.1:8001/test1/ 缓存10s
3,局部模板缓存
urls.py
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^test3/', views.test3), ]
views.py
from django.shortcuts import render,HttpResponse,redirect from app01 import models def test3(request): import time ctime = time.time() return render(request,'test3.html',{'ctime':ctime})
settings.py(原生的不要加全局缓存的配置哦)
MIDDLEWARE = [
#'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
#'django.middleware.cache.FetchFromCacheMiddleware',
]
test3.html
{% load cache %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>TEST1 -> {{ ctime }}</h1>
{% cache 10 "asdfasdfasdf" %}
<h1>TEST1 -> {{ ctime }}</h1>
{% endcache %}
</body>
</html>
访问
http://127.0.0.1:8001/test3/
#############
可以在自定义中间件,继承这个类UPdateCacheMiddlewre,遇到某些url return None,则不缓存了(类似于登录的处理)???
##################
cache_middleware_seconds=10 比 下面全局定义的要高
流程
设置缓存的中间件位置
有process_request 的在最下面
有process_rresponse 的在最上面
7. 信号
问题:如何在数据库中做增加操作时,记录日志
答案:使用django的信号,参考博客:http://www.cnblogs.com/wupeiqi/articles/5246483.html
其他一些使用场景总结
以下情况不要使用signal:
-
signal与一个model紧密相关, 并能移到该model的save()时
-
signal能使用model manager代替时
-
signal与一个view紧密相关, 并能移到该view中时
以下情况可以使用signal:
-
signal的receiver需要同时修改多个model时
-
将多个app的相同signal引到同一receiver中处理时
-
在某一model保存之后将cache清除时
-
无法使用其他方法, 但需要一个被调函数来处理某些问题时
###信号介绍
信号:赛道中障碍物上的信号(车到了障碍物,自动触发)
中间件的某些功能,信号也可以实现,因为有请求到来前的信号,请求结束后这样的信号.
写到和poroject 同名的__init__ 需要把函数注册到信号中..... pymsql的那个也写到这里了????
senser 哪个类执行的操作
中间件和信号的使用区别:中间件主要处理请求到来和结束的时候处理,信号来处理视图函数后面的逻辑
Django内置信号说明
-
django signal 的处理是同步的,勿用于处理大批量任务。
-
django signal 对程序的解耦、代码的复用及维护性有很大的帮助。
- 通常放置于和project同名的__init__.py中,在使用的时候,仅需注册指定账号,则在被触发时,会自动执行,参考这里
- 不同的信号有不同的参数,具体可参考:https://www.cnblogs.com/robinunix/articles/10614369.html
Model signals pre_init # django的modal执行其构造方法前,自动触发 post_init # django的modal执行其构造方法后,自动触发 pre_save # django的modal对象保存前,自动触发 post_save # django的modal对象保存后,自动触发 pre_delete # django的modal对象删除前,自动触发 post_delete # django的modal对象删除后,自动触发 m2m_changed # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发 class_prepared # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发 Management signals pre_migrate # 执行migrate命令前,自动触发 post_migrate # 执行migrate命令后,自动触发 Request/response signals request_started # 请求到来前,自动触发 request_finished # 请求结束后,自动触发 got_request_exception # 请求异常后,自动触发 Test signals setting_changed # 使用test测试修改配置文件时,自动触发 template_rendered # 使用test测试渲染模板时,自动触发 Database Wrappers connection_created # 创建数据库连接时,自动触发
文本示例:
from django.db.models.signals import pre_save, post_save
#1.普通方式绑定 def xxxxxxxx(sender, **kwargs): # xxxxxxxx就是callback函数 print("post_save.xxxxxxxx") print(sender, kwargs) post_save.connect(xxxxxxxx)
#2.decorators方式绑定
from django.dispatch import receiver
from django.core.signals import request_finished
@receiver(request_finished, dispatch_uid="request_finished")
def my_signal_handler(sender, **kwargs):
print("Request finished!================================")
#3.decirators方式绑定并且传参数
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.db.models.signals import pre_save
from django.dispatch import receiver
class User(AbstractUser):
display_name = models.CharField(max_length=255, default='', verbose_name='中文名')
org_id = models.CharField(max_length=63, default='', verbose_name='组织架构 id')
org_path = models.CharField(max_length=255, default='', verbose_name='组织架构路径')
username = models.CharField(max_length=255, default='', verbose_name='MIS账号')
def __str__(self):
return '%s %s' % (self.display_name, self.username)
@receiver(pre_save, sender=User) #我们可以注册只接收特定发送者发送的信号。比如这个例子,指定只需要接收的User这个模型发送的信号
def user_pre_save(sender, instance, **kwargs):
from accounts.utils.user import sync_user_info
sync_user_info(instance.username, instance)
pre_save和post_save使用示例
pre_delete与post_delete使用示例
自定义信号
除了默认的信号外,还可以自定义一些信号。需要以下三个步骤
1 定义信号
import django.dispatch pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
2 注册信号
def callback(sender, **kwargs): print("callback") print(sender,kwargs) pizza_done.connect(callback)
3 触发信号
from 路径 import pizza_done pizza_done.send(sender='seven',toppings=123, size=456)
由于内置信号的触发者已经集成到Django中,所以其会自动调用,而对于自定义信号则需要开发者在任意位置触发。即在任意位置,导入这个函数,然后进行方法调用
参考:https://segmentfault.com/a/1190000008455657
备注:
update方法它不会在模型实例上调用save(),因此不会发出pre_save和post_save信号.如果你想要你的信号接收器被调用,你应该循环查询,并且对于每个模型实例,进行你的更改并自己调用save(). 如下
参考:https://lxkaka.github.io/2017/07/17/django_signals/
update() 并不会触发pre_save, post_save信号 所以当有这样的操作: GroupSettings.objects.filter(business_group=business_group).update(**filterd) 想触发信号发送时,可以这样修改: group = GroupSettings.objects.filter(business_group=business_group).first() if group: for key, value in fileted.items(): setattr(group, key, value) group.save()
当然也可以自定义信号,参考:https://www.jianshu.com/p/02e68fe74323
8. Admin
- Admin
参考博客:http://www.cnblogs.com/wupeiqi/articles/7444717.html
9. 作业
作业:主机管理
用户表(id, user,pwd,email,mm)
业务线(id, name) # 用户表_set
主机表(id host ip port FK(业务线))
用户业务线关系表(id uid bid) ******
1. 登录+注册(Ajax+form组价)
2. FBV&CBV
业务线管理(列表,增加,修改) # 删除对话框确认删除
主机管理(列表,增加,修改) # 删除对话框确认删除
用户管理(列表,增加,修改) # 删除对话框确认删除
3. 分页
4. BootStrap【可选】
如何做:
周一 + 周二: 课上讲的内容理解
周三 : 写作业
周六: 本周不太理解+以前不理解;下午预习
参考博客:
http://www.cnblogs.com/wupeiqi/articles/5246483.html