From 28d26d2e65ae5c29f59e600c56780aaa5106db7d Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Thu, 9 Jan 2014 20:14:51 +0530 Subject: [PATCH 01/15] make filemanager hooks and default them to filesystem --- frappe/core/doctype/file_data/file_data.py | 10 +-- frappe/utils/file_manager.py | 82 +++++++++++++++++----- frappe/widgets/form/load.py | 5 +- 3 files changed, 69 insertions(+), 28 deletions(-) diff --git a/frappe/core/doctype/file_data/file_data.py b/frappe/core/doctype/file_data/file_data.py index 7b58f9e905..c6f9d00005 100644 --- a/frappe/core/doctype/file_data/file_data.py +++ b/frappe/core/doctype/file_data/file_data.py @@ -11,6 +11,7 @@ naming for same name files: file.gif, file-1.gif, file-2.gif etc import frappe, frappe.utils, os from frappe import conf from frappe.model.document import Document +from frappe.utils.file_manager import delete_file_data_content class FileData(Document): def before_insert(self): @@ -45,12 +46,7 @@ class FileData(Document): # if file not attached to any other record, delete it if self.file_name and not frappe.db.count("File Data", {"file_name": self.file_name, "name": ["!=", self.name]}): - if self.file_name.startswith("files/"): - path = frappe.utils.get_site_path("public", self.file_name) - else: - path = frappe.utils.get_site_path("public", "files", self.file_name) - if os.path.exists(path): - os.remove(path) + delete_file_data_content(self.file_name) def on_rollback(self): - self.on_trash() \ No newline at end of file + self.on_trash() diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index 767994b712..036d6a01f1 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -4,6 +4,8 @@ from __future__ import unicode_literals import frappe import os, base64, re +import hashlib +import mimetypes from frappe.utils import cstr, cint, get_site_path from frappe import _ from frappe import conf @@ -92,16 +94,40 @@ def save_file(fname, content, dt, dn, decode=False): if isinstance(content, unicode): content = content.encode("utf-8") content = base64.b64decode(content) + + file_size = check_max_file_size(content) + content_hash = get_content_hash(content) + content_type = mimetypes.guess_type(fname)[0] + + method = (webnotes.get_hooks().get('write_file')) + if method: + method = webnotes.get_attr(method[0]) + else: + method = save_file_on_filesystem + file_path = method(fname, content, content_hash, content_type=content_type) + + f = webnotes.bean({ + "doctype": "File Data", + "file_name": file_path, + "attached_to_doctype": dt, + "attached_to_name": dn, + "file_size": file_size, + "file_hash": content_hash + }) + f.ignore_permissions = True + try: + f.insert(); + except webnotes.DuplicateEntryError: + return webnotes.doc("File Data", f.doc.duplicate_entry) + + return f.doc +def save_file_on_filesystem(fname, content, content_hash, content_type=None): import filecmp from frappe.modules import load_doctype_module files_path = os.path.join(frappe.local.site_path, "public", "files") module = load_doctype_module(dt, frappe.db.get_value("DocType", dt, "module")) - if hasattr(module, "attachments_folder"): - files_path = os.path.join(files_path, module.attachments_folder) - - file_size = check_max_file_size(content) temp_fname = write_file(content, files_path) fname = scrub_file_name(fname) @@ -139,21 +165,8 @@ def save_file(fname, content, dt, dn, decode=False): frappe.throw("File already exists: " + fname) os.rename(temp_fname, fpath.encode("utf-8")) - - f = frappe.get_doc({ - "doctype": "File Data", - "file_name": os.path.relpath(os.path.join(files_path, fname), get_site_path("public")), - "attached_to_doctype": dt, - "attached_to_name": dn, - "file_size": file_size - }) - f.ignore_permissions = True - try: - f.insert(); - except frappe.DuplicateEntryError: - return frappe.get_doc("File Data", f.duplicate_entry) - - return f + return os.path.relpath(fpath, + get_site_path(conf.get("public_path", "public"))) def get_file_versions(files_path, main, extn): out = [] @@ -220,6 +233,22 @@ def remove_all(dt, dn): def remove_file(fid): """Remove file and File Data entry""" frappe.delete_doc("File Data", fid) + +def delete_file_data_content(path): + method = (frappe.get_hooks().get('delete_file_data_content')) + if method: + method = frappe.get_attr(method[0]) + else: + method = delete_file_from_filesystem + method(path) + +def delete_file_from_filesystem(path): + if path.startswith("files/"): + path = frappe.utils.get_site_path("public", self.doc.file_name) + else: + path = frappe.utils.get_site_path("public", "files", self.doc.file_name) + if os.path.exists(path): + os.remove(path) def get_file(fname): f = frappe.db.sql("""select file_name from `tabFile Data` @@ -237,3 +266,18 @@ def get_file(fname): content = f.read() return [file_name, content] + +def get_content_hash(content): + return hashlib.md5(content).hexdigest() + +def get_file_url(file_data_doc): + if file_data_doc.file_url: + return file_url + + method = (webnotes.get_hooks().get('get_file_data_url')) + if method: + method = webnotes.get_attr(method[0]) + return method(file_data_doc.file_name) + else: + return file_name + diff --git a/frappe/widgets/form/load.py b/frappe/widgets/form/load.py index 67e23369a2..28e84e2f66 100644 --- a/frappe/widgets/form/load.py +++ b/frappe/widgets/form/load.py @@ -6,6 +6,7 @@ import frappe, json import frappe.utils import frappe.defaults import frappe.widgets.form.meta +from frappe.utils.file_manager import get_file_url @frappe.whitelist() def getdoc(doctype, name, user=None): @@ -92,7 +93,7 @@ def add_attachments(dt, dn): for f in frappe.db.sql("""select name, file_name, file_url from `tabFile Data` where attached_to_name=%s and attached_to_doctype=%s""", (dn, dt), as_dict=True): - attachments[f.file_url or f.file_name] = f.name + attachments[get_file_url(f)] = f.name return attachments @@ -122,4 +123,4 @@ def get_badge_info(doctypes, filters): for doctype in doctypes: out[doctype] = frappe.db.get_value(doctype, filters, "count(*)") - return out \ No newline at end of file + return out From 8281f07aae9c55148bac33667c2e1191a00f8a20 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Wed, 15 Jan 2014 19:37:11 +0530 Subject: [PATCH 02/15] redo files! --- frappe/core/doctype/file_data/file_data.py | 2 +- frappe/public/js/frappe/form/attachments.js | 52 +++---- frappe/public/js/frappe/upload.js | 6 +- frappe/utils/__init__.py | 8 ++ frappe/utils/file_manager.py | 146 +++++--------------- frappe/widgets/form/load.py | 8 +- 6 files changed, 83 insertions(+), 139 deletions(-) diff --git a/frappe/core/doctype/file_data/file_data.py b/frappe/core/doctype/file_data/file_data.py index c6f9d00005..91fb3cb6fd 100644 --- a/frappe/core/doctype/file_data/file_data.py +++ b/frappe/core/doctype/file_data/file_data.py @@ -46,7 +46,7 @@ class FileData(Document): # if file not attached to any other record, delete it if self.file_name and not frappe.db.count("File Data", {"file_name": self.file_name, "name": ["!=", self.name]}): - delete_file_data_content(self.file_name) + delete_file_data_content(self) def on_rollback(self): self.on_trash() diff --git a/frappe/public/js/frappe/form/attachments.js b/frappe/public/js/frappe/form/attachments.js index cbde3e16f5..d6637f5158 100644 --- a/frappe/public/js/frappe/form/attachments.js +++ b/frappe/public/js/frappe/form/attachments.js @@ -41,13 +41,14 @@ frappe.ui.form.Attachments = Class.extend({ this.$list.empty(); var attachments = this.get_attachments(); - var file_names = keys(attachments).sort(); + var that = this; + // add attachment objects - if(file_names.length) { - for(var i=0; i' + frappe._("None") + '

