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-<doctype>-<no>-<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.
This commit is contained in:
Maharshi Patel 2023-10-10 23:49:16 +05:30
parent 009d9e010b
commit 53cdd6d1bf
5 changed files with 60 additions and 2 deletions

View file

@ -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")

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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));