Python 19 Django 详解
本节概要
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): 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): 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')
解读下上面的那么多装饰器;首先,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')
Session
Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:
- 数据库(默认)
- 缓存
- 文件
- 缓存+数据库
- 加密cookie
1、数据库Session
1 Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。 2 3 a. 配置 settings.py 4 5 SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认) 6 7 SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认) 8 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认) 9 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认) 10 SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认) 11 SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认) 12 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认) 13 SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认) 14 SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认) 15 16 17 18 b. 使用 19 20 def index(request): 21 # 获取、设置、删除Session中数据 22 request.session['k1'] 23 request.session.get('k1',None) 24 request.session['k1'] = 123 25 request.session.setdefault('k1',123) # 存在则不设置 26 del request.session['k1'] 27 28 # 所有 键、值、键值对 29 request.session.keys() 30 request.session.values() 31 request.session.items() 32 request.session.iterkeys() 33 request.session.itervalues() 34 request.session.iteritems() 35 36 37 # 用户session的随机字符串 38 request.session.session_key 39 40 # 将所有Session失效日期小于当前日期的数据删除 41 request.session.clear_expired() 42 43 # 检查 用户session的随机字符串 在数据库中是否 44 request.session.exists("session_key") 45 46 # 删除当前用户的所有Session数据 47 request.session.delete("session_key") 48 49 request.session.set_expiry(value) 50 * 如果value是个整数,session会在些秒数后失效。 51 * 如果value是个datatime或timedelta,session就会在这个时间后失效。 52 * 如果value是0,用户关闭浏览器session就会失效。 53 * 如果value是None,session会依赖全局session失效策略。
2、缓存session
1 a. 配置 settings.py 2 3 SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎 4 SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置 5 6 7 SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串 8 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径 9 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 10 SESSION_COOKIE_SECURE = False # 是否Https传输cookie 11 SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输 12 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) 13 SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期 14 SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存 15 16 17 18 b. 使用 19 20 同上
3、文件session
1 a. 配置 settings.py 2 3 SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎 4 SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T 5 6 7 SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串 8 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径 9 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 10 SESSION_COOKIE_SECURE = False # 是否Https传输cookie 11 SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输 12 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) 13 SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期 14 SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存 15 16 b. 使用 17 18 同上
4、缓存 + 数据库session
1 数据库用于做持久化,缓存用于提高效率 2 3 a. 配置 settings.py 4 5 SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎 6 7 b. 使用 8 9 同上
5、加密cookie session
1 a. 配置 settings.py 2 3 SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎 4 5 b. 使用 6 7 同上
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'}, headers:{'X-CSRFtoken':...}, success:function(){ } })
但是当我们遇到有很多的ajax提交的时候是不是每一个都需要这样加呢?之前我们有说过一个dispatch。当然这边跟dispatch无关。只是利用同样的理念在某个地方是不是有一个位置可以写这些呢?
全局配置
$.ajaxSetup({ beforeSend:function(xhr,settings){ # xml http request对象 xhr.setRequestHeader('X-CSRFtoken',$.cookie('csrftoken')) } });
$.ajaxSetup({ data: {csrfmiddlewaretoken: '{{ csrf_token }}' } });
同样的并不是所有的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 Model操作
字段
1 AutoField(Field) 2 - int自增列,必须填入参数 primary_key=True 3 4 BigAutoField(AutoField) 5 - bigint自增列,必须填入参数 primary_key=True 6 7 注:当model中如果没有自增列,则自动会创建一个列名为id的列 8 from django.db import models 9 10 class UserInfo(models.Model): 11 # 自动创建一个列名为id的且为自增的整数列 12 username = models.CharField(max_length=32) 13 14 class Group(models.Model): 15 # 自定义自增列 16 nid = models.AutoField(primary_key=True) 17 name = models.CharField(max_length=32) 18 19 SmallIntegerField(IntegerField): 20 - 小整数 -32768 ~ 32767 21 22 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 23 - 正小整数 0 ~ 32767 24 IntegerField(Field) 25 - 整数列(有符号的) -2147483648 ~ 2147483647 26 27 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 28 - 正整数 0 ~ 2147483647 29 30 BigIntegerField(IntegerField): 31 - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 32 33 BooleanField(Field) 34 - 布尔值类型 35 36 NullBooleanField(Field): 37 - 可以为空的布尔值 38 39 CharField(Field) 40 - 字符类型 41 - 必须提供max_length参数, max_length表示字符长度 42 43 TextField(Field) 44 - 文本类型 45 46 EmailField(CharField): 47 - 字符串类型,Django Admin以及ModelForm中提供验证机制 48 49 IPAddressField(Field) 50 - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 51 52 GenericIPAddressField(Field) 53 - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 54 - 参数: 55 protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6" 56 unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both" 57 58 URLField(CharField) 59 - 字符串类型,Django Admin以及ModelForm中提供验证 URL 60 61 SlugField(CharField) 62 - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) 63 64 CommaSeparatedIntegerField(CharField) 65 - 字符串类型,格式必须为逗号分割的数字 66 67 UUIDField(Field) 68 - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 69 70 FilePathField(Field) 71 - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 72 - 参数: 73 path, 文件夹路径 74 match=None, 正则匹配 75 recursive=False, 递归下面的文件夹 76 allow_files=True, 允许文件 77 allow_folders=False, 允许文件夹 78 79 FileField(Field) 80 - 字符串,路径保存在数据库,文件上传到指定目录 81 - 参数: 82 upload_to = "" 上传文件的保存路径 83 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage 84 85 ImageField(FileField) 86 - 字符串,路径保存在数据库,文件上传到指定目录 87 - 参数: 88 upload_to = "" 上传文件的保存路径 89 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage 90 width_field=None, 上传图片的高度保存的数据库字段名(字符串) 91 height_field=None 上传图片的宽度保存的数据库字段名(字符串) 92 93 DateTimeField(DateField) 94 - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] 95 96 DateField(DateTimeCheckMixin, Field) 97 - 日期格式 YYYY-MM-DD 98 99 TimeField(DateTimeCheckMixin, Field) 100 - 时间格式 HH:MM[:ss[.uuuuuu]] 101 102 DurationField(Field) 103 - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 104 105 FloatField(Field) 106 - 浮点型 107 108 DecimalField(Field) 109 - 10进制小数 110 - 参数: 111 max_digits,小数总长度 112 decimal_places,小数位长度 113 114 BinaryField(Field) 115 - 二进制类型 116 117 字段列表
1 class UnsignedIntegerField(models.IntegerField): 2 def db_type(self, connection): 3 return 'integer UNSIGNED' 4 5 PS: 返回值为字段在数据库中的属性,Django字段默认的值为: 6 'AutoField': 'integer AUTO_INCREMENT', 7 'BigAutoField': 'bigint AUTO_INCREMENT', 8 'BinaryField': 'longblob', 9 'BooleanField': 'bool', 10 'CharField': 'varchar(%(max_length)s)', 11 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', 12 'DateField': 'date', 13 'DateTimeField': 'datetime', 14 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', 15 'DurationField': 'bigint', 16 'FileField': 'varchar(%(max_length)s)', 17 'FilePathField': 'varchar(%(max_length)s)', 18 'FloatField': 'double precision', 19 'IntegerField': 'integer', 20 'BigIntegerField': 'bigint', 21 'IPAddressField': 'char(15)', 22 'GenericIPAddressField': 'char(39)', 23 'NullBooleanField': 'bool', 24 'OneToOneField': 'integer', 25 'PositiveIntegerField': 'integer UNSIGNED', 26 'PositiveSmallIntegerField': 'smallint UNSIGNED', 27 'SlugField': 'varchar(%(max_length)s)', 28 'SmallIntegerField': 'smallint', 29 'TextField': 'longtext', 30 'TimeField': 'time', 31 'UUIDField': 'char(32)', 32 33 自定义无符号整数字段
1 1.触发Model中的验证和错误提示有两种方式: 2 a. Django Admin中的错误信息会优先根据Admiin内部的ModelForm错误信息提示,如果都成功,才来检查Model的字段并显示指定错误信息 3 b. 使用ModelForm 4 c. 调用Model对象的 clean_fields 方法,如: 5 # models.py 6 class UserInfo(models.Model): 7 nid = models.AutoField(primary_key=True) 8 username = models.CharField(max_length=32) 9 10 email = models.EmailField(error_messages={'invalid': '格式错了.'}) 11 12 # views.py 13 def index(request): 14 obj = models.UserInfo(username='11234', email='uu') 15 try: 16 print(obj.clean_fields()) 17 except Exception as e: 18 print(e) 19 return HttpResponse('ok') 20 21 # Model的clean方法是一个钩子,可用于定制操作,如:上述的异常处理。 22 23 2.Admin中修改错误提示 24 # admin.py 25 from django.contrib import admin 26 from model_club import models 27 from django import forms 28 29 30 class UserInfoForm(forms.ModelForm): 31 age = forms.IntegerField(initial=1, error_messages={'required': '请输入数值.', 'invalid': '年龄必须为数值.'}) 32 33 class Meta: 34 model = models.UserInfo 35 # fields = ('username',) 36 fields = "__all__" 37 exclude = ['title'] 38 labels = { 'name':'Writer', } 39 help_texts = {'name':'some useful help text.',} 40 error_messages={ 'name':{'max_length':"this writer name is too long"} } 41 widgets={'name':Textarea(attrs={'cols':80,'rows':20})} 42 43 class UserInfoAdmin(admin.ModelAdmin): 44 form = UserInfoForm 45 46 admin.site.register(models.UserInfo, UserInfoAdmin) 47 48 注意事项
字段参数
1 null 数据库中字段是否可以为空 2 db_column 数据库中字段的列名 3 default 数据库中字段的默认值 4 primary_key 数据库中字段是否为主键 5 db_index 数据库中字段是否可以建立索引 6 unique 数据库中字段是否可以建立唯一索引 7 unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引 8 unique_for_month 数据库中字段【月】部分是否可以建立唯一索引 9 unique_for_year 数据库中字段【年】部分是否可以建立唯一索引 10 11 verbose_name Admin中显示的字段名称 12 blank Admin中是否允许用户输入为空 13 editable Admin中是否可以编辑 14 help_text Admin中该字段的提示信息 15 choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作 16 如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1) 17 18 error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息; 19 字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date 20 如:{'null': "不能为空.", 'invalid': '格式错误'} 21 22 validators 自定义错误验证(列表类型),从而定制想要的验证规则 23 from django.core.validators import RegexValidator 24 from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\ 25 MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator 26 如: 27 test = models.CharField( 28 max_length=32, 29 error_messages={ 30 'c1': '优先错信息1', 31 'c2': '优先错信息2', 32 'c3': '优先错信息3', 33 }, 34 validators=[ 35 RegexValidator(regex='root_\d+', message='错误了', code='c1'), 36 RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'), 37 EmailValidator(message='又错误了', code='c3'), ] 38 )
元信息
1 class UserInfo(models.Model): 2 nid = models.AutoField(primary_key=True) 3 username = models.CharField(max_length=32) 4 class Meta: 5 # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 6 db_table = "table_name" 7 8 # 联合索引 9 index_together = [ 10 ("pub_date", "deadline"), 11 ] 12 # 索引的最左前缀模式: 13 # select * from where name =.. 可以命中索引 14 # select * from where name = .. and email=... 可以命中索引 15 # select * from where email=.. 无法命中索引 16 # 联合唯一索引 17 unique_together = (("driver", "restaurant"),) 18 19 # admin中显示的表名称 20 verbose_name 21 22 # verbose_name加s 23 verbose_name_plural 24 verbose_name = "上课记录" ==> admin会加s 25 verbose_name_plural = "上课记录" ==> 正常的
基本操作、外键、跨表
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.Users.objects.all().values_list("name", flat=True) -->列表,限制只能取一个字段的时候
==>queryset ==>['wuzdandz', 'wuzfflen']
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
多表关系
1 ForeignKey(ForeignObject) # ForeignObject(RelatedField) 2 to, # 要进行关联的表名 3 to_field=None, # 要关联的表中的字段名称 4 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 5 - models.CASCADE,删除关联数据,与之关联也删除 6 - models.DO_NOTHING,删除关联数据,引发错误IntegrityError 7 - models.PROTECT,删除关联数据,引发错误ProtectedError 8 - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空) 9 - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值) 10 - models.SET,删除关联数据, 11 a. 与之关联的值设置为指定值,设置:models.SET(值) 12 b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象) 13 14 def func(): 15 return 10 16 17 class MyModel(models.Model): 18 user = models.ForeignKey( 19 to="User", 20 to_field="id" 21 on_delete=models.SET(func),) 22 related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() 23 related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') 24 limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: 25 # 如: 26 - limit_choices_to={'nid__gt': 5} 27 - limit_choices_to=lambda : {'nid__gt': 5} 28 29 from django.db.models import Q 30 - limit_choices_to=Q(nid__gt=10) 31 - limit_choices_to=Q(nid=8) | Q(nid__gt=10) 32 - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') 33 db_constraint=True # 是否在数据库中创建外键约束 34 parent_link=False # 在Admin中是否显示关联数据 35 36 37 OneToOneField(ForeignKey) 38 to, # 要进行关联的表名 39 to_field=None # 要关联的表中的字段名称 40 on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为 41 42 ###### 对于一对一 ###### 43 # 1. 一对一其实就是 一对多 + 唯一索引 44 # 2.当两个类之间有继承关系时,默认会创建一个一对一字段 45 # 如下会在A表中额外增加一个c_ptr_id列且唯一: 46 class C(models.Model): 47 nid = models.AutoField(primary_key=True) 48 part = models.CharField(max_length=12) 49 50 class A(C): 51 id = models.AutoField(primary_key=True) 52 code = models.CharField(max_length=1) 53 54 ManyToManyField(RelatedField) 55 to, # 要进行关联的表名 56 related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all() 57 related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名') 58 limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件: 59 # 如: 60 - limit_choices_to={'nid__gt': 5} 61 - limit_choices_to=lambda : {'nid__gt': 5} 62 63 from django.db.models import Q 64 - limit_choices_to=Q(nid__gt=10) 65 - limit_choices_to=Q(nid=8) | Q(nid__gt=10) 66 - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') 67 symmetrical=None, # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段 68 # 做如下操作时,不同的symmetrical会有不同的可选字段 69 models.BB.objects.filter(...) 70 71 # 可选字段有:code, id, m1 72 class BB(models.Model): 73 74 code = models.CharField(max_length=12) 75 m1 = models.ManyToManyField('self',symmetrical=True) 76 77 # 可选字段有: bb, code, id, m1 78 class BB(models.Model): 79 80 code = models.CharField(max_length=12) 81 m1 = models.ManyToManyField('self',symmetrical=False) 82 83 through=None, # 自定义第三张表时,使用字段用于指定关系表 84 through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表 85 from django.db import models 86 87 class Person(models.Model): 88 name = models.CharField(max_length=50) 89 90 class Group(models.Model): 91 name = models.CharField(max_length=128) 92 members = models.ManyToManyField( 93 Person, 94 through='Membership', 95 through_fields=('group', 'person'), 96 ) 97 98 class Membership(models.Model): 99 group = models.ForeignKey(Group, on_delete=models.CASCADE) 100 person = models.ForeignKey(Person, on_delete=models.CASCADE) 101 inviter = models.ForeignKey( 102 Person, 103 on_delete=models.CASCADE, 104 related_name="membership_invites", 105 ) 106 invite_reason = models.CharField(max_length=64) 107 db_constraint=True, # 是否在数据库中创建外键约束 108 db_table=None, # 默认创建第三张表时,数据库中表的名称
操作
1 # 获取个数 2 # 3 # models.Tb1.objects.filter(name='seven').count() 4 5 # 大于,小于 6 # 7 # models.Tb1.objects.filter(id__gt=1) # 获取id大于1的值 8 # models.Tb1.objects.filter(id__gte=1) # 获取id大于等于1的值 9 # models.Tb1.objects.filter(id__lt=10) # 获取id小于10的值 10 # models.Tb1.objects.filter(id__lte=10) # 获取id小于10的值 11 # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 12 13 # in 14 # 15 # models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 16 # models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in 17 18 # isnull 19 # Entry.objects.filter(pub_date__isnull=True) 20 21 # contains 22 # 23 # models.Tb1.objects.filter(name__contains="ven") 24 # models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 25 # models.Tb1.objects.exclude(name__icontains="ven") 26 27 # range 28 # 29 # models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and 30 31 # 其他类似 32 # 33 # startswith,istartswith, endswith, iendswith, 34 35 # order by 36 # 37 # models.Tb1.objects.filter(name='seven').order_by('id') # asc 38 # models.Tb1.objects.filter(name='seven').order_by('-id') # desc 39 40 # group by 41 # 42 # from django.db.models import Count, Min, Max, Sum 43 # models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num')) 44 # SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id" 45 46 # limit 、offset 47 # 48 # models.Tb1.objects.all()[10:20] 49 50 # regex正则匹配,iregex 不区分大小写 51 # 52 # Entry.objects.get(title__regex=r'^(An?|The) +') 53 # Entry.objects.get(title__iregex=r'^(an?|the) +') 54 55 # date 56 # 57 # Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1)) 58 # Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1)) 59 60 # year 61 # 62 # Entry.objects.filter(pub_date__year=2005) 63 # Entry.objects.filter(pub_date__year__gte=2005) 64 65 # month 66 # 67 # Entry.objects.filter(pub_date__month=12) 68 # Entry.objects.filter(pub_date__month__gte=6) 69 70 # day 71 # 72 # Entry.objects.filter(pub_date__day=3) 73 # Entry.objects.filter(pub_date__day__gte=3) 74 75 # week_day 76 # 77 # Entry.objects.filter(pub_date__week_day=2) 78 # Entry.objects.filter(pub_date__week_day__gte=2) 79 80 # hour 81 # 82 # Event.objects.filter(timestamp__hour=23) 83 # Event.objects.filter(time__hour=5) 84 # Event.objects.filter(timestamp__hour__gte=12) 85 86 # minute 87 # 88 # Event.objects.filter(timestamp__minute=29) 89 # Event.objects.filter(time__minute=46) 90 # Event.objects.filter(timestamp__minute__gte=29) 91 92 # second 93 # 94 # Event.objects.filter(timestamp__second=31) 95 # Event.objects.filter(time__second=2) 96 # Event.objects.filter(timestamp__second__gte=31) 97 98 进阶操作
进阶操作
1 # extra 2 # 3 # extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) 4 # Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,)) 5 # Entry.objects.extra(where=['headline=%s'], params=['Lennon']) 6 # Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"]) 7 # Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid']) 8 9 # F 10 # 11 # from django.db.models import F 12 # models.Tb1.objects.update(num=F('num')+1) 13 14 15 # Q 16 # 17 # 方式一: 18 # Q(nid__gt=10) 19 # Q(nid=8) | Q(nid__gt=10) 20 # Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root') 21 22 # 方式二: 23 # con = Q() 24 # q1 = Q() 25 # q1.connector = 'OR' 26 # q1.children.append(('id', 1)) 27 # q1.children.append(('id', 10)) 28 # q1.children.append(('id', 9)) 29 # q2 = Q() 30 # q2.connector = 'OR' 31 # q2.children.append(('c1', 1)) 32 # q2.children.append(('c1', 10)) 33 # q2.children.append(('c1', 9)) 34 # con.add(q1, 'AND') 35 # con.add(q2, 'AND') 36 # 37 # models.Tb1.objects.filter(con) 38 39 40 # 执行原生SQL 41 # 42 # from django.db import connection, connections 43 # cursor = connection.cursor() # cursor = connections['default'].cursor() 44 # cursor.execute("""SELECT * from auth_user where id = %s""", [1]) 45 # row = cursor.fetchone()
其他操作
1 ################################################################## 2 # PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET # 3 ################################################################## 4 5 def all(self) 6 # 获取所有的数据对象 7 8 def filter(self, *args, **kwargs) 9 # 条件查询 10 # 条件可以是:参数,字典,Q 11 12 def exclude(self, *args, **kwargs) 13 # 条件查询 14 # 条件可以是:参数,字典,Q 15 16 def select_related(self, *fields) 17 性能相关:表之间进行join连表操作,一次性获取关联的数据。 18 model.tb.objects.all().select_related() 19 model.tb.objects.all().select_related('外键字段') 20 model.tb.objects.all().select_related('外键字段__外键字段') 21 22 def prefetch_related(self, *lookups) 23 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。 24 # 获取所有用户表 25 # 获取用户类型表where id in (用户表中的查到的所有用户ID) 26 models.UserInfo.objects.prefetch_related('外键字段') 27 28 29 30 from django.db.models import Count, Case, When, IntegerField 31 Article.objects.annotate( 32 numviews=Count(Case( 33 When(readership__what_time__lt=treshold, then=1), 34 output_field=CharField(), 35 )) 36 ) 37 38 students = Student.objects.all().annotate(num_excused_absences=models.Sum( 39 models.Case( 40 models.When(absence__type='Excused', then=1), 41 default=0, 42 output_field=models.IntegerField() 43 ))) 44 45 def annotate(self, *args, **kwargs) 46 # 用于实现聚合group by查询 47 48 from django.db.models import Count, Avg, Max, Min, Sum 49 50 v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')) 51 # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id 52 53 v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1) 54 # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 55 56 v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1) 57 # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 58 59 def distinct(self, *field_names) 60 # 用于distinct去重 61 models.UserInfo.objects.values('nid').distinct() 62 # select distinct nid from userinfo 63 64 注:只有在PostgreSQL中才能使用distinct进行去重 65 66 def order_by(self, *field_names) 67 # 用于排序 68 models.UserInfo.objects.all().order_by('-id','age') 69 70 def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) 71 # 构造额外的查询条件或者映射,如:子查询 72 73 Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,)) 74 Entry.objects.extra(where=['headline=%s'], params=['Lennon']) 75 Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"]) 76 Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid']) 77 78 def reverse(self): 79 # 倒序 80 models.UserInfo.objects.all().order_by('-nid').reverse() 81 # 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序 82 83 84 def defer(self, *fields): 85 models.UserInfo.objects.defer('username','id') 86 或 87 models.UserInfo.objects.filter(...).defer('username','id') 88 #映射中排除某列数据 89 90 def only(self, *fields): 91 #仅取某个表中的数据 92 models.UserInfo.objects.only('username','id') 93 或 94 models.UserInfo.objects.filter(...).only('username','id') 95 96 def using(self, alias): 97 指定使用的数据库,参数为别名(setting中的设置) 98 99 100 ################################################## 101 # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # 102 ################################################## 103 104 def raw(self, raw_query, params=None, translations=None, using=None): 105 # 执行原生SQL 106 models.UserInfo.objects.raw('select * from userinfo') 107 108 # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名 109 models.UserInfo.objects.raw('select id as nid from 其他表') 110 111 # 为原生SQL设置参数 112 models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,]) 113 114 # 将获取的到列名转换为指定列名 115 name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'} 116 Person.objects.raw('SELECT * FROM some_other_table', translations=name_map) 117 118 # 指定数据库 119 models.UserInfo.objects.raw('select * from userinfo', using="default") 120 121 ################### 原生SQL ################### 122 from django.db import connection, connections 123 cursor = connection.cursor() # cursor = connections['default'].cursor() 124 cursor.execute("""SELECT * from auth_user where id = %s""", [1]) 125 row = cursor.fetchone() # fetchall()/fetchmany(..) 126 127 128 def values(self, *fields): 129 # 获取每行数据为字典格式 130 131 def values_list(self, *fields, **kwargs): 132 # 获取每行数据为元祖 133 134 def dates(self, field_name, kind, order='ASC'): 135 # 根据时间进行某一部分进行去重查找并截取指定内容 136 # kind只能是:"year"(年), "month"(年-月), "day"(年-月-日) 137 # order只能是:"ASC" "DESC" 138 # 并获取转换后的时间 139 - year : 年-01-01 140 - month: 年-月-01 141 - day : 年-月-日 142 143 models.DatePlus.objects.dates('ctime','day','DESC') 144 145 def datetimes(self, field_name, kind, order='ASC', tzinfo=None): 146 # 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间 147 # kind只能是 "year", "month", "day", "hour", "minute", "second" 148 # order只能是:"ASC" "DESC" 149 # tzinfo时区对象 150 models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC) 151 models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai')) 152 153 """ 154 pip3 install pytz 155 import pytz 156 pytz.all_timezones 157 pytz.timezone(‘Asia/Shanghai’) 158 """ 159 160 def none(self): 161 # 空QuerySet对象 162 163 164 #################################### 165 # METHODS THAT DO DATABASE QUERIES # 166 #################################### 167 168 def aggregate(self, *args, **kwargs): 169 # 聚合函数,获取字典类型聚合结果 170 from django.db.models import Count, Avg, Max, Min, Sum 171 result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid')) 172 ===> {'k': 3, 'n': 4} 173 174 def count(self): 175 # 获取个数 176 177 def get(self, *args, **kwargs): 178 # 获取单个对象 179 180 def create(self, **kwargs): 181 # 创建对象 182 183 def bulk_create(self, objs, batch_size=None): 184 # 批量插入 185 # batch_size表示一次插入的个数 186 objs = [ 187 models.DDD(name='r11'), 188 models.DDD(name='r22') 189 ] 190 models.DDD.objects.bulk_create(objs, 10) 191 192 def get_or_create(self, defaults=None, **kwargs): 193 # 如果存在,则获取,否则,创建 194 # defaults 指定创建时,其他字段的值 195 obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2}) 196 197 def update_or_create(self, defaults=None, **kwargs): 198 # 如果存在,则更新,否则,创建 199 # defaults 指定创建时或更新时的其他字段 200 obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1}) 201 202 def first(self): 203 # 获取第一个 204 205 def last(self): 206 # 获取最后一个 207 208 def in_bulk(self, id_list=None): 209 # 根据主键ID进行查找 210 id_list = [11,21,31] 211 models.DDD.objects.in_bulk(id_list) 212 213 def delete(self): 214 # 删除 215 216 def update(self, **kwargs): 217 # 更新 218 219 def exists(self): 220 # 是否有结果 221 222 其他操作
Django原生SQL获取cursor字典
1 import pymysql 2 from django.db import connection, connections 3 4 connection.connect() 5 conn = connection.connection 6 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) 7 cursor.execute("""SELECT * from app01_userinfo""") 8 row = cursor.fetchone() 9 connection.close() 10 11 Django原生SQL获取cursor字典
数字自增,字符串更新
1 # 数字自增 2 from django.db.models import F 3 models.UserInfo.objects.update(num=F('num') + 1) 4 5 # 字符串更新 6 from django.db.models.functions import Concat 7 from django.db.models import Value 8 9 models.UserInfo.objects.update(name=Concat('name', 'pwd')) 10 models.UserInfo.objects.update(name=Concat('name', Value('666'))) 11 12 数字自增、字符串更新
ORM函数相关
1 # ########### 基础函数 ########### 2 3 # 1. Concat,用于做类型转换 4 # v = models.UserInfo.objects.annotate(c=Cast('pwd', FloatField())) 5 6 # 2. Coalesce,从前向后,查询第一个不为空的值 7 # v = models.UserInfo.objects.annotate(c=Coalesce('name', 'pwd')) 8 # v = models.UserInfo.objects.annotate(c=Coalesce(Value('666'),'name', 'pwd')) 9 10 # 3. Concat,拼接 11 # models.UserInfo.objects.update(name=Concat('name', 'pwd')) 12 # models.UserInfo.objects.update(name=Concat('name', Value('666'))) 13 # models.UserInfo.objects.update(name=Concat('name', Value('666'),Value('999'))) 14 15 # 4.ConcatPair,拼接(仅两个参数) 16 # v = models.UserInfo.objects.annotate(c=ConcatPair('name', 'pwd')) 17 # v = models.UserInfo.objects.annotate(c=ConcatPair('name', Value('666'))) 18 19 # 5.Greatest,获取比较大的值;least 获取比较小的值; 20 # v = models.UserInfo.objects.annotate(c=Greatest('id', 'pwd',output_field=FloatField())) 21 22 # 6.Length,获取长度 23 # v = models.UserInfo.objects.annotate(c=Length('name')) 24 25 # 7. Lower,Upper,变大小写 26 # v = models.UserInfo.objects.annotate(c=Lower('name')) 27 # v = models.UserInfo.objects.annotate(c=Upper('name')) 28 29 # 8. Now,获取当前时间 30 # v = models.UserInfo.objects.annotate(c=Now()) 31 32 # 9. substr,子序列 33 # v = models.UserInfo.objects.annotate(c=Substr('name',1,2)) 34 35 # ########### 时间类函数 ########### 36 # 1. 时间截取,不保留其他:Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,ExtractSecond, ExtractWeekDay, ExtractYear, 37 # v = models.UserInfo.objects.annotate(c=functions.ExtractYear('ctime')) 38 # v = models.UserInfo.objects.annotate(c=functions.ExtractMonth('ctime')) 39 # v = models.UserInfo.objects.annotate(c=functions.ExtractDay('ctime')) 40 # 41 # v = models.UserInfo.objects.annotate(c=functions.Extract('ctime', 'year')) 42 # v = models.UserInfo.objects.annotate(c=functions.Extract('ctime', 'month')) 43 # v = models.UserInfo.objects.annotate(c=functions.Extract('ctime', 'year_month')) 44 """ 45 MICROSECOND 46 SECOND 47 MINUTE 48 HOUR 49 DAY 50 WEEK 51 MONTH 52 QUARTER 53 YEAR 54 SECOND_MICROSECOND 55 MINUTE_MICROSECOND 56 MINUTE_SECOND 57 HOUR_MICROSECOND 58 HOUR_SECOND 59 HOUR_MINUTE 60 DAY_MICROSECOND 61 DAY_SECOND 62 DAY_MINUTE 63 DAY_HOUR 64 YEAR_MONTH 65 """ 66 67 # 2. 时间截图,保留其他:Trunc, TruncDate, TruncDay,TruncHour, TruncMinute, TruncMonth, TruncSecond, TruncYear 68 # v = models.UserInfo.objects.annotate(c=functions.TruncHour('ctime')) 69 # v = models.UserInfo.objects.annotate(c=functions.TruncDate('ctime')) 70 # v = models.UserInfo.objects.annotate(c=functions.Trunc('ctime','year'))
ORM自定义函数
1 from django.db.models.functions.base import Func 2 class CustomeFunc(Func): 3 function = 'DATE_FORMAT' 4 template = '%(function)s(%(expressions)s,%(format)s)' 5 6 def __init__(self, expression, **extra): 7 expressions = [expression] 8 super(CustomeFunc, self).__init__(*expressions, **extra) 9 10 v = models.UserInfo.objects.annotate(c=CustomeFunc('ctime',format="'%%Y-%%m'"))
model的数据验证(弱)
1 class UserInfo(models.Model): 2 name = models.CharField(max_length=32) 3 mail = models.EmailField() 4 5 def clean(self): 6 from django.core.exceptions import ValidationError 7 c = UserInfo.objects.filter(name=self.name).count() 8 if c: 9 raise ValidationError(message='用户名已经存在', code='code1')
1 obj = models.UserInfo(name='dandy', email='dandy') 2 obj.full_clean() # 数据验证 3 obj.save()
执行的时候是先models的字段的正则表达式验证,然后是models的钩子方法验证(clean),验证都通过了就执行数据的创建。但是对于这边有一点我们可以发现,我们只是做了关于一个字段的验证,如果多个的话不免有些繁琐,数据验证相对来说确实很弱。
Django中间件
https://www.cnblogs.com/wuzdandz/p/9577840.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发送给用户。这就是全局缓存的过程。
信号Signal
https://www.cnblogs.com/wuzdandz/p/9592116.html
强大的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.cleaned_data是不是会回忆起通过字典对model进行操作。
如果验证失败的话,就可以得到下面的错误列表
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': '邮箱格式错误'})
这是直接运行:
之前的报错就已经被替换了。并且自定制的功能也已经加入了。看到这肯定会很惊讶,因为不需要用if else的判断,所以很方便,很清晰。别着急,框架的魅力还有。。。
写了太多的input标签,可以有简单的方式么?=================================================》 有。
看一下这次的模板渲染。
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>{{ obj.user }}{{ obj.errors.user.0 }}</p> 11 <p>{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p> 12 <p>{{ obj.email }}{{ obj.errors.email.0 }}</p> 13 <input type="submit" name="提交" /> 14 </form> 15 </body> 16 </html>
同时需要修改下视图函数:
1 def fm(request): 2 if request.method == 'GET': 3 obj = FM() 4 return render(request,'form.html', {'obj': obj})
直接运行:
可以发现form帮我们封装了最简单的输入框了,当然啦,名字一定要对,因为都是通用的方法,道理都懂。并且报错其实form里面也已经封装好了,我们的obj.errors的渲染其实也不需要了(不需要是指最简单的验证,form里面提供的字段的验证)。
可以很极端的按F12去掉所有的装饰,还是可以看到我们辛辛苦苦的渲染的obj.errors的报错的。
会不会有人又说,不行,还是烦。。。能不能在简单点。。。能!!!
1 <form action="/fm/" method="POST"> 2 {% csrf_token %} 3 {{ obj.as_p }} 4 {{ obj.as_ul }} 5 <input type="submit" name="提交" /> 6 </form>
可以用obj.as_p或者ul来实现,还有种不太一样的。
1 <form action="/fm/" method="POST"> 2 <table>{{ obj.as_table }}</table> 3 <input type="submit" name="提交" /> 4 </form>
效果如下:
但是这样的缺点就是不太灵活,可定制新差,道理你应该也能相同,我们更推荐上面的方式。
这时候还有一个问题,方便归方便,那样式呢?form里面也可以实现样式编写。
1 class FM(forms.Form): 2 user = forms.CharField( 3 error_messages={'required':'用户名不能为空!'}, 4 widget=widgets.Textarea(attrs={'class':'c1'}) 5 ) 6 pwd = forms.CharField( 7 max_length=12, 8 min_length=6, 9 error_messages={'required':'密码不能为空!', 'min_length': '密码长度不能小于6.', 'max_length': '密码长度不能大于12.'}, 10 widget=widgets.PasswordInput 11 ) 12 email = forms.EmailField(error_messages={'required':'邮箱不能为空!', 'invalid': '邮箱格式错误'})
替换标签与定制样式就可以搞定了。
既然说到了form生成标签,就讲全了吧。
生成单选框的两种方式:
1 gender =fields.ChoiceField( 2 choices=((1, '男'), (2, '女'),), 3 initial=2, 4 widget=widgets.RadioSelect 5 ) 6 city = fields.CharField( 7 initial=2, 8 widget=widgets.Select(choices=((1,'上海'), (2, '北京'),)) 9 )
Form类
创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;
Django内置字段
1 Field 2 required=True, 是否允许为空 3 widget=None, HTML插件 4 label=None, 用于生成Label标签或显示内容 5 initial=None, 初始值 6 help_text='', 帮助信息(在标签旁边显示) 7 error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'} 8 show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直) 9 validators=[], 自定义验证规则 10 localize=False, 是否支持本地化 11 disabled=False, 是否可以编辑 12 label_suffix=None Label内容后缀 13 14 15 CharField(Field) 16 max_length=None, 最大长度 17 min_length=None, 最小长度 18 strip=True 是否移除用户输入空白 19 20 IntegerField(Field) 21 max_value=None, 最大值 22 min_value=None, 最小值 23 24 FloatField(IntegerField) 25 ... 26 27 DecimalField(IntegerField) 28 max_value=None, 最大值 29 min_value=None, 最小值 30 max_digits=None, 总长度 31 decimal_places=None, 小数位长度 32 33 BaseTemporalField(Field) 34 input_formats=None 时间格式化 35 36 DateField(BaseTemporalField) 格式:2015-09-01 37 TimeField(BaseTemporalField) 格式:11:12 38 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12 39 40 DurationField(Field) 时间间隔:%d %H:%M:%S.%f 41 ... 42 43 RegexField(CharField) 44 regex, 自定制正则表达式 45 max_length=None, 最大长度 46 min_length=None, 最小长度 47 error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'} 48 49 EmailField(CharField) 50 ... 51 52 FileField(Field) 53 allow_empty_file=False 是否允许空文件 54 55 ImageField(FileField) 56 ... 57 注:需要PIL模块,pip3 install Pillow 58 以上两个字典使用时,需要注意两点: 59 - form表单中 enctype="multipart/form-data" 60 - view函数中 obj = MyForm(request.POST, request.FILES) 61 62 URLField(Field) 63 ... 64 65 66 BooleanField(Field) 67 ... 68 69 NullBooleanField(BooleanField) 70 ... 71 72 ChoiceField(Field) 73 ... 74 choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),) 75 required=True, 是否必填 76 widget=None, 插件,默认select插件 77 label=None, Label内容 78 initial=None, 初始值 79 help_text='', 帮助提示 80 81 82 ModelChoiceField(ChoiceField) 83 ... django.forms.models.ModelChoiceField 84 queryset, # 查询数据库中的数据 85 empty_label="---------", # 默认空显示内容 86 to_field_name=None, # HTML中value的值对应的字段 87 limit_choices_to=None # ModelForm中对queryset二次筛选 88 89 ModelMultipleChoiceField(ModelChoiceField) 90 ... django.forms.models.ModelMultipleChoiceField 91 92 93 94 TypedChoiceField(ChoiceField) 95 coerce = lambda val: val 对选中的值进行一次转换 96 empty_value= '' 空值的默认值 97 98 MultipleChoiceField(ChoiceField) 99 ... 100 101 TypedMultipleChoiceField(MultipleChoiceField) 102 coerce = lambda val: val 对选中的每一个值进行一次转换 103 empty_value= '' 空值的默认值 104 105 ComboField(Field) 106 fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式 107 fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),]) 108 109 MultiValueField(Field) 110 PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用 111 112 SplitDateTimeField(MultiValueField) 113 input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y'] 114 input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M'] 115 116 FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中 117 path, 文件夹路径 118 match=None, 正则匹配 119 recursive=False, 递归下面的文件夹 120 allow_files=True, 允许文件 121 allow_folders=False, 允许文件夹 122 required=True, 123 widget=None, 124 label=None, 125 initial=None, 126 help_text='' 127 128 GenericIPAddressField 129 protocol='both', both,ipv4,ipv6支持的IP格式 130 unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用 131 132 SlugField(CharField) 数字,字母,下划线,减号(连字符) 133 ... 134 135 UUIDField(CharField) uuid类型 136 ...
Django内置插件:
1 TextInput(Input) 2 NumberInput(TextInput) 3 EmailInput(TextInput) 4 URLInput(TextInput) 5 PasswordInput(TextInput) 6 HiddenInput(TextInput) 7 Textarea(Widget) 8 DateInput(DateTimeBaseInput) 9 DateTimeInput(DateTimeBaseInput) 10 TimeInput(DateTimeBaseInput) 11 CheckboxInput 12 Select 13 NullBooleanSelect 14 SelectMultiple 15 RadioSelect 16 CheckboxSelectMultiple 17 FileInput 18 ClearableFileInput 19 MultipleHiddenInput 20 SplitDateTimeWidget 21 SplitHiddenDateTimeWidget 22 SelectDateWidget
常用选择插件
1 # 单radio,值为字符串 2 # user = fields.CharField( 3 # initial=2, 4 # widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),)) 5 # ) 6 7 # 单radio,值为字符串 8 # user = fields.ChoiceField( 9 # choices=((1, '上海'), (2, '北京'),), 10 # initial=2, 11 # widget=widgets.RadioSelect 12 # ) 13 14 # 单select,值为字符串 15 # user = fields.CharField( 16 # initial=2, 17 # widget=widgets.Select(choices=((1,'上海'),(2,'北京'),)) 18 # ) 19 20 # 单select,值为字符串 21 # user = fields.ChoiceField( 22 # choices=((1, '上海'), (2, '北京'),), 23 # initial=2, 24 # widget=widgets.Select 25 # ) 26 27 # 多选select,值为列表 28 # user = fields.MultipleChoiceField( 29 # choices=((1,'上海'),(2,'北京'),), 30 # initial=[1,], 31 # widget=widgets.SelectMultiple 32 # ) 33 34 35 # 单checkbox 36 # user = fields.CharField( 37 # widget=widgets.CheckboxInput() 38 # ) 39 40 41 # 多选checkbox,值为列表 42 # user = fields.MultipleChoiceField( 43 # initial=[2, ], 44 # choices=((1, '上海'), (2, '北京'),), 45 # widget=widgets.CheckboxSelectMultiple 46 # )
在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么需要自定义构造方法从而达到此目的。
方式一:
1 from django.forms import Form 2 from django.forms import widgets 3 from django.forms import fields 4 from django.core.validators import RegexValidator 5 6 class MyForm(Form): 7 8 user = fields.ChoiceField( 9 # choices=((1, '上海'), (2, '北京'),), 10 initial=2, 11 widget=widgets.Select 12 ) 13 14 def __init__(self, *args, **kwargs): 15 super(MyForm,self).__init__(*args, **kwargs) 16 # self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),) 17 # 或 18 self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')
方式二:
使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现
1 from django import forms 2 from django.forms import fields 3 from django.forms import widgets 4 from django.forms import models as form_model 5 from django.core.exceptions import ValidationError 6 from django.core.validators import RegexValidator 7 8 class FInfo(forms.Form): 9 authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) 10 # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())
自定义验证规则
方式一:
1 from django.forms import Form 2 from django.forms import widgets 3 from django.forms import fields 4 from django.core.validators import RegexValidator 5 6 class MyForm(Form): 7 user = fields.CharField( 8 validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], 9 )
方式二:
1 import re 2 from django.forms import Form 3 from django.forms import widgets 4 from django.forms import fields 5 from django.core.exceptions import ValidationError 6 7 8 # 自定义验证规则 9 def mobile_validate(value): 10 mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') 11 if not mobile_re.match(value): 12 raise ValidationError('手机号码格式错误') 13 14 15 class PublishForm(Form): 16 17 18 title = fields.CharField(max_length=20, 19 min_length=5, 20 error_messages={'required': '标题不能为空', 21 'min_length': '标题最少为5个字符', 22 'max_length': '标题最多为20个字符'}, 23 widget=widgets.TextInput(attrs={'class': "form-control", 24 'placeholder': '标题5-20个字符'})) 25 26 27 # 使用自定义验证规则 28 phone = fields.CharField(validators=[mobile_validate, ], 29 error_messages={'required': '手机不能为空'}, 30 widget=widgets.TextInput(attrs={'class': "form-control", 31 'placeholder': u'手机号码'})) 32 33 email = fields.EmailField(required=False, 34 error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'}, 35 widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
方法三:自定义方法
1 from django import forms 2 from django.forms import fields 3 from django.forms import widgets 4 from django.core.exceptions import ValidationError 5 from django.core.validators import RegexValidator 6 7 class FInfo(forms.Form): 8 username = fields.CharField(max_length=5, 9 validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], ) 10 email = fields.EmailField() 11 12 def clean_username(self): 13 """ 14 Form中字段中定义的格式匹配完之后,执行此方法进行验证 15 :return: 16 """ 17 value = self.cleaned_data['username'] 18 if "666" in value: 19 raise ValidationError('666已经被玩烂了...', 'invalid') 20 return value
方式四:同时生成多个标签进行验证
1 from django.forms import Form 2 from django.forms import widgets 3 from django.forms import fields 4 5 from django.core.validators import RegexValidator 6 7 8 ############## 自定义字段 ############## 9 class PhoneField(fields.MultiValueField): 10 def __init__(self, *args, **kwargs): 11 # Define one message for all fields. 12 error_messages = { 13 'incomplete': 'Enter a country calling code and a phone number.', 14 } 15 # Or define a different message for each field. 16 f = ( 17 fields.CharField( 18 error_messages={'incomplete': 'Enter a country calling code.'}, 19 validators=[ 20 RegexValidator(r'^[0-9]+$', 'Enter a valid country calling code.'), 21 ], 22 ), 23 fields.CharField( 24 error_messages={'incomplete': 'Enter a phone number.'}, 25 validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid phone number.')], 26 ), 27 fields.CharField( 28 validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.')], 29 required=False, 30 ), 31 ) 32 super(PhoneField, self).__init__(error_messages=error_messages, fields=f, require_all_fields=False, *args, 33 **kwargs) 34 35 def compress(self, data_list): 36 """ 37 当用户验证都通过后,该值返回给用户 38 :param data_list: 39 :return: 40 """ 41 return data_list 42 43 ############## 自定义插件 ############## 44 class SplitPhoneWidget(widgets.MultiWidget): 45 def __init__(self): 46 ws = ( 47 widgets.TextInput(), 48 widgets.TextInput(), 49 widgets.TextInput(), 50 ) 51 super(SplitPhoneWidget, self).__init__(ws) 52 53 def decompress(self, value): 54 """ 55 处理初始值,当初始值initial不是列表时,调用该方法 56 :param value: 57 :return: 58 """ 59 if value: 60 return value.split(',') 61 return [None, None, None]
初始化数据
在Web应用程序中开发编写功能时,时常用到获取数据库中的数据并将值初始化在HTML中的标签上。
1、Form
1 from django.forms import Form 2 from django.forms import widgets 3 from django.forms import fields 4 from django.core.validators import RegexValidator 5 6 7 class MyForm(Form): 8 user = fields.CharField() 9 10 city = fields.ChoiceField( 11 choices=((1, '上海'), (2, '北京'),), 12 widget=widgets.Select 13 )
2、Views
1 from django.shortcuts import render, redirect 2 from .forms import MyForm 3 4 5 def index(request): 6 if request.method == "GET": 7 values = {'user': 'root', 'city': 2} 8 obj = MyForm(values) 9 10 return render(request, 'index.html', {'form': obj}) 11 elif request.method == "POST": 12 return redirect('http://www.google.com') 13 else: 14 return redirect('http://www.google.com')
3、HTML
1 <form method="POST" enctype="multipart/form-data"> 2 {% csrf_token %} 3 <p>{{ form.user }} {{ form.user.errors }}</p> 4 <p>{{ form.city }} {{ form.city.errors }}</p> 5 6 <input type="submit"/> 7 </form>
select实例
1 class FM(forms.Form): 2 user = forms.CharField( 3 error_messages={'required':'用户名不能为空!'}, 4 widget=widgets.Textarea(attrs={'class':'c1'}) 5 ) 6 pwd = forms.CharField( 7 max_length=12, 8 min_length=6, 9 error_messages={'required':'密码不能为空!', 'min_length': '密码长度不能小于6.', 'max_length': '密码长度不能大于12.'}, 10 widget=widgets.PasswordInput(render_value=True) 11 ) 12 email = forms.EmailField(error_messages={'required':'邮箱不能为空!', 'invalid': '邮箱格式错误'}) 13 gender =fields.ChoiceField( 14 choices=((1, '男'), (2, '女'),), 15 initial=2, 16 widget=widgets.RadioSelect 17 ) 18 city = fields.CharField( 19 initial=2, 20 widget=widgets.Select(choices=((1,'上海'), (2, '北京'),)) 21 ) 22 usertype = fields.ChoiceField( 23 choices=[], 24 widget= widgets.Select 25 ) 26 usertype1 = fields.CharField( 27 widget=widgets.Select() 28 ) 29 usertype2 = ModelChoiceField( 30 queryset= models.UserType.objects.all() 31 ) 32 33 def __init__(self, *args, **kwargs): 34 super(FM, self).__init__(*args, **kwargs) 35 # self.fields['usertype'].choices = models.UserType.objects.values_list('id','user_type') 36 self.fields['usertype1'].widget.choices = models.UserType.objects.values_list('id', 'user_type')
1 def fm(request): 2 if request.method == 'GET': 3 obj = FM() 4 obj.fields['usertype'].choices = models.UserType.objects.values_list('id', 'user_type') 5 return render(request,'form.html', {'obj': obj})
1 <form action="/fm/" method="POST"> 2 {% csrf_token %} 3 <table>{{ obj.as_table }}</table> 4 <input type="submit" name="提交" /> 5 </form>
这里需要详细说明下的是,后面我们定义了三种形式的usertype,通过fields里面的ChoiceField,CharField标签和(from django.forms.models import ModelChoiceField)的ModelChoiceField标签。usertype是通过ChoiceField来做的,所以我们需要配置它的choices选项,并且把标签设置为select;usertype1是通过CharField来实现的,同样需要通过widget配置其select标签,不过选项应该是配置在Select()里面的widget=widgets.Select(choices=[]).当然,到现在为止我们都没有给标签赋值,因为现在赋值是有问题的,首先,我们看一下这个class的结构,就会发现,所有的标签对象都是类变量,在程序启动时就会执行,类的实例化对象不会重新执行。所以这里需要重新赋值给这个类变量标签。
方法一,就是在view视图函数里面,因为实例化完FM类需要传给前台,而控制标签的函数就是obj.fields['对应的标签类变量名'].choices,需要重新去数据库获取数据赋值。
方法二,FM类继承forms.Form类,对其构造函数继承并重构,跟方法一的思想是一样的,因为__init__是类的实例化过程的一部分,所以在这里我们可以直接给标签的choices重新赋值。
以上2中原理上都是一样的,只是一个通过类的继承与重构实现的,一个是在视图函数实现的,当然了,这里当然是推荐大家使用继承与重构的方法,毕竟当需要在很多个网页做这样的效果实现的时候,不需要再所有视图函数就行赋值,直接做到了一步到位。
usertype 和 usertype1的区别则在于,一个是使用ChoiceField另一个是使用CharField实现的。ChoiceField需要将参数配置的其内部,而CharField则需要配置在其内部的widget里面。而最后的usertype2则又是一种完全不同的方法,它是django提供给我们的一个控件,已经实现了对于绑定数据的重写。但是需要在models内部写一个__str__返回一下self.名称才能看到其选项名称。
Form的钩子(强大的验证功能)
前面我们说到了form的生成标签的简单验证,现在我们来具体提及一下form提供给我的验证钩子。
obj = FM(request.POST) auth = obj.is_valid()
这里其实我们是知道的,is_valid是对于form的类的验证,所以既然提到了钩子验证,那当然是在这个is_valid内部预留的方法位置。根据is_valid往内部翻,会找到not self.errors,然后找到full_clean方法,下面有三个方法
self._clean_fields() self._clean_form() self._post_clean()
这三个函数对应了3种钩子,我们一一下钻看一下。首先是_clean_fields()
1 for name, field in self.fields.items(): 2 if field.disabled: 3 value = self.get_initial_for_field(field, name) 4 else: 5 value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) 6 try: 7 if isinstance(field, FileField): 8 initial = self.get_initial_for_field(field, name) 9 value = field.clean(value, initial) 10 else: 11 value = field.clean(value) 12 self.cleaned_data[name] = value 13 if hasattr(self, 'clean_%s' % name): 14 value = getattr(self, 'clean_%s' % name)() 15 self.cleaned_data[name] = value 16 except ValidationError as e: 17 self.add_error(name, e)
从代码可以发现里面有一个映射hasattr(self, 'clean_%s' % name), getattr(self, 'clean_%s' % name)(),看到这应该就懂了,其实这边是最基础的各个标签对应的验证钩子,form会去找,有没有对应的标签钩子,比如定义了一个标签user,对应的user标签的验证就是clean_user。所以需要在继承forms类里面定义这个函数。
1 def clean_user(self): 2 c = models.User.objects.filter(user=self.cleaned_data['user']).count() 3 if c: 4 return self.cleaned_data['user'] 5 else: 6 raise ValidationError('用户名不存在111', code='invalid')
可是各个标签单独的验证并不能解决一个很简单的问题,就比如登陆,需要考虑到账户名跟密码的组合,那就不再属于单个标签的钩子了。此时需要提到下面一个钩子,_clean_form()里面有一个clean()方法。这就是预留给组合标签使用的一个方法。同样的
1 def clean(self): 2 cp = models.User.objects.filter(user=self.cleaned_data.get('user'), pwd=self.cleaned_data['pwd']).count() 3 if cp: 4 return self.cleaned_data 5 else: 6 raise ValidationError('用户名或者密码错误!')
这里楼主遇到一个坑,就是在标签跟组合标签验证的时候,就比如上面的用户名,我们可能验证到用户名不存在的问题,django的clean_data的原理就是清理出通过验证的数据,所以这个时候,可以发现,clean_user执行完不通过的时候,就已经没有clean_data['user']这个数据了,所以组合验证的时候就不可以用clean_data['user']来去user标签的值了,这样会直接报错,需要些try catch,明显很麻烦,只需要改成get就会返回None,这时候models的数据库查询不受影响,就可以完美避开这个坑。所以需要理解django forms的执行顺序跟验证数据clean_data的关系。
最后一个钩子是留下来做其他验证的,其实我也不是很清楚作用
1 def _post_clean(self): 2 pass
概述一下form里面的验证,1、标签的基本验证,(CharField之类的正则表达式验证);2、标签的自定制功能验证(CharField内部的钩子);3、标签对应的钩子函数clean_func;4、整体的自定制钩子方法;5、其他的预留钩子_post_clean
errors的结构
1 obj.errors 2 { 3 '__all__': [], 4 'user': [{'code':'xxx', 'message':'xxxxx'}], 5 'pwd': [{'code':'xxx', 'message': 'xxxxq'}] 6 }
其中__all__是指向全局的报错,也可能叫做NON_FIELD_ERRORS(from django.core.exceptions import NON_FIELD_ERRORS)
Form内置错误信息的序列化
上面已经说到了errors的结构了。首先我们打印一下errors的类型,发现是django.forms.utils.ErrorDict,导入一下这个ErrorDict,可以下钻到这个方法内部
其内部封装了如下的方法,as_json指的是转换成json字符串;as_data则是转换成字典。其他的可以自行打印一下看看是什么格式。
假设有这样一个自定制的字典:
{'status':True, 'errors':None, 'data':None}
errors的信息加入
ret = obj.errors.as_json()
这时候序列化,以ajax请求为例子的
return HttpResponse(json.dumps(ret))
此时前台取到数据需要做2部反序列化操作。就。。。不写了。
另外一种是as_data():前面说这个是返回的一个字典,此时对于字典打印一下,发现里面的value是ValidationError('This field is required').
下面稍微深入的补充一下序列化
从json.dumps下钻,找到其参数cls,会发现
再来看看JSONEncoder是什么
这里已经引出来了这个default方法,打开看一下
def default(self, o): """Implement this method in a subclass such that it returns a serializable object for ``o``, or calls the base implementation (to raise a ``TypeError``). For example, to support arbitrary iterators, you could implement default like this:: def default(self, o): try: iterable = iter(o) except TypeError: pass else: return list(iterable) # Let the base class default method raise the TypeError return JSONEncoder.default(self, o) """ raise TypeError("Object of type '%s' is not JSON serializable" % o.__class__.__name__)
有没有发现,其实这一块也是预留了一个钩子一样的方法给我们自定制,如果不满足,序列化报错。
我们重写这个方法
1 class JsonCustomEncoder(json.JSONEncoder): 2 def default(self, field): 3 if isinstance(field, ValidationError): 4 return {'code': field.code, 'messages': field.messages} 5 else: 6 return json.JSONEncoder.default(self, field)
调用
1 ret['errors'] = obj.errors.as_data() 2 result = json.dumps(ret, cls=JsonCustomEncoder) 3 return HttpResponse(result)
这样前台ajax请求就可以接收到并一次解决。是不是高大上很多,屌丝都看不懂的。
序列化
既然说到序列化操作,就一起讲完。关于Django中的序列化主要应用在将数据库中检索的数据返回给客户端用户,特别的Ajax请求一般返回的为Json格式。
1、serializers
from django.core import serializers ret = models.BookType.objects.all() data = serializers.serialize("json", ret)
2、json.dumps
import json ret = models.BookType.objects.all().values('id', 'caption') ret=list(ret) result = json.dumps(ret)
由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,大家是不是知道是什么了?没错就是上面的errors的自定制序列化
1 import json 2 from datetime import date 3 from datetime import datetime 4 5 class JsonCustomEncoder(json.JSONEncoder): 6 7 def default(self, field): 8 9 if isinstance(field, datetime): 10 return field.strftime('%Y-%m-%d %H:%M:%S') 11 elif isinstance(field, date): 12 return field.strftime('%Y-%m-%d') 13 else: 14 return json.JSONEncoder.default(self, field)
ModelForm
前面讲解完model和form,现在终于可以说modelform了。ModelForm是什么呢?其实可以这样理解,model跟form都需要对字段进行定义并指定类型,而通过ModelForm则可以省去form中的定义。可以看做是一种轻量级的更简化的工具。既然是model和form的结合,大概可以想到是不是结合了model和form的功能呢?带着这个疑问,开启这一段。
一个简单的modelform
1 class UserModelForm(forms.ModelForm): 2 class Meta: 3 model = models.User # 指向类 4 fields = '__all__' # 指向参数标签 5 # fields = {'user', 'email'} 选择网页产生标签的字段 6 # exclude = {'pwd'} 选择网页不产生标签的字段(排除掉)
然后view里面
1 def mf(request): 2 if request.method == 'GET': 3 obj = UserModelForm() 4 return render(request, 'mf.html', {"obj": obj})
model
1 class UserType(models.Model): 2 user_type = models.CharField(max_length=32) 3 def __str__(self): 4 return self.user_type 5 6 class User(models.Model): 7 user = models.CharField(max_length=32) 8 pwd = models.CharField(max_length=32) 9 email = models.EmailField(max_length=32) 10 ut = models.ForeignKey(to='UserType', to_field='id')
模板
1 <body> 2 <form action="mf.html" method="post"> 3 {% csrf_token %} 4 {{ obj.as_table }} 5 <input type="submit" /> 6 </form> 7 8 </body>
效果:
这样相比于之前,又方便了很多。大家不免好奇,modelform跟form到底是怎么组成的,下面我们下钻来看一下
Form验证 UserInfoForm ==> Form ==> BaseForm(is_valid...) ModelForm验证 UserInfoModelForm ==> ModelForm ==> BaseModelForm ==> BaseForm
此时你一定会疑惑,关于标签的定制化修饰,跟数据验证呢?
ModelForm组件
1 ModelForm 2 a. class Meta: 3 model, # 对应Model的 4 fields=None, # 字段 5 exclude=None, # 排除字段 6 labels=None, # 提示信息 7 help_texts=None, # 帮助提示信息 8 widgets=None, # 自定义插件 9 error_messages=None, # 自定义错误信息(整体错误信息from django.core.exceptions import NON_FIELD_ERRORS) 10 field_classes=None # 自定义字段类 (也可以自定义字段) 11 localized_fields=('birth_date',) # 本地化,如:根据不同时区显示数据 12 如: 13 数据库中 14 2016-12-27 04:10:57 15 setting中的配置 16 TIME_ZONE = 'Asia/Shanghai' 17 USE_TZ = True 18 则显示: 19 2016-12-27 12:10:57 20 b. 验证执行过程 21 is_valid -> full_clean -> 钩子 -> 整体错误 22 23 c. 字典字段验证 24 def clean_字段名(self): 25 # 可以抛出异常 26 # from django.core.exceptions import ValidationError 27 return "新值" 28 d. 用于验证 29 model_form_obj = XXOOModelForm() 30 model_form_obj.is_valid() 31 model_form_obj.errors.as_json() 32 model_form_obj.clean() 33 model_form_obj.cleaned_data 34 e. 用于创建 35 model_form_obj = XXOOModelForm(request.POST) 36 #### 页面显示,并提交 ##### 37 # 默认保存多对多 38 obj = form.save(commit=True) 39 # 不做任何操作,内部定义 save_m2m(用于保存多对多) 40 obj = form.save(commit=False) 41 obj.save() # 保存单表信息 42 obj.save_m2m() # 保存关联多对多信息 43 44 f. 用于更新和初始化 45 obj = model.tb.objects.get(id=1) 46 model_form_obj = XXOOModelForm(request.POST,instance=obj) 47 ... 48 49 PS: 单纯初始化 50 model_form_obj = XXOOModelForm(initial={...})
给个简单的例子
1 class UserModelForm(forms.ModelForm): 2 class Meta: 3 model = models.User # 指向类 4 fields = '__all__' # 指向参数标签 5 # fields = {'user', 'email'} 选择产生标签的字段 6 # exclude = {'pwd'} 选择排除开不产生标签的字段 7 # labels = { 8 # 'user': '用户名', 9 # 'pwd': '密码', 10 # } 11 # help_texts = { 12 # 'user': '....', 13 # } 14 # widgets = { 15 # 'user': widgets.Textarea(attrs={'class':'c1'}), 16 # } 17 # error_messages = { 18 # '__all__': {}, # 整体错误信息 19 # 'email': { 20 # 'required': '邮箱不能为空', 21 # 'invalid': '邮箱不合法' 22 # } 23 # } 24 # field_classes = { 25 # # 'email': fields.URLField, 26 # }
加入多对多的实例
1 class UserType(models.Model): 2 user_type = models.CharField(max_length=32) 3 def __str__(self): 4 return self.user_type 5 6 class UserGroup(models.Model): 7 group_name = models.CharField(max_length=32) 8 def __str__(self): 9 return self.group_name 10 11 class User(models.Model): 12 user = models.CharField(max_length=32) 13 pwd = models.CharField(max_length=32) 14 email = models.EmailField(max_length=32) 15 ut = models.ForeignKey(to='UserType', to_field='id') 16 u2g = models.ManyToManyField(UserGroup)
执行数据库语句,重构数据库
python manage.py makemigrations python manage.py migrate
做点不一样的,修改了下view视图函数
1 def mf(request): 2 if request.method == 'GET': 3 obj = UserModelForm() 4 return render(request, 'mf.html', {"obj": obj}) 5 if request.method == 'POST': 6 obj = UserModelForm(request.POST) 7 if obj.is_valid(): 8 obj.save() 9 else: 10 print(obj.errors) 11 return render(request, 'mf.html', {"obj": obj})
运行页面,输入数据:
因为通过了验证,所以可以发现数据库保存了此次输入。就是这么简单粗暴。。
浅浅的看一下obj.save()内部
1 def save(self, commit=True): 2 """ 3 Save this form's self.instance object if commit=True. Otherwise, add 4 a save_m2m() method to the form which can be called after the instance 5 is saved manually at a later time. Return the model instance. 6 """ 7 if self.errors: 8 raise ValueError( 9 "The %s could not be %s because the data didn't validate." % ( 10 self.instance._meta.object_name, 11 'created' if self.instance._state.adding else 'changed', 12 ) 13 ) 14 if commit: 15 # If committing, save the instance and the m2m data immediately. 16 self.instance.save() 17 self._save_m2m() 18 else: 19 # If not committing, add a method to the form to allow deferred 20 # saving of m2m data. 21 self.save_m2m = self._save_m2m 22 return self.instance
值得关注的三个地方,首先是参数申明的时候的commit = True,所以obj.save()会默认提交数据,执行
if commit: # If committing, save the instance and the m2m data immediately. self.instance.save() # 执行数据库的操作,基本表跟外键(不执行ManyToMany) self._save_m2m() # 执行ManyToMany
如果设置为false呢?
else: # If not committing, add a method to the form to allow deferred # saving of m2m data. self.save_m2m = self._save_m2m
这里的_save_m2m赋值给了sace_m2m,并且下面还有一个return self.instance.。所以自己重新提交的时候,
1 def mf(request): 2 if request.method == 'GET': 3 obj = UserModelForm() 4 return render(request, 'mf.html', {"obj": obj}) 5 if request.method == 'POST': 6 obj = UserModelForm(request.POST) 7 if obj.is_valid(): 8 instance = obj.save(False) 9 instance.save() 10 obj.save_m2m() 11 else: 12 print(obj.errors) 13 return render(request, 'mf.html', {"obj": obj})
其实ModelForm更强大的功能在于能够在用于新url产生网页这方面,比如修改用户信息。
一个用户列表网页
1 def user_list(request): 2 obj = models.User.objects.values('user','id') 3 return render(request, 'user_list.html', {'obj': obj})
1 <body> 2 <ul> 3 {% for row in obj %} 4 <li>{{ row.user }} - <a href="/edit-{{ row.id }}">编辑</a></li> 5 {% endfor %} 6 </ul> 7 </body>
一个编辑用户网页
1 def user_edit(request, nid): 2 if request.method == 'GET': 3 obj = models.User.objects.filter(id=nid).first() 4 mf = UserModelForm(instance=obj) 5 return render(request, 'user_edit.html', {'mf': mf, 'nid': nid}) 6 elif request.method == 'POST': 7 8 obj = models.User.objects.filter(id=nid).first() 9 mf = UserModelForm(request.POST, instance=obj) 10 if mf.is_valid(): 11 mf.save() 12 else: 13 print(mf.errors.as_json()) 14 return HttpResponse('ok')
1 <body> 2 <form method="post" action="/edit-{{ nid }}"> 3 {% csrf_token %} 4 {{ mf.as_p }} 5 <input type="submit" /> 6 </form> 7 </body>
对于新url页面的跟赋值应该先通过id获取对象然后把对象传给modelform里面的instance。而更新数据的时候则需要先获取旧的对象,传给modelform的instance来告诉它需要更新的对象是谁,最后是save,中间是一系列的验证之类。可见ModelForm对面上面这种情况书写起来真的是一气呵成,很便利。
最后补充下ModelForm里面如何数据库之外的标签,比如登陆的时候的记住密码的选择。
class UserModelForm(forms.ModelForm): is_rem = fields.CharField( widget=widgets.CheckboxInput ) class Meta: model = models.User # 指向类 fields = '__all__' # 指向参数标签
讨论点一
对于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返回是无效的。
讨论点二
ORM的性能分析
假设models里面有一个user类,里面有基础信息(name, age, mail),还有个外键ut对应usertype这张表。 首先是基本的models操作。 users = models.User.objects.all() for row in users: row.name row.ut.name # 这一步django会重新执行一次sql连接查询操作。所以如果有10条数据就一共需要执行11次sql查询。性能肯定很差 # select_related users = models.User.objects.all().select_related('外键1') for row in users: row.user, row.ut.name # select_related 表之间进行join连表操作,一次性获取关联的数据。 # profetch_related users = models.User.objects.filter(id__gt=30).prefetch_related('ut', '外键2') # 会发起2次sql请求 # select * from Users where id > 30 # 获取上一步中所有的ut_id = [1, 2] # select * from user_type where id in [1, 2] # select * from user_type where id in [1, 2] 如果2个外键,再发一个请求就好了 for row in user: print(...) ==> django会直接到内存里面去抓取而不会去再去进行sql请求了
扩展:自定义分页
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>
import
re
from
django.forms
import
Form
from
django.forms
import
widgets
from
django.forms
import
fields
from
django.core.exceptions
import
ValidationError
# 自定义验证规则
def
mobile_validate(value):
mobile_re
=
re.
compile
(r
'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$'
)
if
not
mobile_re.match(value):
raise
ValidationError(
'手机号码格式错误'
)
class
PublishForm(Form):
title
=
fields.CharField(max_length
=
20
,
min_length
=
5
,
error_messages
=
{
'required'
:
'标题不能为空'
,
'min_length'
:
'标题最少为5个字符'
,
'max_length'
:
'标题最多为20个字符'
},
widget
=
widgets.TextInput(attrs
=
{
'class'
:
"form-control"
,
'placeholder'
:
'标题5-20个字符'
}))
# 使用自定义验证规则
phone
=
fields.CharField(validators
=
[mobile_validate, ],
error_messages
=
{
'required'
:
'手机不能为空'
},
widget
=
widgets.TextInput(attrs
=
{
'class'
:
"form-control"
,
'placeholder'
: u
'手机号码'
}))
email
=
fields.EmailField(required
=
False
,
error_messages
=
{
'required'
: u
'邮箱不能为空'
,
'invalid'
: u
'邮箱格式错误'
},
widget
=
widgets.TextInput(attrs
=
{
'class'
:
"form-control"
,
'placeholder'
: u
'邮箱'
}))