【12.0】Django框架之form组件

发布时间 2023-07-17 17:35:50作者: Chimengmeng

【一】需求

  • 写一个注册功能
    • 获取用户名和密码,利用form表单提交数据
    • 在后端判断用户名和密码是否符合一定的条件
      • 用户名中不能包含啦啦啦
      • 密码不能少于三位
    • 如果符合条件需要你将提示信息展示到前端页面

【二】form表单实现

【1.0】点击提交按钮返回比对信息

  • 前端页面
<form action="" method="post">
    <p>username:
        <input type="text" class="form-control" name="username">
        <span style="color: red">{{ back_dict.username }}</span>
    </p>
    <p>password:
        <input type="password" class="form-control" name="password">
        <span style="color: red">{{ back_dict.password }}</span>

    </p>
    <input type="submit" class="btn btn-danger">
</form>
  • 后端
from django.shortcuts import render


# Create your views here.
def ab_form(request):
    back_dict = {}
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if "啦啦啦" in username:
            back_dict['username'] = '用户名不符合格式!'
        if len(password) < 3:
            back_dict['password'] = '输入的密码长度不够!'

        '''
        无论是 get 请求 还是 post 请求,页面都能获取到字典
            get 请求时,字典是空的,没有值
            post 请求时 ,字典可能是非空的
        '''
    return render(request, 'ab_form.html',locals())
  • 路由
from django.contrib import admin
from django.urls import path, re_path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # form 组件引入
    re_path(r'^ab_form/',views.ab_form),
]
  • 用到的技术点
    • 手动书写前端HTML页面代码
      • 渲染HTML页面
    • 后端对用户数据进行校验
      • 校验数据
    • 对不符合要求的数据进行前端提示
      • 展示提示信息

【2.0】form组件

  • form组件可以帮我们完成

    • 渲染HTML页面
    • 校验数据
    • 展示提示信息
  • 为什么数据校验要在后端执行,而不是前端在JS完成?

    • 数据校验前端可有可无
    • 但是后端必须有!
  • 原因

    • 前端的校验存在实时被修改的风险,可以在前端直接修改输入的数据
    • 利用爬虫程序可以绕过前端页面直接向后端提交数据
  • 例如购物网站

    • 在前端计算完总价后,直接提交给后端
    • 后端如果不做校验就会产生极大的风险安全问题
    • 正确的做法是
      • 在后端查出所有商品的必要信息,再次计算一遍
      • 前段与后端都要进行校验,保证数据的安全性

【三】forms组件如何校验数据

【1】基本使用

# 导入模块
from django import forms


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

【2】校验数据

  • 测试环境可以拷贝代码准备
  • 其实在pycharm已经为我们提供了测试环境
    • 底下的Python Console

(准备数据)

# 导入模块
from app01 import views

# 将想要传入的数据组织成字典的格式传入即可
form_obj = views.MyForm({"username":"dream","password":"521","eemail":"123"})

(1)判断传入的数据是否合法

form_obj.is_valid()
# False
  • 该方法,只有所有传入的数据都合法的情况下才会返回True

(2)查看那些数据是合法的

form_obj.changed_data
# ['username', 'password']
  • 展示符合校验规则的数据

(3)查看不合法的数据格式

form_obj.errors

# {'username': ['Ensure this value has at most 2 characters (it has 5).'], 'password': ['Ensure this value has at most 2 characters (it has 3).'], 'email': ['This field is required.']}
  • 查看所有不符合规则的参数及不符合的原因
  • 报错原因可能有多个

(4)多传参数是否会报错

form_obj = views.MyForm({"username":"dream","password":"521","eemail":"123@qq.com","hobby":"music"})

form_obj.is_valid()
# True
  • 校验数据只校验类中出现的字段
    • 如果有多传的字段会直接自动忽略

(4)少传参数是否会报错

form_obj = views.MyForm({"username":"dream","password":"521"})

form_obj.errors
# {'email': ['This field is required.']}
  • 校验数据
    • 默认情况下,类中所有的字段都必须传值

(5)小结

  • 默认情况下
    • 校验数据可以多传
    • 但是不能少传

【四】forms组件渲染HTML代码

  • forms组件只会帮我们渲染用户输入的标签(input/select...)

【1】渲染方式一

  • 路由
# forms 组件渲染html标签
re_path(r'^index/',views.index),
  • 后端代码
from django import forms


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


