Django: Check If a Field has Changed

I got fed up with manually checking if a field has changed in the save method of Django models so I quickly wrote this snippet to generalize it:

def has_changed(instance, field):
    if not instance.pk:
        return False
    old_value = instance.__class__._default_manager.\
             filter(pk=instance.pk).values(field).get()[field]
    return not getattr(instance, field) == old_value

Here’s how could be used:

class Sneetch(models.Model):
    has_star = models.BooleanField(default=False)
    succumbs_to_peer_pressure = models.BooleanField(default=False)

    def save(self, *args, **kwargs):
        if has_changed(self, 'has_star'):
            self.succumbs_to_peer_pressure = True
        super(Sneetch, self).save(*args, **kwargs)

Let me know if you improve it…

You can leave a response, or trackback from your own site.
  • http://www.lovelive.fr Rencontres

    Hi,
    It looks like it has been available in Django since 1.0
    The attribute has_changed tells if the form has changed, changed_data has, I think, the values of changed fields.

  • http://zmsmith.com Zach

    I’ve never used those attributes, so that’s good to know. This utility is for model instances though, not form instances.

  • http://www.oulfa.fr site de rencontre gratuit

    I someone can help me to give use the right code. I also get problem with this

  • http://twitter.com/douglasandrade Douglas S. Andrade

    Great ! Thanks for this, worked great here !

  • Christopher Adams

    I think this is not going to handle changes in files that have the same name but different content correctly. Also, it’s not going to handle ForeignKeys correctly, because the values() method returns ids rather than objects (like old_value) and so they will always report back to be changed. The following changed code should solve these issues…

    def has_changed(instance, field):
        if not instance.pk:
            return False
        old_value = getattr(instance.__class__._default_manager.get(pk=instance.pk), field)
        new_value = getattr(instance, field)
        if hasattr(new_value, “file”):
            # Handle FileFields as special cases, because the uploaded filename could be
            # the same as the filename that’s already there even though there may
            # be different file contents.
            from django.core.files.uploadedfile import UploadedFile
            return isinstance(new_value.file, UploadedFile)
        
        return not getattr(instance, field) == old_value

  • http://twitter.com/dmkharlamov Dmitri Kharlamov

    Thanks for the snippet, a small improvement for comparing different empty values:

    def has_changed(instance, field):
    if not instance.pk:
    return False

    old_value = instance.__class__._default_manager.
    filter(pk=instance.pk).values(field).get().get(field, None)
    new_value = getattr(instance, field, None)

    if hasattr(new_value, “file”):
    # Handle FileFields as special cases, because the uploaded filename could be
    # the same as the filename that’s already there even though there may
    # be different file contents.
    from django.core.files.uploadedfile import UploadedFile
    return isinstance(new_value.file, UploadedFile)

    if not (new_value or old_value):
    # Avoid comparing different types of empty values (None, ”, {}, etc),
    # result is False in any case
    return False
    else:
    # in other cases return comparison result as usual
    return not new_value == old_value

  • Anonymous

    I would like to warn against this method as it hits your database **every time** it needs to check if a field has changed. Either save an extra model instance once and for all or save copies of field data in your constructor.

  • http://zmsmith.com Zach Smith

    Completely agree. I’ve learned a lot since writing this blog post :)

  • Kulbir

    Does it ok?

    def save(self, *args, **kw):
    if self.pk is not None:
    orig = MyModel.objects.get(pk=self.pk)
    if orig.f1 != self.f1:
    print 'f1 changed'
    super(MyModel, self).save(*args, **kw)

  • Anonymous

    Requesting the instance in save() can save db work.. but if you need it for validation purposes (which is most likely the case), save() is too late. Hence, in the constructor.

blog comments powered by Disqus