在Django模型中跟踪自上次保存以来的更改

9 投票
2 回答
2410 浏览
提问于 2025-04-15 17:48

我遇到过几次这样的情况:在保存数据的时候,我需要知道哪些模型字段会被更新,然后根据这些信息采取相应的措施。

最简单的解决办法就是拿到主键字段,然后从数据库里获取一份模型的副本:

class MyModel(models.Model):

    def save(self, force_insert=False, force_update=False, using=None):
        if self.id is not None:
            unsaved_copy = MyModel.objects.get(id=self.id)
            # Do your comparisons here
        super(MyModel, self).save(force_insert, force_update, using)

这样做是完全可行的,不过每次保存模型实例的时候都会去数据库查询一次(如果你要保存很多这样的实例,这样做就会很麻烦)。

很明显,如果能在模型实例刚创建的时候(也就是在__init__方法里)“记住”旧的字段值,就不需要再从数据库里获取模型的副本了。所以我想出了一个小技巧:

class MyModel(models.Model):

    def __init__(self, *args, **kwargs):
        super(MyModel, self).__init__(*args, **kwargs)
        self.unsaved = {}
        for field in self._meta.fields:
            self.unsaved[field.name] = getattr(self, field.name, None)

    def save(self, force_insert=False, force_update=False, using=None):
        for name, value in self.unsaved.iteritems():
            print "Field:%s Old:%s New:%s" % (name, value, getattr(self, name, None))
        # old values can be accessed through the self.unsaved member
        super(MyModel, self).save(force_insert, force_update, using)

这个方法看起来有效,但它使用了django.db.models.Model的非公开接口。

也许有人知道更简单的方法来实现这个功能?

2 个回答

0

这对测试数据是行不通的。loaddata 这个命令使用的是 models.Model.base_save。可能最简单的方法是为字段使用描述符,但需要弄清楚怎么正确地插入它们。

2

我觉得你的解决方案看起来不错。

另外,你可以创建一个叫做 get_and_copy() 的管理方法(或者其他名字),这个方法会返回一个原始对象的副本。然后你可以再用一个管理方法 save_and_check(),这个方法可以利用刚才复制的原始对象。

顺便提一下:如果你在玩贡献的/admin模板,有一个叫 original 的上下文变量,它是原始对象的一个副本。

更新:我仔细看了一下admin在做什么。在 class ModelAdmin(位于 django/contrib/admin/options.py)里,有一个叫 construct_change_message() 的方法。这个方法是通过 formset.changed_dataformset.changed_objects 来驱动的,所以在 django/forms/models.py 的 class BaseModelFormSet 中有很多操作。你可以看看 save_existing_objects() 这个方法。还有 _existing_object() 这个方法。事情比我之前提到的要复杂一点,因为他们要处理多个对象的可能性,但基本上他们是在第一次访问时缓存查询结果。

撰写回答