diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py
index 8f31eb0b77..0625a717a5 100644
--- a/InvenTree/part/api.py
+++ b/InvenTree/part/api.py
@@ -474,6 +474,56 @@ class PartCopyBOM(generics.CreateAPIView):
return ctx
+class PartValidateBOM(generics.RetrieveUpdateAPIView):
+ """
+ API endpoint for 'validating' the BOM for a given Part
+ """
+
+ class BOMValidateSerializer(serializers.ModelSerializer):
+
+ class Meta:
+ model = Part
+ fields = [
+ 'checksum',
+ 'valid',
+ ]
+
+ checksum = serializers.CharField(
+ read_only=True,
+ source='bom_checksum',
+ )
+
+ valid = serializers.BooleanField(
+ write_only=True,
+ default=False,
+ label=_('Valid'),
+ help_text=_('Validate entire Bill of Materials'),
+ )
+
+ def validate_valid(self, valid):
+ if not valid:
+ raise ValidationError(_('This option must be selected'))
+
+ queryset = Part.objects.all()
+
+ serializer_class = BOMValidateSerializer
+
+ def update(self, request, *args, **kwargs):
+
+ part = self.get_object()
+
+ partial = kwargs.pop('partial', False)
+
+ serializer = self.get_serializer(part, data=request.data, partial=partial)
+ serializer.is_valid(raise_exception=True)
+
+ part.validate_bom(request.user)
+
+ return Response({
+ 'checksum': part.bom_checksum,
+ })
+
+
class PartDetail(generics.RetrieveUpdateDestroyAPIView):
""" API endpoint for detail view of a single Part object """
@@ -1605,8 +1655,11 @@ part_api_urls = [
# Endpoint for extra serial number information
url(r'^serial-numbers/', PartSerialNumberDetail.as_view(), name='api-part-serial-number-detail'),
- # Endpoint for duplicating a BOM
- url(r'^copy-bom/', PartCopyBOM.as_view(), name='api-part-copy-bom'),
+ # Endpoint for duplicating a BOM for the specific Part
+ url(r'^bom-copy/', PartCopyBOM.as_view(), name='api-part-bom-copy'),
+
+ # Endpoint for validating a BOM for the specific Part
+ url(r'^bom-validate/', PartValidateBOM.as_view(), name='api-part-bom-validate'),
# Part detail endpoint
url(r'^.*$', PartDetail.as_view(), name='api-part-detail'),
diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py
index 5afd037c63..c4c7d29228 100644
--- a/InvenTree/part/forms.py
+++ b/InvenTree/part/forms.py
@@ -55,21 +55,6 @@ class PartImageDownloadForm(HelperForm):
]
-class BomValidateForm(HelperForm):
- """ Simple confirmation form for BOM validation.
- User is presented with a single checkbox input,
- to confirm that the BOM for this part is valid
- """
-
- validate = forms.BooleanField(required=False, initial=False, label=_('validate'), help_text=_('Confirm that the BOM is correct'))
-
- class Meta:
- model = Part
- fields = [
- 'validate'
- ]
-
-
class BomMatchItemForm(MatchItemForm):
""" Override MatchItemForm fields """
diff --git a/InvenTree/part/templates/part/bom_validate.html b/InvenTree/part/templates/part/bom_validate.html
deleted file mode 100644
index 3554f9c56a..0000000000
--- a/InvenTree/part/templates/part/bom_validate.html
+++ /dev/null
@@ -1,12 +0,0 @@
-{% extends "modal_form.html" %}
-
-{% load i18n %}
-
-{% block pre_form_content %}
-{% blocktrans with part.full_name as part %}Confirm that the Bill of Materials (BOM) is valid for:
{{ part }}{% endblocktrans %}
-
-
- {% trans 'This will validate each line in the BOM.' %}
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html
index c768c61257..63bd747d0c 100644
--- a/InvenTree/part/templates/part/detail.html
+++ b/InvenTree/part/templates/part/detail.html
@@ -609,12 +609,10 @@
});
$("#validate-bom").click(function() {
- launchModalForm(
- "{% url 'bom-validate' part.id %}",
- {
- reload: true,
- }
- );
+
+ validateBom({{ part.id }}, {
+ reload: true
+ });
});
$("#download-bom").click(function () {
diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py
index a10da3f587..14f3e28b24 100644
--- a/InvenTree/part/urls.py
+++ b/InvenTree/part/urls.py
@@ -35,7 +35,6 @@ part_detail_urls = [
url(r'^delete/?', views.PartDelete.as_view(), name='part-delete'),
url(r'^bom-export/?', views.BomExport.as_view(), name='bom-export'),
url(r'^bom-download/?', views.BomDownload.as_view(), name='bom-download'),
- url(r'^validate-bom/', views.BomValidate.as_view(), name='bom-validate'),
url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'),
diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index 2aa2d5324a..4304d2a95d 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -694,48 +694,6 @@ class PartImageSelect(AjaxUpdateView):
return self.renderJsonResponse(request, form, data)
-class BomValidate(AjaxUpdateView):
- """
- Modal form view for validating a part BOM
- """
-
- model = Part
- ajax_form_title = _("Validate BOM")
- ajax_template_name = 'part/bom_validate.html'
- context_object_name = 'part'
- form_class = part_forms.BomValidateForm
-
- def get_context(self):
- return {
- 'part': self.get_object(),
- }
-
- def get(self, request, *args, **kwargs):
-
- form = self.get_form()
-
- return self.renderJsonResponse(request, form, context=self.get_context())
-
- def validate(self, part, form, **kwargs):
-
- confirm = str2bool(form.cleaned_data.get('validate', False))
-
- if not confirm:
- form.add_error('validate', _('Confirm that the BOM is valid'))
-
- def save(self, part, form, **kwargs):
- """
- Mark the BOM as validated
- """
-
- part.validate_bom(self.request.user)
-
- def get_data(self):
- return {
- 'success': _('Validated Bill of Materials')
- }
-
-
class BomUpload(InvenTreeRoleMixin, FileManagementFormView):
""" View for uploading a BOM file, and handling BOM data importing.
diff --git a/InvenTree/templates/js/translated/api.js b/InvenTree/templates/js/translated/api.js
index d9c23f035f..eadf2e2afc 100644
--- a/InvenTree/templates/js/translated/api.js
+++ b/InvenTree/templates/js/translated/api.js
@@ -207,6 +207,11 @@ function showApiError(xhr, url) {
title = '{% trans "Error 404: Resource Not Found" %}';
message = '{% trans "The requested resource could not be located on the server" %}';
break;
+ // Method not allowed
+ case 405:
+ title = '{% trans "Error 405: Method Not Allowed" %}';
+ message = '{% trans "HTTP method not allowed at URL" %}';
+ break;
// Timeout
case 408:
title = '{% trans "Error 408: Timeout" %}';
diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js
index 1cfd50d66c..fafbfb37c8 100644
--- a/InvenTree/templates/js/translated/part.js
+++ b/InvenTree/templates/js/translated/part.js
@@ -40,6 +40,7 @@
loadStockPricingChart,
partStockLabel,
toggleStar,
+ validateBom,
*/
/* Part API functions
@@ -429,9 +430,34 @@ function toggleStar(options) {
}
+/* Validate a BOM */
+function validateBom(part_id, options={}) {
+
+ var html = `
+
+ {% trans "Validating the BOM will mark each line item as valid" %}
+
+ `;
+
+ constructForm(`/api/part/${part_id}/bom-validate/`, {
+ method: 'PUT',
+ fields: {
+ valid: {},
+ },
+ preFormContent: html,
+ title: '{% trans "Validate Bill of Materials" %}',
+ reload: options.reload,
+ onSuccess: function(response) {
+ showMessage('{% trans "Validated Bill of Materials" %}');
+ }
+ });
+}
+
+
/* Duplicate a BOM */
function duplicateBom(part_id, options={}) {
- constructForm(`/api/part/${part_id}/copy-bom/`, {
+
+ constructForm(`/api/part/${part_id}/bom-copy/`, {
method: 'POST',
fields: {
part: {