欢迎来到Louis的博客

人生三从境界:昨夜西风凋碧树,独上高楼,望尽天涯路。 衣带渐宽终不悔,为伊消得人憔悴。 众里寻他千百度,蓦然回首,那人却在灯火阑珊处。
扩大
缩小

Django - forms组件

forms组件

django中的Form组件有以下几个功能:

  • 生成HTML标签
  • 验证用户数据(显示错误信息)
  • HTML Form提交保留上次提交数据
  • 初始化页面显示内容

校验字段功能:

我们之前写过的登录以及注册页面及其他用form表单的的功能都忽略了一个重要的点,就是用户输入的数据校验,

打开任何一个网站的注册页面都会对用户的输入进行一个校验,验证用户输入是否符合我们制定的规则,看下博客园的注册页面

如果我什么都不输入点击注册就会显示上面的页面,它提示了我们这些字段不能为空,为必须要填的字段

其实一些简单的校验我们可以给input标签添加属性来实现,比如字段不可为空,email字段必须包含@符号,但一旦涉及一些自定义的校验,前端的标签属性就无法完成了

比如:

  • 检测用户名是否已存在,
  • 手机号是否是有效手机格式
  • 显示名称敏感词过滤
  • 密码必须不少于多少位,且必须符合我们制定的规则(包含字母,数字,特殊字符)

上面这些验证在后段如果自己写逻辑,也可以实现,但会显得代码比较繁琐,没有解藕,Django给我们的前端form表单提供了一个用户认证的组件forms组件。

来看个示例:

forms_check.py

from django import forms
from django.forms import widgets as wid
from app01.models import UserInfo
from django.core.exceptions import ValidationError
import re


# 常规 form
class UserRegForm(forms.Form):
    # 用户名
    username = forms.CharField(min_length=4,
                               max_length=16,
                               label='用户名:',
                               error_messages={'required': '用户名不能为空',
                                               'min_length': '用户名不能少于4位'},
                               widget=wid.TextInput(attrs={'placeholder': "用户名为4~16为位"})
                               )
    # 校验密码
    password = forms.CharField(min_length=6,
                               max_length=16,
                               label='密码:',
                               error_messages={'required': '密码不能为空',
                                               'min_length': '密码不能少于6位'},
                               widget=wid.PasswordInput(attrs={'placeholder': "密码为6~16位"})
                               )
    # 校验确认密码
    confirm_password = forms.CharField(min_length=6,
                                       max_length=16,
                                       label='确认密码:',
                                       error_messages={'required': '密码不能为空',
                                                       'min_length': '密码不能少于6位'},
                                       widget=wid.PasswordInput(attrs={'placeholder': "请确认密码"}))
    # 校验邮箱
    email = forms.EmailField(error_messages={"required": "邮箱不能为空", "invalid": "邮箱格式错误"},
                             label='邮箱',
                             widget=wid.EmailInput(attrs={'placeholder': "请输入邮箱邮箱"}))
    # 校验手机号
    telephone = forms.CharField(label='手机号码:',
                                error_messages={'required': '手机号不能为空'},
                                widget=wid.TextInput(attrs={'placeholder': "请输入手机号"}))

    # 给所有字段添加form-control类
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for filed in self.fields.values():
            filed.widget.attrs.update({'class': 'form-control'})

    # 校验用户名是否已存在(局部钩子)
    def clean_username(self):
        val = self.cleaned_data.get('username')
        if UserInfo.objects.filter(username=val).first():
            raise ValidationError('用户名已存在')
        else:
            return val

    # 校验密码不能位纯数字
    def clean_password(self):
        val = self.cleaned_data.get('password')
        if val.isdigit():
            raise ValidationError('密码不能为纯数字')
        else:
            return val

    # 校验邮箱格式
    def clean_email(self):
        val = self.cleaned_data.get('email')
        if re.search(r'^[\w.\-]+@(?:[a-z0-9]+(?:-[a-z0-9]+)*\.)+[a-z]{2,3}$', val):
            return val
        else:
            raise ValidationError('无效的邮箱')

    # 校验手机号格式
    def clean_telephone(self):
        val = self.cleaned_data.get('telephone')
        if re.search(r'^(13\d|14[5|7]|15\d|166|17[3|6|7]|18\d)\d{8}$', val):
            return val
        else:
            raise ValidationError('无效的手机号')

    # 校验两次密码是否一致(全局钩子)
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if password and confirm_password and password != confirm_password:
            self.add_error('confirm_password', ValidationError('两次密码不一致'))
        else:
            return self.cleaned_data
