diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 1748c60020..316915e43a 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -922,3 +922,40 @@ def update_existing_file_docs(doc): content_hash=doc.content_hash, file_name=doc.name )) + +def attach_files_to_document(doc, event): + """ Runs on on_update hook of all documents. + Goes through every Attach and Attach Image field and attaches + the file url to the document if it is not already attached. + """ + + attach_fields = doc.meta.get( + "fields", {"fieldtype": ["in", ["Attach", "Attach Image"]]} + ) + + for df in attach_fields: + # this method runs in on_update hook of all documents + # we dont want the update to fail if file cannot be attached for some reason + try: + value = doc.get(df.fieldname) + if not value.startswith(("/files", "/private/files")): + return + + if frappe.db.exists("File", { + "file_url": value, + "attached_to_name": doc.name, + "attached_to_doctype": doc.doctype, + "attached_to_field": df.fieldname, + }): + return + + frappe.get_doc( + doctype="File", + file_url=value, + attached_to_name=doc.name, + attached_to_doctype=doc.doctype, + attached_to_field=df.fieldname, + folder="Home/Attachments", + ).insert() + except Exception: + frappe.log_error(title=_("Error Attaching File")) diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index ec4f97bf67..85397ea1ee 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -328,3 +328,49 @@ class TestFile(unittest.TestCase): self.assertTrue(os.path.exists(file2.get_full_path())) +class TestAttachment(unittest.TestCase): + test_doctype = 'Test For Attachment' + + def setUp(self): + if frappe.db.exists('DocType', self.test_doctype): + return + + frappe.get_doc( + doctype='DocType', + name=self.test_doctype, + module='Custom', + custom=1, + fields=[ + {'label': 'Title', 'fieldname': 'title', 'fieldtype': 'Data'}, + {'label': 'Attachment', 'fieldname': 'attachment', 'fieldtype': 'Attach'}, + ] + ).insert() + + def tearDown(self): + frappe.delete_doc('DocType', self.test_doctype) + + def test_file_attachment_on_update(self): + doc = frappe.get_doc( + doctype=self.test_doctype, + title='test for attachment on update' + ).insert() + + file = frappe.get_doc({ + 'doctype': 'File', + 'file_name': 'test_attach.txt', + 'content': 'Test Content' + }) + file.save() + + doc.attachment = file.file_url + doc.save() + + exists = frappe.db.exists('File', { + 'file_name': 'test_attach.txt', + 'file_url': file.file_url, + 'attached_to_doctype': self.test_doctype, + 'attached_to_name': doc.name, + 'attached_to_field': 'attachment' + }) + + self.assertTrue(exists) diff --git a/frappe/hooks.py b/frappe/hooks.py index efacaee17e..7ecc199814 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -136,7 +136,8 @@ doc_events = { "frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions", "frappe.automation.doctype.assignment_rule.assignment_rule.apply", "frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone", - "frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers" + "frappe.core.doctype.file.file.attach_files_to_document", + "frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers", ], "after_rename": "frappe.desk.notifications.clear_doctype_notifications", "on_cancel": [ diff --git a/frappe/public/js/frappe/file_uploader/FileUploader.vue b/frappe/public/js/frappe/file_uploader/FileUploader.vue index 845fbf92b4..f8d9253931 100644 --- a/frappe/public/js/frappe/file_uploader/FileUploader.vue +++ b/frappe/public/js/frappe/file_uploader/FileUploader.vue @@ -144,6 +144,9 @@ export default { docname: { default: null }, + fieldname: { + default: null + }, folder: { default: 'Home' }, @@ -406,6 +409,10 @@ export default { form_data.append('docname', this.docname); } + if (this.fieldname) { + form_data.append('fieldname', this.fieldname); + } + if (this.method) { form_data.append('method', this.method); } diff --git a/frappe/public/js/frappe/file_uploader/index.js b/frappe/public/js/frappe/file_uploader/index.js index f81a1f1776..62a7bff822 100644 --- a/frappe/public/js/frappe/file_uploader/index.js +++ b/frappe/public/js/frappe/file_uploader/index.js @@ -7,6 +7,7 @@ export default class FileUploader { on_success, doctype, docname, + fieldname, files, folder, restrictions, @@ -28,6 +29,7 @@ export default class FileUploader { show_upload_button: !Boolean(this.dialog), doctype, docname, + fieldname, method, folder, on_success, diff --git a/frappe/public/js/frappe/form/controls/attach.js b/frappe/public/js/frappe/form/controls/attach.js index 15cbd3b043..fe662c1ada 100644 --- a/frappe/public/js/frappe/form/controls/attach.js +++ b/frappe/public/js/frappe/form/controls/attach.js @@ -66,6 +66,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ if (this.frm) { options.doctype = this.frm.doctype; options.docname = this.frm.docname; + options.fieldname = this.df.fieldname; } if (this.df.options) {