Django admin site is a very useful feature, it allows you to add (though quite simplistic) CRUD interface. Django admin also logs every admin action, so you have some accountability out of the box. However, accessing these records from the UI is not optimal.
Recently in one project, I needed to display what actions were taken by the admins, so I wanted to display all logs for a given object on the detail view. Weirdly there is no official way to do this, so here is my solution (bear in mind that it is quite hackish).
from django.contrib.admin.checks import BaseModelAdminChecks
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.admin import GenericStackedInline
class ModelAdminLog(GenericStackedInline):
model = LogEntry
# All fields are read-only, obviously
readonly_fields = fields = ["action_time", "user", "change_message"]
# No extra fields so noone can add new logs
extra = 0
# No one can delete logs
can_delete = False
# This is a hackity hack! See below
checks_class = BaseModelAdminChecks
def change_message(self, obj):
return obj.get_change_message()
@admin.register(models.MyModel)
class MyModelAdminAdmin(admin.ModelAdmin):
# ...
inlines = [ModelAdminLog]
Magic is in the ModelAdminLog class, this is an inline model admin which displays all actions taken by admin on an object.
Django has a quite interesting mechanism that allows generic relations <https://docs.djangoproject.com/en/2.1/ref/contrib/contenttypes/#generic-relations>`__, that is a relation where an object can have relation with any registered model. I generally dislike these as I prefer the safety of database-enforced foreign keys, but they are very useful in cases like ``AdminLog.
Generic foreign keys need three attributes on a model:
- object_id --- id of a referenced row;
- content_type --- foreign key to the ContentType table, that defines to which model current row relates to;
- content_object --- a python object that exposes ForeignKey interface using the above two fields.
GenericStackedInline is an inline model admin, that uses GenericForeignKey, Django AdminLog model however only uses object_id and content_type but does not define actual GenericForeignKey field. However GenericStackedInline does not require actual GenericForeignKey as it directly uses object_id and content_type. So I just needed to disable admin checks by re-setting them to base check class here: checks_class = BaseModelAdminChecks.
That's it.