Merge pull request #482 from pdvyas/filemanager-hooks-new
hooked filemanager!
This commit is contained in:
commit
d8c8f3fdfa
13 changed files with 435 additions and 151 deletions
|
|
@ -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": 1,
|
||||
"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": "content_hash",
|
||||
"fieldtype": "Data",
|
||||
"hidden": null,
|
||||
"ignore_restrictions": null,
|
||||
"in_filter": null,
|
||||
"in_list_view": null,
|
||||
"label": "Content 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 17:01:13.295614",
|
||||
"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
|
||||
}
|
||||
|
|
@ -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):
|
||||
|
|
@ -19,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]
|
||||
|
|
@ -34,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,13 +45,8 @@ 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)
|
||||
{"content_hash": self.content_hash, "name": ["!=", self.name]}):
|
||||
delete_file_data_content(self)
|
||||
|
||||
def on_rollback(self):
|
||||
self.on_trash()
|
||||
self.on_trash()
|
||||
|
|
|
|||
|
|
@ -48,3 +48,6 @@ 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
|
||||
write_file_keys = file_name
|
||||
|
|
|
|||
|
|
@ -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""" \
|
||||
|
|
|
|||
|
|
@ -16,11 +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
|
||||
|
|
|
|||
27
frappe/patches/4_0/file_manager_hooks.py
Normal file
27
frappe/patches/4_0/file_manager_hooks.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# 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
|
||||
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"""):
|
||||
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.file_url = os.path.normpath('/' + old_file_name)
|
||||
else:
|
||||
b.file_url = os.path.normpath('/files/' + old_file_name)
|
||||
_file_name, content = get_file(name)
|
||||
b.content_hash = get_content_hash(content)
|
||||
b.save()
|
||||
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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<file_names.length; i++) {
|
||||
this.add_attachment(file_names[i], attachments);
|
||||
}
|
||||
if(attachments.length) {
|
||||
attachments.forEach(function(attachment) {
|
||||
that.add_attachment(attachment)
|
||||
});
|
||||
} else {
|
||||
$('<p class="text-muted">' + frappe._("None") + '</p>').appendTo(this.$list);
|
||||
}
|
||||
|
|
@ -58,18 +59,23 @@ 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;
|
||||
if (!file_name) {
|
||||
file_name = file_url;
|
||||
}
|
||||
|
||||
var me = this;
|
||||
var $attach = $(repl('<div class="alert alert-info" style="margin-bottom: 7px">\
|
||||
<span style="display: inline-block; width: 90%; \
|
||||
text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">\
|
||||
<i class="icon-file"></i> <a href="%(href)s"\
|
||||
target="_blank" title="%(filename)s">%(filename)s</a></span><a href="#" class="close">×</a>\
|
||||
<i class="icon-file"></i> <a href="%(file_url)s"\
|
||||
target="_blank" title="%(file_name)s">%(file_name)s</a></span><a href="#" class="close">×</a>\
|
||||
</div>', {
|
||||
filename: filename,
|
||||
href: frappe.utils.get_file_link(filename)
|
||||
file_name: file_name,
|
||||
file_url: file_url
|
||||
}))
|
||||
.appendTo(this.$list)
|
||||
|
||||
|
|
@ -83,7 +89,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 +137,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 +148,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 +188,4 @@ frappe.ui.form.Attachments = Class.extend({
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
80
frappe/tests/test_filemanager.py
Normal file
80
frappe/tests/test_filemanager.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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")
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import os, base64, re
|
||||
from frappe.utils import cstr, cint, get_site_path
|
||||
import hashlib
|
||||
import mimetypes
|
||||
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
|
||||
|
||||
class MaxFileSizeReachedError(frappe.ValidationError): pass
|
||||
|
||||
|
|
@ -31,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()
|
||||
|
|
@ -76,11 +83,12 @@ 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")
|
||||
# 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
|
||||
|
||||
return '<img src="{filename}"'.format(filename = filename)
|
||||
return '<img src="{file_url}"'.format(file_url=file_url)
|
||||
|
||||
if content:
|
||||
content = re.sub('<img\s*src=\s*["\'](data:[^"\']*)["\']', _save_file, content)
|
||||
|
|
@ -92,99 +100,49 @@ def save_file(fname, content, dt, dn, decode=False):
|
|||
if isinstance(content, unicode):
|
||||
content = content.encode("utf-8")
|
||||
content = base64.b64decode(content)
|
||||
|
||||
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)
|
||||
content_hash = get_content_hash(content)
|
||||
content_type = mimetypes.guess_type(fname)[0]
|
||||
fname = get_file_name(fname, content_hash[-6:])
|
||||
|
||||
fname_parts = fname.split(".", -1)
|
||||
main = ".".join(fname_parts[:-1])
|
||||
extn = fname_parts[-1]
|
||||
versions = get_file_versions(files_path, main, extn)
|
||||
|
||||
if versions:
|
||||
found_match = False
|
||||
for version in versions:
|
||||
if filecmp.cmp(os.path.join(files_path, version), temp_fname):
|
||||
# remove new file, already exists!
|
||||
os.remove(temp_fname)
|
||||
fname = version
|
||||
fpath = os.path.join(files_path, fname)
|
||||
found_match = True
|
||||
break
|
||||
|
||||
if not found_match:
|
||||
# get_new_version name
|
||||
fname = get_new_fname_based_on_version(files_path, main, extn, versions)
|
||||
fpath = os.path.join(files_path, fname)
|
||||
|
||||
# rename
|
||||
if os.path.exists(fpath.encode("utf-8")):
|
||||
frappe.throw("File already exists: " + fname)
|
||||
|
||||
os.rename(temp_fname, fpath.encode("utf-8"))
|
||||
else:
|
||||
fpath = os.path.join(files_path, fname)
|
||||
|
||||
# rename new file
|
||||
if os.path.exists(fpath.encode("utf-8")):
|
||||
frappe.throw("File already exists: " + fname)
|
||||
|
||||
os.rename(temp_fname, fpath.encode("utf-8"))
|
||||
method = get_hook_method('write_file', fallback=save_file_on_filesystem)
|
||||
|
||||
f = frappe.get_doc({
|
||||
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",
|
||||
"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
|
||||
"file_size": file_size,
|
||||
"content_hash": content_hash,
|
||||
})
|
||||
|
||||
f = frappe.get_doc(file_data)
|
||||
f.ignore_permissions = True
|
||||
try:
|
||||
f.insert();
|
||||
except frappe.DuplicateEntryError:
|
||||
return frappe.get_doc("File Data", f.duplicate_entry)
|
||||
|
||||
return f
|
||||
|
||||
def get_file_versions(files_path, main, extn):
|
||||
out = []
|
||||
for f in os.listdir(files_path):
|
||||
f = cstr(f)
|
||||
if f.startswith(main) and f.endswith(extn):
|
||||
out.append(f)
|
||||
return out
|
||||
|
||||
def get_new_fname_based_on_version(files_path, main, extn, versions):
|
||||
versions.sort()
|
||||
if "-" in versions[-1]:
|
||||
version = cint(versions[-1].split("-")[-1]) or 1
|
||||
else:
|
||||
version = 1
|
||||
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.get_doc('File Data', name)
|
||||
return {k:b.get(k) for k in frappe.get_hooks()['write_file_keys']}
|
||||
return False
|
||||
|
||||
new_fname = main + "-" + str(version) + "." + extn
|
||||
while os.path.exists(os.path.join(files_path, new_fname).encode("utf-8")):
|
||||
version += 1
|
||||
new_fname = main + "-" + str(version) + "." + extn
|
||||
if version > 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
|
||||
def save_file_on_filesystem(fname, content, content_type=None):
|
||||
import filecmp
|
||||
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': os.path.basename(path),
|
||||
'file_url': '/' + path
|
||||
}
|
||||
|
||||
def check_max_file_size(content):
|
||||
max_file_size = conf.get('max_file_size') or 1000000
|
||||
|
|
@ -196,17 +154,14 @@ 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)
|
||||
frappe.create_folder(files_path)
|
||||
fname = os.path.join(files_path, frappe.generate_hash())
|
||||
|
||||
# create directory (if not exists)
|
||||
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"""
|
||||
|
|
@ -220,6 +175,19 @@ 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(doc):
|
||||
method = get_hook_method('delete_file_data_content', fallback=delete_file_from_filesystem)
|
||||
method(doc)
|
||||
|
||||
def delete_file_from_filesystem(doc):
|
||||
path = doc.file_name
|
||||
if path.startswith("files/"):
|
||||
path = frappe.utils.get_site_path("public", doc.file_name)
|
||||
else:
|
||||
path = frappe.utils.get_site_path("public", "files", 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 +205,14 @@ def get_file(fname):
|
|||
content = f.read()
|
||||
|
||||
return [file_name, content]
|
||||
|
||||
def get_content_hash(content):
|
||||
return hashlib.md5(content).hexdigest()
|
||||
|
||||
def get_file_name(fname, optional_suffix):
|
||||
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)
|
||||
return fname
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
@ -88,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[f.file_url or f.file_name] = f.name
|
||||
attachments.append({
|
||||
'name': f.name,
|
||||
'file_url': f.file_url,
|
||||
'file_name': f.file_name
|
||||
})
|
||||
|
||||
return attachments
|
||||
|
||||
|
|
@ -122,4 +127,4 @@ def get_badge_info(doctypes, filters):
|
|||
for doctype in doctypes:
|
||||
out[doctype] = frappe.db.get_value(doctype, filters, "count(*)")
|
||||
|
||||
return out
|
||||
return out
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue