django基础之Django表单
表单概述
HTML中的表单
单纯从前端的html
来说,表单是用来提交数据给服务器的,不管后台的服务器用的是Django
还是PHP
语言还是其他语言。只要把input
标签放在form
标签中,然后再添加一个提交按钮,那么以后点击提交按钮,就可以将input
标签中对应的值提交给服务器了。
Django中的表单
Django
中的表单丰富了传统的HTML
语言中的表单。在Django
中的表单,主要做以下两件事:
1. 渲染表单模板
2. 表单验证数据是否合法
Django中表单使用流程
在讲解Django
表单的具体每部分的细节之前。我们首先先来看下整体的使用流程。这里以一个做一个留言板为例。首先我们在后台服务器定义一个表单类,继承自django.forms.Form
。示例代码如下:
# forms.py class MessageBoardForm(forms.Form): title = forms.CharField(max_length=3,label='标题',min_length=2,error_messages={"min_length":'标题字符段不符合要求!'}) content = forms.CharField(widget=forms.Textarea,label='内容') email = forms.EmailField(label='邮箱') reply = forms.BooleanField(required=False,label='回复')
然后在视图中,根据是GET
还是POST
请求来做相应的操作。如果是GET
请求,那么返回一个空的表单,如果是POST
请求,那么将提交上来的数据进行校验。示例代码如下:
# views.py class IndexView(View): def get(self,request): form = MessageBoardForm() return render(request,'index.html',{'form':form}) def post(self,request): form = MessageBoardForm(request.POST) if form.is_valid(): title = form.cleaned_data.get('title') content = form.cleaned_data.get('content') email = form.cleaned_data.get('email') reply = form.cleaned_data.get('reply') return HttpResponse('success') else: print(form.errors) return HttpResponse('fail')
在使用GET
请求的时候,我们传了一个form
给模板,那么以后模板就可以使用form
来生成一个表单的html
代码。在使用POST
请求的时候,我们根据前端上传上来的数据,构建一个新的表单,这个表单是用来验证数据是否合法的,如果数据都验证通过了,那么我们可以通过cleaned_data
来获取相应的数据。在模板中渲染表单的HTML
代码如下:
<form action="" method="post" novalidate> {# <p>第一种渲染方式: 代码书写极少, 封装程度太高, 不便于扩展, 一般情况下只在本地测试使用</p>#} {# {{ form.as_p }}#} {# {{ form.as_ul }}#} {# {{ form.as_table }}#} {# <p>第二种渲染方式: 可扩展性很强, 但是书写的代码太多, 一般情况下不使用</p>#} {# <p>{{ form.username.label }}: {{ form.username }}</p>#} {# <p>{{ form.password.label }}: {{ form.password }}</p>#} {# <p>{{ form.email.label }}: {{ form.email }}</p>#} <p>第三种渲染方式: 推荐使用, 代码书写简单, 并且扩展性也高</p> {% for form_obj in form %} <p>{{ form_obj.label }}: {{ form_obj }}</p> <span style="color: red">{{ form_obj.errors.0}}</span> {% endfor %} <button style="color: red;">提交</button> </form>
Form表单的基本使用
Form表单的内置属性与方法
from app import forms # 1. 将带校验的数据组成成字典的形式传入即可 form_obj = forms.MyForm({'username':'json', 'password':'123', 'email':'123'}) # 2. 判断数据是否合法, 注意该方法只有在所有的数据全部合法的情况下才返回True form_obj.is_valid() False # 3. 查看所有校验通过的数据 form_obj.cleaned_data {'username': 'json', 'password': '123'} # 4. 查看所有不符合校验规则的字段以及不符合的原因 form_obj.errors {'email': ['Enter a valid email address.']} # 5. 校验数据只校验类中出现的字段, 多传不影响, 多传的字段直接忽略 form_obj = forms.MyForm({'username':'json', 'password':'123', 'email':'123@qq.com', 'hobby': 'read'}) form_obj.is_valid() True # 6. 校验数据默认情况下, 类里面所有的字段都必须传值 form_obj = forms.MyForm({'username':'json', 'password':'123'}) form_obj.is_valid() False """ 校验数据的时候, 默认情况下数据可以多传但是绝不能少传 """
渲染标签
""" forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox), 不会帮你渲染提交按钮 """ def index(request): # 1. 先生成一个空对象 form_obj = MyForm() # 2. 直接将该空对象传递给html页面 return render(request, 'app02/index.html', locals()) <form action="" method="post"> {# <p>第一种渲染方式: 代码书写极少, 封装程度太高, 不便于扩展, 一般情况下只在本地测试使用</p>#} {# {{ form_obj.as_p }}#} {# {{ form_obj.as_ul }}#} {# {{ form_obj.as_table }}#} {# <p>第二种渲染方式: 可扩展性很强, 但是书写的代码太多, 一般情况下不使用</p>#} {# <p>{{ form_obj.username.label }}: {{ form_obj.username }}</p>#} {# <p>{{ form_obj.password.label }}: {{ form_obj.password }}</p>#} {# <p>{{ form_obj.email.label }}: {{ form_obj.email }}</p>#} <p>第三种渲染方式: 推荐使用, 代码书写简单, 并且扩展性也高</p> {% for form in form_obj %} <p>{{ form.label }}: {{ form }}</p> {% endfor %} </form> """ lable属性默认展示的是类中定义的字段的首字母大写的形式, 也可以进行修改, 直接给字段对象家lable属性即可 username = forms.CharField(min_length=3, max_length=8, label='用户名') """
展示提示信息
""" 浏览器会自动帮你校验数据, 但是前端的校验弱不禁风, 如何让浏览器不做校验 <form action="" method="post" novalidate> """ class MyForm(forms.Form): # username字符串类型最少3位, 最多8位 username = forms.CharField(min_length=3, max_length=8, label='用户名', error_messages={ 'min_length': '用户名最少3位', 'max_length': '用户名最多8位', 'required': '用户名不能为空' }) # username字符串类型最少3位, 最多8位 password = forms.CharField(min_length=3, max_length=8, label='密码', error_messages={ 'min_length': '密码最少3位', 'max_length': '密码最多8位', 'required': '密码不能为空' }) # email字段必须符合邮箱格式 xxx@xxx.com email = forms.EmailField(label='邮箱', error_messages={ 'invalid': '邮箱格式不正确', 'required': '邮箱不能为空' }) {% for form in form_obj %} <p>{{ form.label }}: {{ form }}</p> <span style="color: red">{{ form.errors.0}}</span> {% endfor %}
Form常用字段与插件
创建Form类时,主要涉及到 【字段】 和 【插件】,字段用于对用户请求数据的验证,插件用于自动生成HTML;
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内置字段
Django Form常用字段
initial
初始值,input框里面的初始值
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三" # 设置默认值 ) pwd = forms.CharField(min_length=6, label="密码")
error_messages
重写错误信息
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三", error_messages={ "required": "不能为空", "invalid": "格式错误", "min_length": "用户名最短8位" } ) pwd = forms.CharField(min_length=6, label="密码")
password
class LoginForm(forms.Form): ... pwd = forms.CharField( min_length=6, label="密码", widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) )
radioSelect
单radio值为字符串
class LoginForm(forms.Form): username = forms.CharField( min_length=8, label="用户名", initial="张三", error_messages={ "required": "不能为空", "invalid": "格式错误", "min_length": "用户名最短8位" } ) pwd = forms.CharField(min_length=6, label="密码") gender = forms.fields.ChoiceField( choices=((1, "男"), (2, "女"), (3, "保密")), label="性别", initial=3, widget=forms.widgets.RadioSelect() )
单选Select
class LoginForm(forms.Form): ... hobby = forms.ChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=3, widget=forms.widgets.Select() )
多选Select
class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=[1, 3], widget=forms.widgets.SelectMultiple() )
单选checkbox
class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
多选checkbox
class LoginForm(forms.Form): ... hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"),), label="爱好", initial=[1, 3], widget=forms.widgets.CheckboxSelectMultiple() )
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()) # 单选
Form表单验证数据
常用验证器
在验证某个字段的时候,可以传递一个validators
参数用来指定验证器,进一步对数据进行过滤。验证器有很多,但是很多验证器我们其实已经通过这个Field
或者一些参数就可以指定了。比如EmailValidator
,我们可以通过EmailField
来指定,比如MaxValueValidator
,我们可以通过max_value
参数来指定。以下是一些常用的验证器:
1. MaxValueValidator
:验证最大值。
2. MinValueValidator
:验证最小值。
3. MinLengthValidator
:验证最小长度。
4. MaxLengthValidator
:验证最大长度。
5. EmailValidator
:验证是否是邮箱格式。
6. URLValidator
:验证是否是URL
格式。
7. RegexValidator
:如果还需要更加复杂的验证,那么我们可以通过正则表达式的验证器:RegexValidator
。比如现在要验证手机号码是否合格,那么我们可以通过以下代码实现:
class MyForm(forms.Form): telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='请输入正确格式的手机号码!')])
自定义校验函数
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'邮箱'}))
钩子函数
钩子函数即在特定的节点自动触发完成响应操作, 钩子函数在forms组件中就类似于第二道关卡, 能够让我们自定义校验规则
在forms组件中有两类钩子
1. 局部钩子: 当你需要给单个字段增加校验规则的时候可以使用
2. 全局钩子: 当你需要给多个字段增加校验规则的时候可以使用
局部钩子函数
有时候对一个字段验证,不是一个长度,一个正则表达式能够写清楚的,还需要一些其他复杂的逻辑,那么我们可以对某个字段,进行自定义的验证。比如在注册的表单验证中,我们想要验证手机号码是否已经被注册过了,那么这时候就需要在数据库中进行判断才知道。对某个字段进行自定义的验证方式是,定义一个方法,这个方法的名字定义规则是:clean_fieldname
。如果验证失败,那么就抛出一个验证错误。比如要验证用户表中手机号码之前是否在数据库中存在,那么可以通过以下代码实现:
class MyForm(forms.Form): telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='请输入正确格式的手机号码!')]) def clean_telephone(self): telephone = self.cleaned_data.get('telephone') exists = User.objects.filter(telephone=telephone).exists() if exists: raise forms.ValidationError("手机号码已经存在!") return telephone
全局钩子函数
如果验证数据的时候,需要针对多个字段进行验证,那么可以重写clean
方法。比如要在注册的时候,要判断提交的两个密码是否相等。那么可以使用以下代码来完成:
class MyForm(forms.Form): telephone = forms.CharField(validators=[validators.RegexValidator("1[345678]\d{9}",message='请输入正确格式的手机号码!')]) pwd1 = forms.CharField(max_length=12) pwd2 = forms.CharField(max_length=12) def clean(self): cleaned_data = super().clean() pwd1 = cleaned_data.get('pwd1') pwd2 = cleaned_data.get('pwd2') if pwd1 != pwd2: raise forms.ValidationError('两个密码不一致!')
提取错误信息
如果验证失败了,那么有一些错误信息是我们需要传给前端的。这时候我们可以通过以下属性来获取:
1. form.errors
:这个属性获取的错误信息是一个包含了html
标签的错误信息。
2. form.errors.get_json_data()
:这个方法获取到的是一个字典类型的错误信息。将某个字段的名字作为key
,错误信息作为值的一个字典。
3. form.as_json()
:这个方法是将form.get_json_data()
返回的字典dump
成json
格式的字符串,方便进行传输。
4. 上述方法获取的字段的错误值,都是一个比较复杂的数据。比如以下:
{'username': [{'message': 'Enter a valid URL.', 'code': 'invalid'}, {'message': 'Ensure this value'}]
那么如果我只想把错误信息放在一个列表中,而不要再放在一个字典中。这时候我们可以定义一个方法,把这个数据重新整理一份。实例代码如下:
class MyForm(forms.Form): username = forms.URLField(max_length=4) def get_errors(self): errors = self.errors.get_json_data() new_errors = {} for key,message_dicts in errors.items(): messages = [] for message in message_dicts: messages.append(message['message']) new_errors[key] = messages return new_errors
这样就可以把某个字段所有的错误信息直接放在这个列表中
ModelForm
在写表单的时候,会发现表单中的Field
和模型中的Field
基本上是一模一样的,而且表单中需要验证的数据,也就是我们模型中需要保存的。那么这时候我们就可以将模型中的字段和表单中的字段进行绑定。 比如现在有个Article
的模型。示例代码如下:
from django.db import models from django.core import validators class Article(models.Model): title = models.CharField(max_length=10,validators=[validators.MinLengthValidator(limit_value=3)]) content = models.TextField() author = models.CharField(max_length=100) category = models.CharField(max_length=100) create_time = models.DateTimeField(auto_now_add=True)
那么在写表单的时候,就不需要把Article
模型中所有的字段都一个个重复写一遍了。示例代码如下
from django import forms class MyForm(forms.ModelForm): class Meta: model = Article fields = "__all__"
MyForm
是继承自forms.ModelForm
,然后在表单中定义了一个Meta
类,在Meta
类中指定了model=Article
,以及fields="__all__"
,这样就可以将Article
模型中所有的字段都复制过来,进行验证。如果只想针对其中几个字段进行验证,那么可以给fields
指定一个列表,将需要的字段写进去。比如只想验证title
和content
,那么可以使用以下代码实现:
from django import forms class MyForm(forms.ModelForm): class Meta: model = Article fields = ['title','content']
如果要验证的字段比较多,只是除了少数几个字段不需要验证,那么可以使用exclude
来代替fields
。比如我不想验证category
,那么示例代码如下:
class MyForm(forms.ModelForm): class Meta: model = Article exclude = ['category']
自定义错误消息
使用ModelForm
,因为字段都不是在表单中定义的,而是在模型中定义的,因此一些错误消息无法在字段中定义。那么这时候可以在Meta
类中,定义error_messages
,然后把相应的错误消息写到里面去。示例代码如下:
class MyForm(forms.ModelForm): class Meta: model = Article exclude = ['category'] error_messages ={ 'title':{ 'max_length': '最多不能超过10个字符!', 'min_length': '最少不能少于3个字符!' }, 'content': { 'required': '必须输入content!', } }
save方法
ModelForm
还有save
方法,可以在验证完成后直接调用save
方法,就可以将这个数据保存到数据库中了。示例代码如下:
form = MyForm(request.POST) if form.is_valid(): form.save() return HttpResponse('succes') else: print(form.get_errors()) return HttpResponse('fail')
这个方法必须要在clean
没有问题后才能使用,如果在clean
之前使用,会抛出异常。另外,我们在调用save
方法的时候,如果传入一个commit=False
,那么只会生成这个模型的对象,而不会把这个对象真正的插入到数据库中。比如表单上验证的字段没有包含模型中所有的字段,这时候就可以先创建对象,再根据填充其他字段,把所有字段的值都补充完成后,再保存到数据库中。示例代码如下
form = MyForm(request.POST) if form.is_valid(): article = form.save(commit=False) article.category = 'Python' article.save() return HttpResponse('succes') else: print(form.get_errors()) return HttpResponse('fail')