Model Mixin to Save Only Changed 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

  1. Ordered items in the database - alternative by Leonidas 6 years, 10 months ago
  2. Test runner that installs 'tests' packages as apps by adrian_lc 7 months ago
  3. Improved Pickled Object Field by taavi223 4 years, 8 months ago
  4. "Approved" field with timestamp by miracle2k 6 years, 9 months ago
  5. Custom model field to store dict object in database by rudyryk 4 years ago

Comments

(Forgotten your password?)