第十一篇:Django之Forms组件

第十一篇:Django之Forms组件

一、Forms介绍

我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。

与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.。

Django form组件就实现了上面所述的功能。

为了引出form的知识点,我们先使用自己写一个注册功能,利用form表单提交数据,获取用户名和密码,在后端判断用户名和密码是否符合一定的条件。【用户名中不能含有"范德彪",密码不能少于三位】,那么我们该如何实现这样的要求呢?

代码如下:

"""后端"""
def ab_form(request):
    # 定义一个字典,用来返回提示信息,如果是get请求,没有,如果是post,添加数据
    back_dic = {}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 对拿到的数据进行判断
        if '范德彪' in username:
            back_dic['username_error'] = '范德彪太厉害,不能显示'
        if len(password) < 3:
            back_dic['password_error'] = '你输入的数据有点短'
    return render(request, 'ab_form.html', locals())


"""前端""" 
<form action="" method="post">
    <p>
        username:<input type="text" name="username">
{#        我们需要使用span标签,来进行提示信息的添加#}
        <span style="color: red">{{ back_dic.username_error }}</span>
    </p>
    <p>
        password:<input type="text" name="password">
        <span style="color: red">{{ back_dic.password_error }}</span>
    </p>
    <input type="submit" value="提交">
</form>
    
"""
    无论是post请求还是get请求
    页面都能够获取到字典,只不过get请求来的时候,字典值都是空的
    而post请求来之后,字典可能有值
"""

实现的效果如下。

我们在浏览器中输入数据,然后点击提交。

得到这样的结果。

实现了一个校验的过程,但是存在缺陷,我们点击提交之后,以前输入的用户名和密码不会进行保存【有点反人类】。

总共经历了三个过程。

1.手动书写前端获取用户数据的html代码     渲染html代码
2.后端对用户数据进行校验		         校验数据
3.对不符合要求的数据进行前端提示         展示提示信息

django提供的forms组件也是完成这三个过程。【同时也会保留用户上次输入的内容】

forms组件,能够完成的事情
	1.渲染html代码
	2.校验数据
	3.展示提示信息

同时,我们再补充一个小小的知识点,为什么数据校验非要去后端,不能在前端利用js直接完成呢?

我们在前面已经学习了前端的知识点,似乎是可以完成校验的,但是前端的校验是弱不禁风的,你可以直接修改前端代码,或者利用爬虫程序绕过前端页面直接朝后端提交数据。

比如一个购物网站,选取了货物之后,如果后端不做价格的校验,那前端就可以随便修改数据,后果惨不忍睹。

"""
实际是后端获取到用户选择的所有商品的主键值,
然后在后端查询出所有商品的价格,再次计算一遍,
如果跟前端一致,那么完成支付如果不一致直接拒绝。
"""

二、forms组件基本使用

我们在views.py中直接进行操作,也可以写到其他文件中,使用导入的方式。

先简单定义一个MyForm类,代码如下。

class MyForm(forms.Form):
    # username字符串类型最小3位,最大8位
    username = forms.CharField(min_length=3,max_length=8)
    # password字符串类型最小3位,最大8位
    password = forms.CharField(min_length=3,max_length=8)
    # email字段必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField()

三、forms校验数据

我们需要准备一个测试环境。

1、可以自己拷贝代码准备,test文件中
2、其实在pycharm里面已经帮你准备一个测试环境   >>> python console

测试过程如下。

from app01 import views

# 1 将带校验的数据组织成字典的形式传入即可
form_obj = views.MyForm({'username':'yangyi', 'password':'123','email':'123'})

# 2、我们直接打印产生的对象
form_obj
<MyForm bound=True, valid=True, fields=(username;password;email)>
"""
如果没有传参数
<MyForm bound=False, valid=Unknown, fields=(username;password;email)>
"""

# 3 判断数据是否合法,注意该方法只有在所有的数据全部合法的情况下才会返回True
form_obj.is_valid()
False

# 4 查看所有校验通过的数据
form_obj.cleaned_data
{'username': 'yangyi', 'password': '123'}

# 5 查看所有不符合校验规则以及不符合的原因【可以多个不符合原因,列表存储】
form_obj.errors
{'email': ['Enter a valid email address.']}

# 6 校验数据只校验类中出现的字段,多传不影响,多传的字段直接忽略
form_obj = views.MyForm({'username':'yangyi', 'password':'123','email':'123@163.com','hobby':'study'})
form_obj.is_valid()
True

# 7 校验数据,默认情况下,类里面所有的字段都必须传值
form_obj = views.MyForm({'username':'yangyi','password':'123'})
form_obj.is_valid()
False

"""
也就意味着校验数据的时候,也就意味着校验数据的时候【太合理了】
"""

# 8、我们对form对象进行循环
for i in form_obj:
    print(i)
<input type="text" name="username" value="yangyi" maxlength="8" minlength="3" required id="id_username" />
<input type="text" name="password" value="123" maxlength="8" minlength="3" required id="id_password" />
<input type="email" name="email" value="123@163.com" required id="id_email" />

# 9、我们继续进行测试
for i in form_obj:  # i属于django自带的类产生的对象
    print(type(i))

<class 'django.forms.boundfield.BoundField'>  
<class 'django.forms.boundfield.BoundField'>
<class 'django.forms.boundfield.BoundField'>

四、forms组件渲染标签

forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox),不能帮你渲染提交按钮。

