在 django 模型自定义 save() 方法中,你应该如何识别一个新对象?

Posted

技术标签:

【中文标题】在 django 模型自定义 save() 方法中,你应该如何识别一个新对象?【英文标题】:In a django model custom save() method, how should you identify a new object? 【发布时间】:2010-10-28 19:20:52 【问题描述】:

我想在保存新记录(不更新现有记录)时触发 Django 模型对象的 save() 方法中的特殊操作。

检查 (self.id != None) 是否必要且足以保证 self 记录是新的且未更新?这可能会忽略任何特殊情况?

【问题讨论】:

请选择***.com/a/35647389/8893667作为正确答案。答案在很多情况下都不起作用,例如UUIDField pk 【参考方案1】:

检查self.pk的另一种方法我们可以检查模型的self._state

self._state.adding is True正在创建

self._state.adding is False更新

我从这个page得到它

【讨论】:

这是使用自定义主键字段时唯一正确的方法。 不确定self._state.adding 工作原理的所有细节,但公平警告说,如果您在调用@987654329 之后 进行检查,它似乎总是等于False @:github.com/django/django/blob/stable/1.10.x/django/db/models/… 在使用 Django 的私有属性或方法时,您应该始终小心,因此即使它有效,它也可能不是最好的解决方案 @guival: _state 不是私人的;像_meta 一样,它带有下划线前缀,以避免与字段名称混淆。 (请注意它在链接文档中的使用方式。) 这是最好的方法。我用了is_new = self._state.adding,然后是super(MyModel, self).save(*args, **kwargs),然后是if is_new: my_custom_logic()【参考方案2】:

更新:澄清self._state 不是私有实例变量,而是以这种方式命名以避免冲突,检查self._state.adding 现在是更好的检查方式。


self.pk is None:

在新的 Model 对象中返回 True,除非该对象将 UUIDField 作为其 primary_key

您可能需要担心的极端情况是除了 id 之外的字段是否存在唯一性约束(例如,其他字段上的二级唯一索引)。在这种情况下,您可能仍然有新记录在手,但无法保存。

【讨论】:

在使用None 对象检查身份时,您应该使用is not 而不是!= 并非所有模型都有 id 属性,即一个模型通过models.OneToOneField(OtherModel, primary_key=True) 扩展另一个模型。我认为您需要使用self.pk 这在某些情况下可能不起作用。请检查这个答案:***.com/a/940928/145349 这不是正确的答案。如果使用UUIDField 作为主键,self.pk 永远不会是None 旁注:此答案早于 UUIDField。【参考方案3】:

检查self.id 假定id 是模型的主键。更通用的方法是使用pk shortcut。

is_new = self.pk is None

【讨论】:

专业提示:把这个 BEFORE 放在super(...).save().【参考方案4】:

self.pk == None 的检查不足以确定对象是否要插入或更新到数据库中。

Django O/RM 有一个特别讨厌的 hack,它基本上是检查 PK 位置是否有东西,如果有,则执行 UPDATE,否则执行 INSERT(如果 PK 为 None,则会优化为 INSERT) .

之所以必须这样做是因为您可以在创建对象时设置PK。虽然在主键有序列列的情况下并不常见,但这不适用于其他类型的主键字段。

如果你真的想知道你必须做 O/RM 所做的事情并查看数据库。

当然,您的代码中有一个特定的案例,因此self.pk == None 很可能会告诉您您需要知道的一切,但这不是通用解决方案。

【讨论】:

好点!我可以在我的应用程序中解决这个问题(检查无主键),因为我从未为新对象设置 pk。但这绝对不是对可重用插件或框架的一部分的好检查。 当您自己和通过数据库分配主键时尤其如此。在这种情况下,最可靠的办法就是去数据库。 即使您的应用程序代码没有明确指定 pks,您的测试用例的固定装置也可能。不过,由于它们通常在测试之前加载,因此可能不是问题。 在使用UUIDField 作为主键的情况下尤其如此:该键未在数据库级别填充,因此self.pk 始终为True 此解决方案也适用于这种情况:***.com/a/35647389/4379151【参考方案5】:

