• Ukieweb

    佳的博客

    曾梦想仗剑天涯,后来工作忙没去。

django的modelform重点讲解

什么是 ModelForm

Model 在 Django 对应数据库模型

  • 一个 Model 拥有多个 Model.Field

Form 在 Django 对应表单

  • 一个 Form 拥有多个 Form.Field

ModelForm 即基于 Model 的 Form,把 Model 中的 Field 根据下图中的映射关系自动转化为 Form 中的 Field。

ModelForm 中 Form 的功能

先了解一下ModelForm和Form的关系。

之前学习使用的Form,继承的是BaseForm。我这里用的ModelForm,父类是BaseModelForm,再网上找继承的还是BaseForm。

之前学习Form的时候,讲到Form的2个功能,验证和生成html标签。全部都是在BaseForm这个类里实现的。所以这些功能在ModelForm里一样都有,并且用起来和Form几乎也是一样的。

元类里的参数

  • model :对应models的哪张表

  • fields :显示的字段,__all__表示全部字段, fields = "__all__" 

  • exclude :排除的自动

  • labels :自定义标签名,字典类型 labels = {'username': "名字", 'email': "邮箱", 'user_type': "类型"} 对应上面的例子。如何还在models里设置了verbose_name,还是以这里的labels为准。

  • help_texts :提示信息,显示在输入框后面。字典类型和上面一样。

  • widgets :自定义插件,用的还是form的插件,如果直接导入会重名,要加别名 from django.forms import widgets as my_widgets 。用法举例:widgets = {'username': my_widgets.Textarea(attrs={'class': 'c1'})}

  • error_messages :自定义错误信息,整体错误信息的key是 from django.core.exceptions import NON_FIELD_ERRORS 也就是 '__all__'

  • field_classes :自定义Form验证的类。默认models里是CharField,那么对于Form的类也是CharField。这个设置可以改掉实现自定义。用法举例:field_classes = {'username': forms.fields.EmailField} 。如果直接导入fields依然会有重名的问题,用as改掉

  • localized_fields :本地化,根据不同时区显示数据。参数是需要本地化的字段名的元祖,比如:localized_fields=('create_time',)

元类里的很多字段设置都和Form里的用法是一样的。但是Form里是一个字段一个字段设置的,而ModelForm是整张表设置的。所以这里的设置传入的都是字典,key就是字段名,value就是和Form里设置的值一样了

定制 ModelForm

很多情况下自动生成的 ModelForm 并不能满足设计要求,下面我们来讲一下如何定制

定制有两种方式

Meta

  • 使用 Model 转化的时候自定义转化规则

自定义字段

  • 定义额外的 Field,会覆盖 Model 自动生成的 Field

Meta

ModelForm 是通过 Meta 来把 Model.Field 自动转化为 Form.Field 的,其中涉及到几步转化

validators 不变

添加 widget 属性

  • 即前端的渲染方式

修改 Model 包含的字段

  • 通过 fields 来拿指定字段

  • 通过 exclude 来排除指定字段

修改错误信息

我们通过下面的例子来看一下如何通过 Meta 来定制 ModelForm

class ArticleForm(forms.ModelForm):

    class Meta:
        # 指定 Model
        model = Article

        # Form 需要 Model 中的哪几个 Field
        fields = ['title']

        # Form 排除 Model 中的哪几个 Field
        exclude = ['author']

        # 自定义错误信息 
        error_messages = {
            'invalid' = 'invalid title'
        }

        # 自定义 widget
        # 这里使用了长 80 列,宽 20 行的 textarea
        widgets = {
            'name': Textarea(attrs={'cols': 80, 'rows': 20}),
        }

Meta 的缺点是不能修改字段的 validators,如果需要自定义 validators,需要在 Meta 外部重新定义一个同名 Field 来覆盖自动生成的 Field

在 Form 中另外定义 Field

这是 Form 中定义 Field 的通用方法,在 ModelForm 中它有两个作用

  • 补充 Model 没有的 Field 到 Form

  • 覆盖 Model 中的同名 Field 定义

且看下面的例子,Article 中已经包含了 title 字段,我们在 ModelForm 中重新定义了它,把 CharField 改为了 ChoiceField,并且自定义了 validators。

覆盖 title 的时候,把 title 从 Meta 中 exclude 掉是可选的,去不去掉的区别在于,你是否需要它为你校验 unique=True 这个数据库级限制。

在这里我们需要校验,因为 ModelForm 校验通过后我需要把它存入数据库,如果这里没有校验的话,碰到同标题的书数据库就会在储存时报错,我们希望把这步校验放在 ModelForm 的校验中,而不是在通过校验后再用 try... except... 来捕获它。

