mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Add InvenTreeRoleMixin
- Simplifies permission requirements for views - e.g. 'part.view' rather than 'part.view_partcategory'
This commit is contained in:
parent
d2e2e7511f
commit
dc2c9aa662
@ -22,6 +22,7 @@ from django.views.generic.base import TemplateView
|
|||||||
from part.models import Part, PartCategory
|
from part.models import Part, PartCategory
|
||||||
from stock.models import StockLocation, StockItem
|
from stock.models import StockLocation, StockItem
|
||||||
from common.models import InvenTreeSetting, ColorTheme
|
from common.models import InvenTreeSetting, ColorTheme
|
||||||
|
from users.models import check_user_role, RuleSet
|
||||||
|
|
||||||
from .forms import DeleteForm, EditUserForm, SetPasswordForm, ColorThemeSelectForm
|
from .forms import DeleteForm, EditUserForm, SetPasswordForm, ColorThemeSelectForm
|
||||||
from .helpers import str2bool
|
from .helpers import str2bool
|
||||||
@ -682,3 +683,56 @@ class DatabaseStatsView(AjaxView):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
class InvenTreeRoleMixin(PermissionRequiredMixin):
|
||||||
|
"""
|
||||||
|
Permission class based on user roles, not user 'permissions'.
|
||||||
|
|
||||||
|
To specify which role is required for the mixin,
|
||||||
|
set the class attribute 'role_required' to something like the following:
|
||||||
|
|
||||||
|
role_required = 'part.add'
|
||||||
|
role_required = [
|
||||||
|
'part.change',
|
||||||
|
'build.add',
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# By default, no roles are required
|
||||||
|
# Roles must be specified
|
||||||
|
role_required = None
|
||||||
|
|
||||||
|
def has_permission(self):
|
||||||
|
"""
|
||||||
|
Determine if the current user
|
||||||
|
"""
|
||||||
|
|
||||||
|
roles_required = []
|
||||||
|
|
||||||
|
if type(self.role_required) is str:
|
||||||
|
roles_required.append(self.role_required)
|
||||||
|
elif type(self.role_required) in [list, tuple]:
|
||||||
|
roles_required = self.role_required
|
||||||
|
|
||||||
|
# List of permissions that will be required
|
||||||
|
permissions = []
|
||||||
|
|
||||||
|
user = self.request.user
|
||||||
|
|
||||||
|
# Superuser can have any permissions they desire
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
print(type(self), "Required roles:", roles_required)
|
||||||
|
|
||||||
|
for required in roles_required:
|
||||||
|
|
||||||
|
(role, permission) = required.split('.')
|
||||||
|
|
||||||
|
# Return False if the user does not have *any* of the required roles
|
||||||
|
if not check_user_role(user, role, permission):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# We did not fail any required checks
|
||||||
|
return True
|
||||||
|
@ -44,6 +44,10 @@ class PartCategoryTree(TreeSerializer):
|
|||||||
def get_items(self):
|
def get_items(self):
|
||||||
return PartCategory.objects.all().prefetch_related('parts', 'children')
|
return PartCategory.objects.all().prefetch_related('parts', 'children')
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
permissions.IsAuthenticated,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class CategoryList(generics.ListCreateAPIView):
|
class CategoryList(generics.ListCreateAPIView):
|
||||||
""" API endpoint for accessing a list of PartCategory objects.
|
""" API endpoint for accessing a list of PartCategory objects.
|
||||||
|
@ -12,7 +12,6 @@ from django.shortcuts import HttpResponseRedirect
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.views.generic import DetailView, ListView, FormView, UpdateView
|
from django.views.generic import DetailView, ListView, FormView, UpdateView
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
||||||
from django.forms.models import model_to_dict
|
from django.forms.models import model_to_dict
|
||||||
from django.forms import HiddenInput, CheckboxInput
|
from django.forms import HiddenInput, CheckboxInput
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -39,17 +38,21 @@ from .admin import PartResource
|
|||||||
|
|
||||||
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
from InvenTree.views import AjaxView, AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
||||||
from InvenTree.views import QRCodeView
|
from InvenTree.views import QRCodeView
|
||||||
|
from InvenTree.views import InvenTreeRoleMixin
|
||||||
|
|
||||||
from InvenTree.helpers import DownloadFile, str2bool
|
from InvenTree.helpers import DownloadFile, str2bool
|
||||||
|
|
||||||
|
|
||||||
class PartIndex(PermissionRequiredMixin, ListView):
|
class PartIndex(InvenTreeRoleMixin, ListView):
|
||||||
""" View for displaying list of Part objects
|
""" View for displaying list of Part objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = Part
|
model = Part
|
||||||
template_name = 'part/category.html'
|
template_name = 'part/category.html'
|
||||||
context_object_name = 'parts'
|
context_object_name = 'parts'
|
||||||
permission_required = ('part.view_part', 'part.view_partcategory')
|
|
||||||
|
role_required = 'part.view'
|
||||||
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Part.objects.all().select_related('category')
|
return Part.objects.all().select_related('category')
|
||||||
@ -658,7 +661,7 @@ class PartNotes(UpdateView):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
class PartDetail(PermissionRequiredMixin, DetailView):
|
class PartDetail(InvenTreeRoleMixin, DetailView):
|
||||||
""" Detail view for Part object
|
""" Detail view for Part object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -666,7 +669,7 @@ class PartDetail(PermissionRequiredMixin, DetailView):
|
|||||||
queryset = Part.objects.all().select_related('category')
|
queryset = Part.objects.all().select_related('category')
|
||||||
template_name = 'part/detail.html'
|
template_name = 'part/detail.html'
|
||||||
|
|
||||||
permission_required = 'part.view_part'
|
role_required = 'part.view'
|
||||||
|
|
||||||
# Add in some extra context information based on query params
|
# Add in some extra context information based on query params
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@ -869,7 +872,7 @@ class BomValidate(AjaxUpdateView):
|
|||||||
return self.renderJsonResponse(request, form, data, context=self.get_context())
|
return self.renderJsonResponse(request, form, data, context=self.get_context())
|
||||||
|
|
||||||
|
|
||||||
class BomUpload(PermissionRequiredMixin, FormView):
|
class BomUpload(InvenTreeRoleMixin, FormView):
|
||||||
""" View for uploading a BOM file, and handling BOM data importing.
|
""" View for uploading a BOM file, and handling BOM data importing.
|
||||||
|
|
||||||
The BOM upload process is as follows:
|
The BOM upload process is as follows:
|
||||||
@ -905,7 +908,7 @@ class BomUpload(PermissionRequiredMixin, FormView):
|
|||||||
missing_columns = []
|
missing_columns = []
|
||||||
allowed_parts = []
|
allowed_parts = []
|
||||||
|
|
||||||
permission_required = ('part.change_part', 'part.add_bomitem')
|
role_required = ('part.change', 'part.add')
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
part = self.get_object()
|
part = self.get_object()
|
||||||
@ -1931,7 +1934,7 @@ class PartParameterDelete(AjaxDeleteView):
|
|||||||
ajax_form_title = _('Delete Part Parameter')
|
ajax_form_title = _('Delete Part Parameter')
|
||||||
|
|
||||||
|
|
||||||
class CategoryDetail(PermissionRequiredMixin, DetailView):
|
class CategoryDetail(InvenTreeRoleMixin, DetailView):
|
||||||
""" Detail view for PartCategory """
|
""" Detail view for PartCategory """
|
||||||
|
|
||||||
model = PartCategory
|
model = PartCategory
|
||||||
@ -1939,7 +1942,7 @@ class CategoryDetail(PermissionRequiredMixin, DetailView):
|
|||||||
queryset = PartCategory.objects.all().prefetch_related('children')
|
queryset = PartCategory.objects.all().prefetch_related('children')
|
||||||
template_name = 'part/category_partlist.html'
|
template_name = 'part/category_partlist.html'
|
||||||
|
|
||||||
permission_required = 'part.view_partcategory'
|
role_required = 'part.view'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
||||||
@ -2081,13 +2084,13 @@ class CategoryCreate(AjaxCreateView):
|
|||||||
return initials
|
return initials
|
||||||
|
|
||||||
|
|
||||||
class BomItemDetail(PermissionRequiredMixin, DetailView):
|
class BomItemDetail(InvenTreeRoleMixin, DetailView):
|
||||||
""" Detail view for BomItem """
|
""" Detail view for BomItem """
|
||||||
context_object_name = 'item'
|
context_object_name = 'item'
|
||||||
queryset = BomItem.objects.all()
|
queryset = BomItem.objects.all()
|
||||||
template_name = 'part/bom-detail.html'
|
template_name = 'part/bom-detail.html'
|
||||||
|
|
||||||
permission_required = 'part.view_bomitem'
|
role_required = 'part.view'
|
||||||
|
|
||||||
|
|
||||||
class BomItemCreate(AjaxCreateView):
|
class BomItemCreate(AjaxCreateView):
|
||||||
|
@ -52,6 +52,10 @@ class StockCategoryTree(TreeSerializer):
|
|||||||
def get_items(self):
|
def get_items(self):
|
||||||
return StockLocation.objects.all().prefetch_related('stock_items', 'children')
|
return StockLocation.objects.all().prefetch_related('stock_items', 'children')
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
permissions.IsAuthenticated,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class StockDetail(generics.RetrieveUpdateDestroyAPIView):
|
class StockDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
""" API detail endpoint for Stock object
|
""" API detail endpoint for Stock object
|
||||||
|
@ -329,3 +329,35 @@ def create_missing_rule_sets(sender, instance, **kwargs):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
update_group_roles(instance)
|
update_group_roles(instance)
|
||||||
|
|
||||||
|
|
||||||
|
def check_user_role(user, role, permission):
|
||||||
|
"""
|
||||||
|
Check if a user has a particular role:permission combination.
|
||||||
|
|
||||||
|
If the user is a superuser, this will return True
|
||||||
|
"""
|
||||||
|
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
for group in user.groups.all():
|
||||||
|
|
||||||
|
for rule in group.rule_sets.all():
|
||||||
|
|
||||||
|
if rule.name == role:
|
||||||
|
|
||||||
|
if permission == 'add' and rule.can_add:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if permission == 'change' and rule.can_change:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if permission == 'view' and rule.can_view:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if permission == 'delete' and rule.can_delete:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# No matching permissions found
|
||||||
|
return False
|
||||||
|
Loading…
Reference in New Issue
Block a user