forked from jazzband/django-simple-history
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request jazzband#1128 from raunaq-sailo/feat/adding-histor…
…y-diff Add history diff column to admin change history table
- Loading branch information
Showing
41 changed files
with
1,921 additions
and
456 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,110 @@ | ||
History Diffing | ||
=============== | ||
|
||
When you have two instances of the same ``HistoricalRecord`` (such as the ``HistoricalPoll`` example above), | ||
you can perform diffs to see what changed. This will result in a ``ModelDelta`` containing the following properties: | ||
When you have two instances of the same historical model | ||
(such as the ``HistoricalPoll`` example above), | ||
you can perform a diff using the ``diff_against()`` method to see what changed. | ||
This will return a ``ModelDelta`` object with the following attributes: | ||
|
||
1. A list with each field changed between the two historical records | ||
2. A list with the names of all fields that incurred changes from one record to the other | ||
3. the old and new records. | ||
- ``old_record`` and ``new_record``: The old and new history records | ||
- ``changed_fields``: A list of the names of all fields that were changed between | ||
``old_record`` and ``new_record``, in alphabetical order | ||
- ``changes``: A list of ``ModelChange`` objects - one for each field in | ||
``changed_fields``, in the same order. | ||
These objects have the following attributes: | ||
|
||
This may be useful when you want to construct timelines and need to get only the model modifications. | ||
- ``field``: The name of the changed field | ||
(this name is equal to the corresponding field in ``changed_fields``) | ||
- ``old`` and ``new``: The old and new values of the changed field | ||
|
||
- For many-to-many fields, these values will be lists of dicts from the through | ||
model field names to the primary keys of the through model's related objects. | ||
The lists are sorted by the value of the many-to-many related object. | ||
|
||
This may be useful when you want to construct timelines and need to get only | ||
the model modifications. | ||
|
||
.. code-block:: python | ||
p = Poll.objects.create(question="what's up?") | ||
p.question = "what's up, man?" | ||
p.save() | ||
poll = Poll.objects.create(question="what's up?") | ||
poll.question = "what's up, man?" | ||
poll.save() | ||
new_record, old_record = p.history.all() | ||
new_record, old_record = poll.history.all() | ||
delta = new_record.diff_against(old_record) | ||
for change in delta.changes: | ||
print("{} changed from {} to {}".format(change.field, change.old, change.new)) | ||
print(f"'{change.field}' changed from '{change.old}' to '{change.new}'") | ||
# Output: | ||
# 'question' changed from 'what's up?' to 'what's up, man?' | ||
``diff_against()`` also accepts the following additional arguments: | ||
|
||
- ``excluded_fields`` and ``included_fields``: These can be used to either explicitly | ||
exclude or include fields from being diffed, respectively. | ||
- ``foreign_keys_are_objs``: | ||
|
||
- If ``False`` (default): The diff will only contain the raw primary keys of any | ||
``ForeignKey`` fields. | ||
- If ``True``: The diff will contain the actual related model objects instead of just | ||
the primary keys. | ||
Deleted related objects (both foreign key objects and many-to-many objects) | ||
will be instances of ``DeletedObject``, which only contain a ``model`` field with a | ||
reference to the deleted object's model, as well as a ``pk`` field with the value of | ||
the deleted object's primary key. | ||
|
||
Note that this will add extra database queries for each related field that's been | ||
changed - as long as the related objects have not been prefetched | ||
(using e.g. ``select_related()``). | ||
|
||
A couple examples showing the difference: | ||
|
||
.. code-block:: python | ||
# --- Effect on foreign key fields --- | ||
whats_up = Poll.objects.create(pk=15, name="what's up?") | ||
still_around = Poll.objects.create(pk=31, name="still around?") | ||
choice = Choice.objects.create(poll=whats_up) | ||
choice.poll = still_around | ||
choice.save() | ||
new, old = choice.history.all() | ||
default_delta = new.diff_against(old) | ||
# Printing the changes of `default_delta` will output: | ||
# 'poll' changed from '15' to '31' | ||
delta_with_objs = new.diff_against(old, foreign_keys_are_objs=True) | ||
# Printing the changes of `delta_with_objs` will output: | ||
# 'poll' changed from 'what's up?' to 'still around?' | ||
# Deleting all the polls: | ||
Poll.objects.all().delete() | ||
delta_with_objs = new.diff_against(old, foreign_keys_are_objs=True) | ||
# Printing the changes of `delta_with_objs` will now output: | ||
# 'poll' changed from 'Deleted poll (pk=15)' to 'Deleted poll (pk=31)' | ||
# --- Effect on many-to-many fields --- | ||
informal = Category.objects.create(pk=63, name="informal questions") | ||
whats_up.categories.add(informal) | ||
new = whats_up.history.latest() | ||
old = new.prev_record | ||
default_delta = new.diff_against(old) | ||
# Printing the changes of `default_delta` will output: | ||
# 'categories' changed from [] to [{'poll': 15, 'category': 63}] | ||
delta_with_objs = new.diff_against(old, foreign_keys_are_objs=True) | ||
# Printing the changes of `delta_with_objs` will output: | ||
# 'categories' changed from [] to [{'poll': <Poll: what's up?>, 'category': <Category: informal questions>}] | ||
``diff_against`` also accepts 2 arguments ``excluded_fields`` and ``included_fields`` to either explicitly include or exclude fields from being diffed. | ||
# Deleting all the categories: | ||
Category.objects.all().delete() | ||
delta_with_objs = new.diff_against(old, foreign_keys_are_objs=True) | ||
# Printing the changes of `delta_with_objs` will now output: | ||
# 'categories' changed from [] to [{'poll': <Poll: what's up?>, 'category': DeletedObject(model=<class 'models.Category'>, pk=63)}] |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.