"""后端"""
def index(request):
    # 1 先产生一个空对象
    form_obj = MyForm()
    return render(request, 'index.html', locals())

"""前端"""
<form action="" method="post">
	<p>第一种渲染方式:代码书写极少,封装程度太高 不便于后续的扩展 一般情况下只在本地测试使用</p>
    {{ form_obj.as_p }}  # 外面包p标签
    {{ form_obj.as_ul }}  # 外面包ul标签
    {{ form_obj.as_table }}  # 外面包table标签
    <input type="submit" value="提交">
</form>

# 可以理解为把form对象中的fields=(username;password;email)全部拿出来,加p标签修饰

"""
前端渲染代码:外面包p标签
<p><label for="id_username">Username:</label> <input type="text" name="username" maxlength="8" minlength="3" required="" id="id_username"></p>
"""

"""
前端渲染代码:外面包ul标签
<li><label for="id_username">Username:</label> <input type="text" name="username" maxlength="8" minlength="3" required="" id="id_username"></li>
"""

"""
<form action="" method="post" novalidate>  # 取消浏览器渲染

</form>
"""

<form action="" method="post" novalidate>
    <p>第二种渲染方式:可扩展性很强 但是需要书写的代码太多  一般情况下不用</p>
     # 很奇怪,在python console 中 form_obj.username 会显示:AttributeError: 'MyForm' object has no attribute 'username'
    <p>{{ form_obj.username.label }}:{{ form_obj.username }}</p>
    <p>{{ form_obj.password.label }}:{{ form_obj.password }}</p>
    <p>{{ form_obj.email.label }}:{{ form_obj.email }}</p>
</form>
<form action="" method="post" novalidate>
<p>第三种渲染方式(推荐使用):代码书写简单 并且扩展性也高</p>
    {% for form in form_obj %}
        <p>{{ form.label }}:{{ form }}</p>
{% endfor %}
</form>

"""
label属性默认展示的是类中定义的字段首字母大写的形式
也可以自己修改,直接给字段对象加label属性即可
username = forms.CharField(min_length=3,max_length=8,label='用户名')
"""

五、forms组件展示提示信息

浏览器会自动帮你校验数据,如果不添加novalidate,则看不到效果。

但是前端的校验弱不禁风,如此可以让浏览器不做校验显示。

<form action="" method="post" novalidate>
"""后端"""
def index(request):
    # 1 先产生一个空对象
    form_obj = MyForm()
    if request.method == 'POST':
        # 获取用户数据并且校验
        """
        1.数据获取繁琐
        2.校验数据需要构造成字典的格式传入才行
        ps:但是request.POST可以看成就是一个字典
        """
        # 3.校验数据
        form_obj = MyForm(request.POST)
        # 4.判断数据是否合法
        if form_obj.is_valid():
            # 5.如果合法 操作数据库存储数据
            return HttpResponse('OK')
        # 5.不合法 有错误
    # 2 直接将该空对象传递给html页面
    return render(request,'index.html',locals())


"""前端"""
{% for form in form_obj %}
        <p>
    		# form.label === form.username.label
            {{ form.label }}:{{ form }}  # form其实就是一个个标签对象
            # 注意这里并不是 form_obj.errors
            <span style="color: red">{{ form.errors.0 }}</span>
        </p>
{% endfor %}


"""
如何实现刷新之后,数据不丢失呢?
1.必备的条件,get请求和post传给html页面对象变量名必须一样
2.forms组件当你的数据不合法的情况下,会保存你上次的数据,让你基于之前的结果进行修改,更加的人性化。
"""

当然,对错误的信息可以进行自己定制。代码如下。

# 针对错误的提示信息还可以自己自定制
class MyForm(forms.Form):
    username = forms.CharField(
            min_length=3,
            max_length=8,
            label='用户名',
            error_messages={
               'min_length': '用户名最少3位',
               'max_length': '用户名最大8位',
               'required': "用户名不能为空"
            }
     )
    # password字符串类型最小3位最大8位
    password = forms.CharField(
            min_length=3,
            max_length=8,
            label='密码',
            error_messages={
               'min_length': '密码最少3位',
               'max_length': '密码最大8位',
               'required': "密码不能为空"
            }
     )
    # email字段必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField(
            label='邮箱',
            error_messages={
             'invalid': '邮箱格式不正确',  # 邮箱的专属
             'required': "邮箱不能为空"
            }
     )
    
