记录自django模型上次保存以来的变化

shy*_*ent 9 python django django-models

有几次我遇到了这种情况,在节省时间我需要知道哪些模型字段将被更新并采取相应的行动.

对此最明显的解决方案是获取主键字段并从数据库中检索模型的副本:

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)
Run Code Online (Sandbox Code Playgroud)

这样可以很好地工作,但是,它会为您正在保存的模型的每个实例访问数据库(如果您正在进行大量此类保存,则可能非常不方便).

很明显,如果可以在模型实例的lifetime(__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)
Run Code Online (Sandbox Code Playgroud)

这似乎工作,但它利用了非公共接口django.db.models.Model.

也许有人知道更清洁的方法吗?

Pet*_*ell 2

我认为你的解决方案看起来很合理。

或者,您可以调用一个 Manager 方法get_and_copy()(或其他方法),将原始对象的副本挂在返回的对象上。然后,您可以使用另一种 Manager 方法,save_and_check()该方法利用复制的原始方法。

FWIW:如果您正在使用 contrib/admin 模板,则有一个名为的上下文变量,original它是原始对象的副本。

更新:我更仔细地研究了管理员正在做什么。在class ModelAdmin(位于 django/contrib/admin/options.py 中)有一个名为 的方法construct_change_message()formset.changed_data它由和驱动formset.changed_objects,因此 django/forms/models.pyclass BaseModelFormSet是操作所在。看方法save_existing_objects()。还要看方法_existing_object()。它比我之前提到的要复杂一些,因为它们正在处理多个对象的可能性,但它们基本上是在第一次访问时缓存查询集的结果。