[Fixed]-Django: Before a model is updated, I'd like to "look at" its previous attributes

8👍

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)!!!

👤sebpiq

2👍

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)

0👍

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.

0👍

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.

0👍

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)

Leave a comment