什么是 ModelForm

  • Model 在 Django 对应数据库模型

    • 一个 Model 拥有多个 Model.Field
  • Form 在 Django 对应表单
    • 一个 Form 拥有多个 Form.Field

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

为什么使用 ModelForm

利用 Model 生成 Form,提高 Model 复用性

如何使用 ModelForm

定义 ModelForm

举一个书籍管理例子

# Model

class Article(models.Model):
title = models.CharField(max_length=20, unique=True)
author = models.ForeignKey('Author')

这个 Model 中定义了两个字段

  • title

    • 储存书籍标题
    • 数据类型是 char
    • 最大长度 20
    • 数据库唯一值限制,即不能储存两本相同标题的书
  • author
    • 储存书籍的作者
    • 数据类型是外键,指向 Model Author

下面我们用 ModelForm 构建表单

# ModelForm

class ArticleForm(forms.ModelForm):
class Meta:
model = Article

和下面手动构建表单的代码等效

class ArticleForm(forms.Form):
title = forms.CharField(max_length=20)
author = forms.ModelChoiceField(queryset=Author.objects.all())

定制 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... catch... 来捕获它。

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

form = ArticleForm(request.POST)
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 中的值

数据加载的先后顺序为 instance, initial, request.POST

校验 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() 方法。

定义一个 Form 来新建、更新实例

通常的步骤分为如下几步

  • 检测该对象是否已在数据库

    • 如果已存在,那么手动获取该实例,然后更改相关 field 内容,最后使用 update() 方法保存到数据库
    • 如果不存在,新建一个 Model 实例并修改至完整的 Model,调用 save() 方法保存到数据库

写成代码的话是这样子

f = AuthorForm(request.POST)

if f.is_valid():
try:
# Save the new instance.
new_author = f.save(commit=False)
new_author.some_field = f.cleaned_data['some_field']
new_author.save()
except IntegrityError:
# 已存在
# 若要这样使用 update 的话需要在 cleaned_data 中加入上述 some_field 的改动
# 因为 some_field 的改动只在 new_author 中使用,并不能更新到数据库
Author.objects.filter(pk=f.cleaned_data['pk']).update(f.cleaned_data)

太麻烦了!!其实 Django 中已经有 update_or_create 方法已经实现了上述所有功能,可以避免这个 try ... except ... 判断实例是否已存在,我们来看这个例子

# forms.py

class AuthorAddForm(forms.ModelForm):
# 确保 pk 不是必须字段
# 如果不传,自动识别为 None
pk = forms.IntegerField(required=False) class Meta:
model = Author
fields = ['name', 'address'] def _post_clean(self):
super(forms.ModelForm, self)._post_clean()
# 不传 pk 的话表示需要新建一个条目
if not self.cleaned_data['pk']:
# 添加需要的 Field
self.cleaned_data['Origin'] = City.objects.get(Province='北京', City='北京')
# views.py

class AuthorsView(LoginRequiredMixin, TemplateView):
template_name = 'authors.html' def post(self, request):
form = AuthorAddForm(request.POST)
result = {} if not form.is_valid():
return form.errors # 如果 pk 不存在,为 None,那么 update_or_create 匹配失败,从而进入 create 流程
# 否则 pk 存在表单中,那么尝试匹配数据库,如果命中,进行 update 操作,否则进行 create 操作
Author, created = Author.objects.update_or_create(pk=form.cleaned_data['pk'], defaults=form.cleaned_data) return Author.pk

其中 update_or_create 通过检测所有非 defaults 的字段,在上述例子中就是 id=form.cleaned_data['id'] 一项是否已存在于数据库而判断是用 update() 还是 create(),而不论是 update() 还是 create(),都会使用 cleaned_data 作为数据源来写入数据库。

