Django admin CRUD permissions

By default, the Django admin allows access to users with superuser and staff permissions -- in case you've never heard of the terms Django superuser, Django staff or Django permissions, see the previous chapter which describes Django user management.

A Django superuser, is its name implies, means it's a user with 'super' permissions. By extension, this means a superuser has access to any page in the Django admin, as well as permissions to Create, Read, Update and Delete any type of model record available in the Django admin. Because Django superusers represent an 'all or nothing' proposition, the Django admin is also designed to allow access to Django staff users.

Any Django user marked as staff is given access to the Django admin, but nothing else, unless explicitly given permissions, as illustrated in figure 11-47.

Figure 11-47. Django admin main page for staff user with no permissions

As you can see in figure 11-47, the main Django admin page is empty. Although this scenario of an empty Django admin main page can also present itself when you have no registered models in a project's admin.py files, this case represents a scenario for a staff user with no explicit model permissions.

In order for staff users to gain access to Django admin pages, they must be given explicit permissions by means of model permissions, given Django admin pages operate on the basis of CRUD model actions (e.g. a Django admin page to create model records, a Django admin page to delete model records).

By default, all Django models are given add, change and delete permissions, which you can assign to staff users. As a consequence, each of these model permissions represents an access permission for a Django admin page.

For example, if a staff user is given the delete permission on a model, it means he's also given access to delete records of said model in the Django admin. Similarly, if a staff user is given the add permission on a model, it means he's given access to the create record page of said model in the Django admin. Finally, if a staff user is given the change permission on a model, it means he's given access to the edit record page of said model in the Django admin.

Note Granting a delete permission to a user on a given model, also requires granting the change permission to fulfill the delete action in the Django admin. This is because the Django admin delete action is available on the Django admin record change page.

As you can deduce from this behavior, by using staff users and model permissions, you can allow very fine grained access to different sections of the Django admin, instead of the 'all or nothing' Django admin superuser behavior.

Still, Django admin staff users have one important missing behavior: the ability to allow read only access for model records in the Django admin. Because Django models default to having add, change and delete permissions (i.e. CUD [Create,Update, Delete] behaviors), a read permission is deemed implicit with the presence of change (i.e. if you're able to change a record, then you're able to read it). Therefore, to achieve a standalone read-only permission in the Django admin, you must add a custom model read permission (i.e. the missing R in CRUD).

The previous chapter describes custom model permissions in greater detail, but I'll describe this process in listing 11-25 for the context of the Django admin by adding a read only permission.

Listing 11-25. Django model with custom read permission and Django admin class enforcing read permission.

# models.py
from django.db import models

class Menu(models.Model):
    name = models.CharField(max_length=30)
    creator = models.CharField(max_length=100,default='Coffeehouse Chef')    

class Item(models.Model):
    menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
    name = models.CharField(max_length=30)
    description = models.CharField(max_length=100)
    class Meta:
        permissions = (
            ('read_item','Can read item'),
        )

# admin.py 
from django.contrib import admin
from coffeehouse.items.models import Item

class ItemAdmin(admin.ModelAdmin):    
      list_per_page = 5
      list_display = ['menu','name','menu_creator']
      def get_readonly_fields(self, request, obj=None):
        if not request.user.is_superuser and request.user.has_perm('items.read_item'):
            return [f.name for f in self.model._meta.fields]        
        return super(ItemAdmin, self).get_readonly_fields(
            request, obj=obj
        )

admin.site.register(Item, ItemAdmin)

The first step highlighted in listing 11-25 is the Item model with a custom permission named read_item with the friendly name 'Can read item'. After you run the Item model in listing 11-25 through its corresponding migration, the Item model will get a custom read_item permission. Next, create a staff user and assign it both the read_item and built-in change permission of Item model. Once a staff user is given these permissions, you must enforce the Django admin class for the Item model only allow read access to users with these permissions.

When a user is given the change permission on a model, the Django admin grants a user access to the Django admin form page, which is used to update records of a given model and is shown from figures 11-23 to 11-44. But since you want to restrict the update functionality to read only, you must set this page's form fields to read only, which is the purpose of the get_readonly_fields() method in the second part of the listing 11-25.

By defining an admin class with a custom get_readonly_fields() method, you can tell the Django admin under which circumstances you want to set a Django admin page's form fields to read only. In this case, you can see the logic of the get_readonly_fields() method in listing 11-25 uses the is_superuser() and has_perm() methods the determine if the calling party is not a superuser (i.e. staff) and if the user has the read_item permission on the Item model. If this last rule is true, then the get_readonly_fields() method sets all the model form fields to read-only, which is the whole purpose the get_readonly_fields() method. If this last rule is false, then the get_readonly_fields() method returns its default behavior calling the parent class's default get_readonly_fields() method.

As you can see with this brief exercise, by using custom Django admin class methods in conjunction with standard and custom Django model permissions, there are no limitations to restricting or allowing CRUD operations in the Django admin.