Forms组件

基本使用

  • 导入forms组件
  • 定义一个类, 并继承Form
  • 在类中书写要校验的字段, 字段的属性就是要校验的规则
  • 实例化得到一个Form对象, 把要校验的数据传入
  • 调用[form对象].is_valid( )方法进行校验, 校验通过返回True
  • 校验通过调用[form对象].cleaned_data获得校验后的数据
  • 校验失败调用[form对象].errors获得错误信息

编写一个校验用户名和密码是否合法的功能

view.py

  1. from django.shortcuts import render, HttpResponse
  2. from django import forms
  3. class MyForm(forms.Form):
  4. # 用户名最少三个字符最多八个字符
  5. user = forms.CharField(min_length=3, max_length=8, label='用户名',
  6. error_messages={
  7. 'min_length': '用户名最短3位',
  8. 'max_length': '用户名最长8位',
  9. 'required': '用户名必填'
  10. }
  11. )
  12. # 用户名最小不能小于0,最大不能超过150
  13. age = forms.IntegerField(min_value=0, max_value=150, label='年龄',
  14. error_messages={
  15. 'min_value': '年龄最小0岁',
  16. 'max_value': '年龄最大150岁',
  17. 'required': '年龄必填'
  18. }
  19. )
  20. # 邮箱必须符合邮箱格式(关键符号@)
  21. email = forms.EmailField(label='邮箱',
  22. error_messages={
  23. 'invalid': '邮箱格式不正确',
  24. 'required': '邮箱必填'
  25. }
  26. )
  27. def index(request):
  28. # 1.先产生一个空对象
  29. form_obj = MyForm()
  30. if request.method == 'POST':
  31. # 2.获取用户数据
  32. form_obj = MyForm(request.POST)
  33. # 3.校验用户数据
  34. if form_obj.is_valid():
  35. print(form_obj.cleaned_data)
  36. return HttpResponse('数据没问题')
  37. return render(request, 'index.html', locals())

test.py(测试环境)

  1. from app01 import views
  2. # 1.将数据传入实例化对象
  3. form_obj = views.MyForm({'user': 'kevin', 'age': 22, 'email': '123'})
  4. # 2.查看数据是否合法(全部合法结果才是True)
  5. print(form_obj.is_valid())
  6. # 3.查看不符合条件的数据及原因
  7. print(form_obj.errors)
  8. # 4.查看符合条件的数据
  9. print(form_obj.cleaned_data)

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <form action="" method="post" novalidate>
  9. {% for form in form_obj %}
  10. <p>
  11. {{ form.label }}
  12. {{ form }}
  13. <span style="color: red">{{ form.errors.0 }}</span>
  14. </p>
  15. {% endfor %}
  16. <input type="submit">
  17. </form>
  18. </body>
  19. </html>

补充

  • forms类中所有的字段数据默认都是必填的,可以添加required=False字段就不必填写
  • forms类中额外传入的字段数据不会做任何的校验
  • forms组件只负责渲染获取用户数据的标签,form表单标签和提交按钮需要自己写
  • 渲染标签中文提示可以使用参数 label指定,不指定默认英文提醒
  • forms类中填写的校验性参数前端浏览器会识别并添加校验操作,但是前端的校验是可有可无的后端必须要再次校验
  • form表单可以取消浏览器自动添加校验功能的操作novalidate

钩子函数

在Form类中定义钩子函数,来实现自定义的验证功能,

局部钩子

在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。

views.py

  1. from django.shortcuts import render, HttpResponse
  2. from django import forms
  3. class MyForm(forms.Form):
  4. name = forms.CharField(min_length=3, max_length=8, label='用户名',
  5. error_messages={
  6. 'min_length': '用户名至少三位',
  7. 'max_length': '用户名最大八位',
  8. 'required': '用户名不能为空',
  9. }
  10. )
  11. # 局部钩子:效验用户名是否存在
  12. """钩子函数是数据经过了字段一层校验之后才会执行"""
  13. def clean_name(self): # 自动生成的函数名,专门用于对name字段添加额外的校验规则
  14. name_list = ['kevin', 'jerry', 'tom']
  15. # 1.先获取用户名
  16. name = self.cleaned_data.get('name')
  17. # 2.判断用户名是否重复
  18. if name in name_list:
  19. # 3.提示信息
  20. self.add_error('name', '用户已存在')
  21. # 4.最后将勾上来的name返回回去
  22. return name
  23. def index(request):
  24. form_obj = MyForm()
  25. if request.method == 'POST':
  26. form_obj = MyForm(request.POST)
  27. if form_obj.is_valid():
  28. return HttpResponse('OK')
  29. return render(request, 'index.html', locals())

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <form action="" method="post" novalidate>
  9. {% for foo in form_obj %}
  10. <p>{{ foo.label }}:{{ foo }}<span style="color: red">{{ foo.errors.0 }}</span></p>
  11. {% endfor %}
  12. <input type="submit">
  13. </form>
  14. </body>
  15. </html>

全局钩子

在Fom类中定义 clean()方法,就能够实现对字段进行全局校验

views.py

  1. from django.shortcuts import render, HttpResponse
  2. from django import forms
  3. class MyForm(forms.Form):
  4. password = forms.CharField(min_length=3, max_length=8, label='密码')
  5. confirm_password = forms.CharField(min_length=3, max_length=8, label='确认密码')
  6. # 全局钩子:校验密码与确认密码是否一致(一次性可以勾多个字段)
  7. def clean(self):
  8. # 1.获取多个字段数据
  9. password = self.cleaned_data.get('password')
  10. confirm_password = self.cleaned_data.get('confirm_password')
  11. # 2.判断两次密码是否一致
  12. if not password == confirm_password:
  13. # 3.提示信息
  14. self.add_error('confirm_password', '两次密码不一致')
  15. # 4.最后将整个数据返回
  16. return self.cleaned_data
  17. def index(request):
  18. form_obj = MyForm()
  19. if request.method == 'POST':
  20. form_obj = MyForm(request.POST)
  21. if form_obj.is_valid():
  22. return HttpResponse('OK')
  23. return render(request, 'index.html', locals())

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. <form action="" method="post" novalidate>
  9. {% for foo in form_obj %}
  10. <p>{{ foo.label }}:{{ foo }}<span style="color: red">{{ foo.errors.0 }}</span></p>
  11. {% endfor %}
  12. <input type="submit">
  13. </form>
  14. </body>
  15. </html>

forms组件字段参数

字段参数 说明
min_length 最小长度
max_length 最大长度
label 字段名称
error_messages 错误提示
min_value 最小值
max_value 最大值
initial 默认值
validators 正则校验器
widget 控制渲染出来的标签各项属性
help_text 设置字段的描述文本

核心字段参数

required

给字段添加必填属性

initial

  1. class MyForm(forms.Form):
  2. username = forms.CharField(
  3. min_length=8,
  4. label="用户名",
  5. initial="kevin" # 设置默认值
  6. )
  7. )

validators

指定一个列表,其中包含了为字段进行验证的函数

  1. class MyForm(forms.Form):
  2. phone = forms.CharField(
  3. validators=[
  4. RegexValidator(r'^[0-9]+$', '请输入数字'),
  5. RegexValidator(r'^138[0-9]+$', '数字必须是138开头'),
  6. ]
  7. )

widget

指定渲染Widget时使用的widget类,也就是这个form字段在HTML页面中是显示为什么框

  1. class MyForm(forms.Form):
  2. password = forms.CharField(
  3. widget=forms.widgets.PasswordInput(
  4. attrs={'class': 'form-control'}
  5. )
  6. )

disabled

设置有该属性的字段在前端页面中将显示为不可编辑状态

forms组件字段类型

1. BooleanField

  • 默认的Widget:CheckboxInput
  • 空值:False
  • 规范化为:Python的True或者False
  • 可用的错误信息键:required

2. CharField

  • 默认的Widget:TextInput
  • 空值:与empty_value给出的任何值。
  • 规范化为:一个Unicode 对象。
  • 验证max_lengthmin_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,默认的输入格式为:

  1. ['%Y-%m-%d', # '2006-10-25'
  2. '%m/%d/%Y', # '10/25/2006'
  3. '%m/%d/%y'] # '10/25/06'

另外,如果你在设置中指定USE_L10N=False,以下的格式也将包含在默认的输入格式中:

  1. ['%b %d %Y', # 'Oct 25 2006'
  2. '%b %d, %Y', # 'Oct 25, 2006'
  3. '%d %b %Y', # '25 Oct 2006'
  4. '%d %b, %Y', # '25 Oct, 2006'
  5. '%B %d %Y', # 'October 25 2006'
  6. '%B %d, %Y', # 'October 25, 2006'
  7. '%d %B %Y', # '25 October 2006'
  8. '%d %B, %Y'] # '25 October, 2006'

