Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 885
Armando Pérez Marqués Avatar asked Mar 08 '10 01:03

Armando Pérez Marqués


People also ask

When saving How can you check if a field has changed?

Then in save method you can compare old and new value of field to check if the value has changed. @classmethod def from_db(cls, db, field_names, values): new = super(Alias, cls).

What method can you use to check if from data has been changed when using a form instance?

Use the has_changed() method on your Form when you need to check if the form data has been changed from the initial data.

What is the purpose of the admin site in a Django project?

The Django admin application can use your models to automatically build a site area that you can use to create, view, update, and delete records. This can save you a lot of time during development, making it very easy to test your models and get a feel for whether you have the right data.


3 Answers

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.

like image 91
xEverybodyx Avatar answered Nov 15 '22 17:11

xEverybodyx


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)
like image 21
Michal Čihař Avatar answered Nov 15 '22 16:11

Michal Čihař


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.

like image 30
Dominik Szopa Avatar answered Nov 15 '22 16:11

Dominik Szopa