From 2c71785d572d96d81d59607668f5f0d7ed9f4a5e Mon Sep 17 00:00:00 2001 From: Marco Silva Date: Tue, 2 Mar 2021 08:46:18 -0700 Subject: [PATCH 1/6] POC for #72 Note that if you are using any custom methods on the model for the admin display, you need to use a History Base class for this to work(#311). --- simple_history/admin.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/simple_history/admin.py b/simple_history/admin.py index 34e3033fe..45fe3353b 100644 --- a/simple_history/admin.py +++ b/simple_history/admin.py @@ -283,3 +283,16 @@ def enforce_history_permissions(self): return getattr( settings, "SIMPLE_HISTORY_ENFORCE_HISTORY_MODEL_PERMISSIONS", False ) + + +class SimpleHistoryShowDeletedFilter(admin.SimpleListFilter): + title = "Entries" + parameter_name = "entries" + + def lookups(self, request, model_admin): + return (("deleted_only", "Only Deleted"),) + + def queryset(self, request, queryset): + if self.value(): + return queryset.model.history.filter(history_type="-").distinct() + return queryset From 8e06dc0866e844807a2231cc0586a8c983ed8216 Mon Sep 17 00:00:00 2001 From: Marco Silva Date: Thu, 4 Mar 2021 08:48:36 -0700 Subject: [PATCH 2/6] Fix list_display urls to point to /model//history --- simple_history/admin.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/simple_history/admin.py b/simple_history/admin.py index 45fe3353b..d228be325 100644 --- a/simple_history/admin.py +++ b/simple_history/admin.py @@ -296,3 +296,24 @@ def queryset(self, request, queryset): if self.value(): return queryset.model.history.filter(history_type="-").distinct() return queryset + + +class YourModelAdmin(SimpleHistoryAdmin): + def get_changelist(self, request, **kwargs): + def url_from_result_maker(history=False): + def custom_url_for_result(self, result): + pk = getattr(result, self.pk_attname) + from django.urls import reverse + from django.contrib.admin.utils import quote + route_type = 'history' if history else 'change' + route = f'{self.opts.app_label}_{self.opts.model_name}_{route_type}' + return reverse(f'admin:{route}', + args=(quote(pk),), + current_app=self.model_admin.admin_site.name) + return custom_url_for_result + changelist = super().get_changelist(request, **kwargs) + if request.GET.get('entries', None) == 'deleted_only': + changelist.url_for_result = url_from_result_maker(history=True) + else: + changelist.url_for_result = url_from_result_maker(history=False) + return changelist From fbb1032c41b6d8ec884da0fbf2719ebcf50e2c72 Mon Sep 17 00:00:00 2001 From: Rob Verhoeven Date: Sat, 9 Apr 2022 09:57:14 -0600 Subject: [PATCH 3/6] Collating snippets into derivable class --- simple_history/admin.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/simple_history/admin.py b/simple_history/admin.py index d228be325..664de61e1 100644 --- a/simple_history/admin.py +++ b/simple_history/admin.py @@ -3,7 +3,7 @@ from django.conf import settings from django.contrib import admin from django.contrib.admin import helpers -from django.contrib.admin.utils import unquote +from django.contrib.admin.utils import quote, unquote from django.contrib.auth import get_permission_codename, get_user_model from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404, render @@ -285,35 +285,38 @@ def enforce_history_permissions(self): ) -class SimpleHistoryShowDeletedFilter(admin.SimpleListFilter): - title = "Entries" - parameter_name = "entries" +class SimpleHistoryWithDeletedAdmin(SimpleHistoryAdmin): + class SimpleHistoryShowDeletedFilter(admin.SimpleListFilter): + title = "Entries" + parameter_name = "entries" - def lookups(self, request, model_admin): - return (("deleted_only", "Only Deleted"),) + def lookups(self, request, model_admin): + return (("deleted_only", "Only Deleted"),) - def queryset(self, request, queryset): - if self.value(): - return queryset.model.history.filter(history_type="-").distinct() - return queryset + def queryset(self, request, queryset): + if self.value(): + return queryset.model.history.filter(history_type="-").distinct() + return queryset - -class YourModelAdmin(SimpleHistoryAdmin): def get_changelist(self, request, **kwargs): def url_from_result_maker(history=False): def custom_url_for_result(self, result): pk = getattr(result, self.pk_attname) - from django.urls import reverse - from django.contrib.admin.utils import quote route_type = 'history' if history else 'change' route = f'{self.opts.app_label}_{self.opts.model_name}_{route_type}' return reverse(f'admin:{route}', args=(quote(pk),), current_app=self.model_admin.admin_site.name) return custom_url_for_result + changelist = super().get_changelist(request, **kwargs) if request.GET.get('entries', None) == 'deleted_only': changelist.url_for_result = url_from_result_maker(history=True) else: changelist.url_for_result = url_from_result_maker(history=False) return changelist + + def get_list_filter(self, request): + return [self.SimpleHistoryShowDeletedFilter] + [ + f for f in super().get_list_filter(request) + ] From 7bb7b4fe783437e4d1ff93d018bb54e24ed8c4da Mon Sep 17 00:00:00 2001 From: Kai Duebbert Date: Wed, 25 May 2022 09:55:34 -0600 Subject: [PATCH 4/6] Fix for django 4.0.4 I think the ChangeList.apply_select_related() method might have changed. --- simple_history/admin.py | 64 ++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/simple_history/admin.py b/simple_history/admin.py index 664de61e1..9bf12d5cd 100644 --- a/simple_history/admin.py +++ b/simple_history/admin.py @@ -3,6 +3,7 @@ from django.conf import settings from django.contrib import admin from django.contrib.admin import helpers +from django.contrib.admin.views.main import ChangeList from django.contrib.admin.utils import quote, unquote from django.contrib.auth import get_permission_codename, get_user_model from django.core.exceptions import PermissionDenied @@ -285,38 +286,47 @@ def enforce_history_permissions(self): ) -class SimpleHistoryWithDeletedAdmin(SimpleHistoryAdmin): - class SimpleHistoryShowDeletedFilter(admin.SimpleListFilter): - title = "Entries" - parameter_name = "entries" +class SimpleHistoryChangeList(ChangeList): + def apply_select_related(self, qs): + # Our qs is different if we use the history, so the normal select_related + # won't work and results in an empty QuerySet result. + history = self.params.get("entries", None) == "deleted_only" + if history: + return qs + return super().apply_select_related(qs) + + def url_for_result(self, result) -> str: + history = self.params.get("entries", None) == "deleted_only" + route_type = "history" if history else "change" + route = f"{self.opts.app_label}_{self.opts.model_name}_{route_type}" + pk = getattr(result, self.pk_attname) + return reverse( + f"admin:{route}", + args=(quote(pk),), + current_app=self.model_admin.admin_site.name, + ) + + +class SimpleHistoryShowDeletedFilter(admin.SimpleListFilter): + title = "Entries" + parameter_name = "entries" - def lookups(self, request, model_admin): - return (("deleted_only", "Only Deleted"),) + def lookups(self, request, model_admin): + return (("deleted_only", "Only Deleted"),) - def queryset(self, request, queryset): - if self.value(): - return queryset.model.history.filter(history_type="-").distinct() - return queryset + def queryset(self, request, queryset): + if self.value(): + return queryset.model.history.filter(history_type="-").distinct() + return queryset + +class SimpleHistoryWithDeletedAdmin(SimpleHistoryAdmin): def get_changelist(self, request, **kwargs): - def url_from_result_maker(history=False): - def custom_url_for_result(self, result): - pk = getattr(result, self.pk_attname) - route_type = 'history' if history else 'change' - route = f'{self.opts.app_label}_{self.opts.model_name}_{route_type}' - return reverse(f'admin:{route}', - args=(quote(pk),), - current_app=self.model_admin.admin_site.name) - return custom_url_for_result - - changelist = super().get_changelist(request, **kwargs) - if request.GET.get('entries', None) == 'deleted_only': - changelist.url_for_result = url_from_result_maker(history=True) - else: - changelist.url_for_result = url_from_result_maker(history=False) - return changelist + return SimpleHistoryChangeList def get_list_filter(self, request): - return [self.SimpleHistoryShowDeletedFilter] + [ + # Doing it here will add it to every inherited class. Alternatively, + # add SimpleHistoryShowDeletedFilter to the list_filter and remove the below. + return [SimpleHistoryShowDeletedFilter] + [ f for f in super().get_list_filter(request) ] From 2ccf5505c177221ac98009567528d16f640cd883 Mon Sep 17 00:00:00 2001 From: Marco Sirabella Date: Thu, 27 Apr 2023 11:03:57 -0600 Subject: [PATCH 5/6] Some simple tidying .distinct() -> .latest_of_each() prevents multiple deletes from adding multiple rows for the same object. --- simple_history/admin.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/simple_history/admin.py b/simple_history/admin.py index 9bf12d5cd..6249112a3 100644 --- a/simple_history/admin.py +++ b/simple_history/admin.py @@ -290,8 +290,7 @@ class SimpleHistoryChangeList(ChangeList): def apply_select_related(self, qs): # Our qs is different if we use the history, so the normal select_related # won't work and results in an empty QuerySet result. - history = self.params.get("entries", None) == "deleted_only" - if history: + if self.params.get("entries", None) == "deleted_only": return qs return super().apply_select_related(qs) @@ -316,7 +315,7 @@ def lookups(self, request, model_admin): def queryset(self, request, queryset): if self.value(): - return queryset.model.history.filter(history_type="-").distinct() + return queryset.model.history.filter(history_type="-").latest_of_each() return queryset @@ -327,6 +326,4 @@ def get_changelist(self, request, **kwargs): def get_list_filter(self, request): # Doing it here will add it to every inherited class. Alternatively, # add SimpleHistoryShowDeletedFilter to the list_filter and remove the below. - return [SimpleHistoryShowDeletedFilter] + [ - f for f in super().get_list_filter(request) - ] + return [SimpleHistoryShowDeletedFilter, *super().get_list_filter(request)] From e0015cc028ff6b4e68344206e2abe82b8ef35cd8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 28 Jul 2023 22:47:33 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- simple_history/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simple_history/admin.py b/simple_history/admin.py index 6249112a3..d57d9a357 100644 --- a/simple_history/admin.py +++ b/simple_history/admin.py @@ -3,8 +3,8 @@ from django.conf import settings from django.contrib import admin from django.contrib.admin import helpers -from django.contrib.admin.views.main import ChangeList from django.contrib.admin.utils import quote, unquote +from django.contrib.admin.views.main import ChangeList from django.contrib.auth import get_permission_codename, get_user_model from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404, render @@ -311,7 +311,7 @@ class SimpleHistoryShowDeletedFilter(admin.SimpleListFilter): parameter_name = "entries" def lookups(self, request, model_admin): - return (("deleted_only", "Only Deleted"),) + return (("deleted_only", "Only Deleted"),) def queryset(self, request, queryset): if self.value():