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
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())
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'), ]
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>
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') }) });

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')
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
常用选择插件
# 单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 # )
浙公网安备 33010602011771号