Django 中 ModelForm 的使用的更多相关文章

  1. Django中ModelForm应用

    Django中ModelForm的应用 在传统中Form提交的POST的数据在服务器端获取时将不得不一一获取并验证数据的可靠性,但是使用django提供的Form时可简化该过程并提供相应的验证,同时D ...

  2. Django中Model-Form验证

    Django中Model-Form验证 class UserType(models.Model): caption=models.CharField(max_length=32) class User ...

  3. django中ModelForm save方法 以及快速生成空表单或包含数据的表单 包含错误信息

    django中ModelForm学习系列一~save方法 Model代码 from django.db import models # Create your models here. class P ...

  4. Django中ModelForm详解

    1.ModelForm组件介绍:这个组件的功能就是把model和form组合起来 2.ModelForm的使用 1.首先需要导入ModelForm from django.forms import M ...

  5. django中ModelForm解决多表单组合显示问题

    一.多表单组合显示问题 在项目中用ModelForm生成页面时 当有多表单组合显示时,会显示全部的关联表单数据. 而在实际项目中可能会出现只想让用户选择部分数据,这时候这样的显示就有问题. 二.问题解 ...

  6. Django中使用ModelForm实现Admin功能

    接上一篇<Django中使用Bootstrap> ModelForm 可以将数据库中的信息展示在一个表中,因此我们在查询数据库信息时可以使用ModelForm在前端展示查询到的信息. 在上 ...

  7. Django中的ModelForm与Form

    django表单系统中,所有的表单类都作为django.forms.Form的子类创建,包括ModelForm. 关于django中的表单系统有两种: 基于django.forms.Form 基于dj ...

  8. Django中的Form表单

    Django中已经定义好了form类,可以很容易的使用Django生成一个表单. 一.利用Django生成一个表单: 1.在应用下创建一个forms文件,用于存放form表单.然后在forms中实例华 ...

  9. Django之modelform组件

    一.简介与基本使用 简介:django中的modelform组件同时具有model和form作用,但是耦合度比较高,当项目需要拆分时候就比较困难了,所以在使用modelform时候需要先考虑项目的扩展 ...

随机推荐

  1. 阿里云--安装nginx AND访问超时

    首先先安装PCRE pcre-devel 和Zlib,因为配置nginx的时候会需要这两个东西PCRE(Perl Compatible Regular Expressions) 是一个Perl库,包括 ...

  2. mysql5.7.26部署MHA

    前期准备: mysql先部署好GTID主从,然后才部署MHA 1)环境准备(所有节点) #安装依赖包 yum install perl-DBD-MySQL -y #进入安装包存放目录 [root@my ...

  3. UVALive - 5695 The Last Puzzle (思维+区间dp)

    题目链接 题目大意:有n个按钮排成一条直线,你的任务是通过左右移动按下所有按钮,按钮如果一段时间没有被按下就会被弹开. 以下是我的推论(不一定正确): 直观地看的话,如果选择的是最优路径,那么路径的形 ...

  4. 我说CMMI之七:需求管理过程域--转载

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/dylanren/article/deta ...

  5. DevExpress ASP.NET v19.1版本亮点:Pivot Grid等控件

    行业领先的.NET界面控件DevExpress 发布了v19.1版本,本文将以系列文章的方式为大家介绍DevExpress ASP.NET Controls v19.1中新增的一些控件及增强的控件功能 ...

  6. [傻瓜式一步到位] 阿里云服务器Centos上部署一个Flask项目

    网络上关于flask部署Centos的教程有挺多,不过也很杂乱. 在我第一次将flask上传到centos服务器中遇到了不少问题,也费了挺大的劲. 在参考了一些教程,并综合了几个教程之后才将flask ...

  7. 在echart组件下用canvans画三角形

    //使用的canvans绘制的三角形 drawArrow(){ var canvas = document.createElement('canvas');//创建一个元素 canvas.width ...

  8. 23. ClustrixDB AUTO_UNIQUE

    AUTO_INCREMENT 许多表具有使用AUTO_INCREMENT自动填充的代理键.ClustrixDB也支持这个MySQL特性,并在将记录插入表时创建惟一的ID.这些生成的id单调递增. 惟一 ...

  9. C# socket异步 服务端

    转自:  http://blog.csdn.net/qq_20282263/article/details/54310737 private Dictionary<string, Session ...

  10. ABP .net Core MQTT+signalr通讯

    abp版本: 4.3.0.0 .net core 版本 2.2 1.Mqtt 1.1 添加程序集:M2MqttDotnetCore(差点以为没有.net core 的) 2.2 实现代码:抄了个单例模 ...