开发者

Keeping track of changes since the last save in django models

A couple of times I've run into a situation, when at save time I need to know which model fields are going to be updated and act accordingly.

The most obvious solution to this is to take the primary key field and retrieve a copy of the model from the database:

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)

That works perfectly fine, however, it hits the database for every instance of the model you are saving (might be quite inconvenient if you are doing a lot of such saves).

It is obvious, that if one can "remember" the old field values at the start of model instance's lifetime (__init__), there should be no need to retrieve a copy of the model from the database. So I came up with this little hack:

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, g开发者_StackOverflow中文版etattr(self, name, None))
        # old values can be accessed through the self.unsaved member
        super(MyModel, self).save(force_insert, force_update, using)

This seems to work, however it makes use of the non-public interface of django.db.models.Model.

Perhaps someone knows a cleaner way to do it?


I think your solution looks reasonable.

Alternatively you could have a Manager method called get_and_copy() (or something) that hung a copy of the original object off of what is returned. You could then use another Manager method, save_and_check() which took advantage of the copied original.

FWIW: If you are playing with contrib/admin templates there is a context variable called original which is a copy of the original object.

Update: I looked more closely at what admin is doing. In class ModelAdmin (located in django/contrib/admin/options.py) there is a method called construct_change_message(). It is being driven by formset.changed_data and formset.changed_objects, so django/forms/models.py class BaseModelFormSet is where the action is. See the method save_existing_objects(). Also look at the method _existing_object(). It's a little more complicated than what I mentioned before because they are dealing with the possibility of multiple objects, but they are basically caching the results of the query set on first access.


This will not work for fixtures. loaddata command uses models.Model.base_save. Probably the cleanest method would be to use descriptors for fields, but one has to figure out how to inset them properly.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