13đź‘Ť
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]
))
4đź‘Ť
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.
- Django channels and socket.io-client
- Building a Mobile App With jQuery Mobile, Django, and Phonegap
- Uwsgi http is ambiguous
4đź‘Ť
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
3đź‘Ť
You may customize your ModelAdmin
classes with the readonly_fields
attribute. See this answer for more.
- How to make sure Django models match the database schema
- Adding prefix path to static files in Angular using angular-cli
- Django request.user.is_authenticated is always true?
- Why does Django use tuples for settings and not lists?
- Sphinx and re-usable Django apps
0đź‘Ť
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?
- Apache strips down "Authorization" header
- Django OneToOneField with possible blank field
- Proper declaration of an empty Django PostgreSQL JSONField default value in migration file
-1đź‘Ť
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]
))
- Python venv not creating virtual environment
- Django Views: When is request.data a dict vs a QueryDict?
- How to emit SocketIO event on the serverside
- How do you divide your project into applications in Django?
- Django – Change a ForeignKey relation to OneToOne