View Code

views.py

from django.shortcuts import render, redirect, HttpResponse
from django.http import JsonResponse
from app01.models import UserInfo
from app01.forms_check import UserRegForm

def reg(request):
    # 接收到ajax请求
    if request.is_ajax():
        # 使用forms对象对post请求体中的数据进行校验
        form_check = UserRegForm(request.POST)
        # 响应的数据字典
        response = {'user': None, 'error_msg': ''}
        # 所有表单校验都通过
        if form_check.is_valid():
            # 到数据库插入一条用户记录数据
            username = request.POST.get('username')
            password = request.POST.get('password')
            email = request.POST.get('email')
            telephone = request.POST.get('telephone')
            UserInfo.objects.create_user(username=username, password=password, email=email, telephone=telephone)
            # 响应体数据字典的user的None改为用户名
            response['user'] = form_check.cleaned_data.get('username')
        else:
            # 有表单验证错误的情况,响应体数据字典的error_msg设置值为校验错误信息
            response['error_msg'] = form_check.errors
        # 最后返回响应体数据字典
        return JsonResponse(response)
    else:
        # 创建forms对象
        forms = UserRegModelForm()
        # 返回页面及forms对象,到页面进行标签渲染
        return render(request, 'reg.html', locals())
View Code

urls.py

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

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    # 登录
    path('login/', views.login, name='login'),
    path('', views.default),
    # 验证码
    path('get_valid_code/', views.get_valid_code),

    # 首页
    path('index/', views.index, name='index'),

    # 注册
    path('reg/', views.reg, name='reg'),
]
View Code

reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="Mosaddek">
    <meta name="keyword" content="FlatLab, Dashboard, Bootstrap, Admin, Template, Theme, Responsive, Fluid, Retina">
    <link rel="shortcut icon" href="/static/img/favicon.html">

    <title>FlatLab - Flat & Responsive Bootstrap Admin Template</title>

    <!--注意原模版中的外链资源,路径地址全部都要修改-->
    <!-- Bootstrap core CSS -->
    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
    <link href="/static/css/bootstrap-reset.css" rel="stylesheet">
    <!--external css-->
    <link href="/static/assets/font-awesome/css/font-awesome.css" rel="stylesheet"/>
    <!-- Custom styles for this template -->
    <link href="/static/css/style.css" rel="stylesheet">
    <link href="/static/css/style-responsive.css" rel="stylesheet"/>
    <script src="/static/js/jquery.js"></script>
    <!-- HTML5 shim and Respond.js IE8 support of HTML5 tooltipss and media queries -->
    <!--[if lt IE 9]>
    <script src="/static/js/html5shiv.js"></script>
    <script src="/static/js/respond.min.js"></script>
    <![endif]-->
</head>

<body class="login-body">

