- Author:
- karanlyons
- Posted:
- August 25, 2013
- Language:
- Python
- Version:
- 1.5
- Score:
- 2 (after 2 ratings)
Improved and Released as Save The Change.
Django 1.5 added the update_fields
kwarg
to Model.save()
, which allows the developer to specify that only certain fields should actually be committed to the database. However, Django provides no way to automatically commit only changed fields if they're not specified.
This mixin keeps track of which fields have changed from their value in the database, and automatically applies update_fields
to update only those fields.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | 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 = {}
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 8 months, 3 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 9 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 3 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 4 months ago
- Help text hyperlinks by sa2812 1 year, 5 months ago
Comments
Please login first before commenting.