Move ObjectPermissionRequiredMixin to utilities.views

This commit is contained in:
Jeremy Stretch
2020-05-21 13:12:15 -04:00
parent 40c590f445
commit cc6e74dfd5
5 changed files with 78 additions and 59 deletions

View File

@@ -4,7 +4,8 @@ from copy import deepcopy
from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist, ValidationError
from django.contrib.auth.mixins import AccessMixin
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured, ObjectDoesNotExist, ValidationError
from django.db import transaction, IntegrityError
from django.db.models import ManyToManyField, ProtectedError
from django.forms import Form, ModelMultipleChoiceField, MultipleHiddenInput, Textarea
@@ -32,6 +33,61 @@ from .forms import ConfirmationForm, ImportForm
from .paginator import EnhancedPaginator, get_paginate_count
#
# Mixins
#
class ObjectPermissionRequiredMixin(AccessMixin):
"""
Similar to Django's built-in PermissionRequiredMixin, but extended to check for both model-level and object-level
permission assignments. If the user has only object-level permissions assigned, the view's queryset is filtered
to return only those objects on which the user is permitted to perform the specified action.
"""
permission_required = None
def has_permission(self):
user = self.request.user
# First, check that the user is granted the required permission at either the model or object level.
if not user.has_perm(self.permission_required):
return False
# Superusers implicitly have all permissions
if user.is_superuser:
return True
# Determine whether the permission is model-level or object-level. Model-level permissions grant the
# specified action to *all* objects, so no further action is needed.
if self.permission_required in {*user._user_perm_cache, *user._group_perm_cache}:
return True
# If the permission is granted only at the object level, filter the view's queryset to return only objects
# on which the user is permitted to perform the specified action.
attrs = ObjectPermission.objects.get_attr_constraints(user, self.permission_required)
if attrs:
# Update the view's QuerySet to filter only the permitted objects
self.queryset = self.queryset.filter(attrs)
return True
def dispatch(self, request, *args, **kwargs):
if self.permission_required is None:
raise ImproperlyConfigured(
'{0} is missing the permission_required attribute. Define {0}.permission_required, or override '
'{0}.get_permission_required().'.format(self.__class__.__name__)
)
if not hasattr(self, 'queryset'):
raise ImproperlyConfigured(
'{} has no queryset defined. ObjectPermissionRequiredMixin may only be used on views which define '
'a base queryset'.format(self.__class__.__name__)
)
if not self.has_permission():
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
class GetReturnURLMixin(object):
"""
Provides logic for determining where a user should be redirected after processing a form.
@@ -58,6 +114,10 @@ class GetReturnURLMixin(object):
return reverse('home')
#
# Generic views
#
class ObjectListView(View):
"""
List a series of objects.