Django Admin: Detect if a subset of an object fields has changed and which of them

I need to detect when some of the fields of certain model have changed in the admin, to later send notifications depending on which fields changed and previous/current values of those fields.

I tried using a ModelForm and overriding the save() method, but the form's self.cleaned_data and seld.instance already have the new values of the fields.

Upvotes: 7

Views: 2475

Answers (4)

xEverybodyx
xEverybodyx

Reputation: 86

Modifying the answer above... taking the brilliant function from Dominik Szopa and changing it will solve your relationship change detection: Use this:

def get_changes_between_models(model1, model2, excludes = []):
    changes = {}
    for field in model1._meta.fields:
        if not (field.name in excludes):
            if field.value_from_object(model1) != field.value_from_object(model2):
                changes[field.verbose_name] = (field.value_from_object(model1),
                                                   field.value_from_object(model2))
    return changes

Then in your code you can say (avoid try/except for performance reasons):

if (self.id):
    old = MyModel.Objects.get(pk=self.id)
    changes = get_changes_between_models(self, old)

    if (changes):
        # Process based on what is changed.

If you are doing this at the "model" level, there is no way to save the extra query. The data has already been changed by the time you reach the "Save" point. My first post, so forgive me if I sound like an idiot.

Upvotes: 7

Michal Čihař
Michal Čihař

Reputation: 10091

To avoid extra DB lookup, I modified constructor to remember initial value and use this in save method later:

class Package(models.Model):
    feedback = models.IntegerField(default = 0, choices = FEEDBACK_CHOICES)
    feedback_time = models.DateTimeField(null = True)

    def __init__(self, *args, **kw):
        super(Package, self).__init__(*args, **kw)
        self._old_feedback = self.feedback

    def save(self, force_insert=False, force_update=False, *args, **kwargs):
        if not force_insert and self.feedback != self._old_feedback:
            self.feedback_time = datetime.utcnow()
        return super(Package, self).save(force_insert, force_update, *args, **kwargs)

Upvotes: 7

Dominik Szopa
Dominik Szopa

Reputation: 1919

In order to get differences of two model instances, you can also use this function. It compare to model instances and returns dictionary of changes.

Upvotes: 1

Adam
Adam

Reputation: 7207

What you'll need to do is get an extra copy of the object you're working on from the database inside the save method before fully saving it. Example:

class MyModel(models.Model):
    field1 = models.CharField(max_length=50)

    def save(self):
        if self.id:
            try:
                old = MyModel.objects.get(pk=self.id)
                if old.field1 != self.field1:
                    # Process somehow
            except MyModel.DoesNotExist:
                pass
        super(MyModel, self).save()

Upvotes: 0

Related Questions