Introduce admin filter by changed field

This commit is contained in:
Alieh Rymašeŭski 2020-04-17 12:52:56 +03:00
parent 5cd55ac38c
commit f58b3d7685
2 changed files with 40 additions and 3 deletions

View file

@ -6,7 +6,7 @@ from django.utils.functional import cached_property
from .models import LogEntry
from .mixins import LogEntryAdminMixin
from .filters import ResourceTypeFilter
from .filters import ResourceTypeFilter, FieldFilter
class TimeLimitedPaginator(Paginator):
@ -30,7 +30,7 @@ class TimeLimitedPaginator(Paginator):
class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin):
list_display = ['created', 'resource_url', 'action', 'msg_short', 'user_url']
search_fields = ['timestamp', 'object_repr', 'changes', 'actor__first_name', 'actor__last_name']
list_filter = ['action', ResourceTypeFilter]
list_filter = ['action', ResourceTypeFilter, FieldFilter]
readonly_fields = ['created', 'resource_url', 'action', 'user_url', 'msg']
fieldsets = [
(None, {'fields': ['created', 'user_url', 'resource_url']}),

View file

@ -1,7 +1,9 @@
from django.contrib.admin import SimpleListFilter
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import JSONField
from django.db import connection
from django.db.models import Value
from django.db.models.functions import Concat
from django.db.models.functions import Concat, Cast
from auditlog.registry import auditlog
@ -27,3 +29,38 @@ class ResourceTypeFilter(SimpleListFilter):
if self.value() is None:
return queryset
return queryset.filter(content_type_id=self.value())
class FieldFilter(SimpleListFilter):
title = 'Field'
parameter_name = 'field'
parent = ResourceTypeFilter
def __init__(self, request, *args, **kwargs):
self.target_model = self._get_target_model(request)
super().__init__(request, *args, **kwargs)
def _get_target_model(self, request):
# the parameters consumed by previous filters aren't passed to subsequent filters,
# so we have to look into the request parameters explicitly
content_type_id = request.GET.get(self.parent.parameter_name)
if not content_type_id:
return None
return ContentType.objects.get(id=content_type_id).model_class()
def lookups(self, request, model_admin):
if connection.vendor != 'postgresql':
# filtering inside JSON is PostgreSQL-specific for now
return []
if not self.target_model:
return []
return [(field.name, field.name) for field in self.target_model._meta.fields]
def queryset(self, request, queryset):
if self.value() is None:
return queryset
return (
queryset.annotate(changes_json=Cast("changes", JSONField()))
.filter(**{'changes_json__{}__isnull'.format(self.value()): False})
)