def index(request):
    # (1)产生一个空对象
    form_obj = MyForm()
    # (2) 直接将空对象传递给前端页面
    return render(request,'index.html',locals())
  • 前端页面
<form action="" method="post">
    <p>第一种渲染方式</p>
    {{ form_obj }}
</form>
  • 渲染出的效果是
<form action="" method="post">
    <p>第一种渲染方式</p>
    <label for="id_username">Username:</label><input type="text" name="username" maxlength="8" minlength="3"
                                                     required="" id="id_username">
    <label for="id_password">Password:</label><input type="text" name="password" maxlength="8" minlength="3"
                                                     required="" id="id_password">
    <label for="id_email">Email:</label><input type="email" name="email" required="" id="id_email">
</form>

特点是

  • 代码书写极少,封装程度太高,不便于后期拓展
  • 一般情况下用于本地测试使用

【2】渲染方式二

<form action="" method="post">
    <p>第二种渲染方式</p>
    {{ form_obj.as_ul }}
</form>
  • 渲染到页面的效果是
<form action="" method="post">
    <p>第二种渲染方式</p>
    <li><label for="id_username">Username:</label> <input type="text" name="username" maxlength="8" minlength="3"
                                                          required="" id="id_username"></li>
    <li><label for="id_password">Password:</label> <input type="text" name="password" maxlength="8" minlength="3"
                                                          required="" id="id_password"></li>
    <li><label for="id_email">Email:</label> <input type="email" name="email" required="" id="id_email"></li>
</form>
  • 该方法可以修改后端传入的属性值
# 定义form类
class MyForm(forms.Form):
    # username : 字符串类型  最小三位,最大八位
    username = forms.CharField(max_length=8, min_length=3, label="用户名")
    # # username : 字符串类型  最小三位,最大八位 : 字符串类型  最小三位,最大八位
    password = forms.CharField(max_length=8, min_length=3)
    # email : 必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField()


def index(request):
    # (1)产生一个空对象
    form_obj = MyForm()
    # (2) 直接将空对象传递给前端页面
    return render(request, 'index.html', locals())
  • 前端
