-
Notifications
You must be signed in to change notification settings - Fork 18
/
changes.py
206 lines (164 loc) · 6.93 KB
/
changes.py
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
from django.db.models import signals
from .signals import post_change
SAVE = 0
DELETE = 1
class ChangesMixin(object):
"""
ChangesMixin keeps track of changes for model instances.
It allows you to retrieve the following states from an instance:
1. current_state()
The current state of the instance.
2. previous_state()
The state of the instance **after** it was created, saved
or deleted the last time.
3. old_state()
The previous previous_state(), i.e. the state of the
instance **before** it was created, saved or deleted the
last time.
It also provides convenience methods to get changes between states:
1. changes()
Changes from previous_state to current_state.
2. previous_changes()
Changes from old_state to previous_state.
3. old_changes()
Changes from old_state to current_state.
And the following methods to determine if an instance was/is persisted in
the database:
1. was_persisted()
Was the instance persisted in its old state.
2. is_persisted()
Is the instance is_persisted in its current state.
This schematic tries to illustrate how these methods relate to
each other::
after create/save/delete after save/delete now
| | |
.-----------------------------------.----------------------------------.
|\ |\ |\
| \ | \ | \
| old_state() | previous_state() | current_state()
| | |
|-----------------------------------|----------------------------------|
| previous_changes() (prev - old) | changes() (cur - prev) |
|-----------------------------------|----------------------------------|
| old_changes() (cur - old) |
.----------------------------------------------------------------------.
\ \
\ \
was_persisted() is_persisted()
"""
def __init__(self, *args, **kwargs):
super(ChangesMixin, self).__init__(*args, **kwargs)
self._states = []
self._save_state(new_instance=True)
signals.post_save.connect(
_post_save, sender=self.__class__,
dispatch_uid='django-changes-%s' % self.__class__.__name__
)
signals.post_delete.connect(
_post_delete, sender=self.__class__,
dispatch_uid='django-changes-%s' % self.__class__.__name__
)
def _save_state(self, new_instance=False, event_type='save'):
# Pipe the pk on deletes so that a correct snapshot of the current
# state can be taken.
if event_type == DELETE:
self.pk = None
# Save current state.
self._states.append(self.current_state())
# Drop the previous old state
# _states == [previous old state, old state, previous state]
# ^^^^^^^^^^^^^^^^^^
if len(self._states) > 2:
self._states.pop(0)
# Send post_change signal unless this is a new instance
if not new_instance:
post_change.send(sender=self.__class__, instance=self)
def current_state(self):
"""
Returns a ``field -> value`` dict of the current state of the instance.
"""
field_names = set()
[field_names.add(f.name) for f in self._meta.local_fields]
[field_names.add(f.attname) for f in self._meta.local_fields]
return dict([(field_name, getattr(self, field_name)) for field_name in field_names])
def previous_state(self):
"""
Returns a ``field -> value`` dict of the state of the instance after it
was created, saved or deleted the previous time.
"""
if len(self._states) > 1:
return self._states[1]
else:
return self._states[0]
def old_state(self):
"""
Returns a ``field -> value`` dict of the state of the instance after
it was created, saved or deleted the previous previous time. Returns
the previous state if there is no previous previous state.
"""
return self._states[0]
def _changes(self, other, current):
return dict([(key, (was, current[key])) for key, was in other.iteritems() if was != current[key]])
def changes(self):
"""
Returns a ``field -> (previous value, current value)`` dict of changes
from the previous state to the current state.
"""
return self._changes(self.previous_state(), self.current_state())
def old_changes(self):
"""
Returns a ``field -> (previous value, current value)`` dict of changes
from the old state to the current state.
"""
return self._changes(self.old_state(), self.current_state())
def previous_changes(self):
"""
Returns a ``field -> (previous value, current value)`` dict of changes
from the old state to the previous state.
"""
return self._changes(self.old_state(), self.previous_state())
def was_persisted(self):
"""
Returns true if the instance was persisted (saved) in its old
state.
Examples::
>>> user = User()
>>> user.save()
>>> user.was_persisted()
False
>>> user = User.objects.get(pk=1)
>>> user.delete()
>>> user.was_persisted()
True
"""
pk_name = self._meta.pk.name
return bool(self.old_state()[pk_name])
def is_persisted(self):
"""
Returns true if the instance is persisted (saved) in its current
state.
Examples:
>>> user = User()
>>> user.save()
>>> user.is_persisted()
True
>>> user = User.objects.get(pk=1)
>>> user.delete()
>>> user.is_persisted()
False
"""
return bool(self.pk)
def old_instance(self):
"""
Returns an instance of this model in its old state.
"""
return self.__class__(**self.old_state())
def previous_instance(self):
"""
Returns an instance of this model in its previous state.
"""
return self.__class__(**self.previous_state())
def _post_save(sender, instance, **kwargs):
instance._save_state(new_instance=False, event_type=SAVE)
def _post_delete(sender, instance, **kwargs):
instance._save_state(new_instance=False, event_type=DELETE)