class ArticleForm(forms.ModelForm):
    title = forms.ChoiceFied(choices=((1, 'alice'), (2, 'bob'),), validators=MaxValueValidator(2))

    class Meta:
        model = Article

值得一提的一些 Field 转化

AutoField

该 Field 不会出现在 ModelForm 表单中。所有 editable=False 的 Field 都不会出现在 ModelForm 中。

BooleanField

由于表单提交时统一识别为 string,而 BooleanField 是用 python 中的 bool 来判断的,所以只要传了任意非空值,BooleanField 都会当做 True 来处理,而如果传了空值,由于 forms.Field 默认属性是 required=True,会校验失败,所以如果你需要一个可以填 False 的 Field,那么你需要在 Form 中手动设置这个 Field 的 required=False。

ForeignKey

ForeignKey 自动转化为 ModelChoiceField,用下拉选项菜单渲染,默认渲染出来的选项显示为对应 Field 的 __str__,提交的值为对应 Field 的 id,这些都可以定制。

在后端接收提交的时候会自动在对应的 Model 中用 id 去找,如果没找到则抛出 ValidationError。

ManyToManyField

ManyToManyField 自动转化为 ModelMultipleChoiceField,用多选框渲染,同样默认渲染出来的选项显示为对应 Field 的 __str__,提交的值为对应 Field 的 id 值。

比如有个叫 group 的 ManyToManyField,选中了 'finance' 'develop' 这两个选项,他们的 id 分别为 1 和 2,那么世界上提交的表单 QueryString 就是 group=1&group=2

初始化 ModelForm

article = Article.objects.get(pk=1)
author = Author.objects.first()

form = ArticleForm(request.POST, instance=article, initial={'author': author})
# form 绑定到 article 实例了 
# 初始化表单的时候,author 字段的初始值为 author

if form.is_valid():
    form.save()

instance

  • 给 ModelForm 初始化 Model 实例,后续的操作都作用在这个实例上

initial

  • 给 ModelForm 初始值

  • 如果和 instance 同时被定义,同名 field 的值覆盖 instance 中的值

校验 ModelForm

Form 只会检查内部定义过的 Field,request.POST 中其余 keyword 都会被无视和过滤掉,即不会出现在返回的 cleaned_data 中。

form = ArticleForm(request.POST)

# 校验表单 
if form.is_valid():
    # 保存到数据库 
    article = form.save()

is_valid() 会调用 full_clean() 来对表单进行全面校验,它又分成三步(定义在基类 Form 中)

  1. 根据每个 Field 注册的 validators 做单个 Field 的校验 (比如 title 字段就会校验是否超出最大允许长度 20) 其中在 Field.clean() 执行过后提供了钩子 clean_[field_name],可以自定义该 function 来注册自己的校验方法。

  2. 根据 Form 定义的 Field 之间的依赖关系做整个表单的校验,钩子为 clean(),默认为空。

  3. 自定义校验通过后的表单处理,钩子为 _post_clean()

  • 这一步中,ModelForm 做了一些额外的检验:如果定义在 Meta 中的 Field 有 unique=True 这个限制,那么 ModelForm 会按照现有数据库中的数据对其校验,看这个 Field 的值是否已存在,如果已存在,则抛出一个 IntegrityError。实际操作中如果强制不校验 unique 的话,可以把该字段从 Meta 中移除,在 ModelForm 中重新定义该字段。

储存 ModelForm 对象

调用 save() 的时候可以传入 commit=False 来避免立即储存,从而通过后续的修改或补充来得到完整的 Model 实例后再储存到数据库。

如果初始化的时候传入了 instance,那么调用 save() 的时候会用 ModelForm 中定义过的字段值覆盖绑定实例的相应字段,并写入数据库。

save() 同样会帮你储存 ManyToManyField,如果 save 时使用了 commit=False,那么 ManyToManyField 的储存需要等该条目存入数据库之后手动调用 ModelForm 的 save_m2m() 方法。

0
0
下一篇:鼠标特效 JS点击弹字 随机返回 社会主义核心价值观

0 条评论

老佳啊

85后,大专学历,中原人士,家里没矿。

由于年轻时长的比较帅气,导致在别人眼里,我一直不谈恋爱的原因是清高,实则是自己的小自卑。最大的人生目标就是找一个相知相爱相容的人,共度余生。

和人相处时如果能感受到真诚,会非常注重彼此的关系,对别人没有什么心机,即使有利益冲突,一般也会以和为贵,因为在这个世界上,物质的东西,从来不会吸引到我。

特别迷恋那些大山大水,如果现在还能隐居,可能早就去了。对那些宏伟的有底蕴的人文景观比较不感冒。

从事于IT行业,却一直对厨房念念不忘,由于身材魁梧,总觉得自己上辈子是个将军,可惜这辈子没当兵,也不会打架。