Django: Before a model is updated, I'd like to "look at" its previous attributes
When an update/create is performed on a Django model (.save()
) I would lik开发者_如何学运维e to be able to "step in" and compare some particular attributes to what they were set to previously (if they previously existed at all).
I'm thinking Pre-Save Signals, with a look-up to the original model doing a .objects.get(instance.id)
, but that feels wasteful. Also, has the validation already happened in pre_save()
?
about model validation :
Note that full_clean() will not be called automatically when you call your model’s save() method
Then, about the pre-save signal
, note that you get the instance that is being saved sent as a parameter with the message. As the former version of your model exists only in the database, I don't see where else you could get the previous value of the attributes ...
You don't tell why you want to do this so it's hard to say, but other solutions I'm thinking of right now :
* defining a custom signal that is sent everytime the attributes you are interested in are modified... This signal would then send two arguments : new value, old value
* perform the check directly when setting the attributes
If you give more details, it might be easier...
EDIT :
That's right ... If you emit a custom 'foo_has_updated', you will not be sure that the modification is saved.
In this case, I guess you could cache the variables that interest you while initializing the instance, and catch the post-save OR pre-save signal.
* With pre-save, you would be able to pre-process the data, but the saving operation might fail
* With post-save, you would be sure that the data has been saved.
Caching your variables could be done like this :
class CachedModel(models.Model):
cached_vars = [var1, var2, varN]
def __init__(self, *args, **kwargs):
super(CachedModel, self).__init__(*args, **kwargs)
self.var_cache = {}
for var in self.cached_vars:
self.var_cache[var] = copy.copy(getattr(self, var))
Or something like this ... Then, in your signal handler :
def post_save_handler(sender, **kwargs):
instance = kwargs["instance"]
[(instance.var_cache[var], getattr(instance, var)) for var in instance.cached_var]
#[(<initial value>, <saved value>)
And you got what you needed (I think)!!!
Here's my idea: play around with properties.
Say you have this class:
class Foo(models.Model):
name = models.CharField()
Instead, rename your field (you won't need a migration if it's the first time you're doing this) and:
class Foo(models.Model):
_name = models.CharField()
@property
def name(self):
return self._name
@name.setter
def name(self, new_value):
if not getattr(self, '_initial_name', False):
self._initial_name = self._name
if new_value != self._initial_name:
self._name_changed = True
else:
self._name_changed = False
self._name = new_value
We added two attributes to your Foo instances: '_initial_name' and '_name_changed' and a property: 'name'. These are not model fields and will never be saved to the database. Also, you won't have to mess with the '_name' field any longer as long as the 'name' property takes care of everything.
Now, your 'pre_save' or 'post_save' signal handler can make checks on what has changed:
def handle_pre_save(sender, **kwargs):
foo = kwargs['instance']
if getattr(foo, '_name_changed', False):
log.debug("foo changed its name from '%s' to '%s'",
foo._initial_name, foo.name)
You can ask for the currently stored values in the database before you save. I have done this to log only changed values, but it causes another database query each time you save.
While I very much approve of Sébastien Piquemal's answer I ultimately ended up using both the pre_save
and post_save
signals. Instead of overriding __init__()
, I do something very similar in pre_save
, and then check/compare the values in post_save
and emit a custom signal from there if certain conditions are met.
I think I'll still accept his answer, for the time spent on it. I don't see where we're doing much different, except I'm doing my work in a signal and he's doing it during initialization.
You can use the pre_save signal and compare the db record (the old version) with the instance record (the updated, but not saved in the db version).
Take a model like this one as example:
class Person(models.Model):
Name = models.CharField(max_length=200)
In a pre_save function you can compare the instance version with the db version.
def check_person_before_saving(sender, **kwargs):
person_instance = kwargs['instance']
if person_instance.id:
# you are saving a Person that is already on the db
# now you can get the db old version of Person before the updating
# you should wrap this code on a try/except (just in case)
person_db = Person.objects.get(id=person_instance.id)
# do your compares between person_db and person_instance
...
# connect the signal to the function. You can use a decorator if you prefer
pre_save.connect(check_person_before_saving, sender=Person)
精彩评论