| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- from django.contrib.auth.mixins import AccessMixin
- from django.core.exceptions import ImproperlyConfigured
- from django.urls import reverse
- from django.urls.exceptions import NoReverseMatch
- from django.utils.http import is_safe_url
- from .permissions import resolve_permission
- #
- # View Mixins
- #
- class ContentTypePermissionRequiredMixin(AccessMixin):
- """
- Similar to Django's built-in PermissionRequiredMixin, but extended to check model-level permission assignments.
- This is related to ObjectPermissionRequiredMixin, except that is does not enforce object-level permissions,
- and fits within NetBox's custom permission enforcement system.
- additional_permissions: An optional iterable of statically declared permissions to evaluate in addition to those
- derived from the object type
- """
- additional_permissions = list()
- def get_required_permission(self):
- """
- Return the specific permission necessary to perform the requested action on an object.
- """
- raise NotImplementedError(f"{self.__class__.__name__} must implement get_required_permission()")
- def has_permission(self):
- user = self.request.user
- permission_required = self.get_required_permission()
- # Check that the user has been granted the required permission(s).
- if user.has_perms((permission_required, *self.additional_permissions)):
- return True
- return False
- def dispatch(self, request, *args, **kwargs):
- if not self.has_permission():
- return self.handle_no_permission()
- return super().dispatch(request, *args, **kwargs)
- 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.
- additional_permissions: An optional iterable of statically declared permissions to evaluate in addition to those
- derived from the object type
- """
- additional_permissions = list()
- def get_required_permission(self):
- """
- Return the specific permission necessary to perform the requested action on an object.
- """
- raise NotImplementedError(f"{self.__class__.__name__} must implement get_required_permission()")
- def has_permission(self):
- user = self.request.user
- permission_required = self.get_required_permission()
- # Check that the user has been granted the required permission(s).
- if user.has_perms((permission_required, *self.additional_permissions)):
- # Update the view's QuerySet to filter only the permitted objects
- action = resolve_permission(permission_required)[1]
- self.queryset = self.queryset.restrict(user, action)
- return True
- return False
- def dispatch(self, request, *args, **kwargs):
- 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:
- """
- Provides logic for determining where a user should be redirected after processing a form.
- """
- default_return_url = None
- def get_return_url(self, request, obj=None):
- # First, see if `return_url` was specified as a query parameter or form data. Use this URL only if it's
- # considered safe.
- query_param = request.GET.get('return_url') or request.POST.get('return_url')
- if query_param and is_safe_url(url=query_param, allowed_hosts=request.get_host()):
- return query_param
- # Next, check if the object being modified (if any) has an absolute URL.
- if obj is not None and obj.pk and hasattr(obj, 'get_absolute_url'):
- return obj.get_absolute_url()
- # Fall back to the default URL (if specified) for the view.
- if self.default_return_url is not None:
- return reverse(self.default_return_url)
- # Attempt to dynamically resolve the list view for the object
- if hasattr(self, 'queryset'):
- model_opts = self.queryset.model._meta
- try:
- return reverse(f'{model_opts.app_label}:{model_opts.model_name}_list')
- except NoReverseMatch:
- pass
- # If all else fails, return home. Ideally this should never happen.
- return reverse('home')
|