|
1 | 1 | History Diffing
|
2 | 2 | ===============
|
3 | 3 |
|
4 |
| -When you have two instances of the same ``HistoricalRecord`` (such as the ``HistoricalPoll`` example above), |
5 |
| -you can perform diffs to see what changed. This will result in a ``ModelDelta`` containing the following properties: |
| 4 | +When you have two instances of the same historical model |
| 5 | +(such as the ``HistoricalPoll`` example above), |
| 6 | +you can perform a diff using the ``diff_against()`` method to see what changed. |
| 7 | +This will return a ``ModelDelta`` object with the following attributes: |
6 | 8 |
|
7 |
| -1. A list with each field changed between the two historical records |
8 |
| -2. A list with the names of all fields that incurred changes from one record to the other |
9 |
| -3. the old and new records. |
| 9 | +- ``old_record`` and ``new_record``: The old and new history records |
| 10 | +- ``changed_fields``: A list of the names of all fields that were changed between |
| 11 | + ``old_record`` and ``new_record``, in alphabetical order |
| 12 | +- ``changes``: A list of ``ModelChange`` objects - one for each field in |
| 13 | + ``changed_fields``, in the same order. |
| 14 | + These objects have the following attributes: |
10 | 15 |
|
11 |
| -This may be useful when you want to construct timelines and need to get only the model modifications. |
| 16 | + - ``field``: The name of the changed field |
| 17 | + (this name is equal to the corresponding field in ``changed_fields``) |
| 18 | + - ``old`` and ``new``: The old and new values of the changed field |
| 19 | + |
| 20 | + - For many-to-many fields, these values will be lists of dicts from the through |
| 21 | + model field names to the primary keys of the through model's related objects. |
| 22 | + The lists are sorted by the value of the many-to-many related object. |
| 23 | + |
| 24 | +This may be useful when you want to construct timelines and need to get only |
| 25 | +the model modifications. |
12 | 26 |
|
13 | 27 | .. code-block:: python
|
14 | 28 |
|
15 |
| - p = Poll.objects.create(question="what's up?") |
16 |
| - p.question = "what's up, man?" |
17 |
| - p.save() |
| 29 | + poll = Poll.objects.create(question="what's up?") |
| 30 | + poll.question = "what's up, man?" |
| 31 | + poll.save() |
18 | 32 |
|
19 |
| - new_record, old_record = p.history.all() |
| 33 | + new_record, old_record = poll.history.all() |
20 | 34 | delta = new_record.diff_against(old_record)
|
21 | 35 | for change in delta.changes:
|
22 |
| - print("{} changed from {} to {}".format(change.field, change.old, change.new)) |
| 36 | + print(f"'{change.field}' changed from '{change.old}' to '{change.new}'") |
| 37 | +
|
| 38 | + # Output: |
| 39 | + # 'question' changed from 'what's up?' to 'what's up, man?' |
| 40 | +
|
| 41 | +``diff_against()`` also accepts the following additional arguments: |
| 42 | + |
| 43 | +- ``excluded_fields`` and ``included_fields``: These can be used to either explicitly |
| 44 | + exclude or include fields from being diffed, respectively. |
| 45 | +- ``foreign_keys_are_objs``: |
| 46 | + |
| 47 | + - If ``False`` (default): The diff will only contain the raw primary keys of any |
| 48 | + ``ForeignKey`` fields. |
| 49 | + - If ``True``: The diff will contain the actual related model objects instead of just |
| 50 | + the primary keys. |
| 51 | + Deleted related objects (both foreign key objects and many-to-many objects) |
| 52 | + will be instances of ``DeletedObject``, which only contain a ``model`` field with a |
| 53 | + reference to the deleted object's model, as well as a ``pk`` field with the value of |
| 54 | + the deleted object's primary key. |
| 55 | + |
| 56 | + Note that this will add extra database queries for each related field that's been |
| 57 | + changed - as long as the related objects have not been prefetched |
| 58 | + (using e.g. ``select_related()``). |
| 59 | + |
| 60 | + A couple examples showing the difference: |
| 61 | + |
| 62 | + .. code-block:: python |
| 63 | +
|
| 64 | + # --- Effect on foreign key fields --- |
| 65 | +
|
| 66 | + whats_up = Poll.objects.create(pk=15, name="what's up?") |
| 67 | + still_around = Poll.objects.create(pk=31, name="still around?") |
| 68 | +
|
| 69 | + choice = Choice.objects.create(poll=whats_up) |
| 70 | + choice.poll = still_around |
| 71 | + choice.save() |
| 72 | +
|
| 73 | + new, old = choice.history.all() |
| 74 | +
|
| 75 | + default_delta = new.diff_against(old) |
| 76 | + # Printing the changes of `default_delta` will output: |
| 77 | + # 'poll' changed from '15' to '31' |
| 78 | +
|
| 79 | + delta_with_objs = new.diff_against(old, foreign_keys_are_objs=True) |
| 80 | + # Printing the changes of `delta_with_objs` will output: |
| 81 | + # 'poll' changed from 'what's up?' to 'still around?' |
| 82 | +
|
| 83 | + # Deleting all the polls: |
| 84 | + Poll.objects.all().delete() |
| 85 | + delta_with_objs = new.diff_against(old, foreign_keys_are_objs=True) |
| 86 | + # Printing the changes of `delta_with_objs` will now output: |
| 87 | + # 'poll' changed from 'Deleted poll (pk=15)' to 'Deleted poll (pk=31)' |
| 88 | +
|
| 89 | +
|
| 90 | + # --- Effect on many-to-many fields --- |
| 91 | +
|
| 92 | + informal = Category.objects.create(pk=63, name="informal questions") |
| 93 | + whats_up.categories.add(informal) |
| 94 | +
|
| 95 | + new = whats_up.history.latest() |
| 96 | + old = new.prev_record |
| 97 | +
|
| 98 | + default_delta = new.diff_against(old) |
| 99 | + # Printing the changes of `default_delta` will output: |
| 100 | + # 'categories' changed from [] to [{'poll': 15, 'category': 63}] |
| 101 | +
|
| 102 | + delta_with_objs = new.diff_against(old, foreign_keys_are_objs=True) |
| 103 | + # Printing the changes of `delta_with_objs` will output: |
| 104 | + # 'categories' changed from [] to [{'poll': <Poll: what's up?>, 'category': <Category: informal questions>}] |
23 | 105 |
|
24 |
| -``diff_against`` also accepts 2 arguments ``excluded_fields`` and ``included_fields`` to either explicitly include or exclude fields from being diffed. |
| 106 | + # Deleting all the categories: |
| 107 | + Category.objects.all().delete() |
| 108 | + delta_with_objs = new.diff_against(old, foreign_keys_are_objs=True) |
| 109 | + # Printing the changes of `delta_with_objs` will now output: |
| 110 | + # 'categories' changed from [] to [{'poll': <Poll: what's up?>, 'category': DeletedObject(model=<class 'models.Category'>, pk=63)}] |
0 commit comments