Skip to content

Commit c9963aa

Browse files
ddabbleGur Raunaq Singhtim-schilling
committed
Added Changes column to admin history page
This lists the changes between each historical record of the object. Effort has been made to make it easy for library users to override most parts of this, and to let them customize only the details they want to without having to copy or replace our implementation. An overview of how to override it has been added to `admin.rst`. Most of the code was added to `template_utils.py`, to avoid cluttering `SimpleHistoryAdmin`. Also updated the Norwegian Bokmål translations, and updated all the docs screenshots, to make them have Django 5.0's UI and to showcase the new column. Co-authored-by: Gur Raunaq Singh <raunaqsoni.dev123@gmail.com> Co-authored-by: Tim Schilling <schilling711@gmail.com>
1 parent 04eefad commit c9963aa

16 files changed

+793
-36
lines changed

CHANGES.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Unreleased
2222
lists are sorted by the related object. This should help prevent flaky tests. (gh-1128)
2323
- ``diff_against()`` has a new keyword argument, ``foreign_keys_are_objs``;
2424
see usage in the docs under "History Diffing" (gh-1128)
25+
- Added a "Changes" column to ``SimpleHistoryAdmin``'s object history table, listing
26+
the changes between each historical record of the object; see the docs under
27+
"Customizing the History Admin Templates" for overriding its template context (gh-1128)
2528

2629
3.5.0 (2024-02-19)
2730
------------------

docs/admin.rst

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,26 @@ you can override the following attributes with the names of your own templates:
8484
historical record, which also allows you to revert the object to a previous version.
8585

8686
If you'd like to only customize certain parts of the mentioned templates, look for
87-
``block`` template tags in the source code that you can override.
87+
``block`` template tags in the source code that you can override - like the
88+
``history_delta_changes`` block in ``simple_history/object_history_list.html``,
89+
which lists the changes made between each historical record.
90+
91+
Customizing Context
92+
^^^^^^^^^^^^^^^^^^^
93+
94+
You can also customize the template context by overriding the following methods:
95+
96+
- ``render_history_view()``: Called by both ``history_view()`` and
97+
``history_form_view()`` before the templates are rendered. Customize the context by
98+
changing the ``context`` parameter.
99+
- ``history_view()``: Returns a rendered ``object_history_template``.
100+
Inject context by calling the super method with the ``extra_context`` argument.
101+
- ``get_historical_record_context_helper()``: Returns an instance of
102+
``simple_history.template_utils.HistoricalRecordContextHelper`` that's used to format
103+
some template context for each historical record displayed through ``history_view()``.
104+
Customize the context by extending the mentioned class and overriding its methods.
105+
- ``history_form_view()``: Returns a rendered ``object_history_form_template``.
106+
Inject context by calling the super method with the ``extra_context`` argument.
88107

89108

90109
Disabling the option to revert an object

docs/screens/10_revert_disabled.png

-70.8 KB
Loading

docs/screens/1_poll_history.png

-79.6 KB
Loading

docs/screens/2_revert.png

-91.3 KB
Loading

docs/screens/3_poll_reverted.png

-71.9 KB
Loading
-86.2 KB
Loading
-23 KB
Loading

simple_history/admin.py

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
from django.utils.text import capfirst
1717
from django.utils.translation import gettext as _
1818

19-
from .manager import HistoryManager
19+
from .manager import HistoricalQuerySet, HistoryManager
20+
from .models import HistoricalChanges
21+
from .template_utils import HistoricalRecordContextHelper
2022
from .utils import get_history_manager_for_model, get_history_model_for_model
2123

2224
SIMPLE_HISTORY_EDIT = getattr(settings, "SIMPLE_HISTORY_EDIT", False)
@@ -76,14 +78,16 @@ def history_view(self, request, object_id, extra_context=None):
7678
for record in historical_records:
7779
setattr(record, history_list_entry, value_for_entry(record))
7880

