1

By adding the following code in one of my models, I was able to add a view permission to the model.

class Meta:
    default_permissions = ('add', 'change', 'delete', 'view')

I have created a user in django-admin and given only view permission. I want that the user should be able to only view the values of this particular model.

But after logging in with the user I am getting an error that the user dont have the permission to edit anything.(refer screenshot).

Is there any way that I can allow the user to view a particular model only by giving the VIEW permission only? Any help would be greatly appreciated.

enter image description here

lucasnadalutti
  • 5,818
  • 1
  • 28
  • 48
Naman Sharma
  • 179
  • 1
  • 1
  • 12

1 Answers1

1

It involves quite a hassle. I'll dump few ideas below.

First, let's show a model with a view prermission in the admin:

from django.contrib import admin
from django.contrib.auth import get_permission_codename

from .models import MyModel


@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):

    def get_model_perms(self, request):
        model_perms = super(MyModelAdmin, self).get_model_perms(request)

        view_perm = self.has_view_permission(request)

        model_perms.update({
            'change': view_perm,
            'view': view_perm,
        })

    def has_view_permission(self, request):
        opts = self.opts
        codename = get_permission_codename('view', opts)
        return request.user.has_perm("%s.%s" % (opts.app_label, codename))

Note: I'm hooking in the changelist_view(). I find it easier.

Secondly, let's add two views viewlist_view() and view_view() to the MyModelAdmin:

def get_urls(self):
    from django.conf.urls import url

    def wrap(view):
        def wrapper(*args, **kwargs):
            return self.admin_site.admin_view(view)(*args, **kwargs)
        wrapper.model_admin = self
        return update_wrapper(wrapper, view)

    info = self.model._meta.app_label, self.model._meta.model_name

    urlpatterns = super(MyModelAdmin, self).get_urls()
    urls = [
        url(r'^viewlist/$', wrap(self.viewlist_view), name='%s_%s_viewlist' % info),
        url(r'^(.+)/view/$', wrap(self.view_view), name='%s_%s_view' % info),
    ]

    return urls + urlpatterns

@csrf_protect_m
def viewlist_view(self, request, extra_context=None):
    opts = self.model._meta
    app_label = opts.app_label
    if not self.has_view_permission(request):
        raise PermissionDenied

    context = dict(
        self.admin_site.each_context(request),
        title='%s view list' % force_text(opts.verbose_name),
        queryset=self.get_queryset(request),
    )
    context.update(extra_context or {})

    return TemplateResponse(request, 'admin/view_list.html', context)

@csrf_protect_m
def view_view(self, request, object_id, extra_context=None):
    opts = self.model._meta
    app_label = opts.app_label
    if not self.has_view_permission(request):
        raise PermissionDenied

    context = dict(
        self.admin_site.each_context(request),
        object_id=object_id,
    )

    return TemplateResponse(request, 'admin/view_view.html', context)

Thirdly, hook in the changelist_view():

@csrf_protect_m
def changelist_view(self, request, extra_context=None):
    if not self.has_change_permission(request, None):
        # or redirect
        return self.viewlist_view(request, extra_context)
    return super(MyModelAdmin, self).changelist_view(request, extra_context)

Fourthly, add two templates:

admin/view_list.html

{% extends "admin/base_site.html" %}

{% block content %}
<div id="content-main">
  <ul>
    {% for obj in queryset %}
      <li><a href="{{ obj.get_admin_view_url }}">{{ obj }}</a></li>
    {% endfor %}
  </ul>
</div>
{% endblock %}

admin/view_view.html

{% extends "admin/base_site.html" %}

{% block content %}
<div id="content-main">

{{ object_id }}

</div>
{% endblock %}

Lastly, I've added a method get_admin_view_url() to the MyModel to get to the admin view:

from __future__ import unicode_literals

from django.db import models
from django.core.urlresolvers import reverse


class MyModel(models.Model):
    name = models.CharField(max_length=255)

    class Meta:
        default_permissions = ('add', 'change', 'delete', 'view')

    def __unicode__(self):
        return self.name

    def get_admin_view_url(self):
        info = (self._meta.app_label, self._meta.model_name)
        return reverse('admin:%s_%s_view' % info, args=(self.id,))

I hope this makes sense.

twil
  • 6,032
  • 1
  • 30
  • 28