python学习笔记 Django进阶
https://www.cnblogs.com/wupeiqi/articles/6144178.html
一、Ajax
jquery的ajax
添加:
1、下载引入jquery
2、
$.ajax({ url:'/add_classes.html', type:'GET', data:{'username':'root','password':'123'} success:function(asg){ //回调函数,asg是服务器端返回的数据 }
})
补充 js主动刷新页面:
window.location.reload()
通过serialize()自动获取form标签input框内容
serialize() 方法通过序列化表单值,创建 URL 编码文本字符串。
$.ajax({ data:$('#formid').serialize() })
二、分页
1、分页原理
最基本的分页原理,通过手动设置完成
将数据进行切片发送给前端,前端根据切片数据进行显示。
下一页下一页也是依据后端传输的值来前端设置
views
from django.shortcuts import render import math # Create your views here. # 模拟数据,自动生成一个列表 user_list = [] for i in range(1, 999): user_list.append('admin' + str(i) + '-000' + str(i)) def index(request): # 获取当前页码 p = int(request.GET.get('p')) # 计算切片的起始位置 start = (p-1) * 10 # 计算切片的结束位置 end = p * 10 # 对数据列表进行切片 obj_list = user_list[start:end] # 计算出总页数 count_page = math.ceil(len(user_list)/10) # 对上一页进行判断 # 如果已经到了首页了,就将上一页的页码设置为首页 if p <= 1: per_page = 1 # 否则上一页的页码等于当前页码减一 else: per_page = p - 1 # 对下一页进行判断 # 如果已经到了最后一页了,就将上一页的页码设置为总页数 if p >= count_page: next_page = count_page # 否则下一页页码等于当前页码加一 else: next_page = p + 1 # 给前端发送切割后的数据,以及上一页页码,下一页页码 return render(request, 'index.html', {"user_list": obj_list, 'per_page': per_page, 'next_page': next_page})
html
<body> {% for i in user_list %} <li>{{ i }}</li> {% endfor %} <a href="/index?p={{ per_page }}">上一页</a> <a href="/index?p={{ next_page }}">下一页</a> </body>
2、Django内置分页
如果单单是为了完成上面的功能,可以利用Djando内置的分页方法来完成
- 两个对象 Paginator Page, 需要传入全部数据,每页显示条目数,当前页码
- 一定要利用include来减少html页码代码冗余
首先需要导入模块
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
views
from django.shortcuts import render from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage # 模拟数据,自动生成一个列表 user_list = [] for i in range(1, 999): user_list.append('admin' + str(i) + '-000' + str(i)) def index(request): current_page = request.GET.get('p') # 实例化一个Paginator对象,需要传入数据列表和每页显示多少条数据的值 paginator = Paginator(user_list, 10) # Paginator 的方法 # per_page: 每页显示条目数量 # count: 数据总个数 # num_pages:总页数 # page_range:总页数的索引范围,如: (1,10),(1,200) # page: page对象 try: # 实例化一个page对象,需要传入当前页码,这两个对象都有自己的方法,切两个对象互通 posts = paginator.page(current_page) # page 的方法 # has_next 是否有下一页 # next_page_number 下一页页码 # has_previous 是否有上一页 # previous_page_number 上一页页码 # object_list 分页之后的数据列表,已经切片好的数据 # number 当前页 # paginator paginator对象 # 判断当前页码是否为整数,如果是非整数则将当前页设置为1 except PageNotAnInteger: posts = paginator.page(1) # 判断当前页码是否为空,意思是像-1 和 10000不在页码索引范围内的值,设置为总页数也就是最后一页 except EmptyPage: posts = paginator.page(paginator.num_pages) return render(request, 'index.html', {'posts': posts})
html
<body> {% for i in posts.object_list %} <li>{{ i }}</li> {% endfor %} {% if posts.has_previous %} <a href="/index?p={{ posts.previous_page_number }}">上一页</a> {% else %} <a href="/index?p=#">上一页</a> {% endif %} {% if posts.has_next %} <a href="/index?p={{ posts.next_page_number }}">下一页</a> {% else %} <a href="/index?p={{ posts.paginator.num_pages }}">下一页</a> {% endif %} </body>
3、扩展Django内置分页
自定义一个类CustomPaginator继承Django的Paginator类,然后可以实现页码列表的扩展
views
from django.shortcuts import render from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage # 模拟数据,自动生成一个列表 user_list = [] for i in range(1, 666): user_list.append('admin' + str(i) + '-000' + str(i)) class CustomPaginator(Paginator): def __init__(self, current_page, max_pager_num, *args, **kwargs): # 通过当前页和最多显示页码、总页码,可以计算出页码的范围 # 当前页 self.current_page = int(current_page) # 页码最多显示多少页 self.max_pager_num = max_pager_num # 通过super继承Paginator类的所有方法 super(CustomPaginator, self).__init__(*args, **kwargs) def page_num_range(self): # 对极值的判断 # 先判断总页数如果少于最多的页数时,范围为1到总页数 if self.num_pages < self.max_pager_num: return range(1, self.num_pages) # 当总页数大于最多显示页数时,先取最多显示页码的一半 part = int(self.max_pager_num/2) # 当当前页小于最多页码的一半时,页码范围为1到总页数 # 对最左侧极值的判断,避免出现-1页 if self.current_page <= part: return range(1, self.max_pager_num+1) # 当当前页加上part大于总页数时,显示的是当前页减去最多显示页码加一 到 当前页加上part的范围 # 对最右侧极值的判断,避免出现大于总页数的页码 if self.current_page + part >= self.num_pages: return range(self.num_pages-self.max_pager_num+1, self.num_pages+1) # 对于正常页码就是当前页码减去part 到 当前页码加上part的范围 return range(self.current_page-part, self.current_page+part+1) def index(request): current_page = request.GET.get('p') # 扩展就需要使用自定义的类CustomPaginator来实例化 # 根据自定义需要传入当前页,最多显示页码, 数据列表,每页显示的条数 paginator = CustomPaginator(current_page, 11, user_list, 10) try: # 实例化一个page对象,需要传入当前页码,这两个对象都有自己的方法,切两个对象互通 posts = paginator.page(current_page) # 判断当前页码是否为整数,如果是非整数则将当前页设置为1 except PageNotAnInteger: posts = paginator.page(1) # 判断当前页码是否为空,意思是像-1 和 10000不在页码索引范围内的值,设置为总页数也就是最后一页 except EmptyPage: posts = paginator.page(paginator.num_pages) return render(request, 'index.html', {'posts': posts})
html
<body> {% for i in posts.object_list %} <li>{{ i }}</li> {% endfor %} {% include 'include/pager.html' %} </body>
include
{% if posts.has_previous %} <a href="/index?p={{ posts.previous_page_number }}">上一页</a> {% else %} <a href="/index?p=#">上一页</a> {% endif %} {% for i in posts.paginator.page_num_range %} {% if i == posts.number %} <a style="font-size: 25px" href="/index?p={{ i }}">{{ i }}</a> {% else %} <a href="/index?p={{ i }}">{{ i }}</a> {% endif %} {% endfor %} {% if posts.has_next %} <a href="/index?p={{ posts.next_page_number }}">下一页</a> {% else %} <a href="/index?p={{ posts.paginator.num_pages }}">下一页</a> {% endif %} <span> {{ posts.number }}/{{ posts.paginator.num_pages }} </span>
4、自定义分页
上面传统的分页需要传入几个参数
----所有的数据user_list
----当前页
----每页显示30条
----最多页码数
自定义分页,不需要传入多有的数据,影响性能
---所有数据的总个数
---当前页
---每页显示条数
---最多显示页码数
自定义一个py文件
pager.py
import math from django.utils.safestring import mark_safe class Paginator(object): def __init__(self, totalCount, currentPage, baseUrl, perItemPageNum=10, maxPageNum=7): # 数据的总个数 self.total_count = totalCount try: format_currentPage = int(currentPage) if format_currentPage < 0: self.current_page = 1 else: self.current_page = format_currentPage except Exception as e: # 当前页 self.current_page = 1 # 基础页 self.base_url = baseUrl # 每页显示条目数 self.per_item_pager_num = perItemPageNum # 最多显示多少页 self.max_pager_num = maxPageNum # 数据切片的开始位置 def start(self): if self.current_page > self.num_pages: self.current_page = self.num_pages return (self.num_pages - 1) * self.per_item_pager_num return (self.current_page - 1) * self.per_item_pager_num # 数据切片的结束位置 def end(self): if self.current_page > self.num_pages: return self.num_pages * self.per_item_pager_num return self.current_page * self.per_item_pager_num # 数据的总页数 @property def num_pages(self): # 利用了math的向上取整 return math.ceil(self.total_count/self.per_item_pager_num) # 数据页码范围 def page_num_range(self): # 对极值的判断 # 先判断总页数如果少于最多的页数时,范围为1到总页数 if self.num_pages < self.max_pager_num: return range(1, self.num_pages+1) # 当总页数大于最多显示页数时,先取最多显示页码的一半 part = int(self.max_pager_num/2) # 当当前页小于最多页码的一半时,页码范围为1到总页数 # 对最左侧极值的判断,避免出现-1页 if self.current_page <= part: return range(1, self.max_pager_num+1) # 当当前页加上part大于总页数时,显示的是当前页减去最多显示页码加一 到 当前页加上part的范围 # 对最右侧极值的判断,避免出现大于总页数的页码 if self.current_page + part >= self.num_pages: return range(self.num_pages-self.max_pager_num+1, self.num_pages+1) # 对于正常页码就是当前页码减去part 到 当前页码加上part的范围 return range(self.current_page-part, self.current_page+part+1) # 页码的html标签 def page_str(self): page_list = [] if self.current_page <= 1: first = "<li><a href=''>首页</a></li>" else: first = "<li><a href='{}{}'>首页</a></li>".format(self.base_url, 1) page_list.append(first) if self.current_page == 1: perv = '<li><a href="#">上一页</a>' else: perv = '<li><a href="{}{}">上一页</a></li>'.format(self.base_url, self.current_page-1) page_list.append(perv) for i in self.page_num_range(): if i == self.current_page: temp = '<li class="active"><a href="{}{}">{}</a></li>'.format(self.base_url, i, i) else: temp = '<li><a href="{}{}">{}</a></li>'.format(self.base_url, i, i) page_list.append(temp) if self.current_page >= self.num_pages: nex = '<li><a href="#">下一页</a>' else: nex = '<li><a href="{}{}">下一页</a>'.format(self.base_url, self.current_page+1) page_list.append(nex) if self.current_page >= self.num_pages: last = "<li><a href=''>尾页</a></li>" else: last = "<a href='{}{}'>尾页</a></li>".format(self.base_url, self.num_pages) page_list.append(last) result = ''.join(page_list) # 把字符串转成html语言,否则就需要在html页面加上 |safe return mark_safe(result)
views
from django.shortcuts import render from app01.pager import Paginator # 导入自定义模块 # 模拟数据,自动生成一个列表 user_list = [] for i in range(1, 600): user_list.append('admin' + str(i) + '-000' + str(i)) def index2(request): current_page = request.GET.get('p') baseurl = request.path + '?p=' # 获取基础页 page_obj = Paginator(600, current_page, baseurl) data = user_list[page_obj.start():page_obj.end()] return render(request, 'index2.html', {'data': data, 'page_obj': page_obj})
html
<body> {% for i in data %} <li>{{ i }}</li> {% endfor %} <ul class="pagination"> {{ page_obj.page_str}} </ul> </body>
总结,分页时需要做三件事,自定义分页不需要将多有数据传入进行切片,只需要知道数据总个数就可以,增加后台的可靠性:
- 创建自定义的类,需要传入数据总个数,当前页,基础页参数
- 根据总个数和每页条目数计算出总页数,根据当前页计算出数据的切片位置,根据当前页和最多显示页数计算出页码的范围
- 创建标签文本列表,转换成字符串(首页尾页上一页下一页,【1】【2】【3】等),然后通过mark_safe方法将字符串转换成html语言,否则需要在前端文件添加 |safe
Form组件
Django的Form主要具有一下几大功能:
- 生成HTML标签
- 验证用户数据(显示错误信息)
- HTML Form提交保留上次提交数据
- 初始化页面显示内容
1、对用户请求的验证
-ajax
-form
views:
from django.shortcuts import render, HttpResponse # 导入forms和fields from django import forms from django.forms import fields # 创建一个类,继承forms.Form class FormF1(forms.Form): # 创建字段,匹配前端数据(包含正则表达式) user = fields.CharField( # 验证数据 # 最长6位 max_length=6, # 不能为空 required=True, # 错误信息中文表示 error_messages={ 'required': '不能为空', 'max_length': '最长6位数', 'invalid': '格式错误', } ) pwd = fields.CharField( min_length=8, required=True ) age = fields.IntegerField( required=True, error_messages={ 'required': '不能为空', 'invalid': '格式错误', } ) email = fields.EmailField( required=True, error_messages={ 'required': '不能为空', 'invalid': '格式错误', } ) def index(request): if request.method == 'GET': return render(request, 'index.html') else: # 将post数据传入FormF1类实例化对象obj obj = FormF1(request.POST) # 通过is_valid方法判断数据是否正确 if obj.is_valid(): # 如果数据验证通过,可以通过obj.cleaned_data得到字典 print(obj.cleaned_data) return HttpResponse('ok') else: # 如果验证失败可以通过obj.errors来获取错误提示 print(obj.errors) return render(request, 'index.html', {'obj': obj})
index.html:
<form action="/index/" method="post"> {# post数据传入后端,通过name进行匹配字段进行验证 #} {# obj.errors.user.0 因为错误提示可能有多条,此处选择优先显示第一条即可 #} <p><input type="text" name="user">{{ obj.errors.user.0 }}</p> <p><input type="text" name="pwd">{{ obj.errors.pwd.0 }}</p> <p><input type="text" name="age">{{ obj.errors.age.0 }}</p> <p><input type="text" name="email">{{ obj.errors.email.0 }}</p> <input type="submit"> </form> </body>
2、生成HTML代码
上面的FormF1类有另外一个功能就是生成HTML代码,可以避免前端和后端同时开发时,因为name名不统一造成无法匹配
对于上面views的改动
def index(request): if request.method == 'GET': # 不传参数,生成对象,前端可以通过obj.user....生成对应的HTML标签 obj = FormF1() return render(request, 'index.html', {'obj': obj}) else: # 将post数据传入FormF1类实例化对象obj obj = FormF1(request.POST) # 通过is_valid方法判断数据是否正确 if obj.is_valid(): # 如果数据验证通过,可以通过obj.cleaned_data得到字典 print(obj.cleaned_data) return HttpResponse('ok') else: # 如果验证失败可以通过obj.errors来获取错误提示
# HTML Form提交保留上次提交数据
print(obj.errors) return render(request, 'index.html', {'obj': obj})
index.html:
前端不需要自己再写input框了,通过后端传的obj直接生成html标签
<form action="/index/" method="post"> {# 通过{{ obj.user }}调用html代码 #} <p>{{ obj.user }}{{ obj.errors.user.0 }}</p> <p>{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p> <p>{{ obj.age }}{{ obj.errors.age.0 }}</p> <p>{{ obj.email }}{{ obj.errors.email.0 }}</p> <input type="submit"> </form> </body>
3、Form简单运用
简单的input框 增 改 查
框架结构,form类用一个单独的py文件
models:
from django.db import models # 创建数据库,字段 class UserInfo(models.Model): username = models.CharField(max_length=32) email = models.CharField(max_length=32)
formr.py
from django import forms from django.forms import fields class UserForm(forms.Form): username = fields.CharField(min_length=6, required=True) email = fields.EmailField()
views:
from django.shortcuts import render from django.shortcuts import redirect # 导入form类 from app01 import formr # 导入数据库模板 from app01 import models # 查 def users(request): if request.method == 'GET': user_list = models.UserInfo.objects.all() obj = formr.UserForm() return render(request, 'users.html', {'obj': obj, 'user_list': user_list}) # 增 def add_user(request): if request.method == 'GET': # 通过form类直接生成html标签 obj = formr.UserForm() return render(request, 'add_user.html', {'obj': obj}) else: obj = formr.UserForm(request.POST) if obj.is_valid(): # 将验证通过的字典,直接传入数据库创建, 原理是create可以接受字典数据 models.UserInfo.objects.create(**obj.cleaned_data) return redirect('/users/') else: return render(request, 'add_user.html', {'obj': obj}) # 改 def edit_user(request): # 通过nid定位 nid = request.GET.get('nid') if request.method == 'GET': user = models.UserInfo.objects.filter(id=nid).first() # 通过nid查询到的值,传入form类,生成html标签时自带默认值,form类也可以传入字典数据 obj = formr.UserForm({'username': user.username, 'email': user.email}) return render(request, 'edit_user.html', {'obj': obj, 'nid': nid}) else: obj = formr.UserForm(request.POST) if obj.is_valid(): models.UserInfo.objects.filter(id=nid).update(**obj.cleaned_data) return redirect("/users") else: return render(request, 'edit_user.html', {'obj': obj})
html文件 前端也用一行代码代替{{obj.as_p}},所有的obj字段转换成一个p标签
-----------------users.html-------------------- <body> <a href="/add_user/">添加</a> <ul> {% for row in user_list %} <li>{{ row.id }}-{{ row.username }}-{{ row.email }}<a href="/edit_user/?nid={{ row.id }}">编辑</a></li> {% endfor %} </ul> </body> --------------add_user.html---------------- <body> <form action="/add_user/" method="post" novalidate> {% csrf_token %} <p>{{ obj.username }}{{ obj.username.errors.0 }}</p> <p>{{ obj.email }}{{ obj.email.errors.0 }}</p> <p><input type="submit" value="提交"></p> </form> </body> ----------------edit_user.html------------- <body> <form action="/edit_user/?nid={{ nid }}" method="post" novalidate> {% csrf_token %} <p>{{ obj.username }}{{ obj.username.errors.0 }}</p> <p>{{ obj.email }}{{ obj.email.errors.0 }}</p> <p><input type="submit" value="提交"></p> </form> </body>
4、Form类常用字段和插件
创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;
(1)、Django内置字段如下:红色为重点掌握
所有字段都继承到了Field
Field required=True, 是否允许为空 widget=None, HTML插件 label=None, 用于生成Label标签或显示内容 initial=None, 初始值 help_text='', 帮助信息(在标签旁边显示) error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'} show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直) validators=[], 自定义验证规则 localize=False, 是否支持本地化 disabled=False, 是否可以编辑 label_suffix=None Label内容后缀 CharField(Field) max_length=None, 最大长度 min_length=None, 最小长度 strip=True 是否移除用户输入空白 IntegerField(Field) max_value=None, 最大值 min_value=None, 最小值 FloatField(IntegerField) ... DecimalField(IntegerField) max_value=None, 最大值 min_value=None, 最小值 max_digits=None, 总长度 decimal_places=None, 小数位长度 BaseTemporalField(Field) input_formats=None 时间格式化 DateField(BaseTemporalField) 格式:2015-09-01 TimeField(BaseTemporalField) 格式:11:12 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12 DurationField(Field) 时间间隔:%d %H:%M:%S.%f ... RegexField(CharField) regex, 自定制正则表达式 max_length=None, 最大长度 min_length=None, 最小长度 error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'} EmailField(CharField) ... FileField(Field) allow_empty_file=False 是否允许空文件 ImageField(FileField) ... 注:需要PIL模块,pip3 install Pillow 以上两个字典使用时,需要注意两点: - form表单中 enctype="multipart/form-data" - view函数中 obj = MyForm(request.POST, request.FILES) URLField(Field) ... BooleanField(Field) ... NullBooleanField(BooleanField) ... ChoiceField(Field) ... choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),) required=True, 是否必填 widget=None, 插件,默认select插件 label=None, Label内容 initial=None, 初始值 help_text='', 帮助提示 ModelChoiceField(ChoiceField) ... django.forms.models.ModelChoiceField queryset, # 查询数据库中的数据 empty_label="---------", # 默认空显示内容 to_field_name=None, # HTML中value的值对应的字段 limit_choices_to=None # ModelForm中对queryset二次筛选 ModelMultipleChoiceField(ModelChoiceField) ... django.forms.models.ModelMultipleChoiceField TypedChoiceField(ChoiceField) coerce = lambda val: val 对选中的值进行一次转换 empty_value= '' 空值的默认值 MultipleChoiceField(ChoiceField) ... TypedMultipleChoiceField(MultipleChoiceField) coerce = lambda val: val 对选中的每一个值进行一次转换 empty_value= '' 空值的默认值 ComboField(Field) fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式 fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),]) MultiValueField(Field) PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用 SplitDateTimeField(MultiValueField) input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y'] input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M'] FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中 path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 required=True, widget=None, label=None, initial=None, help_text='' GenericIPAddressField protocol='both', both,ipv4,ipv6支持的IP格式 unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用 SlugField(CharField) 数字,字母,下划线,减号(连字符) ... UUIDField(CharField) uuid类型 UUID是根据MAC以及当前时间等创建的不重复的随机字符串 ...
(2)、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
5、常用选择插件
# 单radio,值为字符串 # user = fields.CharField( # initial=2, # widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),)) # ) # 单radio,值为字符串 # user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), # initial=2, # widget=widgets.RadioSelect # ) # 单select,值为字符串 # user = fields.CharField( # initial=2, # widget=widgets.Select(choices=((1,'上海'),(2,'北京'),)) # ) # 单select,值为字符串 # user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), # initial=2, # widget=widgets.Select # )
# 多选select,值为列表
# xdb = fields.CharField(
# widget=widgets.SelectMultiple(choices=[(1, '武昌'), (2, '江岸'), (3, '汉口')],)
# )
# 多选select,值为列表 # user = fields.MultipleChoiceField( # choices=((1,'上海'),(2,'北京'),), # initial=[1,], # widget=widgets.SelectMultiple # ) # 单checkbox # user = fields.CharField( # widget=widgets.CheckboxInput() # ) # 多选checkbox,值为列表 # user = fields.MultipleChoiceField( # initial=[2, ], # choices=((1, '上海'), (2, '北京'),), # widget=widgets.CheckboxSelectMultiple # )
特殊的单选或多选时,数据能否实时更新??
结果是 ----数据库数据删除了一组数据,变成两个用户,而页面没有实时更新,还是三个用户!!! 如果想要实时更新,需要重启程序。这个不合理!
原因是页面加载时,首先执行了obj = Loveform(), Loveform()是一个类,所以实例化的过程首先执行了类属性__init__方法,并将结果加载到内存中
Loveform()默认没有__init__方法,所以调用了父类的init方法,将price和user_id的值加入了内存中,所以无论怎么刷新也不会实时更新,必须重启程序,
才可以得到正确的结果。
代码如下
views:
class Loveform(forms.Form): price = fields.IntegerField() user_id = fields.IntegerField( widget=widgets.Select(choices=models.UserInfo.objects.values_list('id', 'username')) ) def love(request): obj = Loveform() return render(request,'love.html', {'obj':obj})
html:
<form action=""> <p>价格:{{ obj.price }}</p> <p>姓名:{{ obj.user_id }}</p> </form>
model:
class UserInfo(models.Model): username = models.CharField(max_length=32) email = models.CharField(max_length=32)
解决办法:
方式一:自定义构造方法
1、自定义init方法,同时继承父类的init方法,这个步骤相当于没有做任何操作,但是可以自定制__init__方法了
def __init__(self, *args, **kwargs): super(Loveform, self).__init__(*args, **kwargs)
2、已知每次执行Loveform()都会先执行__init__方法, 所以将user_id的值在__init__中定义,并将数据库查询结果赋值,再加载到内存中的记录。
def __init__(self, *args, **kwargs): # self.fields必须在super后面,因为super会先将Lovefrom中的字段拷贝一份到self.fields中,所以必须在前面 # 否则self.fields中就还没有user_id这个字段 super(Loveform, self).__init__(*args, **kwargs) # self.fields 就是Loveform这些字段,字段里面有user_id,然后这个字段又有widget.choices self.fields['user_id'].widget.choices = models.UserInfo.objects.values_list('id', 'username')
代码如下
views:
class Loveform(forms.Form): price = fields.IntegerField() user_id = fields.IntegerField( widget=widgets.Select() ) def __init__(self, *args, **kwargs): # self.fields必须在super后面,因为super会先将Lovefrom中的字段拷贝一份到self.fields中,所以必须在前面 # 否则self.fields中就还没有user_id这个字段 super(Loveform, self).__init__(*args, **kwargs) # self.fields 就是Loveform里面的所有字段,字段里面有user_id,然后这个字段又有widget.choices,可以赋值 self.fields['user_id'].widget.choices = models.UserInfo.objects.values_list('id', 'username') def love(request): obj = Loveform() return render(request,'love.html', {'obj': obj})
html:
方式二:实时更新的另一种办法,django内置,不推荐使用
ModelChoiceField(ChoiceField) ... django.forms.models.ModelChoiceField queryset, # 查询数据库中的数据 empty_label="---------", # 默认空显示内容 to_field_name=None, # HTML中value的值对应的字段 limit_choices_to=None # ModelForm中对queryset二次筛选 ModelMultipleChoiceField(ModelChoiceField) ... django.forms.models.ModelMultipleChoiceField
Model这两个是跟数据库相关的字段,里面有个参数是queryset,直接可以拿数据库查询结果
代码如下:
from django.forms.models import ModelChoiceField class Loveform(forms.Form): price = fields.IntegerField() # user_id = fields.IntegerField( # widget=widgets.Select() # ) user_id2 = ModelChoiceField( queryset=models.UserInfo.objects.all(), to_field_name='username' ) def love(request): obj = Loveform() return render(request,'love.html', {'obj': obj})
数据是实时更新了,但是页面显示的是一个对象名,不是我们想要的username
这个需要在model中加一个str方法: 如果说这个方法简单的话,他也有一个致命的弱点,那就是耦合性太高
这次我需要的是username,如果还需要email实时更新的话就没办法实现了,因为str方法只有一个,同时也只
能return一个参数,所以此方法不推荐使用!!!
class UserInfo(models.Model): username = models.CharField(max_length=32) email = models.CharField(max_length=32) def __str__(self): return self.username
6、Form组件扩展,自定义验证规则
方式一:
from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.validators import RegexValidator class MyForm(forms.Form): user = fields.CharField( # 利用父类的validators方法自定义验证规则,此规则是一个列表,可以同时验证多个规则 validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], )
RegexValidator两个关键参数,第一个是正则表达式,第二个是错误信息
方式二:
class MyForm(forms.Form): # 利用RegexField 字段实现自定义规则 user = fields.RegexField( regex=r'^[0-9]+$', error_messages={ 'invalid': '请输入数字', 'required': '输入不能为空', } )
方式三:
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): # 使用自定义验证规则 phone = fields.CharField(validators=[mobile_validate, ], error_messages={'required': '手机不能为空'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'手机号码'}))
方式四:自定义方法
当我们需要验证form表单数据同时,也需要判断该数据时候在数据库中存在,比如ID
第一思路是在 if obj.is_valid(): 下面做各种判断,如果不正确那么errors字典中添加错误。
其实Django有更简单的方法,先看下面的源码。
当数据经过正则匹配以后会查找是否有clean_字段名 这样一个方法,如果有就执行这个方法此时就可以自定制规则,再做一次判断
如果有错误需要raise ValidationError错误,只有这个错误才会被捕捉到,然后返回错误信息
代码如下:
class MyForm(forms.Form): # 利用RegexField 字段实现自定义规则 username = fields.RegexField( regex=r'^[\u4e00-\u9fa5]+$', error_messages={ 'invalid': '请输入中文', 'required': '输入不能为空', } ) # 自定义方法 clean_字段名,
Form中字段中定义的格式匹配完之后,执行此方法进行验证
# 返回值必须是self.cleaned_data['字段名'] # 如果判断不正确,需要手动抛出错误raise ValidationError(),错误提示信息直接加到括号内 def clean_username(self): v = self.cleaned_data['username'] if models.UserInfo.objects.filter(username=v).count(): raise ValidationError('用户名已存在') else: return v
此方法的缺陷是自定义方法 clean_字段名 只能对当前字段验证,不能验证其他字段
方式四:
验证多个条件时:利用def clean(self):
def clean(self): value_dict = self.cleaned_data v1 = value_dict.get('user_name') v2 = value_dict.get("user_id") if v1 == 'root' and v2 == '1': raise ValidationError('整体信息错误!') return self.cleaned_data
7、序列化
在通过数据库调用数据是,大概有四种类型的数据
第一种是Queryset集合集合中是对象,由于对象不是python的基本数据类型,所以不能直接用json.dumps
from django.core import serializers
def get_data(request): ret = {'status': False, "message": None} try: user_list = models.Cpu_name.objects.all() print(user_list) # <QuerySet [<Cpu_name: Cpu_name object (1)>, <Cpu_name: Cpu_name object (13)>]> ret['message'] = serializers.serialize('json', user_list) return HttpResponse(json.dumps(ret)) except Exception: pass
返回给前端的是一个字符串,user_list被serialize序列化了一次,还有一次json.dumps,所以需要在前端进行两次json.parse反序列化
注意serialize只能序列化对象,其他的类型不可以
第二种是Queryset集合中是一个列表,列表是python的基本数据类型所以可以直接利用json.dumps序列化
def get_data(request): ret = {'status': False, "message": None} try: user_list = models.Cpu_name.objects.all().values('cpu_name', 'cpu_code') print(user_list) # <QuerySet [{'cpu_name': '骁龙 865', 'cpu_code': 'sm8250'}, {'cpu_name': '2', 'cpu_code': '3'}]> # QuerySet中是一个列表,我们只需要通过list将他转化成列表即可 ret['message'] = list(user_list) print(ret['message']) # [{'cpu_name': '骁龙 865', 'cpu_code': 'sm8250'}, {'cpu_name': '2', 'cpu_code': '3'}] return HttpResponse(json.dumps(ret)) except Exception: pass
第三种和第二种大致相同,结果是一个元祖,元祖也是基本数据类型
def get_data(request): ret = {'status': False, "message": None} try: user_list = models.Cpu_name.objects.all().values_list('cpu_name', 'cpu_code') print(user_list) # <QuerySet [('骁龙 865', 'sm8250'), ('2', '3')]> # QuerySet中是一个列表,我们只需要通过list将他转化成列表即可 ret['message'] = list(user_list) print(ret['message']) # [('骁龙 865', 'sm8250'), ('2', '3')] return HttpResponse(json.dumps(ret)) except Exception: pass
第四种,时间日期
由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,如:
import json from datetime import date from datetime import datetime class JsonCustomEncoder(json.JSONEncoder): def default(self, field): if isinstance(field, datetime): return o.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(field, date): return o.strftime('%Y-%m-%d') else: return json.JSONEncoder.default(self, field) # ds = json.dumps(d, cls=JsonCustomEncoder)
总结,四种数据类型,如果是对象,需要用serialize先进行序列化,然后再用json.dumps序列化
如果是列表或者元祖可以直接利用json.dumps序列化