[Django]-Django admin: Making a required field read-only

34👍

What I usually do is set the field in the model as editable=False and then in the admin.py set the field as read-only like this,

class PersonData(BaseModel):
    person = models.ForeignKey(Person, editable=False)
    data = models.TextField()

Then in admin.py

class PersonDataAdmin(admin.ModelAdmin):
    readonly_fields=('person',)

Hope this works!

👤devdob

4👍

I hit the same problem. A workaround is to define a method in the Admin class that returns the value of the readonly field that you want to display. Then use the name of the method where you would previously have used the name of the field.
e.g. rather than:

class PersonDataAdmin(admin.ModelAdmin):
    readonly_fields=('person',)

use:

class PersonDataAdmin(admin.ModelAdmin):
    readonly_fields=('person_method',)

    def person_method(self, obj):
        return obj.person

    person_method.short_description = 'Person'

The short_description sets the label for the field. If you don’t set it then the field will be labelled as “Person method”

Presumably in the OPs case get_readonly_fields becomes:

def get_readonly_fields(self, request, obj=None):
    if obj:
        return self.readonly_fields + ('person_method',)
    return self.readonly_fields

3👍

You could extend ModelAdmin.get_form() and use Field.disabled to disable the person field, as follows:

def get_form(self, request, obj=None, change=False, **kwargs):
    form = super().get_form(request, obj, change, **kwargs)
    if obj:
        form.base_fields['person'].disabled = True
    return form

This will prevent the person field from being modified.

However, it will still be styled as a select box, and it may still have the "change" and "add another" buttons next to it.

These buttons are added by the RelatedFieldWidgetWrapper. One way to remove them is by removing the wrapper:

...
field = form.base_fields['person']
field.disabled = True
if isinstance(field.widget, RelatedFieldWidgetWrapper):
    # unwrap the widget
    field.widget = field.widget.widget

Another way is to set the following widget attributes:

...
if isinstance(field.widget, RelatedFieldWidgetWrapper):
    field.widget.can_add_related = False
    field.widget.can_change_related = False

If you want the field to be styled as a readonly field, you’ll need some extra work. Also see the fieldset.html template and AdminReadonlyField to understand how django admin displays foreign-key readonly_fields.

Another approach altogether would be to implement your own ReadOnlyWidget, similar to this, and just set form.base_fields['person'].widget = ReadOnlyWidget()

👤djvg

2👍

field1 is a required field here and it is made as read only in admin screen

model.py

class ModelName(models.Model):
  field1 = models.ForeignKey(
      ModelA, 
      on_delete=models.CASCADE,
      db_column='col1',
      related_name='col1',
      verbose_name='col1'
  )
  field2 = models.ForeignKey(
      ModelB, 
      on_delete=models.CASCADE,
      db_column='col2',
      related_name='col2',
      verbose_name='col2'
  )

admin.py

    @admin.register(ModelName)
    class AdminClassName(admin.ModelAdmin):
        fields = ('field1', 'field2')
        readonly_fields = ('field1')

1👍

Here’s ModelAdmin.readonly_fields documentation.

1👍

In Django – 4.1.3

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

class UserAdmin(admin.ModelAdmin):
   readonly_fields = ['last_login']

admin.site.register(User, UserAdmin)

If you want to make fields read-only for specific users

👤Tintin

0👍

I had the exact same problem of yours. Check if you are using get_form on your admin class. Somehow this was the cause of my problem.

👤Alan

Leave a comment