form组件

form组件

在我们日常填写注册信息或者报名信息时,所访问的网页,在输入信息有错误时就显示一些后端传入的提示。

image

我们当然可以通过ajax绑定事件来从后端取一些提示,但是表单是个整体,如果每个输入框都需要我们完整的写一轮这么一套逻辑,这就太麻烦了,所以针对表单标签,django提供了form组件,帮助我们来实现以下一些功能:

  1. 自动校验数据
  2. 自动生成标签
  3. 自动展示信息

form组件基础功能

创建form组件

form组件的创建十分像创建模型表,我们需要先从django导入forms模块。

from django import forms

class Register(forms.Form):  # 继承Form类
    username = forms.CharField(max_length=8, min_length=2)  # 限定字符,限定最大最小长度
    age = forms.IntegerField(min_value=0, max_value=200)  # 限定整型,限定了最大最小值
    email = forms.EmailField()  # 必须符合邮箱形式

此类,继承了Form类,定义了三个数据对象,可以分别对三个键值对做自动校验、生成标签、展示信息的操作。

自动校验数据

通过这个表单类直接填入数据,可以产生一个表单对象,这样产生的对象,可以通过以下方法,验证传入数据是否符合表单的限制条件。

register_obj = Register({
            "username": "leethon",  # 传入字典,键对应register中定义的数据对象名username等
            "age": 201,   # 值就是最后用户要输入的值,这里简单的填充数据模拟一下
            "email": '123@qq.com'
        })

# 首先:判断这个对象中的所有数据是否有效
register_obj.is_valid()  # 这里因为有数据无效,如201超出了 age数据的最大限制,所以整体是不合法的
"""所有数据合法有效会返回True,有数据不合法则返回False"""
# 其次可以拿到清洗过的合法数据,和不合法数据的提示信息
register_obj.cleaned_data  # {'username': 'leethon', 'email': '123@qq.com'}
"""只展示合法数据,age:201就被筛选掉了"""
register_obj.errors  # <ul class="errorlist"><li>age<ul class="errorlist"><li>Ensure this value is less than or equal to 200.</li></ul></li></ul>
"""错误信息是标签式的,方便我们用模板语法返回到前端"""

ps:is_valid()方法并不是简单的对传入数据进行判断,还做了很多其他的操作,比如cleaned_data属性就是执行完is_valid()后产生的。这个可以对is_valid源码进行查看,它内部一定会执行full_clean函数,其中就会设置一个cleaned_data属性并按一些逻辑进行填充

自动生成标签

