day75---Django的form组件

1. form组件的介绍

描述:django框架提供了一个form类,来处理web开发中的表单相关事项

form组件的功能

  • 方便限制字段条件

  • 生成HTML标签

  • 验证用户数据(显示错误信息)

  • 保留上次提交数据

  • 初始化页面显示内容

导入form类

    from django import forms

form组件的执行流程

  • 导入forms类

  • 定义类和字段及其属性(类需要继承forms.Form)

  • 实例化form对象

  • 接收get请求,进行渲染模板

  • 接收post请求,进行数据处理

  • 把处理结果返回到前端

2. form组件的字段和属性

django内置字段

  • 字段类型

    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) # 格式:2018-03-01
    TimeField(BaseTemporalField) # 格式:11:11
    DateTimeField(BaseTemporalField) # 格式:2018-03-01 11:11
    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类型
  • 字段属性

  • required=True:是否允许为空

  • widget=None:HTML插件

  • label=None:用于生成Label标签或显示内容

  • initial=None:初始值

  • help_text='':帮助信息(在标签旁边显示)

  • error_messages=None:定义错误信息错误信息

  • show_hidden_initial=False:是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一致)

  • validators=[]:自定义验证规则

  • localize=False:是否支持本地化

  • disabled=False:是否可以编辑

  • label_suffix=None:Label内容后缀

Django内置插件

    # 单选的check和select值为字符串
    # 多选的check和select值为列表
    '''
    TextInput(Input)
    NumberInput(TextInput)
    EmailInput(TextInput)
    URLInput(TextInput)
    PasswordInput(TextInput)
    HiddenInput(TextInput)
    Textarea(Widget)
    DateInput(DateTimeBaseInput)
    DateTimeInput(DateTimeBaseInput)
    TimeInput(DateTimeBaseInput)
    CheckboxInput
    Select
    NullBooleanSelect
    SelectMultiple
    RadioSelect
    CheckboxSelectMultiple
    FileInput
    ClearableFileInput
    MultipleHiddenInput
    SplitDateTimeWidget
    SplitHiddenDateTimeWidget
    SelectDateWidget
    '''

Widgets

每个表单字段都有一个对应的Widget类,它对应一个HTML表单Widget,使用时需要导入模块from django.forms import widgets

is_valid()方法

调用form对象的is_valid()方法可以进行表单的验证。如果结果是True,代表成功验证,验证后的表单数据将位于form.cleaned_data字典中,此时的数据会按照指定的字段类型做好处理,已经转为python中的数据类型;如果结果是False,可以把对应的error_messages错误信息返回给前端

3. form类中绑定和未绑定的表单的区别

  • 未绑定的表单没有关联的数据,当渲染给用户时,它将为空或包含默认的值

  • 绑定的表单具有提交的数据,因此可以用来检验数据是否合法。如果渲染一个不合法的绑定的表单,它将包含内联的错误信息,告诉用户如何纠正数据

4. form模板渲染

注意:form组件无法渲染form标签和submit标签

表单渲染的选项

  • {{ form.as_table }}:以表格的形式渲染在<tr>标签中

  • {{ form.as_p }}:渲染在<p>标签中

  • {{ form.as_ul }}:渲染在<li>标签中

注意:必须手动提供<table></table>标签或<ul></ul>标签

模版渲染的三种方式

  • 方式一:

    <form action="" method="post" novalidate>
        {% csrf_token %}
        {{ form_obj.as_p }}
        <input type="submit">
    </form>
  • 方式二:

    <form action="" method="post" novalidate>
    	{% csrf_token %}
    	<div>
    		<label for="">用户名</label>
    		{{ form_obj.user }}
    		<span>{{ form_obj.errors.user.0 }}</span>
    	</div>
    	<div>
    		<label for="">密码</label>
    		{{ form_obj.pwd }}
    		<span>{{ form_obj.errors.pwd.0 }}
    		</span>
    	</div>
    	<input type="submit">
    </form>
  • 方式三:

    <form action="" novalidate method="post">
        {% csrf_token %}
        {% for field in form_obj %}
            <div>
                <label for="">{{ field.label }}</label>
                {{ field }}
            </div>
        {% endfor %}
        <input type="submit">
    </form>

5. form组件的钩子

抛出自定义错误,需要导入模块

    from django.core.exceptions import ValidationError

局部钩子

在form类中自定义方法,使用ValidationError抛出自定义的错误

所有错误信息都存储在对象.errors

全局钩子

在form类中定义clean方法,使用ValidationError抛出自定义的错误

全局钩子的错误信息存储在name值为__all__

6. 使用form组件和ajax结合实现注册功能