""""""

如此可以实现如下效果。

六、forms组件钩子函数

在特定的节点自动触发完成响应操作,钩子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则。

在forms组件中有两类钩子
1.局部钩子
	当你需要给单个字段增加校验规则的时候可以使用
# 实际应用:校验用户名中不能含有666,只是校验username字段,局部钩子
    
2.全局钩子
  	当你需要给多个字段增加校验规则的时候可以使用
# 校验密码和确认密码是否一致,password confirm两个字段,全局钩子

那么我们该如何使用钩子函数呢?钩子函数,在类里面书写方法即可。

# 局部钩子
def clean_username(self):
    # 获取到用户名
    username = self.cleaned_data.get('username')
    if '666' in username:
        # 提示前端展示错误信息
        self.add_error('username','666也没用')
    # 将钩子函数钩去出来数据再放回去
    return username

# 全局钩子
def clean(self):
    password = self.cleaned_data.get('password')
    confirm_password = self.cleaned_data.get('confirm_password')
    if not confirm_password == password:
        self.add_error('confirm_password','两次密码不一致')
    # 将钩子函数钩出来数据再放回去
    return self.cleaned_data

实际效果如下。

七、forms组件其他参数及补充知识点

label            -----    字段名
error_messages   -----    自定义报错信息[initial='yangyi']
initial          -----    默认值
required         -----    控制字段是否必填[required=False]
min_length       -----    最小字段
max_length       -----    最长字段
validator        -----    正则
widget           -----    给标签添加一定的属性

"""
针对字段的校验有很多种:
	1、最简单的max_length和min_length
	2、正则validator
	3、钩子函数
"""

直到现在,仍然存在几个问题?

1.字段没有样式
2.针对不同类型的input如何修改
	text
    password
    date
    radio
    checkbox
    ...
  • text
username = forms.CharField(
    min_length=3,
    max_length=8,
    label='用户名',
    # initial='yangyi',
    # required=False,
    error_messages={
        'min_length': '用户名最少3位',
        'max_length': '用户名最大8位',
        'required': "用户名不能为空"
    },
    widget=forms.widgets.TextInput(
        attrs={
            # 可以添加固有属性,也可以添加自定义属性 多个属性值的话,直接空格隔开即可
            'class': 'form-control c1 c2'
        }
    )
)
  • password
# password字符串类型最小3位最大8位
password = forms.CharField(
    min_length=3,
    max_length=8,
    label='密码',
    error_messages={
        'min_length': '密码最少3位',
        'max_length': '密码最大8位',
        'required': "密码不能为空"
    },
    widget=forms.widgets.PasswordInput(
        attrs={
            'class': 'form-control'
        }
    )
)

from django.core.validators import RegexValidator

# 确认密码
confirm_password = forms.CharField(
    min_length=3,
    max_length=8,
    label='密码',
    error_messages={
        'min_length': '密码最少3位',
        'max_length': '密码最大8位',
        'required': "密码不能为空"
    },
    widget=forms.widgets.PasswordInput(
        attrs={
            'class': 'form-control c1 c2'
        }
    ),
    # 第一道关卡里面还支持正则校验
    validators=[
        RegexValidator(r'^[0-9]+$', '请输入数字'),
        RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
    ]
)
  • email
# email字段必须符合邮箱格式  xxx@xx.com
email = forms.EmailField(
    label='邮箱',
    error_messages={
        'invalid': '邮箱格式不正确',  # 邮箱的专属
        'required': "邮箱不能为空"
    },
    widget=forms.widgets.EmailInput(
        attrs={
            'class': 'form-control'
        }
    )
)
  • radio
gender = forms.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
)
  • 单选select
hobby = forms.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )
  • 多选select
hobby1 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )
  • 单选checkbox
keep = forms.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )
  • 多选checkbox
 hobby2 = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

最终显示效果如下。

八、forms组件源码解析【了解】

我们简单研究一下form组建的源码,form源码的研究切入点为form_obj.is_valid(),我们点开源码进行查看。

我们先点击self.is_bound进行查看。

我们再点开self.errors.

到了这里,我们继续观察full_clean函数

我们打开_clean_fields()

"""钩子函数提示前端展示错误信息"""
# 第一种方式
self.add_error('username','666也没用')

# 第二种方式【了解】
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
raise ValidationError('666也没用')

接着打开_clean_form()

_post_clean()是一个内部钩子,我们没有办法使用,只能使用前两个。

posted @   YangYi215  阅读(530)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示