目录
Django forms组件 bound and unbound form instance forms渲染有关 form 校验 form类 ModelForm 利用ModelForm关键就在于model's field mapping to form's field ModelForm.save() 详解 class Meta !!!重写覆盖默认的modelField字段(即自定义一些modelform属性) form有关多选择Field的使用 form's fields are themselves classes Field class 将form校验错误信息改为中文。 BoundField class FileField /ImageField /DateField API widgets class rendering form error messages 定义自己form 实例 包括自定义 局部和全局 钩子 reusable form templates 遗留问题 碰到的错误 总结 Django forms组件 Handle (掌控)一个form是非常复杂的工程,需要做很多功能:不同的类型的数据要有不同的渲染;校验数据;获取检验后的干净数据,并将数据反序列化为相应数据类型如时间对象;保存传递给处理程序等等。Django的forms组件就完成了这些复杂的工作,提供方便的操作form的接口API给我们。
Form对象有很多的API,参考本文API段落。其实每个API都是对应了Form的一个特点。如:form.auto_id 对应就是设置form中表单标签的id属性;form.errors 对应了form的校验和错误信息。等等。官方文档在讲解Form对象API时,也是按照form的功能和特点,来分类介绍每种API的。
bound and unbound form instance 绑定数据的form实例和没绑定数据的form实例,他们之间的区别是非常重要的,这影响到了,同一个api或者属性,在templates 引擎渲染和weight作用时所表现出来的内容是不同的。
一个form instance 要么绑定要么没绑定
绑定了数据的 : 可以调用is_valid等校验api;并且通过该实例可以渲染出html。并且包括inline error messages 校验失败的错误可以渲染到表单后,已提示表单提交用户。 没绑定数据的 :不能校验(因为没绑定数据),but it can still render the blank form as html但是它还是能渲染空白内容的表单空间。没有绑定的form是没有cleaned_data属性的。访问的话会抛出异常。 什么是绑定数据行为?通过form类实例化form对象时,需要提供一个字典类型(映射类型)的数据作为第一个位置参数,如{'name':'ZJQ', 'age': 300} 或 request.POST等。这样初始化的form对象就是绑定了数据的form实例,即使提供一个空的{} 也算是提供了。 没有提供这样一个参数,则实例化出来的是一个没有绑定数据的form实例。 绑定数据的form对象或者没有绑定数据的form对象,可以改变其绑定数据值或者添加绑定数据吗?答案是:NO!. 一旦一个Form 实例对象创建了,要知道它的数据是immutable不可改变的,无论是绑定还是非绑定数据from对象。 forms渲染有关 注意:form对象迭代出来的数据类型。form对象是可迭代的对象,迭代出的是boundfield对象。form对象又是字典类型对象,key是字段名,value是boundfield对象。所以要获取boundfield对象有两种途径,通过for迭代,或者通过字典key访问。以下的field名字没特殊说明,都是boundfield对象。至于获取boundfield对象,刚刚也提到了。下面就来使用它的属性和方法吧:
field.label 是label值,不包括label标签 field.label_tag() 就是一个返回label标签的方法,包含了label值;在渲染标签是指定参数attrs={'class':'foo'} 就能指定标签css class,还可以指定label_suffix=‘::’ 来设置添加label值的后缀. form相当于整个表单,打印form对象就是一个HTML字符串。 field 打印就是一个表单控件的HTML字符串。 form是可以迭代的,迭代出就是boundfield对象。迭代顺序就是form定义的field的顺序。如果要访问某个具体的定义form时的field对象(非boundfield对象)通过form.fields['字段名'] 可以得到。通过boundfield.field也可以拿到对应的字段对象。(区分form定义时的字段对象,和实例化后的boundfield对象) 关于检验失败的错误信息: 通过field.errors拿到。这个拿到的是一个错误集合(或者说错误列表),通过访问改错误列表才能拿到错误。其它拿错误的方式也是一样的。 forms对象在template中的渲染是不会有<\form>标签的。因为form不止可以渲染成表单,还可以渲染成table({{ form.as_table }});如下: There are other output options though for the <label>/<input> pairs:
{{ form.as_table }} will render them as table cells wrapped in <tr> tags
{{ form.as_p }} will render them wrapped in <p> tags
{{ form.as_ul }} will render them wrapped in <li> tags
相应的,都必须自己提供table或这ul
field.id_for_label 这个是获取label应该设置的对应input的id。 field.errors 打印的话会渲染表单错误为一个无序列表,列表的ul会有一个class='errorlist' ,这个需要用户来定义这个 css class 应该这样显示。 由于这个其实是一个错误列表,所以循环来自己渲染错误,通过循环迭代,拿到具体的错误字符串。 form.non_field_errors()表示表单校验时的非field错误,即全局钩子错误或自己添加的错误。 field.value() 就可以拿到表单具体的value所对应的值或非绑定设置的初始化值。 在python代码中打印form对象都是由<\tr><\th>包裹的,而template中使用是没有这些标签包裹的。 form.errors 是一个字典(区别对比field.errors),包含所有字段的错误,key就是字段名,对应的value是一个错误列表。特别注意一个全局钩子的错误放在一个key叫做'__all__'中。注意获取form.errors就会触发form的校验,类似is_valid() 触发一样。同时校验过程也只会发生过一次,对于一个form对象。form.errors有很多的接口,可以获取为json字符串form.errors.as_json();参考:https://docs.djangoproject.com/en/2.0/ref/forms/api/#django.forms.Form.errors 更多模版中渲染有关可以参考:https://docs.djangoproject.com/en/2.0/topics/forms/#looping-over-the-form-s-fields form 渲染时的注意事项:
隐藏一个字段,不渲染它 pass
form 校验 所谓校验,就是绑定到form对象的数据,校验其是否符合定义的约束条件。
关于校验方面,要明白的点:
对于绑定了数据的form对象,可以进行校验其绑定的数据是否匹配form定义的字段的类型和其它约束。form_obj.is_valid() 和 form_obj.errors 的调用执行 都会隐式触发form_obj的校验过程;而想显示的调用校验可以通过from_obj.full_clean()。要明白一个form的校验过程只会进行一次,校验过了就不会在校验了,直接返回结果。所以form_obj.is_valid() 和 form_obj.errors 谁先调用,谁触发有且仅有一次的校验过程。如果有单个字段对象,可以测试调用单个字段对象的clean(校验数据) 方式,参数就是要校验的数据。 校验数据完后,结果无非两种情况:a) 校验通过,django对校验过了的form_obj,会将通过了的数据放入form_obj.cleaned_data 字典中。b) 校验不通过, django对这没通过的form_obj 也会将部分校验通过的放入from_obj.cleaned_data 。对于校验失败了的字段,会将错误存放到form_obj.errors 字典中,字典{'字段名1':['错误信息1','错误信息2']}。 问题:绑定了数据了的字段,会校验哪些方面?第一,定义字段时的一些约束;第二,局部钩子;第三,全局钩子;所以错误信息的字典,主键key是字段,错误信息是一个列表。而全局不是单个字段的,所以Key是不是一个字段名而是‘__all__’ 作为字典key。 没校验通过的form_obj可以用于渲染,将错误信息和验证过的信息都渲染到form表单中,不会出现form表单没有校验通过,就将部分校验通过的数据也清空掉,会保留校验通过的数据,只清空没有通过的字段的数据。而且没通过的错误信息还用于渲染到表单页面中,提醒提交表单的用户错误。 除了form.errors存放全部的校验错误信息外。每个field对象也有一个errors属性,里面存放了字段对象的错误信息,是以一个list列表存放的。 form校验的错误信息的返回格式还可以有多种,有form.errros.as_json()得到一个json字符串,特别是对于ajax提交的form数据,响应错误通过这种方式。 form错误是会有一个ul标签来组织错误信息的。 可以通过直接实例化一个Field对象,通过调用其clean(传入值) 来校验数据是否符合。 form类 继承关系:
from django import forms 导入模块 继承Form类,构造一个自己的表单类。类似于Models类,django通过model操作数据库表。Form对象这是一个表单对象,通过该对象来操纵表单处理的过程,如校验表单字段,渲染表单字段。主要就对这两方面进行操纵。 关于提交的表单数据的校验 ,提供了自定义全局和局部钩子,提供了丰富的内置Field类和其对应的widget来约束表单提交的数据。(插曲:所谓钩子,就是访问入口规定好了,我们就添加入口里面的东西就可以了) 局部钩子注意获取到校验值,进行校验后,符合要返回该值,不符合抛出一个指定的异常 ValidationError 异常 全局钩子主要用于每个空间的值都局部校验后,进行一个全局校验,比如两次密码是否一样。这样就不必在从clean_data取出来比较了。如果校验成功过,注意返回的是clean_data,失败同样抛出ValidationError异常。全局校验错误信息是存在form.errrors 的__all__的一个key对应的列表中。 is_valid clean_name errors 关于渲染表单 form为每个field提供了相对应的一个默认widget。当然也可以自定义,在定义form字段是,可以带入参数widget指定widget类或该类的实例对象。如果传入的是widget类,那么会自动实例一个默认的widget对象用于字段渲染。如果传入的是实例,就按照实例的渲染方式进行渲染。 表单渲染主要就是field对应的widget的作用。当然内置的多种widget都可以传入相同的参数来改变渲染效果,如attrs={'class':'form-control'} 就会给相应标签添加属性。 表单渲染添加css class可以通过widget。而<\label> 和 错误 通过定义form类是添加类属性 error_css_class 和 required_css_class 明天实验这两个hook钩子???? 其实还是不要用完整的,就用他们的label值和错误值,只用field的渲染就好了。 ValidationError导入使用from django.coreexceptions import ValidationError 内置widget都在forms模块中。 form 的实例,可以是空,也可以提前填充数据。归纳总结form实例化数据主要来自三个方面:
来自model instance 来自其它数据源 来自用户提交的表单数据。这种情况通常给用户一个空form后,用户提交,如果无效,再返回一个绑定了数据的form给用户。 ModelForm 出现modelform 这种form类的情况是这样的:
如果你正在开发基于数据库的web app, 很有可能, 你会创建一个forms 是几乎映射到一个django models的。例如,你可能有一个BlogComment model, 然后,你想创建一个form 让用户通过这个form提交博客评论到BlogComment model的表中。在这个例子中,定义form的field types是一种很冗余的做法(django 哲学之避免冗余),因为你已经定义了model的field type了,可以复用给form用。
因为这个原因, django 提供了一个很有帮助的 class 可以让我们创建一个Form class 通过一个django 的model。这样就复用了django model 中的field的定义。
代码实例:
>>> from django.forms import ModelForm
>>> from myapp.models import Article # 导入自己建好的django model
# 创建form class
>>> class ArticleForm(ModelForm):
... class Meta:
... model = Article
... fields = ['pub_date', 'headline', 'content', 'reporter']
# 创建一个form 用于添加文章
>>> form = ArticleForm()
# 创建一个form 用于改变一个存在的文章
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article) #
# 上面创建的两个form都可以用于渲染到模版中,分别用于新增和修改。
小结 :注意这里modelform和普通form实例化的不同。这里传入一个model object instance作为将用于初始化显示的数据。也可以像普通 form一样,传入initail参数。如果两个参数都传递了的话,那么就变成了第三种情况,不过initial会覆盖instance的初始化。
如果实例化绑定数据时,提供了instance参数,那么在save时就是一个update操作数据库。如果只是给了一个类字典的数据没有instance,那么就是insert新增数据到数据库。
所以,在实例化modelform时,instance参数除了会影响save()的行为,还会影响初始化参数initail的效果。
利用ModelForm关键就在于model's field mapping to form's field 每一个model field 有一个与之对应的缺省form field。例如, 一个在model中的CharField 被表示为 一个在form中的CharField. 而一个model ManyToManyField 被表示为 一个form的ModelMultipleChoiceField.
缺省对应关系如下图:
正如你所想的,ForeignKey 和 ManyToManyField model field 类型是特殊情况(OneToOne这则不会有这样的特殊情况):
ForeignKey 通过django.forms.ModelChoiceField所表示,这个实际是一个Choice Field,特殊是它的choices 是一个model QuerySet 也就是一个查询出的queryset结果。对于这种ModelChoiceField。在modelform对象层面和ModelChoiceField层面,进行数据绑定和数据clean()校验是不同的。modelform层面实例化是要提供一个queryset作为代替choice参数,利用queryset生成choices。 ManyToManyField 通过django.forms.ModelMutipleChoiceField 所表示,这个实际是一个MultipleChoiceField,只是它的choices参数是变为了queryset参数,提供一个queryset对象。因为这个queryset对象可以构建出choice。 至于两种Field在校验后,会将单个model对象(对于ModelChoiceField) 和 多个model对象(对于ModelMutipleChoiceField) 存入到cleand_data中。供后续使用。 而且这两类Field对象,就有了queryset属性,这是一个queryset对象。通过这个queryset的API就可以得到对应关联的model的信息了。 如:BoundField.field.queryset.model 就是model class了。这个是访问关联model的重要途径了。 ModelMutipleChoiceField和ModelChoiceField 对应的choice显示调用的queryset中model对象的__str__() 方法的结果,所以model定义时,约定都是要定义__str()__方法。 ModelMutipleChoiceField和ModelChoiceField都有一个可选参数,empty_label 主要用于控制对应select表单的一个空白选项的显示。默认是'-------------' 此外,每一个通过model方式生成的modelform field 会设置如下属性:
如果 model field 有 blank=True, 相应的form将设置required=False。否则,required=True. 这个继承自ModelForm的form的field 的label属性会被设置为model field的verbose_name属性,并且值将是首字母大写 。 而help_text 属性值两者都有,就一一对应了。 如果 model field 有choices 属性设置,这是fomr field's widget 将被设置为Select,该form字段的choices将来自model字段的choices。这个choices 一般会包括一个blank choice代表的是model的默认值。如果field是required,将强制用户做出选择。The blank choice will not be included if the model field has blank=False and an explicit default value(the default value will be initially selected instead). 一个完整的实例定义ModelForm:
# model的
from django.db import models
from django.forms import ModelForm
TITLE_CHOICES = (
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
)
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES) # 注意model和form的字段中都有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']
ModelForm.save() 详解 modelform由于和model产生了关系,所以多出了api来操作model 。比如说form.save()可以保存或者更新form数据到数据库中。这里研究研究。这是modelform非常重要的一点,因为我们得到form正确数据后,是要同步到数据库中的,我们不可能将数据再一一ORM操作到数据库中。所以对于form提交的数据提供了这个save() 同步操作到数据库中。但是要注意对应数据库的新增 和修改 操作,在save时是有不同逻辑的,你自己想想也是,如果是提交的数据和库中存量的数据有约束冲突,那必须解决这个冲突 ;至于新增就简单了直接插入insert就可以了。 save() 创建并保存一个model object instance (利用绑定到该modelform的数据)。绑定到modelform数据有两种方式,一种是普通方式,一种是绑定一个相同model类的实例对象(这种方式多用于修改视图的表单)。对于普通方式,save() 将创建一个new instance of the specificed model 也就是利用提供的数据,实例出一个model object,然后save就会保存新增加一个。对于绑定了对象的方式,如果提供了数据且提供了instance实例,这是update这个实例对象。对于普通模式在ORM操作时出现了主键等冲突,就会save()操作报错。 通过modelform的save操作对应的model对象,关更新操作在实例化modelform时必须带上instance参数指明是更新的哪个model 对象数据,不然会编程新增,失去了想要修改操作的意图。如form(request.POST, instance=model_obj) 这样实例化modelform表单对象才行。 自定义集成成ModelForm的类,其class Meta中的fields是使用哪些对应model的字段应用到modelform中。如果fields = ‘__all__'就是全部model字段应用到。 save() 接受一个可选的参数commit,参数取值可以是True or False,如果是False,那么方法这个save方法会返回一个model 对象,而不会同步到数据库中,这是就只有手动调用model对象的save() 方法去同步到数据库中。这样就提供了一个方式,可以修改对象,再提交到库中。还有一种情况,如果有一个manytomany字段,创建对象,建立关系可能需要先构建关联表中的数据后,才能保存。这时候可以调用modelform.save_m2m()方法保存对象并建立关系数据到中间表。对于commit=True,就没有上面说的两种情况,就直接同步数据库中。 小结:modelform初始化时可以使用initial初始化数据吗?可以的,如果还提供了instance参数用于初始化的话,那么initial优先于instance参数中的值。
modelform的方法和属性除了增加save和save_m2m区别之外,其它和普通form对象API一样。
class Meta !!!重写覆盖默认的modelField字段(即自定义一些modelform属性) 通过class Meta可以定义覆盖默认的一些modelField的元素。
大致在Meta中的属性有:
model = 映射的model class
fields = ['fieldname1', 'fieldname2'...] 全部可以设置为['__all__']
widgets = {'fieldname': widget_obj,...}
labels = {'fieldname':label_value} 设置渲染时的label值
help_text = {'fieldname': help_string}
error_messages = {'fieldname': {'校验code':错误信息}} 通过这个可以改变错误信息为自定义中文
field_classes = {'fieldname': FieldClass}
如果要完全覆盖一个字段,就在modelfrom中建立一个字段的定义就会完全覆盖modelform默认生成的。
参考:https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/#overriding-the-default-fields
form有关多选择Field的使用 form表单中可以使用input-checkbox 和 select-option 及 input-radio 来实现多值或者提供选择项给用户展示。
form表单基本上可以对应数据库中一个表的一条数据。因为数据库中一个表的数据可能关联到其它表的数据(就是常说的manyTomany,manyToone,oneToone)。要通过form表单,操控数据库一条数据,那么表单就要有展示或者操控数据关系的方式。这种方式就是表单的上面提到的三种表单控件了。
再看回django的form组件。主要就是ChoiceField/ModelChoiceField/ModelMultipleChoiceField的使用。
三者的区别:
ChoiceField对应参数choice,就是要提供一个choice参数。 ModelChoiceField对应的是一个queryset参数。**主要利用的是queryset中model对象的pk和对象的__str__的输出。**这个很重要,开始在使用时,要提供一个queryset参数。 ModelMultipleChoiceField对应的也是queryset参数,类似ModelChoiceField只不其widget是一个MultipleSelectWidget罢了。 上面三种Field对应的表单控件默认都是Select,
而对于要使用input-check,就要给字段重新赋值widget参数为一个Check类型的widget。
form's fields are themselves classes Field class Field类实例化对象时,核心参数就五个:
required 是否是必须有数据。用于校验 label 表单的的贴条。主要用于贴示 数据是什么信息。默认是字段名。 widget 主要是表单的渲染,和部分校验。 initial 初始化数据。用于初始化默认值。为后续has_changed()提供对比依据。 help_text
通过form访问Field对象:form.fields['field名字'] 将form校验错误信息改为中文。 由于错误提示校验是分类的,每种类型字段有哪几种校验错误,可以到官网查询https://docs.djangoproject.com/en/2.1/ref/forms/fields/#built-in-field-classes。
知道要改变哪种类型的错误提示后,就在定义field是设置error_messages={'错误类型': 错误信息!}
BoundField class 这个BoundField 类 ,主要用于展示HTML 或者 用于访问form实例的一个Field对象 属性。
BoundField Used to display HTML or access attributes for a single field of a Form instance.
通过BoundField.field 访问到Field对象。
这个类的__str__() 就是展示 字段的HTML。所以打印BoundField对象就输出了HTML。
通过form访问BoundField对象,可以遍历,也可通过字典key操作,因为form是一个类字典的类型。key就是字段字符串啦。
form中的field负责管理表单数据和表单数据的校验当一个表单被提交后。
FileField /ImageField /DateField 和其它的Field不同,有两个特别的Field类型:DateField类与FileField(类似于model中的FileField和ImageField字段比较特别,因为都涉及到文件对象)
在前端页面,需要通过form上传文件,就需要确定form标签的enctype定义了正确的值“multipart/form-data” 现代浏览器对于有文件的上传都会使用这种编码。
这样,才能使用正确的格式编码 form表单中的文件对象和其它数据 到http body中,然后通过http协议传输到服务端,服务端也能正确通过编码方式进行解码,才能正确解析出文件对象和其它数据。
DateField https://docs.djangoproject.com/en/2.0/ref/forms/fields/#django.forms.DateField
在normalized转化到python 对象时,需要特别注意该字段。这个字段会将用户表单中填入的字符串,转化为date对象。(用户的键入只能是字符串形式)。
这个转化过程肯定也是要有依据的,得按照依据规则来,不可能用户随便输入什么字符都能转换换成date对象是吧。所以这个字段在初始化时,需要一个可选参数就是input_formats。提供专业的to_python和to_html 的格式。提供了这些格式,用户输入的时间字符串,就需要按照列表中的格式化提供时间字符串。同时,绑定了值的渲染到页面也是按照其中的格式来的。由于这个时间格式的表示范式,全球各地是不同的,所以会根据整个django项目的F10N参数,来判定默认的input_formats规则是什么。如果F10N=True ,那么input_formats只能用全球同一认可的格式。如下:
['%Y-%m-%d', # '2006-10-25'
'%m/%d/%Y', # '10/25/2006'
'%m/%d/%y'] # '10/25/06'
如果F10N=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'
ImageField
对于该字段,实例化时除了带入request.POST外,还需要request.FILES. 也就是要通过form 来handle 上传的文件,需要将文件绑定到form相应的imagefield。
FileField https://docs.djangoproject.com/en/2.0/ref/forms/fields/#django.forms.FileField
对于该字段,实例化时除了带入request.POST外,还需要request.FILES. 也就是要通过form 来handle 上传的文件,需要将文件绑定到form相应的filefield。
FileField可选参数max_length限制文件对象的文件名。allow_empty_file,文件内容可以为空。
由于FiledField 和ImageField处理类似,这里就已ImageField的form绑定 上传文件为例,来演示实例化这一个form: # Bound form with an image field
>>> from django.core.files.uploadedfile import SimpleUploadedFile
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> file_data = {'mugshot': SimpleUploadedFile('face.jpg', <file data>)}
## 这里的file_data中的value是一个SimpleuploadedFile对象,对象实例,提供了文件的名字和文件的句柄作为参数。
>>> f = ContactFormWithMugshot(data, file_data)
而在实战中,request为我们handle好了像SimpleuploadFile对象,就是request.FILES.
f = ContactFormWithMugshot(request.POST, request.FILES)
问题来了,那如果是一个modelform呢?怎么实例化这样一个带有文件对象的modelform?答:同普通form一样,多带入一个request.FILES。
modelform 是哪个字段映射到FileField字段呢?
答:也是form的FileField对应
那实例化提供了SimpleuploadFile对象后,有怎么通过save()保存到对应的数据库表中记录呢?就算不是modelform,普通的form,是怎么将上传的文件保存在哪里呢?
猜测,这些可能就是UploadFile对象封装了这些繁琐的事情了吧?后续验证。
API 特别提醒:注意将form instance api 与 bound field api 对比查看。
form instance api
form.has_change() 返回True或False。和初始化对比变化。 form.fields是一个字典类型,存放这form定义时的field对象,注意不是boundfield对象(也就不是迭代form对象的产物)。 打印form 输出一个HTML form.label_suffix 设置每个label值后面跟什么字符串,默认是冒号‘:’ form['字段名'] 得到对应的BoundField对象。 form.fields['字段名'] 得到对应的Field对象。 form.is_valid() 返回bool值 True校验通过(包括自定义钩子);Flase校验失败。 form.add_error(field, error) 给参数指定的字段添加错误。如果字段是校验过的,添加错误会将字段数据从form.cleaned_data中删除。 form.errors.as_json() 返回json字符串格式的错误信息 form.has_error(field, code=None) 判定字段是否有指定code的错误。 form.non_field_errors() 返回 全局钩子校验失败的错误信息列表或者通过form.has_error(None,'...) 添加的。记住这个方法,对于容易忘记全局钩子错误的KEY是什么的人,是福利。 form.cleaned_data 得到校验干净的数据,数据会格式化为对应的python对象类型。 form.initial 是初始化数据字典。注意初始化是是不会将form变为绑定数据的form的。 form.error_css_class 属性定义是在form中的类属性,主要是给每行的<\tr>标签添加错误信息时的class。 form.required_css_class 属性定义是在form中的类属性,主要就是给<\label>标签添加css class form.auto_id 有三种值,True,Flase,String。这个主要控制label标签for属性 和 input等标签中的id属性的。如果是True,值就会是字段名。如果是False就不会有id属性。如果是'id_for_%s' 这一类的格式化字符串,那么%s会被字段名替换,构成一个id值给标签属性中用。 form.use_required_attribute 设置为True这默认全部都有required属性,如果是False默认全部都没有required属性。但是对单个字段定义时的required是没有影响的。 form.field_order 设置一个列表,加入字段来定义渲染是字段的顺序。 form.order_fields(field_order) 通过这个api可以随时改变form中字段的顺序。 form.is_multipart() 返回True是 说明form中有文件类字段,False这是普通form. bound field api
boundfield.has_change() boundfield.field 而遍历form时得到的foundfield对象也可以得到form定义时的field对象,就是boundfield.field。 打印boundfield 输出一个HTML boundfield.label_suffix 设置当前label值后面跟什么字符串,默认是冒号‘:’ boundfield.value() 返回当前字段的值,绑定了数据的,就返回绑定了的值;不然返回初始化时提供的值,再不然就返回一个None。 boundfield.auto_id 设置field自己的id形式。 boundfield.data 类似boundfield.value() 方法,都是返回对应的数据。区别是,只有绑定数据才会有值,其它任何情况都是None.小结就是:value()拿到渲染后能看到的值。data拿到绑定了的值。 boundfield.errors 是一个类列表对象。这就到了__str__ 和__repr__ 的区别了.打印的话会call str方法,输出html字符串。只是值的化就是走repr,打印出来就是一个字典字面值。这个同form.errors是一样的。 boundfield.form 就是boundfield所在的form对象 boundfield.help_text 就是field定义是的help_text.如果有定义,可以在渲染是放在input后面作为提示帮助信息。 boundfield.html_name
boundfield.id_for_label 对应字段的id属性值。用于自定义<\label>标签 boundfield.is_hidden 判定是否是隐藏字段 boundfield.label 字段的label自,默认是字段名 boundfield.name field字段的名字。 boundfield.label_tag(contents=None, attrs=None, label_suffix=None) boundfield.as_hidden() boundfield.as_widget() boundfield.css_classes() 小结:对比后发现,无论是has_changed, as_table ,initail参数等。这些都是form 与 boundfield之间的关系。如form level 的调用会去调用boundfield的对应的调用,has_changed();还是form level的initial初始化字典,影响field的初始化。就连form.as_table / form.as_ul 等,对应都是boundfield的 as_...方式的调用。还有,如果整个form容器里面的boundfield对象都要改变的属性或者特点,那么通过form instance api 进行容器空间全局控制,如果是单个boundfield的改变,就通过对象自己,同时会覆盖form全局的设置。
widgets class 每种类型的Field class 都有一个默认对应的Widget class。
为什么这样设计?为什么不Field class 把这个干完呢?
因为一个Field class可以有多种Widget表现,即Field 可以和Widget任意匹配,这样能适应的情况就更多了,不然就有超多的细分类型的Field class。同时也可以把一些功能解耦出去。
后面用的多了再总结这一part
rendering form error messages django官方也一直没定下怎么渲染表单验证错误信息。
关于设置错误校验错误信息为中文:
pass 就是通过error_messages
定义自己form 实例 包括自定义 局部和全局 钩子 from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError
wid_01 = widgets.TextInput(attrs={"class":"form-control"})
wid_02 = widgets.PasswordInput(attrs={"class":"form-control"})
wid_03 = widgets.EmailInput(attrs={"class":"form-control"})
class RegForms(forms.Form):
username = forms.CharField(min_length=4, widget=wid_01)
password = forms.CharField(min_length=4, widget=wid_01)
r_password = forms.CharField(min_length=4, widget=wid_01)
email = forms.EmailField(widget=wid_03)
telephone = forms.CharField(required=False,widget=wid_01)
school = forms.ChoiceField(choices=[('male', '男'), ('female', '女')])
age = forms.BooleanField()
area = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple(attrs={'class': 'checkbox-inline'}), choices=(('china', '中国'), ('america', '美国'), ('english', '英国')))
def clean_telephone(self):
val = self.cleaned_data.get('telephone')
if len(val) == 11 :
return val
else:
raise ValidationError('手机号格式错误!')
def clean(self):
pwd = self.cleaned_data.get('password')
r_pwd = self.cleaned_data.get('r_password')
if pwd and r_pwd:
if pwd == r_pwd :
return self.cleaned_data
else:
raise ValidationError('两次密码不相同!')
return self.cleaned_data
注意:这里抛出错误是不规范,也是官方不推荐的,官方推荐抛入ValidationError方式,参考:https://docs.djangoproject.com/en/2.1/ref/forms/validation/#raising-validationerror
reusable form templates 使用include和 include 。。。 with form = comment_form
遗留问题 查看form类的元类 DeclarativeFieldsMetaclass 怎么给label标签添加class属性,或者只有取label值。以解决,第一form对象的required_css_class属性设置。或者定义form类是添加required_css_class类属性。 碰到的错误 form.is_valid()是一个方法不是一个属性,千万不要忘记后加上()呀。因为is_valid()调用后才会有clean_data数据(其实获取form.errors也会产生clean_data数据)。 有关前端的遗漏只是点,就是form标签有一个属性叫 novalidate .加上它之后,前端就不会对required表单进行提示不能为空。添加它的作用,主要是用于方便测试后端form对象对表单的校验空值的功能,而不是前端就提示了。 总结 发现form就是容器,存放field对象。form和field看成两个Level。很多时候form有的方法,field对象也有。如都有has_changed();都可以设置initial参数等。 form的校验数据,不仅仅是校验,还有清洗数据的作用,比如将提交的字符串,转换成对应field类型的数据对象。如日期字符串,通过cleaned_data后,得到的是一个datetime.date的对象。 我觉得,在定义form类时,字段赋值的是一个如CharField的对象。这个对象有包含了Widget对象。也就是CharField对象主要用于校对和渲染的功能。而form实例化后,form迭代出来的是一个叫bounfield的对象,这个对象应该是绑定数据的一个对象,这个boundfield对象是包裹了CharField对象数据的。通过boundfield对象是不能改变其绑定的数据的,但是渲染是可以改变的,就可以通过改变CharField对象的属性,来改变最后利用boundfield对象渲染的输出。如: You can access the fields of Form instance from its fields attribute:
>>> for row in f.fields.values(): print(row)
...
<django.forms.fields.CharField object at 0x7ffaac632510>
<django.forms.fields.URLField object at 0x7ffaac632f90>
<django.forms.fields.CharField object at 0x7ffaac3aa050>
>>> f.fields['name']
<django.forms.fields.CharField object at 0x7ffaac6324d0>
You can alter the field of Form instance to change the way it is presented in the form:
>>> f.as_table().split('\n')[0]
'<tr><th>Name:</th><td><input name="name" type="text" value="instance" required></td></tr>'
>>> f.fields['name'].label = "Username" ## 这里修改了CharField对象,最后使用影响了渲染。虽然影响不了绑定的数据。
>>> f.as_table().split('\n')[0]
'<tr><th>Username:</th><td><input name="name" type="text" value="instance" required></td></tr>'
官方文档也说明了boundfield就是wraps包裹form定义的field的。作用就是展示出
所以说要特别注意在form中,说道的field是class定义时的field,还是实例化后form对象迭代出的boundfield对象。form和boundfield两个不同level都是可以得到class定义时的field对象的;通过改变class定义时的field对象的属性,可以影响最后form渲染的效果。 有个心得:Model class 的属性是Field对象(特别是关联Field对象)。Model instance 的属性是值或者Manager管理器;Form class 的属性是Field对象。Form instance 迭代出来的是BoundField对象。