[IMP] website: add options for file upload in forms (max size + number)

Currently, we can only upload one file in a form File Upload field and
this file does not have a size limit. (Note that the server can have a
limit to prevent files too large from being uploaded but the file input
itself does not have one.)

This commit adds options to the form file inputs in order to set a
maximum number of files and the maximum file size (in MB) allowed to be
uploaded in these fields. The default values are 1 file and 1 MB. Note
that the option for the number of files is not displayed for the fields
where only one file is supposed to be uploaded.

If the uploaded files do not respect these limits, the form is not sent
and a message is displayed.

task-2798576

Part-of: odoo/odoo#89055
This commit is contained in:
Soukéina Bojabza
2023-05-15 12:55:57 +02:00
parent 88bcd855ae
commit f676463dbb
4 changed files with 88 additions and 2 deletions
@@ -266,7 +266,19 @@
self.$el.find('#s_website_form_result, #o_website_form_result').empty(); // !compatibility
if (!self.check_error_fields({})) {
self.update_status('error', _t("Please fill in the form correctly."));
if (this.fileInputError) {
const errorMessage = this.fileInputError.type === "number"
? _.str.sprintf(_t(
"Please fill in the form correctly. You uploaded too many files. (Maximum %s files)"
), this.fileInputError.limit)
: _.str.sprintf(_t(
"Please fill in the form correctly. The file \"%s\" is too big. (Maximum %s MB)"
), this.fileInputError.fileName, this.fileInputError.limit);
this.update_status("error", errorMessage);
delete this.fileInputError;
} else {
this.update_status("error", _t("Please fill in the form correctly."));
}
return false;
}
@@ -439,6 +451,8 @@
if (!self.is_datetime_valid(input.value, 'datetime')) {
return true;
}
} else if (input.type === "file" && !self.isFileInputValid(input)) {
return true;
}
// Note that checkValidity also takes care of the case where
@@ -531,6 +545,39 @@
})));
},
/**
* Checks if the file input is valid: if the number of files uploaded
* and their size do not exceed the limits that were set.
*
* @param {HTMLElement} inputEl an input of type file
* @returns {Boolean} true if the input is valid, false otherwise.
*/
isFileInputValid(inputEl) {
// Note: the `maxFilesNumber` and `maxFileSize` data-attributes may
// not always be present, if the Form comes from an older version
// for example.
// Checking the number of files.
const maxFilesNumber = inputEl.dataset.maxFilesNumber;
if (maxFilesNumber && inputEl.files.length > maxFilesNumber) {
// Store information to display the error message later.
this.fileInputError = {type: "number", limit: maxFilesNumber};
return false;
}
// Checking the files size.
const maxFileSize = inputEl.dataset.maxFileSize; // in megabytes.
const bytesInMegabyte = 1_000_000;
if (maxFileSize) {
for (const file of Object.values(inputEl.files)) {
if (file.size / bytesInMegabyte > maxFileSize) {
this.fileInputError = {type: "size", limit: maxFileSize, fileName: file.name};
return false;
}
}
}
return true;
},
//----------------------------------------------------------------------
// Private
//----------------------------------------------------------------------
@@ -266,6 +266,7 @@ const FieldEditor = FormEditor.extend({
const classList = this.$target[0].classList;
const textarea = this.$target[0].querySelector('textarea');
const input = this.$target[0].querySelector('input[type="text"], input[type="email"], input[type="number"], input[type="tel"], input[type="url"], textarea');
const fileInputEl = this.$target[0].querySelector("input[type=file]");
const description = this.$target[0].querySelector('.s_website_form_field_description');
field.placeholder = input && input.placeholder;
if (input) {
@@ -273,6 +274,9 @@ const FieldEditor = FormEditor.extend({
field.value = input.getAttribute('value') || input.value;
} else if (field.type === 'boolean') {
field.value = !!this.$target[0].querySelector('input[type="checkbox"][checked]');
} else if (fileInputEl) {
field.maxFilesNumber = fileInputEl.dataset.maxFilesNumber;
field.maxFileSize = fileInputEl.dataset.maxFileSize;
}
// property value is needed for date/datetime (formated date).
field.propertyValue = input && input.value;
@@ -1071,6 +1075,16 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
setVisibilityDependency(previewMode, widgetValue, params) {
this._setVisibilityDependency(widgetValue);
},
/**
* @override
*/
async selectDataAttribute(previewMode, widgetValue, params) {
await this._super(...arguments);
if (params.attributeName === "maxFilesNumber") {
const allowMultipleFiles = params.activeValue > 1;
this.$target[0].toggleAttribute("multiple", allowMultipleFiles);
}
},
//----------------------------------------------------------------------
// Private
@@ -1172,6 +1186,13 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
case 'hidden_opt':
case 'type_opt':
return !this.$target[0].classList.contains('s_website_form_model_required');
case "max_files_number_opt": {
// Do not display the option if only one file is supposed to be
// uploaded in the field.
const fieldEl = this.$target[0].closest(".s_website_form_field");
return fieldEl.classList.contains("s_website_form_custom") ||
["one2many", "many2many"].includes(fieldEl.dataset.type);
}
}
return this._super(...arguments);
},
@@ -379,8 +379,10 @@
class="form-control s_website_form_input"
t-att-name="field.name"
t-att-required="field.required || field.modelRequired || None"
t-att-multiple="multiple"
t-att="field.maxFilesNumber > 1 and {'multiple': ''} or {}"
t-att-id="field.id"
t-att-data-max-files-number="field.maxFilesNumber or '1'"
t-att-data-max-file-size="field.maxFileSize or '1'"
/>
</t>
</t>
@@ -137,6 +137,22 @@
data-apply-to=".s_website_form_date input"/>
<we-checkbox string="Required" data-name="required_opt" data-no-preview="true"
data-toggle-required="s_website_form_required"/>
<we-input data-name="max_files_number_opt"
string="Max # of files"
title="The maximum number of files that can be uploaded."
data-attribute-name="maxFilesNumber"
data-select-data-attribute="1"
data-apply-to="input[type='file']"
data-step="1"/>
<we-input string="Max file size"
title="The maximum size (in MB) an uploaded file can have."
data-attribute-name="maxFileSize"
data-select-data-attribute="1MB"
data-apply-to="input[type='file']"
data-unit="MB"/>
<we-select string="Visibility" data-no-preview="true">
<we-button data-set-visibility="visible" data-select-class="">Always Visible</we-button>
<we-button data-set-visibility="hidden" data-select-class="s_website_form_field_hidden">Hidden</we-button>