<form action="" method="post">
    <p>第二种渲染方式</p>
    {#  对象点属性取到对应的属性值  #}
    {{ form_obj.username.label }} {{ form_obj.username }}

</form>
  • 前端页面展示效果
<form action="" method="post">


    <p>第二种渲染方式</p>


    用户名 <input type="text" name="username" maxlength="8" minlength="3" required="" id="id_username">

</form>
  • 特点
    • 可扩展性非常强
    • 但是需要书写复杂的代码
    • 一般情况下不用

【3】渲染方式三(推荐使用)

  • 前端
<form action="" method="post">

    <p>推介使用第三种</p>
    {% for form in form_obj %}
        <p> {{ form.label }} : {{ form }}</p>
    {% endfor %}

</form>
  • 页面展示
<form action="" method="post">


    <p>推介使用第三种</p>

    <p> 用户名 : <input type="text" name="username" maxlength="8" minlength="3" required="" id="id_username"></p>

    <p> Password : <input type="text" name="password" maxlength="8" minlength="3" required="" id="id_password"></p>

    <p> Email : <input type="email" name="email" required="" id="id_email"></p>


</form>
  • 优点
    • 代码书写简单,扩展性很高

【4】总结

  • 先在后端生成空对象

  • 再在前端拿到想要的属性

  • label属性默认展示的是类中定义的字段首字母大写的形式

  • 支持自己修改字段注释

    • 直接在字典后面加 label 属性赋值即可
    username = forms.CharField(max_length=8, min_length=3, label="用户名")
    
  • 不会渲染提交按钮

【五】forms组件渲染错误信息

【1.0】LowBe版

  • 后端
# 定义form类
class MyForm(forms.Form):
    # username : 字符串类型  最小三位,最大八位
    username = forms.CharField(max_length=8, min_length=3, label="用户名")
    # # username : 字符串类型  最小三位,最大八位 : 字符串类型  最小三位,最大八位
    password = forms.CharField(max_length=8, min_length=3, label="密码")
    # email : 必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField(label="邮箱")


def index(request):
    # (1)产生一个空对象
    form_obj = MyForm()
    # (2) 直接将空对象传递给前端页面

    # (3) 获取前端输入的信息,校验并返回结果
    if request.method == "POST":
        # 获取前端输入的信息
        '''
        数据获取繁琐
        校验数据需要构造成字典格式传回
        ps:request.POST 可以看成是字典数据的格式
        '''
        # 校验并返回结果
        form_obj = MyForm(request.POST)
        # 判断数据是否合法
        if form_obj.is_valid():
            # 合法操作数据库存储数据
            return HttpResponse("OK")
        else:
            # 数据不合法 -- 展示错误信息给前端
            pass

    return render(request, 'index.html', locals())
  • 前端
<form action="" method="post">

    <p>推介使用第三种</p>
    {% for form in form_obj %}
        <p> {{ form.label }} : {{ form }}</p>
        <span style="color: red">{{ form_obj.errors }}</span>
    {% endfor %}

    <input type="submit" class="btn btn-danger">
</form>
  • 前端展示
<form action="" method="post">


    <p>推介使用第三种</p>

    <p> 用户名 : <input type="text" name="username" maxlength="8" minlength="3" required="" id="id_username"></p>
    <span style="color: red"></span>

    <p> 密码 : <input type="text" name="password" maxlength="8" minlength="3" required="" id="id_password"></p>
    <span style="color: red"></span>

    <p> 邮箱 : <input type="email" name="email" required="" id="id_email"></p>
    <span style="color: red"></span>


    <input type="submit" class="btn btn-danger">
</form>

【2.0】升级版

  • 浏览器自动帮我们做数据校验,但是前端的校验不安全
  • 如何禁止浏览器做校验?
    • 在form表单中加入参数即可
<form action="" method="post" novalidate>
  • 前端
<form action="" method="post" novalidate>

    <p>推介使用第三种</p>
    {% for form in form_obj %}
        <p> {{ form.label }} : {{ form }}</p>
        <span style="color: red">{{ form_obj.errors.0 }}</span>
    {% endfor %}

    <input type="submit" class="btn btn-danger">
</form>
  • 页面展示
<form action="" method="post" novalidate="">


    <p>推介使用第三种</p>

    <p> 用户名 : <input type="text" name="username" maxlength="8" minlength="3" required="" id="id_username"></p>
    <span style="color: red"></span>

    <p> 密码 : <input type="text" name="password" maxlength="8" minlength="3" required="" id="id_password"></p>
    <span style="color: red"></span>

    <p> 邮箱 : <input type="email" name="email" required="" id="id_email"></p>
    <span style="color: red"></span>


    <input type="submit" class="btn btn-danger">
</form>
  • 上面的页面提示信息是英文的,如何展示中文?
    • 添加字段信息
# 定义form类
class MyForm(forms.Form):
    # username : 字符串类型  最小三位,最大八位
    username = forms.CharField(max_length=8, min_length=3, label="用户名",
                               error_messages={
                                   "max_length": "最大八位",
                                   "min_length": "最小三位",
                                   "required": "必填字段",
                               })
    # # username : 字符串类型  最小三位,最大八位 : 字符串类型  最小三位,最大八位
    password = forms.CharField(max_length=8, min_length=3, label="密码",
                               error_messages={
                                   "max_length": "最大八位",
                                   "min_length": "最小三位",
                                   "required": "必填字段",
                               })
    # email : 必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField(label="邮箱",
                             error_messages={
                                 "invalid": "格式不正确",
                                 "required": "必填字段",
                             })

【注意事项】

  • 必要条件

    • get 请求 和 post 传给页面对象变量名必须一样
    def index(request):
        # (1)产生一个空对象
        form_obj = MyForm() # ******这里的变量名
        # (2) 直接将空对象传递给前端页面
    
        # (3) 获取前端输入的信息,校验并返回结果
        if request.method == "POST":
            # 获取前端输入的信息
            '''
            数据获取繁琐
            校验数据需要构造成字典格式传回
            ps:request.POST 可以看成是字典数据的格式
            '''
            # 校验并返回结果
            form_obj = MyForm(request.POST) # ******这里的变量名
            # 判断数据是否合法
            if form_obj.is_valid():
                # 合法操作数据库存储数据
                return HttpResponse("OK")
            else:
                # 数据不合法 -- 展示错误信息给前端
                pass
    
        return render(request, 'index.html', locals())
    
  • forms 组件

    • 当你的数据不合法的情况下,会记录上一次的数据,基于上一次的数据再编辑

【六】钩子函数(Hook)

【1】什么是钩子函数

  • 在forms组件中
    • 钩子函数(Hooks)是用来在特定事件发生时执行自定义逻辑的函数。
    • 它们提供了一种创建交互性和动态行为的方式,并可以用于处理表单的各种状态和数据。

