from copy import copy from django.db.models import ManyToManyField from django.db.models.related import RelatedObject class DoesNotExist: """ It's unlikely, but there could potentially be a time when a field is added to or removed from an instance. We need some representation for those cases. """ pass class SaveOnlyChangedFields(object): """ Keeps track of fields that have changed since model instantiation, and on save updates only those fields. If save is called with update_fields, the passed kwarg is given precedence. A caveat: This can't do anything to help you with ManyToManyFields nor reverse relationships, which is par for the course: they aren't handled by save(), but are pushed to the db immediately on change. """ def __init__(self, *args, **kwargs): super(SaveOnlyChangedFields, self).__init__(*args, **kwargs) self._changed_fields = {} def __setattr__(self, name, value): if hasattr(self, '_changed_fields'): try: name_map = self._meta._name_map except AttributeError: name_map = self._meta.init_name_map() if name in name_map and name_map[name][0].__class__ not in {ManyToManyField, RelatedObject}: old = getattr(self, name, DoesNotExist) super(SaveOnlyChangedFields, self).__setattr__(name, value) # A parent's __setattr__ may change value. new = getattr(self, name, DoesNotExist) if old != new: changed_fields = self._changed_fields if name in changed_fields: if changed_fields[name] == new: # We've changed this field back to its value in the db. No need to push it back up. changed_fields.pop(name) else: changed_fields[name] = copy(old) else: super(SaveOnlyChangedFields, self).__setattr__(name, value) else: super(SaveOnlyChangedFields, self).__setattr__(name, value) def save(self, *args, **kwargs): if self._changed_fields and 'update_fields' not in kwargs and not kwargs.get('force_insert', False): kwargs['update_fields'] = [key for key, value in self._changed_fields.iteritems() if value is not DoesNotExist] super(SaveOnlyChangedFields, self).save(*args, **kwargs) self._changed_fields = {}