From 159f260539421e48b9d196d6d3965cfdb4527d50 Mon Sep 17 00:00:00 2001 From: "Jeffrey A. Clark" Date: Tue, 28 Oct 2025 18:58:35 -0400 Subject: [PATCH] Add encrypted admin w/"no count" pagination Existing approaches to "no count" pagination do not seem to include support for pagination in the Django admin, so: - Subclass Django's core Paginator - override count - Subclass Django's contrib admin view ChangeList.get_results - Use len instead of count - Ruff fixes for try/except - Subclass Django's contrib ModelAdmin - override get_paginator - override get_changelist --- django_mongodb_backend/admin.py | 56 +++++++++++++++++++++++++++++++++ docs/ref/contrib/admin.rst | 34 ++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 django_mongodb_backend/admin.py create mode 100644 docs/ref/contrib/admin.rst diff --git a/django_mongodb_backend/admin.py b/django_mongodb_backend/admin.py new file mode 100644 index 000000000..20c04a5f3 --- /dev/null +++ b/django_mongodb_backend/admin.py @@ -0,0 +1,56 @@ +from django.contrib import admin +from django.contrib.admin.options import IncorrectLookupParameters +from django.contrib.admin.views.main import ChangeList +from django.core.paginator import InvalidPage, Paginator +from django.utils.functional import cached_property + + +class EncryptedPaginator(Paginator): + @cached_property + def count(self): + return len(self.object_list) + + +class EncryptedChangeList(ChangeList): + def get_results(self, request): + """ + This is django.contrib.admin.views.main.ChangeList.get_results with + a single modification to avoid COUNT queries. + """ + paginator = self.model_admin.get_paginator(request, self.queryset, self.list_per_page) + result_count = paginator.count + if self.model_admin.show_full_result_count: + # Modification: avoid COUNT query by using len() on the root queryset + full_result_count = len(self.root_queryset) + else: + full_result_count = None + can_show_all = result_count <= self.list_max_show_all + multi_page = result_count > self.list_per_page + if (self.show_all and can_show_all) or not multi_page: + result_list = self.queryset._clone() + else: + try: + result_list = paginator.page(self.page_num).object_list + except InvalidPage as err: + raise IncorrectLookupParameters from err + self.result_count = result_count + self.show_full_result_count = self.model_admin.show_full_result_count + self.show_admin_actions = not self.show_full_result_count or bool(full_result_count) + self.full_result_count = full_result_count + self.result_list = result_list + self.can_show_all = can_show_all + self.multi_page = multi_page + self.paginator = paginator + + +class EncryptedModelAdmin(admin.ModelAdmin): + """ + A ModelAdmin that uses EncryptedPaginator and EncryptedChangeList + to avoid COUNT queries in the admin changelist. + """ + + def get_paginator(self, request, queryset, per_page): + return EncryptedPaginator(queryset, per_page) + + def get_changelist(self, request, **kwargs): + return EncryptedChangeList diff --git a/docs/ref/contrib/admin.rst b/docs/ref/contrib/admin.rst new file mode 100644 index 000000000..d38c7b161 --- /dev/null +++ b/docs/ref/contrib/admin.rst @@ -0,0 +1,34 @@ +===== +Admin +===== + +Django MongoDB Backend supports the Django admin interface. To enable it, ensure +that you have :ref:`specified the default pk field +` for the +:class:`~django.contrib.admin.apps.AdminConfig` class as described in the +:doc:`Getting Started ` guide. + +``EncryptedModelAdmin`` +======================= + +.. class:: EncryptedModelAdmin + + .. versionadded:: 5.2.3 + + A :class:`~django.contrib.admin.ModelAdmin` subclass that supports models + with encrypted fields. Use this class as a base class for your model's admin + class to ensure that encrypted fields are handled correctly in the admin + interface. + + Register encrypted models with the Django admin using the + ``EncryptedModelAdmin`` as shown below:: + + # myapp/admin.py + from django.contrib import admin + from django_mongodb_backend.admin import EncryptedModelAdmin + from .models import Patient + + + @admin.register(Patient) + class PatientAdmin(EncryptedModelAdmin): + pass