From 11d31960c7c64b78b6ced94fe662d611119596a9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 6 Oct 2020 11:42:16 +1100 Subject: [PATCH] Change modal form permissions to use new "role" strategy --- InvenTree/InvenTree/views.py | 119 +++++++++++++++-------------------- InvenTree/part/views.py | 74 +++++++++++----------- 2 files changed, 89 insertions(+), 104 deletions(-) diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index ca8f6c646c..198903db9a 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -108,31 +108,66 @@ class TreeSerializer(views.APIView): return JsonResponse(response, safe=False) -class AjaxMixin(PermissionRequiredMixin): +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 + + user = self.request.user + + # Superuser can have any permissions they desire + if user.is_superuser: + return True + + 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 + + +class AjaxMixin(InvenTreeRoleMixin): """ AjaxMixin provides basic functionality for rendering a Django form to JSON. Handles jsonResponse rendering, and adds extra data for the modal forms to process on the client side. Any view which inherits the AjaxMixin will need - correct permissions set using the 'permission_required' attribute + correct permissions set using the 'role_required' attribute """ - # By default, allow *any* permissions - permission_required = '*' - - def has_permission(self): - """ - Override the default behaviour of has_permission from PermissionRequiredMixin. - - Basically, if permission_required attribute = '*', - no permissions are actually required! - """ - - if self.permission_required == '*': - return True - else: - return super().has_permission() + # By default, allow *any* role + role_required = None # By default, point to the modal_form template # (this can be overridden by a child class) @@ -683,53 +718,3 @@ class DatabaseStatsView(AjaxView): """ 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 - - 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 diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 09390bf8c0..6498774285 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -80,7 +80,7 @@ class PartAttachmentCreate(AjaxCreateView): ajax_form_title = _("Add part attachment") ajax_template_name = "modal_form.html" - permission_required = 'part.add_partattachment' + role_required = 'part.add' def post_save(self): """ Record the user that uploaded the attachment """ @@ -130,7 +130,7 @@ class PartAttachmentEdit(AjaxUpdateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit attachment') - permission_required = 'part.change_partattachment' + role_required = 'part.change' def get_data(self): return { @@ -153,7 +153,7 @@ class PartAttachmentDelete(AjaxDeleteView): ajax_template_name = "attachment_delete.html" context_object_name = "attachment" - permission_required = 'part.delete_partattachment' + role_required = 'part.delete' def get_data(self): return { @@ -168,7 +168,7 @@ class PartTestTemplateCreate(AjaxCreateView): form_class = part_forms.EditPartTestTemplateForm ajax_form_title = _("Create Test Template") - permission_required = 'part.add_parttesttemplate' + role_required = 'part.add' def get_initial(self): @@ -197,7 +197,7 @@ class PartTestTemplateEdit(AjaxUpdateView): form_class = part_forms.EditPartTestTemplateForm ajax_form_title = _("Edit Test Template") - permission_required = 'part.change_parttesttemplate' + role_required = 'part.change' def get_form(self): @@ -213,7 +213,7 @@ class PartTestTemplateDelete(AjaxDeleteView): model = PartTestTemplate ajax_form_title = _("Delete Test Template") - permission_required = 'part.delete_parttesttemplate' + role_required = 'part.delete' class PartSetCategory(AjaxUpdateView): @@ -223,7 +223,7 @@ class PartSetCategory(AjaxUpdateView): ajax_form_title = _('Set Part Category') form_class = part_forms.SetPartCategoryForm - permission_required = 'part.change_part' + role_required = 'part.change' category = None parts = [] @@ -308,7 +308,7 @@ class MakePartVariant(AjaxCreateView): ajax_form_title = _('Create Variant') ajax_template_name = 'part/variant_part.html' - permission_required = 'part.add_part' + role_required = 'part.add' def get_part_template(self): return get_object_or_404(Part, id=self.kwargs['pk']) @@ -388,7 +388,7 @@ class PartDuplicate(AjaxCreateView): ajax_form_title = _("Duplicate Part") ajax_template_name = "part/copy_part.html" - permission_required = 'part.add_part' + role_required = 'part.add' def get_data(self): return { @@ -513,7 +513,7 @@ class PartCreate(AjaxCreateView): ajax_form_title = _('Create new part') ajax_template_name = 'part/create_part.html' - permission_required = 'part.add_part' + role_required = 'part.add' def get_data(self): return { @@ -637,7 +637,7 @@ class PartNotes(UpdateView): template_name = 'part/notes.html' model = Part - permission_required = 'part.change_part' + role_required = 'part.change' fields = ['notes'] @@ -734,7 +734,7 @@ class PartQRCode(QRCodeView): ajax_form_title = _("Part QR Code") - permission_required = 'part.view_part' + role_required = 'part.view' def get_qr_data(self): """ Generate QR code data for the Part """ @@ -755,7 +755,7 @@ class PartImageUpload(AjaxUpdateView): form_class = part_forms.PartImageForm - permission_required = 'part.change_part' + role_required = 'part.change' def get_data(self): return { @@ -770,7 +770,7 @@ class PartImageSelect(AjaxUpdateView): ajax_template_name = 'part/select_image.html' ajax_form_title = _('Select Part Image') - permission_required = 'part.change_part' + role_required = 'part.change' fields = [ 'image', @@ -813,7 +813,7 @@ class PartEdit(AjaxUpdateView): ajax_form_title = _('Edit Part Properties') context_object_name = 'part' - permission_required = 'part.change_part' + role_required = 'part.change' def get_form(self): """ Create form for Part editing. @@ -839,7 +839,7 @@ class BomValidate(AjaxUpdateView): context_object_name = 'part' form_class = part_forms.BomValidateForm - permission_required = ('part.change_part') + role_required = 'part.change' def get_context(self): return { @@ -1507,7 +1507,7 @@ class BomUpload(InvenTreeRoleMixin, FormView): class PartExport(AjaxView): """ Export a CSV file containing information on multiple parts """ - permission_required = 'part.view_part' + role_required = 'part.view' def get_parts(self, request): """ Extract part list from the POST parameters. @@ -1586,7 +1586,7 @@ class BomDownload(AjaxView): - File format should be passed as a query param e.g. ?format=csv """ - permission_required = ('part.view_part', 'part.view_bomitem') + role_required = 'part.view' model = Part @@ -1641,7 +1641,7 @@ class BomExport(AjaxView): form_class = part_forms.BomExportForm ajax_form_title = _("Export Bill of Materials") - permission_required = ('part.view_part', 'part.view_bomitem') + role_required = 'part.view' def get(self, request, *args, **kwargs): return self.renderJsonResponse(request, self.form_class()) @@ -1692,7 +1692,7 @@ class PartDelete(AjaxDeleteView): ajax_form_title = _('Confirm Part Deletion') context_object_name = 'part' - permission_required = 'part.delete_part' + role_required = 'part.delete' success_url = '/part/' @@ -1710,8 +1710,8 @@ class PartPricing(AjaxView): ajax_form_title = _("Part Pricing") form_class = part_forms.PartPriceForm - permission_required = ('company.view_supplierpricebreak', 'part.view_part') - + role_required = ['sales_order.view', 'part.view'] + def get_part(self): try: return Part.objects.get(id=self.kwargs['pk']) @@ -1829,7 +1829,7 @@ class PartPricing(AjaxView): class PartParameterTemplateCreate(AjaxCreateView): """ View for creating a new PartParameterTemplate """ - permission_required = 'part.add_partparametertemplate' + role_required = 'part.add' model = PartParameterTemplate form_class = part_forms.EditPartParameterTemplateForm @@ -1839,7 +1839,7 @@ class PartParameterTemplateCreate(AjaxCreateView): class PartParameterTemplateEdit(AjaxUpdateView): """ View for editing a PartParameterTemplate """ - permission_required = 'part.change_partparametertemplate' + role_required = 'part.change' model = PartParameterTemplate form_class = part_forms.EditPartParameterTemplateForm @@ -1849,7 +1849,7 @@ class PartParameterTemplateEdit(AjaxUpdateView): class PartParameterTemplateDelete(AjaxDeleteView): """ View for deleting an existing PartParameterTemplate """ - permission_required = 'part.delete_partparametertemplate' + role_required = 'part.delete' model = PartParameterTemplate ajax_form_title = _("Delete Part Parameter Template") @@ -1858,7 +1858,7 @@ class PartParameterTemplateDelete(AjaxDeleteView): class PartParameterCreate(AjaxCreateView): """ View for creating a new PartParameter """ - permission_required = 'part.add_partparameter' + role_required = 'part.add' model = PartParameter form_class = part_forms.EditPartParameterForm @@ -1910,7 +1910,7 @@ class PartParameterCreate(AjaxCreateView): class PartParameterEdit(AjaxUpdateView): """ View for editing a PartParameter """ - permission_required = 'part.change_partparameter' + role_required = 'part.change' model = PartParameter form_class = part_forms.EditPartParameterForm @@ -1926,7 +1926,7 @@ class PartParameterEdit(AjaxUpdateView): class PartParameterDelete(AjaxDeleteView): """ View for deleting a PartParameter """ - permission_required = 'part.delete_partparameter' + role_required = 'part.delete' model = PartParameter ajax_template_name = 'part/param_delete.html' @@ -1991,7 +1991,7 @@ class CategoryEdit(AjaxUpdateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Part Category') - permission_required = 'part.change_partcategory' + role_required = 'part.change' def get_context_data(self, **kwargs): context = super(CategoryEdit, self).get_context_data(**kwargs).copy() @@ -2030,7 +2030,7 @@ class CategoryDelete(AjaxDeleteView): context_object_name = 'category' success_url = '/part/' - permission_required = 'part.delete_partcategory' + role_required = 'part.delete' def get_data(self): return { @@ -2046,7 +2046,7 @@ class CategoryCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' form_class = part_forms.EditCategoryForm - permission_required = 'part.add_partcategory' + role_required = 'part.add' def get_context_data(self, **kwargs): """ Add extra context data to template. @@ -2099,7 +2099,7 @@ class BomItemCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Create BOM item') - permission_required = 'part.add_bomitem' + role_required = 'part.add' def get_form(self): """ Override get_form() method to reduce Part selection options. @@ -2167,7 +2167,7 @@ class BomItemEdit(AjaxUpdateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit BOM item') - permission_required = 'part.change_bomitem' + role_required = 'part.change' def get_form(self): """ Override get_form() method to filter part selection options @@ -2217,7 +2217,7 @@ class BomItemDelete(AjaxDeleteView): context_object_name = 'item' ajax_form_title = _('Confim BOM item deletion') - permission_required = 'part.delete_bomitem' + role_required = 'part.delete' class PartSalePriceBreakCreate(AjaxCreateView): @@ -2227,7 +2227,7 @@ class PartSalePriceBreakCreate(AjaxCreateView): form_class = part_forms.EditPartSalePriceBreakForm ajax_form_title = _('Add Price Break') - permission_required = 'part.add_partsellpricebreak' + role_required = 'part.add' def get_data(self): return { @@ -2278,7 +2278,7 @@ class PartSalePriceBreakEdit(AjaxUpdateView): form_class = part_forms.EditPartSalePriceBreakForm ajax_form_title = _('Edit Price Break') - permission_required = 'part.change_partsellpricebreak' + role_required = 'part.change' def get_form(self): @@ -2295,4 +2295,4 @@ class PartSalePriceBreakDelete(AjaxDeleteView): ajax_form_title = _("Delete Price Break") ajax_template_name = "modal_delete_form.html" - permission_required = 'part.delete_partsalepricebreak' + role_required = 'part.delete'