在Django模型的自定义save()方法中,应如何识别新对象?


172

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

(self.id!= None)的检查是否必要且足以确保自身记录是新记录且不会被更新?有什么特殊情况可能会忽略吗?


请选择stackoverflow.com/a/35647389/8893667作为正确答案。答案在很多情况下都不起作用,例如UUIDField pk
Kotlinboy

Answers:


204

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


self.pk is None:

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

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


20
在检查对象的身份时,您应该使用is not而不是!=None
Ben James

3
并非所有模型都具有id属性,即,一个模型通过a扩展了另一个属性models.OneToOneField(OtherModel, primary_key=True)。我认为您需要使用self.pk
AJP

4
在某些情况下,这可能不起作用。请检查以下答案:stackoverflow.com/a/940928/145349
fjsj 2015年

5
这不是正确的答案。如果使用a UUIDField作为主键,self.pk则永远不会None
丹尼尔·范·弗莱门

1
旁注:此答案早于UUIDField。
戴夫·史密斯

190

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

self._state.adding is True 创造

self._state.adding is False 更新中

我从此页面获得


12
这是使用自定义主键字段的唯一正确方法。
webtweakers

9
不确定如何self._state.adding工作的所有细节,但是公平地警告说,False如果调用后进行检查,它似乎总是相等的super(TheModel, self).save(*args, **kwargs)github.com/django/django/blob/stable/1.10.x/django/db/models/ …
agilgur16年

1
这是正确的方法,应该将其改正/设置为正确的答案。
flungo

7
@guival:_state不是私有的;像一样_meta,它带有下划线前缀,以避免与字段名称混淆。(请注意链接文档中的用法。)
Ry-

2
这是最好的方法。我用is_new = self._state.adding,然后super(MyModel, self).save(*args, **kwargs)if is_new: my_custom_logic()
kotrfa

45

检查self.id假定这id是模型的主键。一种更通用的方法是使用pk快捷方式

is_new = self.pk is None


15
专业提示:把这个之前super(...).save()
sbdchd

39

对于检查self.pk == None足够的,以确定该物体将被插入或更新数据库。

Django O / RM具有一个特别讨厌的技巧,该技巧基本上是检查PK位置是否有东西,如果有,则执行UPDATE,否则检查INSERT(如果PK为None,则将其优化为INSERT)。

它必须执行此操作的原因是,允许在创建对象时设置PK。尽管在具有主键的序列列的地方并不常见,但这不适用于其他类型的主键字段。

如果您真的想知道,您必须执行O / RM的操作并查看数据库。

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


好点子!我可以在我的应用程序中解决这个问题(检查None主键),因为我从未为新对象设置pk。但这对于可重用的插件或框架的一部分绝对不是很好的检查。
MikeN

1
当您自己以及通过数据库分配主键时,尤其如此。在那种情况下,最确定的事情就是去数据库。
君士坦丁M

1
即使您的应用程序代码未明确指定pk,测试用例的固定装置也可能会出现。但是,由于它们通常是在测试之前加载的,因此可能不是问题。
Risadinha 2014年

1
在将a UUIDField用作主键的情况下尤其如此:该键不在数据库级别填充,因此self.pk始终填充True
丹尼尔·范·弗莱门

10

您可以只连接到post_save信号,该信号发送一个“已创建”的kwarg,如果为true,则表示已插入您的对象。

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


8
如果负载很大,可能会导致竞争状况。这是因为post_save信号是在保存时发送的,但是在提交事务之前。这可能会出现问题,并使调试变得非常困难。
亚伯·莫勒

我不确定是否发生了变化(从旧版本开始),但是我的信号处理程序在同一事务中被调用,因此任何地方的故障都会回滚整个事务。我正在使用ATOMIC_REQUESTS,所以我不确定默认值。
蒂姆·迪斯达尔

7

检查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

这很方便,因为您新创建的对象(自己)具有它的pk价值


5

我这次谈话很晚了,但是当self.pk具有默认值时,我就遇到了问题。

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

date_created = models.DateTimeField(auto_now_add=True)

从这里你可以去

created = self.date_created is None


4

对于即使您具有UUIDField主键(None即使您只是覆盖save,其他人也指出的主键)也不起作用的解决方案,可以插入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调用,您都可以执行触发通知之类的操作或进一步更新模型,然后再通过网络发送回响应。当然,负责任地使用并将繁重的任务卸载到作业队列中,而不是让用户等待:)



1

这是这样做的常见方法。

该ID将在第一次保存到数据库时给出


0

这对于以上所有情况都可行吗?

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

这将导致额外的数据库命中。
大卫·舒曼

0
> 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为空白。这将根据登录用户的每次更新进行更新
Swelan Auguste

-3

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.