diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index dc8d07f5cd..84628eff04 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -12,6 +12,7 @@ from django.shortcuts import HttpResponseRedirect from django.utils.translation import gettext_lazy as _ from django.urls import reverse, reverse_lazy from django.views.generic import DetailView, ListView, FormView, UpdateView +from django.contrib.auth.mixins import PermissionRequiredMixin from django.forms.models import model_to_dict from django.forms import HiddenInput, CheckboxInput from django.conf import settings @@ -42,12 +43,13 @@ from InvenTree.views import QRCodeView from InvenTree.helpers import DownloadFile, str2bool -class PartIndex(ListView): +class PartIndex(PermissionRequiredMixin, ListView): """ View for displaying list of Part objects """ model = Part template_name = 'part/category.html' context_object_name = 'parts' + permission_required = ('part.view_part', 'part.view_partcategory') def get_queryset(self): return Part.objects.all().select_related('category') @@ -76,6 +78,8 @@ class PartAttachmentCreate(AjaxCreateView): ajax_form_title = _("Add part attachment") ajax_template_name = "modal_form.html" + permission_required = 'part.create_partattachment' + def post_save(self): """ Record the user that uploaded the attachment """ self.object.user = self.request.user @@ -123,6 +127,8 @@ class PartAttachmentEdit(AjaxUpdateView): form_class = part_forms.EditPartAttachmentForm ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit attachment') + + permission_required = 'part.change_partattachment' def get_data(self): return { @@ -145,6 +151,8 @@ class PartAttachmentDelete(AjaxDeleteView): ajax_template_name = "attachment_delete.html" context_object_name = "attachment" + permission_required = 'part.delete_partattachment' + def get_data(self): return { 'danger': _('Deleted part attachment') @@ -157,6 +165,8 @@ class PartTestTemplateCreate(AjaxCreateView): model = PartTestTemplate form_class = part_forms.EditPartTestTemplateForm ajax_form_title = _("Create Test Template") + + permission_required = 'part.create_parttesttemplate' def get_initial(self): @@ -185,6 +195,8 @@ class PartTestTemplateEdit(AjaxUpdateView): form_class = part_forms.EditPartTestTemplateForm ajax_form_title = _("Edit Test Template") + permission_required = 'part.change_parttesttemplate' + def get_form(self): form = super().get_form() @@ -199,6 +211,8 @@ class PartTestTemplateDelete(AjaxDeleteView): model = PartTestTemplate ajax_form_title = _("Delete Test Template") + permission_required = 'part.delete_parttesttemplate' + class PartSetCategory(AjaxUpdateView): """ View for settings the part category for multiple parts at once """ @@ -207,6 +221,8 @@ class PartSetCategory(AjaxUpdateView): ajax_form_title = _('Set Part Category') form_class = part_forms.SetPartCategoryForm + permission_required = 'part.change_part' + category = None parts = [] @@ -290,6 +306,8 @@ class MakePartVariant(AjaxCreateView): ajax_form_title = _('Create Variant') ajax_template_name = 'part/variant_part.html' + permission_required = 'part.create_part' + def get_part_template(self): return get_object_or_404(Part, id=self.kwargs['pk']) @@ -368,6 +386,8 @@ class PartDuplicate(AjaxCreateView): ajax_form_title = _("Duplicate Part") ajax_template_name = "part/copy_part.html" + permission_required = 'part.create_part' + def get_data(self): return { 'success': _('Copied part') @@ -491,6 +511,8 @@ class PartCreate(AjaxCreateView): ajax_form_title = _('Create new part') ajax_template_name = 'part/create_part.html' + permission_required = 'part.create_part' + def get_data(self): return { 'success': _("Created new part"), @@ -613,6 +635,8 @@ class PartNotes(UpdateView): template_name = 'part/notes.html' model = Part + permission_required = 'part.update_part' + fields = ['notes'] def get_success_url(self): @@ -634,7 +658,7 @@ class PartNotes(UpdateView): return ctx -class PartDetail(DetailView): +class PartDetail(PermissionRequiredMixin, DetailView): """ Detail view for Part object """ @@ -642,6 +666,8 @@ class PartDetail(DetailView): queryset = Part.objects.all().select_related('category') template_name = 'part/detail.html' + permission_required = 'part.view_part' + # Add in some extra context information based on query params def get_context_data(self, **kwargs): """ Provide extra context data to template @@ -706,6 +732,8 @@ class PartQRCode(QRCodeView): ajax_form_title = _("Part QR Code") + permission_required = 'part.view_part' + def get_qr_data(self): """ Generate QR code data for the Part """ @@ -722,8 +750,11 @@ class PartImageUpload(AjaxUpdateView): model = Part ajax_template_name = 'modal_form.html' ajax_form_title = _('Upload Part Image') + form_class = part_forms.PartImageForm + permission_required = 'part.update_part' + def get_data(self): return { 'success': _('Updated part image'), @@ -737,6 +768,8 @@ class PartImageSelect(AjaxUpdateView): ajax_template_name = 'part/select_image.html' ajax_form_title = _('Select Part Image') + permission_required = 'part.update_part' + fields = [ 'image', ] @@ -778,6 +811,8 @@ class PartEdit(AjaxUpdateView): ajax_form_title = _('Edit Part Properties') context_object_name = 'part' + permission_required = 'part.update_part' + def get_form(self): """ Create form for Part editing. Overrides default get_form() method to limit the choices @@ -802,6 +837,8 @@ class BomValidate(AjaxUpdateView): context_object_name = 'part' form_class = part_forms.BomValidateForm + permission_required = ('part.update_part') + def get_context(self): return { 'part': self.get_object(), @@ -832,7 +869,7 @@ class BomValidate(AjaxUpdateView): return self.renderJsonResponse(request, form, data, context=self.get_context()) -class BomUpload(FormView): +class BomUpload(PermissionRequiredMixin, FormView): """ View for uploading a BOM file, and handling BOM data importing. The BOM upload process is as follows: @@ -868,6 +905,8 @@ class BomUpload(FormView): missing_columns = [] allowed_parts = [] + permission_required = ('part.update_part', 'part.create_bomitem') + def get_success_url(self): part = self.get_object() return reverse('upload-bom', kwargs={'pk': part.id}) @@ -1466,6 +1505,8 @@ class BomUpload(FormView): class PartExport(AjaxView): """ Export a CSV file containing information on multiple parts """ + permission_required = 'part.view_part' + def get_parts(self, request): """ Extract part list from the POST parameters. Parts can be supplied as: @@ -1543,6 +1584,8 @@ class BomDownload(AjaxView): - File format should be passed as a query param e.g. ?format=csv """ + permission_required = ('part.view_part', 'part.view_bomitem') + model = Part def get(self, request, *args, **kwargs): @@ -1596,6 +1639,8 @@ class BomExport(AjaxView): form_class = part_forms.BomExportForm ajax_form_title = _("Export Bill of Materials") + permission_required = ('part.view_part', 'part.view_bomitem') + def get(self, request, *args, **kwargs): return self.renderJsonResponse(request, self.form_class()) @@ -1645,6 +1690,8 @@ class PartDelete(AjaxDeleteView): ajax_form_title = _('Confirm Part Deletion') context_object_name = 'part' + permission_required = 'part.delete_part' + success_url = '/part/' def get_data(self): @@ -1661,6 +1708,8 @@ class PartPricing(AjaxView): ajax_form_title = _("Part Pricing") form_class = part_forms.PartPriceForm + permission_required = ('company.view_supplierpricebreak', 'part.view_part') + def get_part(self): try: return Part.objects.get(id=self.kwargs['pk']) @@ -1778,6 +1827,8 @@ class PartPricing(AjaxView): class PartParameterTemplateCreate(AjaxCreateView): """ View for creating a new PartParameterTemplate """ + permission_required = 'part.create_partparametertemplate' + model = PartParameterTemplate form_class = part_forms.EditPartParameterTemplateForm ajax_form_title = _('Create Part Parameter Template') @@ -1786,6 +1837,8 @@ class PartParameterTemplateCreate(AjaxCreateView): class PartParameterTemplateEdit(AjaxUpdateView): """ View for editing a PartParameterTemplate """ + permission_required = 'part.change_partparametertemplate' + model = PartParameterTemplate form_class = part_forms.EditPartParameterTemplateForm ajax_form_title = _('Edit Part Parameter Template') @@ -1794,6 +1847,8 @@ class PartParameterTemplateEdit(AjaxUpdateView): class PartParameterTemplateDelete(AjaxDeleteView): """ View for deleting an existing PartParameterTemplate """ + permission_required = 'part.delete_partparametertemplate' + model = PartParameterTemplate ajax_form_title = _("Delete Part Parameter Template") @@ -1801,6 +1856,8 @@ class PartParameterTemplateDelete(AjaxDeleteView): class PartParameterCreate(AjaxCreateView): """ View for creating a new PartParameter """ + permission_required = 'part.create_partparameter' + model = PartParameter form_class = part_forms.EditPartParameterForm ajax_form_title = _('Create Part Parameter') @@ -1851,6 +1908,8 @@ class PartParameterCreate(AjaxCreateView): class PartParameterEdit(AjaxUpdateView): """ View for editing a PartParameter """ + permission_required = 'part.change_partparameter' + model = PartParameter form_class = part_forms.EditPartParameterForm ajax_form_title = _('Edit Part Parameter') @@ -1865,12 +1924,14 @@ class PartParameterEdit(AjaxUpdateView): class PartParameterDelete(AjaxDeleteView): """ View for deleting a PartParameter """ + permission_required = 'part.delete_partparameter' + model = PartParameter ajax_template_name = 'part/param_delete.html' ajax_form_title = _('Delete Part Parameter') -class CategoryDetail(DetailView): +class CategoryDetail(PermissionRequiredMixin, DetailView): """ Detail view for PartCategory """ model = PartCategory @@ -1878,6 +1939,8 @@ class CategoryDetail(DetailView): queryset = PartCategory.objects.all().prefetch_related('children') template_name = 'part/category_partlist.html' + permission_required = 'part.view_partcategory' + def get_context_data(self, **kwargs): context = super(CategoryDetail, self).get_context_data(**kwargs).copy() @@ -1926,6 +1989,8 @@ class CategoryEdit(AjaxUpdateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Part Category') + permission_required = 'part.change_partcategory' + def get_context_data(self, **kwargs): context = super(CategoryEdit, self).get_context_data(**kwargs).copy() @@ -1963,6 +2028,8 @@ class CategoryDelete(AjaxDeleteView): context_object_name = 'category' success_url = '/part/' + permission_required = 'part.delete_partcategory' + def get_data(self): return { 'danger': _('Part category was deleted'), @@ -1977,6 +2044,8 @@ class CategoryCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' form_class = part_forms.EditCategoryForm + permission_required = 'part.create_partcategory' + def get_context_data(self, **kwargs): """ Add extra context data to template. @@ -2012,12 +2081,14 @@ class CategoryCreate(AjaxCreateView): return initials -class BomItemDetail(DetailView): +class BomItemDetail(PermissionRequiredMixin, DetailView): """ Detail view for BomItem """ context_object_name = 'item' queryset = BomItem.objects.all() template_name = 'part/bom-detail.html' + permission_required = 'part.view_bomitem' + class BomItemCreate(AjaxCreateView): """ Create view for making a new BomItem object """ @@ -2026,6 +2097,8 @@ class BomItemCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Create BOM item') + permission_required = 'part.create_bomitem' + def get_form(self): """ Override get_form() method to reduce Part selection options. @@ -2092,6 +2165,8 @@ class BomItemEdit(AjaxUpdateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit BOM item') + permission_required = 'part.change_bomitem' + def get_form(self): """ Override get_form() method to filter part selection options @@ -2140,6 +2215,8 @@ class BomItemDelete(AjaxDeleteView): context_object_name = 'item' ajax_form_title = _('Confim BOM item deletion') + permission_required = 'part.delete_bomitem' + class PartSalePriceBreakCreate(AjaxCreateView): """ View for creating a sale price break for a part """ @@ -2147,6 +2224,8 @@ class PartSalePriceBreakCreate(AjaxCreateView): model = PartSellPriceBreak form_class = part_forms.EditPartSalePriceBreakForm ajax_form_title = _('Add Price Break') + + permission_required = 'part.create_partsellpricebreak' def get_data(self): return { @@ -2197,6 +2276,8 @@ class PartSalePriceBreakEdit(AjaxUpdateView): form_class = part_forms.EditPartSalePriceBreakForm ajax_form_title = _('Edit Price Break') + permission_required = 'part.change_partsellpricebreak' + def get_form(self): form = super().get_form() @@ -2211,3 +2292,5 @@ class PartSalePriceBreakDelete(AjaxDeleteView): model = PartSellPriceBreak ajax_form_title = _("Delete Price Break") ajax_template_name = "modal_delete_form.html" + + permission_required = 'part.delete_partsalepricebreak' diff --git a/InvenTree/templates/403.html b/InvenTree/templates/403.html new file mode 100644 index 0000000000..372bd9fe27 --- /dev/null +++ b/InvenTree/templates/403.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block page_title %} +InvenTree | {% trans "Permission Denied" %} +{% endblock %} + +{% block content %} + +
+

{% trans "Permission Denied" %}

+ +
+ {% trans "You do not have permission to view this page." %} +
+
+ +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/index.html b/InvenTree/templates/InvenTree/index.html index a9e4ae92ea..b20d61116d 100644 --- a/InvenTree/templates/InvenTree/index.html +++ b/InvenTree/templates/InvenTree/index.html @@ -1,7 +1,7 @@ {% extends "base.html" %} - +{% load i18n %} {% block page_title %} -InvenTree | Index +InvenTree | {% trans "Index" %} {% endblock %} {% block content %}