2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-11 07:24:15 +00:00

Merge branch 'master' into stock-item-forms

# Conflicts:
#	InvenTree/stock/serializers.py
#	InvenTree/stock/templates/stock/item_base.html
This commit is contained in:
Oliver
2021-11-01 12:00:40 +11:00
261 changed files with 72210 additions and 5862 deletions

View File

@ -2,7 +2,6 @@
{% load inventree_extras %}
/* globals
attachToggle,
createNewModal,
inventreeFormDataUpload,
inventreeGet,
@ -49,6 +48,9 @@
*
*/
// Set global default theme for select2
$.fn.select2.defaults.set('theme', 'bootstrap-5');
/*
* Return true if the OPTIONS specify that the user
* can perform a GET method at the endpoint.
@ -531,11 +533,6 @@ function constructFormBody(fields, options) {
// Attach clear callbacks (if required)
addClearCallbacks(fields, options);
attachToggle(modal);
$(modal + ' .select2-container').addClass('select-full-width');
$(modal + ' .select2-container').css('width', '100%');
modalShowSubmitButton(modal, true);
$(modal).on('click', '#modal-form-submit', function() {
@ -575,13 +572,14 @@ function insertConfirmButton(options) {
var message = options.confirmMessage || '{% trans "Confirm" %}';
var confirm = `
<span style='float: left;'>
${message}
<input id='modal-confirm' name='confirm' type='checkbox'>
</span>`;
var html = `
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="modal-confirm">
<label class="form-check-label" for="modal-confirm">${message}</label>
</div>
`;
$(options.modal).find('#modal-footer-buttons').append(confirm);
$(options.modal).find('#modal-footer-buttons').append(html);
// Disable the 'submit' button
$(options.modal).find('#modal-form-submit').prop('disabled', true);
@ -633,6 +631,10 @@ function submitFormData(fields, options) {
var has_files = false;
var data_valid = true;
var data_errors = {};
// Extract values for each field
for (var idx = 0; idx < options.field_names.length; idx++) {
@ -645,6 +647,21 @@ function submitFormData(fields, options) {
if (field) {
switch (field.type) {
// Ensure numerical fields are "valid"
case 'integer':
case 'float':
case 'decimal':
if (!validateFormField(name, options)) {
data_valid = false;
data_errors[name] = ['{% trans "Enter a valid number" %}'];
}
break;
default:
break;
}
var value = getFormFieldValue(name, field, options);
// Handle file inputs
@ -674,6 +691,11 @@ function submitFormData(fields, options) {
}
}
if (!data_valid) {
handleFormErrors(data_errors, fields, options);
return;
}
var upload_func = inventreePut;
if (has_files) {
@ -744,7 +766,8 @@ function updateFieldValues(fields, options) {
* Update the value of a named field
*/
function updateFieldValue(name, value, field, options) {
var el = $(options.modal).find(`#id_${name}`);
var el = getFormFieldElement(name, options);
if (!el) {
console.log(`WARNING: updateFieldValue could not find field '${name}'`);
@ -772,6 +795,46 @@ function updateFieldValue(name, value, field, options) {
}
// Find the named field element in the modal DOM
function getFormFieldElement(name, options) {
var el = $(options.modal).find(`#id_${name}`);
if (!el.exists) {
console.log(`ERROR: Could not find form element for field '${name}'`);
}
return el;
}
/*
* Check that a "numerical" input field has a valid number in it.
* An invalid number is expunged at the client side by the getFormFieldValue() function,
* which means that an empty string '' is sent to the server if the number is not valud.
* This can result in confusing error messages displayed under the form field.
*
* So, we can invalid numbers and display errors *before* the form is submitted!
*/
function validateFormField(name, options) {
if (getFormFieldElement(name, options)) {
var el = document.getElementById(`id_${name}`);
if (el.validity.valueMissing) {
// Accept empty strings (server will validate)
return true;
} else {
return el.validity.valid;
}
} else {
return false;
}
}
/*
* Extract and field value before sending back to the server
*
@ -783,7 +846,7 @@ function updateFieldValue(name, value, field, options) {
function getFormFieldValue(name, field, options) {
// Find the HTML element
var el = $(options.modal).find(`#id_${name}`);
var el = getFormFieldElement(name, options);
if (!el) {
return null;
@ -877,7 +940,7 @@ function clearFormErrors(options) {
$(options.modal).find('.form-error-message').remove();
// Remove the "has error" class
$(options.modal).find('.has-error').removeClass('has-error');
$(options.modal).find('.form-field-error').removeClass('form-field-error');
// Hide the 'non field errors'
$(options.modal).find('#non-field-errors').html('');
@ -1050,8 +1113,8 @@ function handleFormErrors(errors, fields, options) {
*/
function addFieldErrorMessage(field_name, error_text, error_idx, options) {
// Add the 'has-error' class
$(options.modal).find(`#div_id_${field_name}`).addClass('has-error');
// Add the 'form-field-error' class
$(options.modal).find(`#div_id_${field_name}`).addClass('form-field-error');
var field_dom = $(options.modal).find(`#errors-${field_name}`);
@ -1098,7 +1161,9 @@ function addFieldCallbacks(fields, options) {
function addFieldCallback(name, field, options) {
$(options.modal).find(`#id_${name}`).change(function() {
var el = getFormFieldElement(name, options);
el.change(function() {
var value = getFormFieldValue(name, field, options);
@ -1244,7 +1309,7 @@ function addSecondaryModal(field, fields, options) {
var html = `
<span style='float: right;'>
<div type='button' class='btn btn-primary btn-secondary' title='${secondary.title || secondary.label}' id='btn-new-${name}'>
<div type='button' class='btn btn-primary btn-secondary btn-form-secondary' title='${secondary.title || secondary.label}' id='btn-new-${name}'>
${secondary.label || secondary.title}
</div>
</span>`;
@ -1311,7 +1376,7 @@ function initializeRelatedField(field, fields, options) {
}
// Find the select element and attach a select2 to it
var select = $(options.modal).find(`#id_${name}`);
var select = getFormFieldElement(name, options);
// Add a button to launch a 'secondary' modal
if (field.secondary != null) {
@ -1438,6 +1503,11 @@ function initializeRelatedField(field, fields, options) {
data = item.element.instance;
}
// Run optional callback function
if (field.onSelect && data) {
field.onSelect(data, field, options);
}
if (!data.pk) {
return field.placeholder || '';
}
@ -1499,7 +1569,7 @@ function initializeRelatedField(field, fields, options) {
*/
function setRelatedFieldData(name, data, options) {
var select = $(options.modal).find(`#id_${name}`);
var select = getFormFieldElement(name, options);
var option = new Option(name, data.pk, true, true);
@ -1520,14 +1590,11 @@ function setRelatedFieldData(name, data, options) {
function initializeChoiceField(field, fields, options) {
var name = field.name;
var select = $(options.modal).find(`#id_${name}`);
var select = getFormFieldElement(field.name, options);
select.select2({
dropdownAutoWidth: false,
dropdownParent: $(options.modal),
width: '100%',
});
}
@ -1673,7 +1740,7 @@ function constructField(name, parameters, options) {
<div class='panel-heading form-panel-heading' id='form-panel-heading-${group}'>`;
if (group_options.collapsible) {
html += `
<div data-toggle='collapse' data-target='#form-panel-content-${group}'>
<div data-bs-toggle='collapse' data-bs-target='#form-panel-content-${group}'>
<a href='#'><span id='group-icon-${group}' class='fas fa-angle-up'></span>
`;
} else {
@ -1699,7 +1766,7 @@ function constructField(name, parameters, options) {
var form_classes = 'form-group';
if (parameters.errors) {
form_classes += ' has-error';
form_classes += ' form-field-error';
}
// Optional content to render before the field
@ -1741,7 +1808,7 @@ function constructField(name, parameters, options) {
html += `<div class='input-group'>`;
if (parameters.prefix) {
html += `<span class='input-group-addon'>${parameters.prefix}</span>`;
html += `<span class='input-group-text'>${parameters.prefix}</span>`;
}
}
@ -1751,7 +1818,7 @@ function constructField(name, parameters, options) {
if (!parameters.required) {
html += `
<span class='input-group-addon form-clear' id='clear_${name}' title='{% trans "Clear input" %}'>
<span class='input-group-text form-clear' id='clear_${name}' title='{% trans "Clear input" %}'>
<span class='icon-red fas fa-backspace'></span>
</span>`;
}
@ -1760,7 +1827,11 @@ function constructField(name, parameters, options) {
}
if (parameters.help_text && !options.hideLabels) {
html += constructHelpText(name, parameters, options);
// Boolean values are handled differently!
if (parameters.type != 'boolean') {
html += constructHelpText(name, parameters, options);
}
}
// Div for error messages
@ -1933,12 +2004,29 @@ function constructInputOptions(name, classes, type, parameters) {
opts.push(`placeholder='${parameters.placeholder}'`);
}
if (parameters.type == 'boolean') {
opts.push(`style='display: inline-block; width: 20px; margin-right: 20px;'`);
switch (parameters.type) {
case 'boolean':
break;
case 'integer':
case 'float':
case 'decimal':
opts.push(`step='any'`);
break;
default:
break;
}
if (parameters.multiline) {
return `<textarea ${opts.join(' ')}></textarea>`;
} else if (parameters.type == 'boolean') {
return `
<div class='form-check form-switch'>
<input ${opts.join(' ')}>
<label class='form-check-label' for=''>
<em><small>${parameters.help_text}</small></em>
</label>
</div>
`;
} else {
return `<input ${opts.join(' ')}>`;
}
@ -1962,7 +2050,7 @@ function constructCheckboxInput(name, parameters) {
return constructInputOptions(
name,
'checkboxinput',
'form-check-input',
'checkbox',
parameters
);
@ -2047,7 +2135,7 @@ function constructChoiceInput(name, parameters) {
*/
function constructRelatedFieldInput(name) {
var html = `<select id='id_${name}' class='select form-control' name='${name}' style='width: 100%;'></select>`;
var html = `<select id='id_${name}' class='select form-control' name='${name}'></select>`;
// Don't load any options - they will be filled via an AJAX request
@ -2121,13 +2209,7 @@ function constructRawInput(name, parameters) {
*/
function constructHelpText(name, parameters) {
var style = '';
if (parameters.type == 'boolean') {
style = `style='display: inline-block; margin-left: 25px' `;
}
var html = `<div id='hint_id_${name}' ${style}class='help-block'><i>${parameters.help_text}</i></div>`;
var html = `<div id='hint_id_${name}' class='help-block'><i>${parameters.help_text}</i></div>`;
return html;
}