From f676463dbb411bccaa8e64867a16ff5b42d1d684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Souk=C3=A9ina=20Bojabza?= Date: Tue, 19 Apr 2022 13:49:12 +0000 Subject: [PATCH] [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 --- .../static/src/snippets/s_website_form/000.js | 49 ++++++++++++++++++- .../src/snippets/s_website_form/options.js | 21 ++++++++ .../static/src/xml/website_form_editor.xml | 4 +- .../website/views/snippets/s_website_form.xml | 16 ++++++ 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/addons/website/static/src/snippets/s_website_form/000.js b/addons/website/static/src/snippets/s_website_form/000.js index f0fd4e04102..ea5dedb5e6c 100644 --- a/addons/website/static/src/snippets/s_website_form/000.js +++ b/addons/website/static/src/snippets/s_website_form/000.js @@ -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 //---------------------------------------------------------------------- diff --git a/addons/website/static/src/snippets/s_website_form/options.js b/addons/website/static/src/snippets/s_website_form/options.js index 46fb5b08715..d7d5e8e7e25 100644 --- a/addons/website/static/src/snippets/s_website_form/options.js +++ b/addons/website/static/src/snippets/s_website_form/options.js @@ -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); }, diff --git a/addons/website/static/src/xml/website_form_editor.xml b/addons/website/static/src/xml/website_form_editor.xml index f5a5ef57eb7..619c39d445c 100644 --- a/addons/website/static/src/xml/website_form_editor.xml +++ b/addons/website/static/src/xml/website_form_editor.xml @@ -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'" /> diff --git a/addons/website/views/snippets/s_website_form.xml b/addons/website/views/snippets/s_website_form.xml index d3e5f6a8768..a3dc44b249f 100644 --- a/addons/website/views/snippets/s_website_form.xml +++ b/addons/website/views/snippets/s_website_form.xml @@ -137,6 +137,22 @@ data-apply-to=".s_website_form_date input"/> + + + + + Always Visible Hidden