<div class="container">
    <form class="form-horizontal form-signin form-signup" action="index.html">
        <h2 class="form-signin-heading">sign up now</h2>
        <!--由于是post请求,需要加入csrf_token通过验证-->
        {% csrf_token %}
        <div class="login-wrap">
            <!--遍历forms对象中的每个字段生成input标签-->
            {% for field in forms %}
                <div class="form-group">
                    <!--field.auto_id为对应的input的id属性-->
                    <!--field.label后端该字段设置的label值-->
                    <label for="{{ field.auto_id }}"
                           class="col-sm-2 control-label signup-label">{{ field.label }}</label>
                    <div class="col-sm-10">
                        <!--field渲染成标签-->
                        {{ field }}
                        <span class="error-msg pull-right"></span>
                    </div>
                </div>
            {% endfor %}
            <!--密码-->
            <!--
            <input type="password" class="form-control" required id="password" placeholder="密码">
            -->
            <!--如果用form表单还想发送ajax请求,必须把使用input标签type为button的按钮,input标签type为submit和button标签都会触发form的action操作-->
            <a href="/login/" class="pull-right re-login">已有帐号?马上登录</a>
            <input type="button" class="btn btn-lg btn-login btn-block" id="signup-btn" value="提交">
            <!--显示验证验证错误信息-->

        </div>
    </form>
</div>
<!--引用自己写的JS文件-->
<script src="/static/js/owner_js/reg.js"></script>
</body>
</html>
View Code

reg.js(使用ajax发送请求)

$(function () {
    // 点击注册发送ajax请求
    $('#signup-btn').click(function () {
        $.ajax({
            url: '',
            type: 'post',
            data: {
                username: $('#id_username').val(),
                password: $('#id_password').val(),
                confirm_password: $('#id_confirm_password').val(),
                email: $('#id_email').val(),
                telephone: $('#id_telephone').val(),
                gender: $('#id_gender').val(),
                csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val()
            },
            success: function (response) {
                if (response.user) {
                    alert('注册成功!');
                    location.href = '/login/'
                } else {
                    // 渲染报错信息
                    $.each(response.error_msg, function (i, j) {
                        $('#id_' + i).next().html(j[0]).css('color', 'red').parent().addClass('has-error')
                    })
                }
            }
        })
    });
    // input获得鼠标焦点,移除校验错误
    $('.login-wrap input').focus(function () {
        $(this).next().html('').parent().removeClass('has-error')
    });
    // select元素发生改变,移除校验错误
    $('.login-wrap select').change(function () {
        $(this).next().html('').parent().removeClass('has-error')
    })
});
View Code

Form类

创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;

1、Django内置字段如下:

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    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类型
    ...

注:UUID是根据MAC以及当前时间等创建的不重复的随机字符串

 

>>> import uuid

    # make a UUID based on the host ID and current time
    >>> uuid.uuid1()    # doctest: +SKIP
    UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')

    # make a UUID using an MD5 hash of a namespace UUID and a name
    >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
    UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')

    # make a random UUID
    >>> uuid.uuid4()    # doctest: +SKIP
    UUID('16fd2706-8baf-433b-82eb-8c7fada847da')

    # make a UUID using a SHA-1 hash of a namespace UUID and a name
    >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
    UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')

    # make a UUID from a string of hex digits (braces and hyphens ignored)
    >>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')

    # convert a UUID to a string of hex digits in standard form
    >>> str(x)
    '00010203-0405-0607-0809-0a0b0c0d0e0f'

    # get the raw 16 bytes of the UUID
    >>> x.bytes
    b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'

    # make a UUID from a 16-byte string
    >>> uuid.UUID(bytes=x.bytes)
    UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')
View Code

2、Django内置插件:

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
View Code

常用选择插件

# 单radio,值为字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
 
# 单radio,值为字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.RadioSelect
# )
 
# 单select,值为字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
 
# 单select,值为字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.Select
# )
 
# 多选select,值为列表
# user = fields.MultipleChoiceField(
#     choices=((1,'上海'),(2,'北京'),),
#     initial=[1,],
#     widget=widgets.SelectMultiple
# )
 
 
# 单checkbox
# user = fields.CharField(
#     widget=widgets.CheckboxInput()
# )
 
 
# 多选checkbox,值为列表
# user = fields.MultipleChoiceField(
#     initial=[2, ],
#     choices=((1, '上海'), (2, '北京'),),
#     widget=widgets.CheckboxSelectMultiple
# )

 

posted on 2018-11-02 19:28  Louiszj  阅读(146)  评论(0)    收藏  举报

导航