diff --git a/InvenTree/InvenTree/context.py b/InvenTree/InvenTree/context.py index f9d856f566..aa66402ed6 100644 --- a/InvenTree/InvenTree/context.py +++ b/InvenTree/InvenTree/context.py @@ -7,6 +7,8 @@ Provides extra global data to all templates. from InvenTree.status_codes import SalesOrderStatus, PurchaseOrderStatus from InvenTree.status_codes import BuildStatus, StockStatus +from users.models import RuleSet + def status_codes(request): @@ -38,22 +40,31 @@ def user_roles(request): roles = { } - for group in user.groups.all(): - for rule in group.rule_sets.all(): + if user.is_superuser: + for ruleset in RuleSet.RULESET_MODELS.keys(): + roles[ruleset] = { + 'view': True, + 'add': True, + 'change': True, + 'delete': True, + } + else: + for group in user.groups.all(): + for rule in group.rule_sets.all(): - # Ensure the role name is in the dict - if rule.name not in roles: - roles[rule.name] = { - 'view': user.is_superuser, - 'add': user.is_superuser, - 'change': user.is_superuser, - 'delete': user.is_superuser - } + # Ensure the role name is in the dict + if rule.name not in roles: + roles[rule.name] = { + 'view': user.is_superuser, + 'add': user.is_superuser, + 'change': user.is_superuser, + 'delete': user.is_superuser + } - # Roles are additive across groups - roles[rule.name]['view'] |= rule.can_view - roles[rule.name]['add'] |= rule.can_add - roles[rule.name]['change'] |= rule.can_change - roles[rule.name]['delete'] |= rule.can_delete + # Roles are additive across groups + roles[rule.name]['view'] |= rule.can_view + roles[rule.name]['add'] |= rule.can_add + roles[rule.name]['change'] |= rule.can_change + roles[rule.name]['delete'] |= rule.can_delete return {'roles': roles} diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index f72c910981..b19887c8aa 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -11,14 +11,16 @@

{% trans "Stock Tracking Information" %}


-
+{% if roles.stock.change %} +
-
+
+{% endif %}
- + {% endblock %} {% block js_ready %} diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 928aa6b7a1..0b1d8f0d09 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -60,10 +60,17 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}

+{% if roles.part.view %} + +{% endif %} +{{ item.part.full_name}} +{% if roles.part.view %} + +{% endif %} {% if item.serialized %} - {{ item.part.full_name}} # {{ item.serial }} + # {{ item.serial }} {% else %} - {{ item.part.full_name }} × {% decimal item.quantity %} + × {% decimal item.quantity %} {% endif %} {% if user.is_staff and roles.stock.change %} @@ -82,14 +89,17 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} + {% if roles.stock.change %}
+ {% endif %} + {% if roles.stock.change %}
+ {% endif %} @@ -147,7 +163,13 @@ InvenTree | {% trans "Stock Item" %} - {{ item }} {% trans "Base Part" %} - {{ item.part.full_name }} + {% if roles.part.view %} + + {% endif %} + {{ item.part.full_name }} + {% if roles.part.view %} + + {% endif %} {% if item.serialized %} diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index d411891078..632219dc0a 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -18,9 +18,11 @@

{% trans "All stock items" %}

