简单使用
- 自定义一个
django.forms.Form
子类 - 根据forms的类型定义需要的form的标签
- 可以指定参数,如label和max_length
- 视图中使用is_valid()方法,form全部通过,返回True
- 使用cleaned_data属性,这是is_valid之后的数据
# web/forms.py
from django import forms
class USER(forms.Form):
name = forms.CharField(max_length=32, label="用户名")
email = forms.EmailField(max_length=128)
# web/views.py
from django.shortcuts import render
from django.http import HttpResponse
from web.forms import USER
# Create your views here.
def register(request):
if request.method == "GET":
form = USER()
return render(request, "web/register.html", {"form": form})
if request.method == "POST":
form = USER(request.POST)
if form.is_valid():
print(form.cleaned_data)
return HttpResponse("ok!")
<!-- web/register.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>register</title>
</head>
<body>
<form action="{% url "web:register" %}" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="提交">
</form>
</body>
</html>
这样我们用GET方式访问web/register
就会生成形如这样的表单:
<form action="/web/register" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="LpAyJTwviENuwiy92DEkOCXAwxte8rGDbBMSgSXvbdOtrnSa81Rd18ZWCdiO7Hyl">
<label for="id_name">用户名:</label><input type="text" name="name" maxlength="32" required="" id="id_name">
<label for="id_email">Email:</label><input type="email" name="email" maxlength="128" required="" id="id_email">
<input type="submit" value="提交">
</form>
注意:form标签和 submit是手动生成的!!
form字段
从上面可以看出,要生成什么input标签,都是我们通过定义类属性来实现的。该类属性就相当于model的字段:
字段 | 对应input | 参数 | 空值 | 错误 |
---|---|---|---|---|
BooleanField | checkbox | 无 | False | required |
CharField | text | max_length、min_length 、strip、empty_value | empty_value参数 | required、max_length、min_length |
ChoiceField | select | choices = [("value", "show in page"), ] |
空字符串 | required、invalid_choice |
DateTimeField | text | input_formats 时间格式 | None | required、invalid |
DateField | text | input_formats 时间格式 | None | required、invalid |
EmailField | max_length、min_length 和 empty_value | empty_value参数 | required、invalid | |
IntegerField | number or text | max_value 和 min_value | None | required、invalid、max_value、min_value |
FileField | file | max_length 和 allow_empty_file | None | required、invalid、missing、empty、max_length |
ImageField 需要Pillow | file | None | required、invalid、missing、empty、invalid_image |
详见官网:内置 Field 类
form字段的参数
这里指的是通用参数
参数 | 说明 |
---|---|
required | 空值时ValidationError 异常,可指定False |
label | 指定label标签 |
label_suffix | 指定label后缀,如user:变为user= |
initial | 指定默认值 |
widget | 指定部件(处理 HTML 的渲染),点击这里查看 |
help_text | 在字段标签后面加上span标签作为提示 |
error_messages | 一个字典,key为该字段可以触发的错误,value为提示信息 |
disabled | 是否可以禁用 |
validators | 指定验证器,点击这里查看验证器 |
localize | 实现表单数据输入和渲染输出的本地化 |
form的api
如:
from django import forms
class UserForm(forms.Form):
name = forms.CharField(max_length=32, label="用户名")
u = UserForm()
那么u
有什么api呢??
.is_bound
有无绑定表单实例,UserForm({”name": "abc"})为True,否则为False.clean()
运行自定义的钩子函数进行验证,如何自定义见下文。.is_valid()
验证数据.cleaned_data
已经经过检验的数据.errors
获取错误信息的html
.errors.as_data() 获得错误信息字典,如{'email': ['错误邮箱格式']}
.errors.as_json() 获得错误信息的json数据,如{"email": [{"message": "u9519u8befu90aeu7bb1u683cu5f0f", "code": "invalid"}]}
.add_error(field, error)
手动添加,会在cleaned_data中删除字段。error 参数可以是一个字符串,但最好是 ValidationError 的实例,如何抛出见这里.has_error(field, code=None)
本方法返回一个布尔值,表示一个字段是否有特定错误 code 的错误。如果 code 是 None,如果字段包含任何错误,它将返回 True。.has_changed()
是否与初始数据发生变化,form的initial参数指定,也可以在实例化时指定:u = UserForm(initial={'name': 'lczmx!'})
.fields
访问字段- .
as_p()
、.as_ul()
、.as_table()
贴出源码:
.as_table()是def as_table(self): "Return this form rendered as HTML <tr>s -- excluding the <table></table>." return self._html_output( normal_row='<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', error_row='<tr><td colspan="2">%s</td></tr>', row_ender='</td></tr>', help_text_html='<br><span class="helptext">%s</span>', errors_on_separate_row=False, ) def as_ul(self): "Return this form rendered as HTML <li>s -- excluding the <ul></ul>." return self._html_output( normal_row='<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>', error_row='<li>%s</li>', row_ender='</li>', help_text_html=' <span class="helptext">%s</span>', errors_on_separate_row=False, ) def as_p(self): "Return this form rendered as HTML <p>s." return self._html_output( normal_row='<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>', error_row='%s', row_ender='</p>', help_text_html=' <span class="helptext">%s</span>', errors_on_separate_row=True, )
__str__
的返回值,所以是默认方式<!-- 正常 --> <tr><th><label for="id_name">用户名:</label></th><td><input type="text" name="name" maxlength="32" required id="id_name"></td></tr> <!-- 异常 --> <tr><th><label for="id_name">用户名:</label></th><td><ul class="errorlist"><li>Ensure this value has at most 2 characters (it has 5).</li></ul><input type="text" name="name" value="lczmx" maxlength="2" required id="id_name"></td></t
自定义form样式
参见:自定义部件实例
-
auto_id
设置字段id- Ture自动生成
- False 不生成
- 'field_%s' 形如:'field_age'、'field_name'
但这是参数:
f = ContactForm(auto_id=False)
-
错误信息类名
rom django import forms class ContactForm(forms.Form): error_css_class = 'error' required_css_class = 'required'
-
css
- 方法1:
class CommentForm(forms.Form): name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'})) url = forms.URLField() comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'}))
- 方法二:
class CommentForm(forms.Form): name = forms.CharField() url = forms.URLField() comment = forms.CharField() name.widget.attrs.update({'class': 'special'}) comment.widget.attrs.update(size='40')
- 对于ModelForm:
class CommentForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['name'].widget.attrs.update({'class': 'special'}) self.fields['comment'].widget.attrs.update(size='40')
-
更细分
可以为每个字段(包括label)提供样式,即将form拆开渲染。{% for field in form %}
获得field,一下都是根据field的。{{ field.label }}
字段的label
参数{{ field.label_tag }}
label标签 包含label_suffix
参数{{ field.id_for_label }}
该字段的 ID{{ field.value }}
字段的vlaue{{ field.html_name }}
字段的name{{ field.help_text }}
与该字段关联的帮助文本。{{ field.errors }}
输出一个<ul class="errorlist">
,其中包含这个字段的所有验证错误信息。你可以使用 {% for error in field.errors %} 循环来自定义错误信息的显示。在这种情况下,循环中的每个对象是包含错误信息的字符串。{{ field.is_hidden }}
如果是隐藏字段,这个属性为 True ,否则为 False{{ field.field }}
访问 Field 的属性, BoundField的API,另外 BoundField。
详见:遍历表单字段
自定义验证方法
要自定义验证方法,那么就要先指定验证的流程,然后我们才知道要操作什么数据,返回什么数据。
.is_valid
方法
return self.is_bound and not self.errors
,即是否绑定数据,以及是否有errors.errors
方法
is_valid调用的是errors方法,源码:
可以看到,其实form就验证一次,没有error的话调用def errors(self): """Return an ErrorDict for the data provided for the form.""" if self._errors is None: self.full_clean() return self._errors
full_clean
方法.full_clean
方法
该方法主要是调用这三个方法验证:self._clean_fields() self._clean_form() self._post_clean()
self._clean_fields()
主要工作:
a. 获得当前的值(空的时候为初始化的值)
b. 将值转换为python格式
c. 检验是否符合required参数
d. 找到对应的验证器进行验证,通过返回对应值, 保存到cleaned_data,否则抛出ValidationError
f. 执行clean_xx
构子函数(我们定义的),通过返回对应值保存到cleaned_data(自动),否则抛出ValidationErrorself._clean_form()
调用我们自定义的clean
钩子函数self._post_clean()
调用_post_clean
钩子函数
暂时不知道怎么用。def _post_clean(self): """ An internal hook for performing additional cleaning after form cleaning is complete. Used for model validation in model forms. """ pass
自定义验证器
该部分内容见使用验证器
验证某一字段
- 定义
clean_xxx
方法 - 从
self.cleaned_data["xxx"]
中取值 - 检验
- 通过,返回值
- 不通过,抛出
django.core.exceptions.ValidationError
,code参数默认invalid
from django import forms
from django.core.exceptions import ValidationError
class USER(forms.Form):
name = forms.CharField(max_length=32, label="用户名")
def clean_name(self):
val = self.cleaned_data["name"]
if not val.startswith("lcz"):
raise ValidationError("必须以lcz开头", code="invalid")
# 通过
return val
验证全部字段
- 定义
clean
方法 - 执行父类的
clean
方法得到cleaned_data
- 验证cleaned_data中自己想要验证的字段
- 不通过就抛出异常,否则不用做
抛出的异常在: .errors["__all__"]
,可通过.non_field_errors()
获取,因为:源码:return self.errors.get("__all__", self.error_class(error_class='nonfield'))
from django import forms
from django.core.exceptions import ValidationError
class USER(forms.Form):
name = forms.CharField(max_length=32, label="用户名")
def clean(self):
cleaned_data = super().clean()
name = cleaned_data.get("name")
if name:
# Only do something if both fields are valid so far.
if "mx" not in name:
raise ValidationError("没有mx")
ModelForm
重点,由于前后端分离,直接生成HTML的用途并不是很大,但是通过model验证POST的数据还是很有用途的。
流程:
- 定义model
- 定义ModelForm类(继承
django.forms.ModelForm
) class Meta
指定哪个类,已经哪些字段- 在views中使用ModelForm,然后调用save方法(save方法隐式验证)
Form字段和Model字段
常用:
模型字段 | 表单字段 |
---|---|
AutoField | 不呈现在表单中 |
BigAutoField | 不呈现在表单中 |
IntegerField | IntegerField |
BigIntegerField | IntegerField 将 min_value 设置为-9223372036854775808,将 max_value 设置为9223372036854775807。 |
FloatField | FloatField |
CharField | CharField 将 max_length 设置为模型字段的 max_length ,如果模型中设置了 null=True ,会将 empty_value 设置为 None 。 |
TextField | CharField 设置中 widget=forms.Textarea |
BinaryField | CharField ,如果在模型字段上的 editable 被设置为 True ,则不在表单中显示。 |
BooleanField | BooleanField, 或 NullBooleanField (如果 null=True )。 |
DateTimeField | DateTimeField |
EmailField | EmailField |
DateField | DateField |
TimeField | TimeField |
FileField | FileField |
FilePathField | FilePathField |
ImageField | ImageField |
URLField | URLField |
UUIDField | UUIDField |
ForeignKey | ModelChoiceField (见下文) |
ManyToManyField | ModelMultipleChoiceField (见下文) |
全部对应关系见:字段类型
- ForeignKey
是一个ChoiceField
,值是选项是一个模型的 QuerySet - ManyToManyField
是一个MultipleChoiceField
,其选项为一个模型 QuerySet
如:authors = models.ManyToManyField(Author)
在ModelForm中就相当于:authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
这两个如何使用,见下文
简单使用
定义model和modelform:
from django.db import models
from django.forms import ModelForm, ChoiceField
TITLE_CHOICES = (
('v1', 'Mr.'),
('v2', 'Mrs.'),
('v3', 'Ms.'),
)
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ['name', 'title', 'birth_date']
class BookForm(ModelForm):
class Meta:
model = Book
fields = ['name', 'authors']
在view 中使用:
# web/views.py
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from django.core.serializers.json import DjangoJSONEncoder
from django.core.exceptions import ValidationError
from web.models import BookForm
# Create your views here.
class ValidationEncoder(DjangoJSONEncoder):
def default(self, val):
if isinstance(val, ValidationError):
return str(val)
super().default(val)
def register(request):
if request.method == "GET":
form = BookForm()
return render(request, "web/register.html", {"form": form})
if request.method == "POST":
form = BookForm(request.POST)
try:
form.save()
except ValueError:
return JsonResponse(form.errors.as_data(), encoder=ValidationEncoder)
return HttpResponse("ok!")
meta
meta类可以为ModelForm提供约束和修饰
- fields:要用ModelForm验证的字段,当值为
"__all__"
时为全部字段 - exclude:除了哪些字段不验证
- model:指定是哪个Model 的ModelForm
- widgets:字典,
{'name': Textarea(attrs={'cols': 80, 'rows': 20}), }
,自定义widgets。 - error_messages:错误信息提示
error_messages = { 'name': { 'max_length': "This writer's name is too long.", }, }
- help_texts:帮助信息
help_texts = { 'name': 'Some useful help text.', }
- labels:input标签的label内容
labels = { 'name': 'Writer', }
- field_classes设置表单字段类型:
field_classes = {'slug': MySlugFormField,}
验证
验证ModelForm主要分两步:
模型的验证(Model.full_clean())紧跟在表单的clean()方法调用之后。(表单的验证流程在上文)
我们可以和form一样自定义clean。
关于错误信息:
- 在 表单字段 级别或者 表单 Meta 级别定义的错误信息优先级总是高于在 模型字段 级别定义的。
- 在 模型字段 上定义的错误信息只有在 模型验证步骤引发
ValidationError
时才会使用,并且没有在表单级定义相应的错误信息
模型字段有内置的default_error_messages
类属性 - ModelForm自定义错误信息
from django.core.exceptions import NON_FIELD_ERRORS from django.forms import ModelForm class ArticleForm(ModelForm): class Meta: error_messages = { NON_FIELD_ERRORS: { 'unique_together': "%(model_name)s's %(field_labels)s are not unique.", } }
操作数据
增加和修改数据都需要调用.save()
方法,调用 save() 将通过检查form.errors
来实现验证。如果表单验证不过,则会抛出ValueError
。
-
增加
- 普通字段
form = BookForm(request.POST) try: form.save() except ValueError: return HttpResponse("error")
- 多对多字段
可以直接保存,但是也可以让其先验证,然后增加一些其它字段,再保存。
调用 save() 的时候使用 commit=False ,那么它会返回一个尚未保存到数据库的对象f = AuthorForm(request.POST) # 调用 save() 的时候使用 commit=False ,那么它会返回一个尚未保存到数据库的对象 new_author = f.save(commit=False) # 增加一些其它字段 new_author.some_field = 'some_value' # 保存多对多的表单数据 new_author.save() f.save_m2m()
- 普通字段
-
修改
通过指定instance参数实现a = Article.objects.get(pk=1) f = ArticleForm(request.POST, instance=a) f.save()
ModelForm的内容很多,本文写了部分内容,更多可以查看官网:从模型创建表单