Django之form组件

1、介绍

  之前form表单提交后,都是自己在前端和后端自定义验证规则,这样只会重复造轮子,不够简洁,所以django封装好了form表单的验证,这就是form组件。
  form组件的主要功能:
    1. 生成可被渲染的HTML标签
    2. 对用户提交的数据经行校验
    3. 把校验过后产生的提示信息返回给前端
    4. 可以保留上次输入内容

2、使用form组件进行一个简单的验证

<!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
      <link rel="stylesheet" href="/static/bootstrap3/css/bootstrap.min.css" />
      <script src="/static/bootstrap3/js/bootstrap.min.js"></script>
    </head>
  <body>
    <form action="" method="post">
    <h1>用户登录</h1>
    <div>
      <div>
        <label for="user">用户名</label>
        <p><input type="text" name="name" id="user"></p>
      </div>
      <div>
        <label for="pwd">密码</label>
        <p><input type="password" name="pwd" id="pwd"></p>
      </div>
      <div>
        <label for="email">邮箱</label>
        <p><input type="text" name="email" id="email"></p>
    </div>
    <input type="submit">
    </div>
  </form>
  </body>
</html>
模板文件(直接复制)
视图view.py 文件

from django import forms

class UserForm(forms.Form):
    """定义登录用户form验证规则"""
    name = forms.CharField(max_length=8)
    pwd = forms.CharField(max_length=8, min_length=3)
    email = forms.EmailField()

def login(request):
    if request.method == "GET":
        return render(request, "login.html")
    else:
        name = request.POST.get("name")
        pwd = request.POST.get("pwd")
        email = request.POST.get("email")
        # 进行判断
        # 传入的字典的key和UserForm的属性名必须一样
        form_obj = UserForm({'name': name, 'pwd': pwd, "email":email}) 
        # form_obj = UserForm(request.POST)
        if form_obj.is_valid():  # 如果数据全部校验通过才为True否则均为False
            pompt = "登录成功"
        else:
            pompt = "登录失败,格式不对"
        return HttpResponse(pompt)

3、form组件的方法和属性

# 示例:
from django import forms
class RegForm(forms.Form):
    name = forms. CharFiled(max_length=6)
    pwd = forms.CharField(max_length=8, min_length=3)
    email = forms.EmailField()

# 传值方式:
res = RegForm({'name': 'abcdefd', 'pwd': "12", 'email': "123"})
# 要求:
#     1. 传入的字典的key和UserForm的属性名必须一样
#     2. 必须是字典格式


# 判断校验是否通过
res.is_vaild()    # 结果是True或False


# 拿到校验不通过的原因信息
res.errors    
#  不同版本打印的格式都不一样,
# 但都可以认为是字典套列表的格式,所以可以通过字典的get()方式获取
res.errors.get(pwd) 
# 结果<ul class="errorlist"><li>Ensure this value has at least 3 characters (it has 1).</li></ul>


# 向错误字典内添加错误信息
res.add_error('name', '必须大写')
print(res.errors.get("name"))
# 结果是:<ul class="errorlist"><li>必须大写</li></ul>


# 拿到校验通过的数据,必须在res.error或res.is_vaild()后才能拿到,单独取res.cleaned_data报错
res.cleaned_data 
# 结果:{'name': 'sun'}

# 拿到所有数据
res.data

# 如果多传了字段,会直接丢弃,不去比较
# 少传字段,校验不会通过

4、使用form组件前端渲染

  后端需要把form对象返回当前端

form_obj = UserForm()
return render(request, "reg.html", {"form_obj": form_obj})

  前端渲染的三种方式:

# 第一种方式:
    {{ form_obj.as_p }}   # 生成p标签, 标签内是input
    {{ form_obj.as_ul }}  # 生成li标签,标签内是input

# 第二种方式:
<form>
    <p><label>{{ form_obj.name.label }}</label>{{ form_obj.name }}</P>
    <p><label>{{ form_obj.pwd.label }}</label>{{ form_obj.pwd }}</P>
</form>


# 通过后端的字段的参数label="用户名: ",可以把上面变成中文
# form组件只能获取用户输入(或选择的,只要是用户操作的)的标签,按钮必须手动设置


# 第三种:
<form>
    {% for foo in form_obj %}
        <p>
            <label>{{ foo.label }}</label>
            {{ foo }}
        </p>
</form>


<form novalidate>  取消浏览器的校验功能

5、表单验证并渲染错误信息

后端实现表单验证

参数1:
# 自己指定错误信息
error_messages = {
    "max_length": "长度最长只能6位",
    "required": "必须填写用户名"    
}
其它的key:
 Email字段:'invalid': '邮箱格式错误'
