Is there a way to make a model read-only in the django admin? but I mean the whole model. So, no adding, no deleting, no changing, just see the objects and the fields, everything as read-only?
-
1It's a work in progress it seems: https://github.com/django/django/pull/5297 – Bosco Dec 03 '15 at 11:06
-
1note, overriding has_change_permission() will allow to add/remove only, but not to edit existing records (in Django Admin) – Nishi Jul 27 '20 at 12:01
6 Answers
ModelAdmin provides the hook get_readonly_fields() - the following is untested, my idea being to determine all fields the way ModelAdmin does it, without running into a recursion with the readonly fields themselves:
from django.contrib.admin.util import flatten_fieldsets
class ReadOnlyAdmin(ModelAdmin):
def get_readonly_fields(self, request, obj=None):
if self.declared_fieldsets:
fields = flatten_fieldsets(self.declared_fieldsets)
else:
form = self.get_formset(request, obj).form
fields = form.base_fields.keys()
return fields
then subclass/mixin this admin whereever it should be a read-only admin.
For add/delete, and to make their buttons disappear, you'll probably also want to add
def has_add_permission(self, request):
# Nobody is allowed to add
return False
def has_delete_permission(self, request, obj=None):
# Nobody is allowed to delete
return False
P.S.: In ModelAdmin, if has_change_permission (lookup or your override) returns False, you don't get to the change view of an object - and the link to it won't even be shown. It would actually be cool if it did, and the default get_readonly_fields() checked the change permission and set all fields to readonly in that case, like above. That way non-changers could at least browse the data... given that the current admin structure assumes view=edit, as jathanism points out, this would probably require the introduction of a "view" permission on top of add/change/delete...
EDIT: regarding setting all fields readonly, also untested but looking promising:
readonly_fields = MyModel._meta.get_all_field_names()
EDIT: Here's another one
if self.declared_fieldsets:
return flatten_fieldsets(self.declared_fieldsets)
else:
return list(set(
[field.name for field in self.opts.local_fields] +
[field.name for field in self.opts.local_many_to_many]
))

- 12,498
- 4
- 43
- 49
-
1P.S.: I've now created a feature request to that effect https://code.djangoproject.com/ticket/17295 – Danny W. Adair Nov 25 '11 at 08:56
-
1
-
Hi, thanks for this compilation of options. Might be polite though to also provide the links to the answers you got the pieces from, as I see they are not all yours. Another upside would be that readers could also see the comments others made on each of the suggestions. – steps Sep 19 '13 at 11:22
-
3Doesn't work in Django 1.11 - AttributeError: 'ReadOnlyAdmin' object has no attribute 'declared_fieldsets' – turbotux Jul 27 '17 at 18:21
-
`_meta.get_all_field_names()` was removed in Django 1.10 https://docs.djangoproject.com/en/1.10/ref/models/meta/#migrating-from-the-old-api – hashlash Jun 15 '19 at 21:07
-
Annoyingly, edits require 6+ characters so I wasn't able to suggest the edit, but flatten_fieldsets appears to be in django.contrib.admin.utils (plural utils) – Oded Apr 20 '21 at 17:35
As "view permissions" will not make it into Django 1.11, unfortunately, here's a solution that makes your ModelAdmin read-only by making both saving model changes and adding model history log entries a no-op.
def false(*args, **kwargs):
"""A simple no-op function to make our changes below readable."""
return False
class MyModelReadOnlyAdmin(admin.ModelAdmin):
list_display = [
# list your admin listview entries here (as usual)
]
readonly_fields = [
# list your read-only fields here (as usual)
]
actions = None
has_add_permission = false
has_delete_permission = false
log_change = false
message_user = false
save_model = false
(NOTE: Don't mistake the false
no-op helper with the False
builtin. If you don't sympathize with the helper function outside the class move it into the class, call it no_op
or something else, or override the affected attributes by usual def
s. Less DRY, but if you don't care...)
This will:
- remove the actions drop-down box (with "delete") in the list view
- disallow adding new model entries
- disallow deleting existing model entries
- avoid creating log entries in the model history
- avoid displaying "was changed successfully" messages after saving
- avoid saving changeform changes to the database
It will not:
- remove or replace the two buttons "Save and continue editing" and "SAVE" (which would be nice to improve the user experience)
Note that get_all_field_names
(as mentioned in the accepted answer) was removed in Django 1.10.
Tested with Django 1.10.5.

- 15,097
- 3
- 28
- 29
The selected answer doesn't work for Django 1.11, and I've found a much simpler way to do it so I thought I'd share:
class MyModelAdmin(admin.ModelAdmin):
def get_readonly_fields(self, request, obj=None):
return [f.name for f in obj._meta.fields]
def has_delete_permission(self, request, obj=None):
return False
def has_add_permission(self, request):
return False

- 6,010
- 6
- 38
- 61
You may customize your ModelAdmin
classes with the readonly_fields
attribute. See this answer for more.
-
but is there a way to make the **whole** model as read_only, without having to add all its attributes to the readonly_fields list? – juliomalegria Oct 27 '11 at 19:40
-
1There is, but not easily. You are able to create custom permissions in Django, but when it comes to the admin site it is non-trivial. Django assumes that if people are allowed to view content in the admin interface, they’re also allowed to edit it. You might try the suggestion from [this question](http://stackoverflow.com/questions/4334049/make-a-django-model-read-only) if you're feeling bold. IMO it's just easier to explicitly set all the fields readonly and move on. – jathanism Oct 27 '11 at 21:00
I had a similar scenario where:
- User should be able to create the model objects
- User should be able to view listing of model objects
- User SHOULD'NT be able to edit an object once it's been created
1. Overriding the Change View
Because it's possible to override the change_view()
in a ModelAdmin, we can exploit that to prevent the editing of model instances once they have been created. Here's an example I've used:
def change_view(self, request, object_id, form_url='', extra_context=None):
messages.error(request, 'Sorry, but editing is NOT ALLOWED')
return redirect(request.META['HTTP_REFERER'])
2. Conditionally Change Edit Permissions
I also realized that the docs interpret the result of ModelAdmin.has_change_permission()
in different ways:
Should return True if editing obj is permitted, False otherwise. If obj is None, should return True or False to indicate whether editing of objects of this type is permitted in general (e.g., False will be interpreted as meaning that the current user is not permitted to edit any object of this type).
Meaning I could check whether obj
is None
, in which case I return True
, otherwise I return False
, and this in effect allows users to view the change-list, but not be able to edit nor view the change_form after the model instance is saved.
def has_change_permission(self, request, obj = None, **kwargs):
if obj is None:
return True
else:
return False
Though am thinking this might also override any MODEL_can_change
permissions allowing unwanted eyes from viewing the change-list?

- 13,591
- 7
- 57
- 63
According to my test on Django 1.8 we can not use following as noted on answer #3 but it works on Django 1.4:
## self.get_formset(request, obj) ##
answer 3 needs fix. Generally, alternative codes for this issue about below section
## form = self.get_formset(request, obj).form ##
## fields = form.base_fields.keys() ##
Can be something like:
#~ (A) or
[f.name for f in self.model._meta.fields]
#~ (B) or
MyModel._meta.get_all_field_names()
#~ (C)
list(set([field.name for field in self.opts.local_fields] +
[field.name for field in self.opts.local_many_to_many]
))

- 5,753
- 72
- 57
- 129