diff --git a/InvenTree/InvenTree/permissions.py b/InvenTree/InvenTree/permissions.py index 1c3c6ee5db..973395a2a0 100644 --- a/InvenTree/InvenTree/permissions.py +++ b/InvenTree/InvenTree/permissions.py @@ -58,8 +58,10 @@ class RolePermission(permissions.BasePermission): # Extract the model name associated with this request model = view.serializer_class.Meta.model - # And the specific database table - table = model._meta.db_table + app_label = model._meta.app_label + model_name = model._meta.model_name + + table = f"{app_label}_{model_name}" except AttributeError: # We will assume that if the serializer class does *not* have a Meta, # then we don't need a permission diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index d392c4cb62..5a080d7cdc 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -16,7 +16,7 @@ from django.urls import reverse_lazy from django.contrib.auth.mixins import PermissionRequiredMixin from django.views import View -from django.views.generic import UpdateView, CreateView, FormView +from django.views.generic import ListView, DetailView, CreateView, FormView, DeleteView, UpdateView from django.views.generic.base import TemplateView from part.models import Part, PartCategory @@ -113,14 +113,39 @@ 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: + There are a number of ways that the permissions can be specified for a view: - role_required = 'part.add' - role_required = [ - 'part.change', - 'build.add', - ] + 1. Specify the 'role_required' attribute (e.g. part.change) + 2. Specify the 'permission_required' attribute (e.g. part.change_bomitem) + (Note: This is the "normal" django-esque way of doing this) + 3. Do nothing. The mixin will attempt to "guess" what permission you require: + a) If there is a queryset associated with the View, we have the model! + b) The *type* of View tells us the permission level (e.g. AjaxUpdateView = change) + c) 1 + 1 = 3 + d) Use the combination of model + permission as we would in 2) + + 1. Specify the 'role_required' attribute + ===================================== + 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', + ] + + 2. Specify the 'permission_required' attribute + =========================================== + To specify a particular low-level permission, + set the class attribute 'permission_required' to something like: + + permission_required = 'company.delete_company' + + 3. Do Nothing + ========== + + See above. """ # By default, no roles are required @@ -132,10 +157,6 @@ class InvenTreeRoleMixin(PermissionRequiredMixin): Determine if the current user has specified permissions """ - if self.permission_required: - # Ignore role-based permissions - return super().has_permission() - roles_required = [] if type(self.role_required) is str: @@ -163,9 +184,80 @@ class InvenTreeRoleMixin(PermissionRequiredMixin): if not check_user_role(user, role, permission): return False + # If a permission_required is specified, use that! + if self.permission_required: + # Ignore role-based permissions + return super().has_permission() + + # Ok, so at this point we have not explicitly require a "role" or a "permission" + # Instead, we will use the model to introspect the data we need + + model = getattr(self, 'model', None) + + if not model: + queryset = getattr(self, 'queryset', None) + + if queryset is not None: + model = queryset.model + + # We were able to introspect a database model + if model is not None: + app_label = model._meta.app_label + model_name = model._meta.model_name + + table = f"{app_label}_{model_name}" + + permission = self.get_permission_class() + + if not permission: + raise AttributeError(f"permission_class not defined for {type(self).__name__}") + + # Check if the user has the required permission + return RuleSet.check_table_permission(user, table, permission) + # We did not fail any required checks return True + def get_permission_class(self): + """ + Return the 'permission_class' required for the current View. + + Must be one of: + + - view + - change + - add + - delete + + This can either be explicitly defined, by setting the + 'permission_class' attribute, + or it can be "guessed" by looking at the type of class + """ + + perm = getattr(self, 'permission_class', None) + + # Permission is specified by the class itself + if perm: + return perm + + # Otherwise, we will need to have a go at guessing... + permission_map = { + AjaxView: 'view', + ListView: 'view', + DetailView: 'view', + UpdateView: 'change', + DeleteView: 'delete', + AjaxUpdateView: 'change', + AjaxCreateView: 'add', + } + + for view_class in permission_map.keys(): + + if issubclass(type(self), view_class): + return permission_map[view_class] + + return None + class AjaxMixin(InvenTreeRoleMixin): """ AjaxMixin provides basic functionality for rendering a Django form to JSON. diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 272cd1e5d8..e8a1d33ddc 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -28,7 +28,6 @@ class BuildIndex(InvenTreeRoleMixin, ListView): model = Build template_name = 'build/index.html' context_object_name = 'builds' - role_required = 'build.view' def get_queryset(self): """ Return all Build objects (order by date, newest first) """ @@ -58,7 +57,6 @@ class BuildCancel(AjaxUpdateView): ajax_form_title = _('Cancel Build') context_object_name = 'build' form_class = forms.CancelBuildForm - role_required = 'build.change' def validate(self, build, form, **kwargs): @@ -92,7 +90,6 @@ class BuildAutoAllocate(AjaxUpdateView): context_object_name = 'build' ajax_form_title = _('Allocate Stock') ajax_template_name = 'build/auto_allocate.html' - role_required = 'build.change' def get_initial(self): """ @@ -181,7 +178,6 @@ class BuildOutputCreate(AjaxUpdateView): form_class = forms.BuildOutputCreateForm ajax_template_name = 'build/build_output_create.html' ajax_form_title = _('Create Build Output') - role_required = 'build.change' def validate(self, build, form, **kwargs): """ @@ -280,6 +276,7 @@ class BuildOutputDelete(AjaxUpdateView): model = Build form_class = forms.BuildOutputDeleteForm ajax_form_title = _('Delete Build Output') + role_required = 'build.delete' def get_initial(self): @@ -340,8 +337,7 @@ class BuildUnallocate(AjaxUpdateView): form_class = forms.UnallocateBuildForm ajax_form_title = _("Unallocate Stock") ajax_template_name = "build/unallocate.html" - role_required = 'build.change' - + def get_initial(self): initials = super().get_initial() @@ -408,7 +404,7 @@ class BuildComplete(AjaxUpdateView): model = Build form_class = forms.CompleteBuildForm - role_required = 'build.change' + ajax_form_title = _('Complete Build Order') ajax_template_name = 'build/complete.html' @@ -444,8 +440,7 @@ class BuildOutputComplete(AjaxUpdateView): context_object_name = "build" ajax_form_title = _("Complete Build Output") ajax_template_name = "build/complete_output.html" - role_required = 'build.change' - + def get_form(self): build = self.get_object() @@ -579,13 +574,15 @@ class BuildOutputComplete(AjaxUpdateView): } -class BuildNotes(UpdateView): +class BuildNotes(InvenTreeRoleMixin, UpdateView): """ View for editing the 'notes' field of a Build object. """ context_object_name = 'build' template_name = 'build/notes.html' model = Build + + # Override the default permission role for this View role_required = 'build.view' fields = ['notes'] @@ -602,13 +599,12 @@ class BuildNotes(UpdateView): return ctx -class BuildDetail(DetailView): +class BuildDetail(InvenTreeRoleMixin, DetailView): """ Detail view of a single Build object. """ model = Build template_name = 'build/detail.html' context_object_name = 'build' - role_required = 'build.view' def get_context_data(self, **kwargs): @@ -623,12 +619,11 @@ class BuildDetail(DetailView): return ctx -class BuildAllocate(DetailView): +class BuildAllocate(InvenTreeRoleMixin, DetailView): """ View for allocating parts to a Build """ model = Build context_object_name = 'build' template_name = 'build/allocate.html' - role_required = ['build.change'] def get_context_data(self, **kwargs): """ Provide extra context information for the Build allocation page """ @@ -652,13 +647,15 @@ class BuildAllocate(DetailView): class BuildCreate(AjaxCreateView): - """ View to create a new Build object """ + """ + View to create a new Build object + """ + model = Build context_object_name = 'build' form_class = forms.EditBuildForm ajax_form_title = _('New Build Order') ajax_template_name = 'modal_form.html' - role_required = 'build.add' def get_form(self): form = super().get_form() @@ -734,7 +731,6 @@ class BuildUpdate(AjaxUpdateView): context_object_name = 'build' ajax_form_title = _('Edit Build Order Details') ajax_template_name = 'modal_form.html' - role_required = 'build.change' def get_form(self): @@ -776,7 +772,6 @@ class BuildDelete(AjaxDeleteView): model = Build ajax_template_name = 'build/delete_build.html' ajax_form_title = _('Delete Build Order') - role_required = 'build.delete' class BuildItemDelete(AjaxDeleteView): @@ -788,8 +783,7 @@ class BuildItemDelete(AjaxDeleteView): ajax_template_name = 'build/delete_build_item.html' ajax_form_title = _('Unallocate Stock') context_object_name = 'item' - role_required = 'build.delete' - + def get_data(self): return { 'danger': _('Removed parts from build allocation') @@ -805,7 +799,6 @@ class BuildItemCreate(AjaxCreateView): form_class = forms.EditBuildItemForm ajax_template_name = 'build/create_build_item.html' ajax_form_title = _('Allocate stock to build output') - role_required = 'build.add' # The output StockItem against which the allocation is being made output = None @@ -1021,8 +1014,7 @@ class BuildItemEdit(AjaxUpdateView): ajax_template_name = 'build/edit_build_item.html' form_class = forms.EditBuildItemForm ajax_form_title = _('Edit Stock Allocation') - role_required = 'build.change' - + def get_data(self): return { 'info': _('Updated Build Item'), @@ -1055,8 +1047,7 @@ class BuildAttachmentCreate(AjaxCreateView): model = BuildOrderAttachment form_class = forms.EditBuildAttachmentForm ajax_form_title = _('Add Build Order Attachment') - role_required = 'build.add' - + def save(self, form, **kwargs): """ Add information on the user that uploaded the attachment @@ -1105,7 +1096,6 @@ class BuildAttachmentEdit(AjaxUpdateView): model = BuildOrderAttachment form_class = forms.EditBuildAttachmentForm ajax_form_title = _('Edit Attachment') - role_required = 'build.change' def get_form(self): form = super().get_form() @@ -1127,7 +1117,6 @@ class BuildAttachmentDelete(AjaxDeleteView): model = BuildOrderAttachment ajax_form_title = _('Delete Attachment') context_object_name = 'attachment' - role_required = 'build.delete' def get_data(self): return { diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index e863fa1d72..59e9b23904 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -269,7 +269,6 @@ class SupplierPartEdit(AjaxUpdateView): form_class = EditSupplierPartForm ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Supplier Part') - role_required = 'purchase_order.change' def get_form(self): form = super().get_form() @@ -294,7 +293,6 @@ class SupplierPartCreate(AjaxCreateView): ajax_template_name = 'company/supplier_part_create.html' ajax_form_title = _('Create new Supplier Part') context_object_name = 'part' - role_required = 'purchase_order.add' def validate(self, part, form): @@ -413,6 +411,7 @@ class SupplierPartDelete(AjaxDeleteView): success_url = '/supplier/' ajax_template_name = 'company/partdelete.html' ajax_form_title = _('Delete Supplier Part') + role_required = 'purchase_order.delete' parts = [] @@ -485,7 +484,6 @@ class PriceBreakCreate(AjaxCreateView): form_class = EditPriceBreakForm ajax_form_title = _('Add Price Break') ajax_template_name = 'modal_form.html' - role_required = 'purchase_order.add' def get_data(self): return { @@ -547,7 +545,6 @@ class PriceBreakEdit(AjaxUpdateView): form_class = EditPriceBreakForm ajax_form_title = _('Edit Price Break') ajax_template_name = 'modal_form.html' - role_required = 'purchase_order.change' def get_form(self): @@ -563,4 +560,3 @@ class PriceBreakDelete(AjaxDeleteView): model = SupplierPriceBreak ajax_form_title = _("Delete Price Break") ajax_template_name = 'modal_delete_form.html' - role_required = 'purchase_order.delete' diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 091b11e825..2cd8e8a4cb 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -44,8 +44,6 @@ class PurchaseOrderIndex(InvenTreeRoleMixin, ListView): template_name = 'order/purchase_orders.html' context_object_name = 'orders' - role_required = 'purchase_order.view' - def get_queryset(self): """ Retrieve the list of purchase orders, ensure that the most recent ones are returned first. """ @@ -65,7 +63,6 @@ class SalesOrderIndex(InvenTreeRoleMixin, ListView): model = SalesOrder template_name = 'order/sales_orders.html' context_object_name = 'orders' - role_required = 'sales_order.view' class PurchaseOrderDetail(InvenTreeRoleMixin, DetailView): @@ -74,7 +71,6 @@ class PurchaseOrderDetail(InvenTreeRoleMixin, DetailView): context_object_name = 'order' queryset = PurchaseOrder.objects.all().prefetch_related('lines') template_name = 'order/purchase_order_detail.html' - role_required = 'purchase_order.view' def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) @@ -88,7 +84,6 @@ class SalesOrderDetail(InvenTreeRoleMixin, DetailView): context_object_name = 'order' queryset = SalesOrder.objects.all().prefetch_related('lines') template_name = 'order/sales_order_detail.html' - role_required = 'sales_order.view' class PurchaseOrderAttachmentCreate(AjaxCreateView): @@ -100,7 +95,6 @@ class PurchaseOrderAttachmentCreate(AjaxCreateView): form_class = order_forms.EditPurchaseOrderAttachmentForm ajax_form_title = _("Add Purchase Order Attachment") ajax_template_name = "modal_form.html" - role_required = 'purchase_order.add' def save(self, form, **kwargs): @@ -150,7 +144,6 @@ class SalesOrderAttachmentCreate(AjaxCreateView): model = SalesOrderAttachment form_class = order_forms.EditSalesOrderAttachmentForm ajax_form_title = _('Add Sales Order Attachment') - role_required = 'sales_order.add' def save(self, form, **kwargs): """ @@ -191,7 +184,6 @@ class PurchaseOrderAttachmentEdit(AjaxUpdateView): model = PurchaseOrderAttachment form_class = order_forms.EditPurchaseOrderAttachmentForm ajax_form_title = _("Edit Attachment") - role_required = 'purchase_order.change' def get_data(self): return { @@ -213,7 +205,6 @@ class SalesOrderAttachmentEdit(AjaxUpdateView): model = SalesOrderAttachment form_class = order_forms.EditSalesOrderAttachmentForm ajax_form_title = _("Edit Attachment") - role_required = 'sales_order.change' def get_data(self): return { @@ -235,7 +226,6 @@ class PurchaseOrderAttachmentDelete(AjaxDeleteView): ajax_form_title = _("Delete Attachment") ajax_template_name = "order/delete_attachment.html" context_object_name = "attachment" - role_required = 'purchase_order.delete' def get_data(self): return { @@ -250,7 +240,6 @@ class SalesOrderAttachmentDelete(AjaxDeleteView): ajax_form_title = _("Delete Attachment") ajax_template_name = "order/delete_attachment.html" context_object_name = "attachment" - role_required = 'sales_order.delete' def get_data(self): return { @@ -264,6 +253,8 @@ class PurchaseOrderNotes(InvenTreeRoleMixin, UpdateView): context_object_name = 'order' template_name = 'order/order_notes.html' model = PurchaseOrder + + # Override the default permission roles role_required = 'purchase_order.view' fields = ['notes'] @@ -311,7 +302,6 @@ class PurchaseOrderCreate(AjaxCreateView): model = PurchaseOrder ajax_form_title = _("Create Purchase Order") form_class = order_forms.EditPurchaseOrderForm - role_required = 'purchase_order.add' def get_initial(self): initials = super().get_initial().copy() @@ -347,7 +337,6 @@ class SalesOrderCreate(AjaxCreateView): model = SalesOrder ajax_form_title = _("Create Sales Order") form_class = order_forms.EditSalesOrderForm - role_required = 'sales_order.add' def get_initial(self): initials = super().get_initial().copy() @@ -383,7 +372,6 @@ class PurchaseOrderEdit(AjaxUpdateView): model = PurchaseOrder ajax_form_title = _('Edit Purchase Order') form_class = order_forms.EditPurchaseOrderForm - role_required = 'purchase_order.change' def get_form(self): @@ -404,7 +392,6 @@ class SalesOrderEdit(AjaxUpdateView): model = SalesOrder ajax_form_title = _('Edit Sales Order') form_class = order_forms.EditSalesOrderForm - role_required = 'sales_order.change' def get_form(self): form = super().get_form() @@ -422,7 +409,6 @@ class PurchaseOrderCancel(AjaxUpdateView): ajax_form_title = _('Cancel Order') ajax_template_name = 'order/order_cancel.html' form_class = order_forms.CancelPurchaseOrderForm - role_required = 'purchase_order.change' def validate(self, order, form, **kwargs): @@ -449,7 +435,6 @@ class SalesOrderCancel(AjaxUpdateView): ajax_form_title = _("Cancel sales order") ajax_template_name = "order/sales_order_cancel.html" form_class = order_forms.CancelSalesOrderForm - role_required = 'sales_order.change' def validate(self, order, form, **kwargs): @@ -476,7 +461,6 @@ class PurchaseOrderIssue(AjaxUpdateView): ajax_form_title = _('Issue Order') ajax_template_name = "order/order_issue.html" form_class = order_forms.IssuePurchaseOrderForm - role_required = 'purchase_order.change' def validate(self, order, form, **kwargs): @@ -506,7 +490,6 @@ class PurchaseOrderComplete(AjaxUpdateView): ajax_template_name = "order/order_complete.html" ajax_form_title = _("Complete Order") context_object_name = 'order' - role_required = 'purchase_order.change' def get_context_data(self): @@ -543,7 +526,6 @@ class SalesOrderShip(AjaxUpdateView): context_object_name = 'order' ajax_template_name = 'order/sales_order_ship.html' ajax_form_title = _('Ship Order') - role_required = 'sales_order.change' def post(self, request, *args, **kwargs): @@ -587,6 +569,8 @@ class PurchaseOrderExport(AjaxView): """ model = PurchaseOrder + + # Specify role as we cannot introspect from "AjaxView" role_required = 'purchase_order.view' def get(self, request, *args, **kwargs): @@ -619,6 +603,8 @@ class PurchaseOrderReceive(AjaxUpdateView): form_class = order_forms.ReceivePurchaseOrderForm ajax_form_title = _("Receive Parts") ajax_template_name = "order/receive_parts.html" + + # Specify role as we do not specify a Model against this view role_required = 'purchase_order.change' # Where the parts will be going (selected in POST request) @@ -1117,7 +1103,6 @@ class POLineItemCreate(AjaxCreateView): context_object_name = 'line' form_class = order_forms.EditPurchaseOrderLineItemForm ajax_form_title = _('Add Line Item') - role_required = 'purchase_order.add' def validate(self, item, form, **kwargs): @@ -1201,7 +1186,6 @@ class SOLineItemCreate(AjaxCreateView): context_order_name = 'line' form_class = order_forms.EditSalesOrderLineItemForm ajax_form_title = _('Add Line Item') - role_required = 'sales_order.add' def get_form(self, *args, **kwargs): @@ -1253,7 +1237,6 @@ class SOLineItemEdit(AjaxUpdateView): model = SalesOrderLineItem form_class = order_forms.EditSalesOrderLineItemForm ajax_form_title = _('Edit Line Item') - role_required = 'sales_order.change' def get_form(self): form = super().get_form() @@ -1272,7 +1255,6 @@ class POLineItemEdit(AjaxUpdateView): form_class = order_forms.EditPurchaseOrderLineItemForm ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Line Item') - role_required = 'purchase_order.change' def get_form(self): form = super().get_form() @@ -1290,7 +1272,6 @@ class POLineItemDelete(AjaxDeleteView): model = PurchaseOrderLineItem ajax_form_title = _('Delete Line Item') ajax_template_name = 'order/po_lineitem_delete.html' - role_required = 'purchase_order.delete' def get_data(self): return { @@ -1303,7 +1284,6 @@ class SOLineItemDelete(AjaxDeleteView): model = SalesOrderLineItem ajax_form_title = _("Delete Line Item") ajax_template_name = "order/so_lineitem_delete.html" - role_required = 'sales_order.delete' def get_data(self): return { @@ -1317,8 +1297,7 @@ class SalesOrderAllocationCreate(AjaxCreateView): model = SalesOrderAllocation form_class = order_forms.EditSalesOrderAllocationForm ajax_form_title = _('Allocate Stock to Order') - role_required = 'sales_order.add' - + def get_initial(self): initials = super().get_initial().copy() @@ -1392,8 +1371,7 @@ class SalesOrderAllocationEdit(AjaxUpdateView): model = SalesOrderAllocation form_class = order_forms.EditSalesOrderAllocationForm ajax_form_title = _('Edit Allocation Quantity') - role_required = 'sales_order.change' - + def get_form(self): form = super().get_form() @@ -1410,4 +1388,3 @@ class SalesOrderAllocationDelete(AjaxDeleteView): ajax_form_title = _("Remove allocation") context_object_name = 'allocation' ajax_template_name = "order/so_allocation_delete.html" - role_required = 'sales_order.delete' diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index cce4db700d..1cb2976ff8 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -57,8 +57,6 @@ class PartIndex(InvenTreeRoleMixin, ListView): template_name = 'part/category.html' context_object_name = 'parts' - role_required = 'part.view' - def get_queryset(self): return Part.objects.all().select_related('category') @@ -85,7 +83,6 @@ class PartRelatedCreate(AjaxCreateView): form_class = part_forms.CreatePartRelatedForm ajax_form_title = _("Add Related Part") ajax_template_name = "modal_form.html" - role_required = 'part.change' def get_initial(self): """ Set parent part as part_1 field """ @@ -141,6 +138,8 @@ class PartRelatedDelete(AjaxDeleteView): model = PartRelated ajax_form_title = _("Delete Related Part") context_object_name = "related" + + # Explicit role requirement role_required = 'part.change' @@ -154,8 +153,6 @@ class PartAttachmentCreate(AjaxCreateView): ajax_form_title = _("Add part attachment") ajax_template_name = "modal_form.html" - role_required = 'part.add' - def save(self, form, **kwargs): """ Record the user that uploaded this attachment @@ -207,8 +204,6 @@ class PartAttachmentEdit(AjaxUpdateView): form_class = part_forms.EditPartAttachmentForm ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit attachment') - - role_required = 'part.change' def get_data(self): return { @@ -245,8 +240,6 @@ class PartTestTemplateCreate(AjaxCreateView): model = PartTestTemplate form_class = part_forms.EditPartTestTemplateForm ajax_form_title = _("Create Test Template") - - role_required = 'part.add' def get_initial(self): @@ -275,8 +268,6 @@ class PartTestTemplateEdit(AjaxUpdateView): form_class = part_forms.EditPartTestTemplateForm ajax_form_title = _("Edit Test Template") - role_required = 'part.change' - def get_form(self): form = super().get_form() @@ -291,8 +282,6 @@ class PartTestTemplateDelete(AjaxDeleteView): model = PartTestTemplate ajax_form_title = _("Delete Test Template") - role_required = 'part.delete' - class PartSetCategory(AjaxUpdateView): """ View for settings the part category for multiple parts at once """ @@ -386,8 +375,6 @@ class MakePartVariant(AjaxCreateView): ajax_form_title = _('Create Variant') ajax_template_name = 'part/variant_part.html' - role_required = 'part.add' - def get_part_template(self): return get_object_or_404(Part, id=self.kwargs['pk']) @@ -468,8 +455,6 @@ class PartDuplicate(AjaxCreateView): ajax_form_title = _("Duplicate Part") ajax_template_name = "part/copy_part.html" - role_required = 'part.add' - def get_data(self): return { 'success': _('Copied part') @@ -594,8 +579,6 @@ class PartCreate(AjaxCreateView): ajax_form_title = _('Create New Part') ajax_template_name = 'part/create_part.html' - role_required = 'part.add' - def get_data(self): return { 'success': _("Created new part"), @@ -772,8 +755,6 @@ class PartDetail(InvenTreeRoleMixin, DetailView): queryset = Part.objects.all().select_related('category') template_name = 'part/detail.html' - role_required = 'part.view' - # Add in some extra context information based on query params def get_context_data(self, **kwargs): """ Provide extra context data to template @@ -859,8 +840,6 @@ class PartImageUpload(AjaxUpdateView): form_class = part_forms.PartImageForm - role_required = 'part.change' - def get_data(self): return { 'success': _('Updated part image'), @@ -874,8 +853,6 @@ class PartImageSelect(AjaxUpdateView): ajax_template_name = 'part/select_image.html' ajax_form_title = _('Select Part Image') - role_required = 'part.change' - fields = [ 'image', ] @@ -917,8 +894,6 @@ class PartEdit(AjaxUpdateView): ajax_form_title = _('Edit Part Properties') context_object_name = 'part' - role_required = 'part.change' - def get_form(self): """ Create form for Part editing. Overrides default get_form() method to limit the choices @@ -948,7 +923,6 @@ class BomDuplicate(AjaxUpdateView): ajax_form_title = _('Duplicate BOM') ajax_template_name = 'part/bom_duplicate.html' form_class = part_forms.BomDuplicateForm - role_required = 'part.change' def get_form(self): @@ -1002,8 +976,6 @@ class BomValidate(AjaxUpdateView): context_object_name = 'part' form_class = part_forms.BomValidateForm - role_required = 'part.change' - def get_context(self): return { 'part': self.get_object(), @@ -1854,8 +1826,6 @@ class PartDelete(AjaxDeleteView): ajax_form_title = _('Confirm Part Deletion') context_object_name = 'part' - role_required = 'part.delete' - success_url = '/part/' def get_data(self): @@ -1977,9 +1947,9 @@ class PartPricing(AjaxView): class PartParameterTemplateCreate(AjaxCreateView): - """ View for creating a new PartParameterTemplate """ - - role_required = 'part.add' + """ + View for creating a new PartParameterTemplate + """ model = PartParameterTemplate form_class = part_forms.EditPartParameterTemplateForm @@ -1987,9 +1957,9 @@ class PartParameterTemplateCreate(AjaxCreateView): class PartParameterTemplateEdit(AjaxUpdateView): - """ View for editing a PartParameterTemplate """ - - role_required = 'part.change' + """ + View for editing a PartParameterTemplate + """ model = PartParameterTemplate form_class = part_forms.EditPartParameterTemplateForm @@ -1999,8 +1969,6 @@ class PartParameterTemplateEdit(AjaxUpdateView): class PartParameterTemplateDelete(AjaxDeleteView): """ View for deleting an existing PartParameterTemplate """ - role_required = 'part.delete' - model = PartParameterTemplate ajax_form_title = _("Delete Part Parameter Template") @@ -2008,8 +1976,6 @@ class PartParameterTemplateDelete(AjaxDeleteView): class PartParameterCreate(AjaxCreateView): """ View for creating a new PartParameter """ - role_required = 'part.add' - model = PartParameter form_class = part_forms.EditPartParameterForm ajax_form_title = _('Create Part Parameter') @@ -2060,8 +2026,6 @@ class PartParameterCreate(AjaxCreateView): class PartParameterEdit(AjaxUpdateView): """ View for editing a PartParameter """ - role_required = 'part.change' - model = PartParameter form_class = part_forms.EditPartParameterForm ajax_form_title = _('Edit Part Parameter') @@ -2076,8 +2040,6 @@ class PartParameterEdit(AjaxUpdateView): class PartParameterDelete(AjaxDeleteView): """ View for deleting a PartParameter """ - role_required = 'part.change' - model = PartParameter ajax_template_name = 'part/param_delete.html' ajax_form_title = _('Delete Part Parameter') @@ -2091,8 +2053,6 @@ class CategoryDetail(InvenTreeRoleMixin, DetailView): queryset = PartCategory.objects.all().prefetch_related('children') template_name = 'part/category_partlist.html' - role_required = ['part_category.view', 'part.view'] - def get_context_data(self, **kwargs): context = super(CategoryDetail, self).get_context_data(**kwargs).copy() @@ -2135,14 +2095,15 @@ class CategoryParametric(CategoryDetail): class CategoryEdit(AjaxUpdateView): - """ Update view to edit a PartCategory """ + """ + Update view to edit a PartCategory + """ + model = PartCategory form_class = part_forms.EditCategoryForm ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Part Category') - role_required = 'part_category.change' - def get_context_data(self, **kwargs): context = super(CategoryEdit, self).get_context_data(**kwargs).copy() @@ -2173,15 +2134,16 @@ class CategoryEdit(AjaxUpdateView): class CategoryDelete(AjaxDeleteView): - """ Delete view to delete a PartCategory """ + """ + Delete view to delete a PartCategory + """ + model = PartCategory ajax_template_name = 'part/category_delete.html' ajax_form_title = _('Delete Part Category') context_object_name = 'category' success_url = '/part/' - role_required = 'part_category.delete' - def get_data(self): return { 'danger': _('Part category was deleted'), @@ -2196,8 +2158,6 @@ class CategoryCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' form_class = part_forms.EditCategoryForm - role_required = 'part_category.add' - def get_context_data(self, **kwargs): """ Add extra context data to template. @@ -2236,8 +2196,6 @@ class CategoryCreate(AjaxCreateView): class CategoryParameterTemplateCreate(AjaxCreateView): """ View for creating a new PartCategoryParameterTemplate """ - role_required = 'part_category.change' - model = PartCategoryParameterTemplate form_class = part_forms.EditCategoryParameterTemplateForm ajax_form_title = _('Create Category Parameter Template') @@ -2339,8 +2297,6 @@ class CategoryParameterTemplateCreate(AjaxCreateView): class CategoryParameterTemplateEdit(AjaxUpdateView): """ View for editing a PartCategoryParameterTemplate """ - role_required = 'part_category.change' - model = PartCategoryParameterTemplate form_class = part_forms.EditCategoryParameterTemplateForm ajax_form_title = _('Edit Category Parameter Template') @@ -2398,8 +2354,6 @@ class CategoryParameterTemplateEdit(AjaxUpdateView): class CategoryParameterTemplateDelete(AjaxDeleteView): """ View for deleting an existing PartCategoryParameterTemplate """ - role_required = 'part_category.change' - model = PartCategoryParameterTemplate ajax_form_title = _("Delete Category Parameter Template") @@ -2413,14 +2367,15 @@ class CategoryParameterTemplateDelete(AjaxDeleteView): class BomItemCreate(AjaxCreateView): - """ Create view for making a new BomItem object """ + """ + Create view for making a new BomItem object + """ + model = BomItem form_class = part_forms.EditBomItemForm ajax_template_name = 'modal_form.html' ajax_form_title = _('Create BOM Item') - role_required = 'part.add' - def get_form(self): """ Override get_form() method to reduce Part selection options. @@ -2491,8 +2446,6 @@ class BomItemEdit(AjaxUpdateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit BOM item') - role_required = 'part.change' - def get_form(self): """ Override get_form() method to filter part selection options @@ -2543,13 +2496,12 @@ class BomItemEdit(AjaxUpdateView): class BomItemDelete(AjaxDeleteView): """ Delete view for removing BomItem """ + model = BomItem ajax_template_name = 'part/bom-delete.html' context_object_name = 'item' ajax_form_title = _('Confim BOM item deletion') - role_required = 'part.change' - class PartSalePriceBreakCreate(AjaxCreateView): """ View for creating a sale price break for a part """ @@ -2557,8 +2509,6 @@ class PartSalePriceBreakCreate(AjaxCreateView): model = PartSellPriceBreak form_class = part_forms.EditPartSalePriceBreakForm ajax_form_title = _('Add Price Break') - - role_required = 'part.add' def get_data(self): return { @@ -2608,8 +2558,6 @@ class PartSalePriceBreakEdit(AjaxUpdateView): form_class = part_forms.EditPartSalePriceBreakForm ajax_form_title = _('Edit Price Break') - role_required = 'part.change' - def get_form(self): form = super().get_form() @@ -2624,5 +2572,3 @@ class PartSalePriceBreakDelete(AjaxDeleteView): model = PartSellPriceBreak ajax_form_title = _("Delete Price Break") ajax_template_name = "modal_delete_form.html" - - role_required = 'part.delete' diff --git a/InvenTree/stock/urls.py b/InvenTree/stock/urls.py index 93758588ce..5c6c678978 100644 --- a/InvenTree/stock/urls.py +++ b/InvenTree/stock/urls.py @@ -45,9 +45,6 @@ stock_tracking_urls = [ # delete url(r'^(?P\d+)/delete', views.StockItemTrackingDelete.as_view(), name='stock-tracking-delete'), - - # list - url('^.*$', views.StockTrackingIndex.as_view(), name='stock-tracking-list') ] stock_urls = [ diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 0129332d1d..e697bf103a 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -49,7 +49,6 @@ class StockIndex(InvenTreeRoleMixin, ListView): 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() @@ -75,7 +74,6 @@ class StockLocationDetail(InvenTreeRoleMixin, DetailView): template_name = 'stock/location.html' queryset = StockLocation.objects.all() model = StockLocation - role_required = ['stock_location.view', 'stock.view'] class StockItemDetail(InvenTreeRoleMixin, DetailView): @@ -87,7 +85,6 @@ class StockItemDetail(InvenTreeRoleMixin, DetailView): template_name = 'stock/item.html' queryset = StockItem.objects.all() model = StockItem - role_required = 'stock.view' class StockItemNotes(InvenTreeRoleMixin, UpdateView): @@ -96,6 +93,7 @@ class StockItemNotes(InvenTreeRoleMixin, UpdateView): context_object_name = 'item' template_name = 'stock/item_notes.html' model = StockItem + role_required = 'stock.view' fields = ['notes'] @@ -123,8 +121,7 @@ class StockLocationEdit(AjaxUpdateView): context_object_name = 'location' ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Stock Location') - role_required = 'stock_location.change' - + def get_form(self): """ Customize form data for StockLocation editing. @@ -246,6 +243,7 @@ class StockLocationQRCode(QRCodeView): """ View for displaying a QR code for a StockLocation object """ ajax_form_title = _("Stock Location QR code") + role_required = ['stock_location.view', 'stock.view'] def get_qr_data(self): @@ -266,7 +264,6 @@ 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 save(self, form, **kwargs): """ Record the user that uploaded the attachment """ @@ -312,8 +309,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): form = super().get_form() @@ -331,8 +327,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 { 'danger': _("Deleted attachment"), @@ -348,7 +343,6 @@ class StockItemAssignToCustomer(AjaxUpdateView): ajax_form_title = _("Assign to Customer") context_object_name = "item" form_class = StockForms.AssignStockItemToCustomerForm - role_required = 'stock.change' def validate(self, item, form, **kwargs): @@ -382,8 +376,7 @@ class StockItemReturnToStock(AjaxUpdateView): ajax_form_title = _("Return to Stock") context_object_name = "item" form_class = StockForms.ReturnStockItemForm - role_required = 'stock.change' - + def validate(self, item, form, **kwargs): location = form.cleaned_data.get('location', None) @@ -412,6 +405,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): @@ -448,7 +442,6 @@ class StockItemTestResultCreate(AjaxCreateView): model = StockItemTestResult form_class = StockForms.EditStockItemTestResultForm ajax_form_title = _("Add Test Result") - role_required = 'stock.add' def save(self, form, **kwargs): """ @@ -489,7 +482,6 @@ class StockItemTestResultEdit(AjaxUpdateView): model = StockItemTestResult form_class = StockForms.EditStockItemTestResultForm ajax_form_title = _("Edit Test Result") - role_required = 'stock.change' def get_form(self): @@ -508,7 +500,6 @@ class StockItemTestResultDelete(AjaxDeleteView): model = StockItemTestResult ajax_form_title = _("Delete Test Result") context_object_name = "result" - role_required = 'stock.delete' class StockExportOptions(AjaxView): @@ -517,7 +508,6 @@ 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): @@ -665,7 +655,6 @@ 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 @@ -1232,7 +1221,6 @@ 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. @@ -1343,7 +1331,6 @@ 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): """ @@ -1369,7 +1356,6 @@ class StockLocationCreate(AjaxCreateView): context_object_name = 'location' ajax_template_name = 'modal_form.html' ajax_form_title = _('Create new Stock Location') - role_required = 'stock_location.add' def get_initial(self): initials = super(StockLocationCreate, self).get_initial().copy() @@ -1462,7 +1448,6 @@ 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): @@ -1555,7 +1540,6 @@ 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): """ @@ -1880,7 +1864,6 @@ class StockLocationDelete(AjaxDeleteView): ajax_template_name = 'stock/location_delete.html' context_object_name = 'location' ajax_form_title = _('Delete Stock Location') - role_required = 'stock_location.delete' class StockItemDelete(AjaxDeleteView): @@ -1894,7 +1877,6 @@ 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): @@ -1906,18 +1888,6 @@ 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(InvenTreeRoleMixin, ListView): - """ - StockTrackingIndex provides a page to display StockItemTracking objects - """ - - model = StockItemTracking - template_name = 'stock/tracking.html' - context_object_name = 'items' - role_required = 'stock.view' class StockItemTrackingEdit(AjaxUpdateView): @@ -1926,7 +1896,6 @@ class StockItemTrackingEdit(AjaxUpdateView): model = StockItemTracking ajax_form_title = _('Edit Stock Tracking Entry') form_class = StockForms.TrackingEntryForm - role_required = 'stock.change' class StockItemTrackingCreate(AjaxCreateView): @@ -1936,7 +1905,6 @@ 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/users/models.py b/InvenTree/users/models.py index 3952c00e91..903bc10dfa 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -68,6 +68,7 @@ class RuleSet(models.Model): 'part_partparameter', 'part_partrelated', 'part_partstar', + 'company_supplierpart', ], 'stock_location': [ 'stock_stocklocation', @@ -94,11 +95,11 @@ class RuleSet(models.Model): ], 'purchase_order': [ 'company_company', - 'company_supplierpart', 'company_supplierpricebreak', 'order_purchaseorder', 'order_purchaseorderattachment', 'order_purchaseorderlineitem', + 'company_supplierpart', ], 'sales_order': [ 'company_company', @@ -342,7 +343,7 @@ def update_group_roles(group, debug=False): content_type = ContentType.objects.get(app_label=app, model=model) permission = Permission.objects.get(content_type=content_type, codename=perm) except ContentType.DoesNotExist: - print(f"Error: Could not find permission matching '{permission_string}'") + raise ValueError(f"Error: Could not find permission matching '{permission_string}'") permission = None return permission