From 53cdd6d1bf02fde66273bcf47ac7ba706362baec Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 10 Oct 2023 23:49:16 +0530 Subject: [PATCH] fix: relink attachments before saving doc Certain people add attachment, before filling mandatory fields which will raise Missing Fields error. Or any other kind of errors raised by different validators due to which file is uploaded but doc is not saved. This will lead to orphaned/mislinked files. ex. new-purchase-receipt-1 This fix changes name of new docs to new---<10digithash> after saving the document we can use this new name to find any mislinked files created in past hour and relink them to the new doc on save. --- frappe/core/doctype/file/utils.py | 44 ++++++++++++++++++++ frappe/desk/form/save.py | 4 ++ frappe/model/document.py | 2 + frappe/public/js/frappe/model/create_new.js | 3 +- frappe/public/js/frappe/views/breadcrumbs.js | 9 +++- 5 files changed, 60 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/file/utils.py b/frappe/core/doctype/file/utils.py index b106ea67c5..035e0730b2 100644 --- a/frappe/core/doctype/file/utils.py +++ b/frappe/core/doctype/file/utils.py @@ -339,6 +339,7 @@ def attach_files_to_document(doc: "Document", event) -> None: "attached_to_name": doc.name, "attached_to_doctype": doc.doctype, "attached_to_field": df.fieldname, + "is_private": value.startswith("/private"), }, ) continue @@ -357,6 +358,49 @@ def attach_files_to_document(doc: "Document", event) -> None: doc.log_error("Error Attaching File") +def relink_files(doc, fieldname, temp_doc_name): + from frappe.utils.data import add_to_date, now_datetime + + """ + Relink files attached to incorrect document name to the new document name + by check if file with temp name exists that was created in last 60 minutes + """ + mislinked_file = frappe.db.exists( + "File", + { + "file_url": doc.get(fieldname), + "attached_to_name": temp_doc_name, + "attached_to_doctype": doc.doctype, + "attached_to_field": fieldname, + "creation": ( + "between", + [now_datetime() - add_to_date(date=now_datetime(), minutes=-60), now_datetime()], + ), + }, + ) + """If file exists, attach it to the new docname""" + if mislinked_file: + frappe.db.set_value( + "File", + mislinked_file, + field={ + "attached_to_name": doc.name, + "attached_to_doctype": doc.doctype, + "attached_to_field": fieldname, + }, + ) + return + + +def relink_mismatched_files(doc: "Document") -> None: + if not doc.get("file_relink_temp_docname", None): + return + attach_fields = doc.meta.get("fields", {"fieldtype": ["in", ["Attach", "Attach Image"]]}) + for df in attach_fields: + if doc.get(df.fieldname): + relink_files(doc, df.fieldname, doc.file_relink_temp_docname) + + def decode_file_content(content: bytes) -> bytes: if isinstance(content, str): content = content.encode("utf-8") diff --git a/frappe/desk/form/save.py b/frappe/desk/form/save.py index 180717da40..97dad8503d 100644 --- a/frappe/desk/form/save.py +++ b/frappe/desk/form/save.py @@ -17,6 +17,10 @@ def savedocs(doc, action): """save / submit / update doclist""" doc = frappe.get_doc(json.loads(doc)) capture_doc(doc, action) + if doc.doctype not in ["DocType", "File"] and doc.name.startswith( + "new-" + doc.doctype.lower().replace(" ", "-") + ): + doc.file_relink_temp_docname = doc.name set_local_name(doc) # action diff --git a/frappe/model/document.py b/frappe/model/document.py index 7f4758d45e..2721ebf1e7 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -10,6 +10,7 @@ from werkzeug.exceptions import NotFound import frappe from frappe import _, is_whitelisted, msgprint +from frappe.core.doctype.file.utils import relink_mismatched_files from frappe.core.doctype.server_script.server_script_utils import run_server_script_for_doc_event from frappe.desk.form.document_follow import follow_document from frappe.integrations.doctype.webhook import run_webhooks @@ -304,6 +305,7 @@ class Document(BaseDocument): if self.get("amended_from"): self.copy_attachments_from_amended_from() + relink_mismatched_files(self) self.run_post_save_methods() self.flags.in_insert = False diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index 4d1b293c78..eab684c25b 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -83,7 +83,8 @@ $.extend(frappe.model, { var cnt = frappe.model.new_name_count; if (!cnt[doctype]) cnt[doctype] = 0; cnt[doctype]++; - return frappe.router.slug(`new-${doctype}-${cnt[doctype]}`); + // random hash is added to idenity mislinked files when doc is not saved and file is uploaded. + return frappe.router.slug(`new-${doctype}-${cnt[doctype]}${frappe.utils.get_random(10)}`); }, set_default_values: function (doc, parent_doc) { diff --git a/frappe/public/js/frappe/views/breadcrumbs.js b/frappe/public/js/frappe/views/breadcrumbs.js index 93399489c1..dce726be41 100644 --- a/frappe/public/js/frappe/views/breadcrumbs.js +++ b/frappe/public/js/frappe/views/breadcrumbs.js @@ -186,7 +186,14 @@ frappe.breadcrumbs = { set_form_breadcrumb(breadcrumbs, view) { const doctype = breadcrumbs.doctype; - const docname = frappe.get_route().slice(2).join("/"); + let docname = frappe.get_route().slice(2).join("/"); + if (docname.startsWith("new-" + doctype.toLowerCase().replace(/ /g, "-"))) { + // using docname instead of doctype to include No like Doctype Name + 1, 2, 3 + docname = docname + .slice(0, -10) + .replace(/-/g, " ") + .replace(/\b\w/g, (l) => l.toUpperCase()); + } let form_route = `/app/${frappe.router.slug(doctype)}/${docname}`; this.append_breadcrumb_element(form_route, __(docname));