下面是一些常见的forms组件中使用的钩子函数:

  1. onInputChange: 当输入框的值发生变化时触发。你可以通过这个钩子函数获取最新的输入值,并进行相应的处理。

  2. onSubmit: 当表单提交时触发。你可以在这个钩子函数中获取表单中的所有字段值,并进行数据验证、提交或其他操作。

  3. onBlur: 当输入框失去焦点时触发。你可以在这个钩子函数中执行验证操作,例如检查输入是否符合预期的格式或是否满足某些条件。

  4. onFocus: 当输入框获得焦点时触发。你可以在这个钩子函数中执行一些针对输入框焦点状态的逻辑操作,例如显示一个下拉列表或提示信息。

  5. onReset: 当表单重置时触发。你可以在这个钩子函数中对表单进行一些初始化操作,将表单恢复到初始状态。

  • 除了上述常见的钩子函数外,不同的forms组件可能还有其它特定的钩子函数,用于处理更具体的需求。
  • 在使用特定的forms组件之前,建议查阅相应的文档或官方手册,以了解可用的钩子函数及其使用方式。
  • 在特定的节点自动触发完成响应动作
    • 钩子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则
  • 在forms组件中有两类钩子
    • 局部钩子
      • 当需要给某个字段增加校验规则的时候使用
      • 在自定义的 forms 类中添加类方法即可
    • 全局钩子
      • 当需要给多个字段增加校验规则的时候使用
      • 在自定义的 forms 类中添加类方法即可

【2】案例

(1)校验用户名中不能含有 666

只需要校验 username 字段 --- 局部钩子

# 定义form类
class MyForm(forms.Form):
    # username : 字符串类型  最小三位,最大八位
    username = forms.CharField(max_length=8, min_length=3, label="用户名",
                               error_messages={
                                   "max_length": "最大八位",
                                   "min_length": "最小三位",
                                   "required": "必填字段",
                               })
    # # password : 字符串类型  最小三位,最大八位 : 字符串类型  最小三位,最大八位
    password = forms.CharField(max_length=8, min_length=3, label="密码",
                               error_messages={
                                   "max_length": "最大八位",
                                   "min_length": "最小三位",
                                   "required": "必填字段",
                               })
    # confirm_password : 字符串类型  最小三位,最大八位 : 字符串类型  最小三位,最大八位
    confirm_password = forms.CharField(max_length=8, min_length=3, label="确认密码",
                                       error_messages={
                                           "max_length": "最大八位",
                                           "min_length": "最小三位",
                                           "required": "必填字段",
                                       })
    # email : 必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField(label="邮箱",
                             error_messages={
                                 "invalid": "格式不正确",
                                 "required": "必填字段",
                             })

    # 钩子函数
    # 局部钩子
    def clean_username(self):
        # 获取到用户名
        username = self.cleaned_data.get("username")
        if "666" in username:
            # 提示给前端错误信息
            self.add_error("username", "用户名不能包含敏感词")
        # 将钩子勾出来的数据再放回到原来的逻辑中
        return username

(2)校验密码和确认密码是否一致

需要校验 password 和 confirm_password 两个字段 --- 全局钩子

# 定义form类
class MyForm(forms.Form):
    # username : 字符串类型  最小三位,最大八位
    username = forms.CharField(max_length=8, min_length=3, label="用户名",
                               error_messages={
                                   "max_length": "最大八位",
                                   "min_length": "最小三位",
                                   "required": "必填字段",
                               })
    # # password : 字符串类型  最小三位,最大八位 : 字符串类型  最小三位,最大八位
    password = forms.CharField(max_length=8, min_length=3, label="密码",
                               error_messages={
                                   "max_length": "最大八位",
                                   "min_length": "最小三位",
                                   "required": "必填字段",
                               })
    # confirm_password : 字符串类型  最小三位,最大八位 : 字符串类型  最小三位,最大八位
    confirm_password = forms.CharField(max_length=8, min_length=3, label="确认密码",
                                       error_messages={
                                           "max_length": "最大八位",
                                           "min_length": "最小三位",
                                           "required": "必填字段",
                                       })
    # email : 必须符合邮箱格式  xxx@xx.com
    email = forms.EmailField(label="邮箱",
                             error_messages={
                                 "invalid": "格式不正确",
                                 "required": "必填字段",
                             })

    # 钩子函数
    # 局部钩子
    def clean_username(self):
        # 获取到用户名
        username = self.cleaned_data.get("username")
        if "666" in username:
            # 提示给前端错误信息
            self.add_error("username", "用户名不能包含敏感词")
        # 将钩子勾出来的数据再放回到原来的逻辑中
        return username

    # 全局钩子
    def clean(self):
        # 获取到需要校验的数据
        password = self.cleaned_data.get("password")
        confirm_password = self.cleaned_data.get("confirm_password")
        # 校验参数
        if password == confirm_password:
            # 提示前端报错信息
            self.add_error("confirm_password", "两次密码不一致")
        # 将钩子勾出来的数据再放回到原来的逻辑中 --- 全部数据都被勾出来了
        return self.cleaned_data