自定义form组件(forms.py)

    from .models import *
    from django import forms
    from django.forms import widgets
    from django.core.exceptions import ValidationError
    class RegisterForms(forms.Form):
        user = forms.CharField(
            max_length=20,
            min_length=2,
            error_messages={
                'required': '用户名不能为空',
                'min_length': '用户名长度为2-20个字符',
                'max_length': '用户名长度为2-20个字符'
            },
            widget=widgets.TextInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '用户名',
                    'autocomplete': 'off'
                }
            )
        )
        pwd = forms.CharField(
            max_length=20,
            min_length=8,
            error_messages={
                'required': '密码不能为空',
                'min_length': '密码长度为8-20个字符',
                'max_length': '密码长度为8-20个字符'
            },
            widget=widgets.PasswordInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '密码',
                    'autocomplete': 'off'
                }
            )
        )
        repeat_pwd = forms.CharField(
            max_length=20,
            min_length=8,
            error_messages={
                'required': '确认密码不能为空',
                'min_length': '密码长度不符合规则',
                'max_length': '密码长度不符合规则'
            },
            widget=widgets.PasswordInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '确认密码',
                    'autocomplete': 'off'
                }
            )
        )
        email = forms.EmailField(
            error_messages={
                'required': '邮箱不能为空',
                'invalid': '邮箱格式不正确'
            },
            widget=widgets.EmailInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '邮箱地址',
                    'autocomplete': 'off'
                }
            )
        )
        tel = forms.CharField(
            error_messages={
                'required': '手机号码不能为空',
            },
            widget=widgets.EmailInput(
                attrs={
                    'class': 'form-control',
                    'placeholder': '手机号码',
                    'autocomplete': 'off'
                }
            )
        )
        # 局部钩子(验证用户名是否存在)
        def clean_user(self):
            user = self.cleaned_data.get('user')
            if not UserInfo.objects.filter(username=user):
                return user
            else:
                raise ValidationError('用户名已存在')
        # 全局钩子(验证密码和确认密码输入是否一致)
        def clean(self):
            pwd = self.cleaned_data.get('pwd')
            repeat_pwd = self.cleaned_data.get('repeat_pwd')
            if pwd and repeat_pwd:
                if pwd == repeat_pwd:
                    return self.cleaned_data
                else:
                    raise ValidationError('两次密码不一致')
            else:
                return self.cleaned_data

创建表结构(models.py)

    class UserInfo(models.Model):
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=32)
        email = models.EmailField(null=True)
        telephone = models.BigIntegerField(null=True)

配置视图函数(views.py)

from django.shortcuts import render
from django.http import JsonResponse
from .forms import RegisterForms
from .models import *
def register(request):
    if request.method == 'POST':
        register_forms = RegisterForms(request.POST)
        register_response = {'user': None, 'err_msg': None}
        if register_forms.is_valid():
            user = register_forms.cleaned_data.get('user')
            pwd = register_forms.cleaned_data.get('pwd')
            email = register_forms.cleaned_data.get('email')
            tel = register_forms.cleaned_data.get('tel')
            user_obj = UserInfo.objects.create(username=user, password=pwd, email=email, telephone=tel)
            register_response['user'] = user_obj.username
        else:
            register_response['err_msg'] = register_forms.errors
        return JsonResponse(register_response)
    register_forms = RegisterForms()
    return render(request, 'register.html', {'register_forms': register_forms})    

配置模板页面(register.html)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
        <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
        <script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
        <title>注册页面</title>
    </head>
    <body>
    <div class="container">
        <div class="row">
            <div class="col-md-offset-3 col-md-6">
                <div class="page-header">
                    <h1>
                        注册页面
                        <small>register</small>
                    </h1>
                </div>
                <form class="form-horizontal">
                    {% csrf_token %}
                    <div class="form-group">
                        <label for="id_user" class="col-md-3 control-label">用户名:</label>
                        <div class="col-md-6">
                            {{ register_forms.user }}
                        </div>
                        <span class="err_msg pull-left control-label"></span>
                    </div>
                    <div class="form-group">
                        <label for="id_pwd" class="col-md-3 control-label">密码:</label>
                        <div class="col-md-6">
                            {{ register_forms.pwd }}
                        </div>
                        <span class="err_msg pull-left control-label"></span>
                    </div>
                    <div class="form-group">
                        <label for="id_repeat_pwd" class="col-md-3 control-label">确认密码:</label>
                        <div class="col-md-6">
                            {{ register_forms.repeat_pwd }}
                        </div>
                        <span class="err_msg pull-left control-label"></span>
                    </div>
                    <div class="form-group">
                        <label for="id_email" class="col-md-3 control-label">邮箱地址:</label>
                        <div class="col-md-6">
                            {{ register_forms.email }}
                        </div>
                        <span class="err_msg pull-left control-label"></span>
                    </div>
                    <div class="form-group">
                        <label for="id_tel" class="col-md-3 control-label">手机号码:</label>
                        <div class="col-md-6">
                            {{ register_forms.tel }}
                        </div>
                        <span class="err_msg pull-left control-label"></span>
                    </div>
                    <div class="form-group">
                        <div class="col-md-offset-3 col-md-3">
                            <button type="button" id="register" class="btn btn-primary btn-block">注册</button>
                        </div>
                        <div class="col-md-3">
                            <button type="reset" id="cancel" class="btn btn-warning btn-block">取消</button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>
    <script>
        $("#register").click(function () {
            $.ajax({
                url: "/register/",
                type: "post",
                data: {
                    "csrfmiddlewaretoken": $("[name='csrfmiddlewaretoken']").val(),
                    "user": $("#id_user").val(),
                    "pwd": $("#id_pwd").val(),
                    "repeat_pwd": $("#id_repeat_pwd").val(),
                    "email": $("#id_email").val(),
                    "tel": $("#id_tel").val()
                },
                success: function (data) {
                    if (data.user) {
                        location.href = '/login/'
                    } else {
                        // 清空之前的错误信息
                        $("div span").html("");
                        $(".form-group div").removeClass("has-error");
                        // 显示当前错误信息
                        $.each(data.err_msg, function (name, msg) {
                            $("#id_" + name).parent().addClass("has-error").next().html(msg[0]).css({
                                "color": "red",
                                "font-weight": 700
                            });
                            // 判断全局错误
                            if (name === "__all__") {
                                $("#id_repeat_pwd").parent().addClass("has-error").next().html(msg[0]).css({
                                    "color": "red",
                                    "font-weight": 700
                                });
                            }
                        })
                    }
                }
            })
        })
    </script>
    </body>
    </html>
posted @ 2018-03-01 09:31  _岩哥  阅读(301)  评论(0)    收藏  举报