').appendTo(this.$list); } @@ -58,18 +59,20 @@ frappe.ui.form.Attachments = Class.extend({ get_attachments: function() { return this.frm.get_docinfo().attachments; }, - add_attachment: function(filename, attachments) { - var fileid = attachments[filename]; + add_attachment: function(attachment) { + var file_name = attachment.file_name; + var file_url = attachment.file_url; + var fileid = attachment.name; var me = this; var $attach = $(repl('', { - filename: filename, - href: frappe.utils.get_file_link(filename) + file_name: file_name, + file_url: file_url })) .appendTo(this.$list) @@ -83,7 +86,7 @@ frappe.ui.form.Attachments = Class.extend({ me.remove_attachment($(remove_btn).data("fileid")) } ); - return false; + return false }); if(!frappe.model.can_write(this.frm.doctype, this.frm.name)) { @@ -131,9 +134,9 @@ frappe.ui.form.Attachments = Class.extend({ doctype: this.frm.doctype, docname: this.frm.docname, }, - callback: function(fileid, filename, r) { + callback: function(fileid, filename, file_url, r) { me.dialog.hide(); - me.update_attachment(fileid, filename, fieldname, r); + me.update_attachment(fileid, filename, file_url, fieldname, r.message); }, onerror: function() { me.dialog.hide(); @@ -142,26 +145,27 @@ frappe.ui.form.Attachments = Class.extend({ max_height: this.frm.cscript ? this.frm.cscript.attachment_max_height : null, }); }, - update_attachment: function(fileid, filename, fieldname, r) { + update_attachment: function(fileid, filename, fieldname, file_url, attachment) { if(fileid) { - this.add_to_attachments(fileid, filename); + this.add_to_attachments(attachment); this.refresh(); if(fieldname) { - this.frm.set_value(fieldname, frappe.utils.get_file_link(filename)); + this.frm.set_value(fieldname, file_url); this.frm.cscript[fieldname] && this.frm.cscript[fieldname](this.frm.doc); this.frm.toolbar.show_infobar(); } } }, - add_to_attachments: function(fileid, filename) { - this.get_attachments()[filename] = fileid; + add_to_attachments: function (attachment) { + this.get_attachments().push(attachment); }, remove_fileid: function(fileid) { var attachments = this.get_attachments(); - var new_attachments = {}; - $.each(attachments, function(key, value) { - if(value!=fileid) - new_attachments[key] = value; + var new_attachments = []; + $.each(attachments, function(i, attachment) { + if(attachment.name!=fileid) { + new_attachments.push(attachment); + } }); this.frm.get_docinfo().attachments = new_attachments; this.refresh(); @@ -181,4 +185,4 @@ frappe.ui.form.Attachments = Class.extend({ } } } -}); \ No newline at end of file +}); diff --git a/frappe/public/js/frappe/upload.js b/frappe/public/js/frappe/upload.js index 90356fe317..19f49af828 100644 --- a/frappe/public/js/frappe/upload.js +++ b/frappe/public/js/frappe/upload.js @@ -91,9 +91,9 @@ frappe.upload = { opts.onerror ? opts.onerror(r) : opts.callback(null, null, r); return; } - opts.callback(r.message.fid, r.message.filename, r); + opts.callback(r.message.name , r.message.file_name, r.message.file_url, r); $(document).trigger("upload_complete", - [r.message.fid, r.message.filename]); + [r.message.name , r.message.file_name]); } }); } @@ -123,4 +123,4 @@ frappe.upload = { freader.readAsDataURL(fileobj); } } -} \ No newline at end of file +} diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index abcf2d11c8..835217e744 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -913,3 +913,11 @@ def touch_file(path): def get_test_client(): from frappe.app import application return Client(application) + +def get_hook_method(hook_name, fallback=None): + method = (frappe.get_hooks().get(hook_name)) + if method: + method = frappe.get_attr(method[0]) + return method + if fallback: + return fallback diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index 036d6a01f1..60e3aeddc2 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -6,9 +6,10 @@ import frappe import os, base64, re import hashlib import mimetypes -from frappe.utils import cstr, cint, get_site_path +from frappe.utils import cstr, cint, get_site_path, get_hook_method from frappe import _ from frappe import conf +from copy import copy class MaxFileSizeReachedError(frappe.ValidationError): pass @@ -33,7 +34,11 @@ def upload(): elif file_url: filedata = save_url(file_url, dt, dn) - return {"fid": filedata.name, "filename": filedata.file_name or filedata.file_url } + return { + "name": filedata.name, + "file_name": filedata.file_name, + "file_url": filedata.file_url + } def save_uploaded(dt, dn): fname, content = get_uploaded_content() @@ -78,11 +83,11 @@ def extract_images_from_html(doc, fieldname): data = match.group(1) headers, content = data.split(",") filename = headers.split("filename=")[-1] - filename = save_file(filename, content, doc.doctype, doc.name, decode=True).get("file_name") + file_url = save_file(filename, content, doc.doctype, doc.name, decode=True).get("file_url") if not frappe.flags.has_dataurl: frappe.flags.has_dataurl = True - return ' 100: - frappe.msgprint("Too many versions", raise_exception=True) - - return new_fname - -def scrub_file_name(fname): - if '\\' in fname: - fname = fname.split('\\')[-1] - if '/' in fname: - fname = fname.split('/')[-1] - return fname + public_path = os.path.join(frappe.local.site_path, "public", "files") + temp_fname = write_file(content, public_path, fname) + path = os.path.relpath(fpath, public_path) + return { + 'file_name': path, + 'file_path': path + } def check_max_file_size(content): max_file_size = conf.get('max_file_size') or 1000000 @@ -209,16 +147,13 @@ def check_max_file_size(content): return file_size -def write_file(content, files_path): +def write_file(content, file_path, fname): """write file to disk with a random name (to compare)""" - # create account folder (if not exists) + # create directory (if not exists) frappe.create_folder(files_path) - fname = os.path.join(files_path, frappe.generate_hash()) - # write the file with open(fname, 'w+') as f: f.write(content) - return fname def remove_all(dt, dn): @@ -234,15 +169,12 @@ def remove_file(fid): """Remove file and File Data entry""" frappe.delete_doc("File Data", fid) -def delete_file_data_content(path): - method = (frappe.get_hooks().get('delete_file_data_content')) - if method: - method = frappe.get_attr(method[0]) - else: - method = delete_file_from_filesystem - method(path) +def delete_file_data_content(doc): + method = get_hook_method('delete_file_data_content', fallback=delete_file_from_filesystem) + method(doc) -def delete_file_from_filesystem(path): +def delete_file_from_filesystem(doc): + path = doc.file_name if path.startswith("files/"): path = frappe.utils.get_site_path("public", self.doc.file_name) else: @@ -270,14 +202,10 @@ def get_file(fname): def get_content_hash(content): return hashlib.md5(content).hexdigest() -def get_file_url(file_data_doc): - if file_data_doc.file_url: - return file_url - - method = (webnotes.get_hooks().get('get_file_data_url')) - if method: - method = webnotes.get_attr(method[0]) - return method(file_data_doc.file_name) - else: - return file_name +def get_file_name(fname, optional_suffix): + n_records = webnotes.conn.sql("select name from `tabFile Data` where file_name='{}'".format(fname)) + if len(n_records) > 0: + partial, extn = fname.rsplit('.', 1) + return '{partial}{suffix}.{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix) + return fname diff --git a/frappe/widgets/form/load.py b/frappe/widgets/form/load.py index 28e84e2f66..2e26d43ad2 100644 --- a/frappe/widgets/form/load.py +++ b/frappe/widgets/form/load.py @@ -89,11 +89,15 @@ def get_restrictions(meta): return out def add_attachments(dt, dn): - attachments = {} + attachments = [] for f in frappe.db.sql("""select name, file_name, file_url from `tabFile Data` where attached_to_name=%s and attached_to_doctype=%s""", (dn, dt), as_dict=True): - attachments[get_file_url(f)] = f.name + attachments.append({ + 'name': f.name, + 'file_url': f.file_url, + 'file_name': f.file_name + }) return attachments From a257d47a768bdfeb31ac71fe6f393aa06b92892c Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Mon, 3 Mar 2014 21:48:14 +0530 Subject: [PATCH 03/15] filemanager fixes --- frappe/utils/__init__.py | 4 ++-- frappe/utils/file_manager.py | 27 +++++++++++++-------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 835217e744..d722c13087 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -782,8 +782,8 @@ def get_site_base_path(sites_dir=None, hostname=None): def get_site_path(*path): return get_path(base=get_site_base_path(), *path) -def get_files_path(): - return get_site_path("public", "files") +def get_files_path(*path): + return get_site_path("public", "files", *path) def get_backups_path(): return get_site_path("private", "backups") diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index 60e3aeddc2..3557d7ea90 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -6,7 +6,7 @@ import frappe import os, base64, re import hashlib import mimetypes -from frappe.utils import cstr, cint, get_site_path, get_hook_method +from frappe.utils import cstr, cint, get_site_path, get_hook_method, get_files_path from frappe import _ from frappe import conf from copy import copy @@ -117,24 +117,23 @@ def save_file(fname, content, dt, dn, decode=False): "file_hash": content_hash }) - f = webnotes.bean(file_data) + f = frappe.bean(file_data) f.ignore_permissions = True try: f.insert(); - except webnotes.DuplicateEntryError: - return webnotes.doc("File Data", f.doc.duplicate_entry) + except frappe.DuplicateEntryError: + return frappe.doc("File Data", f.doc.duplicate_entry) return f.doc def save_file_on_filesystem(fname, content, content_type=None): import filecmp - from frappe.modules import load_doctype_module - public_path = os.path.join(frappe.local.site_path, "public", "files") - temp_fname = write_file(content, public_path, fname) + public_path = os.path.join(frappe.local.site_path, "public") + fpath = write_file(content, get_files_path(), fname) path = os.path.relpath(fpath, public_path) return { 'file_name': path, - 'file_path': path + 'file_url': '/' + path } def check_max_file_size(content): @@ -150,11 +149,11 @@ def check_max_file_size(content): def write_file(content, file_path, fname): """write file to disk with a random name (to compare)""" # create directory (if not exists) - frappe.create_folder(files_path) + frappe.create_folder(get_files_path()) # write the file - with open(fname, 'w+') as f: + with open(os.path.join(file_path, fname), 'w+') as f: f.write(content) - return fname + return get_files_path(fname) def remove_all(dt, dn): """remove all files in a transaction""" @@ -176,9 +175,9 @@ def delete_file_data_content(doc): def delete_file_from_filesystem(doc): path = doc.file_name if path.startswith("files/"): - path = frappe.utils.get_site_path("public", self.doc.file_name) + path = frappe.utils.get_site_path("public", doc.file_name) else: - path = frappe.utils.get_site_path("public", "files", self.doc.file_name) + path = frappe.utils.get_site_path("public", "files", doc.file_name) if os.path.exists(path): os.remove(path) @@ -203,7 +202,7 @@ def get_content_hash(content): return hashlib.md5(content).hexdigest() def get_file_name(fname, optional_suffix): - n_records = webnotes.conn.sql("select name from `tabFile Data` where file_name='{}'".format(fname)) + n_records = frappe.db.sql("select name from `tabFile Data` where file_name='{}'".format(fname)) if len(n_records) > 0: partial, extn = fname.rsplit('.', 1) return '{partial}{suffix}.{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix) From 36a149a678d2bacfed9a1ea7af4acb7b674445bf Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Tue, 4 Mar 2014 00:26:17 +0530 Subject: [PATCH 04/15] show correct name for file data link --- frappe/public/js/frappe/form/attachments.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/public/js/frappe/form/attachments.js b/frappe/public/js/frappe/form/attachments.js index d6637f5158..1d9f7248ec 100644 --- a/frappe/public/js/frappe/form/attachments.js +++ b/frappe/public/js/frappe/form/attachments.js @@ -63,6 +63,9 @@ frappe.ui.form.Attachments = Class.extend({ var file_name = attachment.file_name; var file_url = attachment.file_url; var fileid = attachment.name; + if (!file_name) { + file_name = file_url; + } var me = this; var $attach = $(repl('
\ From 5103366eb9e824209b3ecc4cde2af742122e99e6 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Tue, 4 Mar 2014 00:27:07 +0530 Subject: [PATCH 05/15] add filemanager patch --- frappe/patches.txt | 1 + frappe/patches/4_0/file_manager_hooks.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 frappe/patches/4_0/file_manager_hooks.py diff --git a/frappe/patches.txt b/frappe/patches.txt index bcb5b9efa3..ffc14b4433 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -24,3 +24,4 @@ frappe.patches.4_0.remove_old_parent frappe.patches.4_0.rename_profile_to_user frappe.patches.4_0.update_datetime frappe.patches.4_0.deprecate_control_panel +frappe.patches.4_0.file_manager_hooks diff --git a/frappe/patches/4_0/file_manager_hooks.py b/frappe/patches/4_0/file_manager_hooks.py new file mode 100644 index 0000000000..080db3241f --- /dev/null +++ b/frappe/patches/4_0/file_manager_hooks.py @@ -0,0 +1,22 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals + +import frappe +import os +from frappe.utils import get_files_path + +def execute(): + for name, file_name, file_url in frappe.db.sql( + """select name, file_name, file_url from `tabFile Data` + where file_name is not null"""): + b = frappe.bean('File Data', name) + old_file_name = b.doc.file_name + b.doc.file_name = os.path.basename(old_file_name) + if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): + b.doc.file_url = os.path.normpath('/' + old_file_name) + else: + b.doc.file_url = os.path.normpath('/files/' + old_file_name) + b.save() + From e30dfd4ed07e4b42b9176c93eec7617d6f9e7faa Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Wed, 5 Mar 2014 16:59:45 +0530 Subject: [PATCH 06/15] file deduplication --- frappe/core/doctype/file_data/file_data.py | 4 ++-- frappe/hooks.txt | 2 ++ frappe/patches/4_0/file_manager_hooks.py | 3 +++ frappe/utils/file_manager.py | 15 ++++++++++++--- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/file_data/file_data.py b/frappe/core/doctype/file_data/file_data.py index 91fb3cb6fd..9e8bd0c156 100644 --- a/frappe/core/doctype/file_data/file_data.py +++ b/frappe/core/doctype/file_data/file_data.py @@ -44,8 +44,8 @@ class FileData(Document): pass # if file not attached to any other record, delete it - if self.file_name and not frappe.db.count("File Data", - {"file_name": self.file_name, "name": ["!=", self.name]}): + if self.doc.file_name and not frappe.db.count("File Data", + {"content_hash": self.content_hash, "name": ["!=", self.name]}): delete_file_data_content(self) def on_rollback(self): diff --git a/frappe/hooks.txt b/frappe/hooks.txt index c3aff8d520..dbc5a5b071 100644 --- a/frappe/hooks.txt +++ b/frappe/hooks.txt @@ -48,3 +48,5 @@ doc_event:Website Route Permission:on_update = frappe.templates.generators.websi doc_event:*:on_update = frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications doc_event:*:on_cancel = frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications doc_event:*:on_trash = frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications + +write_file_keys = file_url diff --git a/frappe/patches/4_0/file_manager_hooks.py b/frappe/patches/4_0/file_manager_hooks.py index 080db3241f..f5e83ec69b 100644 --- a/frappe/patches/4_0/file_manager_hooks.py +++ b/frappe/patches/4_0/file_manager_hooks.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe import os from frappe.utils import get_files_path +from frappe.utils.filemanager import get_content_hash, get_file def execute(): for name, file_name, file_url in frappe.db.sql( @@ -18,5 +19,7 @@ def execute(): b.doc.file_url = os.path.normpath('/' + old_file_name) else: b.doc.file_url = os.path.normpath('/files/' + old_file_name) + _file_name, content = get_file(file_name) + b.doc.content_hash = get_content_hash(content) b.save() diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index 3557d7ea90..c4a8d78647 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -107,14 +107,17 @@ def save_file(fname, content, dt, dn, decode=False): method = get_hook_method('write_file', fallback=save_file_on_filesystem) - file_data = method(fname, content, content_type=content_type) - file_data = copy(file_data) + file_data = get_file_data_from_hash(content_hash) + if not file_data: + file_data = method(fname, content, content_type=content_type) + file_data = copy(file_data) file_data.update({ "doctype": "File Data", "attached_to_doctype": dt, "attached_to_name": dn, "file_size": file_size, - "file_hash": content_hash + "file_hash": content_hash, + "file_name": fname }) f = frappe.bean(file_data) @@ -125,6 +128,12 @@ def save_file(fname, content, dt, dn, decode=False): return frappe.doc("File Data", f.doc.duplicate_entry) return f.doc + +def get_file_data_from_hash(content_hash): + for name in frappe.db.sql_list("select name from `tabFile Data` where content_hash='{}'".format(content_hash)): + b = frappe.bean('File Data', name) + return {k:b.doc.fields[k] for k in frappe.get_hooks()['write_file_keys']} + return False def save_file_on_filesystem(fname, content, content_type=None): import filecmp From 0791292d34e98647010088006d240571c7015afd Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Mon, 7 Apr 2014 13:01:50 +0530 Subject: [PATCH 07/15] schema changes for file manager hooks --- frappe/core/doctype/file_data/file_data.json | 201 ++++++++++++++++++- 1 file changed, 192 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/file_data/file_data.json b/frappe/core/doctype/file_data/file_data.json index d416ebbb3f..a3b056ca32 100644 --- a/frappe/core/doctype/file_data/file_data.json +++ b/frappe/core/doctype/file_data/file_data.json @@ -1,72 +1,255 @@ { + "_last_update": null, + "_user_tags": null, + "allow_attach": null, + "allow_copy": null, + "allow_email": null, + "allow_import": null, + "allow_print": null, + "allow_rename": null, + "allow_trash": null, "autoname": "File.######", - "creation": "2012-12-12 11:19:22.000000", + "change_log": null, + "client_script": null, + "client_script_core": null, + "client_string": null, + "colour": null, + "creation": "2012-12-12 11:19:22", + "custom": null, + "default_print_format": null, + "description": null, "docstatus": 0, "doctype": "DocType", + "document_type": null, + "dt_template": null, "fields": [ { + "allow_on_submit": null, + "default": null, + "depends_on": null, + "description": null, "fieldname": "file_name", "fieldtype": "Data", + "hidden": null, + "ignore_restrictions": null, + "in_filter": null, + "in_list_view": null, "label": "File Name", + "no_column": null, + "no_copy": null, "oldfieldname": "file_name", "oldfieldtype": "Data", + "options": null, "permlevel": 0, - "read_only": 1 + "print_hide": null, + "print_width": null, + "read_only": 1, + "report_hide": null, + "reqd": null, + "search_index": null, + "set_only_once": null, + "trigger": null, + "width": null }, { + "allow_on_submit": null, + "default": null, + "depends_on": null, + "description": null, "fieldname": "file_url", "fieldtype": "Data", + "hidden": null, + "ignore_restrictions": null, + "in_filter": null, "in_list_view": 1, "label": "File URL", + "no_column": null, + "no_copy": null, + "oldfieldname": null, + "oldfieldtype": null, + "options": null, "permlevel": 0, - "read_only": 1 + "print_hide": null, + "print_width": null, + "read_only": 1, + "report_hide": null, + "reqd": null, + "search_index": null, + "set_only_once": null, + "trigger": null, + "width": null }, { + "allow_on_submit": null, + "default": null, + "depends_on": null, + "description": null, "fieldname": "attached_to_doctype", "fieldtype": "Link", + "hidden": null, + "ignore_restrictions": null, + "in_filter": null, "in_list_view": 1, "label": "Attached To DocType", + "no_column": null, + "no_copy": null, + "oldfieldname": null, + "oldfieldtype": null, "options": "DocType", "permlevel": 0, + "print_hide": null, + "print_width": null, "read_only": 1, - "search_index": 1 + "report_hide": null, + "reqd": null, + "search_index": 1, + "set_only_once": null, + "trigger": null, + "width": null }, { + "allow_on_submit": null, + "default": null, + "depends_on": null, + "description": null, "fieldname": "attached_to_name", "fieldtype": "Data", + "hidden": null, + "ignore_restrictions": null, + "in_filter": null, "in_list_view": 1, "label": "Attached To Name", + "no_column": null, + "no_copy": null, + "oldfieldname": null, + "oldfieldtype": null, + "options": null, "permlevel": 0, + "print_hide": null, + "print_width": null, "read_only": 1, - "search_index": 1 + "report_hide": null, + "reqd": null, + "search_index": 1, + "set_only_once": null, + "trigger": null, + "width": null }, { + "allow_on_submit": null, + "default": null, + "depends_on": null, + "description": null, "fieldname": "file_size", "fieldtype": "Int", + "hidden": null, + "ignore_restrictions": null, + "in_filter": null, "in_list_view": 1, "label": "File Size", + "no_column": null, + "no_copy": null, + "oldfieldname": null, + "oldfieldtype": null, + "options": null, "permlevel": 0, - "read_only": 1 + "print_hide": null, + "print_width": null, + "read_only": 1, + "report_hide": null, + "reqd": null, + "search_index": null, + "set_only_once": null, + "trigger": null, + "width": null + }, + { + "allow_on_submit": null, + "default": null, + "depends_on": null, + "description": null, + "fieldname": "file_hash", + "fieldtype": "Data", + "hidden": null, + "ignore_restrictions": null, + "in_filter": null, + "in_list_view": null, + "label": "File Hash", + "no_column": null, + "no_copy": null, + "oldfieldname": null, + "oldfieldtype": null, + "options": null, + "permlevel": 0, + "print_hide": null, + "print_width": null, + "read_only": null, + "report_hide": null, + "reqd": null, + "search_index": 1, + "set_only_once": null, + "trigger": null, + "width": null } ], + "hide_heading": null, + "hide_toolbar": null, "icon": "icon-file", "idx": 1, - "modified": "2014-01-20 17:48:46.000000", + "in_create": null, + "in_dialog": null, + "is_submittable": null, + "is_transaction_doc": null, + "issingle": null, + "istable": null, + "max_attachments": null, + "menu_index": null, + "modified": "2014-04-07 13:00:57.412982", "modified_by": "Administrator", "module": "Core", "name": "File Data", + "name_case": null, "owner": "Administrator", + "parent": null, + "parent_node": null, + "parentfield": null, + "parenttype": null, "permissions": [ { - "cancel": 1, + "amend": null, + "cancel": 0, + "create": null, "delete": 1, "email": 1, + "export": null, + "import": null, + "match": null, "permlevel": 0, "print": 1, "read": 1, + "report": null, + "restrict": null, + "restricted": null, "role": "System Manager", + "submit": null, "write": 1 } ], - "read_only": 0 + "plugin": null, + "print_outline": null, + "read_only": 0, + "read_only_onload": null, + "search_fields": null, + "section_style": null, + "server_code": null, + "server_code_compiled": null, + "server_code_core": null, + "server_code_error": null, + "show_in_menu": null, + "smallicon": null, + "subject": null, + "tag_fields": null, + "title_field": null, + "use_template": null, + "version": null } \ No newline at end of file From 66c7de1799597a83f170ffc38bad13aaf7849732 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Mon, 7 Apr 2014 16:58:53 +0530 Subject: [PATCH 08/15] fixes for new model --- frappe/core/doctype/file_data/file_data.json | 6 +++--- frappe/core/doctype/file_data/file_data.py | 4 ++-- frappe/hooks.txt | 1 + frappe/patches/4_0/file_manager_hooks.py | 12 ++++++------ frappe/utils/file_manager.py | 17 ++++++++--------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/frappe/core/doctype/file_data/file_data.json b/frappe/core/doctype/file_data/file_data.json index a3b056ca32..3feee0c22f 100644 --- a/frappe/core/doctype/file_data/file_data.json +++ b/frappe/core/doctype/file_data/file_data.json @@ -168,13 +168,13 @@ "default": null, "depends_on": null, "description": null, - "fieldname": "file_hash", + "fieldname": "content_hash", "fieldtype": "Data", "hidden": null, "ignore_restrictions": null, "in_filter": null, "in_list_view": null, - "label": "File Hash", + "label": "Content Hash", "no_column": null, "no_copy": null, "oldfieldname": null, @@ -204,7 +204,7 @@ "istable": null, "max_attachments": null, "menu_index": null, - "modified": "2014-04-07 13:00:57.412982", + "modified": "2014-04-07 13:05:47.684017", "modified_by": "Administrator", "module": "Core", "name": "File Data", diff --git a/frappe/core/doctype/file_data/file_data.py b/frappe/core/doctype/file_data/file_data.py index 9e8bd0c156..ec8fd70d06 100644 --- a/frappe/core/doctype/file_data/file_data.py +++ b/frappe/core/doctype/file_data/file_data.py @@ -35,7 +35,7 @@ class FileData(Document): if self.attached_to_name: # check persmission try: - if not self.ignore_permissions and \ + if not getattr(self, 'ignore_permissions', False) and \ not frappe.has_permission(self.attached_to_doctype, "write", self.attached_to_name): frappe.msgprint(frappe._("No permission to write / remove."), raise_exception=True) @@ -44,7 +44,7 @@ class FileData(Document): pass # if file not attached to any other record, delete it - if self.doc.file_name and not frappe.db.count("File Data", + if self.file_name and not frappe.db.count("File Data", {"content_hash": self.content_hash, "name": ["!=", self.name]}): delete_file_data_content(self) diff --git a/frappe/hooks.txt b/frappe/hooks.txt index dbc5a5b071..5feb017e48 100644 --- a/frappe/hooks.txt +++ b/frappe/hooks.txt @@ -50,3 +50,4 @@ doc_event:*:on_cancel = frappe.core.doctype.notification_count.notification_coun doc_event:*:on_trash = frappe.core.doctype.notification_count.notification_count.clear_doctype_notifications write_file_keys = file_url +write_file_keys = file_name diff --git a/frappe/patches/4_0/file_manager_hooks.py b/frappe/patches/4_0/file_manager_hooks.py index f5e83ec69b..7413d4a526 100644 --- a/frappe/patches/4_0/file_manager_hooks.py +++ b/frappe/patches/4_0/file_manager_hooks.py @@ -12,14 +12,14 @@ def execute(): for name, file_name, file_url in frappe.db.sql( """select name, file_name, file_url from `tabFile Data` where file_name is not null"""): - b = frappe.bean('File Data', name) - old_file_name = b.doc.file_name - b.doc.file_name = os.path.basename(old_file_name) + b = frappe.get_doc('File Data', name) + old_file_name = b.file_name + b.file_name = os.path.basename(old_file_name) if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): - b.doc.file_url = os.path.normpath('/' + old_file_name) + b.file_url = os.path.normpath('/' + old_file_name) else: - b.doc.file_url = os.path.normpath('/files/' + old_file_name) + b.file_url = os.path.normpath('/files/' + old_file_name) _file_name, content = get_file(file_name) - b.doc.content_hash = get_content_hash(content) + b.content_hash = get_content_hash(content) b.save() diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index c4a8d78647..06dea9ee33 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -83,6 +83,7 @@ def extract_images_from_html(doc, fieldname): data = match.group(1) headers, content = data.split(",") filename = headers.split("filename=")[-1] + # TODO fix this file_url = save_file(filename, content, doc.doctype, doc.name, decode=True).get("file_url") if not frappe.flags.has_dataurl: frappe.flags.has_dataurl = True @@ -116,23 +117,21 @@ def save_file(fname, content, dt, dn, decode=False): "attached_to_doctype": dt, "attached_to_name": dn, "file_size": file_size, - "file_hash": content_hash, - "file_name": fname + "content_hash": content_hash, }) - f = frappe.bean(file_data) + f = frappe.get_doc(file_data) f.ignore_permissions = True try: f.insert(); except frappe.DuplicateEntryError: - return frappe.doc("File Data", f.doc.duplicate_entry) - - return f.doc + return frappe.get_doc("File Data", f.duplicate_entry) + return f def get_file_data_from_hash(content_hash): for name in frappe.db.sql_list("select name from `tabFile Data` where content_hash='{}'".format(content_hash)): - b = frappe.bean('File Data', name) - return {k:b.doc.fields[k] for k in frappe.get_hooks()['write_file_keys']} + b = frappe.get_doc('File Data', name) + return {k:b.get(k) for k in frappe.get_hooks()['write_file_keys']} return False def save_file_on_filesystem(fname, content, content_type=None): @@ -141,7 +140,7 @@ def save_file_on_filesystem(fname, content, content_type=None): fpath = write_file(content, get_files_path(), fname) path = os.path.relpath(fpath, public_path) return { - 'file_name': path, + 'file_name': os.path.basename(path), 'file_url': '/' + path } From 0bd269448a7cee956af314e5cd6943253c9b8cdb Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Tue, 8 Apr 2014 17:44:43 +0530 Subject: [PATCH 09/15] fix rename_doc for DocType --- frappe/model/rename_doc.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 22763351f9..b177708681 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -114,21 +114,14 @@ def update_child_docs(old, new, meta): % (df.options, '%s', '%s'), (new, old)) def update_link_field_values(link_fields, old, new, doctype): - update_list = [] - - # update values for field in link_fields: - # if already updated, do not do it again - if [field['parent'], field['fieldname']] in update_list: - continue - update_list.append([field['parent'], field['fieldname']]) if field['issingle']: frappe.db.sql("""\ update `tabSingles` set value=%s where doctype=%s and field=%s and value=%s""", (new, field['parent'], field['fieldname'], old)) else: - if doctype!='DocType' and field['parent']!=new: + if field['parent']!=new: frappe.db.sql("""\ update `tab%s` set `%s`=%s where `%s`=%s""" \ From 631de3881fc689905d05abdd5080b44e494adae2 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Tue, 8 Apr 2014 19:10:05 +0530 Subject: [PATCH 10/15] add File Data columns to list view --- frappe/core/doctype/file_data/file_data.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/file_data/file_data.json b/frappe/core/doctype/file_data/file_data.json index 3feee0c22f..cb42e57ad1 100644 --- a/frappe/core/doctype/file_data/file_data.json +++ b/frappe/core/doctype/file_data/file_data.json @@ -33,7 +33,7 @@ "hidden": null, "ignore_restrictions": null, "in_filter": null, - "in_list_view": null, + "in_list_view": 1, "label": "File Name", "no_column": null, "no_copy": null, @@ -204,7 +204,7 @@ "istable": null, "max_attachments": null, "menu_index": null, - "modified": "2014-04-07 13:05:47.684017", + "modified": "2014-04-07 17:01:13.295614", "modified_by": "Administrator", "module": "Core", "name": "File Data", From 6fd5f02015240a29c1292a2596c89b695cce39bf Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Tue, 8 Apr 2014 19:10:47 +0530 Subject: [PATCH 11/15] check duplicate assignment using content_hash --- frappe/core/doctype/file_data/file_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/file_data/file_data.py b/frappe/core/doctype/file_data/file_data.py index ec8fd70d06..a457a3da5b 100644 --- a/frappe/core/doctype/file_data/file_data.py +++ b/frappe/core/doctype/file_data/file_data.py @@ -20,10 +20,10 @@ class FileData(Document): def on_update(self): # check duplicate assignement n_records = frappe.db.sql("""select name from `tabFile Data` - where file_name=%s + where content_hash=%s and name!=%s and attached_to_doctype=%s - and attached_to_name=%s""", (self.file_name, self.name, self.attached_to_doctype, + and attached_to_name=%s""", (self.content_hash, self.name, self.attached_to_doctype, self.attached_to_name)) if len(n_records) > 0: self.duplicate_entry = n_records[0][0] From d1a54091dad52f4ca0388fa693ed991db6008787 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Tue, 8 Apr 2014 19:11:50 +0530 Subject: [PATCH 12/15] fix file_manager hooks patch --- frappe/patches/4_0/file_manager_hooks.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/patches/4_0/file_manager_hooks.py b/frappe/patches/4_0/file_manager_hooks.py index 7413d4a526..5c72e62da3 100644 --- a/frappe/patches/4_0/file_manager_hooks.py +++ b/frappe/patches/4_0/file_manager_hooks.py @@ -6,9 +6,11 @@ from __future__ import unicode_literals import frappe import os from frappe.utils import get_files_path -from frappe.utils.filemanager import get_content_hash, get_file +from frappe.utils.file_manager import get_content_hash, get_file + def execute(): + frappe.reload_doc('core', 'doctype', 'file_data') for name, file_name, file_url in frappe.db.sql( """select name, file_name, file_url from `tabFile Data` where file_name is not null"""): @@ -19,7 +21,7 @@ def execute(): b.file_url = os.path.normpath('/' + old_file_name) else: b.file_url = os.path.normpath('/files/' + old_file_name) - _file_name, content = get_file(file_name) + _file_name, content = get_file(name) b.content_hash = get_content_hash(content) b.save() From 5e6319530cff9813410a47b8735051a409304837 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Wed, 9 Apr 2014 12:48:48 +0530 Subject: [PATCH 13/15] change patch order of rename profile to user --- frappe/patches.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/patches.txt b/frappe/patches.txt index ffc14b4433..b1ac2d88f6 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -16,12 +16,12 @@ frappe.patches.4_0.website_sitemap_hierarchy frappe.patches.4_0.webnotes_to_frappe execute:frappe.reset_perms("Module Def") frappe.patches.4_0.rename_sitemap_to_route +frappe.patches.4_0.rename_profile_to_user frappe.patches.4_0.set_website_route_idx execute:import frappe.installer;frappe.installer.make_site_dirs() #2014-02-19 frappe.patches.4_0.private_backups frappe.patches.4_0.set_module_in_report frappe.patches.4_0.remove_old_parent -frappe.patches.4_0.rename_profile_to_user frappe.patches.4_0.update_datetime frappe.patches.4_0.deprecate_control_panel frappe.patches.4_0.file_manager_hooks From 4a39f50106bc5c341a48df2d70d08317bf3af32c Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Wed, 9 Apr 2014 12:49:33 +0530 Subject: [PATCH 14/15] rename field only if it exists in profile to user patch --- frappe/patches/4_0/rename_profile_to_user.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/patches/4_0/rename_profile_to_user.py b/frappe/patches/4_0/rename_profile_to_user.py index 6c2b984429..66b8752da6 100644 --- a/frappe/patches/4_0/rename_profile_to_user.py +++ b/frappe/patches/4_0/rename_profile_to_user.py @@ -1,6 +1,7 @@ import frappe from frappe.model import rename_field +from frappe.model.meta import get_table_columns def execute(): tables = frappe.db.sql_list("show tables") @@ -9,6 +10,7 @@ def execute(): if frappe.db.exists("DocType", "Website Route Permission"): frappe.reload_doc("website", "doctype", "website_route_permission") - rename_field("Website Route Permission", "profile", "user") + if "profile" in get_table_columns("Website Route Permission"): + rename_field("Website Route Permission", "profile", "user") frappe.reload_doc("website", "doctype", "blogger") rename_field("Blogger", "profile", "user") From bad85591acf4cade294d5c4bafd82b5a1f9ec966 Mon Sep 17 00:00:00 2001 From: Pratik Vyas Date: Wed, 9 Apr 2014 16:02:32 +0530 Subject: [PATCH 15/15] add filemanager tests --- frappe/tests/test_filemanager.py | 80 ++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 frappe/tests/test_filemanager.py diff --git a/frappe/tests/test_filemanager.py b/frappe/tests/test_filemanager.py new file mode 100644 index 0000000000..45207d8857 --- /dev/null +++ b/frappe/tests/test_filemanager.py @@ -0,0 +1,80 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +import frappe +import os +import unittest + +from frappe.utils.file_manager import save_file, get_file, get_files_path + +test_content1 = 'Hello' +test_content2 = 'Hello World' + +def make_test_doc(): + d = frappe.new_doc('ToDo') + d.description = 'Test' + d.save() + return d.doctype, d.name + +class TestSimpleFile(unittest.TestCase): + + def setUp(self): + self.attached_to_doctype, self.attached_to_docname = make_test_doc() + self.test_content = test_content1 + self.saved_file = save_file('hello.txt', self.test_content, self.attached_to_doctype, self.attached_to_docname) + self.saved_filename = get_files_path(self.saved_file.file_name) + + def test_save(self): + filename, content = get_file(self.saved_file.name) + self.assertEqual(content, self.test_content) + + def tearDown(self): + # File gets deleted on rollback, so blank + pass + + +class TestSameFileName(unittest.TestCase): + + def setUp(self): + self.attached_to_doctype, self.attached_to_docname = make_test_doc() + self.test_content1 = test_content1 + self.test_content2 = test_content2 + self.saved_file1 = save_file('hello.txt', self.test_content1, self.attached_to_doctype, self.attached_to_docname) + self.saved_file2 = save_file('hello.txt', self.test_content2, self.attached_to_doctype, self.attached_to_docname) + self.saved_filename1 = get_files_path(self.saved_file1.file_name) + self.saved_filename2 = get_files_path(self.saved_file2.file_name) + + def test_saved_content(self): + filename1, content1 = get_file(self.saved_file1.name) + self.assertEqual(content1, self.test_content1) + filename2, content2 = get_file(self.saved_file2.name) + self.assertEqual(content2, self.test_content2) + + def tearDown(self): + # File gets deleted on rollback, so blank + pass + + +class TestSameContent(unittest.TestCase): + + def setUp(self): + self.attached_to_doctype1, self.attached_to_docname1 = make_test_doc() + self.attached_to_doctype2, self.attached_to_docname2 = make_test_doc() + self.test_content1 = test_content1 + self.test_content2 = test_content1 + self.orig_filename = 'hello.txt' + self.dup_filename = 'hello2.txt' + self.saved_file1 = save_file(self.orig_filename, self.test_content1, self.attached_to_doctype1, self.attached_to_docname1) + self.saved_file2 = save_file(self.dup_filename, self.test_content2, self.attached_to_doctype2, self.attached_to_docname2) + self.saved_filename1 = get_files_path(self.saved_file1.file_name) + self.saved_filename2 = get_files_path(self.saved_file2.file_name) + + def test_saved_content(self): + filename1, content1 = get_file(self.saved_file1.name) + filename2, content2 = get_file(self.saved_file2.name) + self.assertEqual(filename1, filename2) + self.assertFalse(os.path.exists(get_files_path(self.dup_filename))) + + def tearDown(self): + # File gets deleted on rollback, so blank + pass