【问题标题】:What is the canonical way to find out if a Django model is saved to db?确定 Django 模型是否保存到数据库的规范方法是什么?
【发布时间】:2011-01-03 11:39:00
【问题描述】:

我通常会检查if obj.pk 以了解对象是否已保存。但是,如果您在某些字段上设置了primary_key = True,这将不起作用。例如,我在UserProfile 上设置了user = models.OneToOneField(User, primary_key=True)

确定 Django 模型是否保存到 db 的规范方法是什么?

【问题讨论】:

    标签: django django-models


    【解决方案1】:

    重要提示(截至 2019 年 5 月 6 日):如果您的模型使用 UUID 字段(或其他内部 ID 生成方法,请使用 cmets 中提到的 self._state.adding

    其实obj.pk是最规范的方式。 Django 本身通常不“知道”对象是否被保存。根据the django model instance reference,如果已经设置了主键,它会通过在任何插入之前选择数据库中的id来检查save()调用。

    即使您设置了user = models.OneToOneField(..., primary_key=True).pk 属性仍将指向正确的主键(很可能是user_id),您可以使用它并将其设置为相同的属性。

    如果你想知道一个对象被保存后,你可以抓住post_save signal。此信号在模型保存时触发,如果您愿意,可以将自己的应用程序特定属性添加到模型中,例如 obj.was_saved = True。我认为 django 避免这样做是为了保持他们的实例干净,但是没有真正的理由你不能自己做这件事。这是一个最小的例子:

    from django.db.models.signals import post_save
    from myapp.models import MyModel
    
    def save_handler(sender, instance, **kwargs):
        instance.was_saved = True
    
    post_save.connect(save_handler, sender=MyModel)
    

    您也可以通过简单地连接信号而不指定sender= 参数来让此功能适用于您应用中的所有 模型。但请注意,如果您在您正在导入的其他人的模型实例上覆盖一个属性,您可能会创建未定义的行为。

    【讨论】:

    • 在 post save 信号中默认有一个 boolean kwarg created,所以不需要插入你自己的。
    • 对于现代 Django,应该注意的是,当您使用自己的主键字段和默认值生成器(如 UUIDField 或其他生成 id 的方法)时,这不起作用。如其他答案所述,您应该使用self._state.addingstackoverflow.com/a/19379636/23972
    【解决方案2】:

    假设objMyModel 的一个实例。然后我们可以使用以下代码块来检查数据库中是否已经存在具有该主键的实例:

    if obj.pk is None:
        # Definitely doesn't exist, since there's no `pk`.
        exists = False
    else:
        # The `pk` is set, but it doesn't guarantee exists in db.
        try:
            obj_from_db = MyModel.objects.get(pk=obj.pk)
            exists = True
        except MyModel.DoesNotExist:
            exists = False
    

    这比检查obj.pk is None是否更好,因为你可以这样做

    obj = MyModel()
    obj.pk = 123
    

    然后

    obj.pk is None  # False
    

    当您不使用自动增量 id 字段作为主键而是使用自然键时,这甚至很有可能。

    或者,正如 Matthew 在 cmets 中指出的那样,您可以这样做

    obj.delete()
    

    之后你还有

    obj.pk is None  # False
    

    【讨论】:

    • 这个答案帮助了我 b/c 我发现 .pk 在 delete() 之后仍然设置。谢谢。
    【解决方案3】:

    现在您可以检查:

    self._state.adding
    

    此值由QuerySet.iterator() 为尚未添加到数据库中的对象设置。您还不能在 __init__() 方法中使用此值,因为它是在对象构建后设置的。

    【讨论】:

    • 谢谢,我不知道这存在于 Django 1.8 (Python 3.x) 上
    • 在 Django 1.8 中它变得更好,现在有 Model.from_db()
    • 这应该是当前接受的答案,因为如果您使用 UUID 字段检查主键不起作用,因为这些是在创建实例时自动生成的。
    • self._state.adding == True 表示“尚未添加到数据库中”。见:docs.djangoproject.com/en/1.8/ref/models/instances
    • 在此解释一下Model.from_db() 的相关性?
    【解决方案4】:

    @Crast 的回答很好,但我认为不完整。我在单元测试中用于确定对象是否在数据库中的代码如下。下面,我将解释为什么我认为它优于检查obj.pk is None

    我的解决方案

    from django.test import TestCase
    class TestCase(TestCase):
        def assertInDB(self, obj, msg=None):
            """Test for obj's presence in the database."""
            fullmsg = "Object %r unexpectedly not found in the database" % obj
            fullmsg += ": " + msg if msg else ""
            try:
                type(obj).objects.get(pk=obj.pk)
            except obj.DoesNotExist:
                self.fail(fullmsg)
    
        def assertNotInDB(self, obj, msg=None):
            """Test for obj's absence from the database."""
            fullmsg = "Object %r unexpectedly found in the database" % obj
            fullmsg += ": " + msg if msg else ""
            try:
                type(obj).objects.get(pk=obj.pk)
            except obj.DoesNotExist:
                return
            else:
                self.fail(fullmsg)
    

    注意:如果您在模型名称上使用除 objects 以外的自定义管理器,请谨慎使用上述代码。 (我确信有办法让 Django 告诉你默认管理器是什么。)此外,我知道 /assert(Not)?InDB/ 不是 PEP 8 方法名称,但我使用了 unittest 包的其余部分的样式用过。

    理由

    我认为assertInDB(obj)优于assertIsNotNone(obj.pk)的原因是因为以下情况。假设您有以下模型。

    from django.db import models
    class Node(models.Model):
        next = models.OneToOneField('self', null=True, related_name='prev')
    

    Node 模拟一个双向链表:您可以使用外键将任意数据附加到每个节点,尾部是 Node obj 使得 obj.next is None。默认情况下,Django 将 SQL 约束 ON DELETE CASCADE 添加到 Node 的主键中。现在,假设您有一个长度为 nlist nodes 使得nodes[i].next == nodes[i + 1] for i in [0, n - 1)。假设您拨打nodes[0].delete()。在我对 Python 3.3 上的 Django 1.5.1 的测试中,我发现 nodes[i].pk is not None 用于 [1, n) 中的 i 并且只有 nodes[0].pk is None。但是,我上面的 /assert(Not)?InDB/ 方法正确检测到 [1, n) 中 inodes[i] 确实已被删除。

    【讨论】:

    • 这对于所有版本的 Django 和 Python 都是如此,因为您的 nodes 列表中的项目是实例化的模型对象,因此不再受数据库中发生的任何事情的影响。检查pk is not None 至少可以告诉您该实例是否在某个时候 已保存到数据库中(...假设没有人手动为实例上的pk 分配值)跨度>
    • assertInDb 的更好选择是:obj.refresh_from_db()assertNotInDbwith self.assertRaises(obj.DoesNotExist): obj.refresh_from_db()
    猜你喜欢
    • 1970-01-01
    • 2012-10-29
    • 2010-09-05
    • 2011-11-26
    • 2016-11-28
    • 1970-01-01
    • 2014-07-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多