API¶
Developer Interface¶
-
save_the_change.decorators.
SaveTheChange
(cls)[source]¶ Decorator that wraps models with a save hook to save only what’s changed.
-
save_the_change.decorators.
UpdateTogether
(*groups)[source]¶ Decorator for specifying groups of fields to be updated together.
- Usage:
>>> from django.db import models >>> from save_the_change.decorators import SaveTheChange, UpdateTogether >>> >>> @SaveTheChange >>> @UpdateTogether( ... ('height_feet', 'height_inches'), ... ('weight_pounds', 'weight_kilos') ... ) >>> class Knight(models.model): >>> ...
-
save_the_change.decorators.
TrackChanges
(cls)[source]¶ Decorator that adds some methods and properties to models for working with changed fields.
has_changed
True
if any fields on the model have changed from its last known database representation.changed_fields
- A
set
of the names of all changed fields on the model. old_values
- The model’s fields in their last known database representation as a
read-only mapping (
OldValues
). revert_fields()
- Reverts the given fields back to their last known database representation.
Internals¶
-
class
save_the_change.decorators.
STCMixin
(*args, **kwargs)[source]¶ Hooks into
__init__()
,save()
, andrefresh_from_db()
, and adds some new, private attributes to the model:
-
save_the_change.decorators.
_inject_stc
(cls)[source]¶ Wraps model attributes in descriptors to track their changes.
Injects a mixin into the model’s __bases__ as well to handle the {create,load}/change/save lifecycle, and adds some attributes to the model’s
_meta
:
-
save_the_change.decorators.
_save_the_change_save_hook
(instance, *args, **kwargs)[source]¶ Sets
update_fields
onsave()
to only what’s changed.update_fields
is only set if it doesn’t already exist and when doing so is safe. This means its not set if the instance is new and yet to be saved to the database, if the instance is being saved with a new primary key, or ifsave()
has been called withforce_insert
.Returns: (continue_saving, args, kwargs) Return type: tuple
-
save_the_change.decorators.
_update_together_save_hook
(instance, *args, **kwargs)[source]¶ Sets
update_fields
onsave()
to include any fields that have been marked as needing to be updated together with fields already inupdate_fields
.Returns: (continue_saving, args, kwargs) Return type: tuple
-
class
save_the_change.descriptors.
ChangeTrackingDescriptor
(name, django_descriptor=None)[source]¶ Descriptor that wraps model attributes to detect changes.
Not all fields in older versions of Django are represented by descriptors themselves, so we handle both getting/setting bare attributes on the model and calling out to descriptors if they exist.
-
save_the_change.descriptors.
_inject_descriptors
(cls)[source]¶ Iterates over concrete fields in a model and wraps them in a descriptor to track their changes.
-
save_the_change.util.
is_mutable
(obj)[source]¶ Checks if given object is likely mutable.
Parameters: obj – object to check. We check that the object is itself a known immutable type, and then attempt to recursively check any objects within it. Strings are special cased to prevent us getting stuck in an infinite loop.
Returns: True
if the object is likely mutable,False
if it definitely is not.Return type: bool
Save The Change¶
Save The Change takes this:
>>> lancelot = Knight.objects.get(name="Sir Lancelot")
>>> lancelot.favorite_color = "Blue"
>>> lancelot.save()
And does this:
UPDATE "roundtable_knight"
SET "favorite_color" = 'Blue'
Instead of this:
UPDATE "roundtable_knight"
SET "name" = 'Sir Lancelot',
"from" = 'Camelot',
"quest" = 'To seek the Holy Grail.',
"favorite_color" = 'Blue',
"epithet" = 'The brave',
"actor" = 'John Cleese',
"full_name" = 'John Marwood Cleese',
"height" = '6''11"',
"birth_date" = '1939-10-27',
"birth_union" = 'UK',
"birth_country" = 'England',
"birth_county" = 'Somerset',
"birth_town" = 'Weston-Super-Mare',
"facial_hair" = 'mustache',
"graduated" = true,
"university" = 'Cambridge University',
"degree" = 'LL.B.',
Installation¶
Install Save The Change just like everything else:
$ pip install django-save-the-change
Usage¶
Just add the SaveTheChange
decorator to
your model:
from django.db import models
from save_the_change.decorators import SaveTheChange
@SaveTheChange
class Knight(models.model):
...
And that’s it! Keep using Django like you always have, Save The Change will take care of you.
How It Works¶
Save The Change encapsulates the fields of your model with its own descriptors
that track their values for any changes. When you call
save()
, Save The Change passes the names of
your changed fields through Django’s update_fields
argument, and Django does
the rest, sending only those fields back to the database.
Caveats¶
Save The Change can’t help you with
ManyToManyField
s nor reverse relations, as
those aren’t handled through save()
. But
everything else should work.
Goodies¶
Save The Change also comes with two additional decorators,
TrackChanges
and
UpdateTogether
.
TrackChanges
provides some additional
properties and methods to keep interact with changes made to your model,
including comparing the old and new values and reverting any changes to your
model before you save it. It can be used independently
of SaveTheChange
.
UpdateTogether
is an additional decorator
which allows you to specify groups of fields that are dependent on each other in
your model, ensuring that if any of them change they’ll all be saved together.
For example:
from django.db import models
from save_the_change.decorators import SaveTheChange, UpdateTogether
@SaveTheChange
@UpdateTogether(('height_feet', 'height_inches'))
class Knight(models.model):
...
Now if you ever make a change to either part of our Knight’s height, both the feet and the inches will be sent to the database together, so that they can’t accidentally fall out of sync.