- {% endblock %}
diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py
index 761356a5de..1bb92cbbf3 100644
--- a/InvenTree/part/test_views.py
+++ b/InvenTree/part/test_views.py
@@ -138,23 +138,3 @@ class PartQRTest(PartViewTestCase):
response = self.client.get(reverse('part-qr', args=(9999,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
-
-
-class CategoryTest(PartViewTestCase):
- """Tests for PartCategory related views."""
-
- def test_set_category(self):
- """Test that the "SetCategory" view works."""
- url = reverse('part-set-category')
-
- response = self.client.get(url, {'parts[]': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(response.status_code, 200)
-
- data = {
- 'part_id_10': True,
- 'part_id_1': True,
- 'part_category': 5
- }
-
- response = self.client.post(url, data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
- self.assertEqual(response.status_code, 200)
diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py
index ac34225c00..cc0bdbc4eb 100644
--- a/InvenTree/part/urls.py
+++ b/InvenTree/part/urls.py
@@ -56,9 +56,6 @@ part_urls = [
# Part category
re_path(r'^category/', include(category_urls)),
- # Change category for multiple parts
- re_path(r'^set-category/?', views.PartSetCategory.as_view(), name='part-set-category'),
-
# Individual part using IPN as slug
re_path(r'^(?P
[-\w]+)/', views.PartDetailFromIPN.as_view(), name='part-detail-from-ipn'),
diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py
index a57f6905bd..9b6e699541 100644
--- a/InvenTree/part/views.py
+++ b/InvenTree/part/views.py
@@ -8,7 +8,6 @@ from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile
-from django.db import transaction
from django.shortcuts import HttpResponseRedirect, get_object_or_404
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
@@ -67,80 +66,6 @@ class PartIndex(InvenTreeRoleMixin, ListView):
return context
-class PartSetCategory(AjaxUpdateView):
- """View for settings the part category for multiple parts at once."""
-
- ajax_template_name = 'part/set_category.html'
- ajax_form_title = _('Set Part Category')
- form_class = part_forms.SetPartCategoryForm
-
- role_required = 'part.change'
-
- category = None
- parts = []
-
- def get(self, request, *args, **kwargs):
- """Respond to a GET request to this view."""
- self.request = request
-
- if 'parts[]' in request.GET:
- self.parts = Part.objects.filter(id__in=request.GET.getlist('parts[]'))
- else:
- self.parts = []
-
- return self.renderJsonResponse(request, form=self.get_form(), context=self.get_context_data())
-
- def post(self, request, *args, **kwargs):
- """Respond to a POST request to this view."""
- self.parts = []
-
- for item in request.POST:
- if item.startswith('part_id_'):
- pk = item.replace('part_id_', '')
-
- try:
- part = Part.objects.get(pk=pk)
- except (Part.DoesNotExist, ValueError):
- continue
-
- self.parts.append(part)
-
- self.category = None
-
- if 'part_category' in request.POST:
- pk = request.POST['part_category']
-
- try:
- self.category = PartCategory.objects.get(pk=pk)
- except (PartCategory.DoesNotExist, ValueError):
- self.category = None
-
- valid = self.category is not None
-
- data = {
- 'form_valid': valid,
- 'success': _('Set category for {n} parts').format(n=len(self.parts))
- }
-
- if valid:
- with transaction.atomic():
- for part in self.parts:
- part.category = self.category
- part.save()
-
- return self.renderJsonResponse(request, data=data, form=self.get_form(), context=self.get_context_data())
-
- def get_context_data(self):
- """Return context data for rendering in the form."""
- ctx = {}
-
- ctx['parts'] = self.parts
- ctx['categories'] = PartCategory.objects.all()
- ctx['category'] = self.category
-
- return ctx
-
-
class PartImport(FileManagementFormView):
"""Part: Upload file, match to fields and import parts(using multi-Step form)"""
permission_required = 'part.add'
diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js
index 33840b1caa..c62e29bdf5 100644
--- a/InvenTree/templates/js/translated/bom.js
+++ b/InvenTree/templates/js/translated/bom.js
@@ -642,7 +642,7 @@ function bomSubstitutesDialog(bom_item_id, substitutes, options={}) {
addRemoveCallback(opts.modal, `#button-row-remove-${response.pk}`);
// Re-enable the "submit" button
- $(opts.modal).find('#modal-form-submit').prop('disabled', false);
+ enableSubmitButton(opts, true);
// Reload the parent BOM table
reloadParentTable();
diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js
index b684e2f387..5aeff3e80c 100644
--- a/InvenTree/templates/js/translated/forms.js
+++ b/InvenTree/templates/js/translated/forms.js
@@ -596,7 +596,7 @@ function constructFormBody(fields, options) {
// Immediately disable the "submit" button,
// to prevent the form being submitted multiple times!
- $(options.modal).find('#modal-form-submit').prop('disabled', true);
+ enableSubmitButton(options, false);
// Run custom code before normal form submission
if (options.beforeSubmit) {
@@ -639,13 +639,13 @@ function insertConfirmButton(options) {
$(options.modal).find('#modal-footer-buttons').append(html);
// Disable the 'submit' button
- $(options.modal).find('#modal-form-submit').prop('disabled', true);
+ enableSubmitButton(options, true);
// Trigger event
$(options.modal).find('#modal-confirm').change(function() {
var enabled = this.checked;
- $(options.modal).find('#modal-form-submit').prop('disabled', !enabled);
+ enableSubmitButton(options, !enabled);
});
}
@@ -1063,7 +1063,7 @@ function handleFormSuccess(response, options) {
// Reset the status of the "submit" button
if (options.modal) {
- $(options.modal).find('#modal-form-submit').prop('disabled', false);
+ enableSubmitButton(options, true);
}
// Remove any error flags from the form
@@ -1228,7 +1228,7 @@ function handleFormErrors(errors, fields={}, options={}) {
// Reset the status of the "submit" button
if (options.modal) {
- $(options.modal).find('#modal-form-submit').prop('disabled', false);
+ enableSubmitButton(options, true);
}
// Remove any existing error messages from the form
diff --git a/InvenTree/templates/js/translated/modals.js b/InvenTree/templates/js/translated/modals.js
index 9f2a6ba5e7..ebf7a6ea61 100644
--- a/InvenTree/templates/js/translated/modals.js
+++ b/InvenTree/templates/js/translated/modals.js
@@ -11,10 +11,10 @@
clearFieldOptions,
closeModal,
enableField,
+ enableSubmitButton,
getFieldValue,
reloadFieldOptions,
showModalImage,
- removeRowFromModalForm,
showQuestionDialog,
showModalSpinner,
*/
@@ -146,6 +146,24 @@ function createNewModal(options={}) {
}
+/*
+ * Convenience function to enable (or disable) the "submit" button on a modal form
+ */
+function enableSubmitButton(options, enable=true) {
+
+ if (!options || !options.modal) {
+ console.warn('enableSubmitButton() called without modal reference');
+ return;
+ }
+
+ if (enable) {
+ $(options.modal).find('#modal-form-submit').prop('disabled', false);
+ } else {
+ $(options.modal).find('#modal-form-submit').prop('disabled', true);
+ }
+}
+
+
function makeOption(text, value, title) {
/* Format an option for a select element
*/
@@ -536,18 +554,6 @@ function modalSubmit(modal, callback) {
}
-function removeRowFromModalForm(e) {
- /* Remove a row from a table in a modal form */
- e = e || window.event;
-
- var src = e.target || e.srcElement;
-
- var row = $(src).attr('row');
-
- $('#' + row).remove();
-}
-
-
function renderErrorMessage(xhr) {
var html = '' + xhr.statusText + '
';
diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js
index 36ff76d503..707903c15a 100644
--- a/InvenTree/templates/js/translated/part.js
+++ b/InvenTree/templates/js/translated/part.js
@@ -8,7 +8,6 @@
imageHoverIcon,
inventreeGet,
inventreePut,
- launchModalForm,
linkButtonsToSelection,
loadTableFilters,
makeIconBadge,
@@ -1604,6 +1603,7 @@ function loadPartTable(table, url, options={}) {
/* Button callbacks for part table buttons */
+ // Callback function for the "order parts" button
$('#multi-part-order').click(function() {
var selections = getTableData(table);
@@ -1613,31 +1613,82 @@ function loadPartTable(table, url, options={}) {
parts.push(part);
});
- orderParts(
- parts,
- {
-
- }
- );
+ orderParts(parts, {});
});
+ // Callback function for the "set category" button
$('#multi-part-category').click(function() {
- var selections = $(table).bootstrapTable('getSelections');
-
+ var selections = getTableData(table);
var parts = [];
selections.forEach(function(item) {
parts.push(item.pk);
});
- launchModalForm('/part/set-category/', {
- data: {
- parts: parts,
+ var html = `
+
+ {% trans "Set the part category for the selected parts" %}
+
+ `;
+
+ constructFormBody({}, {
+ title: '{% trans "Set Part Category" %}',
+ preFormContent: html,
+ fields: {
+ category: {
+ label: '{% trans "Category" %}',
+ help_text: '{% trans "Select Part Category" %}',
+ required: true,
+ type: 'related field',
+ model: 'partcategory',
+ api_url: '{% url "api-part-category-list" %}',
+ }
+ },
+ onSubmit: function(fields, opts) {
+ var category = getFormFieldValue('category', fields['category'], opts);
+
+ if (category == null) {
+ handleFormErrors(
+ {
+ 'category': ['{% trans "Category is required" %}']
+ },
+ opts.fields,
+ opts
+ );
+ return;
+ }
+
+ // Set the category for each part in sequence
+ function setCategory() {
+ if (parts.length > 0) {
+ var part = parts.shift();
+
+ inventreePut(
+ `/api/part/${part}/`,
+ {
+ category: category,
+ },
+ {
+ method: 'PATCH',
+ complete: setCategory,
+ }
+ );
+ } else {
+ // We are done!
+ $(opts.modal).modal('hide');
+
+ $(table).bootstrapTable('refresh');
+ }
+ };
+
+ // Start the ball rolling
+ showModalSpinner(opts.modal);
+ setCategory();
},
- reload: true,
});
});
+ // Callback function for the "print label" button
$('#multi-part-print-label').click(function() {
var selections = getTableData(table);