【七】forms组件其他参数及补充

【1】基本参数

【1】autocomplete

设置是否启用自动填充功能,可以使用以下值:

  • on:浏览器自动填充表单项。
  • off:禁用浏览器自动填充。

【2】disabled

设置表单项是否禁用,禁用后用户无法编辑或选择该项的值。

【3】readonly

设置表单项是否只读,只读的表单项可以显示但不允许用户修改。

【4】maxmin

用于限制数字或日期类型的输入值的最大和最小值。

【5】***maxlengthminlength

设置输入值的最大长度和最小长度,超过或低于长度限制时将触发验证错误。

【6】pattern

使用正则表达式指定输入值的格式验证规则。

【7】required

设置表单项是否为必填项,如果未填写必填项,提交表单时将触发验证错误。

【8】step

用于数字类型的输入值,指定允许的增量或减量范围。

【9】aria-describedby

指定与表单项关联的描述性文本的id,辅助技术可以使用此信息提供更好的可访问性。

【10】aria-invalid

设置是否将表单项标记为无效,如果表单项的值不符合验证规则,可以将其设置为"true"。

【2】参数详解

(1)***widget:添加标签属性

  • 指定字段的控件显示方式。
  • 可以使用各种预定义的小部件(widgets)或自定义的小部件类。
  • 例如,可以使用 forms.TextInput 将字段以文本框的形式显示出来:
from django import forms

class MyForm(forms.Form):
    username = forms.CharField(
        max_length=8,
        min_length=3,
        label="用户名",
        widget=forms.TextInput(attrs={"class": "form-control"})
    )
  • 在Django的表单中
    • widget参数用于设置表单字段的展示方式或使用自定义的HTML元素来呈现字段。
  • widget参数
    • 可以接受一个Django提供的内置的小部件类(widget class)
    • 也可以使用自定义的小部件类。
  • 以下是几个常用的小部件类及其作用:
  1. forms.TextInput:默认的文本输入框小部件。
  2. forms.Textarea:多行文本输入框小部件。
  3. forms.CheckboxInput:复选框小部件。
  4. forms.Select:下拉选择框小部件。
  5. forms.RadioSelect:单选按钮小部件。
  6. forms.FileInput:文件上传小部件。
  7. forms.HiddenInput:隐藏输入框小部件。
  • 下面是一个示例代码片段
    • 演示如何使用widget参数定义一个表单字段的小部件:
from django import forms

class MyForm(forms.Form):
    username = forms.CharField(
        max_length=100,
        widget=forms.TextInput(attrs={'class': 'my-custom-class'})
    )
    password = forms.CharField(
        max_length=100,
        widget=forms.PasswordInput()
    )
  • 上述代码中
    • username 字段使用自定义的CSS类样式,并将其应用到一个文本输入框小部件中。
    • password 字段则使用了密码输入框小部件。
  • 需要注意的是
    • 可以通过attrs参数传递其他的HTML属性给小部件。
    • 例如,在上面的示例中,我们为username字段定义了class属性。
  • 另外
    • 如果想要使用自定义的小部件类
    • 可以定义一个继承自Django小部件类的类,并将其传递给widget参数。例如:
from django import forms

class MyCustomWidget(forms.widgets.TextInput):
    # 自定义的小部件类

class MyForm(forms.Form):
    my_field = forms.CharField(
        widget=MyCustomWidget()
    )
  • 上述代码中
    • 我们创建了一个名为MyCustomWidget的自定义小部件类,
    • 并将其用于my_field字段。
  • 通过使用widget参数
    • 我们可以对Django表单字段的展示方式进行灵活的定制
    • 以满足具体项目的需求。

