Login

Generic CBV Permissions Helper

Author:
stuartaccent
Posted:
November 3, 2017
Language:
Python
Version:
1.10
Tags:
permissions
Score:
0 (after 0 ratings)

A permission helper that can be included in any generic CBV, it uses the model attribute of the class to load all the permissions and tests a user can perform that action before dispatching the view.

  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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# Permissions helper
# This class inspects the user and determines if they have a specific model permission

from django.contrib.auth import get_permission_codename
from django.contrib.auth.models import Permission


class PermissionsHelper(object):
    """
    Provides permission-related helper functions to help determine what a
    user can do with a 'typical' model (where permissions are granted
    model-wide), and to a specific instance of that model.
    """

    def __init__(self, model, inspect_view_enabled=False):
        self.model = model
        self.opts = model._meta
        self.inspect_view_enabled = inspect_view_enabled

    def get_all_model_permissions(self):
        """
        Return a queryset of all Permission objects pertaining to the `model` specified at initialisation.
        """

        return Permission.objects.filter(
            content_type__app_label=self.opts.app_label,
            content_type__model=self.opts.model_name,
        )

    def get_perm_codename(self, action):
        return get_permission_codename(action, self.opts)

    def user_has_specific_permission(self, user, perm_codename):
        """
        Call the provided django user's built-in `has_perm` method.
        """

        return user.has_perm("%s.%s" % (self.opts.app_label, perm_codename))

    def user_has_any_permissions(self, user):
        """
        Return a boolean to indicate whether `user` has any model-wide permissions
        """

        for perm in self.get_all_model_permissions().values('codename'):
            if self.user_has_specific_permission(user, perm['codename']):
                return True
        return False

    def user_can_list(self, user):
        """
        Return a boolean to indicate whether `user` is permitted on at least one action.
        """

        return self.user_has_any_permissions(user)

    def user_can_view(self, user):
        """
        Return a boolean to indicate whether `user` is permitted on at least one action 
        and if we are allowing a view perm.
        """

        return self.inspect_view_enabled and self.user_has_any_permissions(user)

    def user_can_create(self, user):
        """
        Return a boolean to indicate whether `user` is permitted to create new instances of `self.model`
        """

        perm_codename = self.get_perm_codename('add')
        return self.user_has_specific_permission(user, perm_codename)

    def user_can_edit(self, user):
        """
        Return a boolean to indicate whether `user` is permitted to 'change' a specific `self.model` instance.
        """

        perm_codename = self.get_perm_codename('change')
        return self.user_has_specific_permission(user, perm_codename)

    def user_can_delete(self, user):
        """
        Return a boolean to indicate whether `user` is permitted to 'delete' a specific `self.model` instance.
        """

        perm_codename = self.get_perm_codename('delete')
        return self.user_has_specific_permission(user, perm_codename)

##########################################################

# Generic view mixin
# Use on a generic view to test the permission

from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied

from users.permissions import PermissionsHelper


class PermissionRequiredMixin(LoginRequiredMixin):
    """ Check a user has the required permission before proceeding """

    # one of PermissionsHelper.user_can_* methods
    dispatch_requires_permission = None

    @property
    def permission_helper(self):
        return PermissionsHelper(model=self.model)

    def dispatch(self, request, *args, **kwargs):

        # ensure self.dispatch_requires_permission exists
        if not self.dispatch_requires_permission:
            raise NotImplementedError('Please define self.dispatch_requires_permission')

        # ensure value of self.dispatch_requires_permission is a method on the class PermissionsHelper
        if not hasattr(self.permission_helper, self.dispatch_requires_permission):
            raise AttributeError(
                'The class {} has no attribute {}'.format(self.permission_helper, self.dispatch_requires_permission)
            )

        # check user has the permission
        check = getattr(self.permission_helper, self.dispatch_requires_permission)(request.user)
        if not check:
            raise PermissionDenied

        return super(PermissionRequiredMixin, self).dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        """ pass users permissions into the context to use in templates """

        context = super(PermissionRequiredMixin, self).get_context_data(**kwargs)
        context.update({
            'user_can_list': self.permission_helper.user_can_list(self.request.user),
            'user_can_view': self.permission_helper.user_can_view(self.request.user),
            'user_can_create': self.permission_helper.user_can_create(self.request.user),
            'user_can_edit': self.permission_helper.user_can_edit(self.request.user),
            'user_can_delete': self.permission_helper.user_can_delete(self.request.user),
        })

        return context


##########################################################

# Generic view example

from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.views.generic import CreateView
from django.utils.translation import ugettext as _

from someapp.forms import CreateForm
from someapp.models import SomeModel
from .mixins import PermissionRequiredMixin


class SomeModelCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
    model = SomeModel
    form_class = CreateForm
    dispatch_requires_permission = 'user_can_create'
    template_name = 'someapp/somemodel/create.html'
    success_message = _("Created successfully")
    success_url = reverse_lazy('someapp:somemodel-list')

##########################################################

# Template usage
# In the template you can also do stuff like:

<ul>
    {% if user_can_create %}
    <li><a href="{% url 'someapp:somemodel-create' %}">{%  trans 'Add' %}</a></li>
    {% endif %}
    {% if user_can_view %}
    <li><a href="{% url 'someapp:somemodel-detail' pk %}">{%  trans 'View' %}</a></li>
    {% endif %}
    {% if user_can_edit %}
    <li><a href="{% url 'someapp:somemodel-update' pk %}">{%  trans 'Edit' %}</a></li>
    {% endif %}
</ul>

More like this

Comments

Please login first before commenting.