Django Admin: Dynamically Controlling Read-Only Fields with get_readonly_fields()


Purpose

  • It allows you to make certain fields uneditable based on specific conditions or user permissions.
  • This method is used within a custom ModelAdmin class to dynamically control which fields in your model are displayed as read-only on the Django admin interface's edit form.

How it Works

    • You define get_readonly_fields() within your ModelAdmin class.
    • It takes three arguments:
      • self: Reference to the current ModelAdmin instance.
      • request: The Django HTTP request object (optional).
      • obj: The model instance being edited (optional).
  1. Logic Implementation

    • Inside the method, you implement the logic to determine which fields should be read-only. This can involve checks on:
      • User permissions: You can use Django's permission system to restrict editability based on user roles.
      • Model instance state: You can make fields read-only based on the values of other fields in the same model instance.
      • Any other custom criteria specific to your application.
  2. Returning the List

    • Based on your logic, return a list of field names (strings) that you want to make read-only.

Example

from django.contrib import admin

class MyModelAdmin(admin.ModelAdmin):
    def get_readonly_fields(self, request, obj=None):
        if obj is not None and obj.is_published:  # Make fields read-only if published
            return ['title', 'content']  # List of fields to make read-only
        return []  # No fields read-only by default

admin.site.register(MyModel, MyModelAdmin)

Key Points

  • For more complex logic, you can access the model's fields using self.model._meta.fields.
  • You can use self.readonly_fields (defined directly in the ModelAdmin class) to set a static list of read-only fields, but get_readonly_fields() allows for dynamic control based on conditions.
  • If get_readonly_fields() returns an empty list, no fields will be read-only (default behavior).


Making Fields Read-Only Based on User Permissions

from django.contrib import admin
from django.contrib.auth.models import User

class PostAdmin(admin.ModelAdmin):
    def get_readonly_fields(self, request, obj=None):
        if not request.user.is_superuser:  # Only superusers can edit all fields
            return ['author', 'created_at']  # Make author and created_at read-only for others
        return []  # No fields read-only for superusers

admin.site.register(Post, PostAdmin)

Making Fields Read-Only Based on Model Instance State

from django.contrib import admin

class OrderAdmin(admin.ModelAdmin):
    def get_readonly_fields(self, request, obj=None):
        if obj is not None and obj.status == 'shipped':  # Make fields read-only for shipped orders
            return ['customer', 'items', 'total_price']  # These fields cannot be changed
        return []  # No fields read-only for non-shipped orders

admin.site.register(Order, OrderAdmin)

Making Inline Fields Read-Only

This example showcases how to make inline form fields read-only within a StackedInline or TabularInline class:

from django.contrib import admin

class BookInline(admin.StackedInline):
    model = Author
    readonly_fields = ['name', 'birth_date']  # Make name and birth_date read-only for authors

class AuthorAdmin(admin.ModelAdmin):
    inlines = [BookInline]

admin.site.register(Author, AuthorAdmin)

In this case, the readonly_fields attribute is defined directly within the BookInline class, making the specified fields read-only for all authors, regardless of their creation or edit state.



readonly_fields Attribute

  • Directly define a tuple or list of field names within your ModelAdmin class:
  • This is a simpler option for static read-only fields.
class MyModelAdmin(admin.ModelAdmin):
    readonly_fields = ['created_at', 'updated_at']  # These fields will always be read-only
  • This approach is suitable when you know upfront which fields should never be edited and don't require dynamic control.

Custom ModelForm

  • Override the __init__ method of your custom form to set specific fields as read-only using the widget attribute:
  • Create a custom ModelForm class that inherits from the default form generated by Django.
from django import forms

class MyModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['created_at'].widget = forms.widgets.TextInput(attrs={'readonly': True})

class MyModelAdmin(admin.ModelAdmin):
    form = MyModelForm
  • This provides more granular control over individual field widgets and their attributes.

JavaScript-Based Disabling

  • However, it requires additional JavaScript expertise and might be less robust than server-side control.
  • This approach can be useful for complex scenarios where read-only behavior depends on user interaction or real-time data changes.
  • Employ JavaScript code to dynamically disable fields on the admin edit form.
  • Opt for JavaScript-based disabling cautiously, as it adds complexity and potential security concerns.
  • If you require dynamic control or more granular customization of form widgets, consider a custom ModelForm.
  • For basic needs, readonly_fields is often sufficient.