(2)***required:是否为必填字段

  • 设置字段是否为必填项,默认为 True
  • 如果设置为 False,则允许用户提交空值。例如:
class MyForm(forms.Form):
    username = forms.CharField(
        max_length=8,
        min_length=3,
        label="用户名",
        required=False
    )

(3)***label:自定义标签文本

  • 指定字段的标签显示文本。
  • 可以使用该参数自定义字段在表单中的标签文本。例如:
class MyForm(forms.Form):
    username = forms.CharField(
        max_length=8,
        min_length=3,
        label="Your Username"
    )

(4)***error_messages:标签不合法的提示信息

  • 用于自定义错误消息。
  • 可以通过该参数自定义字段验证失败时显示的错误消息。例如:
class MyForm(forms.Form):
    username = forms.CharField(
        max_length=8,
        min_length=3,
        label="用户名",
        error_messages={
            "max_length": "用户名最多为8个字符",
            "min_length": "用户名至少为3个字符",
            "required": "请填写用户名"
        }
    )

(5)help_text:描述提醒信息

  • 为字段提供帮助文本。
  • 可以使用该参数为字段添加描述性的帮助文本,以向用户解释该字段的用途或提供输入建议。例如:
class MyForm(forms.Form):
    username = forms.CharField(
        max_length=8,
        min_length=3,
        label="用户名",
        help_text="请输入3-8个字符的用户名"
    )

(6)***validators:正则表达式

  • 用于自定义字段验证器。
  • 可以使用该参数添加额外的验证函数来对字段进行更复杂的验证。例如:
from django.core.validators import RegexValidator

class MyForm(forms.Form):
    username = forms.CharField(
        max_length=8,
        min_length=3,
        label="用户名",
        validators=[
            RegexValidator(
                regex=r"^[a-zA-Z]+$",
                message="只允许字母字符"
            )
        ]
    )

(7)***initial:默认值

  • 可以通过 initial 参数为表单字段设置默认值。
  • 该参数指定了在表单加载时显示的初始值。以下是示例代码:
from django import forms

class MyForm(forms.Form):
    username = forms.CharField(
        max_length=8,
        min_length=3,
        label="用户名",
        initial="JohnDoe"
    )
  • 在上述示例中
    • username 字段将显示一个文本框
    • 并且默认值为 "JohnDoe"。
  • 另外
    • 如果要为多个字段设置默认值
    • 可以通过使用 initial 字典来一次性设置多个字段的初始值。例如:
from django import forms

class MyForm(forms.Form):
    username = forms.CharField(
        max_length=8,
        min_length=3,
        label="用户名"
    )
    email = forms.EmailField(
        label="邮箱"
    )

    initial = {
        'username': 'JohnDoe',
        'email': 'johndoe@example.com'
    }
  • 可以在实例化表单对象时传递 initial 字典来设置这两个字段的默认值:
form = MyForm(initial={
    'username': 'JohnSmith',
    'email': 'johnsmith@example.com'
})
  • 当表单加载时
    • username 字段将显示为 "JohnSmith"
    • email 字段将显示为 "johnsmith@example.com"。
  • 这样
    • 通过使用 initial 参数
    • 可以方便地为表单字段设置默认值,提供更好的用户体验。

【3】其他字段渲染

(1)radioSelect(单选):

  • 您可以在表单类中使用forms.RadioSelect字段来渲染radioSelect字段。下面是一个案例演示:
from django import forms

class GenderForm(forms.Form):
    gender_choices = [
        ('male', 'Male'),
        ('female', 'Female'),
    ]
    gender = forms.ChoiceField(choices=gender_choices, widget=forms.RadioSelect)
  • 在这个案例中,我们定义了一个名为GenderForm的表单类,其中包含了一个gender字段用于选择性别。
  • 通过设置widget=forms.RadioSelect,我们将该字段渲染为单选按钮。

(2)单选Select:

  • 您可以使用forms.Select字段来呈现下拉菜单形式的单选选项。以下是一个案例演示:
from django import forms

class CityForm(forms.Form):
    city_choices = [
        ('1', 'New York'),
        ('2', 'London'),
        ('3', 'Tokyo'),
    ]
    city = forms.ChoiceField(choices=city_choices, widget=forms.Select)
  • 在这个案例中,我们定义了一个名为CityForm的表单类,其中包含了一个city字段用于选择城市。通过设置widget=forms.Select,我们将该字段渲染为下拉菜单。

