- Forms组件
- 钩子函数
- forms组件字段参数
- forms组件字段类型
- 1. BooleanField
- 2. CharField
- 3. ChoiceField
- 4. TypedChoiceField
- 5. DateField
- 6. DateTimeField
- 7. DecimalField
- 8. DurationField
- 9. EmailField
- 10. FileField
- 11. FilePathField
- 12. FloatField
- 13. ImageField
- 14. IntegerField
- 15.JSONField
- 16. GenericIPAddressField
- 17. MultipleChoiceField
- 18. TypedMultipleChoiceField
- 19. NullBooleanField
- 20.RegexField
- 21. SlugField
- 22. TimeField
- 23. URLField
- 24. UUIDField
- 25. ComboField
- 26. MultiValueField
- 27. SplitDateTimeField
- forms组件源码分析
- ModelForm组件
Forms组件
基本使用
- 导入forms组件
- 定义一个类, 并继承Form
- 在类中书写要校验的字段, 字段的属性就是要校验的规则
- 实例化得到一个Form对象, 把要校验的数据传入
- 调用
[form对象].is_valid( )方法进行校验, 校验通过返回True - 校验通过调用
[form对象].cleaned_data获得校验后的数据 - 校验失败调用
[form对象].errors获得错误信息
编写一个校验用户名和密码是否合法的功能
view.py
from django.shortcuts import render, HttpResponsefrom django import formsclass MyForm(forms.Form):# 用户名最少三个字符最多八个字符user = forms.CharField(min_length=3, max_length=8, label='用户名',error_messages={'min_length': '用户名最短3位','max_length': '用户名最长8位','required': '用户名必填'})# 用户名最小不能小于0,最大不能超过150age = forms.IntegerField(min_value=0, max_value=150, label='年龄',error_messages={'min_value': '年龄最小0岁','max_value': '年龄最大150岁','required': '年龄必填'})# 邮箱必须符合邮箱格式(关键符号@)email = forms.EmailField(label='邮箱',error_messages={'invalid': '邮箱格式不正确','required': '邮箱必填'})def index(request):# 1.先产生一个空对象form_obj = MyForm()if request.method == 'POST':# 2.获取用户数据form_obj = MyForm(request.POST)# 3.校验用户数据if form_obj.is_valid():print(form_obj.cleaned_data)return HttpResponse('数据没问题')return render(request, 'index.html', locals())
test.py(测试环境)
from app01 import views# 1.将数据传入实例化对象form_obj = views.MyForm({'user': 'kevin', 'age': 22, 'email': '123'})# 2.查看数据是否合法(全部合法结果才是True)print(form_obj.is_valid())# 3.查看不符合条件的数据及原因print(form_obj.errors)# 4.查看符合条件的数据print(form_obj.cleaned_data)
index.html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="" method="post" novalidate>{% for form in form_obj %}<p>{{ form.label }}{{ form }}<span style="color: red">{{ form.errors.0 }}</span></p>{% endfor %}<input type="submit"></form></body></html>
补充
forms类中所有的字段数据默认都是必填的,可以添加required=False字段就不必填写forms类中额外传入的字段数据不会做任何的校验forms组件只负责渲染获取用户数据的标签,form表单标签和提交按钮需要自己写- 渲染标签中文提示可以使用参数
label指定,不指定默认英文提醒 forms类中填写的校验性参数前端浏览器会识别并添加校验操作,但是前端的校验是可有可无的后端必须要再次校验form表单可以取消浏览器自动添加校验功能的操作novalidate
钩子函数
在Form类中定义钩子函数,来实现自定义的验证功能,
局部钩子
在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。
views.py
from django.shortcuts import render, HttpResponsefrom django import formsclass MyForm(forms.Form):name = forms.CharField(min_length=3, max_length=8, label='用户名',error_messages={'min_length': '用户名至少三位','max_length': '用户名最大八位','required': '用户名不能为空',})# 局部钩子:效验用户名是否存在"""钩子函数是数据经过了字段一层校验之后才会执行"""def clean_name(self): # 自动生成的函数名,专门用于对name字段添加额外的校验规则name_list = ['kevin', 'jerry', 'tom']# 1.先获取用户名name = self.cleaned_data.get('name')# 2.判断用户名是否重复if name in name_list:# 3.提示信息self.add_error('name', '用户已存在')# 4.最后将勾上来的name返回回去return namedef index(request):form_obj = MyForm()if request.method == 'POST':form_obj = MyForm(request.POST)if form_obj.is_valid():return HttpResponse('OK')return render(request, 'index.html', locals())
index.html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="" method="post" novalidate>{% for foo in form_obj %}<p>{{ foo.label }}:{{ foo }}<span style="color: red">{{ foo.errors.0 }}</span></p>{% endfor %}<input type="submit"></form></body></html>
全局钩子
在Fom类中定义 clean()方法,就能够实现对字段进行全局校验
views.py
from django.shortcuts import render, HttpResponsefrom django import formsclass MyForm(forms.Form):password = forms.CharField(min_length=3, max_length=8, label='密码')confirm_password = forms.CharField(min_length=3, max_length=8, label='确认密码')# 全局钩子:校验密码与确认密码是否一致(一次性可以勾多个字段)def clean(self):# 1.获取多个字段数据password = self.cleaned_data.get('password')confirm_password = self.cleaned_data.get('confirm_password')# 2.判断两次密码是否一致if not password == confirm_password:# 3.提示信息self.add_error('confirm_password', '两次密码不一致')# 4.最后将整个数据返回return self.cleaned_datadef index(request):form_obj = MyForm()if request.method == 'POST':form_obj = MyForm(request.POST)if form_obj.is_valid():return HttpResponse('OK')return render(request, 'index.html', locals())
index.html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="" method="post" novalidate>{% for foo in form_obj %}<p>{{ foo.label }}:{{ foo }}<span style="color: red">{{ foo.errors.0 }}</span></p>{% endfor %}<input type="submit"></form></body></html>
forms组件字段参数
| 字段参数 | 说明 |
|---|---|
min_length |
最小长度 |
max_length |
最大长度 |
label |
字段名称 |
error_messages |
错误提示 |
min_value |
最小值 |
max_value |
最大值 |
initial |
默认值 |
validators |
正则校验器 |
widget |
控制渲染出来的标签各项属性 |
help_text |
设置字段的描述文本 |
核心字段参数
required
给字段添加必填属性
initial
class MyForm(forms.Form):username = forms.CharField(min_length=8,label="用户名",initial="kevin" # 设置默认值))
validators
指定一个列表,其中包含了为字段进行验证的函数
class MyForm(forms.Form):phone = forms.CharField(validators=[RegexValidator(r'^[0-9]+$', '请输入数字'),RegexValidator(r'^138[0-9]+$', '数字必须是138开头'),])
widget
指定渲染Widget时使用的widget类,也就是这个form字段在HTML页面中是显示为什么框
class MyForm(forms.Form):password = forms.CharField(widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}))
disabled
设置有该属性的字段在前端页面中将显示为不可编辑状态
forms组件字段类型
1. BooleanField
- 默认的Widget:CheckboxInput
- 空值:False
- 规范化为:Python的True或者False
- 可用的错误信息键:required
2. CharField
- 默认的Widget:TextInput
- 空值:与empty_value给出的任何值。
- 规范化为:一个Unicode 对象。
- 验证
max_length或min_length,如果设置了这两个参数。 否则,所有的输入都是合法的。 - 可用的错误信息键:min_length, max_length, required
有四个可选参数:
- max_length,min_length:设置字符串的最大和最小长度。
- strip:如果True(默认),去除输入的前导和尾随空格。
- empty_value:用来表示“空”的值。 默认为空字符串。
3. ChoiceField
- 默认的Widget:Select
- 空值:’’(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给定的值是否在选项列表中。
- 可用的错误信息键:required, invalid_choice
参数choices:用来作为该字段选项的一个二元组组成的可迭代对象(例如,列表或元组)或者一个可调用对象。格式与用于和ORM模型字段的choices参数相同。
4. TypedChoiceField
像ChoiceField一样,只是还有两个额外的参数:coerce和empty_value。
- 默认的Widget:Select
- 空值:empty_value参数设置的值。
- 规范化为:coerce参数类型的值。
- 验证给定的值在选项列表中存在并且可以被强制转换。
- 可用的错误信息的键:required, invalid_choice
5. DateField
- 默认的Widget:DateInput
- 空值:None
- 规范化为:datetime.date对象。
- 验证给出的值是一个datetime.date、datetime.datetime 或指定日期格式的字符串。
- 错误信息的键:required, invalid
接收一个可选的参数:input_formats。一个格式的列表,用于转换字符串为datetime.date对象。
如果没有提供input_formats,默认的输入格式为:
['%Y-%m-%d', # '2006-10-25''%m/%d/%Y', # '10/25/2006''%m/%d/%y'] # '10/25/06'
另外,如果你在设置中指定USE_L10N=False,以下的格式也将包含在默认的输入格式中:
['%b %d %Y', # 'Oct 25 2006''%b %d, %Y', # 'Oct 25, 2006''%d %b %Y', # '25 Oct 2006''%d %b, %Y', # '25 Oct, 2006''%B %d %Y', # 'October 25 2006''%B %d, %Y', # 'October 25, 2006''%d %B %Y', # '25 October 2006''%d %B, %Y'] # '25 October, 2006'
6. DateTimeField
- 默认的Widget:DateTimeInput
- 空值:None
- 规范化为:Python的datetime.datetime对象。
- 验证给出的值是一个datetime.datetime、datetime.date或指定日期格式的字符串。
- 错误信息的键:required, invalid
接收一个可选的参数:input_formats
如果没有提供input_formats,默认的输入格式为:
['%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59''%Y-%m-%d %H:%M', # '2006-10-25 14:30''%Y-%m-%d', # '2006-10-25''%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59''%m/%d/%Y %H:%M', # '10/25/2006 14:30''%m/%d/%Y', # '10/25/2006''%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59''%m/%d/%y %H:%M', # '10/25/06 14:30''%m/%d/%y'] # '10/25/06'
7. DecimalField
- 默认的Widget:当Field.localize是False时为NumberInput,否则为TextInput。
- 空值:None
- 规范化为:Python decimal对象。
- 验证给定的值为一个十进制数。 忽略前导和尾随的空白。
- 错误信息的键:
max_whole_digits,max_digits,max_decimal_places,max_value, invalid, required,min_value
接收四个可选的参数:
max_value,min_value:允许的值的范围,需要赋值decimal.Decimal对象,不能直接给个整数类型。
max_digits:值允许的最大位数(小数点之前和之后的数字总共的位数,前导的零将被删除)。
decimal_places:允许的最大小数位。
8. DurationField
- 默认的Widget:TextInput
- 空值:None
- 规范化为:Python timedelta。
- 验证给出的值是一个字符串,而且可以转换为timedelta对象。
- 错误信息的键:required, invalid.
9. EmailField
- 默认的Widget:EmailInput
- 空值:’’(一个空字符串)
- 规范化为:Unicode 对象。
- 使用正则表达式验证给出的值是一个合法的邮件地址。
- 错误信息的键:required, invalid
两个可选的参数用于验证,max_length 和min_length。
10. FileField
- 默认的Widget:ClearableFileInput
- 空值:None
- 规范化为:一个UploadedFile对象,它封装文件内容和文件名到一个对象内。
- 验证非空的文件数据已经绑定到表单。
- 错误信息的键:missing, invalid, required, empty, max_length
具有两个可选的参数用于验证:max_length 和 allow_empty_file。
11. FilePathField
- 默认的Widget:Select
- 空值:None
- 规范化为:Unicode 对象。
- 验证选择的选项在选项列表中存在。
- 错误信息的键:required, invalid_choice
这个字段允许从一个特定的目录选择文件。 它有五个额外的参数,其中的path是必须的:
path:要列出的目录的绝对路径。 这个目录必须存在。
recursive:如果为False(默认值),只用直接位于path下的文件或目录作为选项。如果为True,将递归访问这个目录,其内所有的子目录和文件都将作为选项。
match:正则表达模式;只有具有与此表达式匹配的文件名称才被允许作为选项。
allow_files:可选。默认为True。表示是否应该包含指定位置的文件。它和allow_folders必须有一个为True。
allow_folders可选。默认为False。表示是否应该包含指定位置的目录。
12. FloatField
- 默认的Widget:当Field.localize是False时为NumberInput,否则为TextInput。
- 空值:None
- 规范化为:Float 对象。
- 验证给定的值是一个浮点数。
- 错误信息的键:max_value, invalid, required, min_value
接收两个可选的参数用于验证,max_value和min_value,控制允许的值的范围。
13. ImageField
- 默认的Widget:ClearableFileInput
- 空值:None
- 规范化为:一个UploadedFile 象,它封装文件内容和文件名为一个单独的对象。
- 验证文件数据已绑定到表单,并且该文件是Pillow可以解析的图像格式。
- 错误信息的键:missing, invalid, required, empty, invalid_image
使用ImageField需要安装Pillow(pip install pillow)。如果在上传图片时遇到图像损坏错误,通常意味着使用了Pillow不支持的格式。
>>> from PIL import Image>>> from django import forms>>> from django.core.files.uploadedfile import SimpleUploadedFile>>> class ImageForm(forms.Form):... img = forms.ImageField()>>> file_data = {'img': SimpleUploadedFile('test.png', <file data>)}>>> form = ImageForm({}, file_data)# Pillow closes the underlying file descriptor.>>> form.is_valid()True>>> image_field = form.cleaned_data['img']>>> image_field.image<PIL.PngImagePlugin.PngImageFile image mode=RGBA size=191x287 at 0x7F5985045C18>>>> image_field.image.width191>>> image_field.image.height287>>> image_field.image.format'PNG'>>> image_field.image.getdata()# Raises AttributeError: 'NoneType' object has no attribute 'seek'.>>> image = Image.open(image_field)>>> image.getdata()<ImagingCore object at 0x7f5984f874b0>
14. IntegerField
- 默认的Widget:当Field.localize是False时为NumberInput,否则为TextInput。
- 空值:None
- 规范化为:Python 整数或长整数。
- 验证给定值是一个整数。 允许前导和尾随空格,类似Python的int()函数。
- 错误信息的键:max_value, invalid, required, min_value
两个可选参数:max_value和min_value,控制允许的值的范围。
15.JSONField
Django3.1新增。
接收JSON编码的字段。
- 默认的Widget:Textarea
- 空值:None
- 规范化为:一个JSON对象。
- 验证给定值是否合法的JSON。
- 错误信息的键:required, invalid
- 可接受两个参数:encoder和decoder,编码器和解码器
16. GenericIPAddressField
包含IPv4或IPv6地址的字段。
- 默认的Widget:TextInput
- 空值:’’(一个空字符串)
- 规范化为:一个Unicode对象。
- 验证给定值是有效的IP地址。
- 错误信息的键:required, invalid
有两个可选参数:protocol和unpack_ipv4
17. MultipleChoiceField
- 默认的Widget:SelectMultiple
- 空值:[](一个空列表)
- 规范化为:一个Unicode 对象列表。
- 验证给定值列表中的每个值都存在于选择列表中。
- 错误信息的键:invalid_list, invalid_choice, required
18. TypedMultipleChoiceField
类似MultipleChoiceField,除了需要两个额外的参数,coerce和empty_value。
- 默认的Widget:SelectMultiple
- 空值:empty_value
- 规范化为:coerce参数提供的类型值列表。
- 验证给定值存在于选项列表中并且可以强制。
- 错误信息的键:required, invalid_choice
19. NullBooleanField
- 默认的Widget:NullBooleanSelect
- 空值:None
- 规范化为:Python None, False 或True 值。
- 不验证任何内容(即,它从不引发ValidationError)。
20.RegexField
- 默认的Widget:TextInput
- 空值:’’(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给定值与某个正则表达式匹配。
- 错误信息的键:required, invalid
需要一个必需的参数:regex,需要匹配的正则表达式。
还可以接收max_length,min_length和strip参数,类似CharField。
21. SlugField
- 默认的Widget:TextInput
- 空值:’’(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给定的字符串只包括字母、数字、下划线及连字符。
- 错误信息的键:required, invalid
此字段用于在表单中表示模型的SlugField。
22. TimeField
- 默认的Widget:TextInput
- 空值:None
- 规范化为:一个Python 的datetime.time 对象。
- 验证给定值是datetime.time或以特定时间格式格式化的字符串。
- 错误信息的键:required, invalid
接收一个可选的参数:input_formats,用于尝试将字符串转换为有效的datetime.time对象的格式列表。
如果没有提供input_formats,默认的输入格式为:
'%H:%M:%S', # '14:30:59''%H:%M', # '14:30'
23. URLField
- 默认的Widget:URLInput
- 空值:’’(一个空字符串)
- 规范化为:一个Unicode 对象。
- 验证给定值是个有效的URL。
- 错误信息的键:required, invalid
可选参数:max_length和min_length
24. UUIDField
- 默认的Widget:TextInput
- 空值:’’(一个空字符串)
- 规范化为:UUID对象。
- 错误信息的键:required, invalid
25. ComboField
- 默认的Widget:TextInput
- 空值:’’(一个空字符串)
- 规范化为:Unicode 对象。
- 根据指定为ComboField的参数的每个字段验证给定值。
- 错误信息的键:required, invalid
接收一个额外的必选参数:fields,用于验证字段值的字段列表(按提供它们的顺序)。
>>> from django.forms import ComboField>>> f = ComboField(fields=[CharField(max_length=20), EmailField()])>>> f.clean('test@example.com')'test@example.com'>>> f.clean('longemailaddress@example.com')Traceback (most recent call last):...ValidationError: ['Ensure this value has at most 20 characters (it has 28).']
26. MultiValueField
- 默认的Widget:TextInput
- 空值:’’(一个空字符串)
- 规范化为:子类的compress方法返回的类型。
- 根据指定为MultiValueField的参数的每个字段验证给定值。
- 错误信息的键:incomplete, invalid, required
27. SplitDateTimeField
- 默认的Widget:SplitDateTimeWidget
- 空值:None
- 规范化为:Python datetime.datetime 对象。
- 验证给定的值是datetime.datetime或以特定日期时间格式格式化的字符串。
- 错误信息的键:invalid_date, invalid, required, invalid_time
forms组件源码分析
为什么局部钩子要写成`clean_字段名?
从is_valid()进行源码分析
def is_valid(self):"""Returns True if the form has no errors. Otherwise, False. If errors arebeing ignored, returns False."""return self.is_bound and not self.errors
self.is_bound(可以点击看一下)是只要传了数据data就一定true,着重点放在self.errors点击查看源码
def errors(self):"Returns an ErrorDict for the data provided for the form"if self._errors is None:self.full_clean()return self._errors
点击_errors查看默认是None,所以肯定会走self.full_clean()方法,查看源码
def full_clean(self):"""Cleans all of self.data and populates self._errors andself.cleaned_data."""self._errors = ErrorDict # 继承字典用来存放不符合的字段if not self.is_bound: # 传入数据肯定不走returnself.cleaned_data = {} # 符合的字段都放在这里.if self.empty_permitted and not self.has_changed(): # self.empty_permitted 默认false 不会走,不用考虑(可以点击查看)returnself._clean_fields() # 局部钩子方法self._clean_form() # 全局钩方法self._post_clean() # 内部为pass
先查看self._clean_fields()(局部钩子执行位置 )
def _clean_fields(self):for name, field in self.fields.items(): # 循环获取字段名和字段对象# fields是一个字典,forms组件的实例化就会自动创建一个fieldsif field.disabled: # 如果这个字段禁用value = self.get_initial_for_field(field, name)else:# 获取字段对应的用户数据,把用户上传的数据往字段里面填写校验value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))try:if isinstance(field, FileField): # 是文件拿文件数据initial = self.get_initial_for_field(field, name)value = field.clean(value, initial)else: # 对传入的value进行校验value = field.clean(value)self.cleaned_data[name] = value # 把校验后的数据放到cleaned_dataif hasattr(self, 'clean_%s' % name): # 判断有没有局部钩子value = getattr(self, 'clean_%s' % name)() # 执行局部钩子self.cleaned_data[name] = value # 校验通过,把数据替换一下except ValidationError as e: # 如果校验不通过,会抛异常,会被捕获self.add_error(name, e) # 捕获后执行,添加到错误信息,errors是个列表,错误可能有多个
点击 add_error 查看关键信息
def add_error(self, field, error):............if field in self.cleaned_data: # 如果field字段对象在cleaned_datadel self.cleaned_data[field] # 那么就将其从中删除
查看源码发现校验数据的整个过程内部都有异常处理机制
把上面局部钩子不写self.add_error,直接主动报错
# 主动报错from django.core.exceptions import ValidationErrorraise ValidationError('用户名已存在!!!!!!!!!')
class MyForm(forms.Form):name = forms.CharField(min_length=3, max_length=8, label='用户名',error_messages={'min_length': '用户名至少三位','max_length': '用户名最大八位','required': '用户名不能为空',})# 局部钩子:效验用户名是否存在"""钩子函数是数据经过了字段一层校验之后才会执行"""def clean_name(self): # 自动生成的函数名,专门用于对name字段添加额外的校验规则name_list = ['kevin', 'jerry', 'tom']# 1.先获取用户名name = self.cleaned_data.get('name')# 2.判断用户名是否重复if name in name_list:# 3.提示信息# self.add_error('name', '用户已存在')# 主动报错from django.core.exceptions import ValidationErrorraise ValidationError('用户名已存在!!!!!!!!!')# 4.最后将勾上来的name返回回去return name
再看self._clean_form()(全局钩子执行位置)
def _clean_form(self):try:cleaned_data = self.clean() # 执行全局钩子并拿到返回值(父类中有clea()方法,如果你自己写了则执行你写的)except ValidationError as e:self.add_error(None, e) # key作为None就是__all__else:if cleaned_data is not None: # 没出错则判断全局钩子的返回值是否为空self.cleaned_data = cleaned_data # 不是空则原封不动的返回给elf.cleaned_data
所以在全局钩子可以看到最后要返回cleaned_data,因为我们重写了这个方法
下面的_post_clean是一些可以自己写的功能接口,重新具备一定的功能
ModelForm组件
ModelForm(基于forms组件)比form减少代码代码冗余
models.py
class User(models.Model):name = models.CharField(max_length=32,)password = models.CharField(max_length=32)
views.py
from django.shortcuts import render, HttpResponsefrom django import formsfrom app01 import modelsclass MyUser(forms.ModelForm):class Meta:model = models.User # 指定关联的表fields = '__all__' # 所有的字段全部生成对应的forms字段# exclude = ('name',) # 排除的字段labels = {'name': '用户名','password': '密码',}widgets = {'name': forms.widgets.TextInput(attrs={'class': 'form-control'}),'password': forms.widgets.PasswordInput(attrs={'class': 'form-control'}),}def index(request):form_obj = MyUser()if request.method == 'POST':form_obj = MyUser(request.POST)if form_obj.is_valid():edit_obj = models.User.objects.filter(pk=1).first()form_obj = MyUser(request.POST, instance=edit_obj) # 新增还是保存,取决于instance 参数,没有则新增form_obj.save() # 编辑并保存return render(request, 'index.html', locals())
index.html
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script></head><body><form action="" method="post" novalidate style="max-width: 300px;margin: 10px auto" >{% for foo in form_obj %}<p>{{ foo.label }}:{{ foo }}<span style="color: red">{{ foo.errors.0 }}</span></p>{% endfor %}<input type="submit"></form></body></html>
