Django的Form校验组件—简单介绍及一个实例
Form组件简介
现实需求
我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。
与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确,如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息。
而且用户不希望页面在他输错的时候刷新清空掉他之前写好的内容,他只需要改一下输入错误的地方就可以了。
Form组件的功能
生成HTML标签 --> 前端页面是form类的对象生成的
用户提交校验功能 --> 当用户输入有误时,页面都会异步提示出错误信息
保留上次输入内容 --> 当用户输错之后不会清空之前输入的内容,上次的内容还保留在input框里
Form组件的一个简单事例
新建一个Django项目FormTest
__init__.py中数据库的配置、静态文件等的配置略~
数据库的连接配置如下:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql',#引擎,选mysql 'NAME':'formtest',#要连接的数据库,连接前需要创建好 'USER':'root',#连接数据库的用户名 'PASSWORD':'123',#连接数据库的密码 'HOST':'127.0.0.1',#连接主机,默认本本机 'PORT':3306,#端口 默认3306 #Django中设置数据库的严格模式 'OPTIONS':{ 'init_command':"set sql_mode='STRICT_TRANS_TABLES' ", } } }
新建一个应用myform
记得在settings中注册一下myform应用
myform应用里models.py文件创建三张表:
UserInfo、City跟Hobby:用户表跟City是多对一的关系,跟Hobby表是多对多的关系:
from django.db import models class UserInfo(models.Model): name = models.CharField(max_length=10) password = models.CharField(max_length=20) birthday = models.DateField() email = models.EmailField() phone = models.CharField(max_length=11) #choices~性别 GENDER_CHOICE = ( ('m','male'), ('f','female'), ) gender = models.CharField(max_length=2,choices=GENDER_CHOICE,default='m') #多对多 hobbies = models.ManyToManyField(to='Hobby') #多对一 city = models.ForeignKey(to='City',to_field='id',on_delete=models.CASCADE) def __str__(self): return self.name class City(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=20) def __str__(self): return self.name class Hobby(models.Model): name = models.CharField(max_length=20) def __str__(self): return self.name
最后执行makemigrations与migrate命令在数据库中创建表。
创建Form校验规则
在项目中新建一个core包~专门用来存放各种逻辑。core包中新建一个myform.py文件~这里写我们的检验类MyForm:
# -*- coding:utf-8 -*- from django import forms from django.forms import widgets from django.core.validators import RegexValidator from django.core.exceptions import ValidationError, NON_FIELD_ERRORS from myform import models # 做校验的类 class MyForm(forms.Form): # 检验字段~~校验字段最好跟models中写入数据库的属性一致~ # 后面需要往数据库中写入数据的话比较方便 name = forms.CharField(max_length=10, min_length=3, label='用户名', widget=forms.widgets.TextInput(attrs={'class': 'form-control'}), error_messages={'required': '输入不能为空', 'min_length': '字段长度不能小于3', 'max_length': '字段长度不能超过10'} ) # 针对password~输错的话会清空输入框~在widget设置render_value=True可以不清空 password = forms.CharField(max_length=20, label='密码', widget=widgets.PasswordInput(render_value=True,attrs={'class': 'form-control', 'placeholder': '密码'}), error_messages={'required': '输入不能为空', }, ) # # 针对password~输错的话会清空输入框~在widget设置render_value=True可以不清空 r_password = forms.CharField(max_length=20, label='确认密码', widget=widgets.PasswordInput(render_value=True,attrs={'class': 'form-control', 'placeholder': '密码'}), error_messages={'required': '输入不能为空', } )
# 这里input是text,要用datetype的话,在attrs中加上type:date就可以了 birthday = forms.DateField(label='出生日期', widget=widgets.TextInput(attrs={'class': 'form-control', 'type': 'date', }), error_messages={'required': '输入不能为空', } ) email = forms.EmailField(label='email', widget=widgets.EmailInput(attrs={'class': 'form-control', 'placeholder': '邮箱'}), error_messages={'required': '输入不能为空', 'invalid': '邮箱格式错误'} ) # NumberInput 只能输入数字! phone = forms.CharField(label='联系电话', widget=widgets.NumberInput(attrs={'class': 'form-control', 'placeholder': '联系电话'}), error_messages={'required': '输入不能为空', } ) # 性别写死~单选 gender = forms.ChoiceField( choices=( ('m', 'male'), ('f', 'female'), ), error_messages={'required': '输入不能为空',} ) # 所在城市,从数据库找~且是单选 city = forms.ModelChoiceField(
# 这里必须用all,不能用values_list queryset=models.City.objects.all(), # widget=widgets.Select() 是原生的~可以给里面设置样式 widget=widgets.Select(attrs={'class': 'form-control'}), error_messages={'required': '输入不能为空', } ) # 爱好,从数据库中找~是多选的 hobbies = forms.ModelMultipleChoiceField( queryset=models.Hobby.objects.all(), widget=widgets.SelectMultiple(attrs={'class': 'form-control'}),
error_messages={'required': '输入不能为空', } ) ''' ################ __init__方法 ################ #(1)指定choices值 def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs)
# 单选下拉框与单选radio self.fields['city'].choices = models.City.objects.all().values_list('pk','name') #[(1,'包头')
#(2)批量添加样式 def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs) for field in self.fields: self.fields[field].widget.attrs.update({ 'class': 'form-control' }) ''' #局部钩子 #用户名是否已经存在/不能为纯数字 def clean_name(self): val = self.cleaned_data.get('name') #cleaned_data是一个有序字典 # 数据库中检测 ret = models.UserInfo.objects.filter(name=val) if ret: raise ValidationError('该用户名已经注册了!') elif val.isdigit(): raise ValidationError('用户名不能全为数字') else: return val #手机号必须为11位 def clean_phone(self): val = self.cleaned_data.get('phone') if len(val) == 11: return val else: raise ValidationError('手机号必须为11位') #邮箱已经注册过了 def clean_email(self): val = self.cleaned_data.get('email') # 数据库中检测 ret = models.UserInfo.objects.filter(email=val) if ret: raise ValidationError('这个邮箱已经注册过了!') else: return val # 全局钩子 def clean(self): p1 = self.cleaned_data.get('password') p2 = self.cleaned_data.get('r_password') if p1 == p2: # 将cleaned_data全部返回 return self.cleaned_data else: self.add_error('r_password','两次输入的密码不一致') # 需要注意全局钩子的错误在form_obj对象中~局部获取不到! # raise ValidationError('两次输入的密码不一致!') 这样写的话局部取不到
路由
路由这里我配置了根目录直接进index页面
from django.contrib import admin from django.urls import path,re_path from myform import views urlpatterns = [ path('admin/', admin.site.urls), re_path(r'',views.index), re_path(r'^index/$',views.index,name='index'), ]
视图函数
视图函数需要引入我们上面写的那个校验的类。
针对get请求,用校验的类实例化的对象去渲染index页面~
针对post请求,校验的类用提交上来的数据去实例化出一个对象,然后根据校验的结果去进行不同的处理~
需要注意的是:如果校验成功后需要将数据写入数据库的话~校验类的字段最好跟models文件中类的字段一致~这样的话可以进行批量处理了
from django.shortcuts import render,HttpResponse from myform import models from core.myform import MyForm def index(request): if request.method == 'GET': # 实例化一个MyForm类的对象~并把它传给前端页面~ form_obj = MyForm() return render(request,'index.html',{'form_obj':form_obj}) elif request.method == 'POST': data = request.POST # MyForm利用request.POST的数据进行实例化 form_obj = MyForm(data) # 验证每个字段传过来的数据是不是正确的 if form_obj.is_valid(): data = form_obj.cleaned_data #cleaned_data得到的是一个有序字典 print(data) #有序字典~hobbies的值是一个QuerySet对象 # 人跟爱好是多对多的关系~有第三张表~因此拿出来单独处理 hobbies_data = data.pop('hobbies') # print(hobbies_data) #Queryset对象 # 往数据库中写入数据 # 注意写数据前把数据库中没有的属性去掉~~这里是r_password data.pop('r_password') user_obj = models.UserInfo.objects.create(**data) # 多对多添加数据 user_obj.hobbies.add(*hobbies_data) return HttpResponse('OK') else: print(form_obj.errors) # 注意 还得用form_obj渲染一下index页面!否则用户之前输入的都没了~ return render(request,'index.html',{'form_obj':form_obj})
index页面
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Index</title> <link rel="icon" href="{% static 'whw.ico' %}"> <link rel="stylesheet" href="{% static 'bootstrap-3.3.7/css/bootstrap.css' %}"> </head> <body> <div class="container-fluid"> <div class="row"> <div class="col-md-3 col-md-offset-3"> <form action="{% url 'index' %}" method="post" novalidate> {% csrf_token %} {# 全局钩子的错误在form_obj对象的errors中 #} {# {{ form_obj.errors }}#} {# 模板中利用for渲染页面~~也可以单个添加 #} {% for field in form_obj %} {# 如果有错误的话 这个div的边框标红 #} <div class="form-group {% if field.errors.0 %} has-error {% endif %} "> <label for="{{ field.id_for_label }}">{{ field.label }}</label> {{ field }} <span class="text-success">{{ field.help_text }}</span> <span class="text-danger">{{ field.errors.0 }}</span> </div> {% endfor %} <div class="form-group"> <input type="submit" class="pull-right btn btn-success" value="注册"> </div> </form> </div> </div> </div> <script src="{% static 'jquery-3.4.1.js' %}"></script> <script src="{% static 'bootstrap-3.3.7/js/bootstrap.js' %}"></script> </body> </html>
页面效果如下(这里只截取了一部分):