批量操作数据、自定义分页器、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表单)

  1. 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})
  1. 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>
  1. 效果展示:点击后验证的信息都会发送到html页面的span标签文本内

image-20220908174120078

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()                                  # 邮箱必须符合邮箱格式(至少有@符)

数据校验功能

  1. 传递待校验的数据
form_obj=views.MyForm({'name':'jason','pwd':12345,'email':'123'})
  1. 判断所有的数据是否符合校验
form_obj.is_vaild()
  1. 获取符合校验规则的数据
form_obj.cleaned_data
  1. 查阅不符合校验规则的数据及错误原因
form_obj.errors

image-20220908182202433

注意:

  • form类中编写的字段默认都是必填的,少传则肯定通不过校验:is_valid
  • 校验如果多传了一些字段,则不参与校验,全程忽略

渲染标签功能

image-20220908183958507

当我们创建一个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>

image-20220908183231685

但是这种方式封装程度太高了,也无法使用单独的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>

image-20220908183603373

第二种封装程度低,扩展性好,但是当字段过多的时候,代码就过于冗余。

方式3:推荐使用

<form action="" method="post">
    {% for form in form_obj %}
        {{ form.label }}
        {{ form }}
    {% endfor %}
    <p><input type="submit"></p>
</form>

image-20220908185408819

如果某个标签不需要,在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>

image-20220908195237162

重要的字段参数与插件

创建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() 将创建模型的一个新实例。

posted @ 2022-09-08 22:33  荀飞  阅读(73)  评论(0编辑  收藏  举报