(3)多选Select:

  • 使用forms.SelectMultiple字段可以渲染多选Select组件。以下是一个案例演示:
from django import forms

class InterestsForm(forms.Form):
    interest_choices = [
        ('coding', 'Coding'),
        ('gaming', 'Gaming'),
        ('reading', 'Reading'),
    ]
    interests = forms.MultipleChoiceField(choices=interest_choices, widget=forms.SelectMultiple)
  • 在这个案例中,我们定义了一个名为InterestsForm的表单类
  • 其中包含了一个interests字段用于选择兴趣。
  • 通过设置widget=forms.SelectMultiple,我们将该字段渲染为支持多选的下拉菜单。

(4)单选checkbox:

  • 要渲染单选checkbox字段,您可以使用forms.CheckboxInput字段。
  • 以下是一个案例演示:
from django import forms

class AgreeForm(forms.Form):
    agree = forms.BooleanField(widget=forms.CheckboxInput)
  • 在这个案例中,我们定义了一个名为AgreeForm的表单类
  • 其中包含了一个agree字段用于接受条款。
  • 通过设置widget=forms.CheckboxInput,我们将该字段渲染为单选复选框。

(5)多选checkbox:

  • 要渲染多选checkbox字段
  • 可以使用forms.CheckboxSelectMultiple字段。
  • 以下是一个案例演示:
from django import forms

class ColorsForm(forms.Form):
    color_choices = [
        ('red', 'Red'),
        ('green', 'Green'),
        ('blue', 'Blue'),
    ]
    colors = forms.MultipleChoiceField(choices=color_choices, widget=forms.CheckboxSelectMultiple)
  1. 在这个案例中,我们定义了一个名为ColorsForm的表单类,其中包含了一个colors字段用于选择颜色。
  2. 通过设置widget=forms.CheckboxSelectMultiple,我们将该字段渲染为多个复选框供用户选择。

【八】forms组件源码简解

切入点form_obj.is_valid()

def is_valid(self):
    """Return True if the form has no errors, or False otherwise."""
    return self.is_bound and not self.errors
  • 如果 is_valid 要想返回True
    • 那么 self.is_bound 要为True
    • self.errors 要为 False

【1】self.is_bound

def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
             initial=None, error_class=ErrorList, label_suffix=None,
             empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
    self.is_bound = data is not None or files is not None
  • data 是我们传入的数据
    • 只要传入数据有值
    • 那么 self.is_bound 一定是True

【2】self.errors

@property
def errors(self):
    """Return an ErrorDict for the data provided for the form."""
    if self._errors is None:
        self.full_clean()
        return self._errors

  • forms组件所有的功能基本都出自这个方法
def full_clean(self):
    """
        Clean all of self.data and populate self._errors and self.cleaned_data.
        """
    self._errors = ErrorDict()
    if not self.is_bound:  # Stop further processing.
        return
    self.cleaned_data = {}
    # If the form is permitted to be empty, and none of the form data has
    # changed from the initial data, short circuit any validation.
    if self.empty_permitted and not self.has_changed():
        return

    self._clean_fields() # 校验字段
    self._clean_form()
    self._post_clean()

(1)self._clean_fields() 校验字段 + 局部钩子

    def _clean_fields(self):
        for name, field in self.fields.items():  # 循环获取字段对象
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            if field.disabled:
                value = self.get_initial_for_field(field, name)
            else:
                # 获取字段对应的值
                value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                if isinstance(field, FileField):
                    initial = self.get_initial_for_field(field, name)
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value   # 将合法的字段添加到字典里
                if hasattr(self, 'clean_%s' % name): # 利用反射获取局部钩子函数
                    value = getattr(self, 'clean_%s' % name)()  # 局部钩子需要有返回值
                    self.cleaned_data[name] = value
            except ValidationError as e:
                self.add_error(name, e)  # 添加提示信息

循环获取字段对象

  • 局部钩子报错也可以使用 ValidationError 主动抛出异常

    • 较为繁琐,一般不用

(2)_clean_form 全局钩子

    def _clean_form(self):
        try:
            cleaned_data = self.clean() # 调用父类的clean方法或者自定义的clean方法
        except ValidationError as e:
            self.add_error(None, e)
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data

(3)_post_clean

    def _post_clean(self):
        """
        An internal hook for performing additional cleaning after form cleaning
        is complete. Used for model validation in model forms.
        """
        pass