表单类不填入数据,直接产生对象,将这个对象传入render函数,可以使用模板语法渲染一些表单标签。(register_obj = Register()|render(request, 'test.html', register_obj))

  1. 高度自动化创建:

    <form action="" method="post">
        {{ register_obj.as_p }}
        <input type="submit">
    </form>
    

    上述代码中,只需要一句,就产生了三行输入框,查看源码,可以看出模板语法将三个数据表单处理成了三个p标签包裹的input等标签,并赋予了很多限制属性。

    image

    可以高度自动化创建的还有:

    {{ register_obj.as_ul }}   {# 以无序列表组织 #}
    {{ register_obj.as_table }}   {# 以顺序排开 #}
    

    这样创建的表单标签主要用于本地测试,因为扩展性很差。

  2. 手动diy创建

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

    上述代码中,是将register_obj中的每个数据对象单独取出来,将label标签和输入框分别排列,我们可以对排版做一些改动。

光是对排版的调整显然是不够的,我们还可以对标签添加一些属性,这个属性可以在创建Form子类时,对数据对象添加widget属性:

username = forms.CharField(max_length=8, min_length=2,
                      widget=forms.widgets.TextInput(attrs={'class': 'form-control', 'username': '自定义属性'})
                          )  # 字符字段,限定最大最小长度

这样,我们username的input框就拥有了一些样式属性和自定义属性。

image

自动展示信息

我们不仅可以通过以上方法生成label和input标签与用户交互,还可以对用户输入的数据清洗后的错误的信息渲染到页面,上文提到错误信息是标签式的,我们可以直接用到页面上:

<form action="" method="post" novalidate>
    {% for regi in register_obj %}
    {{ regi.label }}
    {{ regi }}
    {{ regi.errors }}
    {% endfor %}
    <input type="submit">
</form>

image

我们也可以通过索引的方式,只取出errors的文本部分:

  • {{ regi.errors.0 }}只取出错误信息的文本

表单生成——校验——返回信息的简单逻辑实现

views层:

class Register(forms.Form):
    username = forms.CharField(max_length=8, min_length=2 )  # 字符字段,限定最大最小长度
    age = forms.IntegerField(min_value=0, max_value=200 )  # 整型字段,限定了最大最小值
    email = forms.EmailField()  # 必须符合邮箱形式

def register(request):
    register_obj = Register()  # get请求就产生空对象用于自动生成标签
    if request.method == "POST":
        register_obj = Register(request.POST)  # POST数据可以直接传入用于初始化表单对象
        if register_obj.is_valid():   # 清洗数据并做合法判断
            return HttpResponse('注册成功')  # 如果所有输入都合法就录入数据
        # 如果有错误信息就走原本的界面,但是register对象有很多新属性,包括错误信息
    return render(request, "register.html", locals())

html模板:

<form action="" method="post" novalidate>
    {% for regi in register_obj %}
        {{ regi.label }}
        {{ regi }}
        {{ regi.errors.0 }}
    {% endfor %}
    <input type="submit">
</form>

一些细节汇总:

  • request.POST也是个大字典,可以直接填入表单类做初始化,不必我们费力的取出所有表单数据了
    而且,如果有多余的数据也不会报错,只是不会添加到对象的数据属性中。
  • 对输入框的限制,前端标签是没有强大约束力的(因为网页html源码可以被用户修改),我们主要是通过后端返回,可以通过form标签novalidate属性来禁用前端的约束。
  • 第一次通过get请求访问,第二次点击提交访问是通过post访问,而两种请求最终返回的界面结构是一样的,所以两次产生的Form子对象的变量名要一致,方便模板语法按照同一种方式处理。

image

  • 虽然是同一个模板,但是由于两次引用的register_obj不一样(第二次包含用户输入和错误信息),所以展示的界面也不一样。

form组件功能补充

校验方式补充

我们可以通过三种方式对数据进行校验,三种方式的校验可以同时使用。

  1. 数据字段自带参数
    可以直接填写一些属性进行限制,这是form组件预设好的,上文就是用的这种方式

    username = forms.CharField(max_length=8, min_length=2)  # 限定字符,限定最大最小长度
    

    如max_length\min_length等属性就是CharField类本身带有的一些数据的限制,后续可以基于这些属性就是对数据进行校验。

  2. 正则表达式匹配

    from django.core.validators import RegexValidator   # 导入正则校验类
    
    class MyForm(forms.Form):
        tel = forms.CharField(
            validators=[
                RegexValidator(r"[0-9]", "电话号码必须是数字"),  # 第一个参数正则,第二个参数是提示信息
                RegexValidator(r"^[0-9]{3}-[0-9]{4}|[0-9]{8}-[0-9]{7}", "不是国内号码")  # 可以写多个正则判别
            ]
    
  3. 钩子函数

    钩子函数是基于前两步校验完后得到的cleaned_data数据做校验的。

    钩子函数需要给一个返回值,这个返回值会作为最终值处理到cleaned_data中去。

    可以通过自定义钩子函数校验数据,进行清洗,钩子函数需要定义在表单类中,命名也需要遵照一定的规则。这让我们可以自己编写一些校验逻辑。

    局部钩子

    每次只能校验一个数据。

    # 校验用户名是否存在
    def clean_username(self):  # 按照clean_ 加字段名的方式进行清洗
        username = self.cleaned_data.get('age')  # 校验过的age进行拿取
        if username in user_list:  # user_list是假想的数据库
            self.add_error('username', "用户名已存在")  # 朝字段添加错误信息
        return username  # 将原数据返回,也可以进行修改
    

    全局钩子

    全局钩子每次可以校验多个数据

    # 校验两次输入密码是否一致
    def clean(self):  # clean是全局钩子
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if not password == confirm_password:
            self.add_error('confirm_password',"两次密码不一致")
        return self.cleaned_data  # 底层这个self是它给的,我们只需要通过这个形式传回去即可保持不变
    

这个我们可以通过查看源码得知钩子函数的逻辑:

在is_valid()中,一定会运行full_clean()函数,其中有一步为self._clean_fields()

image

form组件参数小结

字段参数 含义 补充
min_length 字符最小长度
max_length 字符最大长度
min_value 数字最小值
max_value 数字最大值
label 字段标签注释 模板语法 {{ 表单字段对象.label }}
error_messages 错误提示 可以自定义限制条件的报错信息
validators 正则校验
initial 默认值 字段没有传值则标签value属性等于这个值
required 必填项 默认为True,设置为False选填
widget 属性设置 需要确定标签类型,在forms.widgets有封装

error_messages

username = forms.CharField(max_length=8, min_length=2,
                               error_messages={
                                   'max_length': "啊 长了",   # 针对某些限制条件,来自定义错误信息
                                   'min_length': "啊 短了",
                               }
                          )

initial

字段没有传值则标签value属性等于这个值,即输入框中的默认值

class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三"  # 设置默认值
    )
    pwd = forms.CharField(min_length=6, label="密码")

widget

username = forms.CharField(max_length=8, min_length=2,
                      widget=forms.widgets.TextInput(attrs={'class': 'form-control', 'username': '自定义属性'})
                          )  # 字符字段,限定最大最小长度
  • 上述代码中widget属性可以先确定标签的类型,比如说这里TextInput对应text类型的input标签

  • 然后可以在括号中添加一些参数,其中attr参数可以往标签中添加一些属性

  • 常见的widgets中封装的标签类型有

    关键字 对应标签含义
    PasswordInput() 密码输入框password
    RadioSelect() 单选框radio
    Select() 单选下拉框select
    SelectMultiple() 多选下拉框select
    CheckboxInput() 单选checkbox
    CheckboxSelectMultiple() 多选checkbox

modelform组件

modelform组件省去我们编写form类的字段,直接让表单参照modelform自动生成表单字段。

from django import forms   # 导入ModelForm
from app01 import models  # 将app的模型类导入


class BookForm(forms.ModelForm):

    class Meta:
        model = models.Book
        fields = "__all__"
        labels = {
            "title": "书名",
            "price": "价格"
        }
        widgets = {
            "password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
        }
  • 至少需要在Meta类中设置model和fields来确认模型表及其字段
  • label、widget等form字段中的参数,都可以在这里Meta类中直接通过加s的方式来统一用大字典来设置。

Meta属性

model = models.Book  # 对应的Model中的类
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 帮助提示信息
widgets = None  # 自定义插件
error_messages = None  # 自定义错误信息

ModelForm的验证

与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid() 或访问errors 属性时隐式调用。

我们可以像使用Form类一样自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。

如果我们不重写具体字段并设置validators属性的化,ModelForm是按照模型中字段的validators来校验的。

save()方法

每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例:

>>> from myapp.forms import BookForm

# 根据POST数据创建一个新的form对象
>>> form_obj = BookForm(request.POST)

# 保存书籍数据
>>> form_obj.save()

# 基于一个书籍对象创建form对象
>>> edit_obj = Book.objects.get(id=1)
# 使用POST提交的数据更新书籍对象
>>> form_obj = BookForm(request.POST, instance=edit_obj)
>>> form_obj.save()
posted @ 2022-12-21 20:32  leethon  阅读(64)  评论(0编辑  收藏  举报