"""
django-model-permissions - simple object permissions for django.
Copyright (C) 2018 Mathias Stelzer
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from django.contrib.auth.context_processors import PermLookupDict, PermWrapper
from django.core.exceptions import PermissionDenied
from django.views.generic import (
DetailView as DjangoDetailView,
UpdateView as DjangoUpdateView,
DeleteView as DjangoDeleteView,
)
[docs]class ObjPermLookupDict(PermLookupDict):
"""Object permissions lookup dictionary."""
def __init__(self, user, app_label, obj=None):
"""Initialize object permission lookup dictionary."""
super(ObjPermLookupDict, self).__init__(user, app_label)
self.object = obj
def __getitem__(self, perm_name):
"""Check whether the user has the given permission for the object."""
return self.user.has_perm('{}.{}'.format(self.app_label, perm_name), self.object)
[docs]class ObjPermWrapper(PermWrapper):
"""Object permissions wrapper to traverse a list of permissions."""
def __init__(self, user, obj=None):
"""Initialize object permissions wrapper."""
super(ObjPermWrapper, self).__init__(user)
self.object = obj
def __getitem__(self, app_label):
"""Get a lookup dictionary for the object."""
return ObjPermLookupDict(self.user, app_label, self.object)
[docs]class PermissionContextMixin(object):
"""
View mixin to insert permissions in the template context.
If the `get_context_data` method is called and the context contains
an `'object'` element, this will insert a `'object_perms'` permission
wrapper.
The `get_context_object_name` method will be used too, if it exists.
"""
[docs] def get_context_data(self, **kwargs):
"""Add object permissions to template context."""
kwargs = super().get_context_data(**kwargs)
if getattr(self, 'object', None):
permission_wrapper = ObjPermWrapper(self.request.user, self.object)
kwargs['object_perms'] = permission_wrapper
if hasattr(self, 'get_context_object_name'):
context_object_name = self.get_context_object_name(self.object)
if context_object_name:
kwargs[context_object_name + '_perms'] = permission_wrapper
return kwargs
[docs]class RequirePermissionMixin(object):
"""View mixin to require object permissions for access."""
required_permission = None
# noinspection PyMethodMayBeStatic
[docs] def get_required_permission_object(self, obj):
"""Overwrite to check the required_permission against another model object."""
return obj
[docs] def check_required_permission(self, permission_object):
"""
Check the `required_permission` on the given object.
:raises: :class:`django.core.exceptions.PermissionDenied` if the
current user does not have the `required_permission`.
"""
if not self.has_perm(self.required_permission, permission_object):
raise PermissionDenied()
[docs] def get_object(self, queryset=None):
"""
Get the object and check the `required_permission` on it.
:raises: :class:`django.core.exceptions.PermissionDenied` if the
current user does not have the `required_permission`.
"""
obj = super().get_object(queryset=queryset)
if self.required_permission is not None:
perm_obj = self.get_required_permission_object(obj)
self.check_required_permission(perm_obj)
return obj
[docs] def has_perm(self, perm, obj=None):
"""Check the given permission on the given object."""
return self.request.user.has_perm(perm, obj=obj)
[docs]class PermissionMixin(RequirePermissionMixin, PermissionContextMixin):
"""View mixin to enable permissions."""
pass
[docs]class DetailView(PermissionMixin, DjangoDetailView):
"""DetailView with permissions."""
pass
[docs]class UpdateView(PermissionMixin, DjangoUpdateView):
"""UpdateView with permissions."""
pass
[docs]class DeleteView(PermissionMixin, DjangoDeleteView):
"""DeleteView with permissions."""
pass