Django model重写save方法及update踩坑详解
作者:ops-coffee 发布时间:2021-01-21 08:45:52
一个非常实用的小方法
试想一下,Django中如果我们想对保存进数据库的数据做校验,有哪些实现的方法?
我们可以在view中去处理,每当view接收请求,就对提交的数据做校验,校验不通过直接返回错误,不写数据库,校验通过再调用create或update方法写入数据库
以上方式比较简单,容易理解,但随之又带来了麻烦,我们需在所有接收数据的地方都要去校验,那么有没有更加优雅的方式呢?如果你看过我之前的文章『Django使用Signals监测model字段变化发送通知』]就能想到可以通过signals信号来处理,添加一个pre_save的信号,每当数据库数据变更前都会触发pre_save方法,可以在这里进行校验,免去了view中多个地方校验的麻烦
而今天要说的并不是signals,而是另一种比较常用的做法:重写model的save方法
重写save方法
save方法的主要作用就是将一个对象保存到数据库。如果我们想在数据入库之前做一些处理,除了上边提到的signals之外,还可以通过重写save方法来实现。具体实现方式看下面这个例子
假如我们定义了model如下:
class TempTask(models.Model):
...
exechost = models.CharField(max_length=64, default='localhost', verbose_name='执行主机')
execuser = models.ForeignKey(ExecUser, null=True, on_delete=models.PROTECT, db_constraint=False)
exechost默认为Localhost,execuser默认为空,现有需求:当exechost不为localhost时,他必须符合ip:port的格式,且
execuser不能为空。这是一个比较复杂的校验方式,我们可以通过重写save方法来处理
class TempTask(models.Model):
...
def save(self, *args, **kwargs):
if self.exechost and (self.exechost.strip() != 'localhost'):
if len(self.exechost.split(':')) != 2:
raise ValidationError('执行主机格式错误,应为ip:port格式')
if not self.execuser:
raise ValidationError('当执行主机存在时执行用户不能为空')
super().save(*args, **kwargs)
我们可以在save函数内执行各种自定义逻辑,但需要注意的是,最后必须要调用super().save()方法来保证执行了父类的save(),这样才能保证数据写入了数据库。
这样在当我们执行create语句插入数据的时候就会先去执行save中的校验方法进行校验了
TempTask.objects.create(**postdata)
update踩坑
就当我以为一切都要结束准备起身冲杯咖啡的时候,我发现新加数据可以正常进行校验,但更新数据却不行,更新的代码如下:
TempTask.objects.filter(id=pk).update(**postdata)
经过一番查找发现了问题所在,官方文档中有这么一句话
Unfortunately, there isn't a workaround when creating or updating objects in bulk, since none of save(), pre_save, and post_save are called.
也就是说,当使用查询集批量更新对象时,将不会为每个对象调用save()方法,连pre_save和post_save也不会被调用。与save()类似的还有model的delete()方法,当批量删除的时候,同样不会调用model的delete()方法,但delete是可以使用pre_delete或post_delete信号的
解决这个问题的方法很简单,那就是将更新的代码换成下边这种,保证调用到save方法
_t = TempTask.objects.get(id=pk)
_t.__dict__.update(**postdata)
_t.save()
补充知识:django model save方法对未更改的字段依然进行了保存
看代码吧~
obj = Obj.objects.get(id=1)
print obj.name #此时name的值假定为'abc'
def handler(oid):
obj = Obj.objects.get(id=oid)
obj.name = '123'
obj.save()
handler(obj.id)
obj.age = 10
obj.save()
print obj.name
最终的name结果依然为'abc'。save()保存时,虽然没有更改其它字段,但依然会将内存中的值,再次存入数据库,子函数和其它进程更改的值会被覆盖。
来源:https://www.cnblogs.com/37Y37/p/12840890.html


猜你喜欢
- 前言近端时间看了一个短视频,觉得视频的背景音乐片段很不错,想把这个背景音乐得到,虽然小编运用Python爬虫肯定能得到这首音乐,但是这个音乐
- python 列表和链表的区别python 中的 list 并不是我们传统意义上的列表,传统列表——通常也叫作链表(linked list)
- Tensorboard:如何更直观的观察数据在神经网络中的变化,或是已经构建的神经网络的结构。上一篇文章说到,可以使用matplotlib第
- 我们最近的项目中需要使用谷歌机器人验证,这个最主要的就是要有vpn,还需要有公司申请的google账号(自己申请的没用)用于商用的,利用这个
- 查了查《VBSCRIPT 速查手册》InstrRev的用法:InstrRev函数,功能:返回某字符串在另一个字符串中出现的从结尾计起的位置语
- 获取每一天的统计数据做项目的时候需要统对项目日志做分析,其中有一个需求是获取某个给定的时间段内,每一天的日志数据,比如说要获取从2018-0
- Python中的set集合一、集合是什么?集合是什么呢?相信读者朋友们哪怕是没有用过集合这个数据类型。也一定在数学课堂上听过集合这个名词。数
- 上面两个简单的Sql,我们从表面理解,查询的最终结果应该是一样的,但实际结果却和我们想象的不一样第一条sql查询的结果有一条数据第二条sql
- 线性表 线性表是线性结构的抽象,线性结构的特点是结构中的数据元素之间存在一对一的线性关系。 数据元素之间的位置关系是一个接一个的排列: .除
- 本文实例为大家分享了JSP学生信息管理系统源码,供大家参考,具体内容如下新建学生信息数据库1.添加记录模块<%@ page conte
- 连接分为:内连接、外连接、交叉连接 一、内连接——最常用 定义:仅将两个表中满足连接条件的行组合起来作为结果集。 在内连接中,只有在两个表中
- B-Tree 索引不同的存储引擎也可能使用不同的存储结构,i如,NDB集群存储引擎内部实现使用了T-Tree结构存储这种索引,即使其名字是B
- RPC是Remote Procedure Call的缩写,翻译成中文就是远程方法调用,是一种在本地的机器上调用远端机器上的一个过程(方法)的
- 1.lxml库介绍lxml是XML和HTML的解析器,其主要功能是解析和提取XML和HTML中的数据;lxml和正则一样,也是用C语言实现的
- 前言在linux有个命令叫做md5sum,能生成文件的md5值,一般情况下都会将结果记录到一个文件中用于校验使用,比如会这样使用:[craz
- 表达式的优先级表达式(Expression)是运算符(operator)和操作数(operand)所构成的序列代码段a = 1b = 2c
- 这篇文章主要介绍了Python enumerate函数遍历数据对象组合过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定
- 本文实例为大家分享了js省市县 * 级联特效的实现代码,供大家参考,具体内容如下主要思想1.省改变,市改变,并初始化县2.市改变,县改变htm
- CREATE TABLE [dbo].[TbGuidTable]( [TableName] [varchar](50) NOT NULL,
- 前几天,Opera宣布其用户已经超过1亿——桌面版和手机版均超过5000万。Opera Mini是一个很优秀的手机浏览器,对手机用户而言,O