# error_messages里面的key错误,则报错
# ValueError: dictionary update sequence element #0 has length 0; 2 is required

# 参数2:
# 把密码隐藏,并加入了类名
widget = forms.widgets.PasswordInput(attrs={'class': " c1 form-control"})

# 参数3:
# 设置初始值
initial="ywsun"


前端使用方式
{{ foo.errors }}  拿到所有文本,并自动生成一个ul标签
{{ foo.errors.0 }} 只拿到文本

   示例:

from django import forms
from django.forms import widgets
class UserForm(forms.Form):
    """定义登录用户form验证规则"""
    name = forms.CharField(
        max_length=8,
        error_messages={
            "max_length": "用户名长度最长8位",
            "required": "必须填写用户名",
        }
    )
    pwd = forms.CharField(
        max_length=8,
        min_length=3,
        error_messages={
            "max_length": "用户名长度最长8位",
            "min_length": "用户名长度最短3位",
            "required": "必须填写密码",
        },
        widget = widgets.PasswordInput(attrs={"class": "form-control"}),
    )
    email = forms.EmailField(
        error_messages={
            "required": "必须填写邮箱",
            "invalid": "邮箱格式错误",  # ValueError: dictionary update sequence element #0 has length 0; 2 is required
        }
    )


def login(request):
    if request.method == "GET":
        form_obj = UserForm()
        return render(request, "login.html", {"form_obj": form_obj})
    else:
        form_obj = UserForm(request.POST)
        if form_obj.is_valid():  
            return HttpResponse("OK")
        else:
            return render(request, "login.html", {"form_obj": form_obj})
后端view.py
<body>
  <div>
    <form method="post" action="" novalidate>
      {% for foo in form_obj %}
        <p>
          <label>{{ foo.label }}</label>
          {{ foo }}{{ foo.errors.0 }}
        </p>
      {% endfor %}
      <input type="submit" value="提交">
    </form>
  </div>
</body>
前端

6、钩子函数(自定义验证规则)

  6.1 局部钩子

from django.core.exceptions import ValidationError
def clean_name(self):
    name = self.cleaned_data.get("name")
    if "aaa" in name:
        self.add_error("name",  "不能包含aaa")
        # 另一种方法
        # raise ValidationError("不能包含aaa")
    # 拿出的数据必须返回去
    return name

  6.2 全局钩子

# 全局校验
def clean(self):
    pwd_value = self.cleaned_data.get("pwd")
    repwd_value = self.cleaned_data.get("re_pwd")
    if pwd_value == repwd_value:
        # 需要把错误加入errors内
        self.add_error("repwd_value",  "两次密码不一致")
        # 另一种方式
        raise ValidationError({"re_pwd": "两次密码不一致"})
    # 全局校验过后,把cleaned_data 把数据返回(也可以不写)
    return self.cleaned_data
        

  强调:

    1. 局部钩子使用raise添加错误信息

    原因:如果使用self.add_error("name", "error"), 添加错误信息,最后的cleaned_data内仍然会有name的键值对存在,而 raise ValidationError("error"), 则不会,(和定义的函数有关,使用raise则没有返回值)

    2. validationError的使用:

      参数可以是字符串,列表,字典(key是字段名)

      对于局部钩子:只能使用字符串和列表

      对于全局钩子:三者都可以使用,但字符串和列表的错误信息会放在"__all__"下(在前端取不出来错误信息,同时cleaned_data里仍然会有校验不同过的字段),推荐使用字典格式,或self.addr_error()

7、form 组件中常用参数

  7.1 Field字段中常用参数(其他的字段都继承了Field)

参数
说明
required
required=False,表示这个字段可以为空
widget
用来设置input标签的类型
initial
initial="sun",表示input框内的初始值为sun
label
label="姓名",表示label标签的内容的内容为"姓名",不能自动生成label标签
label_suffix
label_suffix=">>", 表示label内容的后缀为">>", 单拿通过{{foo. label_tag}}, 拿到label和后缀
error_messages
自定义验证不通过提示信息,error_messges={"参数": "错误提示信息"}
help_text
在标签旁生成帮助信息,单个标签通过{{foo.help_text}} 拿到
validators=[]
使用正则自定义验证规则
localize=False
是否支持本地化(时间和语言)
disabled=False
是否可编辑,Ture即不可编辑
 # validators 用法:
   from django.core.validators import RegexValidator
    name = serializers.CharField(
        # max_length=3,
        min_length=2,
        validators=[RegexValidator(regex='^\d+$', message="必须是数字")],
        error_messages={
            'max_length': 'must be less than 3 words',
            'min_length': 'must be more than 2 words',
        },

    )

  7.2 widget 主要用在单选、多选和下拉框

    如果需要个性化样式,就得手动添加CSS

    1. radio 单选框

