Skip to content

Commit f275f0a

Browse files
aclark4lifetimgraham
authored andcommitted
Add EncryptedModelAdmin for encrypted fields
1 parent 71a611d commit f275f0a

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed

django_mongodb_backend/admin.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from django.contrib import admin
2+
from django.contrib.admin.options import IncorrectLookupParameters
3+
from django.contrib.admin.views.main import ChangeList
4+
from django.core.paginator import InvalidPage, Paginator
5+
from django.utils.functional import cached_property
6+
7+
8+
class EncryptedPaginator(Paginator):
9+
@cached_property
10+
def count(self):
11+
return len(self.object_list)
12+
13+
14+
class EncryptedChangeList(ChangeList):
15+
def get_results(self, request):
16+
"""
17+
This is django.contrib.admin.views.main.ChangeList.get_results with
18+
a single modification to avoid COUNT queries.
19+
"""
20+
paginator = self.model_admin.get_paginator(request, self.queryset, self.list_per_page)
21+
result_count = paginator.count
22+
if self.model_admin.show_full_result_count:
23+
# Modification: avoid COUNT query by using len() on the root queryset
24+
full_result_count = len(self.root_queryset)
25+
else:
26+
full_result_count = None
27+
can_show_all = result_count <= self.list_max_show_all
28+
multi_page = result_count > self.list_per_page
29+
if (self.show_all and can_show_all) or not multi_page:
30+
result_list = self.queryset._clone()
31+
else:
32+
try:
33+
result_list = paginator.page(self.page_num).object_list
34+
except InvalidPage as err:
35+
raise IncorrectLookupParameters from err
36+
self.result_count = result_count
37+
self.show_full_result_count = self.model_admin.show_full_result_count
38+
self.show_admin_actions = not self.show_full_result_count or bool(full_result_count)
39+
self.full_result_count = full_result_count
40+
self.result_list = result_list
41+
self.can_show_all = can_show_all
42+
self.multi_page = multi_page
43+
self.paginator = paginator
44+
45+
46+
class EncryptedModelAdmin(admin.ModelAdmin):
47+
"""
48+
A ModelAdmin that uses EncryptedPaginator and EncryptedChangeList
49+
to avoid COUNT queries in the admin changelist.
50+
"""
51+
52+
def get_paginator(self, request, queryset, per_page):
53+
return EncryptedPaginator(queryset, per_page)
54+
55+
def get_changelist(self, request, **kwargs):
56+
return EncryptedChangeList

docs/howto/queryable-encryption.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,5 +306,26 @@ settings:
306306
},
307307
}
308308
309+
Configuring the ``EncryptedModelAdmin``
310+
=======================================
311+
312+
When using the :doc:`the Django admin site <django:ref/contrib/admin/index>`
313+
with models that have encrypted fields, use the :class:`EncryptedModelAdmin`
314+
class to ensure that encrypted fields are handled correctly. To do this, inherit
315+
from :class:`EncryptedModelAdmin` in your admin classes instead of the standard
316+
:class:`~django.contrib.admin.ModelAdmin`.
317+
318+
.. code-block:: python
319+
320+
# myapp/admin.py
321+
from django.contrib import admin
322+
from .models import Patient
323+
from django_mongodb_backend.admin import EncryptedModelAdmin
324+
325+
326+
@admin.register(Patient)
327+
class PatientAdmin(EncryptedModelAdmin):
328+
pass
329+
309330
You are now ready to :doc:`start developing applications
310331
</topics/queryable-encryption>` with Queryable Encryption!

docs/ref/contrib/admin.rst

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
=====
2+
Admin
3+
=====
4+
5+
Django MongoDB Backend supports the Django admin interface. To enable it, ensure
6+
that you have :ref:`specified the default pk field
7+
<specifying the-default-pk-field>` for the
8+
:class:`~django.contrib.admin.apps.AdminConfig` class as described in the
9+
:doc:`Getting Started </intro/configure>` guide.
10+
11+
``EncryptedModelAdmin``
12+
=======================
13+
14+
.. class:: EncryptedModelAdmin
15+
16+
.. versionadded:: 5.2.3
17+
18+
A :class:`~django.contrib.admin.ModelAdmin` subclass that supports models
19+
with encrypted fields. Use this class as a base class for your model's admin
20+
class to ensure that encrypted fields are handled correctly in the admin
21+
interface.
22+
23+
Define a model with encrypted fields:
24+
25+
.. code-block:: python
26+
27+
# myapp/models.py
28+
from django.db import models
29+
from django_mongodb_backend.fields import EmbeddedModelField
30+
31+
32+
class Patient(models.Model):
33+
patient_name = models.CharField(max_length=255)
34+
patient_id = models.BigIntegerField()
35+
patient_record = EmbeddedModelField("PatientRecord")
36+
37+
def __str__(self):
38+
return f"{self.patient_name} ({self.patient_id})"
39+
40+
Register encrypted models with the Django admin using the
41+
``EncryptedModelAdmin`` as shown below::
42+
43+
# myapp/admin.py
44+
from django.contrib import admin
45+
from django_mongodb_backend.admin import EncryptedModelAdmin
46+
from .models import Patient
47+
48+
49+
@admin.register(Patient)
50+
class PatientAdmin(EncryptedModelAdmin):
51+
pass

docs/ref/contrib/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ Notes for Django's :doc:`django:ref/contrib/index` live here.
77
.. toctree::
88
:maxdepth: 1
99

10+
admin
1011
gis

0 commit comments

Comments
 (0)