Django之Form组件
抛砖引玉
相信登录注册什么的大家都很熟悉了,但是现在我们想要的是在前端用户输入注册信息的时候,当他点击提交按钮的那一刻,如果用户输入的用户名或者密码不和规范,直接在当前页面给他一些提示信息,告诉他输入的信息不符合我们网站的要求,成功的话直接让他去执行登录功能。那么对于校验数据,我们需要知道的是,前后端都可以校验数据,前端校验数据只是展示给用户看的一些信息,但是对有一些技术能力的人来说,可以直接朝接口发送请求,绕过前端校验,比如爬虫,这个时候如果说你不做后端数据的校验,你的网站就完蛋了,直接被别人攻破,所以我们后端数据的校验是必须的,具体做法如下:
views展示
def register(request): # print(request.POST) error_msg = {'username':None,'password':None} if request.POST.get('type'): # 基于Ajax实现当前页面跳转 return render(request, 'register.html') if request.method == 'POST': # form表单提交数据的时候 username = request.POST.get('username') password = request.POST.get('password') # print('1111',username) confirm_password = request.POST.get('confirm_password') username:str if len(username)<6 and username.isalnum(): error_msg['username'] = '用户名不能小于6位,必须由字母数字组成!' return render(request, 'register.html', locals()) if confirm_password != password: error_msg['password'] = '两次输入用户密码不一致' return render(request, 'register.html',locals()) else: return redirect(reverse('login')) # 重定向到登录页面 return render(request,'register.html',locals()) # 一个视图函数必须要有一个HttpResponse,实际上没走
Ajax展示(home.html)
register.html展示
<div class="container"> <div class="row"> <div class="col-md-6 col-xs-offset-1"> <form action="{% url 'register' %}" method="post"> <p>username:<input type="text" class="form-control" name="username"></p> <span>{{ error_msg.username }}</span> <p>password:<input type="password" class="form-control" name="password"></p> <p>confirm_password:<input type="password" class="form-control" name="confirm_password"></p> <span>{{ error_msg.password }}</span> <input type="submit"> </form> </div> </div> </div>
如果我们每次都要去做,发现我们要做的实在是太多了,会写一大堆的逻辑判断,我们上面仅仅只是特别简单的判断了一下用户名和密码,如果我们后面还要判断别的东西的话,就会特别麻烦,django替我们做了这些事,我们只需要导入使用就好,这就是form组件
Form介绍
我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。
与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息.。
Django form组件就实现了上面所述的功能。
总结一下,其实form组件的主要功能如下:
- 生成页面可用的HTML标签 >>>渲染标签
- 对用户提交的数据进行校验 >>>校验数据
- 校验未通过展示上次输入内容 >>>展示信息
校验数据
先导入模块
from django import forms
然后我们需要写一个类来继承这个Form
class MyForm(forms.Form): name = forms.CharField(max_length = 6) password = forms.CharField(max_length = 8,min_length = 3) email = forms.EmailField()
测试方式分为两种
第一种:test.py 从manage.py导入
import os if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day61.settings") import django django.setup()
第二种
左下角的python console中进行测试
对应步骤分别是
0.先导入模块
from app01 import views
1.实例化一个对象
form_obj = views.MyForm({'name':'egon','password':'223','email':'234'})
2.判断输入数据合不合法
form_obj.is_valid() #这个只有在全部都正确的情况下才会显示True
3.错误信息的存放和具体错误信息展示
form_obj.errors # 错误信息展示 {'password': ['Ensure this value has at least 5 characters (it has 3).'], 'email': ['Enter a valid email address.']}
4.合法信息的存放
form_obj.cleaned_data #合法信息展示 {'name': 'egon'}
ps:
form组件校验数据的规则是从上往下依次取值校验
校验通过的放到cleaded_data里面
校验失败的放到errors里面
疑问?????
如果说我多传几个字段呢?
form_obj = views.MyForm({'name':'egon','password':'123','email':'234','hobby':'read'}) form_obj.is_valid() False form_obj.cleaned_data {'name': 'egon'} form_obj.errors {'password': ['Ensure this value has at least 5 characters (it has 3).'], 'email': ['Enter a valid email address.']}
我们发现,多传字段他不会做任何的校验,相当于直接扔掉
那少传字段呢?
from app01 import views form_obj = views.MyForm({'name':'egon','password':'123'}) form_obj.is_valid() False form_obj.cleaned_data {'name': 'egon'} form_obj.errors {'password': ['Ensure this value has at least 5 characters (it has 3).'], 'email': ['This field is required.']}
在form表单中require字段默认是True,如果我们要是不想传这个字段,可以修改为False,就不用传参了
总结:
注意:form中所有字段默认都是必须传值的(required=True)
校验数据的时候可以多传,多传的数据不会做任何的校验---不会影响到form校验规则
渲染标签
form组件只帮你渲染获取用户输入的标签,不会帮你渲染提交按钮,需要手动添加
第一种(可拓展性太差)
<body> <h1>第一种渲染方式</h1> {{ form_obj.as_p }}
{{ form_obj.as_ul }} <!--这是列表的样式-->
<input type="submit"> </body>
第二种
<h1>这是第二种渲染方式</h1> <form action="" method="post"> <P>{{ form_obj.name.label }}{{ form_obj.name }}</P> <P>{{ form_obj.password.label }}{{ form_obj.password }}</P> <P>{{ form_obj.email.label }}{{ form_obj.email }}</P> <input type="submit"> </form>
第三种
<h1>这是第三种渲染方式</h1> <form action="" method="post"> {% for foo in form_obj %} <p> {{ foo.label }}{{ foo }} </p> {% endfor %} <input type="submit"> </form> </body> </html>
这三种方式,我们推荐使用第三种方式,前面两种方式的可拓展性太差了,而且第三种灵活
前端取消校验
前端可以帮我们校验,我们后端也会校验,就可以直接将前端的校验取消
<form action="" method="post" novalidate> </form>
效果展示
我们自己纯手动校验数据的时候,当数据输入不合法的时候还是会刷新,如果你的网站有好多好多个标签,一刷新全没了,用户此时估计重写的话估计早炸了,那肯定不合理,用户输入应该哪里不合理给他一个提示才对,而且应该保证页面不刷新才对! 然后form组件在提交数据的时候如果说数据不合法,页面上会保留之前用户输入的信息(感觉就是天使派来拯救我们的,是不是🤭🤭🤭)。
还有就是我们在使用form组件对模型表进行数据校验的时候,只需要保证字段一致,那么后面我们在创建对象的时候就可以直接**form_obj.cleaned_data
form组件实现注册
views代码展示
def reg(request): form_obj = MyForm() # 生成一个空对象 if request.method == 'POST': form_obj = MyForm(request.POST) #(里面要求传的是具有一一对应关系的,reques.POST刚好是字典) if form_obj.is_valid(): models.User.objects.create(**form_obj.cleaned_data) # 字段数据一致可以打散传值,多余字段form组件直接过滤掉了 return render(request,'reg.html',locals())
html页面展示
<h1>这是第三种渲染方式</h1> <form action="" method="post" novalidate> {% for foo in form_obj %} <p> {{ foo.label }}{{ foo }} <span>{{ foo.errors.0 }}</span> <!--这里写0的原因是错误信息是列表,不好看--> </p> {% endfor %} <input type="submit"> </form> </body> </html>
钩子函数
在实现了简易版的注册之后,我们发现还存在着诸多的不合理之处,比如说我们只是让用户输入了依次密码,没有对密码进行二次校验,那我们要如何实现两个密码之间的二次校验,就是两个字段之间的校验呢?这就用到了钩子函数。在使用钩子函数的时候我们一定要知道的是form组件执行代码的顺序是从上往下依次执行,只要中间出错了,下面的代码就不会走了
局部钩子函数
局部钩子函数针对的是单个字段的校验,比如一些敏感词汇等等。这个函数是放在对应的form组件那个类里面的,用到的是clean_字段名
class MyForm(forms.Form): # 写一个类继承这个模块,和模型层中类似 name = forms.CharField(max_length=6) # 这里的参数指的是用户名最长位数 password = forms.CharField(max_length=8,min_length=5) # 这两个参数分别是最长密码数和最小密码数 email = forms.EmailField() def clean_name(self): # 能走到这里说明是name字段的规则都是通过的 name = self.cleaned_data.get('name') if '666' in name: self.add_error('name','我们不要只做一个路边喊口号的人,要有真实力') return name # 返回主要是你从合法信息中拿走,就得给人家还回去
全局钩子函数
全局钩子函数针对的是多个字段的检验,用到的是clean
class MyForm(forms.Form): # 写一个类继承这个模块,和模型层中类似 name = forms.CharField(max_length=6) # 这里的参数指的是用户名最长位数 password = forms.CharField(max_length=8, min_length=5) # 这两个参数分别是最长密码数和最小密码数 confirm_password = forms.CharField(max_length=8, min_length=5) email = forms.EmailField() def clean_name(self): # 能走到这里说明是name字段的规则都是通过的 name = self.cleaned_data.get('name') if '666' in name: self.add_error('name','我们不要只做一个路边喊口号的人,要有真实力') return name # 返回主要是你从合法信息中拿走,就得给人家还回去 def clean(self): password = self.cleaned_data.get('password') confirm_password = self.cleaned_data.get('confirm_password') if password != confirm_password: self.add_error('confirm_password', '两次输入密码不一致,你这个大傻瓜!') return self.cleaned_data
下面我们输入用户名合法然后密码不一致来看
我们在试试在所有的规则都符合然后看敏感词汇的显示
我们发现,当每次输入的时候如果输入不合法就给你报错误信息,但是都是英文,不太合理吧,还有就是像什么密码这些不应该都是密码的吗,直接明文肯定不合理,那我们就要对标签的样式进行设置了
设置标签样式
创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;
initial
初始值,input框里面的初始值。
class MyForm(forms.Form): name = forms.CharField( min_length=6, label='用户名', initial='cc' ) password = forms.CharField( max_length=8, min_length=5, label='密码' ) confirm_password = forms.CharField( max_length=8, min_length=5, label='再次输入密码' ) email = forms.EmailField( initial='123@qq.com', label='邮箱' )
error_messages
重写错误信息。
class MyForm(forms.Form): name = forms.CharField( min_length=6, label='用户名', initial='cc', error_messages={ 'required': '输入不可以为空', 'invalid': '格式错误', 'min_length': '用户名最短为6位' } ) password = forms.CharField( max_length=8, min_length=5, label='密码' )
password
class MyForm(forms.Form): name = forms.CharField( min_length=6, label='用户名', initial='cc', error_messages={ 'required': '输入不可以为空', 'invalid': '格式错误', 'min_length': '用户名最短为6位' } ) password = forms.CharField( max_length=8, min_length=5, label='密码', widget=widgets.PasswordInput(attrs={'class':'c1'})
radioSelect
单radio值为字符串
class MyForm(forms.Form): gender = forms.fields.ChoiceField( choices=((1, '男'), (2, '女'), (3, '保密')), label='性别', initial=3, widget=widgets.RadioSelect() )
单选Select
class MyForm(forms.Form): hobby = forms.ChoiceField( choices=((1, 'basketball'), (2, 'soccer ball'), (3, 'baseball')), label='爱好', initial=[1, 3], widget=widgets.Select() )
多选Select
hobby2 = forms.ChoiceField( choices=((1, 'basketball'), (2, 'soccer ball'), (3, 'baseball')), label='爱好', initial=[1, 3], widget=widgets.SelectMultiple()
)
单选checkbox
keep = forms.ChoiceField( label='remember me', initial='checked', widget=widgets.CheckboxInput() )
多选checkbox
hobby2 = forms.MultipleChoiceField( choices=((1, 'basketball'), (2, 'soccer ball'), (3, 'baseball')), label='爱好', initial=[1,3], widget=widgets.CheckboxSelectMultiple() )
通过上面,我们发现ChoiceField对象的属性中widget默认是Select,CharField对象的widget默认是input,widget是用来指定属性的,也可以对他进行属性的设置,第一种渲染标签的样式默认是table。
choice字段注意事项
在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
方式一:
from django.forms import Form from django.forms import widgets from django.forms import fields class MyForm(Form): user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.Select ) def __init__(self, *args, **kwargs): super(MyForm,self).__init__(*args, **kwargs) # self.fields['user'].choices = ((1, '上海'), (2, '北京'),) # 或 self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
方式二:
from django import forms from django.forms import fields from django.forms import models as form_model class FInfo(forms.Form): authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选 # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 单选
Django Form所有内置字段
Field required=True, 是否允许为空 widget=None, HTML插件 label=None, 用于生成Label标签或显示内容 initial=None, 初始值 help_text='', 帮助信息(在标签旁边显示) error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'} 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类型 Django Form内置字段
字段校验
RegexValidator验证器
from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.validators import RegexValidator class MyForm(Form): user = fields.CharField( validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], )
自定义验证函数
import re from django.forms import Form from django.forms import widgets from django.forms import fields from django.core.exceptions import ValidationError # 自定义验证规则 def mobile_validate(value): mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') if not mobile_re.match(value): raise ValidationError('手机号码格式错误') class PublishForm(Form): title = fields.CharField(max_length=20, min_length=5, error_messages={'required': '标题不能为空', 'min_length': '标题最少为5个字符', 'max_length': '标题最多为20个字符'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': '标题5-20个字符'})) # 使用自定义验证规则 phone = fields.CharField(validators=[mobile_validate, ], error_messages={'required': '手机不能为空'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'手机号码'})) email = fields.EmailField(required=False, error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'}, widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
补充进阶
应用Bootstrap样式
<!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="/static/bootstrap/css/bootstrap.min.css"> <title>login</title> </head> <body> <div class="container"> <div class="row"> <form action="/login2/" method="post" novalidate class="form-horizontal"> {% csrf_token %} <div class="form-group"> <label for="{{ form_obj.username.id_for_label }}" class="col-md-2 control-label">{{ form_obj.username.label }}</label> <div class="col-md-10"> {{ form_obj.username }} <span class="help-block">{{ form_obj.username.errors.0 }}</span> </div> </div> <div class="form-group"> <label for="{{ form_obj.pwd.id_for_label }}" class="col-md-2 control-label">{{ form_obj.pwd.label }}</label> <div class="col-md-10"> {{ form_obj.pwd }} <span class="help-block">{{ form_obj.pwd.errors.0 }}</span> </div> </div> <div class="form-group"> <label class="col-md-2 control-label">{{ form_obj.gender.label }}</label> <div class="col-md-10"> <div class="radio"> {% for radio in form_obj.gender %} <label for="{{ radio.id_for_label }}"> {{ radio.tag }}{{ radio.choice_label }} </label> {% endfor %} </div> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <button type="submit" class="btn btn-default">注册</button> </div> </div> </form> </div> </div> <script src="/static/jquery-3.2.1.min.js"></script> <script src="/static/bootstrap/js/bootstrap.min.js"></script> </body> </html> Django form应用Bootstrap样式简单示例
批量添加样式
可通过重写form类的init方法来实现。
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三", error_messages={ "required": "不能为空", "invalid": "格式错误", "min_length": "用户名最短8位" } ... def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) for field in iter(self.fields): self.fields[field].widget.attrs.update({ 'class': 'form-control' }) 批量添加样式
ModelForm
通常在Django项目中,我们编写的大部分都是与Django 的模型紧密映射的表单。 举个例子,你也许会有个Book 模型,并且你还想创建一个form表单用来添加和编辑书籍信息到这个模型中。 在这种情况下,在form表单中定义字段将是冗余的,因为我们已经在模型中定义了那些字段。
基于这个原因,Django 提供一个辅助类来让我们可以从Django 的模型创建Form,这就是ModelForm。
modelForm定义
form与model的终极结合。
class BookForm(forms.ModelForm): class Meta: model = models.Book fields = "__all__" labels = { "title": "书名", "price": "价格" } widgets = { "password": forms.widgets.PasswordInput(attrs={"class": "c1"}), }
class Meta下常用参数:
model = models.Book # 对应的Model中的类 fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段 exclude = None # 排除的字段 labels = None # 提示信息 help_texts = None # 帮助提示信息 widgets = None # 自定义插件 error_messages = None # 自定义错误信息
ModelForm的验证
与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid() 或访问errors 属性时隐式调用。
我们可以像使用Form类一样自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。
如果我们不重写具体字段并设置validators属性的化,ModelForm是按照模型中字段的validators来校验的。
save()方法
每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例:
>>> from myapp.models import Book >>> from myapp.forms import BookForm # 根据POST数据创建一个新的form对象 >>> form_obj = BookForm(request.POST) # 创建书籍对象 >>> new_ book = form_obj.save() # 基于一个书籍对象创建form对象 >>> edit_obj = Book.objects.get(id=1) # 使用POST提交的数据更新书籍对象 >>> form_obj = BookForm(request.POST, instance=edit_obj) >>> form_obj.save()
是不是你又来看我博客!