How to create Django admin with readonly permission for all users?

How to create Django admin with readonly permission for all users?

The TLDR answer:

We all have encounter situations when someone from the product team asks us to create a model dashboard. With Django admin page, this is just a few lines of code. But what about those scenarios when someone asks for access to a crucial table in the admin page, but soon that admin page gets shared across say, 20+ other people, we can always rely on the permissions framework of Django. But we all know that someday someone makes a mistake and gives edit access to someone unwanted, then it all falls back to the developer to fix the blunder.
From my personal experience, I can tell where I needed to create a Django admin for some product manager and then that got forwarded to some marketing team and by mistake, the marketing team person had edit access, and boom, middle of the night I get a call to fix certain data, because of some human error. So what did I learn from my past mistakes? Well, you don't need to rely on some else to give appropriate permission for the admin page, rather you simply block any operation apart from read-only for that given admin.

All you need to do is to use the following Base Class while creating the ModelAdmin. Here I am using Python's multiple Inheritance concept. You need to inherit from BaseReadOnlyAdminMixin and admin.ModelAdmin both. Please do remember the order in which you are declaring, this is very important since we are using multiple inheritance concept here.

class BaseReadOnlyAdminMixin:
    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

from django.contrib import admin

class RandomModelAdmin(BaseReadOnlyAdminMixin, admin.ModelAdmin):
    list_display = ("id", "created_at", "state"), RandomModelAdmin)

Now the RandomModelAdmin is a readonly admin Page. You should be able to see something similar as shown below.

Admin page UI after implementing the Readonly Admin

The Detailed answer:

Now that we have seen the code snippet on how we can create a read-only Django Admin for a model, let us deep dive into what magic these 6 lines of code is doing which is disabling any permission for users to modify content for a table from Django admin.

Like mentioned before we are using multiple inheritance concept here, the first class which we are inheriting from is BaseReadOnlyAdminMixin, this is Mixin Class.

What is a Mixin in Django?

In Object Oriented Programming paradigm, a mixin is a class that contains methods for use by other classes without having to be the parent class of those other classes. That was a lot of technical terms punched inside a single statement, but let to try to simplify that for you, mixin is a class which provides methods for multiple operations to its child classes.

Coming back to our topic, BaseReadOnlyAdminMixin is a Mixin class which helps us to perform operations, if we look deep into the code we can see 3 methods has_add_permission, has_change_permission and has_delete_permission, these are the methods which are called to check if a user has permission to perform certain operations, and we are returning by default False to all these methods. Ultimately whenever someone would request the given admin page then, we shall call this methods and always get False, that is how we are going to make our Admin page as ReadOnly.

Another thing we need to keep in mind is, we are overriding the default methods, so the order of the Class is very important, if you move the order from RandomModelAdmin(BaseReadOnlyAdminMixin, admin.ModelAdmin) to RandomModelAdmin(admin.ModelAdmin, BaseReadOnlyAdminMixin) then you will notice that we no more have the admin page as readonly, this is because of the way how python handles Method Order Resolution (MRO) for multiple inheritance. I won't get into the details of MRO here, but this is something which handles how Python would handle Methods with same name in multiple classes in the same level while we do multiple inheritance.