本节概要
Django详解
前言
有一部分原因是,确实djando的课程有点多;并且,最近又在研究利用python做数据分析时间上耽误了。所以楼主讲所有的课程全部重新观看了一遍,再来撰写博客,其实说起来,django的博客很难写,必须是代码配合着写,尽量做到吧。为了自己,也为了大家,一定会用代码表述出所有的知识点的。
然后就是,加油!2018!希望大家都能跟我一样定下一个小目标并实现它。
关于django项目的新建其实真的没必要多说。对于其余的重点,一一讲起吧。
1、首先Django的请求生命周期
客户端发送请求==》服务器端的url ==》对应服务器端的函数==》返回字符串 《== open html+ sql orm Form表单提交: 提交 -> url -> 函数或类中的方法 ->DB 或者HTML HttpResponse(已经渲染完成的字符串,obj.name已经被目的变量字符串替代) render/ redirect 返回给字符串中的html语言语法,都是经过处理的,即render发送的html跟参数并不是直接返回给用户,而是处理好的标准字符串 用户 <<<<<<<<<<<<<<返回<<<<<<<<<<<<< 字符串 (接收到redirect时)自动发起另外一个请求 --> url ... Ajax: 提交 -> url -> 函数或类(HttpResponse) 返回字符串 <<<<<<<<<<<< location.reload() ==> 刷新 location.href="某个地址" ==> 跳转
2、创建django project
1、django-admin startproject mysite 2、python manage.py startapp cmdb 3、模板路径、静态文件路径、注释csrf
3、路由系统
/index/ -> 函数或类.as_view() /detail-(\d+)/ -> 函数(参数)或类.as_view() (参数) /detail/(?P<nid>\d+) -> 函数(参数)或者类.as_view() (参数) /cmdb/ -> include("app01.urls") /cmdb/ name=‘a1‘ -> include("app01.urls") - 视图中:reverse - 模板语言中:{%url “a1”%} ********* url(r‘^wqefvqwbeqnwdla/‘, views.index, name=‘indexx‘) <form action=‘{%url ‘indexx‘%}‘ method="post"> url(r‘^wqefvqwbeqnwdla/(\d+)/‘, views.index, name=‘indexx‘) => <form action=‘{%url ‘indexx‘ 2%}‘ method="post"> form里面的2是写死的,没办法灵活追踪 ******* urlpatterns = patterns(‘‘, url(r‘^article$‘,‘get_news_index‘ ,name="news_index"), ) <a href="{%url ‘appname:news_index‘%}">资讯</a> 其中appname是该url所在的app的name,而new_index是url(r‘^article$‘,‘get_news_index‘ ,name="news_index")中的name参数 HttpResponseRedirect(reverse("news_index")) # 默认值 url(r‘^index/‘,views.index, {‘name‘:‘dandy‘}), def index(request, name): # 注意这里必须接收name参数,不然报错,因为url里面有**kwargs参数的存在 # 命名空间 /admin/ include(‘app01.urls‘, namespace=‘admin‘) /cmdb/ include(‘app01.urls‘, namespace=‘cmdb‘) app01.urls /index/ name =‘n1‘ # 同时 app01外部设置一下app01的名称、app_name = ‘app01‘ def index(request): reverse(‘admin:n1‘) # 生成urls 模板语言里面: {% url ‘admin:index‘%}
4、视图
request.GET request.POST request.FILES # checkbox 等多选内容 request.POST.getlist() # 上传文件 <form enctype="multipart/form-data"> request.path_info ==> 获取当前请求的网址 文件对象 = request.FILES.get() 文件对象.name => 文件名 文件对象.size =>字节大小 文件对象.chunks() obj = request.FILES.get(‘aaa‘) obj.name f = open(obj.name, mode="wb") for item in obj.chunks(): f.write(item) f.close() FBV:函数 def index(request, *args, **kwargs): .. CBV:类 class Home(views.View): def get(self, request, *args, **kwargs): ... # 视图的其他信息 from django.core.handlers.wsgi import WSGIRequest #从WSGIRequest 下钻可以查到内部environ的方法 request.environ request.environ[‘HTTP_USER_AGENT‘] ==> 查看发送过来的终端,iphone,Android,pc. #根据key可以取很多内容,可以for循环查看
关于请求头:
\r\n\r\n 作为请求头跟body的分割 request.body 所有原生内容存储 request.Meta(...) 用户终端就存储在这里 服务器返回的时候 也是两部分,响应内容和响应头
response[‘name‘] = ‘dandy‘
接受的响应头里面还有上面定制的这组键值对
CBV 和 FBV的装饰器
我们以登陆用户认证为例:
- 装饰器 ==> 用装饰器解决重复用户认证 FBV def auth(func): # FBV def inner(request, *args, **kwargs): v = request.COOKIES.get(‘username‘) if not v: return redirect(‘/app01/temp1‘) return func(request, *args, **kwargs) return inner CBV from django.utils.decorators import method_decorator def auth(func): # FBV def inner(request, *args, **kwargs): v = request.COOKIES.get(‘username‘) if not v: return redirect(‘/app01/temp1‘) return func(request, *args, **kwargs) return inner # @method_decorator(auth, name=‘dispatch‘) class Order(views.View): #@method_decorator(auth) #def dispatch(self, request, *args, **kwargs): # return super(Order, self).dispatch(request, *args, **kwargs) @method_decorator(auth) def get(self,request): v = request.COOKIES.get(‘username‘) return render(request, ‘temp3.html‘) def get(self,request): v = request.COOKIES.get(‘username‘) return render(request, ‘temp3.html‘)
解读下上面的那么多装饰器;首先,CBV需要用django的自带装饰器。如果我们只想作用于一个函数方法可以直接在方法前面加上装饰器@method_decorator(auth),就比如上面例子里面的get方法上面的那个装饰器。但是如果想要作用域所有的函数方法,比较笨的方法就是全部一个个加上装饰器,然而,之前提过一个dispatch函数,执行任何函数前,都会执行这个方法,所以我们可以改造一下这个方法,即继承并加上装饰器,就如图上的dispatch方法上面的装饰器。当然啦,高手正常连这样写都嫌烦,因为要写dispatch的继承再装饰。可以针对整个CBV进行装饰,设定装饰器的定位函数为dispatch,这么看起来就更高大上,@method_decorator(auth, name=‘dispatch‘)
5、模板语言
**************在django的模板语言里面,索引用点,没有中括号**************** # 循环取值 {% for i in dict%} <td>{{%i.item%}}</td> {%endfor%} # 取值 <h1>{{obj.name}}</h1> # if else逻辑判断 {%if a==b%} <p>aaa</p> {%else%} <p>vvv</p> {%endif%} render(request,"*.html", {"obj":1234, "k1":{1,2,3,4}, "k2":{"name":"dandy"}) <h1>{{obj}}</h1> <h2>{{k1.0}}</h1> <h3>{{k2.name}}</h3> {%for i in k1%} <td>i</td> {%endfor%} {%for row in k2.keys%} <td>row</td> {%endfor%} {%for row in k2.values%} <td>row</td> {%endfor%} {%for key,value in k2.items%} <td>key, value</td> {%endfor%}
模板语言序列化
JSON.parse(data)==>转换为json数据
JSON.stringify(data)==>json数据转换为字符串
{{forloop.counter}} --> 模板语言里给表的每行加一个序号,自增,相当于数据库ID {{forloop.counter0}} -->从0开始 {{forloop.revcounter}} -->倒序 {{forloop.revcounter0}} -->倒序排到0 {{forloop.last}} -->是否是最后一个循环 {{forloop.first}} -->是否是第一个循环 {{forloop.parentloop}} -->父循环的序列号(外层还有个循环,嵌套的在里面的用这一句,第一次输出全是1,第二次2)
模板继承与导入
- 母版..html 模板继承 - master {% block content %}{% endblock %} - master:header {% block title %}{% endblock %} 页网页先写上{% extends ‘master.html‘ %},表明继承哪个模板 - 子页面 {% block content %}{% endblock %} ==> 替换模板里面的哪个block - 子页面 {% block title %}{% endblock %} 关于继承母版的css跟js: 在母版的css下面写上一个 {% block css %}{% endblock %} {% block js %}{% endblock %} 子页面继承: -css {% block css %} <style> body{background-color: red} </style> {% endblock %} -js {% block js %} <script> .. </script> {% endblock %} include tag页面,相当于网页上某一块标签样式一直重复的使用,渲染网页的某一块 - tag.html ==> <form> <input type=‘text‘ /> <input type=‘button‘ /> </form> - 要使用tag的界面==> {% include ‘tag.html‘%}
模板自定义函数
自定义sample_tag 1、在app下创建一个templatetags目录 2、在此目录下新建任意xxoo.py文件 3、from django import template register = template.Library() @register.simple_tag def func(a1, a2) return a1+a2 4、settings注册app 5、顶部{% load xxoo%} 如果有extends,写在之后 6、{% func 1 3%} ==>{% func arg1 arg2 %} 缺点:不能作为if条件 优点:参数任意 自定义filter 1、2、同上 3、 from django import template from django.utils.safestring import mark_safe register = template.Library() @register.filter def func(a1, a2) # filter只能传2个参数 return a1+a2 4、5、 6、 {{"maliya"|func:"LaoShi"}} ==> {{arg1|func:"参数二"}} 优点:可以作为if条件(模板语言里) 缺点:参数不能任意,最多2个,不能加空格
模板语言自带的方法
帮助方法: {{ item.event_start|date:"Y-m-d H:i:s"}} {{ bio|truncatewords:"30" }} {{ my_list|first|upper }} {{ name|lower }}
6、Ajax请求
$.ajax({ url:‘/host‘ # 提交到哪 type:“POST” # 提交方式 data:{‘k1‘:‘v1‘,‘k2‘:‘v2‘} success: function(data){ # 这个匿名函数会等到服务端给我们返回数据时自动触发 if(data){ location.reload() }else{ alert(data) } } }) ajax请求 return HttpResponse $.get(url=‘xxx‘,data={‘x‘:‘x‘}) -->调用上面的ajax,type为get $.post -->调用ajax,type为post $.getJson 建议:永远让服务器端返回一个字典 return HttpResponse(json.dumps(字典)) 模板语言序列化 JSON.parse(data)==>转换为json数据 JSON.stringify(data)==>json数据转换为字符串 $.ajax({ data:$(‘.edit_form‘).serialize() # 获取所有的editform里面的元素值,打包 dataType:‘JSON‘ # 匿名函数的data就会自动序列化成json。#如果这样写返回给success的数据已经就是json数据类型对象 traditional:true, # 传给后台的json数据中有列表的时候必须加上, })
jquery
var list = []
hid = $(this).val()
list.push(hid)
7、Cookie & Session
cookie 客户端浏览器上的一个文件,信息以键值对保存。{‘name‘:‘dandy‘}
# 设置cookies res = redirect(request, ‘/index/‘) res.set_cookies(‘username‘,‘dandy‘) ==> 设置cookie,关闭浏览器失效 或者 res = render(request, ‘index.html‘) res.set_cookies(‘username‘,‘dandy‘) # 获取cookies v = request.COOKIES.get(‘username‘) v = request.COOKIES[‘username‘] 其他参数 key, 键 value=‘‘, 值 max_age=None, 超时时间 expires=None, 截止时间失效 path=‘/‘, Cookie生效的路径,/ 表示根路径,特殊的:跟路径的cookie可以被任何url的页面访问 domain=None, Cookie生效的域名 secure=False, https传输 httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
由于cookie保存在客户端的电脑上,所以,JavaScript和jquery也可以操作cookie。
<script src=‘/static/js/jquery.cookie.js‘></script> $.cookie("list_pager_num", 30,{ path: ‘/‘ });
加盐
# 基于salt的值加盐 obj = HttpResponse(‘s‘) obj.set_signed_cookie(‘username‘,‘dandy‘, salt=‘asdfqw‘) # 基于salt进行解密 request.get_signed_cookie(‘username‘, salt=‘asdfqw‘)
8、CSRF
讲到这里大家基本都应该遇到了不少的这样的问题,并且百度到答案了。
大概浅浅的说一下是什么吧,因为跟百度上的csrf blog比起来,这一篇真的差远了。
前面已经说过了cookies跟session的作用,认证信息和用户私密信息都是通过session保存的,服务器会给客户端返回一串随机字符串,在服务器上,这一串随机字符串就是对应的用户信息字典。假如这是购物网站,或者银行等等,其他的恶意网页获取了找个随意字符串,通过验证,就可以随意的获取用户的session里面的私密信息。这显然是不可以的。
django里面提供的csrf的原理就是在返回给客户单数据的时候再产生一个私密的加盐字符串(只有django自身可以识别的算法字符串)。
CSRF攻击的主要目的是让用户在不知情的情况下攻击自己已登录的一个系统,类似于钓鱼。如用户当前已经登录了邮箱,或bbs,同时用户又在使用另外一个,已经被你控制的站点,我们姑且叫它钓鱼网站。这个网站上面可能因为某个图片吸引你,你去点击一下,此时可能就会触发一个js的点击事件,构造一个bbs发帖的请求,去往你的bbs发帖,由于当前你的浏览器状态已经是登陆状态,所以session登陆cookie信息都会跟正常的请求一样,纯天然的利用当前的登陆状态,让用户在不知情的情况下,帮你发帖或干其他事情。
Django里面的csrf防御机制:
一、简介
django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成。而对于django中设置防跨站请求伪造功能有分为全局和局部。
全局:
中间件 django.middleware.csrf.CsrfViewMiddleware
局部:
@csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。 @csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。 注:from django.views.decorators.csrf import csrf_exempt,csrf_protect
应用
form提交
1 1、普通表单 2 veiw中设置返回值: 3 return render_to_response(‘Account/Login.html‘,data,context_instance=RequestContext(request)) 4 或者 5 return render(request, ‘xxx.html‘, data) 6 7 html中设置Token: 8 {% csrf_token %} 9 # 上面这个比较常用,反正楼主是用的这个。因为就一句话啊。。。。。
ajax提交
在ajax的函数里面加上一个header就好了 header:{‘X-CSRFtoken‘,$.cookie(‘csrftoken‘)
$.ajax({ url:‘/login/‘, type:‘POST‘, data:{‘user‘:‘root‘,‘pwd‘:‘123‘}, header:{‘X-CSRFtoken‘:...}, success:function(){ } })
但是当我们遇到有很多的ajax提交的时候是不是每一个都需要这样加呢?之前我们有说过一个dispatch。当然这边跟dispatch无关。只是利用同样的理念在某个地方是不是有一个位置可以写这些呢?
全局配置
$.ajaxSetup({ beforeSend:function(xhr,settings){ # xml http request对象 xhr.setRequestHeader(‘X-CSRFtoken‘,$.cookie(‘csrftoken‘)) } });
同样的并不是所有的ajax都需要这样设置的。如django官方推荐的:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> {% csrf_token %} <input type="button" onclick="Do();" value="Do it"/> <script src="/static/plugin/jquery/jquery-1.8.0.js"></script> <script src="/static/plugin/jquery/jquery.cookie.js"></script> <script type="text/javascript"> var csrftoken = $.cookie(‘csrftoken‘); function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); # 这四种都不需要进行csrf认证 } $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); function Do(){ $.ajax({ # ajax函数不变 url:"/app01/test/", data:{id:1}, type:‘POST‘, success:function(data){ console.log(data); } }); } </script> </body> </html>
Django ORM操作
基本操作、外键、跨表
a.创建类和字段 class User(models.Model): id = models.IntegerField() name = models.CharField(max_length=12) python manage.py makemigrations python manage.py migrate # settings.py 注册APP b.操作 增 =>models.User.objects.create(id=2,name="dandy") ==>id 不是主键,不自增,不是key => dic = {‘name‘:‘xx‘, "age":22} models.User.objects.create(**dic) obj = models.User("name":"xx", "age":22) obj.save() 删 models.User.objects.filter(name=‘xx‘).delete() 改 models.User.objects.filter(id__gt=1).update(name=‘asd‘) dic = {"name":"xx", "age",22} models.User.objects.filter(id__gt=1).update(**dic) 查 models.User.objects.filter(id__gte=1) >= models.User.objects.filter(id__lte=1, name=‘xxo‘) dic = {"name":"dandy","age__gt":22} models.User.objects.filter(**dic) 字典 外键 class UserType(models.Model): caption = models.CharField(max_length=22) class User(models.Model): name = models.CharField(max_length=23) age = models.IntegerField() # user_type_id = models.IntegerField() user_type = models.ForeignKey("UserType", to_field="id") choices_detail = ( (u‘M‘, u‘Male‘), (u‘F‘, u‘Female‘), ) model = models.CharField(max_length=2, choices=choices_detail) 查询:models.User.objects.all() ==>queryset ==>[obj(id, name, age),obj(id, name, age)] -->对象 models.User.objects.all().values("id","name") ==>queryset ==>[{"id":1, "name": "dandy"},{}] -->字典 models.User.objects.all().values_list("id","name") ==>queryset ==>[(1,"dandy"),()] -->列表 models.User.objects.get(id=1) # 获取到一个对象,如果id不存在就报错 models.User.objects.filter(id=1) # 没有拿到就是空列表 models.User.objects.filter(id=1).first() # 没有拿到就是None # 用.进行跨表 v = models.User.objects.filter(id__gt=0) v[0].b.caption --> 通过.进行跨表 ==> [obj(id, name,另一个对象(....))] # 用__双下划线进行跨表 v = models.User.objects.filter(id__gt=0).values(‘id,name,user_type__caption‘) # 获取到的列表内部,是一个个元组,用index取值 v = models.User.objects.filter(id__gt=0).values_list(‘id,name,user_type__caption‘) {%for row in v%} row.0 - row.1 {%endfor%} {{forloop.counter}} --> 模板语言里给表的每行加一个序号,自增,相当于数据库ID {{forloop.counter0}} -->从0开始 {{forloop.revcounter}} -->倒序 {{forloop.revcounter0}} -->倒序排到0 {{forloop.last}} -->是否是最后一个循环 {{forloop.first}} -->是否是第一个循环 {{forloop.parentloop}} -->父循环的序列号(外层还有个循环,嵌套的在里面的用这一句,第一次输出全是1,第二次2)
多对多操作
创建多对多 方式一:自定义创建表 自己创建表1、表2,然后创建表1_to_表2 方式二:Django自动生成(Django自动创建关系表) 创建表1,创建表2并在二中加一列 - r = models.ManyToManyField(‘表1‘) 自动创建的表,无法对表直接做操作 只有3个列,自增id,2张表的自增id 绑定 obj = 表1.objects.get(id=1) obj.r.add(1)==>表1的id1跟表2的id1绑定 也可以批量传入列表,下面2种都支持 obj.r.add(1,2,3) obj.r.add([1,2,3]) 删除 obj.r.remove(1) obj.r.remove(2,4) obj.r.remove(*[1,2,3]) obj.r.clear() # 清空表1 id=1的在表3里面的所有关系 obj.set([3,5,6]) ==> 表3的数据库里面只会保留1 3;1 5;1 6这三个;
清空其他所有的 obj.r.all() ==>表1 id=1的所有的组合对象列表 queryset
Django中间件
还是涉及到django的请求生命周期。middle ware 请求穿过中间件到达url,再经过中间件返回给用户。
简单实例
django项目根目录新建一个Middle文件夹,再新建一个test.py文件
在test文件中写入;其中的类必须继承 from django.utils.deprecation import MiddlewareMixin
1 from django.utils.deprecation import MiddlewareMixin 2 class M1(MiddlewareMixin): 3 def process_request(self, request): 4 print(‘m1‘) 5 def process_response(self, request, response): 6 print(‘m1_r‘) 7 return response 8 9 10 class M2(MiddlewareMixin): 11 def process_request(self, request): 12 print(‘m2‘) 13 def process_response(self, request, response): 14 print(‘m2_r‘) 15 return response 16 17 18 class M3(MiddlewareMixin): 19 def process_request(self, request): 20 print(‘m3‘) 21 22 def process_response(self, request, response): 23 print(‘m3_r‘) 24 return response
将你的测试中间件加入Django的中间件配置中,settings文件
随便建一组对应路由。
在index函数里面写上
def index(request): print(‘到达‘) return HttpResponse(‘ok‘)
查看结果:
此时如果给某个中间件的process_request返回一个HttpResponse:
#!/user/bin/env python # -*-coding: utf-8-*- from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse class M1(MiddlewareMixin): def process_request(self, request): print(‘m1‘) def process_response(self, request, response): print(‘m1_r‘) return response class M2(MiddlewareMixin): def process_request(self, request): print(‘m2‘) return HttpResponse(‘中断‘) # 这里返回 def process_response(self, request, response): print(‘m2_r‘) return response class M3(MiddlewareMixin): def process_request(self, request): print(‘m3‘) def process_response(self, request, response): print(‘m3_r‘) return response
查看下结果
可能你好像懂了,但是并没有,真正的中间件过程其实还有一个process_view.
1 #!/user/bin/env python 2 # -*-coding: utf-8-*- 3 from django.utils.deprecation import MiddlewareMixin 4 from django.shortcuts import HttpResponse 5 class M1(MiddlewareMixin): 6 def process_request(self, request): 7 print(‘m1‘) 8 def process_response(self, request, response): 9 print(‘m1_r‘) 10 return response 11 def process_view(self, request, view_func, view_func_args, view_func_kwargs): 12 print("m1_view") 13 14 15 class M2(MiddlewareMixin): 16 def process_request(self, request): 17 print(‘m2‘) 18 def process_response(self, request, response): 19 print(‘m2_r‘) 20 return response 21 def process_view(self, request, view_func, view_func_args, view_func_kwargs): 22 print("m2_view") 23 24 25 class M3(MiddlewareMixin): 26 def process_request(self, request): 27 print(‘m3‘) 28 def process_response(self, request, response): 29 print(‘m3_r‘) 30 return response 31 def process_view(self, request, view_func, view_func_args, view_func_kwargs): 32 print("m3_view")
可参考文档:http://python.usyiyi.cn/documents/django_182/topics/http/middleware.html
Django的缓存
由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者Redis中之前缓存的内容拿到,并返回。
Django中提供了6种缓存方式:
- 开发调试
- 内存
- 文件
- 数据库
- Memcache缓存(python-memcached模块)
- Memcache缓存(pylibmc模块)
配置
a、开发测试
1 # 此为开始调试用,实际内部不做任何操作 2 # 配置: 3 CACHES = { 4 ‘default‘: { 5 ‘BACKEND‘: ‘django.core.cache.backends.dummy.DummyCache‘, # 引擎 6 ‘TIMEOUT‘: 300, # 缓存超时时间(默认300,None表示永不过期,0表示立即过期) 7 ‘OPTIONS‘:{ 8 ‘MAX_ENTRIES‘: 300, # 最大缓存个数(默认300) 9 ‘CULL_FREQUENCY‘: 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3) 10 }, 11 ‘KEY_PREFIX‘: ‘‘, # 缓存key的前缀(默认空) 12 ‘VERSION‘: 1, # 缓存key的版本(默认1) 13 ‘KEY_FUNCTION‘ 函数名 # 生成key的函数(默认函数会生成为:【前缀:版本:key】) 14 } 15 } 16 17 18 # 自定义key 19 def default_key_func(key, key_prefix, version): 20 """ 21 Default function to generate keys. 22 23 Constructs the key used by all other methods. By default it prepends 24 the `key_prefix‘. KEY_FUNCTION can be used to specify an alternate 25 function with custom key making behavior. 26 """ 27 return ‘%s:%s:%s‘ % (key_prefix, version, key) 28 29 def get_key_func(key_func): 30 """ 31 Function to decide which key function to use. 32 33 Defaults to ``default_key_func``. 34 """ 35 if key_func is not None: 36 if callable(key_func): 37 return key_func 38 else: 39 return import_string(key_func) 40 return default_key_func
b、内存
1 # 此缓存将内容保存至内存的变量中 2 # 配置: 3 CACHES = { 4 ‘default‘: { 5 ‘BACKEND‘: ‘django.core.cache.backends.locmem.LocMemCache‘, 6 ‘LOCATION‘: ‘unique-snowflake‘, 7 } 8 } 9 10 # 注:其他配置同开发调试版本
c、文件
1 # 此缓存将内容保存至文件 2 # 配置: 3 4 CACHES = { 5 ‘default‘: { 6 ‘BACKEND‘: ‘django.core.cache.backends.filebased.FileBasedCache‘, 7 ‘LOCATION‘: ‘/var/tmp/django_cache‘, 8 } 9 } 10 # 注:其他配置同开发调试版本
d、数据库
1 # 此缓存将内容保存至数据库 2 3 # 配置: 4 CACHES = { 5 ‘default‘: { 6 ‘BACKEND‘: ‘django.core.cache.backends.db.DatabaseCache‘, 7 ‘LOCATION‘: ‘my_cache_table‘, # 数据库表 8 } 9 } 10 11 # 注:执行创建表命令 python manage.py createcachetable
e、Memcache缓存(python-memcached模块)
1 # 此缓存使用python-memcached模块连接memcache 2 3 CACHES = { 4 ‘default‘: { 5 ‘BACKEND‘: ‘django.core.cache.backends.memcached.MemcachedCache‘, 6 ‘LOCATION‘: ‘127.0.0.1:11211‘, 7 } 8 } 9 10 CACHES = { 11 ‘default‘: { 12 ‘BACKEND‘: ‘django.core.cache.backends.memcached.MemcachedCache‘, 13 ‘LOCATION‘: ‘unix:/tmp/memcached.sock‘, 14 } 15 } 16 17 CACHES = { 18 ‘default‘: { 19 ‘BACKEND‘: ‘django.core.cache.backends.memcached.MemcachedCache‘, 20 ‘LOCATION‘: [ 21 ‘172.19.26.240:11211‘, 22 ‘172.19.26.242:11211‘, 23 ] 24 } 25 }
f、Memcache缓存(pylibmc模块)
1 # 此缓存使用pylibmc模块连接memcache 2 3 CACHES = { 4 ‘default‘: { 5 ‘BACKEND‘: ‘django.core.cache.backends.memcached.PyLibMCCache‘, 6 ‘LOCATION‘: ‘127.0.0.1:11211‘, 7 } 8 } 9 10 CACHES = { 11 ‘default‘: { 12 ‘BACKEND‘: ‘django.core.cache.backends.memcached.PyLibMCCache‘, 13 ‘LOCATION‘: ‘/tmp/memcached.sock‘, 14 } 15 } 16 17 CACHES = { 18 ‘default‘: { 19 ‘BACKEND‘: ‘django.core.cache.backends.memcached.PyLibMCCache‘, 20 ‘LOCATION‘: [ 21 ‘172.19.26.240:11211‘, 22 ‘172.19.26.242:11211‘, 23 ] 24 } 25 }
关于缓存memcache的分配。
请求来的时候会将它的key转换成数字,在看列表中的机器数量,用key转换的数字除以缓存机器数量,余数是几就存在哪台机器上(列表的第几台机器)。
加上权重的话
CACHES = { ‘default‘: { ‘BACKEND‘: ‘django.core.cache.backends.memcached.PyLibMCCache‘, ‘LOCATION‘: [ (‘172.19.26.240:11211‘,11) (‘172.19.26.242:11211‘,10) ] } }
就是数字除以21,余数是几就存在第几台机器。(240==》11台==》0~10, 242==》10台==》11~20),这个不是django提供的,而是memcache的模块包含的。
应用
针对视图函数
1 方式一: 2 from django.views.decorators.cache import cache_page 3 4 @cache_page(60 * 15) 5 def my_view(request): 6 ... 7 8 方式二: 9 from django.views.decorators.cache import cache_page 10 11 urlpatterns = [ 12 url(r‘^foo/([0-9]{1,2})/$‘, cache_page(60 * 15)(my_view)), 13 ]
针对局部视图
1 a. 引入TemplateTag 2 3 {% load cache %} 4 5 b. 使用缓存 6 7 {% cache 5000 缓存key %} 8 缓存内容 9 {% endcache %}
全站使用
1 使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存 2 3 MIDDLEWARE = [ 4 ‘django.middleware.cache.UpdateCacheMiddleware‘, 5 # 其他中间件... 6 ‘django.middleware.cache.FetchFromCacheMiddleware‘, 7 ] 8 9 CACHE_MIDDLEWARE_ALIAS = "" 10 CACHE_MIDDLEWARE_SECONDS = "" 11 CACHE_MIDDLEWARE_KEY_PREFIX = ""
针对于文本缓存的一个实例
新建缓存文件夹
配置路径
CACHES = { ‘default‘: { ‘BACKEND‘: ‘django.core.cache.backends.filebased.FileBasedCache‘, ‘LOCATION‘: os.path.join(BASE_DIR, ‘cache‘) } }
写一个路由对应的网页
from django.views.decorators.cache import cache_page @cache_page(10) def cache_test(request): ctime = time.time() print(ctime) return render(request, ‘cache.html‘, {"obj": ctime})
这就已经ok了,网页打开的效果就是10秒缓存。
局部视图,修改网页如下,再将刚刚的缓存装饰器注释掉。
1 {% load cache %} 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="UTF-8"> 6 <title>Title</title> 7 </head> 8 <body> 9 <p>{{ obj }}</p> 10 {% cache 10 k1 %} 11 <p>{{ obj }}</p> 12 {% endcache %} 13 </body> 14 </html>
1 def cache_test(request): 2 ctime = time.time() 3 print(ctime) 4 return render(request, ‘cache.html‘, {"obj": ctime})
这时候就会发现被装饰的标签不会被缓存了,一个标签刷新,另一个不刷新。这是一种更细致的缓存。
全局缓存需要用中间件来做,首先讲解下基本机制
星1的地方缓存很好理解,因为中间件可能要进行一系列认证或者其他操作;星2主要是中间件可能要对数据进行一些其他的全局修饰,所以放在最后。而且从他们的顺序我们也可以发现:为缓存自定义的两个中间件里面一个只有process_request,另一个只有process_response.可以直接把中间件拿出来用from import然后查看最后倒入的方法的具体内容。
看着这张图我们就可以更好的说明一下缓存周期了。首先绿色的是请求经过中间件认证一系列之后进行缓存记录,然后执行视图函数,再经过中间件的response的装饰处理,最外层缓存记录,然后发给请求发起者。第二次是红色的线,接收到用户请求经过一系列验证等等,走到中间层最后一级,查看是否有缓存,有就去缓存区取数据从response发送给用户。这就是全局缓存的过程。
Django信号
Django中提供了“信号调度”,用于在框架执行操作时解耦。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。
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 # 创建数据库连接时,自动触发
对于Django内置的信号,仅需注册指定信号,当程序执行相应操作时,自动触发注册函数:
导入
from django.core.signals import request_finished from django.core.signals import request_started from django.core.signals import got_request_exception from django.db.models.signals import class_prepared from django.db.models.signals import pre_init, post_init from django.db.models.signals import pre_save, post_save from django.db.models.signals import pre_delete, post_delete from django.db.models.signals import m2m_changed from django.db.models.signals import pre_migrate, post_migrate from django.test.signals import setting_changed from django.test.signals import template_rendered from django.db.backends.signals import connection_created def callback(sender, **kwargs): print("xxoo_callback") print(sender,kwargs) xxoo.connect(callback) # xxoo指上述导入的内容
Django程序一运行起来就会执行主程序目录里面的__init__.py。是不是有印象,写数据库的时候我们也在__init__.py文件里面导入了
import pymysql pymysql.install_as_MySQLdb()
所以想让自定制的信号执行,同样需要导入到__init__.py文件中(上面写好的py文件)。
import .....
from django.core.signals import request_finished from django.dispatch import receiver @receiver(request_finished) def my_callback(sender, **kwargs): print("Request finished!")
自定义信号
a、定义信号
import django.dispatch pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
b、注册信号
def callback(sender, **kwargs): print("callback") print(sender,kwargs) pizza_done.connect(callback)
c、触发信号
from 路径 import pizza_done pizza_done.send(sender=‘seven‘,toppings=123, size=456)
由于内置信号的触发者已经集成到Django中,所以其会自动调用,而对于自定义信号则需要开发者在任意位置触发。
强大的Form
花里胡哨的写了那么多知识点,很多人看着估计也是云里雾里的。现在这个点,配合实例讲解,大家一定会被form强大的功能惊讶到。
就按照普通的登陆业务需求,来写一个登陆页面,需要创建一个路由,一个网页,一个视图函数。
路由系统添加:
url(r‘^fm/‘, views.fm),
html:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form action="/fm/" method="POST"> 9 {% csrf_token %} 10 <p><input type="text" name="user" /></p> 11 <p><input type="text" name="pwd" /></p> 12 <p><input type="text" name="email" /></p> 13 <input type="submit" name="提交" /> 14 </form> 15 </body> 16 </html>
视图函数:
1 def fm(request): 2 if request.method == ‘GET‘: 3 return render(request,‘form.html‘) 4 elif request.method == ‘POST‘: 5 # 这边应该就是所有的验证信息编辑处,账号,密码,邮箱验证包括格式。大概就是账户名格式对不对,密码长短对不对,邮箱格式对不对,最后加个整体验证。
其实写到这里我们已经觉得很烦锁了,就直接导入form验证吧。首先新建一个类继承forms.Form
1 class FM(forms.Form): 2 user = forms.CharField() 3 pwd = forms.CharField() 4 email = forms.EmailField()
更改视图函数
1 def fm(request): 2 if request.method == ‘GET‘: 3 return render(request,‘form.html‘) 4 elif request.method == ‘POST‘: 5 obj = FM(request.POST) 6 auth = obj.is_valid() # 验证 7 if auth: 8 print(obj.cleaned_data) 9 return redirect(‘/fm/‘) 10 else: 11 print(obj.errors) 12 print(obj.errors.as_json()) 13 return render(request, ‘form.html‘, {‘obj‘: obj})
这里我们首先可以看下效果,auth = obj.is_valid()其实就是验证结果的返回,布尔值True or False.
如果视图函数验证成功,我们可以得到这样的obj.cleaned_data
{‘user‘: ‘dandy‘, ‘pwd‘: ‘password.1‘, ‘email‘: ‘fqoweq@fac.com‘}
如果验证失败的话,就可以得到下面的错误列表
obj.errors对应的输出 <ul class="errorlist"><li>user<ul class="errorlist"><li>This field is required.</li></ul></li><li>pwd<ul class="errorlist"><li>This field is required.</li></ul></li><li>email<ul class="errorlist"><li>This field is required.</li></ul></li></ul> obj.errors.as_json()对应的输出结果 {"user": [{"message": "This field is required.", "code": "required"}], "pwd": [{"message": "This field is required.", "code": "required"}], "email": [{"message": "This field is required.", "code": "required"}]}
这时候我们拿到输出结果就可以做模板语言的渲染了。
1 <p><input type="text" name="user" />{{ obj.errors.user.0 }}</p> 2 <p><input type="text" name="pwd" />{{ obj.errors.pwd.0 }}</p> 3 <p><input type="text" name="email" />{{ obj.errors.email.0 }}</p>
这样运行起来,就可以将form的验证发送给前台了。
这里其实才是最简单的验证功能,报错的信息是英文的,很官方,而且,没有自定制的验证,想必这么一说大家也知道了,肯定接下来提到的就是对于继承form的类进行改造了吧。
是的!
1 class FM(forms.Form): 2 user = forms.CharField(error_messages={‘required‘:‘用户名不能为空!‘}) # error_messages是错误信息,对应的key对应报错内容,改造之前的报错跟自定制报错就可以完成了。 3 pwd = forms.CharField( 4 max_length=12, 5 min_length=6, 6 error_messages={‘required‘:‘密码不能为空!‘, ‘min_length‘: ‘密码长度不能小于6.‘, ‘max_length‘: ‘密码长度不能大于12.‘} 7 ) 8 email = forms.EmailField(error_messages={‘required‘:‘邮箱不能为空!‘, ‘invalid‘: ‘邮箱格式错误‘})
这是直接运行:
之前的报错就已经被替换了。并且自定制的功能也已经加入了。
讨论点一
对于ajax请求,我们可以返回HttpResponse,或者Render,存在即是有原因的,那么如何运用这两种呢?
首先,HttpResponse是我们所推荐的方法,一个自定义的序列化字典,前段接收到这个字符串进行反序列化,就可以从json数组中随意的选取自己的值,灵活性更高,自定制效果更好。 当然我们也可以用Render来返回一个Html,当然这里的html并不是常规意义上的html,比如是这样一个html <p>{{obj.name}}</p> 然后函数或者类里面返回 render(request,‘index.html‘,{‘obj‘:obj}) 根据django的请求生命周期,很容易我们就可以看出,我们首先执行函数或者类,然后得到这个obj对象,再加上这个html,其次对这个渲染的html进行解释,最后得到的其实就是一个<p>dandy</p>
这样的标签的字符串,通常我们会用这种方式返回给ajax一些标签。但是对于这类字符串,是无法进行修改的,或者说很难,灵活性跟直接返回序列化字典对比就能看出来差很多。 所以还是那句话,在绝大多数情况下,更推荐使用HttpResponse来返回序列化字典给前端的ajax。 另外,切记,redirect返回是无效的。
扩展:自定义分页
a、XSS恶意攻击:防止写script之类循环让网页一直输出弹框(最简单的实例)恶意攻击:防止写script之类循环让网页一直输出弹框(最简单的实例) 前端:{{name|safe}}==>告诉网页这个返回的字符串是安全的。不屏蔽成字符串
b、from django.utils.safestring import mark_safe page_str = "<a href=‘index‘>name</a>" page_str = mark_safe(page_str)
逻辑:
divmod 除 余数 active 逻辑: 当前页:current_page 总页数:total_count 每页显示10条数据:page_per_count 页码:11 如果:总页数<11 start_index = 0 end_index = 总页数 else: 当前页<=6 显示1~11 当前页小于6 显示当前页-5,当前页+5+1 如果当前页+5>总页数: end_index = total_count + 1
自定义类Page:
#!/user/bin/env python # -*-coding: utf-8-*- from django.utils.safestring import mark_safe class Page: def __init__(self, data_total_count, current_index, show_data_count=10, show_tag_count=11, ): self.data_total_count = data_total_count self.current_index = current_index self.show_data_count = show_data_count self.show_tag_count = show_tag_count @property def start(self): return (self.current_index - 1) * self.show_data_count @property def end(self): return self.current_index * self.show_data_count @property def all_count(self): v, y = divmod(self.data_total_count, self.show_data_count) if y: v += 1 return v def page_str(self, base_url): if self.all_count < self.show_tag_count: start_index =1 end_index = self.all_count + 1 else: if self.current_index <= (self.show_tag_count+1)/2: start_index = 1 end_index = self.show_tag_count + 1 else: start_index = self.current_index - (self.show_tag_count - 1)/2 end_index = self.current_index + (self.show_tag_count + 1)/2 if (self.current_index + 5) > self.all_count: start_index = self.all_count - self.show_tag_count + 1 end_index = self.all_count + 1 page_list = [] if self.current_index == 1: prev = "<a class=‘page‘ href=‘javascript:void(0)‘>上一页</a>" else: prev = "<a class=‘page‘ href=‘%s?p=%s‘>上一页</a>" %(base_url, self.current_index - 1) page_list.append(prev) for i in range(int(start_index), int(end_index)): if i == self.current_index: temp = "<a class=‘page active‘ href=‘%s?p=%s‘>%s</a>" %(base_url, i, i) else: temp = "<a class=‘page‘ href=‘%s?p=%s‘>%s</a>" % (base_url, i, i) page_list.append(temp) if self.current_index == self.all_count: nex = "<a class=‘page‘ href=‘javascript:void(0)‘>下一页</a>" else: nex = "<a class=‘page‘ href=‘%s?p=%s‘>下一页</a>" % (base_url, self.current_index + 1) page_list.append(nex) dest = """ <input type=‘text‘ value=1 /><a onclick="jumpTo(this,"%s?p=");">GO</a> <script> function jumpTo(ths, base){ var val = ths.previousSibling.value; location.href = base + val; } </script> """ % base_url page_list.append(dest) page_str = mark_safe("".join(page_list)) return page_str
外部调用
from utils import paging def temp3(request): if request.method == "GET": page_count = int(request.COOKIES.get("page_count", 10)) current_index = request.GET.get(‘p‘, 1) current_index = int(current_index) # 当前的页数 page_obj = paging.Page(len(li), current_index, page_count) data_list = li[int(page_obj.start):int(page_obj.end)] page_str = page_obj.page_str("/app01/temp3/") return render(request, ‘temp3.html‘, {"list": data_list, "str": page_str})
模板语言
<ul> {% for row in list %} <li>{{ row }}</li> {% endfor %} </ul> <select id="slt" onchange="page_count(this)"> <option value="5">5</option> <option value="10">10</option> <option value="20">20</option> <option value="30">30</option> </select> <div class="page_index"> {{ str|safe }} </div> <script src="/static/jquery-3.2.1.js"></script> <script src="/static/jquery.cookie.js"></script> <script> $(function() { var v = $.cookie("page_count"); {# var v = $.cookie("page_count", {‘path‘: "/app01/temp3/"});#} $("#slt").val(v) }); function page_count(ths) { val = $(ths).val(); $.cookie("page_count", val) } </script>