81+
self.set_history_delta_changes(request, historical_records)
82+
7983
content_type = self.content_type_model_cls.objects.get_for_model(
8084
get_user_model()
8185
)
82-
8386
admin_user_view = "admin:{}_{}_change".format(
8487
content_type.app_label,
8588
content_type.model,
8689
)
90+
8791
context = {
8892
"title": self.history_view_title(request, obj),
8993
"object_history_list_template": self.object_history_list_template,
@@ -117,10 +121,12 @@ def get_history_queryset(
117121
:param pk_name: The name of the original model's primary key field.
118122
:param object_id: The primary key of the object whose history is listed.
119123
"""
120-
qs = history_manager.filter(**{pk_name: object_id})
124+
qs: HistoricalQuerySet = history_manager.filter(**{pk_name: object_id})
121125
if not isinstance(history_manager.model.history_user, property):
122126
# Only select_related when history_user is a ForeignKey (not a property)
123127
qs = qs.select_related("history_user")
128+
# Prefetch related objects to reduce the number of DB queries when diffing
129+
qs = qs._select_related_history_tracked_objs()
124130
return qs
125131

126132
def get_history_list_display(self, request) -> Sequence[str]:
@@ -131,6 +137,44 @@ def get_history_list_display(self, request) -> Sequence[str]:
131137
"""
132138
return self.history_list_display
133139

140+
def get_historical_record_context_helper(
141+
self, request, historical_record: HistoricalChanges
142+
) -> HistoricalRecordContextHelper:
143+
"""
144+
Return an instance of ``HistoricalRecordContextHelper`` for formatting
145+
the template context for ``historical_record``.
146+
"""
147+
return HistoricalRecordContextHelper(self.model, historical_record)
148+
149+
def set_history_delta_changes(
150+
self,
151+
request,
152+
historical_records: Sequence[HistoricalChanges],
153+
foreign_keys_are_objs=True,
154+
):
155+
"""
156+
Add a ``history_delta_changes`` attribute to all historical records
157+
except the first (oldest) one.
158+
159+
:param request:
160+
:param historical_records:
161+
:param foreign_keys_are_objs: Passed to ``diff_against()`` when calculating
162+
the deltas; see its docstring for details.
163+
"""
164+
previous = None
165+
for current in historical_records:
166+
if previous is None:
167+
previous = current
168+
continue
169+
# Related objects should have been prefetched in `get_history_queryset()`
170+
delta = previous.diff_against(
171+
current, foreign_keys_are_objs=foreign_keys_are_objs
172+
)
173+
helper = self.get_historical_record_context_helper(request, previous)
174+
previous.history_delta_changes = helper.context_for_delta_changes(delta)
175+
176+
previous = current
177+
134178
def history_view_title(self, request, obj):
135179
if self.revert_disabled(request, obj) and not SIMPLE_HISTORY_EDIT:
136180
return _("View history: %s") % force_str(obj)
50 Bytes
Binary file not shown.

simple_history/locale/nb/LC_MESSAGES/django.po

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,31 @@ msgstr ""
1919

2020
# Dette er en tittel, ikke en handlingsbeskrivelse, så f.eks.
2121
# "Se/Vis (endrings)historikk" hadde ikke fungert så bra
22-
#: simple_history/admin.py:102
22+
#: simple_history/admin.py:109
2323
#, python-format
2424
msgid "View history: %s"
2525
msgstr "Endringshistorikk: %s"
2626

27-
#: simple_history/admin.py:104
27+
#: simple_history/admin.py:111
2828
#, python-format
2929
msgid "Change history: %s"
3030
msgstr "Endringshistorikk: %s"
3131

32-
#: simple_history/admin.py:110
32+
#: simple_history/admin.py:117
3333
#, python-format
3434
msgid "The %(name)s \"%(obj)s\" was changed successfully."
3535
msgstr "%(name)s «%(obj)s» ble endret."
3636

37-
#: simple_history/admin.py:116
37+
#: simple_history/admin.py:123
3838
msgid "You may edit it again below"
3939
msgstr "Du kan redigere videre nedenfor"
4040

41-
#: simple_history/admin.py:217
41+
#: simple_history/admin.py:224
4242
#, python-format
4343
msgid "View %s"
4444
msgstr "Se %s"
4545

46-
#: simple_history/admin.py:219
46+
#: simple_history/admin.py:226
4747
#, python-format
4848
msgid "Revert %s"
4949
msgstr "Tilbakestill %s"
@@ -126,6 +126,10 @@ msgstr "Endret av"
126126
msgid "Change reason"
127127
msgstr "Endringsårsak"
128128

129+
#: simple_history/templates/simple_history/object_history_list.html:17
130+
msgid "Changes"
131+
msgstr "Endringer"
132+
129133
#: simple_history/templates/simple_history/object_history_list.html:42
130134
msgid "None"
131135
msgstr "Ingen"

0 commit comments

Comments
 (0)