批量操作数据、自定义分页器、form组件、钩子函数、modelform组件
批量操作数据
需求:用之前将的内容去实现浏览器访问django路由,并立刻创建10万条数据展示到前端页面
def index(request):
book_list=[]
for i in range(10000):
book_obj=models.Book.objects.create(title=f'第{i}本书')
book_list.append(book_obj)
return render(request,'index.html',{'book_list':book_list})
但是,这样明显地效率太低了,等于要执行10000次插入数据的请求。
我们可以先创建出10000个book对象到内存中,然后再批量插入数据,这样会快很多。
book_list=(models.Book(title=f'第{i}本书') for i in range(10000)) # 列表生成式,在被迭代前是个迭代器对象,可以节约内存地址
models.Book.objects.bulk_create(book_list) # 批量创建数据
book_queryset=models.Book.objects.all()
return render(request, 'index.html', locals())
对于大批量数据的创建,直接使用create可能会造成数据库的崩溃,django对此进行了优化,并为我们提供了批量操作的方法:
- 批量数据创建:bulk_create()
- 批量数据修改:bulk_update()
bulk_update的使用
"""
def bulk_update(self, objs, fields, batch_size=None):
...
"""
book_list=(models.Book(pk=i,title=f'第{i}本书123') for i in range(1,10001))
models.Book.objects.bulk_update(book_list,['title'],1000) # 第二个参数必须是列表或元组包字段名,第三个参数是切片添加,默认为0就是一次添加完
批量数据展示
当页面的数据量比较大的时候,就应该考虑分页。注意以下几点,就可以实现一个简单的分页器:
- QuerySet切片操作
- 分页样式添加
- 页码展示:根据总数据和每页展示的数据得出总页码:
divmod
方法 - 如何渲染出所有的页码标签:前端模版语法不支持range,但是后端支持,我们可以在后端创建好html标签然后传递给html页面使用
- 如何限制住展示的页面标签个数:页码推荐使用奇位数(对称美),利用当前页前后固定位数来限制
- 首尾页码展示范围问题
上述就是分页器组件的推导流程,我们无需真正编写,django自带一个分页器组件,但是不太好用,我们自己也写了一个。
自定义分页器
from app01.plugins import mypage
book_query = models.Book.objects.all()
page_obj = mypage.Pagination(current_page=request.GET.get('page'),
all_count=book_query.count()
)
page_query = book_query[page_obj.start:page_obj.end]
return render(request, 'bookList.html', locals())
{% for book_obj in page_query %}
<p class="text-center">{{ book_obj.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }}
form组件
前言
实现该需求:编写用户登录功能并且校验数据返回提示信息(form表单)
- views.py
def login(request):
user_dict={'username':'','password':''}
if request.method=='POST':
username=request.POST.get('username')
password=request.POST.get('password')
if username=='jason':
user_dict['username']='用户名不能是jason'
if password=="123":
user_dict['password']='密码错了'
return render(request,'login.html',{'user_dict':user_dict})
- html页面
<form action="" method="post">
<p>用户:<input type="text" name="username">
<span>{{ user_dict.username }}</span>
</p>
<p>密码:<input type="text" name="password">
<span>{{ user_dict.password }}</span>
</p>
<p><input type="submit">提交</p>
</form>
- 效果展示:点击后验证的信息都会发送到html页面的span标签文本内
django为我们提供了一个功能更加全面,也更为简洁的组件:form
组件
form组件功能
- 数据校验:支持提前设置各种校验规则,之后自动校验
- 渲染页面:支持直接渲染获取用户数据的各种标签
- 展示信息:支持针对不同的校验失败展示不同的提示
form类创建
class MyForm(forms.Form):
name=forms.CharField(max_length=6,min_length=2) # 用户名最长6个字符,最短2个字符
pwd=forms.IntegerField(max_value=100000,min_value=0) # 密码范围为0到100000
email=forms.EmailField() # 邮箱必须符合邮箱格式(至少有@符)
数据校验功能
- 传递待校验的数据
form_obj=views.MyForm({'name':'jason','pwd':12345,'email':'123'})
- 判断所有的数据是否符合校验
form_obj.is_vaild()
- 获取符合校验规则的数据
form_obj.cleaned_data
- 查阅不符合校验规则的数据及错误原因
form_obj.errors
注意:
- form类中编写的字段默认都是必填的,少传则肯定通不过校验:
is_valid
- 校验如果多传了一些字段,则不参与校验,全程忽略
渲染标签功能
当我们创建一个form_obj时,通过上图可以看出,form_obj就是name、pwd、email三个input框,当然它的实际功能不止于此。
方式1:
<form action="" method="post">
{{ form_obj.as_p}}
{{ form_obj.as_table }}
{{ form_obj.as_ul }}
<p><input type="submit"></p>
</form>
但是这种方式封装程度太高了,也无法使用单独的form属性,扩展性差
方式2:
<form action="" method="post">
{{form_obj.name.label}}
{{ form_obj.name }}
{{ form_obj.pwd.label }}
{{ form_obj.pwd }}
{{ form_obj.email.label }}
{{ form_obj.email }}
<p><input type="submit"></p>
</form>
第二种封装程度低,扩展性好,但是当字段过多的时候,代码就过于冗余。
方式3:推荐使用
<form action="" method="post">
{% for form in form_obj %}
{{ form.label }}
{{ form }}
{% endfor %}
<p><input type="submit"></p>
</form>
如果某个标签不需要,在for循环里面加个if判断就好了,扩展性好,封装程度低,代码也不冗余。
补充知识点
模版语法的注释和html的注释的区别
{# 模版语法注释 #} ---通过浏览器的检查无法看到
<!--html页面注释--> ---通过浏览器的检查可以看到
展示提示信息
因为前端的校验是弱不禁风的,往往数据校验这种事情都是后端完成,但是form组件默认在前端做了校验,我们可以取消:
form表单取消浏览器自动添加的数据校验功能:novalidate
参数
<form action="" method="post" novalidate>
取消了前段的校验后我们就可以将数据通过post请求发送到后端,让后端进行验证,如果有不合法的信息,通过模版语法将errors传递到前端显示。
代码如下:
# views.py
from django import forms
class MyForm(forms.Form):
name=forms.CharField(max_length=6,min_length=2)
pwd=forms.IntegerField(max_value=100000)
email=forms.EmailField()
def login(request):
form_obj=MyForm()
if request.method=='POST':
form_obj=MyForm(request.POST)
if form_obj.is_valid(): # 当数据全部合法后执行下面代码
print(form_obj.cleaned_data) # 清洗后的数据,不合法的会剔除
else:
print(form_obj.errors) # 不合法的数据
return render(request,'login.html',locals())
# html
<form action="" method="post" novalidate>
{% for form in form_obj %}
<p>
{{ form.label }}
{{ form }}
<span style="color: red">{{ form.errors.0 }}</span>
</p>
{% endfor %}
<p><input type="submit"></p>
</form>
重要的字段参数与插件
创建Form类时,主要涉及到字段
和插件
,字段用于对用户请求数据的验证,插件用于自动生成HTML
initial
初始值,input框里面的初始值
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三" # 设置默认值
)
pwd = forms.CharField(min_length=6, label="密码")
error_messages
重写错误信息提示
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required": "不能为空",
"invalid": "格式错误",
"min_length": "用户名最短8位"
}
)
pwd = forms.CharField(min_length=6, label="密码")
django form所有内置字段
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
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) 数字,字母,下划线,减号(连字符)
...
字段校验
RegexValidator验证器
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
class MyForm(Form):
user = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
)
钩子函数
在组件中实现自定义的校验方式,它分为:
- 局部钩子:校验单个字段
- 全局钩子:校验多个字段
from django import forms
class MyForm(forms.Form):
"""第一层校验"""
name=forms.CharField(max_length=6,min_length=2,label="用户名")
pwd=forms.IntegerField(max_value=100000,min_value=0,label='密码')
age=forms.IntegerField(max_value=150,min_value=0,label='年龄')
confirm_pwd=forms.IntegerField(max_value=100000,label='确认密码')
email=forms.EmailField(label='邮箱')
"""第二层校验:钩子函数"""
# 判断name是否存在=======> 局部钩子:只用到了cleaned_data的name值
def clean_name(self):
name=self.cleaned_data.get('name')
res=models.User.objects.filter(name=name).first()
if res:
return self.add_error('name','用户名已经存在')
return name
# 判断两个密码是否一致======>全局钩子:当用到两个或以上的cleaned_data的键值,需要返回cleaned_data
def clean(self):
pwd=self.cleaned_data.get('pwd')
confirm_pwd=self.cleaned_data.get('confirm_pwd')
if not confirm_pwd==pwd:
return self.add_error('confirm_pwd','两次密码必须一致')
return self.cleaned_data
def login(request):
form_obj=MyForm()
if request.method=='POST':
form_obj=MyForm(request.POST)
if form_obj.is_valid():
res=form_obj.cleaned_data
res.pop('confirm_pwd')
models.User.objects.create(**res)
return render(request,'login.html',locals())
ModelForm组件
通常在django项目中,我们编写的大部分都是与django的模型紧密联系的表单。举个例子,你也许会有一个Book模型,并且你还想创建一个form表单用来添加和编辑书籍信息到这个模型中。在这种情况下,在Form表单中定义字段将是冗余的,因为我们已经在模型中定义了那些字段。
基于这个原因,django提供一个辅助类来让我们可以从django的模型创建Form,这就是ModelForm。ModelForm是form的优化版本,使用更简单,功能更加强大。
代码演示
class MyModelForm(forms.ModelForm):
class Meta:
model=models.User
fields='__all__'
exclude=['age',]
labels={
'name':'姓名',
'pwd':'密码',
'email':'邮箱'
}
widgets={
'pwd':forms.widgets.TextInput(attrs={'class':'form-control'})
}
def login(request,user_id):
modelform_obj=MyModelForm()
if request.method=='POST':
edit_obj =models.User.objects.filter(pk=user_id).first() # 当主键为user_id时,save()方法就是修改
modelform_obj=MyModelForm(request.POST,instance=edit_obj) # instance有值就是修改
if modelform_obj.is_valid():
modelform_obj.save()
return render(request,'login.html',locals())
class Meta下常用参数:
model = models.Book # 对应的Model中的类
fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 帮助提示信息
widgets = None # 自定义插件
error_messages = None # 自定义错误信息
ModelForm的验证
与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_vaild()
或访问errors属性时隐式调用。
我们可以像使用Form类自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。
每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例。