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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With