View permissions in Django
Django 2.1 added a view permission to the default permissions. The solution below may work in earlier versions of Django. https://docs.djangoproject.com/en/2.1/releases/2.1/#model-view-permission
This is a working solution tested in Django 1.6.2
[X] 1. Added 'view' to default permission list
: OK[X] 2. Test the 'view' permission is added to all models
: OK
[X] 3. Add "get_view_permission" to default model class.
is useless any more:
def get_add_permission(self):
"""
This method has been deprecated in favor of
`django.contrib.auth.get_permission_codename`. refs #20642
"""
warnings.warn(
"`Options.get_add_permission` has been deprecated in favor "
"of `django.contrib.auth.get_permission_codename`.",
PendingDeprecationWarning, stacklevel=2)
return 'add_%s' % self.model_name
And that the case for all of those methods get_foo_permission
[X] 4. Add "has_view_permission" to default admin class
should be:
def has_view_permission(self, request, obj=None):
"""
Returns True if the given request has permission to change or view
the given Django model instance.
If obj is None, this should return True if the given request has
permission to change *any* object of the given type.
"""
opts = self.opts
codename = get_permission_codename('view', opts)
return self.has_change_permission(request, obj) or \
request.user.has_perm("%s.%s" % (opts.app_label, codename))
if the model is an inline one check its right, so need to be aware of the right view
def get_inline_instances(self, request, obj=None):
...
if not (inline.has_add_permission(request) or
inline.has_change_permission(request, obj) or
inline.has_delete_permission(request, obj) or
inline.has_view_permission(request, obj)): # add the view right
continue
...
Do the modification on get_model_perms
to include 'view', in the same idea do this one:
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
...
context.update({
...
'has_view_permission': self.has_view_permission(request, obj), # add the view right
...
})
....
Allow the 'right view' to render the page (of one object) and disable the 'right view' to save the modification done on a page avoid [X] 8. Modify "view" permission to make form read only
@csrf_protect_m
@transaction.atomic
def change_view(self, request, object_id, form_url='', extra_context=None):
"The 'change' admin view for this model."
model = self.model
opts = model._meta
obj = self.get_object(request, unquote(object_id))
# addthe view right
if not (self.has_view_permission(request, obj) or
self.has_change_permission(request, obj)):
raise PermissionDenied
...
inline_instances = self.get_inline_instances(request, obj)
# do not save the change if I'm not allowed to:
if request.method == 'POST' and self.has_change_permission(request, obj):
form = ModelForm(request.POST, request.FILES, instance=obj)
...
Allow the 'right view' to render the page (the list of all objects)
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
"""
The 'change list' admin view for this model.
"""
from django.contrib.admin.views.main import ERROR_FLAG
opts = self.model._meta
app_label = opts.app_label
# allow user with the view right to see the page
if not (self.has_view_permission(request, None) or
self.has_change_permission(request, None)):
raise PermissionDenied
....
[X] 5. Update default template to list models if user has view permission
: OK but to avoid to modify the html template edit this file: contrib/admin/site.py
class AdminSite(object):
@never_cache
def index(self, request, extra_context=None):
...
# add the view right
if perms.get('view', False) or perms.get('change', False):
try:
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
except NoReverseMatch:
pass
...
def app_index(self, request, app_label, extra_context=None):
...
# add the view right
if perms.get('view', False) or perms.get('change', False):
try:
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
except NoReverseMatch:
pass
...
[X] 6. Confirm user can "view" but not "change" the model
and [X] 7. Remove "Save and Add another" button if user is viewing an item
: should be ok but i did that:
'show_save_as_new': context['has_add_permission'] and not is_popup and change and save_as,
'show_save': context['has_change_permission'],
[X] 8. Modify "view" permission to make form read only: Ok but i have an other solution see above
Adding 'view' permission to default permissions list
Your solution works, but you should really avoid editing source code if possible. There's a few ways to accomplish this within the framework:
1. Add the permission during post_syncdb()
:
In a file under your_app/management/
from django.db.models.signals import post_syncdb
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission
def add_view_permissions(sender, **kwargs):
"""
This syncdb hooks takes care of adding a view permission too all our
content types.
"""
# for each of our content types
for content_type in ContentType.objects.all():
# build our permission slug
codename = "view_%s" % content_type.model
# if it doesn't exist..
if not Permission.objects.filter(content_type=content_type, codename=codename):
# add it
Permission.objects.create(content_type=content_type,
codename=codename,
name="Can view %s" % content_type.name)
print "Added view permission for %s" % content_type.name
# check for all our view permissions after a syncdb
post_syncdb.connect(add_view_permissions)
Whenever you issue a 'syncdb' command, all content types can be checked to see if they have a 'view' permission, and if not, create one.
- SOURCE: The Nyaruka Blog
2. Add the permission to the Meta permissions option:
Under every model you would add something like this to its Meta
options:
class Pizza(models.Model):
cheesiness = models.IntegerField()
class Meta:
permissions = (
('view_pizza', 'Can view pizza'),
)
This will accomplish the same as 1 except you have to manually add it to each class.
3. NEW in Django 1.7, Add the permission to the Meta default_permissions option:
In Django 1.7 they added the default_permissions Meta option. Under every model you would add 'view' to the default_permissions option:
class Pizza(models.Model):
cheesiness = models.IntegerField()
class Meta:
default_permissions = ('add', 'change', 'delete', 'view')