diff --git a/InvenTree/InvenTree/permissions.py b/InvenTree/InvenTree/permissions.py index dee5e0256d..da472b8637 100644 --- a/InvenTree/InvenTree/permissions.py +++ b/InvenTree/InvenTree/permissions.py @@ -69,6 +69,10 @@ class RolePermission(permissions.BasePermission): # The required role may be defined for the view class if role := getattr(view, 'role_required', None): + # If the role is specified as "role.permission", split it + if '.' in role: + role, permission = role.split('.') + return users.models.check_user_role(user, role, permission) try: diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index ac46683243..c601f6478a 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -123,6 +123,8 @@ class StockDetail(RetrieveUpdateDestroyAPI): class StockItemContextMixin: """Mixin class for adding StockItem object to serializer context.""" + role_required = 'stock.change' + queryset = StockItem.objects.none() def get_serializer_context(self): diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index d811aa17f3..4512d13d5d 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -196,6 +196,7 @@ stock_item: {{ item.pk }}, part: {{ item.part.pk }}, quantity: {{ item.quantity|unlocalize }}, + can_edit: {% js_bool roles.stock.change %}, } ); diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index e5b075942e..6d448a0d14 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -346,7 +346,11 @@ function constructForm(url, options={}) { getApiEndpointOptions(url, function(OPTIONS) { // Copy across entire actions struct - options.actions = OPTIONS.actions.POST || OPTIONS.actions.PUT || OPTIONS.actions.PATCH || OPTIONS.actions.DELETE || {}; + if (OPTIONS && OPTIONS.actions) { + options.actions = OPTIONS.actions.POST || OPTIONS.actions.PUT || OPTIONS.actions.PATCH || OPTIONS.actions.DELETE || {}; + } else { + options.actions = {}; + } // Extract any custom 'context' information from the OPTIONS data options.context = OPTIONS.context || {}; diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index 88613f09bc..6790ee20a8 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -3101,11 +3101,14 @@ function loadInstalledInTable(table, options) { field: 'buttons', title: '', switchable: false, + visible: options.can_edit, formatter: function(value, row) { let pk = row.pk; let html = ''; - html += makeIconButton('fa-unlink', 'button-uninstall', pk, '{% trans "Uninstall Stock Item" %}'); + if (options.can_edit) { + html += makeIconButton('fa-unlink', 'button-uninstall', pk, '{% trans "Uninstall Stock Item" %}'); + } return wrapButtons(html); }