Login

Generic views with row-level permission handling

Author:
mwicat
Posted:
May 4, 2011
Language:
Python
Version:
1.3
Score:
2 (after 2 ratings)

These generic views extend default views so that they also do permission checking on per-object basis.

  • detail, update and delete - check access for user
  • create - create permissions for user on object
  • list - narrow object list with permissions

Classes prefixed with Owned are example implementation where user has access to object if designed object attribute references him.

Example:

create_article = OwnedCreateView.as_view(owner='creator', model=Article, form_class=ArticleForm, success_url='/articles/article/%(id)d')

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
from django.views.generic.detail import DetailView
from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.views.generic.list import ListView
from django.core import exceptions
from django.core.exceptions import ImproperlyConfigured
from django.http import Http404, HttpResponseRedirect


class GuardedDetailView(DetailView):
    def get(self, request, **kwargs):
        self.object = self.get_object()
        self.check_permission(request.user, self.object)
        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)


class GuardedUpdateView(UpdateView):
    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        self.check_permission(request.user, self.object)
        return super(GuardedUpdateView, self).get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        self.check_permission(request.user, self.object)
        return super(GuardedUpdateView, self).post(request, *args, **kwargs)


class GuardedCreateView(CreateView):
    def post(self, request, *args, **kwargs):
        self.object = None
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        if form.is_valid():
            self.ensure_permissions(request.user, form.instance)
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

class GuardedDeleteView(DeleteView):
    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        self.check_permission(request.user, self.object)
        return super(DeleteView, self).get(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        self.object = self.get_object()
        self.check_permission(request.user, self.object)
        self.object.delete()
        return HttpResponseRedirect(self.get_success_url())


class OwnedCreateView(GuardedCreateView):
    
    owner = None
    
    def ensure_permissions(self, user, object):
        setattr(object, self.owner, user)

    
class OwnedDetailView(GuardedDetailView):
    
    owner = None
    
    def check_permission(self, user, object):
        if getattr(object, self.owner) != user:
            raise exceptions.PermissionDenied()


class OwnedUpdateView(GuardedUpdateView):
    
    owner = None
    
    def check_permission(self, user, object):
        if getattr(object, self.owner) != user:
            raise exceptions.PermissionDenied()

class OwnedDeleteView(GuardedDeleteView):
    
    owner = None
    
    def check_permission(self, user, object):
        if getattr(object, self.owner) != user:
            raise exceptions.PermissionDenied()


class OwnedListView(ListView):
    
    owner = None
    
    def get_queryset_perm(self, user):
        """
        Get the list of items for this view. This must be an interable, and may
        be a queryset (in which qs-specific behavior will be enabled).
        """
        if self.queryset is not None:
            queryset = self.queryset
            if hasattr(queryset, '_clone'):
                queryset = queryset._clone()
        elif self.model is not None:
            lookup_args = { self.owner: user }
            queryset = self.model._default_manager.filter(**lookup_args)
        else:
            raise ImproperlyConfigured(u"'%s' must define 'queryset' or 'model'"
                                       % self.__class__.__name__)
        return queryset

    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset_perm(request.user)
        allow_empty = self.get_allow_empty()
        if not allow_empty and len(self.object_list) == 0:
            raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.")
                          % {'class_name': self.__class__.__name__})
        context = self.get_context_data(object_list=self.object_list)
        return self.render_to_response(context)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 9 months ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 9 months, 1 week ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 4 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 4 months ago
  5. Help text hyperlinks by sa2812 1 year, 5 months ago

Comments

bradbeattie (on June 15, 2012):

To get this working, I had to modify getattr(object, "owner") and lookup_args = { "owner": user }.

#

Please login first before commenting.