From 247ad4dcf933738b971d1bb0c58e7fa8c7221890 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 14 Nov 2023 14:49:45 +1100 Subject: [PATCH] Revert "Form improvements (#5837)" (#5913) This reverts commit 0d193d8cffae0dd48cfd79e9ca3306de9b77cb70. --- InvenTree/InvenTree/metadata.py | 12 - InvenTree/InvenTree/serializers.py | 88 ------- InvenTree/label/api.py | 5 +- InvenTree/templates/js/translated/forms.js | 280 +++------------------ InvenTree/templates/js/translated/label.js | 5 - 5 files changed, 36 insertions(+), 354 deletions(-) diff --git a/InvenTree/InvenTree/metadata.py b/InvenTree/InvenTree/metadata.py index a526cf152e..2d4fb95024 100644 --- a/InvenTree/InvenTree/metadata.py +++ b/InvenTree/InvenTree/metadata.py @@ -10,7 +10,6 @@ from rest_framework.utils import model_meta import InvenTree.permissions import users.models from InvenTree.helpers import str2bool -from InvenTree.serializers import DependentField logger = logging.getLogger('inventree') @@ -243,10 +242,6 @@ class InvenTreeMetadata(SimpleMetadata): We take the regular DRF metadata and add our own unique flavor """ - # Try to add the child property to the dependent field to be used by the super call - if self.label_lookup[field] == 'dependent field': - field.get_child(raise_exception=True) - # Run super method first field_info = super().get_field_info(field) @@ -280,11 +275,4 @@ class InvenTreeMetadata(SimpleMetadata): else: field_info['api_url'] = model.get_api_url() - # Add more metadata about dependent fields - if field_info['type'] == 'dependent field': - field_info['depends_on'] = field.depends_on - return field_info - - -InvenTreeMetadata.label_lookup[DependentField] = "dependent field" diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 96956ec427..1e2ab59515 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -2,7 +2,6 @@ import os from collections import OrderedDict -from copy import deepcopy from decimal import Decimal from django.conf import settings @@ -95,93 +94,6 @@ class InvenTreeCurrencySerializer(serializers.ChoiceField): super().__init__(*args, **kwargs) -class DependentField(serializers.Field): - """A dependent field can be used to dynamically return child fields based on the value of other fields.""" - child = None - - def __init__(self, *args, depends_on, field_serializer, **kwargs): - """A dependent field can be used to dynamically return child fields based on the value of other fields. - - Example: - This example adds two fields. If the client selects integer, an integer field will be shown, but if he - selects char, an char field will be shown. For any other value, nothing will be shown. - - class TestSerializer(serializers.Serializer): - select_type = serializers.ChoiceField(choices=[ - ("integer", "Integer"), - ("char", "Char"), - ]) - my_field = DependentField(depends_on=["select_type"], field_serializer="get_my_field") - - def get_my_field(self, fields): - if fields["select_type"] == "integer": - return serializers.IntegerField() - if fields["select_type"] == "char": - return serializers.CharField() - """ - super().__init__(*args, **kwargs) - - self.depends_on = depends_on - self.field_serializer = field_serializer - - def get_child(self, raise_exception=False): - """This method tries to extract the child based on the provided data in the request by the client.""" - data = deepcopy(self.context["request"].data) - - def visit_parent(node): - """Recursively extract the data for the parent field/serializer in reverse.""" - nonlocal data - - if node.parent: - visit_parent(node.parent) - - # only do for composite fields and stop right before the current field - if hasattr(node, "child") and node is not self and isinstance(data, dict): - data = data.get(node.field_name, None) - visit_parent(self) - - # ensure that data is a dictionary and that a parent exists - if not isinstance(data, dict) or self.parent is None: - return - - # check if the request data contains the dependent fields, otherwise skip getting the child - for f in self.depends_on: - if not data.get(f, None): - return - - # partially validate the data for options requests that set raise_exception while calling .get_child(...) - if raise_exception: - validation_data = {k: v for k, v in data.items() if k in self.depends_on} - serializer = self.parent.__class__(context=self.context, data=validation_data, partial=True) - serializer.is_valid(raise_exception=raise_exception) - - # try to get the field serializer - field_serializer = getattr(self.parent, self.field_serializer) - child = field_serializer(data) - - if not child: - return - - self.child = child - self.child.bind(field_name='', parent=self) - - def to_internal_value(self, data): - """This method tries to convert the data to an internal representation based on the defined to_internal_value method on the child.""" - self.get_child() - if self.child: - return self.child.to_internal_value(data) - - return None - - def to_representation(self, value): - """This method tries to convert the data to representation based on the defined to_representation method on the child.""" - self.get_child() - if self.child: - return self.child.to_representation(value) - - return None - - class InvenTreeModelSerializer(serializers.ModelSerializer): """Inherits the standard Django ModelSerializer class, but also ensures that the underlying model class data are checked on validation.""" diff --git a/InvenTree/label/api.py b/InvenTree/label/api.py index a76b8019e7..0371b2220f 100644 --- a/InvenTree/label/api.py +++ b/InvenTree/label/api.py @@ -159,8 +159,7 @@ class LabelPrintMixin(LabelFilterMixin): # Check the request to determine if the user has selected a label printing plugin plugin = self.get_plugin(self.request) - kwargs.setdefault('context', self.get_serializer_context()) - serializer = plugin.get_printing_options_serializer(self.request, *args, **kwargs) + serializer = plugin.get_printing_options_serializer(self.request) # if no serializer is defined, return an empty serializer if not serializer: @@ -227,7 +226,7 @@ class LabelPrintMixin(LabelFilterMixin): raise ValidationError('Label has invalid dimensions') # if the plugin returns a serializer, validate the data - if serializer := plugin.get_printing_options_serializer(request, data=request.data, context=self.get_serializer_context()): + if serializer := plugin.get_printing_options_serializer(request, data=request.data): serializer.is_valid(raise_exception=True) # At this point, we offload the label(s) to the selected plugin. diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js index c357bb6a94..69ab8cbd89 100644 --- a/InvenTree/templates/js/translated/forms.js +++ b/InvenTree/templates/js/translated/forms.js @@ -298,7 +298,7 @@ function constructDeleteForm(fields, options) { * - closeText: Text for the "close" button * - fields: list of fields to display, with the following options * - filters: API query filters - * - onEdit: callback or array of callbacks which get fired when field is edited + * - onEdit: callback when field is edited * - secondary: Define a secondary modal form for this field * - label: Specify custom label * - help_text: Specify custom help_text @@ -493,30 +493,6 @@ function constructFormBody(fields, options) { html += options.header_html; } - // process every field by recursively walking down nested fields - const processField = (name, field, optionsField) => { - if (field.type === "nested object") { - for (const [k, v] of Object.entries(field.children)) { - processField(`${name}__${k}`, v, optionsField.children[k]); - } - } - - if (field.type === "dependent field") { - if(field.child) { - // copy child attribute from parameters to options - optionsField.child = field.child; - - processField(name, field.child, optionsField.child); - } else { - delete optionsField.child; - } - } - } - - for (const [k,v] of Object.entries(fields)) { - processField(k, v, options.fields[k]); - } - // Client must provide set of fields to be displayed, // otherwise *all* fields will be displayed var displayed_fields = options.fields || fields; @@ -623,6 +599,14 @@ function constructFormBody(fields, options) { var field = fields[field_name]; + switch (field.type) { + // Skip field types which are simply not supported + case 'nested object': + continue; + default: + break; + } + html += constructField(field_name, field, options); } @@ -826,7 +810,7 @@ function insertSecondaryButtons(options) { /* * Extract all specified form values as a single object */ -function extractFormData(fields, options, includeLocal = true) { +function extractFormData(fields, options) { var data = {}; @@ -839,7 +823,6 @@ function extractFormData(fields, options, includeLocal = true) { if (!field) continue; if (field.type == 'candy') continue; - if (!includeLocal && field.localOnly) continue; data[name] = getFormFieldValue(name, field, options); } @@ -1048,17 +1031,6 @@ function updateFieldValue(name, value, field, options) { } // TODO - Specify an actual value! break; - case 'nested object': - for (const [k, v] of Object.entries(value)) { - if (!(k in field.children)) continue; - updateFieldValue(`${name}__${k}`, v, field.children[k], options); - } - break; - case 'dependent field': - if (field.child) { - updateFieldValue(name, value, field.child, options); - } - break; case 'file upload': case 'image upload': break; @@ -1193,17 +1165,6 @@ function getFormFieldValue(name, field={}, options={}) { case 'email': value = sanitizeInputString(el.val()); break; - case 'nested object': - value = {}; - for (const [name, subField] of Object.entries(field.children)) { - value[name] = getFormFieldValue(subField.name, subField, options); - } - break; - case 'dependent field': - if(!field.child) return undefined; - - value = getFormFieldValue(name, field.child, options); - break; default: value = el.val(); break; @@ -1488,28 +1449,19 @@ function handleFormErrors(errors, fields={}, options={}) { var field = fields[field_name] || {}; var field_errors = errors[field_name]; - // for nested objects with children and dependent fields with a child defined, extract nested errors - if (((field.type == 'nested object') && ('children' in field)) || ((field.type == 'dependent field') && ('child' in field))) { + if ((field.type == 'nested object') && ('children' in field)) { // Handle multi-level nested errors - const handleNestedError = (parent_name, sub_field_errors) => { - for (const sub_field in sub_field_errors) { - const sub_sub_field_name = `${parent_name}__${sub_field}`; - const sub_sub_field_errors = sub_field_errors[sub_field]; - if (!first_error_field && sub_sub_field_errors && isFieldVisible(sub_sub_field_name, options)) { - first_error_field = sub_sub_field_name; - } + for (var sub_field in field_errors) { + var sub_field_name = `${field_name}__${sub_field}`; + var sub_field_errors = field_errors[sub_field]; - // if the error is an object, its a nested object, recursively handle the errors - if (typeof sub_sub_field_errors === "object" && !Array.isArray(sub_sub_field_errors)) { - handleNestedError(sub_sub_field_name, sub_sub_field_errors) - } else { - addFieldErrorMessage(sub_sub_field_name, sub_sub_field_errors, options); - } + if (!first_error_field && sub_field_errors && isFieldVisible(sub_field_name, options)) { + first_error_field = sub_field_name; } - } - handleNestedError(field_name, field_errors); + addFieldErrorMessage(sub_field_name, sub_field_errors, options); + } } else if ((field.type == 'field') && ('child' in field)) { // This is a "nested" array field handleNestedArrayErrors(errors, field_name, options); @@ -1604,7 +1556,7 @@ function addFieldCallbacks(fields, options) { var field = fields[name]; - if (!field || field.type === "candy") continue; + if (!field || !field.onEdit) continue; addFieldCallback(name, field, options); } @@ -1612,34 +1564,15 @@ function addFieldCallbacks(fields, options) { function addFieldCallback(name, field, options) { - const el = getFormFieldElement(name, options); - if (field.onEdit) { - el.change(function() { + var el = getFormFieldElement(name, options); - var value = getFormFieldValue(name, field, options); - let onEditHandlers = field.onEdit; + el.change(function() { - if (!Array.isArray(onEditHandlers)) { - onEditHandlers = [onEditHandlers]; - } + var value = getFormFieldValue(name, field, options); - for (const onEdit of onEditHandlers) { - onEdit(value, name, field, options); - } - }); - } - - // attach field callback for nested fields - if(field.type === "nested object") { - for (const [c_name, c_field] of Object.entries(field.children)) { - addFieldCallback(`${name}__${c_name}`, c_field, options); - } - } - - if(field.type === "dependent field" && field.child) { - addFieldCallback(name, field.child, options); - } + field.onEdit(value, name, field, options); + }); } @@ -1794,32 +1727,16 @@ function initializeRelatedFields(fields, options={}) { if (!field || field.hidden) continue; - initializeRelatedFieldsRecursively(field, fields, options); - } -} - -function initializeRelatedFieldsRecursively(field, fields, options) { - switch (field.type) { - case 'related field': - initializeRelatedField(field, fields, options); - break; - case 'choice': - initializeChoiceField(field, fields, options); - break; - case 'nested object': - for (const [c_name, c_field] of Object.entries(field.children)) { - if(!c_field.name) c_field.name = `${field.name}__${c_name}`; - initializeRelatedFieldsRecursively(c_field, field.children, options); + switch (field.type) { + case 'related field': + initializeRelatedField(field, fields, options); + break; + case 'choice': + initializeChoiceField(field, fields, options); + break; + default: + break; } - break; - case 'dependent field': - if (field.child) { - if(!field.child.name) field.child.name = field.name; - initializeRelatedFieldsRecursively(field.child, fields, options); - } - break; - default: - break; } } @@ -2429,7 +2346,7 @@ function constructField(name, parameters, options={}) { html += `
`; // Add a label - if (!options.hideLabels && parameters.type !== "nested object" && parameters.type !== "dependent field") { + if (!options.hideLabels) { html += constructLabel(name, parameters); } @@ -2584,12 +2501,6 @@ function constructInput(name, parameters, options={}) { case 'raw': func = constructRawInput; break; - case 'nested object': - func = constructNestedObject; - break; - case 'dependent field': - func = constructDependentField; - break; default: // Unsupported field type! break; @@ -2869,129 +2780,6 @@ function constructRawInput(name, parameters) { } -/* - * Construct a nested object input - */ -function constructNestedObject(name, parameters, options) { - let html = ` -
-
-
-
${parameters.label}
-
-
-
- `; - - parameters.field_names = []; - - for (const [key, field] of Object.entries(parameters.children)) { - const subFieldName = `${name}__${key}`; - field.name = subFieldName; - parameters.field_names.push(subFieldName); - - html += constructField(subFieldName, field, options); - } - - html += "
"; - - return html; -} - -function getFieldByNestedPath(name, fields) { - if (typeof name === "string") { - name = name.split("__"); - } - - if (name.length === 0) return fields; - - if (fields.type === "nested object") fields = fields.children; - - if (!(name[0] in fields)) return null; - let field = fields[name[0]]; - - if (field.type === "dependent field" && field.child) { - field = field.child; - } - - return getFieldByNestedPath(name.slice(1), field); -} - -/* - * Construct a dependent field input - */ -function constructDependentField(name, parameters, options) { - // add onEdit handler to all fields this dependent field depends on - for (let d_field_name of parameters.depends_on) { - const d_field = getFieldByNestedPath([...name.split("__").slice(0, -1), d_field_name], options.fields); - if (!d_field) continue; - - const onEdit = (value, name, field, options) => { - if(value === undefined) return; - - // extract the current form data to include in OPTIONS request - const data = extractFormData(options.fields, options, false) - - $.ajax({ - url: options.url, - type: "OPTIONS", - data: JSON.stringify(data), - contentType: "application/json", - dataType: "json", - accepts: { json: "application/json" }, - success: (res) => { - const fields = res.actions[options.method]; - - // merge already entered values in the newly constructed form - options.data = extractFormData(options.fields, options); - - // remove old submit handlers - $(options.modal).off('click', '#modal-form-submit'); - - if (options.method === "POST") { - constructCreateForm(fields, options); - } - - if (options.method === "PUT" || options.method === "PATCH") { - constructChangeForm(fields, options); - } - - if (options.method === "DELETE") { - constructDeleteForm(fields, options); - } - }, - error: (xhr) => showApiError(xhr, options.url) - }); - } - - // attach on edit handler - const originalOnEdit = d_field.onEdit; - d_field.onEdit = [onEdit]; - - if(typeof originalOnEdit === "function") { - d_field.onEdit.push(originalOnEdit); - } else if (Array.isArray(originalOnEdit)) { - // push old onEdit handlers, but omit the old - d_field.onEdit.push(...originalOnEdit.filter(h => h !== d_field._currentDependentFieldOnEdit)); - } - - // track current onEdit handler function - d_field._currentDependentFieldOnEdit = onEdit; - } - - // child is not specified already, return a dummy div with id so no errors can happen - if (!parameters.child) { - return ``; - } - - // copy label to child if not already provided - if(!parameters.child.label) { - parameters.child.label = parameters.label; - } - - // construct the provided child field - return constructField(name, parameters.child, options); -} /* * Construct a 'help text' div based on the field parameters diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js index a9a75f0f56..57ee46e919 100644 --- a/InvenTree/templates/js/translated/label.js +++ b/InvenTree/templates/js/translated/label.js @@ -137,11 +137,6 @@ function printLabels(options) { // update form updateForm(formOptions); - - // workaround to fix a bug where one cannot scroll after changing the plugin - // without opening and closing the select box again manually - $("#id__plugin").select2("open"); - $("#id__plugin").select2("close"); } const printingFormOptions = {