diff --git a/README.md b/README.md index db93113..6690424 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ It supports RTL layout and dark mode out of the box. ⚠️ **v2 is still in BETA stage** ⚠️ -![v2 Beta15](https://img.shields.io/badge/v2_Beta15-2024/05/02-green?style=plastic) +![v2 Beta16](https://img.shields.io/badge/v2_Beta16-2024/05/30-green?style=plastic) **Apologies in advance for any problem or bug you face with this module.** **Please report any problem or bug you face so it can be fixed.** @@ -177,16 +177,17 @@ You can't modify the original fields of a doctype, so create a new field or clon ### Available Field Options | Option | Description | | :--- | :--- | -| **dialog_title** 🔴 | Upload dialog title to be displayed ️(🔶Frappe >= v14.0.0).

🔹Example: **"Upload Images"**
🔹Default: **"Upload"** | +| **dialog_title** | Upload dialog title to be displayed ️(🔶Frappe >= v14.0.0).

🔹Example: **"Upload Images"**
🔹Default: **"Upload"** | | **upload_notes** | Upload text to be displayed.

🔹Example: **"Only images and videos, with maximum size of 2MB, are allowed to be uploaded"**
🔹Default: **""** | -| **disable_file_browser** 🔴 | Disable file browser uploads.

⚠️ *(File browser is always disabled in Web Form)*

🔹Default: **false** | +| **disable_auto_save** 🔴 | Disable form auto save after upload.

🔹Default: **false** | +| **disable_file_browser** | Disable file browser uploads.

⚠️ *(File browser is always disabled in Web Form)*

🔹Default: **false** | | **allow_multiple** | Allow multiple uploads.

⚠️ *(Field value is a JSON array of files url)*

🔹Default: **false** | | **max_file_size** | Maximum file size (in bytes) that is allowed to be uploaded.

🔹Example: **2048** for **2KB**
🔹Default: **Value of maximum file size in Frappe's settings** | | **allowed_file_types** | Array of allowed file types (mimes) or extensions to upload. Prefix escaped RegExp string types with **$**.

⚠️ *(File extensions must have a leading dot ".")*
⚠️ *(RegExp string types will not be used to in HTML accept attribute)*

🔹Example: **["image/*", "video/*", ".pdf", ".doc", "$audio\/([a-z]+)"]**
🔹Default: **null** or **["image/*"]** | | **max_number_of_files** | Maximum number of files allowed to be uploaded if multiple upload is allowed.

⚠️ *(Bypassing the maximum attachments of doctype might not work)*

🔹Example: **4**
🔹Default: **Value of maximum attachments set for the doctype** | | **crop_image_aspect_ratio** | Crop aspect ratio for images (🔶Frappe >= v14.0.0).

🔹Example: **1** or **16/9** or **4/3**
🔹Default: **null** | | **as_public** | Force uploads to be saved in public folder by default.

🔹Default: **false** | -| **allowed_filename** 🔴 | Only allow files that match a specific file name to be uploaded.

🔹Example: (String)**"picture.png"** or (RegExp String)**"/picture\-([0-9]+)\.png/"**
🔹Default: **null** | +| **allowed_filename** | Only allow files that match a specific file name to be uploaded.

🔹Example: (String)**"picture.png"** or (RegExp String)**"/picture\-([0-9]+)\.png/"**
🔹Default: **null** | | **allow_reload** | Allow reloading attachments (🔶Frappe >= v13.0.0).

🔶 Affect the visibility of the reload button.🔶

🔹Default: **true** | | **allow_remove** | Allow removing and clearing attachments.

🔶 Affect the visibility of the remove and clear buttons.🔶

🔹Default: **true** | @@ -195,6 +196,7 @@ You can't modify the original fields of a doctype, so create a new field or clon ### Available JavaScript Methods | Method | Description | | :--- | :--- | +| **auto_save(enable: Boolean)** | Enable/Disable form auto save after upload. | | **toggle_reload(allow: Boolean !Optional)** | Allow/Deny reloading attachments and toggle the reload button (🔶Frappe >= v13.0.0). | | **toggle_remove(allow: Boolean !Optional)** | Allow/Deny removing and clearing attachments and toggle the clear and remove buttons. | | **set_options(options: JSON Object)** | Set or change the plugin options. | diff --git a/frappe_better_attach_control/api/attachment.py b/frappe_better_attach_control/api/attachment.py index 87c1c1b..b4ad77c 100644 --- a/frappe_better_attach_control/api/attachment.py +++ b/frappe_better_attach_control/api/attachment.py @@ -5,14 +5,6 @@ import frappe -from frappe import _, is_whitelisted -from frappe.utils import cint - -from .common import ( - is_version_gt, - parse_json_if_valid, - send_console_log -) _FILE_DOCTYPE_ = "File" @@ -33,6 +25,8 @@ @frappe.whitelist(allow_guest=True) def upload_file(): + from frappe_better_attach_control.version import is_version_gt + user = None ignore_permissions = False @@ -91,6 +85,8 @@ def upload_file(): ): filetype = mimetypes.guess_type(filename)[0] if filetype not in _ALLOWED_MIMETYPES_: + from frappe import _ + frappe.throw(_("You can only upload JPG, PNG, PDF, TXT or Microsoft documents.")) elif is_version_gt(12): @@ -99,9 +95,11 @@ def upload_file(): if method: method = frappe.get_attr(method) - is_whitelisted(method) + frappe.is_whitelisted(method) return method() else: + from frappe.utils import cint + ret = frappe.get_doc({ "doctype": _FILE_DOCTYPE_, "attached_to_doctype": doctype, @@ -124,10 +122,12 @@ def upload_file(): @frappe.whitelist(methods=["POST"], allow_guest=True) def remove_files(files): if files and isinstance(files, str): + from .common import parse_json_if_valid + files = parse_json_if_valid(files) if not files or not isinstance(files, list): - send_console_log({ + _send_console_log({ "message": "Invalid files list", "data": files }) @@ -148,7 +148,7 @@ def remove_files(files): file_names.append(file) if not file_urls and not file_names: - send_console_log({ + _send_console_log({ "message": "Invalid files path", "data": files }) @@ -176,8 +176,15 @@ def remove_files(files): return 1 - send_console_log({ + _send_console_log({ "message": "Files not found", "data": files }) - return 3 \ No newline at end of file + return 3 + + +# [Internal] +def _send_console_log(data): + from .common import send_console_log + + send_console_log(data) \ No newline at end of file diff --git a/frappe_better_attach_control/api/common.py b/frappe_better_attach_control/api/common.py index 9fdb454..a3f5660 100644 --- a/frappe_better_attach_control/api/common.py +++ b/frappe_better_attach_control/api/common.py @@ -7,26 +7,17 @@ import json import frappe -from frappe import __version__, _, _dict - - -__frappe_base_ver__ = int(__version__.split(".")[0]) - - -def is_version_gt(num: int): - return __frappe_base_ver__ > num - - -def is_version_lt(num: int): - return __frappe_base_ver__ < num +from frappe import _, _dict def error(msg, throw=True): + from frappe_better_attach_control.version import is_version_lt + title = "Better Attach Control" if is_version_lt(14): - frappe.log_error(text, title) + frappe.log_error(msg, title) else: - frappe.log_error(title, text) + frappe.log_error(title, msg) if throw: frappe.throw(msg, title=title) diff --git a/frappe_better_attach_control/api/field.py b/frappe_better_attach_control/api/field.py index 09ad52c..98f7357 100644 --- a/frappe_better_attach_control/api/field.py +++ b/frappe_better_attach_control/api/field.py @@ -6,20 +6,18 @@ import frappe -from .common import send_console_log - @frappe.whitelist(methods=["POST"], allow_guest=True) def get_options(doctype, name, webform): if not doctype or not isinstance(doctype, str): - send_console_log({ + _send_console_log({ "message": "Empty or invalid field doctype", "data": [doctype, name] }) return "" if not name or not isinstance(name, str): - send_console_log({ + _send_console_log({ "message": "Empty or invalid field name", "data": [doctype, name] }) @@ -67,8 +65,15 @@ def get_options(doctype, name, webform): if options and isinstance(options, str): return options - send_console_log({ + _send_console_log({ "message": "Empty or invalid field options", "data": [doctype, name, options] }) - return "" \ No newline at end of file + return "" + + +# [Internal] +def _send_console_log(data): + from .common import send_console_log + + send_console_log(data) \ No newline at end of file diff --git a/frappe_better_attach_control/api/file_manager.py b/frappe_better_attach_control/api/file_manager.py index a457d78..46c779e 100644 --- a/frappe_better_attach_control/api/file_manager.py +++ b/frappe_better_attach_control/api/file_manager.py @@ -8,12 +8,9 @@ import frappe from frappe import _ -from frappe.utils import flt, cint, cstr, get_url, get_files_path -from frappe.utils.file_manager import is_safe_path +from frappe.utils import cint, cstr, get_url from frappe.core.doctype.file.file import URL_PREFIXES -from .common import error, get_cached_value - _FILE_DOCTYPE_ = "File" _FILE_FIELDS_ = [ @@ -48,6 +45,8 @@ def _get_files_in_folder(folder, start, page_length): ) if folder == "Home": + from .common import get_cached_value + attachment_folder = get_cached_value( _FILE_DOCTYPE_, "Home/Attachments", @@ -87,6 +86,8 @@ def _prepare_files(files): file = files[i] file["size"] = 0 if not cint(file["is_folder"]): + from frappe.utils import flt + file["size"] = flt(file["file_size"]) if not file["size"]: try: @@ -114,21 +115,34 @@ def _get_full_path(file): file_path = f"/files/{file_path}" if file_path.startswith("/private/files/"): + from frappe.utils import get_files_path + file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1) elif file_path.startswith("/files/"): + from frappe.utils import get_files_path + file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/")) elif file_path.startswith(URL_PREFIXES): pass elif not file["file_url"]: - error(_("There is some problem with the file url: {0}").format(file_path)) + _error(_("There is some problem with the file url: {0}").format(file_path)) + + from frappe.utils.file_manager import is_safe_path if not is_safe_path(file_path): - error(_("Cannot access file path {0}").format(file_path)) + _error(_("Cannot access file path {0}").format(file_path)) if os.path.sep in file["file_name"]: - error(_("File name cannot have {0}").format(os.path.sep)) + _error(_("File name cannot have {0}").format(os.path.sep)) + + return file_path + + +# [Internal] +def _error(msg): + from .common import error - return file_path \ No newline at end of file + error(msg) \ No newline at end of file diff --git a/frappe_better_attach_control/api/website.py b/frappe_better_attach_control/api/website.py index d3f2da0..edbc482 100644 --- a/frappe_better_attach_control/api/website.py +++ b/frappe_better_attach_control/api/website.py @@ -8,20 +8,19 @@ import frappe from frappe import _ +from frappe.utils import cstr def website_context(context): if ( not context.get("doc", "") or - not hasattr(context.doc, "doctype") or - not hasattr(context.doc, "name") or + "doctype" not in context.doc or + "name" not in context.doc or context.doc.doctype != "Web Form" or not context.doc.name ): return 0 - from .common import error - try: fields = frappe.get_all( "Web Form Field", @@ -33,17 +32,18 @@ def website_context(context): "fieldtype": ["in", ["Attach", "Attach Image"]], } ) - if not fields: + if not fields or not isinstance(fields, list): return 0 options = {} - for field in fields: + for v in fields: + v["options"] = cstr(v["options"]).strip() if ( - field["options"] and - isinstance(field["options"], str) and - field["options"][0] == "{" + v["options"] and + v["options"].startswith("{") and + v["options"].endswith("{") ): - options[field["fieldname"]] = field["options"] + options[v["fieldname"]] = v["options"] if not options: return 0 @@ -59,7 +59,7 @@ def website_context(context): script = f"{script}\nfrappe.BAC.options = {options};" set_context(context, "script", script) except Exception: - error(_("Unable to get the Attach fields of the web form."), throw=False) + _error(_("Unable to get the Attach fields of the web form.")) return 0 app_name = "frappe_better_attach_control" @@ -73,13 +73,13 @@ def website_context(context): set_context(context, "script", scripts) js_loaded = True except Exception: - error(_("Unable to inject the js bundled file to context."), throw=False) + _error(_("Unable to inject the js bundled file to context.")) if not js_loaded: try: js_files = frappe.get_hooks("better_webform_include_js", default=None, app_name=app_name) if not js_files: - error(_("Unable to inject the js files to context."), throw=False) + _error(_("Unable to inject the js files to context.")) else: if not isinstance(js_files, list): js_files = [js_files] @@ -91,7 +91,7 @@ def website_context(context): data = read_file(path) scripts.append(f"// {js}\n{data}") else: - error(_("Unable to inject the js file \"{0}\" to context.").format(js), throw=False) + _error(_("Unable to inject the js file \"{0}\" to context.").format(js)) if scripts: scripts = clean_js_script("\n\n\n".join(scripts)) @@ -99,12 +99,12 @@ def website_context(context): write_file(bundled_js, scripts) set_context(context, "script", scripts) except Exception: - error(_("Unable to inject the js files to context."), throw=False) + _error(_("Unable to inject the js files to context.")) try: css_files = frappe.get_hooks("better_webform_include_css", default=None, app_name=app_name) if not css_files: - error(_("Unable to inject the css files to context."), throw=False) + _error(_("Unable to inject the css files to context.")) else: if not isinstance(css_files, list): css_files = [css_files] @@ -116,12 +116,12 @@ def website_context(context): data = read_file(path) styles.append(f"// {css}\n{data}") else: - error(_("Unable to inject the css file \"{0}\" to context.").format(css), throw=False) + _error(_("Unable to inject the css file \"{0}\" to context.").format(css)) if styles: set_context(context, "style", "\n\n\n".join(styles)) except Exception: - error(_("Unable to inject the css files to context."), throw=False) + _error(_("Unable to inject the css files to context.")) def set_context(context, key, data): @@ -177,4 +177,10 @@ def clean_js_script(data): data = re.sub("(" + "|".join(rgx) + ")", "", data) data = re.sub("^([\s\n\r]+)", "", data) data = re.sub("([\n\r]{3,})", "\n\n\n", data) - return data \ No newline at end of file + return data + + +def _error(msg): + from .common import error + + error(msg, throw=False) \ No newline at end of file diff --git a/frappe_better_attach_control/hooks.py b/frappe_better_attach_control/hooks.py index ddab61d..1e3df76 100644 --- a/frappe_better_attach_control/hooks.py +++ b/frappe_better_attach_control/hooks.py @@ -4,8 +4,7 @@ # Licence: Please refer to LICENSE file -from . import __version__ as app_version -from frappe import __version__ as frappe_version +from .version import is_version_gt app_name = "frappe_better_attach_control" @@ -18,13 +17,9 @@ app_license = "MIT" -is_frappe_above_v13 = int(frappe_version.split(".")[0]) > 13 -is_frappe_above_v12 = int(frappe_version.split(".")[0]) > 12 - - app_include_css = [ "better_attach.bundle.css" -] if is_frappe_above_v13 else [ +] if is_version_gt(13) else [ "/assets/frappe_better_attach_control/css/better_attach.css" ] @@ -36,9 +31,9 @@ app_include_js = [ "better_attach.bundle.js" -] if is_frappe_above_v13 else ([ +] if is_version_gt(13) else ([ "/assets/frappe_better_attach_control/js/better_attach_v13.bundle.js" -] if is_frappe_above_v12 else [ +] if is_version_gt(12) else [ "/assets/frappe_better_attach_control/js/better_attach_v12.bundle.js" ]) @@ -49,13 +44,13 @@ "/assets/frappe_better_attach_control/js/uploader/index.js", "/assets/frappe_better_attach_control/js/controls/attach.js", "/assets/frappe_better_attach_control/js/controls/attach_image.js" -] if is_frappe_above_v13 else ([ +] if is_version_gt(13) else ([ "/assets/frappe_better_attach_control/js/utils/index.js", "/assets/frappe_better_attach_control/js/filetypes/index.js", "/assets/frappe_better_attach_control/js/uploader/v13/index.js", "/assets/frappe_better_attach_control/js/controls/v13/attach.js", "/assets/frappe_better_attach_control/js/controls/v13/attach_image.js" -] if is_frappe_above_v12 else [ +] if is_version_gt(12) else [ "/assets/frappe_better_attach_control/js/utils/index.js", "/assets/frappe_better_attach_control/js/filetypes/index.js", "/assets/frappe_better_attach_control/js/uploader/v12/index.js", diff --git a/frappe_better_attach_control/public/js/controls/attach.js b/frappe_better_attach_control/public/js/controls/attach.js index ec9ab8e..9b3cb6d 100644 --- a/frappe_better_attach_control/public/js/controls/attach.js +++ b/frappe_better_attach_control/public/js/controls/attach.js @@ -171,6 +171,9 @@ frappe.ui.form.ControlAttach = class ControlAttach extends frappe.ui.form.Contro this.set_options(this.df.options); } // Custom Methods + auto_save(enable) { + this._disable_auto_save = enable ? false : true; + } toggle_reload(allow) { if (allow != null) this._allow_reload = !!allow; else this._allow_reload = !this._allow_reload; @@ -182,14 +185,13 @@ frappe.ui.form.ControlAttach = class ControlAttach extends frappe.ui.form.Contro this._toggle_remove_button(); } set_options(opts) { + if (Helpers.isString(opts) && opts.length) opts = Helpers.parseJson(opts, null); if (Helpers.isEmpty(opts) || !Helpers.isPlainObject(opts)) return; $.extend(true, this.df.options, opts); this._update_options(); } // Private Methods _setup_control() { - if (this._is_better) return; - this._is_better = 1; this._doctype = (this.frm && this.frm.doctype) || this.doctype || (this.doc && this.doc.doctype) @@ -212,6 +214,7 @@ frappe.ui.form.ControlAttach = class ControlAttach extends frappe.ui.form.Contro this._options = null; this._value = []; this._files = []; + this._disable_auto_save = false; this._allow_multiple = false; this._max_attachments = {}; this._allow_reload = true; @@ -241,8 +244,9 @@ frappe.ui.form.ControlAttach = class ControlAttach extends frappe.ui.form.Contro tmp.allow_reload = Helpers.toBool(Helpers.ifNull(opts.allow_reload, true)); tmp.allow_remove = Helpers.toBool(Helpers.ifNull(opts.allow_remove, true)); Helpers.each([ - ['upload_notes', 's'], ['allow_multiple', 'b'], - ['disable_file_browser', 'b'], ['dialog_title', 's'], + ['upload_notes', 's'], ['disable_auto_save', 'b'], + ['allow_multiple', 'b'], ['disable_file_browser', 'b'], + ['dialog_title', 's'], ], function(k) { tmp.options[k[0]] = this._parse_options_val(opts[k[0]], k[1]); }, this); @@ -305,6 +309,8 @@ frappe.ui.form.ControlAttach = class ControlAttach extends frappe.ui.form.Contro if (this.upload_options) this.upload_options = this.image_upload_options = null; + this._disable_auto_save = this._options && this._options.disable_auto_save; + if (Helpers.ifNull(opts.allow_reload, true) !== this._allow_reload) this.toggle_reload(!this._allow_reload); if (Helpers.ifNull(opts.allow_remove, true) !== this._allow_remove) @@ -687,6 +693,7 @@ frappe.ui.form.ControlAttach = class ControlAttach extends frappe.ui.form.Contro }); } _form_save() { + if (this._disable_auto_save) return; this.frm.doc.docstatus == 1 ? this.frm.save('Update') : this.frm.save(); } }; diff --git a/frappe_better_attach_control/public/js/controls/v12/attach.js b/frappe_better_attach_control/public/js/controls/v12/attach.js index ab4f5a0..6c524a9 100644 --- a/frappe_better_attach_control/public/js/controls/v12/attach.js +++ b/frappe_better_attach_control/public/js/controls/v12/attach.js @@ -153,20 +153,22 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ this.set_input(Helpers.toArray(this.value)); }, // Custom Methods + auto_save: function(enable) { + this._disable_auto_save = enable ? false : true; + }, toggle_remove: function(allow) { if (allow != null) this._allow_remove = !!allow; else this._allow_remove = !this._allow_remove; this._toggle_remove_button(); }, set_options: function(opts) { + if (Helpers.isString(opts) && opts.length) opts = Helpers.parseJson(opts, null); if (Helpers.isEmpty(opts) || !Helpers.isPlainObject(opts)) return; $.extend(true, this.df.options, opts); this._update_options(); }, // Private Methods _setup_control: function() { - if (this._is_better) return; - this._is_better = 1; this._doctype = (this.frm && this.frm.doctype) || this.doctype || (this.doc && this.doc.doctype) @@ -189,6 +191,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ this._options = null; this._value = []; this._files = []; + this._disable_auto_save = false; this._allow_multiple = false; this._max_attachments = {}; this._allow_remove = true; @@ -216,8 +219,8 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ var tmp = {options: {restrictions: {}, extra: {}}}; tmp.allow_remove = Helpers.toBool(Helpers.ifNull(opts.allow_remove, true)); Helpers.each([ - ['upload_notes', 's'], ['allow_multiple', 'b'], - ['disable_file_browser', 'b'], + ['upload_notes', 's'], ['disable_auto_save', 'b'], + ['allow_multiple', 'b'], ['disable_file_browser', 'b'], ], function(k) { tmp.options[k[0]] = this._parse_options_val(opts[k[0]], k[1]); }, this); @@ -277,6 +280,8 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ _reload_control: function(opts) { if (this.upload_options) this.upload_options = null; + this._disable_auto_save = this._options && this._options.disable_auto_save; + if (Helpers.ifNull(opts.allow_remove, true) !== this._allow_remove) this.toggle_remove(!this._allow_remove); @@ -654,6 +659,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ }); }, _form_save: function() { + if (this._disable_auto_save) return; this.frm.doc.docstatus == 1 ? this.frm.save('Update') : this.frm.save(); } }); \ No newline at end of file diff --git a/frappe_better_attach_control/public/js/controls/v13/attach.js b/frappe_better_attach_control/public/js/controls/v13/attach.js index 0d31b60..d758292 100644 --- a/frappe_better_attach_control/public/js/controls/v13/attach.js +++ b/frappe_better_attach_control/public/js/controls/v13/attach.js @@ -163,6 +163,9 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ this.set_input(Helpers.toArray(this.value)); }, // Custom Methods + auto_save: function(enable) { + this._disable_auto_save = enable ? false : true; + }, toggle_reload: function(allow) { if (allow != null) this._allow_reload = !!allow; else this._allow_reload = !this._allow_reload; @@ -174,14 +177,13 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ this._toggle_remove_button(); }, set_options: function(opts) { + if (Helpers.isString(opts) && opts.length) opts = Helpers.parseJson(opts, null); if (Helpers.isEmpty(opts) || !Helpers.isPlainObject(opts)) return; $.extend(true, this.df.options, opts); this._update_options(); }, // Private Methods _setup_control: function() { - if (this._is_better) return; - this._is_better = 1; this._doctype = (this.frm && this.frm.doctype) || this.doctype || (this.doc && this.doc.doctype) @@ -204,6 +206,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ this._options = null; this._value = []; this._files = []; + this._disable_auto_save = false; this._allow_multiple = false; this._max_attachments = {}; this._allow_reload = true; @@ -233,8 +236,8 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ tmp.allow_reload = Helpers.toBool(Helpers.ifNull(opts.allow_reload, true)); tmp.allow_remove = Helpers.toBool(Helpers.ifNull(opts.allow_remove, true)); Helpers.each([ - ['upload_notes', 's'], ['allow_multiple', 'b'], - ['disable_file_browser', 'b'], + ['upload_notes', 's'], ['disable_auto_save', 'b'], + ['allow_multiple', 'b'], ['disable_file_browser', 'b'], ], function(k) { tmp.options[k[0]] = this._parse_options_val(opts[k[0]], k[1]); }, this); @@ -294,6 +297,8 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ _reload_control: function(opts) { if (this.upload_options) this.upload_options = null; + this._disable_auto_save = this._options && this._options.disable_auto_save; + if (Helpers.ifNull(opts.allow_reload, true) !== this._allow_reload) this.toggle_reload(!this._allow_reload); if (Helpers.ifNull(opts.allow_remove, true) !== this._allow_remove) @@ -673,6 +678,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlAttach.extend({ }); }, _form_save: function() { + if (this._disable_auto_save) return; this.frm.doc.docstatus == 1 ? this.frm.save('Update') : this.frm.save(); } }); \ No newline at end of file diff --git a/frappe_better_attach_control/public/js/uploader/v12/index.js b/frappe_better_attach_control/public/js/uploader/v12/index.js index ab24db0..c3f4d13 100644 --- a/frappe_better_attach_control/public/js/uploader/v12/index.js +++ b/frappe_better_attach_control/public/js/uploader/v12/index.js @@ -18,7 +18,7 @@ frappe.ui.FileUploader = class FileUploader extends frappe.ui.FileUploader { super(opts); if (this.uploader) this._override_uploader(opts, extra); } - _override_uploader(opts) { + _override_uploader(opts, extra) { var up = this.uploader, me = this; up._extra_restrictions = extra; @@ -92,6 +92,7 @@ frappe.ui.FileUploader = class FileUploader extends frappe.ui.FileUploader { 'File skipped because it exceeds the allowed specified limit of ' + max_number_of_files + ' uploads', file ); + var MSG; if (up.doctype) { MSG = __('File "{0}" was skipped because only {1} uploads are allowed for DocType "{2}"', [file.name, max_number_of_files, up.doctype]); diff --git a/frappe_better_attach_control/public/js/uploader/v13/index.js b/frappe_better_attach_control/public/js/uploader/v13/index.js index 2d76a96..60204a8 100644 --- a/frappe_better_attach_control/public/js/uploader/v13/index.js +++ b/frappe_better_attach_control/public/js/uploader/v13/index.js @@ -92,6 +92,7 @@ frappe.ui.FileUploader = class FileUploader extends frappe.ui.FileUploader { 'File skipped because it exceeds the allowed specified limit of ' + max_number_of_files + ' uploads', file ); + var MSG; if (up.doctype) { MSG = __('File "{0}" was skipped because only {1} uploads are allowed for DocType "{2}"', [file.name, max_number_of_files, up.doctype]); diff --git a/frappe_better_attach_control/public/js/utils/index.js b/frappe_better_attach_control/public/js/utils/index.js index f61e01b..c6af81d 100644 --- a/frappe_better_attach_control/public/js/utils/index.js +++ b/frappe_better_attach_control/public/js/utils/index.js @@ -115,7 +115,7 @@ var Helpers = { if (!this.isIteratable(data) || !this.isIteratable(base)) return data == base; var ret = true; this.each(data, function(v, k) { - if (!this.isEqual(y, base[k])) return (ret = false); + if (!this.isEqual(v, base[k])) return (ret = false); }); return ret; }, diff --git a/frappe_better_attach_control/version.py b/frappe_better_attach_control/version.py new file mode 100644 index 0000000..941d72d --- /dev/null +++ b/frappe_better_attach_control/version.py @@ -0,0 +1,21 @@ +# Frappe Better Attach Control © 2024 +# Author: Ameen Ahmed +# Company: Level Up Marketing & Software Development Services +# Licence: Please refer to LICENSE file + + +from frappe import __version__ + + +# [Internal] +__frappe_version__ = int(__version__.split(".")[0]) + + +# [Hooks, Attachment] +def is_version_gt(num: int): + return __frappe_version__ > num + + +# [Common] +def is_version_lt(num: int): + return __frappe_version__ < num \ No newline at end of file