Skip to content

Commit 0204816

Browse files
committed
Added normalise_function to provide control on how values are stored in dirty fields dict
1 parent 0dbec2e commit 0204816

File tree

3 files changed

+47
-7
lines changed

3 files changed

+47
-7
lines changed

docs/index.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,30 @@ You can now define how you want to compare 2 values by passing a function on Dir
191191

192192
Have a look at ``dirtyfields.compare`` module to get some examples.
193193

194+
Custom value normalisation function
195+
----------------------------
196+
By default, ``dirtyfields`` reports on the dirty fields as is. If a date field was changed
197+
the result of ``get_dirty_fields`` will return the current and saved datetime object.
198+
In some cases it is useful to normalise those values, e.g., when wanting ot save the diff data as a json dataset in a database.
199+
The default behaviour of using values as is can be changed by providing a ``normalise_function``
200+
in your model. That function can evaluate the value's type and rewrite it accordingly.
201+
202+
::
203+
204+
import datetime
205+
from django.db import models
206+
from dirtyfields import DirtyFieldsMixin
207+
208+
def your_normalise_function(value):
209+
if isinstance(value, (datetime.datetime, datetime.date)):
210+
return value.isoformat()
211+
else:
212+
return value
213+
214+
class TestModel(DirtyFieldsMixin, models.Model):
215+
normalise_function = (your_normalise_function, {'extra_kwargs': 1})
216+
217+
194218
Why would you want this?
195219
========================
196220

src/dirtyfields/compare.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.utils import timezone
66

77

8-
def compare_states(new_state, original_state, compare_function):
8+
def compare_states(new_state, original_state, compare_function, normalise_function):
99
modified_field = {}
1010

1111
for key, value in new_state.items():
@@ -21,7 +21,10 @@ def compare_states(new_state, original_state, compare_function):
2121
if is_identical:
2222
continue
2323

24-
modified_field[key] = {'saved': original_value, 'current': value}
24+
modified_field[key] = {
25+
'saved': normalise_function[0](original_value, **normalise_function[1]),
26+
'current': normalise_function[0](value, **normalise_function[1])
27+
}
2528

2629
return modified_field
2730

@@ -56,3 +59,13 @@ def timezone_support_compare(new_value, old_value, timezone_to_set=pytz.UTC):
5659
old_value = timezone.make_aware(old_value, pytz.utc).astimezone(timezone_to_set)
5760

5861
return raw_compare(new_value, old_value)
62+
63+
64+
def normalise_value(value):
65+
"""
66+
Default normalisation of value simply returns the value as is.
67+
Custom implementations can normalise the value for various storage schemes.
68+
For example, converting datetime objects to iso datetime strings in order to
69+
comply with JSON standard.
70+
"""
71+
return value

src/dirtyfields/dirtyfields.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
from django.db.models.expressions import Combinable
77
from django.db.models.signals import post_save, m2m_changed
88

9-
from .compare import raw_compare, compare_states
9+
from .compare import raw_compare, compare_states, normalise_value
1010
from .compat import is_buffer, get_m2m_with_model, remote_field
1111

1212

1313
class DirtyFieldsMixin(object):
1414
compare_function = (raw_compare, {})
15+
normalise_function = (normalise_value, {})
1516

1617
# This mode has been introduced to handle some situations like this one:
1718
# https://github.com/romgar/django-dirtyfields/issues/73
@@ -101,7 +102,7 @@ def get_dirty_fields(self, check_relationship=False, check_m2m=None, verbose=Fal
101102
pk_specified = self.pk is not None
102103
initial_dict = self._as_dict(check_relationship, include_primary_key=pk_specified)
103104
if verbose:
104-
initial_dict = {key: {'saved': None, 'current': value}
105+
initial_dict = {key: {'saved': None, 'current': self.normalise_function[0](value)}
105106
for key, value in initial_dict.items()}
106107
return initial_dict
107108

@@ -110,17 +111,19 @@ def get_dirty_fields(self, check_relationship=False, check_m2m=None, verbose=Fal
110111

111112
modified_fields = compare_states(self._as_dict(check_relationship),
112113
self._original_state,
113-
self.compare_function)
114+
self.compare_function,
115+
self.normalise_function)
114116

115117
if check_m2m:
116118
modified_m2m_fields = compare_states(check_m2m,
117119
self._original_m2m_state,
118-
self.compare_function)
120+
self.compare_function,
121+
self.normalise_function)
119122
modified_fields.update(modified_m2m_fields)
120123

121124
if not verbose:
122125
# Keeps backward compatibility with previous function return
123-
modified_fields = {key: value['saved'] for key, value in modified_fields.items()}
126+
modified_fields = {key: self.normalise_function[0](value['saved']) for key, value in modified_fields.items()}
124127

125128
return modified_fields
126129

0 commit comments

Comments
 (0)