mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 05:05:42 +00:00 
			
		
		
		
	This reverts commit 0d193d8cff.
			
			
This commit is contained in:
		| @@ -10,7 +10,6 @@ from rest_framework.utils import model_meta | |||||||
| import InvenTree.permissions | import InvenTree.permissions | ||||||
| import users.models | import users.models | ||||||
| from InvenTree.helpers import str2bool | from InvenTree.helpers import str2bool | ||||||
| from InvenTree.serializers import DependentField |  | ||||||
|  |  | ||||||
| logger = logging.getLogger('inventree') | logger = logging.getLogger('inventree') | ||||||
|  |  | ||||||
| @@ -243,10 +242,6 @@ class InvenTreeMetadata(SimpleMetadata): | |||||||
|  |  | ||||||
|         We take the regular DRF metadata and add our own unique flavor |         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 |         # Run super method first | ||||||
|         field_info = super().get_field_info(field) |         field_info = super().get_field_info(field) | ||||||
|  |  | ||||||
| @@ -280,11 +275,4 @@ class InvenTreeMetadata(SimpleMetadata): | |||||||
|                 else: |                 else: | ||||||
|                     field_info['api_url'] = model.get_api_url() |                     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 |         return field_info | ||||||
|  |  | ||||||
|  |  | ||||||
| InvenTreeMetadata.label_lookup[DependentField] = "dependent field" |  | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ | |||||||
|  |  | ||||||
| import os | import os | ||||||
| from collections import OrderedDict | from collections import OrderedDict | ||||||
| from copy import deepcopy |  | ||||||
| from decimal import Decimal | from decimal import Decimal | ||||||
|  |  | ||||||
| from django.conf import settings | from django.conf import settings | ||||||
| @@ -95,93 +94,6 @@ class InvenTreeCurrencySerializer(serializers.ChoiceField): | |||||||
|         super().__init__(*args, **kwargs) |         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): | class InvenTreeModelSerializer(serializers.ModelSerializer): | ||||||
|     """Inherits the standard Django ModelSerializer class, but also ensures that the underlying model class data are checked on validation.""" |     """Inherits the standard Django ModelSerializer class, but also ensures that the underlying model class data are checked on validation.""" | ||||||
|  |  | ||||||
|   | |||||||
| @@ -159,8 +159,7 @@ class LabelPrintMixin(LabelFilterMixin): | |||||||
|         # Check the request to determine if the user has selected a label printing plugin |         # Check the request to determine if the user has selected a label printing plugin | ||||||
|         plugin = self.get_plugin(self.request) |         plugin = self.get_plugin(self.request) | ||||||
|  |  | ||||||
|         kwargs.setdefault('context', self.get_serializer_context()) |         serializer = plugin.get_printing_options_serializer(self.request) | ||||||
|         serializer = plugin.get_printing_options_serializer(self.request, *args, **kwargs) |  | ||||||
|  |  | ||||||
|         # if no serializer is defined, return an empty serializer |         # if no serializer is defined, return an empty serializer | ||||||
|         if not serializer: |         if not serializer: | ||||||
| @@ -227,7 +226,7 @@ class LabelPrintMixin(LabelFilterMixin): | |||||||
|             raise ValidationError('Label has invalid dimensions') |             raise ValidationError('Label has invalid dimensions') | ||||||
|  |  | ||||||
|         # if the plugin returns a serializer, validate the data |         # 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) |             serializer.is_valid(raise_exception=True) | ||||||
|  |  | ||||||
|         # At this point, we offload the label(s) to the selected plugin. |         # At this point, we offload the label(s) to the selected plugin. | ||||||
|   | |||||||
| @@ -298,7 +298,7 @@ function constructDeleteForm(fields, options) { | |||||||
|  * - closeText: Text for the "close" button |  * - closeText: Text for the "close" button | ||||||
|  * - fields: list of fields to display, with the following options |  * - fields: list of fields to display, with the following options | ||||||
|  *      - filters: API query filters |  *      - 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 |  *      - secondary: Define a secondary modal form for this field | ||||||
|  *      - label: Specify custom label |  *      - label: Specify custom label | ||||||
|  *      - help_text: Specify custom help_text |  *      - help_text: Specify custom help_text | ||||||
| @@ -493,30 +493,6 @@ function constructFormBody(fields, options) { | |||||||
|         html += options.header_html; |         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, |     // Client must provide set of fields to be displayed, | ||||||
|     // otherwise *all* fields will be displayed |     // otherwise *all* fields will be displayed | ||||||
|     var displayed_fields = options.fields || fields; |     var displayed_fields = options.fields || fields; | ||||||
| @@ -623,6 +599,14 @@ function constructFormBody(fields, options) { | |||||||
|  |  | ||||||
|         var field = fields[field_name]; |         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); |         html += constructField(field_name, field, options); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -826,7 +810,7 @@ function insertSecondaryButtons(options) { | |||||||
| /* | /* | ||||||
|  * Extract all specified form values as a single object |  * Extract all specified form values as a single object | ||||||
|  */ |  */ | ||||||
| function extractFormData(fields, options, includeLocal = true) { | function extractFormData(fields, options) { | ||||||
|  |  | ||||||
|     var data = {}; |     var data = {}; | ||||||
|  |  | ||||||
| @@ -839,7 +823,6 @@ function extractFormData(fields, options, includeLocal = true) { | |||||||
|         if (!field) continue; |         if (!field) continue; | ||||||
|  |  | ||||||
|         if (field.type == 'candy') continue; |         if (field.type == 'candy') continue; | ||||||
|         if (!includeLocal && field.localOnly) continue; |  | ||||||
|  |  | ||||||
|         data[name] = getFormFieldValue(name, field, options); |         data[name] = getFormFieldValue(name, field, options); | ||||||
|     } |     } | ||||||
| @@ -1048,17 +1031,6 @@ function updateFieldValue(name, value, field, options) { | |||||||
|         } |         } | ||||||
|         // TODO - Specify an actual value! |         // TODO - Specify an actual value! | ||||||
|         break; |         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 'file upload': | ||||||
|     case 'image upload': |     case 'image upload': | ||||||
|         break; |         break; | ||||||
| @@ -1193,17 +1165,6 @@ function getFormFieldValue(name, field={}, options={}) { | |||||||
|     case 'email': |     case 'email': | ||||||
|         value = sanitizeInputString(el.val()); |         value = sanitizeInputString(el.val()); | ||||||
|         break; |         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: |     default: | ||||||
|         value = el.val(); |         value = el.val(); | ||||||
|         break; |         break; | ||||||
| @@ -1488,28 +1449,19 @@ function handleFormErrors(errors, fields={}, options={}) { | |||||||
|         var field = fields[field_name] || {}; |         var field = fields[field_name] || {}; | ||||||
|         var field_errors = errors[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)) { | ||||||
|         if (((field.type == 'nested object') && ('children' in field)) || ((field.type == 'dependent field') && ('child' in field))) { |  | ||||||
|             // Handle multi-level nested errors |             // 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)) { |             for (var sub_field in field_errors) { | ||||||
|                         first_error_field = sub_sub_field_name; |                 var sub_field_name = `${field_name}__${sub_field}`; | ||||||
|  |                 var sub_field_errors = field_errors[sub_field]; | ||||||
|  |  | ||||||
|  |                 if (!first_error_field && sub_field_errors && isFieldVisible(sub_field_name, options)) { | ||||||
|  |                     first_error_field = sub_field_name; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                     // if the error is an object, its a nested object, recursively handle the errors |                 addFieldErrorMessage(sub_field_name, sub_field_errors, options); | ||||||
|                     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); |  | ||||||
|             } |             } | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             handleNestedError(field_name, field_errors); |  | ||||||
|         } else if ((field.type == 'field') && ('child' in field)) { |         } else if ((field.type == 'field') && ('child' in field)) { | ||||||
|             // This is a "nested" array field |             // This is a "nested" array field | ||||||
|             handleNestedArrayErrors(errors, field_name, options); |             handleNestedArrayErrors(errors, field_name, options); | ||||||
| @@ -1604,7 +1556,7 @@ function addFieldCallbacks(fields, options) { | |||||||
|  |  | ||||||
|         var field = fields[name]; |         var field = fields[name]; | ||||||
|  |  | ||||||
|         if (!field || field.type === "candy") continue; |         if (!field || !field.onEdit) continue; | ||||||
|  |  | ||||||
|         addFieldCallback(name, field, options); |         addFieldCallback(name, field, options); | ||||||
|     } |     } | ||||||
| @@ -1612,34 +1564,15 @@ function addFieldCallbacks(fields, options) { | |||||||
|  |  | ||||||
|  |  | ||||||
| function addFieldCallback(name, field, options) { | function addFieldCallback(name, field, options) { | ||||||
|     const el = getFormFieldElement(name, options); |  | ||||||
|  |  | ||||||
|     if (field.onEdit) { |     var el = getFormFieldElement(name, options); | ||||||
|  |  | ||||||
|     el.change(function() { |     el.change(function() { | ||||||
|  |  | ||||||
|         var value = getFormFieldValue(name, field, options); |         var value = getFormFieldValue(name, field, options); | ||||||
|             let onEditHandlers = field.onEdit; |  | ||||||
|  |  | ||||||
|             if (!Array.isArray(onEditHandlers)) { |         field.onEdit(value, name, field, options); | ||||||
|                 onEditHandlers = [onEditHandlers]; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             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); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -1794,11 +1727,6 @@ function initializeRelatedFields(fields, options={}) { | |||||||
|  |  | ||||||
|         if (!field || field.hidden) continue; |         if (!field || field.hidden) continue; | ||||||
|  |  | ||||||
|         initializeRelatedFieldsRecursively(field, fields, options); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function initializeRelatedFieldsRecursively(field, fields, options) { |  | ||||||
|         switch (field.type) { |         switch (field.type) { | ||||||
|         case 'related field': |         case 'related field': | ||||||
|             initializeRelatedField(field, fields, options); |             initializeRelatedField(field, fields, options); | ||||||
| @@ -1806,21 +1734,10 @@ function initializeRelatedFieldsRecursively(field, fields, options) { | |||||||
|         case 'choice': |         case 'choice': | ||||||
|             initializeChoiceField(field, fields, options); |             initializeChoiceField(field, fields, options); | ||||||
|             break; |             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); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|     case 'dependent field': |  | ||||||
|         if (field.child) { |  | ||||||
|             if(!field.child.name) field.child.name = field.name; |  | ||||||
|             initializeRelatedFieldsRecursively(field.child, fields, options); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|         default: |         default: | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -2429,7 +2346,7 @@ function constructField(name, parameters, options={}) { | |||||||
|     html += `<div id='div_id_${field_name}' class='${form_classes}' ${hover_title} ${css}>`; |     html += `<div id='div_id_${field_name}' class='${form_classes}' ${hover_title} ${css}>`; | ||||||
|  |  | ||||||
|     // Add a label |     // Add a label | ||||||
|     if (!options.hideLabels && parameters.type !== "nested object" && parameters.type !== "dependent field") { |     if (!options.hideLabels) { | ||||||
|         html += constructLabel(name, parameters); |         html += constructLabel(name, parameters); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -2584,12 +2501,6 @@ function constructInput(name, parameters, options={}) { | |||||||
|     case 'raw': |     case 'raw': | ||||||
|         func = constructRawInput; |         func = constructRawInput; | ||||||
|         break; |         break; | ||||||
|     case 'nested object': |  | ||||||
|         func = constructNestedObject; |  | ||||||
|         break; |  | ||||||
|     case 'dependent field': |  | ||||||
|         func = constructDependentField; |  | ||||||
|         break; |  | ||||||
|     default: |     default: | ||||||
|         // Unsupported field type! |         // Unsupported field type! | ||||||
|         break; |         break; | ||||||
| @@ -2869,129 +2780,6 @@ function constructRawInput(name, parameters) { | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /* |  | ||||||
|  * Construct a nested object input |  | ||||||
|  */ |  | ||||||
| function constructNestedObject(name, parameters, options) { |  | ||||||
|     let html = ` |  | ||||||
|         <div id="div_id_${name}" class='panel form-panel' style="margin-bottom: 0; padding-bottom: 0;"> |  | ||||||
|             <div class='panel-heading form-panel-heading'> |  | ||||||
|                 <div> |  | ||||||
|                     <h6 style='display: inline;'>${parameters.label}</h6> |  | ||||||
|                 </div> |  | ||||||
|             </div> |  | ||||||
|             <div class='panel-content form-panel-content' id="id_${name}"> |  | ||||||
|     `; |  | ||||||
|  |  | ||||||
|     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 += "</div></div>"; |  | ||||||
|  |  | ||||||
|     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 `<div id="id_${name}" hidden></div>`; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 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 |  * Construct a 'help text' div based on the field parameters | ||||||
|   | |||||||
| @@ -137,11 +137,6 @@ function printLabels(options) { | |||||||
|  |  | ||||||
|         // update form |         // update form | ||||||
|         updateForm(formOptions); |         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 = { |     const printingFormOptions = { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user