Reputation: 10086
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, getattr(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?
Upvotes: 9
Views: 2467
Reputation: 492
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.
Upvotes: 0
Reputation: 17713
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.
Upvotes: 2