6. DateTimeField

  • 默认的Widget:DateTimeInput
  • 空值:None
  • 规范化为:Python的datetime.datetime对象。
  • 验证给出的值是一个datetime.datetime、datetime.date或指定日期格式的字符串。
  • 错误信息的键:required, invalid

接收一个可选的参数:input_formats

如果没有提供input_formats,默认的输入格式为:

  1. ['%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
  2. '%Y-%m-%d %H:%M', # '2006-10-25 14:30'
  3. '%Y-%m-%d', # '2006-10-25'
  4. '%m/%d/%Y %H:%M:%S', # '10/25/2006 14:30:59'
  5. '%m/%d/%Y %H:%M', # '10/25/2006 14:30'
  6. '%m/%d/%Y', # '10/25/2006'
  7. '%m/%d/%y %H:%M:%S', # '10/25/06 14:30:59'
  8. '%m/%d/%y %H:%M', # '10/25/06 14:30'
  9. '%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不支持的格式。

  1. >>> from PIL import Image
  2. >>> from django import forms
  3. >>> from django.core.files.uploadedfile import SimpleUploadedFile
  4. >>> class ImageForm(forms.Form):
  5. ... img = forms.ImageField()
  6. >>> file_data = {'img': SimpleUploadedFile('test.png', <file data>)}
  7. >>> form = ImageForm({}, file_data)
  8. # Pillow closes the underlying file descriptor.
  9. >>> form.is_valid()
  10. True
  11. >>> image_field = form.cleaned_data['img']
  12. >>> image_field.image
  13. <PIL.PngImagePlugin.PngImageFile image mode=RGBA size=191x287 at 0x7F5985045C18>
  14. >>> image_field.image.width
  15. 191
  16. >>> image_field.image.height
  17. 287
  18. >>> image_field.image.format
  19. 'PNG'
  20. >>> image_field.image.getdata()
  21. # Raises AttributeError: 'NoneType' object has no attribute 'seek'.
  22. >>> image = Image.open(image_field)
  23. >>> image.getdata()
  24. <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,默认的输入格式为:

  1. '%H:%M:%S', # '14:30:59'
  2. '%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,用于验证字段值的字段列表(按提供它们的顺序)。

  1. >>> from django.forms import ComboField
  2. >>> f = ComboField(fields=[CharField(max_length=20), EmailField()])
  3. >>> f.clean('test@example.com')
  4. 'test@example.com'
  5. >>> f.clean('longemailaddress@example.com')
  6. Traceback (most recent call last):
  7. ...
  8. 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()进行源码分析

  1. def is_valid(self):
  2. """
  3. Returns True if the form has no errors. Otherwise, False. If errors are
  4. being ignored, returns False.
  5. """
  6. return self.is_bound and not self.errors

self.is_bound(可以点击看一下)是只要传了数据data就一定true,着重点放在self.errors点击查看源码

  1. def errors(self):
  2. "Returns an ErrorDict for the data provided for the form"
  3. if self._errors is None:
  4. self.full_clean()
  5. return self._errors

点击_errors查看默认是None,所以肯定会走self.full_clean()方法,查看源码

  1. def full_clean(self):
  2. """
  3. Cleans all of self.data and populates self._errors and
  4. self.cleaned_data.
  5. """
  6. self._errors = ErrorDict # 继承字典用来存放不符合的字段
  7. if not self.is_bound: # 传入数据肯定不走
  8. return
  9. self.cleaned_data = {} # 符合的字段都放在这里
  10. .
  11. if self.empty_permitted and not self.has_changed(): # self.empty_permitted 默认false 不会走,不用考虑(可以点击查看)
  12. return
  13. self._clean_fields() # 局部钩子方法
  14. self._clean_form() # 全局钩方法
  15. self._post_clean() # 内部为pass

先查看self._clean_fields()(局部钩子执行位置 )

  1. def _clean_fields(self):
  2. for name, field in self.fields.items(): # 循环获取字段名和字段对象
  3. # fields是一个字典,forms组件的实例化就会自动创建一个fields
  4. if field.disabled: # 如果这个字段禁用
  5. value = self.get_initial_for_field(field, name)
  6. else:
  7. # 获取字段对应的用户数据,把用户上传的数据往字段里面填写校验
  8. value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
  9. try:
  10. if isinstance(field, FileField): # 是文件拿文件数据
  11. initial = self.get_initial_for_field(field, name)
  12. value = field.clean(value, initial)
  13. else: # 对传入的value进行校验
  14. value = field.clean(value)
  15. self.cleaned_data[name] = value # 把校验后的数据放到cleaned_data
  16. if hasattr(self, 'clean_%s' % name): # 判断有没有局部钩子
  17. value = getattr(self, 'clean_%s' % name)() # 执行局部钩子
  18. self.cleaned_data[name] = value # 校验通过,把数据替换一下
  19. except ValidationError as e: # 如果校验不通过,会抛异常,会被捕获
  20. self.add_error(name, e) # 捕获后执行,添加到错误信息,errors是个列表,错误可能有多个

点击 add_error 查看关键信息

  1. def add_error(self, field, error):
  2. ......
  3. ......
  4. if field in self.cleaned_data: # 如果field字段对象在cleaned_data
  5. del self.cleaned_data[field] # 那么就将其从中删除

查看源码发现校验数据的整个过程内部都有异常处理机制

把上面局部钩子不写self.add_error,直接主动报错

  1. # 主动报错
  2. from django.core.exceptions import ValidationError
  3. raise ValidationError('用户名已存在!!!!!!!!!')
  1. class MyForm(forms.Form):
  2. name = forms.CharField(min_length=3, max_length=8, label='用户名',
  3. error_messages={
  4. 'min_length': '用户名至少三位',
  5. 'max_length': '用户名最大八位',
  6. 'required': '用户名不能为空',
  7. }
  8. )
  9. # 局部钩子:效验用户名是否存在
  10. """钩子函数是数据经过了字段一层校验之后才会执行"""
  11. def clean_name(self): # 自动生成的函数名,专门用于对name字段添加额外的校验规则
  12. name_list = ['kevin', 'jerry', 'tom']
  13. # 1.先获取用户名
  14. name = self.cleaned_data.get('name')
  15. # 2.判断用户名是否重复
  16. if name in name_list:
  17. # 3.提示信息
  18. # self.add_error('name', '用户已存在')
  19. # 主动报错
  20. from django.core.exceptions import ValidationError
  21. raise ValidationError('用户名已存在!!!!!!!!!')
  22. # 4.最后将勾上来的name返回回去
  23. return name

再看self._clean_form()(全局钩子执行位置)

  1. def _clean_form(self):
  2. try:
  3. cleaned_data = self.clean() # 执行全局钩子并拿到返回值(父类中有clea()方法,如果你自己写了则执行你写的)
  4. except ValidationError as e:
  5. self.add_error(None, e) # key作为None就是__all__
  6. else:
  7. if cleaned_data is not None: # 没出错则判断全局钩子的返回值是否为空
  8. self.cleaned_data = cleaned_data # 不是空则原封不动的返回给elf.cleaned_data

所以在全局钩子可以看到最后要返回cleaned_data,因为我们重写了这个方法

下面的_post_clean是一些可以自己写的功能接口,重新具备一定的功能

ModelForm组件

ModelForm(基于forms组件)比form减少代码代码冗余

models.py

  1. class User(models.Model):
  2. name = models.CharField(max_length=32,)
  3. password = models.CharField(max_length=32)

views.py

  1. from django.shortcuts import render, HttpResponse
  2. from django import forms
  3. from app01 import models
  4. class MyUser(forms.ModelForm):
  5. class Meta:
  6. model = models.User # 指定关联的表
  7. fields = '__all__' # 所有的字段全部生成对应的forms字段
  8. # exclude = ('name',) # 排除的字段
  9. labels = {
  10. 'name': '用户名',
  11. 'password': '密码',
  12. }
  13. widgets = {
  14. 'name': forms.widgets.TextInput(attrs={'class': 'form-control'}),
  15. 'password': forms.widgets.PasswordInput(attrs={'class': 'form-control'}),
  16. }
  17. def index(request):
  18. form_obj = MyUser()
  19. if request.method == 'POST':
  20. form_obj = MyUser(request.POST)
  21. if form_obj.is_valid():
  22. edit_obj = models.User.objects.filter(pk=1).first()
  23. form_obj = MyUser(request.POST, instance=edit_obj) # 新增还是保存,取决于instance 参数,没有则新增
  24. form_obj.save() # 编辑并保存
  25. return render(request, 'index.html', locals())

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
  7. <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  8. </head>
  9. <body>
  10. <form action="" method="post" novalidate style="max-width: 300px;margin: 10px auto" >
  11. {% for foo in form_obj %}
  12. <p>{{ foo.label }}:{{ foo }}<span style="color: red">{{ foo.errors.0 }}</span></p>
  13. {% endfor %}
  14. <input type="submit">
  15. </form>
  16. </body>
  17. </html>