Change modal form permissions to use new "role" strategy

This commit is contained in:
Oliver Walters 2020-10-06 11:42:16 +11:00
parent c740cce5e4
commit 11d31960c7
2 changed files with 89 additions and 104 deletions

View File

@ -108,31 +108,66 @@ class TreeSerializer(views.APIView):
return JsonResponse(response, safe=False) 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. """ AjaxMixin provides basic functionality for rendering a Django form to JSON.
Handles jsonResponse rendering, and adds extra data for the modal forms to process Handles jsonResponse rendering, and adds extra data for the modal forms to process
on the client side. on the client side.
Any view which inherits the AjaxMixin will need 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 # By default, allow *any* role
permission_required = '*' role_required = None
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, point to the modal_form template # By default, point to the modal_form template
# (this can be overridden by a child class) # (this can be overridden by a child class)
@ -683,53 +718,3 @@ 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
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

View File

@ -80,7 +80,7 @@ class PartAttachmentCreate(AjaxCreateView):
ajax_form_title = _("Add part attachment") ajax_form_title = _("Add part attachment")
ajax_template_name = "modal_form.html" ajax_template_name = "modal_form.html"
permission_required = 'part.add_partattachment' role_required = 'part.add'
def post_save(self): def post_save(self):
""" Record the user that uploaded the attachment """ """ Record the user that uploaded the attachment """
@ -130,7 +130,7 @@ class PartAttachmentEdit(AjaxUpdateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit attachment') ajax_form_title = _('Edit attachment')
permission_required = 'part.change_partattachment' role_required = 'part.change'
def get_data(self): def get_data(self):
return { return {
@ -153,7 +153,7 @@ class PartAttachmentDelete(AjaxDeleteView):
ajax_template_name = "attachment_delete.html" ajax_template_name = "attachment_delete.html"
context_object_name = "attachment" context_object_name = "attachment"
permission_required = 'part.delete_partattachment' role_required = 'part.delete'
def get_data(self): def get_data(self):
return { return {
@ -168,7 +168,7 @@ class PartTestTemplateCreate(AjaxCreateView):
form_class = part_forms.EditPartTestTemplateForm form_class = part_forms.EditPartTestTemplateForm
ajax_form_title = _("Create Test Template") ajax_form_title = _("Create Test Template")
permission_required = 'part.add_parttesttemplate' role_required = 'part.add'
def get_initial(self): def get_initial(self):
@ -197,7 +197,7 @@ class PartTestTemplateEdit(AjaxUpdateView):
form_class = part_forms.EditPartTestTemplateForm form_class = part_forms.EditPartTestTemplateForm
ajax_form_title = _("Edit Test Template") ajax_form_title = _("Edit Test Template")
permission_required = 'part.change_parttesttemplate' role_required = 'part.change'
def get_form(self): def get_form(self):
@ -213,7 +213,7 @@ class PartTestTemplateDelete(AjaxDeleteView):
model = PartTestTemplate model = PartTestTemplate
ajax_form_title = _("Delete Test Template") ajax_form_title = _("Delete Test Template")
permission_required = 'part.delete_parttesttemplate' role_required = 'part.delete'
class PartSetCategory(AjaxUpdateView): class PartSetCategory(AjaxUpdateView):
@ -223,7 +223,7 @@ class PartSetCategory(AjaxUpdateView):
ajax_form_title = _('Set Part Category') ajax_form_title = _('Set Part Category')
form_class = part_forms.SetPartCategoryForm form_class = part_forms.SetPartCategoryForm
permission_required = 'part.change_part' role_required = 'part.change'
category = None category = None
parts = [] parts = []
@ -308,7 +308,7 @@ class MakePartVariant(AjaxCreateView):
ajax_form_title = _('Create Variant') ajax_form_title = _('Create Variant')
ajax_template_name = 'part/variant_part.html' ajax_template_name = 'part/variant_part.html'
permission_required = 'part.add_part' role_required = 'part.add'
def get_part_template(self): def get_part_template(self):
return get_object_or_404(Part, id=self.kwargs['pk']) return get_object_or_404(Part, id=self.kwargs['pk'])
@ -388,7 +388,7 @@ class PartDuplicate(AjaxCreateView):
ajax_form_title = _("Duplicate Part") ajax_form_title = _("Duplicate Part")
ajax_template_name = "part/copy_part.html" ajax_template_name = "part/copy_part.html"
permission_required = 'part.add_part' role_required = 'part.add'
def get_data(self): def get_data(self):
return { return {
@ -513,7 +513,7 @@ class PartCreate(AjaxCreateView):
ajax_form_title = _('Create new part') ajax_form_title = _('Create new part')
ajax_template_name = 'part/create_part.html' ajax_template_name = 'part/create_part.html'
permission_required = 'part.add_part' role_required = 'part.add'
def get_data(self): def get_data(self):
return { return {
@ -637,7 +637,7 @@ class PartNotes(UpdateView):
template_name = 'part/notes.html' template_name = 'part/notes.html'
model = Part model = Part
permission_required = 'part.change_part' role_required = 'part.change'
fields = ['notes'] fields = ['notes']
@ -734,7 +734,7 @@ class PartQRCode(QRCodeView):
ajax_form_title = _("Part QR Code") ajax_form_title = _("Part QR Code")
permission_required = 'part.view_part' role_required = 'part.view'
def get_qr_data(self): def get_qr_data(self):
""" Generate QR code data for the Part """ """ Generate QR code data for the Part """
@ -755,7 +755,7 @@ class PartImageUpload(AjaxUpdateView):
form_class = part_forms.PartImageForm form_class = part_forms.PartImageForm
permission_required = 'part.change_part' role_required = 'part.change'
def get_data(self): def get_data(self):
return { return {
@ -770,7 +770,7 @@ class PartImageSelect(AjaxUpdateView):
ajax_template_name = 'part/select_image.html' ajax_template_name = 'part/select_image.html'
ajax_form_title = _('Select Part Image') ajax_form_title = _('Select Part Image')
permission_required = 'part.change_part' role_required = 'part.change'
fields = [ fields = [
'image', 'image',
@ -813,7 +813,7 @@ class PartEdit(AjaxUpdateView):
ajax_form_title = _('Edit Part Properties') ajax_form_title = _('Edit Part Properties')
context_object_name = 'part' context_object_name = 'part'
permission_required = 'part.change_part' role_required = 'part.change'
def get_form(self): def get_form(self):
""" Create form for Part editing. """ Create form for Part editing.
@ -839,7 +839,7 @@ class BomValidate(AjaxUpdateView):
context_object_name = 'part' context_object_name = 'part'
form_class = part_forms.BomValidateForm form_class = part_forms.BomValidateForm
permission_required = ('part.change_part') role_required = 'part.change'
def get_context(self): def get_context(self):
return { return {
@ -1507,7 +1507,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
class PartExport(AjaxView): class PartExport(AjaxView):
""" Export a CSV file containing information on multiple parts """ """ Export a CSV file containing information on multiple parts """
permission_required = 'part.view_part' role_required = 'part.view'
def get_parts(self, request): def get_parts(self, request):
""" Extract part list from the POST parameters. """ 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 - 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 model = Part
@ -1641,7 +1641,7 @@ class BomExport(AjaxView):
form_class = part_forms.BomExportForm form_class = part_forms.BomExportForm
ajax_form_title = _("Export Bill of Materials") 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): def get(self, request, *args, **kwargs):
return self.renderJsonResponse(request, self.form_class()) return self.renderJsonResponse(request, self.form_class())
@ -1692,7 +1692,7 @@ class PartDelete(AjaxDeleteView):
ajax_form_title = _('Confirm Part Deletion') ajax_form_title = _('Confirm Part Deletion')
context_object_name = 'part' context_object_name = 'part'
permission_required = 'part.delete_part' role_required = 'part.delete'
success_url = '/part/' success_url = '/part/'
@ -1710,7 +1710,7 @@ class PartPricing(AjaxView):
ajax_form_title = _("Part Pricing") ajax_form_title = _("Part Pricing")
form_class = part_forms.PartPriceForm form_class = part_forms.PartPriceForm
permission_required = ('company.view_supplierpricebreak', 'part.view_part') role_required = ['sales_order.view', 'part.view']
def get_part(self): def get_part(self):
try: try:
@ -1829,7 +1829,7 @@ class PartPricing(AjaxView):
class PartParameterTemplateCreate(AjaxCreateView): class PartParameterTemplateCreate(AjaxCreateView):
""" View for creating a new PartParameterTemplate """ """ View for creating a new PartParameterTemplate """
permission_required = 'part.add_partparametertemplate' role_required = 'part.add'
model = PartParameterTemplate model = PartParameterTemplate
form_class = part_forms.EditPartParameterTemplateForm form_class = part_forms.EditPartParameterTemplateForm
@ -1839,7 +1839,7 @@ class PartParameterTemplateCreate(AjaxCreateView):
class PartParameterTemplateEdit(AjaxUpdateView): class PartParameterTemplateEdit(AjaxUpdateView):
""" View for editing a PartParameterTemplate """ """ View for editing a PartParameterTemplate """
permission_required = 'part.change_partparametertemplate' role_required = 'part.change'
model = PartParameterTemplate model = PartParameterTemplate
form_class = part_forms.EditPartParameterTemplateForm form_class = part_forms.EditPartParameterTemplateForm
@ -1849,7 +1849,7 @@ class PartParameterTemplateEdit(AjaxUpdateView):
class PartParameterTemplateDelete(AjaxDeleteView): class PartParameterTemplateDelete(AjaxDeleteView):
""" View for deleting an existing PartParameterTemplate """ """ View for deleting an existing PartParameterTemplate """
permission_required = 'part.delete_partparametertemplate' role_required = 'part.delete'
model = PartParameterTemplate model = PartParameterTemplate
ajax_form_title = _("Delete Part Parameter Template") ajax_form_title = _("Delete Part Parameter Template")
@ -1858,7 +1858,7 @@ class PartParameterTemplateDelete(AjaxDeleteView):
class PartParameterCreate(AjaxCreateView): class PartParameterCreate(AjaxCreateView):
""" View for creating a new PartParameter """ """ View for creating a new PartParameter """
permission_required = 'part.add_partparameter' role_required = 'part.add'
model = PartParameter model = PartParameter
form_class = part_forms.EditPartParameterForm form_class = part_forms.EditPartParameterForm
@ -1910,7 +1910,7 @@ class PartParameterCreate(AjaxCreateView):
class PartParameterEdit(AjaxUpdateView): class PartParameterEdit(AjaxUpdateView):
""" View for editing a PartParameter """ """ View for editing a PartParameter """
permission_required = 'part.change_partparameter' role_required = 'part.change'
model = PartParameter model = PartParameter
form_class = part_forms.EditPartParameterForm form_class = part_forms.EditPartParameterForm
@ -1926,7 +1926,7 @@ class PartParameterEdit(AjaxUpdateView):
class PartParameterDelete(AjaxDeleteView): class PartParameterDelete(AjaxDeleteView):
""" View for deleting a PartParameter """ """ View for deleting a PartParameter """
permission_required = 'part.delete_partparameter' role_required = 'part.delete'
model = PartParameter model = PartParameter
ajax_template_name = 'part/param_delete.html' ajax_template_name = 'part/param_delete.html'
@ -1991,7 +1991,7 @@ class CategoryEdit(AjaxUpdateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit Part Category') ajax_form_title = _('Edit Part Category')
permission_required = 'part.change_partcategory' role_required = 'part.change'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CategoryEdit, self).get_context_data(**kwargs).copy() context = super(CategoryEdit, self).get_context_data(**kwargs).copy()
@ -2030,7 +2030,7 @@ class CategoryDelete(AjaxDeleteView):
context_object_name = 'category' context_object_name = 'category'
success_url = '/part/' success_url = '/part/'
permission_required = 'part.delete_partcategory' role_required = 'part.delete'
def get_data(self): def get_data(self):
return { return {
@ -2046,7 +2046,7 @@ class CategoryCreate(AjaxCreateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
form_class = part_forms.EditCategoryForm form_class = part_forms.EditCategoryForm
permission_required = 'part.add_partcategory' role_required = 'part.add'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Add extra context data to template. """ Add extra context data to template.
@ -2099,7 +2099,7 @@ class BomItemCreate(AjaxCreateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Create BOM item') ajax_form_title = _('Create BOM item')
permission_required = 'part.add_bomitem' role_required = 'part.add'
def get_form(self): def get_form(self):
""" Override get_form() method to reduce Part selection options. """ Override get_form() method to reduce Part selection options.
@ -2167,7 +2167,7 @@ class BomItemEdit(AjaxUpdateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit BOM item') ajax_form_title = _('Edit BOM item')
permission_required = 'part.change_bomitem' role_required = 'part.change'
def get_form(self): def get_form(self):
""" Override get_form() method to filter part selection options """ Override get_form() method to filter part selection options
@ -2217,7 +2217,7 @@ class BomItemDelete(AjaxDeleteView):
context_object_name = 'item' context_object_name = 'item'
ajax_form_title = _('Confim BOM item deletion') ajax_form_title = _('Confim BOM item deletion')
permission_required = 'part.delete_bomitem' role_required = 'part.delete'
class PartSalePriceBreakCreate(AjaxCreateView): class PartSalePriceBreakCreate(AjaxCreateView):
@ -2227,7 +2227,7 @@ class PartSalePriceBreakCreate(AjaxCreateView):
form_class = part_forms.EditPartSalePriceBreakForm form_class = part_forms.EditPartSalePriceBreakForm
ajax_form_title = _('Add Price Break') ajax_form_title = _('Add Price Break')
permission_required = 'part.add_partsellpricebreak' role_required = 'part.add'
def get_data(self): def get_data(self):
return { return {
@ -2278,7 +2278,7 @@ class PartSalePriceBreakEdit(AjaxUpdateView):
form_class = part_forms.EditPartSalePriceBreakForm form_class = part_forms.EditPartSalePriceBreakForm
ajax_form_title = _('Edit Price Break') ajax_form_title = _('Edit Price Break')
permission_required = 'part.change_partsellpricebreak' role_required = 'part.change'
def get_form(self): def get_form(self):
@ -2295,4 +2295,4 @@ class PartSalePriceBreakDelete(AjaxDeleteView):
ajax_form_title = _("Delete Price Break") ajax_form_title = _("Delete Price Break")
ajax_template_name = "modal_delete_form.html" ajax_template_name = "modal_delete_form.html"
permission_required = 'part.delete_partsalepricebreak' role_required = 'part.delete'