from django import forms
from django.forms import widgets

# 方式1:
gender= forms.ChoiceField(
    choices=choices=((1, "boy"), (2, "girl"), (3, "other")),
    widget=widgets.RadioSelect()
)

# 方式2:
gender = forms.IntegerField(
    widget=widgets.RadioSelect(
        choices=((1, "boy"), (2, "girl"), (3, "other")),
    )
)
前端只能通过{{ foo }} 拿到一个label和ul标签,如下:
<div><label for="id_gender_0">Gender:</label>
  <ul id="id_gender" class="radio">
    <li>
      <label for="id_gender_0"><input type="radio" name="gender" value="1" class="radio" required="" id="id_gender_0">
        boy
      </label>
    </li>
    <li>
      <label for="id_gender_1"><input type="radio" name="gender" value="2" class="radio" required="" id="id_gender_1">
        girl
      </label>
    </li>
    <li>
      <label for="id_gender_2"><input type="radio" name="gender" value="3" class="radio" required="" id="id_gender_2">
      other
    </label>
    </li>
  </ul>
</div>

    2. checkbox 单选

keep = forms.ChoiceField(
    label="是否记住密码",
    initial="1",
    widget=forms.widgets.CheckboxInput()
)
# 通过设置initial,则选框会value="1",且默认被选中

# 方式2:
keep
= forms.ChoiceField( label="是否记住密码", widget=forms.widgets.CheckboxInput(attrs={"value": "checked"}) ) # 可以直接设置value属性,而不被选中
得到前端标签

<p>
  <label for="id_keep">是否记住密码:</label>
  <input type="checkbox" name="keep" value="1" required="" id="id_keep" checked="">
</p>

    3. checkbox 多选框

# 方式一:
hobby = forms.MultipleChoiceField(
    choices=((1, "basketball"), (2, "football"), (3, "PingPang"),),
    widget=forms.widgets.CheckboxSelectMultiple()
)

# 方式二:
hobby = forms.IntegerField(
    widget=forms.widgets.CheckboxSelectMultiple(
        choices=((1, "basketball"), (2, "football"), (3, "PingPang"),)
    )
)
前端得到标签

<div><label>Hobby:</label>
  <ul id="id_hobby">
    <li>
      <label for="id_hobby_0"><input type="checkbox" name="hobby" value="1" id="id_hobby_0">
        basketball
      </label>
    </li>
    <li>
      <label for="id_hobby_1"><input type="checkbox" name="hobby" value="2" id="id_hobby_1">
        football
      </label>
    </li>
    <li>
      <label for="id_hobby_2"><input type="checkbox" name="hobby" value="3" id="id_hobby_2">
        PingPang
      </label>
    </li>
  </ul>
</div>

    4. select 单选框

# 方式1:
hobby = forms.ChoiceField(
    choices=((1, "basketball"), (2, "football"), (3, "PingPang"),),
    widget=forms.widgets.Select()
)

# 方式2:
hobby = forms.CharField(
    widget=forms.widgets.Select(
        choices=((1, "basketball"), (2, "football"), (3, "PingPang"),)
    )
)
<div>
  <label for="id_hobby">Hobby:</label>
  <select name="hobby" id="id_hobby">
    <option value="1">basketball</option>
    <option value="2">football</option>
    <option value="3">PingPang</option>
  </select>
</div>

    5. select 多选框

# 方式1:
hobby = forms.MultipleChoiceField(
    choices=((1, "basketball"), (2, "football"), (3, "PingPang"),),
    widget=forms.widgets.SelectMultiple()
)

# 方式2:
hobby = forms.CharField(
    widget=forms.widgets.SelectMultiple(
        choices=((1, "basketball"), (2, "football"), (3, "PingPang"),)
    )
)

    6. 解决choice 动态显示数据

      使用choices从数据库中获取数据时,由于是静态字段,获取的值无法实时更新, 解决办法:

from django.forms import Form
from django.forms import widgets
from django.forms import fields

class RegisterForm(Form):
    ...
    hobby = forms.MultipleChoiceField(
        # choices=models.Hoppy.objects.values_list("id", "option")
        widget=forms.widgets.SelectMultiple()
    )
    def __init__(self, *args, **kwargs):
        super(RegisterForm, self).__init__(*args, **kwargs)
        self.fields['hoppy'].choices = models.Hoppy.objects.values_list("id", "option")

8、form 的内置字段(类比于model的内置字段)

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)           数字,字母,下划线,减号(连字符)
    ...

UUIDField(CharField)           uuid类型
常用字段

 

     
posted @ 2019-04-30 12:06  yw_sun  阅读(189)  评论(0)    收藏  举报