Last active
April 24, 2023 12:59
-
-
Save jurrian/a932b5064255602dcc6055f6475316ea to your computer and use it in GitHub Desktop.
Django Simple History for list pages
This file contains hidden or 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
{# file: templates/admin/change_list.html #} | |
{% extends 'admin/change_list.html' %} | |
{% load i18n admin_urls %} | |
{% block object-tools-items %} | |
{% if cl.pk_attname != 'history_id' %} | |
<li> | |
{% url opts|admin_urlname:'changelist_history' as history_url %} | |
<a href="{% add_preserved_filters history_url %}" class="historylink">{% translate "History" %}</a> | |
</li> | |
{% endif %} | |
{{ block.super }} | |
{% endblock %} |
This file contains hidden or 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
from django.contrib.admin import ModelAdmin | |
from django.urls import path, reverse | |
from simple_history.admin import SimpleHistoryAdmin | |
def url_for_result(result): | |
"""Urls for history entries should go to the object's history page. | |
""" | |
meta = result.history_object._meta # pylint: disable=protected-access | |
return reverse(f'admin:{meta.app_label}_{meta.model_name}_history', args=[result.id]) | |
class BaseHistoryAdmin(ModelAdmin): | |
"""Base class for django-simple-history admin classes, can be subclassed to add custom functionality. | |
""" | |
actions = None | |
list_filter = ('history_type',) | |
list_display = ('history_object', 'history_date', 'history_user', 'history_type', 'history_change_reason') | |
def has_add_permission(self, request, obj=None): | |
return False | |
def has_delete_permission(self, request, obj=None): | |
return False | |
def get_changelist_instance(self, request): | |
"""Overrule `url_for_result` to point to the object's history page. | |
This cannot happen on `get_changelist()` because it might be changed by other packages like DjangoQL. | |
""" | |
changelist_instance = super().get_changelist_instance(request) | |
changelist_instance.url_for_result = url_for_result | |
return changelist_instance | |
def get_queryset(self, request): | |
"""Join the history user and prefetch if possible. | |
Without the `select_related('history_user')`, `ChangeList.apply_select_related()` | |
will call `select_related()` without parameters which will add a lot of joins. | |
Also try to prefetch the relation to the history object, so we can reduce some queries. | |
""" | |
qs = super().get_queryset(request) | |
if not isinstance(self.model.history_user, property): | |
qs = qs.select_related('history_user') | |
if hasattr(self.model, 'history_relation'): | |
qs = qs.prefetch_related('history_relation') | |
return qs | |
class WMSimpleHistoryAdmin(SimpleHistoryAdmin): | |
"""Allows to see all history entries on a model using an embedded ModelAdmin. | |
Adds a button in the changelist to <app_label>/<model_name>/history/ | |
in addition to the already existing object-level history. | |
""" | |
history_admin = BaseHistoryAdmin | |
def get_history_admin_class(self): | |
"""Returns HistoryAdmin class for model, with `history_admin` as superclass. | |
Change this to tweak the admin class. | |
""" | |
return type(f'{self.model.__name__}HistoryAdmin', (self.history_admin,), {}) | |
def get_urls(self): | |
"""Register additional "changelist history" to the admin urls. | |
""" | |
urls = super().get_urls() | |
admin_site = self.admin_site | |
opts = self.model._meta # pylint: disable=protected-access | |
try: | |
# pylint: disable=protected-access | |
model = getattr(self.model, self.model._meta.simple_history_manager_attribute).model | |
except AttributeError: | |
# History is not enabled, do nothing, just return urls | |
return urls | |
admin_class = self.get_history_admin_class() | |
model_admin_instance = admin_class(admin_site=admin_site, model=model) | |
history_urls = [ | |
path( | |
'history/', | |
admin_site.admin_view(model_admin_instance.changelist_view), | |
name=f'{opts.app_label}_{opts.model_name}_changelist_history', | |
) | |
] | |
return history_urls + urls |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I wrote a blog article about the usage:
https://deepintodjango.com/show-deleted-entries-in-django-simple-history