-
-
Notifications
You must be signed in to change notification settings - Fork 472
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added foreign_keys_are_objs arg to diff_against()
This makes it easier to get the related objects from the diff objects, and facilitates prefetching the related objects when listing the diffs - which is needed for the upcoming changes to the admin history page adding a "Changes" column. The returned `ModelDelta` fields are now alphabetically ordered, to prevent flakiness of one of the added tests. The `old` and `new` M2M lists of `ModelChange` are now also sorted, which will prevent inconsistent ordering of the `Place` objects in `AdminSiteTest.test_history_list_contains_diff_changes_for_m2m_fields()` (will be added as part of the previously mentioned upcoming admin history changes). Lastly, cleaned up some `utils` imports in `models.py`.
- Loading branch information
Showing
5 changed files
with
296 additions
and
28 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,103 @@ | ||
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. | ||
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) # Will raise `Place.DoesNotExist` | ||
# --- 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': None}] |
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.