您可以只连接到发送“已创建”kwargs 的 post_save 信号,如果为真,则您的对象已被插入。

http://docs.djangoproject.com/en/stable/ref/signals/#post-save

【讨论】:

如果负载很大,这可能会导致竞争条件。这是因为 post_save 信号是在保存时发送的,但在事务提交之前。这可能会产生问题,并且会使调试变得非常困难。 我不确定事情是否发生了变化(从旧版本开始),但我的信号处理程序是在同一个事务中调用的,因此任何地方的失败都会回滚整个事务。我使用的是ATOMIC_REQUESTS,所以我不太确定默认值。【参考方案6】:

检查self.idforce_insert 标志。

if not self.pk or kwargs.get('force_insert', False):
    self.created = True

# call save method.
super(self.__class__, self).save(*args, **kwargs)

#Do all your post save actions in the if block.
if getattr(self, 'created', False):
    # So something
    # Do something else

这很方便,因为您新创建的对象(self)具有 pk

【讨论】:

【参考方案7】:

我很晚才开始这个对话,但是当 self.pk 具有与之关联的默认值时,我遇到了一个问题。

我解决这个问题的方法是在模型中添加一个 date_created 字段

date_created = models.DateTimeField(auto_now_add=True)

从这里你可以去

created = self.date_created is None

【讨论】:

【参考方案8】:

对于即使您将UUIDField 作为主键也有效的解决方案(正如其他人所指出的,如果您只是覆盖save,则它不是None),您可以插入Django 的post_save信号。将此添加到您的 models.py

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=MyModel)
def mymodel_saved(sender, instance, created, **kwargs):
    if created:
        # do extra work on your instance, e.g.
        # instance.generate_avatar()
        # instance.send_email_notification()
        pass

此回调将阻止 save 方法,因此无论您使用表单还是 Django REST 框架进行 AJAX 调用,您都可以在通过网络发送回您的响应之前执行触发通知或进一步更新模型等操作.当然,负责任地使用并将繁重的任务卸载到作业队列,而不是让您的用户等待:)

【讨论】:

【参考方案9】:

宁可使用 pk 而不是 id

if not self.pk:
  do_something()

【讨论】:

【参考方案10】:

这是这样做的常用方法。

第一次保存到数据库时会给出id

【讨论】:

【参考方案11】:
> def save_model(self, request, obj, form, change):
>         if form.instance._state.adding:
>             form.instance.author = request.user
>             super().save_model(request, obj, form, change)
>         else:
>             obj.updated_by = request.user.username
> 
>             super().save_model(request, obj, form, change)

【讨论】:

使用cleaned_data.get(),我能够确定我是否有一个实例,我还有一个CharField,其中为null,而为true。这将根据登录的用户在每次更新时更新【参考方案12】:

这是否适用于上述所有场景?

if self.pk is not None and <ModelName>.objects.filter(pk=self.pk).exists():
...

【讨论】:

这会导致额外的数据库命中。【参考方案13】:

要了解您是更新还是插入对象(数据),请在表单中使用self.instance.fieldname。在您的表单中定义一个干净的函数并检查当前值条目是否与先前的值相同,如果不是,则您正在更新它。

self.instanceself.instance.fieldname 与新值比较

【讨论】:

以上是关于在 django 模型自定义 save() 方法中,你应该如何识别一个新对象?的主要内容,如果未能解决你的问题,请参考以下文章

在 Django 模型中使用 save() 会产生 TypeError

Django - 自定义模型保存方法不显示属性值

Django中模型

重写save()方法

Django 创建自定义User模型 CustomUser

Django 管理员在模型保存期间返回自定义错误消息