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 = {}