django - comparing old and new field value before saving

Django 1.8+ and above (Including Django 2.x and 3.x), there is a from_db classmethod, which can be used to customize model instance creation when loading from the database.

Note: There is NO additional database query if you use this method.

Here is the link to the official docs Model instance - Customize model loading

from django.db import Model

class MyClass(models.Model):
    
    @classmethod
    def from_db(cls, db, field_names, values):
        instance = super().from_db(db, field_names, values)
        
        # save original values, when model is loaded from database,
        # in a separate attribute on the model
        instance._loaded_values = dict(zip(field_names, values))
        
        return instance

So now the original values are available in the _loaded_values attribute on the model. You can access this attribute inside your save method to check if some value is being updated.

class MyClass(models.Model):
    field_1 = models.CharField(max_length=1)

    @classmethod
    def from_db(cls, db, field_names, values):
        ...
        # use code from above

    def save(self, *args, **kwargs):

        # check if a new db row is being added
        # When this happens the `_loaded_values` attribute will not be available
        if not self._state.adding:

            # check if field_1 is being updated
            if self._loaded_values['field_1'] != self.field_1:
                # do something

        super().save(*args, **kwargs)
            
            

There is very simple django way for doing it.

"Memorise" the values in model init like this:

def __init__(self, *args, **kwargs):
    super(MyClass, self).__init__(*args, **kwargs)
    self.initial_parametername = self.parametername
    ---
    self.initial_parameternameX = self.parameternameX

Real life example:

At class:

def __init__(self, *args, **kwargs):
    super(MyClass, self).__init__(*args, **kwargs)
    self.__important_fields = ['target_type', 'target_id', 'target_object', 'number', 'chain', 'expiration_date']
    for field in self.__important_fields:
        setattr(self, '__original_%s' % field, getattr(self, field))

def has_changed(self):
    for field in self.__important_fields:
        orig = '__original_%s' % field
        if getattr(self, orig) != getattr(self, field):
            return True
    return False

And then in modelform save method:

def save(self, force_insert=False, force_update=False, commit=True):
    # Prep the data
    obj = super(MyClassForm, self).save(commit=False)

    if obj.has_changed():

        # If we're down with commitment, save this shit
        if commit:
            obj.save(force_insert=True)

    return obj

Also you can use FieldTracker from django-model-utils for this:

  1. Just add tracker field to your model:

    tracker = FieldTracker()
    
  2. Now in pre_save and post_save you can use:

    instance.tracker.previous('modelfield')     # get the previous value
    instance.tracker.has_changed('modelfield')  # just check if it is changed
    

It is better to do this at ModelForm level.

There you get all the Data that you need for comparison in save method:

  1. self.data : Actual Data passed to the Form.
  2. self.cleaned_data : Data cleaned after validations, Contains Data eligible to be saved in the Model
  3. self.changed_data : List of Fields which have changed. This will be empty if nothing has changed

If you want to do this at Model level then you can follow the method specified in Odif's answer.