forms组件


1.form组件--校验字段

基于用户注册演示,展现的功能为

  • 校验字段
  • 渲染标签
  • 渲染错误与重置输入信息功能
  • 局部钩子(单个字段自定义校验)与全局钩子(多个字段之间的关系,报错信息放在errors的全局key __all__中)

全局urls.py

from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('reg/', views.reg),
]

app01/models.py

from django.db import models
class UserInfo(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    email = models.EmailField()
    tel = models.CharField(max_length=32)

app01/myforms.py 订制forms组件

from app01.models import *
import re
# 引入form组件
from django import forms

# widgets是一个配置forms组件的参数配置(标签类型,属性等)
from django.forms import widgets

# Error处理组件
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError


# 创建一个继承了forms.Form的类,类名随便取

class UserForm(forms.Form):
    """
    1. 不写验证规则,默认规则为不为空,
    2. label是字段的名称(可用于form表单渲染)
    3. error_messages字典里面为自定义错误信息key为 required表示告诉请求的类型 invalid表示非法
    4. widget可以为form渲染模板提供相应支持,可以在widget中指定标签的类型(默认TextInput)和属性(值) attrs

    """
    name = forms.CharField(min_length=4, label="用户名", error_messages={"required": "该字段不能为空"},
                           widget=widgets.TextInput(attrs={"class": "form-control"}))

    pwd = forms.CharField(min_length=4, label="密码",
                          widget=widgets.PasswordInput(attrs={"class": "form-control"}))

    r_pwd = forms.CharField(min_length=4, label="确认密码",
                            widget=widgets.PasswordInput(attrs={"class": "form-control"}))

    email = forms.EmailField(label="邮箱",
                             widget=widgets.TextInput(attrs={"class": "form-control"}))

    tel = forms.CharField(label="手机号",
                          widget=widgets.TextInput(attrs={"class": "form-control"}))

    """form组件校验的局部钩子(深度定制校验功能,源码利用反射提供的一个接口)
    检验是通过is_valid()-->errors-->full_clean()--_clean_fields()开始进行,对应的源码(关键部分)
        def _clean_fields(self):
        for name, field in self.fields.items(): # 拿到自定义字段的名字name以及验证规则fields
           ...
            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  # 将匹配成功的字段以及值放入cleaned_data字典中
                if hasattr(self, 'clean_%s' % name): # 局部钩子!判断是否有自定义验证规则clean_%s % name(所以自定义必须以clean_相关属性名为扩展函数名)
                    value = getattr(self, 'clean_%s' % name)() # 执行自定义规则
                    self.cleaned_data[name] = value
            except ValidationError as e: # 抛出ValidationError错误,自定义的抛错也必须一致
                self.add_error(name, e)

    """

    def clean_name(self):  # 验证name字段的扩展!
        # 能执行到这一步说明之前已经校验正确了,cleaned_data已存在正确的值
        val = self.cleaned_data.get("name")

        # 查询名称是否已经存在表记录中
        ret = UserInfo.objects.filter(name=val)
        if not ret:
            return val
        else:
            raise ValidationError("该用户已注册,请重新输入用户名!")

    def clean_tel(self):
        var = self.cleaned_data.get("tel")

        if re.fullmatch("[0-9]{11}", var):
            return var
        else:
            raise ValidationError("手机号必须是11位阿拉伯数字")

    """forms组件的全局钩子
    is_valid()-->errors-->full_clean()--_clean_form()-->clean()
    clean()方法没有写任何逻辑,这部分完全由用户写逻辑,用户写一个clean方法覆盖原来的clean方法
    关键部分源码:
    def _clean_form(self):
        try:
            cleaned_data = self.clean()
        except ValidationError as e: # 全局校验如果出错,会把报错信息添加到如下:
            self.add_error(None, e) # errors {"__all__":[e,]}
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data

    def clean(self):
        return self.cleaned_data

    """

    # 验证密码和确认密码是否相等
    def clean(self):  # 这部分始终会执行
        """如果两个密码分别校验成功则执行下面的,
        如果存在一个为空说明其中有校验错误的字段,后面则没有必要校验了
        """
        pwd = self.cleaned_data.get("pwd")
        r_pwd = self.cleaned_data.get("r_pwd")
        if pwd and r_pwd:
            if pwd == r_pwd:
                return self.cleaned_data  # 对应源码的clean方法
            else:  # 对应源码_clean_form抛错部分
                raise ValidationError("两次密码输入不一致")
        else:
            return self.cleaned_data

app01/view.py

from django.shortcuts import render,HttpResponse
from app01.myforms import UserForm
def reg(request):

    """forms组件校验演示(调用父类的方法进行验证)
    1. is_valid     判断是否全部合法
    2. forms.errors 存放错误信息
    3. cleaned_data 存放正确匹配值
    4. 前端传过来的key必须与自定义form组件对应的字段要匹配

    forms校验必须将定义的字段交验完,如果传入其他不需要校验的字段无影响(不相关字段无法传出),
    但是少一个必须校验的字段可不行

    传递的参数为一个字典
    forms = UserForm({"name":"yuan","email":"123"})

    断值是否是符合自己定义的规则
    print("is_valid",forms.is_valid())

    如果不符规则放在errors字典(直接打印是一个ul形式)里面
    每个键值下面是一个列表

    if not forms.is_valid():
        print(forms.errors)
        print(forms.errors.get("name")[0])
        return HttpResponse("%s"%str(forms.errors))
    else:
        print(forms.cleaned_data)

    if 有字段校验成功
    则正确的校验的数据都放在forms.cleaned_data字典中
    """

    if request.method == "POST":
        form = UserForm(request.POST)
        if form.is_valid():
            print("form.is_valid()",form.cleaned_data)
            return HttpResponse("OK")
        else:
            print("form.is_valid()",form.errors)
            pwd_error = form.errors.get("__all__")[0]
            # forms组件渲染错误信息errors
            # 通过forms组件生渲染的form表单!
            return render(request,"index.html",locals())


    """forms组件的渲染标签功能(模板中实现)
    保证forms里面的标签与定义的forms组件属性一致
    在提交的时候会验证当前字段是否合法
    """
    form = UserForm()


    return render(request,"index.html",locals())

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
        <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
    <form action="" method="post">
        {% csrf_token %}
        用户名:<input type="text" name="name"> </br>
        密码: <input type="password" name="pwd"> </br>
        确认密码: <input type="password" name="r_pwd"> </br>
        邮箱:<input type="text" name="email"> <br>
        电话:<input type="text" name="tel">

        <input type="submit" value="提交">
    </form>


<hr>
 forms组件渲染标签功能--重点是保证forms里面的标签与定义的forms组件属性一致
 直接将需要渲染的标签用forms的相关变量代替
 方式一: form.name.lable是取得相应的标签命
    <h1>方式一</h1>
    <form action="">
        {% csrf_token %}
            form.name.errors.0拿到报错信息
        {{ form.name.label }}:{{ form.name }} <span>{{ form.name.errors.0 }}</span> <br>
        {{ form.pwd.label }} : {{ form.pwd }} <span>{{ form.name.pwd.errors.0 }}</span> <br>
        {{ form.r_pwd.label }}: {{ form.r_pwd }} <span>{{ form.name.r_pwd.errors.0 }}</span>  <br>
        {{ form.email.label }}: {{ form.email }} <span>{{ form.name.email.errors.0 }}</span> <br>
        {{ form.tel.label }}:{{ form.tel }}

        <input type="submit" value="提交">
    </form>

<hr>
{# 方式二: 推荐的方式#}
<div class="container">
<div class="row text-center">
    <h1>方式二</h1>
</div>
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" method="post">
                {% csrf_token %}
                {% for field in form %}
                    <label for="field.id">{{ field.label }}</label>
                    {{ field }} <br>
                    <span style="color: red">{{ field.errors.0 }}</span>  <br>
                    {% if field.name == "r_pwd" %}
                        <span style="color: red">{{ pwd_error }}</span> <br>
                    {% endif %}
                {% endfor %}

                <input type="submit" value="提交">
            </form>
        </div>
    </div>
</div>


<hr>
{# 方式三: 不推荐使用 as_p做p标签#}
{#    <form action="">#}
{#        {% csrf_token %}#}
{#        {{ form.as_p }}#}
{#        <input type="submit" value="提交">#}
{#    </form>#}


</body>
</html>
posted @ 2018-08-09 00:04  哈哈大圣  阅读(254)  评论(0编辑  收藏  举报