{% endif %}
- + + {% endif %} {% if location %}
@@ -31,20 +33,24 @@
  • {% trans "Check-in Items" %}
  • - - + {% if roles.stock.change %} + +
    + + +
    + {% endif %} {% endif %}
    diff --git a/InvenTree/stock/templates/stock/location_list.html b/InvenTree/stock/templates/stock/location_list.html index a6e1811f46..4ad30e1310 100644 --- a/InvenTree/stock/templates/stock/location_list.html +++ b/InvenTree/stock/templates/stock/location_list.html @@ -1,5 +1,6 @@ {% extends "collapse.html" %} +{% if roles.stock.view %} {% block collapse_title %} Sub-Locations{{ children|length }} {% endblock %} @@ -14,4 +15,5 @@ Sub-Locations{{ children|length }} {% endfor %} -{% endblock %} \ No newline at end of file +{% endblock %} +{% endif %} \ No newline at end of file diff --git a/InvenTree/stock/test_views.py b/InvenTree/stock/test_views.py index 51532f36bb..2245aff17d 100644 --- a/InvenTree/stock/test_views.py +++ b/InvenTree/stock/test_views.py @@ -3,6 +3,7 @@ from django.test import TestCase from django.urls import reverse from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group import json @@ -23,7 +24,24 @@ class StockViewTestCase(TestCase): # Create a user User = get_user_model() - User.objects.create_user('username', 'user@email.com', 'password') + self.user = User.objects.create_user( + username='username', + email='user@email.com', + password='password' + ) + + # Put the user into a group with the correct permissions + group = Group.objects.create(name='mygroup') + self.user.groups.add(group) + + # Give the group *all* the permissions! + for rule in group.rule_sets.all(): + rule.can_view = True + rule.can_change = True + rule.can_add = True + rule.can_delete = True + + rule.save() self.client.login(username='username', password='password') diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 9d078bf702..65c8f30893 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -17,6 +17,7 @@ from django.utils.translation import ugettext as _ from InvenTree.views import AjaxView from InvenTree.views import AjaxUpdateView, AjaxDeleteView, AjaxCreateView from InvenTree.views import QRCodeView +from InvenTree.views import InvenTreeRoleMixin from InvenTree.forms import ConfirmForm from InvenTree.helpers import str2bool, DownloadFile, GetExportFormats @@ -36,12 +37,13 @@ from .admin import StockItemResource from . import forms as StockForms -class StockIndex(ListView): +class StockIndex(InvenTreeRoleMixin, ListView): """ StockIndex view loads all StockLocation and StockItem object """ model = StockItem template_name = 'stock/location.html' context_obect_name = 'locations' + role_required = 'stock.view' def get_context_data(self, **kwargs): context = super(StockIndex, self).get_context_data(**kwargs).copy() @@ -58,7 +60,7 @@ class StockIndex(ListView): return context -class StockLocationDetail(DetailView): +class StockLocationDetail(InvenTreeRoleMixin, DetailView): """ Detailed view of a single StockLocation object """ @@ -67,9 +69,10 @@ class StockLocationDetail(DetailView): template_name = 'stock/location.html' queryset = StockLocation.objects.all() model = StockLocation + role_required = 'stock.view' -class StockItemDetail(DetailView): +class StockItemDetail(InvenTreeRoleMixin, DetailView): """ Detailed view of a single StockItem object """ @@ -78,14 +81,16 @@ class StockItemDetail(DetailView): template_name = 'stock/item.html' queryset = StockItem.objects.all() model = StockItem + role_required = 'stock.view' -class StockItemNotes(UpdateView): +class StockItemNotes(InvenTreeRoleMixin, UpdateView): """ View for editing the 'notes' field of a StockItem object """ context_object_name = 'item' template_name = 'stock/item_notes.html' model = StockItem + role_required = 'stock.view' fields = ['notes'] @@ -112,6 +117,7 @@ class StockLocationEdit(AjaxUpdateView): context_object_name = 'location' ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Stock Location') + role_required = 'stock.change' def get_form(self): """ Customize form data for StockLocation editing. @@ -136,6 +142,7 @@ class StockLocationQRCode(QRCodeView): """ View for displaying a QR code for a StockLocation object """ ajax_form_title = _("Stock Location QR code") + role_required = 'stock.view' def get_qr_data(self): """ Generate QR code data for the StockLocation """ @@ -155,6 +162,7 @@ class StockItemAttachmentCreate(AjaxCreateView): form_class = StockForms.EditStockItemAttachmentForm ajax_form_title = _("Add Stock Item Attachment") ajax_template_name = "modal_form.html" + role_required = 'stock.add' def post_save(self, **kwargs): """ Record the user that uploaded the attachment """ @@ -199,6 +207,7 @@ class StockItemAttachmentEdit(AjaxUpdateView): model = StockItemAttachment form_class = StockForms.EditStockItemAttachmentForm ajax_form_title = _("Edit Stock Item Attachment") + role_required = 'stock.change' def get_form(self): @@ -217,6 +226,7 @@ class StockItemAttachmentDelete(AjaxDeleteView): ajax_form_title = _("Delete Stock Item Attachment") ajax_template_name = "attachment_delete.html" context_object_name = "attachment" + role_required = 'stock.delete' def get_data(self): return { @@ -233,6 +243,7 @@ class StockItemAssignToCustomer(AjaxUpdateView): ajax_form_title = _("Assign to Customer") context_object_name = "item" form_class = StockForms.AssignStockItemToCustomerForm + role_required = 'stock.change' def post(self, request, *args, **kwargs): @@ -270,6 +281,7 @@ class StockItemReturnToStock(AjaxUpdateView): ajax_form_title = _("Return to Stock") context_object_name = "item" form_class = StockForms.ReturnStockItemForm + role_required = 'stock.change' def post(self, request, *args, **kwargs): @@ -303,6 +315,7 @@ class StockItemSelectLabels(AjaxView): model = StockItem ajax_form_title = _('Select Label Template') + role_required = 'stock.view' def get_form(self): @@ -351,6 +364,8 @@ class StockItemPrintLabels(AjaxView): label: Valid pk of a StockItemLabel template """ + role_required = 'stock.view' + def get(self, request, *args, **kwargs): label = request.GET.get('label', None) @@ -387,6 +402,7 @@ class StockItemDeleteTestData(AjaxUpdateView): model = StockItem form_class = ConfirmForm ajax_form_title = _("Delete All Test Data") + role_required = ['stock.change', 'stock.delete'] def get_form(self): return ConfirmForm() @@ -422,6 +438,7 @@ class StockItemTestResultCreate(AjaxCreateView): model = StockItemTestResult form_class = StockForms.EditStockItemTestResultForm ajax_form_title = _("Add Test Result") + role_required = 'stock.add' def post_save(self, **kwargs): """ Record the user that uploaded the test result """ @@ -459,6 +476,7 @@ class StockItemTestResultEdit(AjaxUpdateView): model = StockItemTestResult form_class = StockForms.EditStockItemTestResultForm ajax_form_title = _("Edit Test Result") + role_required = 'stock.change' def get_form(self): @@ -477,6 +495,7 @@ class StockItemTestResultDelete(AjaxDeleteView): model = StockItemTestResult ajax_form_title = _("Delete Test Result") context_object_name = "result" + role_required = 'stock.delete' class StockItemTestReportSelect(AjaxView): @@ -487,6 +506,7 @@ class StockItemTestReportSelect(AjaxView): model = StockItem ajax_form_title = _("Select Test Report Template") + role_required = 'stock.view' def get_form(self): @@ -527,6 +547,7 @@ class StockItemTestReportDownload(AjaxView): template - Valid PK of a TestReport template object """ + role_required = 'stock.view' def get(self, request, *args, **kwargs): @@ -554,6 +575,7 @@ class StockExportOptions(AjaxView): model = StockLocation ajax_form_title = _('Stock Export Options') form_class = StockForms.ExportOptionsForm + role_required = 'stock.view' def post(self, request, *args, **kwargs): @@ -586,6 +608,7 @@ class StockExport(AjaxView): """ model = StockItem + role_required = 'stock.view' def get(self, request, *args, **kwargs): @@ -673,6 +696,7 @@ class StockItemQRCode(QRCodeView): """ View for displaying a QR code for a StockItem object """ ajax_form_title = _("Stock Item QR Code") + role_required = 'stock.view' def get_qr_data(self): """ Generate QR code data for the StockItem """ @@ -699,6 +723,7 @@ class StockItemInstall(AjaxUpdateView): form_class = StockForms.InstallStockForm ajax_form_title = _('Install Stock Item') ajax_template_name = "stock/item_install.html" + role_required = 'stock.change' part = None @@ -798,6 +823,7 @@ class StockItemUninstall(AjaxView, FormMixin): ajax_template_name = 'stock/stock_uninstall.html' ajax_form_title = _('Uninstall Stock Items') form_class = StockForms.UninstallStockForm + role_required = 'stock.change' # List of stock items to uninstall (initially empty) stock_items = [] @@ -931,6 +957,7 @@ class StockAdjust(AjaxView, FormMixin): ajax_form_title = _('Adjust Stock') form_class = StockForms.AdjustStockForm stock_items = [] + role_required = 'stock.change' def get_GET_items(self): """ Return list of stock items initally requested using GET. @@ -1251,6 +1278,7 @@ class StockItemEdit(AjaxUpdateView): context_object_name = 'item' ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Stock Item') + role_required = 'stock.change' def get_form(self): """ Get form for StockItem editing. @@ -1287,6 +1315,7 @@ class StockItemConvert(AjaxUpdateView): ajax_form_title = _('Convert Stock Item') ajax_template_name = 'stock/stockitem_convert.html' context_object_name = 'item' + role_required = 'stock.change' def get_form(self): """ @@ -1312,6 +1341,7 @@ class StockLocationCreate(AjaxCreateView): context_object_name = 'location' ajax_template_name = 'modal_form.html' ajax_form_title = _('Create new Stock Location') + role_required = 'stock.add' def get_initial(self): initials = super(StockLocationCreate, self).get_initial().copy() @@ -1334,6 +1364,7 @@ class StockItemSerialize(AjaxUpdateView): ajax_template_name = 'stock/item_serialize.html' ajax_form_title = _('Serialize Stock') form_class = StockForms.SerializeStockForm + role_required = 'stock.change' def get_form(self): @@ -1426,6 +1457,7 @@ class StockItemCreate(AjaxCreateView): context_object_name = 'item' ajax_template_name = 'modal_form.html' ajax_form_title = _('Create new Stock Item') + role_required = 'stock.add' def get_part(self, form=None): """ @@ -1701,6 +1733,7 @@ class StockLocationDelete(AjaxDeleteView): ajax_template_name = 'stock/location_delete.html' context_object_name = 'location' ajax_form_title = _('Delete Stock Location') + role_required = 'stock.delete' class StockItemDelete(AjaxDeleteView): @@ -1714,6 +1747,7 @@ class StockItemDelete(AjaxDeleteView): ajax_template_name = 'stock/item_delete.html' context_object_name = 'item' ajax_form_title = _('Delete Stock Item') + role_required = 'stock.delete' class StockItemTrackingDelete(AjaxDeleteView): @@ -1725,9 +1759,10 @@ class StockItemTrackingDelete(AjaxDeleteView): model = StockItemTracking ajax_template_name = 'stock/tracking_delete.html' ajax_form_title = _('Delete Stock Tracking Entry') + role_required = 'stock.delete' -class StockTrackingIndex(ListView): +class StockTrackingIndex(InvenTreeRoleMixin, ListView): """ StockTrackingIndex provides a page to display StockItemTracking objects """ @@ -1735,6 +1770,7 @@ class StockTrackingIndex(ListView): model = StockItemTracking template_name = 'stock/tracking.html' context_object_name = 'items' + role_required = 'stock.view' class StockItemTrackingEdit(AjaxUpdateView): @@ -1743,6 +1779,7 @@ class StockItemTrackingEdit(AjaxUpdateView): model = StockItemTracking ajax_form_title = _('Edit Stock Tracking Entry') form_class = StockForms.TrackingEntryForm + role_required = 'stock.change' class StockItemTrackingCreate(AjaxCreateView): @@ -1752,6 +1789,7 @@ class StockItemTrackingCreate(AjaxCreateView): model = StockItemTracking ajax_form_title = _("Add Stock Tracking Entry") form_class = StockForms.TrackingEntryForm + role_required = 'stock.add' def post(self, request, *args, **kwargs): diff --git a/InvenTree/templates/attachment_table.html b/InvenTree/templates/attachment_table.html index 32407cbc5b..f7d5411290 100644 --- a/InvenTree/templates/attachment_table.html +++ b/InvenTree/templates/attachment_table.html @@ -1,10 +1,12 @@ {% load i18n %} +{% if roles.stock.change %}
    +{% endif %}