[enhancement] document management systemversion-14
@@ -38,9 +38,12 @@ class _dict(dict): | |||||
def copy(self): | def copy(self): | ||||
return _dict(dict(self).copy()) | return _dict(dict(self).copy()) | ||||
def _(msg): | |||||
def _(msg, lang=None): | |||||
"""Returns translated string in current lang, if exists.""" | """Returns translated string in current lang, if exists.""" | ||||
if local.lang == "en": | |||||
if not lang: | |||||
lang = local.lang | |||||
if lang == "en": | |||||
return msg | return msg | ||||
from frappe.translate import get_full_dict | from frappe.translate import get_full_dict | ||||
@@ -905,6 +908,20 @@ def get_all(doctype, *args, **kwargs): | |||||
kwargs["limit_page_length"] = 0 | kwargs["limit_page_length"] = 0 | ||||
return get_list(doctype, *args, **kwargs) | return get_list(doctype, *args, **kwargs) | ||||
def get_value(*args, **kwargs): | |||||
"""Returns a document property or list of properties. | |||||
Alias for `frappe.db.get_value` | |||||
:param doctype: DocType name. | |||||
:param filters: Filters like `{"x":"y"}` or name of the document. `None` if Single DocType. | |||||
:param fieldname: Column name. | |||||
:param ignore: Don't raise exception if table, column is missing. | |||||
:param as_dict: Return values as dict. | |||||
:param debug: Print query in error log. | |||||
""" | |||||
return db.get_value(*args, **kwargs) | |||||
def add_version(doc): | def add_version(doc): | ||||
"""Insert a new **Version** of the given document. | """Insert a new **Version** of the given document. | ||||
A **Version** is a JSON dump of the current document state.""" | A **Version** is a JSON dump of the current document state.""" | ||||
@@ -53,6 +53,7 @@ def get_bootinfo(): | |||||
load_conf_settings(bootinfo) | load_conf_settings(bootinfo) | ||||
load_print(bootinfo, doclist) | load_print(bootinfo, doclist) | ||||
doclist.extend(get_meta_bundle("Page")) | doclist.extend(get_meta_bundle("Page")) | ||||
bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1}) | |||||
# ipinfo | # ipinfo | ||||
if frappe.session['data'].get('ipinfo'): | if frappe.session['data'].get('ipinfo'): | ||||
@@ -45,6 +45,15 @@ def get_data(): | |||||
"link": "List/Note", | "link": "List/Note", | ||||
"type": "list" | "type": "list" | ||||
}, | }, | ||||
"File Manager": { | |||||
"color": "#905df5", | |||||
"doctype": "File", | |||||
"icon": "icon-folder-close", | |||||
"icon": "octicon octicon-file-directory", | |||||
"label": _("File Manager"), | |||||
"link": "List/File", | |||||
"type": "list" | |||||
}, | |||||
"Website": { | "Website": { | ||||
"color": "#16a085", | "color": "#16a085", | ||||
"icon": "icon-globe", | "icon": "icon-globe", | ||||
@@ -100,7 +100,7 @@ def get_data(): | |||||
}, | }, | ||||
{ | { | ||||
"type": "doctype", | "type": "doctype", | ||||
"name": "File Data", | |||||
"name": "File", | |||||
"description": _("Manage uploaded files.") | "description": _("Manage uploaded files.") | ||||
} | } | ||||
] | ] | ||||
@@ -0,0 +1,21 @@ | |||||
frappe.ui.form.on("File", "refresh", function(frm) { | |||||
if(!frm.doc.is_folder) { | |||||
frm.add_custom_button(__('Download'), function() { | |||||
window.open(frm.doc.file_url); | |||||
}, "icon-download"); | |||||
} | |||||
var wrapper = frm.get_field("preview_html").$wrapper; | |||||
var is_viewable = frappe.utils.is_image_file(frm.doc.file_url); | |||||
frm.toggle_display("preview", is_viewable); | |||||
frm.toggle_display("preview_html", is_viewable); | |||||
if(is_viewable){ | |||||
wrapper.html('<div class="img_preview">\ | |||||
<img class="img-responsive" src="'+frm.doc.file_url+'"></img>\ | |||||
</div>'); | |||||
} else { | |||||
wrapper.empty(); | |||||
} | |||||
}); |
@@ -0,0 +1,486 @@ | |||||
{ | |||||
"allow_copy": 0, | |||||
"allow_import": 1, | |||||
"allow_rename": 0, | |||||
"autoname": "hash", | |||||
"creation": "2012-12-12 11:19:22", | |||||
"custom": 0, | |||||
"docstatus": 0, | |||||
"doctype": "DocType", | |||||
"fields": [ | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "file_name", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "File Name", | |||||
"no_copy": 0, | |||||
"oldfieldname": "file_name", | |||||
"oldfieldtype": "Data", | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "preview", | |||||
"fieldtype": "Section Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Preview", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "preview_html", | |||||
"fieldtype": "HTML", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Preview HTML", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "section_break_5", | |||||
"fieldtype": "Section Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"depends_on": "", | |||||
"fieldname": "is_home_folder", | |||||
"fieldtype": "Check", | |||||
"hidden": 1, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Is Home Folder", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "is_attachments_folder", | |||||
"fieldtype": "Check", | |||||
"hidden": 1, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Is Attachments Folder", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "file_size", | |||||
"fieldtype": "Int", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "File Size", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "column_break_5", | |||||
"fieldtype": "Column Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"depends_on": "eval:!doc.is_folder", | |||||
"fieldname": "file_url", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "File URL", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "folder", | |||||
"fieldtype": "Link", | |||||
"hidden": 1, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Folder", | |||||
"no_copy": 0, | |||||
"options": "File", | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "is_folder", | |||||
"fieldtype": "Check", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Is Folder", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"depends_on": "eval:!doc.is_folder", | |||||
"fieldname": "section_break_8", | |||||
"fieldtype": "Section Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "attached_to_doctype", | |||||
"fieldtype": "Link", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Attached To DocType", | |||||
"no_copy": 0, | |||||
"options": "DocType", | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 1, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "column_break_10", | |||||
"fieldtype": "Column Break", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "attached_to_name", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Attached To Name", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 1, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "content_hash", | |||||
"fieldtype": "Data", | |||||
"hidden": 1, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Content Hash", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 1, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "lft", | |||||
"fieldtype": "Int", | |||||
"hidden": 1, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "lft", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "rgt", | |||||
"fieldtype": "Int", | |||||
"hidden": 1, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "rgt", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "old_parent", | |||||
"fieldtype": "Data", | |||||
"hidden": 1, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "old_parent", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"precision": "", | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
} | |||||
], | |||||
"hide_heading": 0, | |||||
"hide_toolbar": 0, | |||||
"icon": "icon-file", | |||||
"idx": 1, | |||||
"in_create": 0, | |||||
"in_dialog": 0, | |||||
"is_submittable": 0, | |||||
"issingle": 0, | |||||
"istable": 0, | |||||
"modified": "2015-09-18 06:22:10.902847", | |||||
"modified_by": "Administrator", | |||||
"module": "Core", | |||||
"name": "File", | |||||
"owner": "Administrator", | |||||
"permissions": [ | |||||
{ | |||||
"amend": 0, | |||||
"apply_user_permissions": 0, | |||||
"cancel": 0, | |||||
"create": 1, | |||||
"delete": 1, | |||||
"email": 1, | |||||
"export": 1, | |||||
"if_owner": 0, | |||||
"import": 1, | |||||
"permlevel": 0, | |||||
"print": 1, | |||||
"read": 1, | |||||
"report": 1, | |||||
"role": "System Manager", | |||||
"set_user_permissions": 0, | |||||
"share": 1, | |||||
"submit": 0, | |||||
"write": 1 | |||||
}, | |||||
{ | |||||
"amend": 0, | |||||
"apply_user_permissions": 0, | |||||
"cancel": 0, | |||||
"create": 1, | |||||
"delete": 1, | |||||
"email": 1, | |||||
"export": 1, | |||||
"if_owner": 1, | |||||
"import": 0, | |||||
"permlevel": 0, | |||||
"print": 1, | |||||
"read": 1, | |||||
"report": 1, | |||||
"role": "All", | |||||
"set_user_permissions": 0, | |||||
"share": 1, | |||||
"submit": 0, | |||||
"write": 1 | |||||
} | |||||
], | |||||
"read_only": 0, | |||||
"read_only_onload": 0, | |||||
"title_field": "file_name" | |||||
} |
@@ -0,0 +1,192 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# MIT License. See license.txt | |||||
from __future__ import unicode_literals | |||||
""" | |||||
record of files | |||||
naming for same name files: file.gif, file-1.gif, file-2.gif etc | |||||
""" | |||||
import frappe, frappe.utils | |||||
from frappe.utils.file_manager import delete_file_data_content | |||||
from frappe import _ | |||||
from frappe.utils.nestedset import NestedSet | |||||
import json | |||||
class FolderNotEmpty(frappe.ValidationError): pass | |||||
class File(NestedSet): | |||||
nsm_parent_field = 'folder' | |||||
no_feed_on_delete = True | |||||
def before_insert(self): | |||||
frappe.local.rollback_observers.append(self) | |||||
self.set_folder_name() | |||||
self.set_name() | |||||
def get_name_based_on_parent_folder(self): | |||||
path = get_breadcrumbs(self.folder) | |||||
folder_name = frappe.get_value("File", self.folder, "file_name") | |||||
return "/".join([d.file_name for d in path] + [folder_name, self.file_name]) | |||||
def set_name(self): | |||||
"""Set name for folder""" | |||||
if self.is_folder: | |||||
if self.folder: | |||||
self.name = self.get_name_based_on_parent_folder() | |||||
else: | |||||
# home | |||||
self.name = self.file_name | |||||
else: | |||||
self.name = self.file_url | |||||
def after_insert(self): | |||||
self.update_parent_folder_size() | |||||
def after_rename(self, olddn, newdn, merge=False): | |||||
for successor in self.get_successor(): | |||||
setup_folder_path(successor, self.name) | |||||
def get_successor(self): | |||||
return frappe.db.sql_list("select name from tabFile where folder='%s'"%self.name) or [] | |||||
def validate(self): | |||||
self.validate_duplicate_entry() | |||||
self.validate_folder() | |||||
self.set_folder_size() | |||||
def set_folder_size(self): | |||||
"""Set folder size if folder""" | |||||
if self.is_folder and not self.is_new(): | |||||
self.file_size = self.get_folder_size() | |||||
frappe.db.set_value("File", self.name, "file_size", self.file_size) | |||||
for folder in self.get_ancestors(): | |||||
frappe.db.set_value("File", folder, "file_size", self.get_folder_size(folder)) | |||||
def get_folder_size(self, folder=None): | |||||
"""Returns folder size for current folder""" | |||||
if not folder: | |||||
folder = self.name | |||||
file_size = frappe.db.sql("""select sum(ifnull(file_size,0)) | |||||
from tabFile where folder=%s """, (folder))[0][0] | |||||
return file_size | |||||
def update_parent_folder_size(self): | |||||
"""Update size of parent folder""" | |||||
if self.folder and not self.is_folder: # it not home | |||||
frappe.get_doc("File", self.folder).save() | |||||
def set_folder_name(self): | |||||
"""Make parent folders if not exists based on reference doctype and name""" | |||||
if self.attached_to_doctype and not self.folder: | |||||
self.folder = frappe.db.get_value("File", {"is_attachments_folder": 1}) | |||||
def validate_folder(self): | |||||
if not self.is_home_folder and not self.folder and \ | |||||
not self.flags.ignore_folder_validate: | |||||
frappe.throw(_("Folder is mandatory")) | |||||
def validate_duplicate_entry(self): | |||||
if not self.flags.ignore_duplicate_entry_error: | |||||
# check duplicate assignement | |||||
n_records = frappe.db.sql("""select name from `tabFile` | |||||
where content_hash=%s | |||||
and name!=%s | |||||
and attached_to_doctype=%s | |||||
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] | |||||
frappe.throw(frappe._("Same file has already been attached to the record"), frappe.DuplicateEntryError) | |||||
def on_trash(self): | |||||
self.check_folder_is_empty() | |||||
self.check_reference_doc_permission() | |||||
super(File, self).on_trash() | |||||
self.delete_file() | |||||
def after_delete(self): | |||||
self.update_parent_folder_size() | |||||
def check_folder_is_empty(self): | |||||
"""Throw exception if folder is not empty""" | |||||
if self.is_folder and frappe.get_all("File", filters={"folder": self.name}): | |||||
frappe.throw(_("Folder {0} is not empty").format(self.name), FolderNotEmpty) | |||||
def check_reference_doc_permission(self): | |||||
"""Check if permission exists for reference document""" | |||||
if self.attached_to_name: | |||||
# check persmission | |||||
try: | |||||
if not self.flags.ignore_permissions and \ | |||||
not frappe.has_permission(self.attached_to_doctype, | |||||
"write", self.attached_to_name): | |||||
frappe.throw(frappe._("No permission to write / remove."), | |||||
frappe.PermissionError) | |||||
except frappe.DoesNotExistError: | |||||
pass | |||||
def delete_file(self): | |||||
"""If file not attached to any other record, delete it""" | |||||
if self.file_name and self.content_hash and (not frappe.db.count("File", | |||||
{"content_hash": self.content_hash, "name": ["!=", self.name]})): | |||||
delete_file_data_content(self) | |||||
def on_rollback(self): | |||||
self.on_trash() | |||||
def on_doctype_update(): | |||||
frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"]) | |||||
def make_home_folder(): | |||||
home = frappe.get_doc({ | |||||
"doctype": "File", | |||||
"is_folder": 1, | |||||
"is_home_folder": 1, | |||||
"file_name": _("Home") | |||||
}).insert() | |||||
frappe.get_doc({ | |||||
"doctype": "File", | |||||
"folder": home.name, | |||||
"is_folder": 1, | |||||
"is_attachments_folder": 1, | |||||
"file_name": _("Attachments") | |||||
}).insert() | |||||
@frappe.whitelist() | |||||
def get_breadcrumbs(folder): | |||||
"""returns name, file_name of parent folder""" | |||||
lft, rgt = frappe.db.get_value("File", folder, ["lft", "rgt"]) | |||||
return frappe.db.sql("""select name, file_name from tabFile | |||||
where lft < %s and rgt > %s order by lft asc""", (lft, rgt), as_dict=1) | |||||
@frappe.whitelist() | |||||
def create_new_folder(file_name, folder): | |||||
""" create new folder under current parent folder """ | |||||
file = frappe.new_doc("File") | |||||
file.file_name = file_name | |||||
file.is_folder = 1 | |||||
file.folder = folder | |||||
file.insert() | |||||
@frappe.whitelist() | |||||
def move_file(file_list, new_parent, old_parent): | |||||
for file_obj in json.loads(file_list): | |||||
setup_folder_path(file_obj.get("name"), new_parent) | |||||
# recalculate sizes | |||||
frappe.get_doc("File", old_parent).save() | |||||
frappe.get_doc("File", new_parent).save() | |||||
def setup_folder_path(filename, new_parent): | |||||
file = frappe.get_doc("File", filename) | |||||
file.folder = new_parent | |||||
file.save() | |||||
if file.is_folder: | |||||
frappe.rename_doc("File", file.name, file.get_name_based_on_parent_folder(), ignore_permissions=True) |
@@ -0,0 +1,207 @@ | |||||
frappe.provide("frappe.ui"); | |||||
frappe.listview_settings['File'] = { | |||||
hide_name_column: true, | |||||
use_route: true, | |||||
add_fields: ["is_folder", "file_name", "file_url", "folder"], | |||||
formatters: { | |||||
file_size: function(value) { | |||||
// formatter for file size | |||||
if(value > 1048576) { | |||||
value = flt(flt(value) / 1048576, 1) + "M"; | |||||
} else if (value > 1024) { | |||||
value = flt(flt(value) / 1024, 1) + "K"; | |||||
} | |||||
return value; | |||||
} | |||||
}, | |||||
prepare_data: function(data) { | |||||
// set image icons | |||||
if(data.is_folder) { | |||||
data._title = '<i class="icon-folder-close-alt icon-fixed-width"></i> ' + data.file_name; | |||||
} else if(frappe.utils.is_image_file(data.file_name)) { | |||||
data._title = '<i class="icon-picture icon-fixed-width"></i> ' + data.file_name; | |||||
} else { | |||||
data._title = '<i class="icon-file-alt icon-fixed-width"></i> \ | |||||
' + (data.file_name ? data.file_name : data.file_url); | |||||
} | |||||
}, | |||||
onload: function(doclist) { | |||||
doclist.filter_area = doclist.wrapper.find(".show_filters"); | |||||
doclist.breadcrumb = $('<ol class="breadcrumb for-file-list"></ol>') | |||||
.insertBefore(doclist.filter_area); | |||||
doclist.listview.settings.setup_menu(doclist); | |||||
doclist.listview.settings.setup_dragdrop(doclist); | |||||
doclist.$page.on("click", ".list-delete", function(event) { | |||||
doclist.listview.settings.add_menu_item_copy(doclist); | |||||
}) | |||||
}, | |||||
list_view_doc:function(doclist){ | |||||
$(doclist.wrapper).on("click", 'button[list_view_doc="'+doclist.doctype+'"]', function(){ | |||||
dialog = frappe.ui.get_upload_dialog({ | |||||
"args": { | |||||
"folder": doclist.current_folder, | |||||
"from_form": 1 | |||||
}, | |||||
callback: function() { | |||||
doclist.refresh(); | |||||
} | |||||
}); | |||||
}); | |||||
}, | |||||
setup_menu: function(doclist) { | |||||
doclist.page.add_menu_item(__("New Folder"), function() { | |||||
var d = frappe.prompt(__("Name"), function(values) { | |||||
if((values.value.indexOf("/") > -1)){ | |||||
frappe.throw("Folder name should not include / !!!"); | |||||
return; | |||||
} | |||||
var data = { | |||||
"file_name": values.value, | |||||
"folder": doclist.current_folder | |||||
}; | |||||
frappe.call({ | |||||
method: "frappe.core.doctype.file.file.create_new_folder", | |||||
args: data, | |||||
callback: function(r) { } | |||||
}) | |||||
}, __('Enter folder name'), __("Create")); | |||||
}); | |||||
doclist.page.add_menu_item(__("Edit Folder"), function() { | |||||
frappe.set_route("Form", "File", doclist.current_folder); | |||||
}); | |||||
}, | |||||
setup_dragdrop: function(doclist) { | |||||
$(doclist.$page).on('dragenter dragover', false) | |||||
.on('drop', function (e) { | |||||
var dataTransfer = e.originalEvent.dataTransfer; | |||||
if (!(dataTransfer && dataTransfer.files && dataTransfer.files.length > 0)) { | |||||
return; | |||||
} | |||||
e.stopPropagation(); | |||||
e.preventDefault(); | |||||
frappe.upload.upload_file(dataTransfer.files[0], { | |||||
"folder": doclist.current_folder, | |||||
"from_form": 1 | |||||
}, {}); | |||||
}); | |||||
}, | |||||
add_menu_item_copy: function(doclist){ | |||||
if (!doclist.copy) { | |||||
var copy_menu = doclist.page.add_menu_item(__("Copy"), function() { | |||||
if(doclist.$page.find(".list-delete:checked").length){ | |||||
doclist.selected_files = doclist.get_checked_items(); | |||||
doclist.old_parent = doclist.current_folder; | |||||
doclist.listview.settings.add_menu_item_paste(doclist); | |||||
} | |||||
else{ | |||||
frappe.throw("Please select file to copy"); | |||||
} | |||||
}) | |||||
doclist.copy = true; | |||||
} | |||||
}, | |||||
add_menu_item_paste:function(doclist){ | |||||
var paste_menu = doclist.page.add_menu_item(__("Paste"), function(){ | |||||
frappe.call({ | |||||
method:"frappe.core.doctype.file.file.move_file", | |||||
args: { | |||||
"file_list": doclist.selected_files, | |||||
"new_parent": doclist.current_folder, | |||||
"old_parent": doclist.old_parent | |||||
}, | |||||
callback:function(r){ | |||||
doclist.paste = false; | |||||
frappe.msgprint(__(r.message)); | |||||
doclist.selected_files = []; | |||||
$(paste_menu).remove(); | |||||
} | |||||
}) | |||||
}) | |||||
}, | |||||
before_run: function(doclist) { | |||||
var name_filter = doclist.filter_list.get_filter("file_name"); | |||||
if(name_filter) { | |||||
doclist.filter_area.removeClass("hide"); | |||||
doclist.breadcrumb.addClass("hide"); | |||||
} else { | |||||
doclist.filter_area.addClass("hide"); | |||||
doclist.breadcrumb.removeClass("hide"); | |||||
} | |||||
}, | |||||
refresh: function(doclist) { | |||||
// set folder before querying | |||||
var name_filter = doclist.filter_list.get_filter("file_name"); | |||||
var folder_filter = doclist.filter_list.get_filter("folder"); | |||||
if(folder_filter) { | |||||
folder_filter.remove(true); | |||||
} | |||||
if(name_filter) return; | |||||
var route = frappe.get_route(); | |||||
if(route[2]) { | |||||
doclist.current_folder = route.slice(2).join("/"); | |||||
doclist.current_folder_name = route.slice(-1)[0]; | |||||
} | |||||
if(!doclist.current_folder) { | |||||
doclist.current_folder = frappe.boot.home_folder; | |||||
doclist.current_folder_name = __("Home"); | |||||
} | |||||
doclist.filter_list.add_filter("File", "folder", "=", doclist.current_folder, true); | |||||
doclist.dirty = true; | |||||
doclist.fresh = false; | |||||
doclist.page.set_title(doclist.current_folder_name); | |||||
frappe.utils.set_title(doclist.current_folder_name); | |||||
}, | |||||
set_primary_action:function(doclist){ | |||||
doclist.page.clear_primary_action(); | |||||
doclist.page.set_primary_action(__("New"), function() { | |||||
dialog = frappe.ui.get_upload_dialog({ | |||||
"args": { | |||||
"folder": doclist.current_folder, | |||||
"from_form": 1 | |||||
}, | |||||
callback: function() { | |||||
doclist.refresh(); | |||||
} | |||||
}); | |||||
}, "octicon octicon-plus"); | |||||
}, | |||||
post_render_item: function(list, row, data) { | |||||
if(data.is_folder) { | |||||
$(row).find(".list-id").attr("href", "#List/File/" + data.name); | |||||
} | |||||
}, | |||||
set_file_route: function(name) { | |||||
frappe.set_route(["List", "File"].concat(decodeURIComponent(name).split("/"))); | |||||
}, | |||||
post_render: function(doclist) { | |||||
frappe.call({ | |||||
method: "frappe.core.doctype.file.file.get_breadcrumbs", | |||||
args: { | |||||
folder: doclist.current_folder | |||||
}, | |||||
callback: function(r) { | |||||
doclist.breadcrumb.empty(); | |||||
if(r.message && r.message.length) { | |||||
$.each(r.message, function(i, folder) { | |||||
$('<li><a href="#List/File/'+folder.name+'">' | |||||
+ folder.file_name+'</a></li>') | |||||
.appendTo(doclist.breadcrumb); | |||||
}); | |||||
} | |||||
$('<li class="active">'+ doclist.current_folder_name+'</li>') | |||||
.appendTo(doclist.breadcrumb); | |||||
} | |||||
}); | |||||
} | |||||
} |
@@ -0,0 +1,106 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# See license.txt | |||||
from __future__ import unicode_literals | |||||
import frappe | |||||
import unittest | |||||
from frappe.utils.file_manager import save_file, get_file, get_files_path | |||||
from frappe import _ | |||||
from frappe.core.doctype.file.file import move_file | |||||
import json | |||||
# test_records = frappe.get_test_records('File') | |||||
class TestFile(unittest.TestCase): | |||||
def setUp(self): | |||||
self.delete_test_data() | |||||
self.upload_file() | |||||
def delete_test_data(self): | |||||
for file_name in ["folder_copy.txt", "file_copy.txt", "Test Folder 2"]: | |||||
file_name = frappe.db.get_value("File", {"file_name": file_name}, "name") | |||||
if file_name: | |||||
file = frappe.get_doc("File", file_name) | |||||
ancestors = file.get_ancestors() | |||||
file.delete() | |||||
self.delete_ancestors(ancestors) | |||||
def delete_ancestors(self, ancestors): | |||||
for folder in ancestors: | |||||
if folder != "Home": | |||||
folder = frappe.get_doc("File", folder) | |||||
folder.delete() | |||||
def upload_file(self): | |||||
self.saved_file = save_file('file_copy.txt', "Testing file copy example.",\ | |||||
"", "", self.get_folder("Test Folder 1", "Home").name) | |||||
self.saved_filename = get_files_path(self.saved_file.file_name) | |||||
def get_folder(self, folder_name, parent_folder="Home"): | |||||
return frappe.get_doc({ | |||||
"doctype": "File", | |||||
"file_name": _(folder_name), | |||||
"is_folder": 1, | |||||
"folder": _(parent_folder) | |||||
}).insert() | |||||
def tests_after_upload(self): | |||||
self.assertEqual(self.saved_file.folder, _("Home/Test Folder 1")) | |||||
folder_size = frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size") | |||||
saved_file_size = frappe.db.get_value("File", self.saved_file.name, "file_size") | |||||
self.assertEqual(folder_size, saved_file_size) | |||||
def test_file_copy(self): | |||||
folder = self.get_folder("Test Folder 2", "Home") | |||||
file = frappe.get_doc("File", "/files/file_copy.txt") | |||||
file_dict = [{"name": file.name}] | |||||
move_file(json.dumps(file_dict), folder.name, file.folder) | |||||
file = frappe.get_doc("File", "/files/file_copy.txt") | |||||
self.assertEqual(_("Home/Test Folder 2"), file.folder) | |||||
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), file.file_size) | |||||
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), None) | |||||
def test_folder_copy(self): | |||||
folder = self.get_folder("Test Folder 2", "Home") | |||||
folder = self.get_folder("Test Folder 3", "Home/Test Folder 2") | |||||
self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name) | |||||
file_dict = [{"name": folder.name}] | |||||
move_file(json.dumps(file_dict), 'Home/Test Folder 1', folder.folder) | |||||
file = frappe.get_doc("File", "/files/folder_copy.txt") | |||||
self.assertEqual(_("Home/Test Folder 1/Test Folder 3"), file.folder) | |||||
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), file.file_size) | |||||
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 2"), "file_size"), None) | |||||
def test_non_parent_folder(self): | |||||
d = frappe.get_doc({ | |||||
"doctype": "File", | |||||
"file_name": _("Test_Folder"), | |||||
"is_folder": 1 | |||||
}) | |||||
self.assertRaises(frappe.ValidationError, d.save) | |||||
def test_on_delete(self): | |||||
file = frappe.get_doc("File", "/files/file_copy.txt") | |||||
file.delete() | |||||
self.assertEqual(frappe.db.get_value("File", _("Home/Test Folder 1"), "file_size"), None) | |||||
folder = self.get_folder("Test Folder 3", "Home/Test Folder 1") | |||||
self.saved_file = save_file('folder_copy.txt', "Testing folder copy example.", "", "", folder.name) | |||||
folder = frappe.get_doc("File", "Home/Test Folder 1/Test Folder 3") | |||||
self.assertRaises(frappe.ValidationError, folder.delete) | |||||
@@ -1,3 +0,0 @@ | |||||
File record and its relation to document to which the file is attached. | |||||
This is appened to the form as `docinfo` when a document is loaded. |
@@ -1,4 +0,0 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# MIT License. See license.txt | |||||
from __future__ import unicode_literals |
@@ -1,179 +0,0 @@ | |||||
{ | |||||
"allow_copy": 0, | |||||
"allow_import": 1, | |||||
"allow_rename": 0, | |||||
"autoname": "File.######", | |||||
"creation": "2012-12-12 11:19:22", | |||||
"custom": 0, | |||||
"docstatus": 0, | |||||
"doctype": "DocType", | |||||
"fields": [ | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "file_name", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "File Name", | |||||
"no_copy": 0, | |||||
"oldfieldname": "file_name", | |||||
"oldfieldtype": "Data", | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "file_url", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "File URL", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "attached_to_doctype", | |||||
"fieldtype": "Link", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "Attached To DocType", | |||||
"no_copy": 0, | |||||
"options": "DocType", | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 1, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "attached_to_name", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "Attached To Name", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 1, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "file_size", | |||||
"fieldtype": "Int", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 1, | |||||
"label": "File Size", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 1, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 0, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
}, | |||||
{ | |||||
"allow_on_submit": 0, | |||||
"bold": 0, | |||||
"collapsible": 0, | |||||
"fieldname": "content_hash", | |||||
"fieldtype": "Data", | |||||
"hidden": 0, | |||||
"ignore_user_permissions": 0, | |||||
"in_filter": 0, | |||||
"in_list_view": 0, | |||||
"label": "Content Hash", | |||||
"no_copy": 0, | |||||
"permlevel": 0, | |||||
"print_hide": 0, | |||||
"read_only": 0, | |||||
"report_hide": 0, | |||||
"reqd": 0, | |||||
"search_index": 1, | |||||
"set_only_once": 0, | |||||
"unique": 0 | |||||
} | |||||
], | |||||
"hide_heading": 0, | |||||
"hide_toolbar": 0, | |||||
"icon": "icon-file", | |||||
"idx": 1, | |||||
"in_create": 1, | |||||
"in_dialog": 0, | |||||
"is_submittable": 0, | |||||
"issingle": 0, | |||||
"istable": 0, | |||||
"modified": "2015-02-05 05:11:38.944926", | |||||
"modified_by": "Administrator", | |||||
"module": "Core", | |||||
"name": "File Data", | |||||
"owner": "Administrator", | |||||
"permissions": [ | |||||
{ | |||||
"amend": 0, | |||||
"apply_user_permissions": 0, | |||||
"cancel": 0, | |||||
"create": 1, | |||||
"delete": 1, | |||||
"email": 1, | |||||
"export": 0, | |||||
"if_owner": 0, | |||||
"import": 0, | |||||
"permlevel": 0, | |||||
"print": 1, | |||||
"read": 1, | |||||
"report": 0, | |||||
"role": "System Manager", | |||||
"set_user_permissions": 0, | |||||
"share": 1, | |||||
"submit": 0, | |||||
"write": 1 | |||||
} | |||||
], | |||||
"read_only": 0, | |||||
"read_only_onload": 0 | |||||
} |
@@ -1,57 +0,0 @@ | |||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||||
# MIT License. See license.txt | |||||
from __future__ import unicode_literals | |||||
""" | |||||
record of files | |||||
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): | |||||
no_feed_on_delete = True | |||||
def before_insert(self): | |||||
frappe.local.rollback_observers.append(self) | |||||
def validate(self): | |||||
if not self.flags.ignore_duplicate_entry_error: | |||||
# check duplicate assignement | |||||
n_records = frappe.db.sql("""select name from `tabFile Data` | |||||
where content_hash=%s | |||||
and name!=%s | |||||
and attached_to_doctype=%s | |||||
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] | |||||
frappe.throw(frappe._("Same file has already been attached to the record"), frappe.DuplicateEntryError) | |||||
def on_trash(self): | |||||
if self.attached_to_name: | |||||
# check persmission | |||||
try: | |||||
if not self.flags.ignore_permissions 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) | |||||
except frappe.DoesNotExistError: | |||||
pass | |||||
# if file not attached to any other record, delete it | |||||
if self.file_name and self.content_hash and (not frappe.db.count("File Data", | |||||
{"content_hash": self.content_hash, "name": ["!=", self.name]})): | |||||
delete_file_data_content(self) | |||||
def on_rollback(self): | |||||
self.on_trash() | |||||
def on_doctype_update(): | |||||
frappe.db.add_index("File Data", ["attached_to_doctype", "attached_to_name"]) | |||||
@@ -85,16 +85,18 @@ class Page(Document): | |||||
from frappe.modules import get_module_path, scrub | from frappe.modules import get_module_path, scrub | ||||
import os | import os | ||||
path = os.path.join(get_module_path(self.module), 'page', scrub(self.name)) | |||||
page_name = scrub(self.name) | |||||
path = os.path.join(get_module_path(self.module), 'page', page_name) | |||||
# script | # script | ||||
fpath = os.path.join(path, scrub(self.name) + '.js') | |||||
fpath = os.path.join(path, page_name + '.js') | |||||
if os.path.exists(fpath): | if os.path.exists(fpath): | ||||
with open(fpath, 'r') as f: | with open(fpath, 'r') as f: | ||||
self.script = unicode(f.read(), "utf-8") | self.script = unicode(f.read(), "utf-8") | ||||
# css | # css | ||||
fpath = os.path.join(path, scrub(self.name) + '.css') | |||||
fpath = os.path.join(path, page_name + '.css') | |||||
if os.path.exists(fpath): | if os.path.exists(fpath): | ||||
with open(fpath, 'r') as f: | with open(fpath, 'r') as f: | ||||
self.style = unicode(f.read(), "utf-8") | self.style = unicode(f.read(), "utf-8") | ||||
@@ -109,3 +111,5 @@ class Page(Document): | |||||
if frappe.lang != 'en': | if frappe.lang != 'en': | ||||
from frappe.translate import get_lang_js | from frappe.translate import get_lang_js | ||||
self.script += get_lang_js("page", self.name) | self.script += get_lang_js("page", self.name) | ||||
@@ -200,11 +200,11 @@ CREATE TABLE `__Auth` ( | |||||
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; | ||||
-- | -- | ||||
-- Table structure for table `tabFile` | |||||
-- | -- | ||||
DROP TABLE IF EXISTS `tabFile Data`; | |||||
CREATE TABLE `tabFile Data` ( | |||||
DROP TABLE IF EXISTS `tabFile`; | |||||
CREATE TABLE `tabFile` ( | |||||
`name` varchar(255) NOT NULL, | `name` varchar(255) NOT NULL, | ||||
`creation` datetime(6) DEFAULT NULL, | `creation` datetime(6) DEFAULT NULL, | ||||
`modified` datetime(6) DEFAULT NULL, | `modified` datetime(6) DEFAULT NULL, | ||||
@@ -100,7 +100,7 @@ def get_user_permissions(meta): | |||||
return out | return out | ||||
def get_attachments(dt, dn): | def get_attachments(dt, dn): | ||||
return frappe.get_all("File Data", fields=["name", "file_name", "file_url"], | |||||
return frappe.get_all("File", fields=["name", "file_name", "file_url"], | |||||
filters = {"attached_to_name": dn, "attached_to_doctype": dt}) | filters = {"attached_to_name": dn, "attached_to_doctype": dt}) | ||||
def get_comments(dt, dn, limit=100): | def get_comments(dt, dn, limit=100): | ||||
@@ -121,7 +121,7 @@ def get_comments(dt, dn, limit=100): | |||||
as_dict=True) | as_dict=True) | ||||
for c in communications: | for c in communications: | ||||
c.attachments = json.dumps([f.file_url for f in frappe.get_all("File Data", | |||||
c.attachments = json.dumps([f.file_url for f in frappe.get_all("File", | |||||
fields=["file_url"], | fields=["file_url"], | ||||
filters={"attached_to_doctype": "Communication", | filters={"attached_to_doctype": "Communication", | ||||
"attached_to_name": c.name} | "attached_to_name": c.name} | ||||
@@ -59,14 +59,18 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, | |||||
# build from doctype | # build from doctype | ||||
if txt: | if txt: | ||||
search_fields = ["name"] | |||||
if meta.title_field: | |||||
search_fields.append(meta.title_field) | |||||
if meta.search_fields: | if meta.search_fields: | ||||
for f in meta.get_search_fields(): | |||||
fmeta = meta.get_field(f.strip()) | |||||
if f == "name" or (fmeta and fmeta.fieldtype in ["Data", "Text", "Small Text", "Long Text", | |||||
"Link", "Select", "Read Only", "Text Editor"]): | |||||
or_filters.append([doctype, f.strip(), "like", "%{0}%".format(txt)]) | |||||
else: | |||||
filters.append([doctype, searchfield or "name", "like", "%{0}%".format(txt)]) | |||||
search_fields.extend(meta.get_search_fields()) | |||||
for f in search_fields: | |||||
fmeta = meta.get_field(f.strip()) | |||||
if f == "name" or (fmeta and fmeta.fieldtype in ["Data", "Text", "Small Text", "Long Text", | |||||
"Link", "Select", "Read Only", "Text Editor"]): | |||||
or_filters.append([doctype, f.strip(), "like", "%{0}%".format(txt)]) | |||||
if meta.get("fields", {"fieldname":"enabled", "fieldtype":"Check"}): | if meta.get("fields", {"fieldname":"enabled", "fieldtype":"Check"}): | ||||
filters.append([doctype, "enabled", "=", 1]) | filters.append([doctype, "enabled", "=", 1]) | ||||
@@ -41,8 +41,8 @@ class TestEmailAccount(unittest.TestCase): | |||||
def test_incoming_with_attach(self): | def test_incoming_with_attach(self): | ||||
frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'") | frappe.db.sql("delete from tabCommunication where sender='test_sender@example.com'") | ||||
existing_file = frappe.get_doc({'doctype': 'File Data', 'file_name': 'erpnext-conf-14.png'}) | |||||
frappe.delete_doc("File Data", existing_file.name) | |||||
existing_file = frappe.get_doc({'doctype': 'File', 'file_name': 'erpnext-conf-14.png'}) | |||||
frappe.delete_doc("File", existing_file.name) | |||||
delete_file_from_filesystem(existing_file) | delete_file_from_filesystem(existing_file) | ||||
with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-2.raw"), "r") as f: | with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-2.raw"), "r") as f: | ||||
@@ -177,8 +177,8 @@ class FrappeClient(object): | |||||
self.migrate_doctype("Comment", {"comment_doctype": doctype, "comment_docname": doc["name"]}, | self.migrate_doctype("Comment", {"comment_doctype": doctype, "comment_docname": doc["name"]}, | ||||
update={"comment_docname": new_doc.name}, verbose=0) | update={"comment_docname": new_doc.name}, verbose=0) | ||||
if doctype != "File Data": | |||||
self.migrate_doctype("File Data", {"attached_to_doctype": doctype, | |||||
if doctype != "File": | |||||
self.migrate_doctype("File", {"attached_to_doctype": doctype, | |||||
"attached_to_name": doc["name"]}, update={"attached_to_name": new_doc.name}, verbose=0) | "attached_to_name": doc["name"]}, update={"attached_to_name": new_doc.name}, verbose=0) | ||||
def migrate_single(self, doctype): | def migrate_single(self, doctype): | ||||
@@ -82,6 +82,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa | |||||
update_naming_series(doc) | update_naming_series(doc) | ||||
delete_from_table(doctype, name, ignore_doctypes, doc) | delete_from_table(doctype, name, ignore_doctypes, doc) | ||||
doc.run_method("after_delete") | |||||
if doc: | if doc: | ||||
try: | try: | ||||
@@ -86,6 +86,10 @@ class Document(BaseDocument): | |||||
self._default_new_docs = {} | self._default_new_docs = {} | ||||
self.flags = frappe._dict() | self.flags = frappe._dict() | ||||
def reload(self): | |||||
"""Reload document from database""" | |||||
self.load_from_db() | |||||
def load_from_db(self): | def load_from_db(self): | ||||
"""Load document and children from database and create properties | """Load document and children from database and create properties | ||||
from fields""" | from fields""" | ||||
@@ -66,8 +66,9 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F | |||||
def update_attachments(doctype, old, new): | def update_attachments(doctype, old, new): | ||||
try: | try: | ||||
frappe.db.sql("""update `tabFile Data` set attached_to_name=%s | |||||
where attached_to_name=%s and attached_to_doctype=%s""", (new, old, doctype)) | |||||
if old != "File Data" and doctype != "DocType": | |||||
frappe.db.sql("""update `tabFile` set attached_to_name=%s | |||||
where attached_to_name=%s and attached_to_doctype=%s""", (new, old, doctype)) | |||||
except Exception, e: | except Exception, e: | ||||
if e.args[0]!=1054: # in patch? | if e.args[0]!=1054: # in patch? | ||||
raise | raise | ||||
@@ -96,7 +97,7 @@ def validate_rename(doctype, new, meta, merge, force, ignore_permissions): | |||||
if not (ignore_permissions or frappe.has_permission(doctype, "write")): | if not (ignore_permissions or frappe.has_permission(doctype, "write")): | ||||
frappe.msgprint(_("You need write permission to rename"), raise_exception=1) | frappe.msgprint(_("You need write permission to rename"), raise_exception=1) | ||||
if not force and not meta.allow_rename: | |||||
if not (force or ignore_permissions) and not meta.allow_rename: | |||||
frappe.msgprint(_("{0} not allowed to be renamed").format(_(doctype)), raise_exception=1) | frappe.msgprint(_("{0} not allowed to be renamed").format(_(doctype)), raise_exception=1) | ||||
# validate naming like it's done in doc.py | # validate naming like it's done in doc.py | ||||
@@ -91,3 +91,4 @@ frappe.patches.v6_0.document_type_rename | |||||
frappe.patches.v6_0.fix_ghana_currency | frappe.patches.v6_0.fix_ghana_currency | ||||
frappe.patches.v6_2.ignore_user_permissions_if_missing | frappe.patches.v6_2.ignore_user_permissions_if_missing | ||||
execute:frappe.db.sql("delete from tabSessions where user is null") | execute:frappe.db.sql("delete from tabSessions where user is null") | ||||
frappe.patches.v6_1.rename_file_data |
@@ -12,9 +12,9 @@ from frappe.utils.file_manager import get_content_hash, get_file | |||||
def execute(): | def execute(): | ||||
frappe.reload_doc('core', 'doctype', 'file_data') | frappe.reload_doc('core', 'doctype', 'file_data') | ||||
for name, file_name, file_url in frappe.db.sql( | for name, file_name, file_url in frappe.db.sql( | ||||
"""select name, file_name, file_url from `tabFile Data` | |||||
"""select name, file_name, file_url from `tabFile` | |||||
where file_name is not null"""): | where file_name is not null"""): | ||||
b = frappe.get_doc('File Data', name) | |||||
b = frappe.get_doc('File', name) | |||||
old_file_name = b.file_name | old_file_name = b.file_name | ||||
b.file_name = os.path.basename(old_file_name) | b.file_name = os.path.basename(old_file_name) | ||||
if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): | if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): | ||||
@@ -18,15 +18,15 @@ from frappe.utils import get_files_path, get_site_path | |||||
# * make missing_files.txt in site dir with files that should be recovered from | # * make missing_files.txt in site dir with files that should be recovered from | ||||
# a backup from a time before version 3 migration | # a backup from a time before version 3 migration | ||||
# | # | ||||
# * Patch remaining unpatched file data records. | |||||
# * Patch remaining unpatched File records. | |||||
def execute(): | def execute(): | ||||
frappe.db.auto_commit_on_many_writes = True | frappe.db.auto_commit_on_many_writes = True | ||||
rename_replacing_files() | rename_replacing_files() | ||||
for name, file_name, file_url in frappe.db.sql( | for name, file_name, file_url in frappe.db.sql( | ||||
"""select name, file_name, file_url from `tabFile Data` | |||||
"""select name, file_name, file_url from `tabFile` | |||||
where ifnull(file_name, '')!='' and ifnull(content_hash, '')=''"""): | where ifnull(file_name, '')!='' and ifnull(content_hash, '')=''"""): | ||||
b = frappe.get_doc('File Data', name) | |||||
b = frappe.get_doc('File', name) | |||||
old_file_name = b.file_name | old_file_name = b.file_name | ||||
b.file_name = os.path.basename(old_file_name) | b.file_name = os.path.basename(old_file_name) | ||||
if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): | if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): | ||||
@@ -45,8 +45,8 @@ def execute(): | |||||
def get_replaced_files(): | def get_replaced_files(): | ||||
ret = [] | ret = [] | ||||
new_files = dict(frappe.db.sql("select name, file_name from `tabFile Data` where file_name not like 'files/%'")) | |||||
old_files = dict(frappe.db.sql("select name, file_name from `tabFile Data` where ifnull(content_hash, '')=''")) | |||||
new_files = dict(frappe.db.sql("select name, file_name from `tabFile` where file_name not like 'files/%'")) | |||||
old_files = dict(frappe.db.sql("select name, file_name from `tabFile` where ifnull(content_hash, '')=''")) | |||||
invfiles = invert_dict(new_files) | invfiles = invert_dict(new_files) | ||||
for nname, nfilename in new_files.iteritems(): | for nname, nfilename in new_files.iteritems(): | ||||
@@ -63,7 +63,7 @@ def rename_replacing_files(): | |||||
for file_name, file_datas in replaced_files: | for file_name, file_datas in replaced_files: | ||||
print 'processing ' + file_name | print 'processing ' + file_name | ||||
content_hash = frappe.db.get_value('File Data', file_datas[0], 'content_hash') | |||||
content_hash = frappe.db.get_value('File', file_datas[0], 'content_hash') | |||||
if not content_hash: | if not content_hash: | ||||
continue | continue | ||||
new_file_name = get_file_name(file_name, content_hash) | new_file_name = get_file_name(file_name, content_hash) | ||||
@@ -75,7 +75,7 @@ def rename_replacing_files(): | |||||
except OSError: | except OSError: | ||||
print 'Error renaming ', file_name | print 'Error renaming ', file_name | ||||
for name in file_datas: | for name in file_datas: | ||||
f = frappe.get_doc('File Data', name) | |||||
f = frappe.get_doc('File', name) | |||||
f.file_name = new_file_name | f.file_name = new_file_name | ||||
f.file_url = '/files/' + new_file_name | f.file_url = '/files/' + new_file_name | ||||
f.save() | f.save() | ||||
@@ -0,0 +1,30 @@ | |||||
import frappe | |||||
def execute(): | |||||
from frappe.core.doctype.file.file import make_home_folder | |||||
if not frappe.db.exists("DocType", "File"): | |||||
frappe.rename_doc("DocType", "File Data", "File") | |||||
frappe.reload_doctype("File") | |||||
if not frappe.db.exists("File", {"is_home_folder": 1}): | |||||
make_home_folder() | |||||
# make missing folders and set parent folder | |||||
for file in frappe.get_all("File", filters={"is_folder": 0}): | |||||
file = frappe.get_doc("File", file.name) | |||||
file.flags.ignore_folder_validate = True | |||||
file.set_folder_name() | |||||
file.save() | |||||
from frappe.utils.nestedset import rebuild_tree | |||||
rebuild_tree("File", "folder") | |||||
# reset file size | |||||
for folder in frappe.db.sql("""select name from tabFile f1 where is_folder = 1 and | |||||
(select count(*) from tabFile f2 where f2.folder = f1.name and f2.is_folder = 1) = 0"""): | |||||
folder = frappe.get_doc("File", folder[0]) | |||||
folder.save() | |||||
@@ -52,6 +52,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): | |||||
if doc_name in shared: | if doc_name in shared: | ||||
if verbose: print "Shared" | if verbose: print "Shared" | ||||
if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype): | if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype): | ||||
if verbose: print "Is shared" | |||||
return True | return True | ||||
elif shared: | elif shared: | ||||
@@ -60,6 +61,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): | |||||
if verbose: print "Has a shared document" | if verbose: print "Has a shared document" | ||||
return True | return True | ||||
if verbose: print "Not Shared" | |||||
return False | return False | ||||
role_permissions = get_role_permissions(meta, user=user, verbose=verbose) | role_permissions = get_role_permissions(meta, user=user, verbose=verbose) | ||||
@@ -67,28 +69,40 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): | |||||
if not role_permissions.get(ptype): | if not role_permissions.get(ptype): | ||||
return false_if_not_shared() | return false_if_not_shared() | ||||
perm = True | |||||
if doc: | if doc: | ||||
if isinstance(doc, basestring): | if isinstance(doc, basestring): | ||||
doc = frappe.get_doc(meta.name, doc) | doc = frappe.get_doc(meta.name, doc) | ||||
# if owner match, then return True | |||||
if doc.owner == frappe.session.user and role_permissions["if_owner"].get(ptype) and ptype!="create": | |||||
return True | |||||
owner_perm = user_perm = controller_perm = None | |||||
if role_permissions["if_owner"].get(ptype) and ptype!="create": | |||||
owner_perm = doc.owner == frappe.session.user | |||||
if verbose: print "Owner permission: {0}".format(owner_perm) | |||||
# check if user permission | # check if user permission | ||||
if role_permissions["apply_user_permissions"].get(ptype): | |||||
if not user_has_permission(doc, verbose=verbose, user=user, | |||||
user_permission_doctypes=role_permissions.get("user_permission_doctypes", {}).get(ptype) or []): | |||||
if verbose: print "No user permission" | |||||
return false_if_not_shared() | |||||
if not has_controller_permissions(doc, ptype, user=user): | |||||
if verbose: print "No controller permission" | |||||
return false_if_not_shared() | |||||
if verbose: | |||||
print "Has Role" | |||||
return True | |||||
if not owner_perm and role_permissions["apply_user_permissions"].get(ptype): | |||||
user_perm = user_has_permission(doc, verbose=verbose, user=user, | |||||
user_permission_doctypes=role_permissions.get("user_permission_doctypes", {}).get(ptype) or []) | |||||
if verbose: print "User permission: {0}".format(user_perm) | |||||
if not owner_perm and not user_perm: | |||||
controller_perm = has_controller_permissions(doc, ptype, user=user) | |||||
if verbose: print "Controller permission: {0}".format(controller_perm) | |||||
# permission true if any one condition is explicitly True or all permissions are undefined (None) | |||||
perm = any([owner_perm, user_perm, controller_perm]) or \ | |||||
all([owner_perm==None, user_perm==None, controller_perm==None]) | |||||
if not perm: | |||||
perm = false_if_not_shared() | |||||
if verbose: print "Final Permission: {0}".format(perm) | |||||
return perm | |||||
def get_doc_permissions(doc, verbose=False, user=None): | def get_doc_permissions(doc, verbose=False, user=None): | ||||
"""Returns a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`""" | """Returns a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`""" | ||||
@@ -271,9 +285,15 @@ def user_has_permission(doc, verbose=True, user=None, user_permission_doctypes=N | |||||
return _user_has_permission | return _user_has_permission | ||||
def has_controller_permissions(doc, ptype, user=None): | def has_controller_permissions(doc, ptype, user=None): | ||||
"""Returns controller permissions if defined. None if not defined""" | |||||
if not user: user = frappe.session.user | if not user: user = frappe.session.user | ||||
for method in frappe.get_hooks("has_permission").get(doc.doctype, []): | |||||
methods = frappe.get_hooks("has_permission").get(doc.doctype, []) | |||||
if not methods: | |||||
return None | |||||
for method in methods: | |||||
if not frappe.call(frappe.get_attr(method), doc=doc, ptype=ptype, user=user): | if not frappe.call(frappe.get_attr(method), doc=doc, ptype=ptype, user=user): | ||||
return False | return False | ||||
@@ -521,3 +521,12 @@ ul.linked-with-list li { | |||||
height: 451px; | height: 451px; | ||||
font-family: Monaco, "Courier New", monospace; | font-family: Monaco, "Courier New", monospace; | ||||
} | } | ||||
.breadcrumb { | |||||
font-size: 12px; | |||||
background-color: #fff; | |||||
} | |||||
.breadcrumb.for-file-list { | |||||
margin-bottom: 0px; | |||||
border-bottom: 1px solid #d1d8dd; | |||||
border-radius: 0px; | |||||
} |
@@ -145,28 +145,14 @@ frappe.ui.form.Attachments = Class.extend({ | |||||
}, | }, | ||||
new_attachment: function(fieldname) { | new_attachment: function(fieldname) { | ||||
var me = this; | var me = this; | ||||
if(!this.dialog) { | |||||
this.dialog = new frappe.ui.Dialog({ | |||||
title: __('Upload Attachment'), | |||||
if(!this.dialog){ | |||||
this.dialog = frappe.ui.get_upload_dialog({ | |||||
"args": me.get_args(), | |||||
"callback": function(attachment, r) { me.attachment_uploaded(attachment, r) }, | |||||
"max_width": me.frm.cscript ? me.frm.cscript.attachment_max_width : null, | |||||
"max_height": me.frm.cscript ? me.frm.cscript.attachment_max_height : null | |||||
}); | }); | ||||
} | } | ||||
this.dialog.show(); | |||||
this.fieldname = fieldname; | |||||
$(this.dialog.body).empty(); | |||||
frappe.upload.make({ | |||||
parent: this.dialog.body, | |||||
args: this.get_args(), | |||||
callback: function(attachment, r) { | |||||
me.attachment_uploaded(attachment, r); | |||||
}, | |||||
onerror: function() { | |||||
me.dialog.hide(); | |||||
}, | |||||
btn: this.dialog.set_primary_action(__("Attach")), | |||||
max_width: this.frm.cscript ? this.frm.cscript.attachment_max_width : null, | |||||
max_height: this.frm.cscript ? this.frm.cscript.attachment_max_height : null, | |||||
}); | |||||
}, | }, | ||||
get_args: function() { | get_args: function() { | ||||
return { | return { | ||||
@@ -213,3 +199,38 @@ frappe.ui.form.Attachments = Class.extend({ | |||||
this.refresh(); | this.refresh(); | ||||
} | } | ||||
}); | }); | ||||
frappe.ui.get_upload_dialog = function(opts){ | |||||
dialog = new frappe.ui.Dialog({ | |||||
title: __('Upload Attachment'), | |||||
}); | |||||
var btn = dialog.set_primary_action(__("Attach")); | |||||
btn.removeClass("btn-primary").addClass("btn-default"); | |||||
dialog.show(); | |||||
$(dialog.body).empty(); | |||||
frappe.upload.make({ | |||||
parent: dialog.body, | |||||
args: opts.args, | |||||
callback: function(attachment, r) { | |||||
dialog.hide(); | |||||
if(opts.callback){ | |||||
opts.callback(attachment, r); | |||||
} | |||||
}, | |||||
on_select: function() { | |||||
btn.removeClass("btn-default").addClass("btn-primary"); | |||||
}, | |||||
onerror: function() { | |||||
dialog.hide(); | |||||
}, | |||||
btn: btn, | |||||
max_width: opts.max_width, | |||||
max_height: opts.max_height, | |||||
}); | |||||
return dialog; | |||||
} |
@@ -14,10 +14,14 @@ frappe.views.ListFactory = frappe.views.Factory.extend({ | |||||
if(locals["DocType"][doctype].issingle) { | if(locals["DocType"][doctype].issingle) { | ||||
frappe.set_re_route("Form", doctype); | frappe.set_re_route("Form", doctype); | ||||
} else { | } else { | ||||
new frappe.views.DocListView({ | |||||
doctype: doctype, | |||||
parent: me.make_page(true) | |||||
}); | |||||
if(!frappe.views.doclistview[doctype]) { | |||||
frappe.views.doclistview[doctype] = new frappe.views.DocListView({ | |||||
doctype: doctype, | |||||
parent: me.make_page(true, "List/" + doctype) | |||||
}); | |||||
} else { | |||||
frappe.container.change_to(frappe.views.doclistview[doctype].page_name); | |||||
} | |||||
me.set_cur_list(); | me.set_cur_list(); | ||||
} | } | ||||
}); | }); | ||||
@@ -44,13 +48,18 @@ $(document).on("save", function(event, doc) { | |||||
frappe.views.set_list_as_dirty = function(doctype) { | frappe.views.set_list_as_dirty = function(doctype) { | ||||
var list_page = "List/" + doctype; | var list_page = "List/" + doctype; | ||||
if(frappe.pages[list_page]) { | if(frappe.pages[list_page]) { | ||||
if(frappe.pages[list_page].doclistview) | |||||
if(frappe.pages[list_page].doclistview) { | |||||
if(frappe.pages[list_page].doclistview.dirty) { | |||||
// already refreshing... | |||||
return; | |||||
} | |||||
frappe.pages[list_page].doclistview.dirty = true; | frappe.pages[list_page].doclistview.dirty = true; | ||||
} | |||||
} | } | ||||
var route = frappe.get_route(); | var route = frappe.get_route(); | ||||
if(route[0]==="List" && route[1]===doctype) { | if(route[0]==="List" && route[1]===doctype) { | ||||
setTimeout(function() { | setTimeout(function() { | ||||
frappe.pages[list_page].doclistview.run(); | |||||
frappe.pages[list_page].doclistview.refresh(); | |||||
}, 100); | }, 100); | ||||
} | } | ||||
} | } | ||||
@@ -65,6 +74,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||||
}; | }; | ||||
this.label = __(this.doctype); | this.label = __(this.doctype); | ||||
this.page_name = "List/" + this.doctype; | |||||
this.dirty = true; | this.dirty = true; | ||||
this.tags_shown = false; | this.tags_shown = false; | ||||
this.label = (this.label.toLowerCase().substr(-4) == 'list') ? | this.label = (this.label.toLowerCase().substr(-4) == 'list') ? | ||||
@@ -118,7 +128,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||||
&& !this.listview.no_delete) | && !this.listview.no_delete) | ||||
}); | }); | ||||
this.list_header = $(frappe.render_template("list_item_row_head", { main:main, list:this })) | |||||
this.list_header = $(frappe.render_template("list_item_row_head", { main:main, list:this.listview })) | |||||
.appendTo(this.page.main.find(".list-headers")); | .appendTo(this.page.main.find(".list-headers")); | ||||
}, | }, | ||||
@@ -153,7 +163,9 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||||
added = true; | added = true; | ||||
} | } | ||||
}); | }); | ||||
added && me.run(); | |||||
if(added) { | |||||
me.refresh(true); | |||||
} | |||||
}); | }); | ||||
this.$page.find(".result-list").on("click", ".list-row-left", function(e) { | this.$page.find(".result-list").on("click", ".list-row-left", function(e) { | ||||
// don't open in case of checkbox, star, filterable | // don't open in case of checkbox, star, filterable | ||||
@@ -218,16 +230,21 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||||
// make_new_doc can be overridden so that default values can be prefilled | // make_new_doc can be overridden so that default values can be prefilled | ||||
// for example - communication list in customer | // for example - communication list in customer | ||||
$(this.wrapper).on("click", 'button[list_view_doc="'+me.doctype+'"]', function(){ | |||||
(me.listview.make_new_doc || me.make_new_doc).apply(me, [me.doctype]); | |||||
}); | |||||
if(this.listview.settings.list_view_doc) { | |||||
this.listview.settings.list_view_doc(this); | |||||
} | |||||
else{ | |||||
$(this.wrapper).on("click", 'button[list_view_doc="'+me.doctype+'"]', function(){ | |||||
(me.listview.make_new_doc || me.make_new_doc).apply(me, [me.doctype]); | |||||
}); | |||||
} | |||||
if((auto_run !== false) && (auto_run !== 0)) | if((auto_run !== false) && (auto_run !== 0)) | ||||
this.refresh(); | this.refresh(); | ||||
}, | }, | ||||
refresh: function() { | |||||
var me = this; | |||||
refresh: function(dirty) { | |||||
if(dirty!==undefined) this.dirty = dirty; | |||||
this.init_stats(); | this.init_stats(); | ||||
if(this.listview.settings.refresh) { | if(this.listview.settings.refresh) { | ||||
@@ -235,13 +252,14 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||||
} | } | ||||
if(frappe.route_options) { | if(frappe.route_options) { | ||||
me.set_route_options(); | |||||
} else if(me.dirty) { | |||||
me.run(); | |||||
this.set_route_options(); | |||||
this.run(); | |||||
} else if(this.dirty) { | |||||
this.run(); | |||||
} else { | } else { | ||||
if(new Date() - (me.last_updated_on || 0) > 30000) { | |||||
if(new Date() - (this.last_updated_on || 0) > 30000) { | |||||
// older than 5 mins, refresh | // older than 5 mins, refresh | ||||
me.run(); | |||||
this.run(); | |||||
} | } | ||||
} | } | ||||
}, | }, | ||||
@@ -265,17 +283,28 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||||
} | } | ||||
}); | }); | ||||
frappe.route_options = null; | frappe.route_options = null; | ||||
me.run(); | |||||
}, | }, | ||||
run: function(more) { | run: function(more) { | ||||
// set filter from route | // set filter from route | ||||
var route = frappe.get_route(); | |||||
var me = this; | var me = this; | ||||
if(route[2]) { | |||||
$.each(frappe.utils.get_args_dict_from_url(route[2]), function(key, val) { | |||||
me.set_filter(key, val, true); | |||||
}); | |||||
if(this.fresh && !more) { | |||||
return; | |||||
} | |||||
if(this.listview.settings.before_run) { | |||||
this.listview.settings.before_run(this); | |||||
} | |||||
if(!this.listview.settings.use_route) { | |||||
var route = frappe.get_route(); | |||||
var me = this; | |||||
if(route[2]) { | |||||
$.each(frappe.utils.get_args_dict_from_url(route[2]), function(key, val) { | |||||
me.set_filter(key, val, true); | |||||
}); | |||||
} | |||||
} | } | ||||
this.list_header.find(".list-starred-by-me") | this.list_header.find(".list-starred-by-me") | ||||
@@ -283,7 +312,25 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||||
this.last_updated_on = new Date(); | this.last_updated_on = new Date(); | ||||
this.dirty = false; | this.dirty = false; | ||||
// set a fresh so that multiple refreshes do not happen | |||||
// at the same time. This is true when deleting. | |||||
// AJAX response will try to refresh and list_update notification | |||||
// via async will also try to update. | |||||
// It is not possible to guess which will reach first | |||||
// (most probably async will) but this is a forced way | |||||
// to prevent instant refreshes on mutilple triggers | |||||
// in a loosly coupled way. | |||||
this.fresh = true; | |||||
setTimeout(function() { | |||||
me.fresh = false; | |||||
}, 1000); | |||||
this._super(more); | this._super(more); | ||||
if(this.listview.settings.post_render) { | |||||
this.listview.settings.post_render(this); | |||||
} | |||||
}, | }, | ||||
make_no_result: function() { | make_no_result: function() { | ||||
@@ -336,12 +383,14 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||||
this.$page.on("click", ".list-tag-preview", function() { me.toggle_tags(); }); | this.$page.on("click", ".list-tag-preview", function() { me.toggle_tags(); }); | ||||
this.page.set_secondary_action(__("Refresh"), function() { | this.page.set_secondary_action(__("Refresh"), function() { | ||||
me.run(); | |||||
me.dirty = true; | |||||
me.refresh(); | |||||
}, "octicon octicon-sync"); | }, "octicon octicon-sync"); | ||||
this.page.btn_secondary.addClass("hidden-xs"); | this.page.btn_secondary.addClass("hidden-xs"); | ||||
this.page.add_menu_item(__("Refresh"), function() { | this.page.add_menu_item(__("Refresh"), function() { | ||||
me.run(); | |||||
me.dirty = true; | |||||
me.refresh(); | |||||
}, "octicon octicon-sync").addClass("visible-xs"); | }, "octicon octicon-sync").addClass("visible-xs"); | ||||
if(frappe.model.can_import(this.doctype)) { | if(frappe.model.can_import(this.doctype)) { | ||||
@@ -526,7 +575,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||||
callback: function(r) { | callback: function(r) { | ||||
if(!r.exc) { | if(!r.exc) { | ||||
me.list_header.find(".list-select-all").prop("checked", false); | me.list_header.find(".list-select-all").prop("checked", false); | ||||
me.run(); | |||||
me.refresh(); | |||||
} | } | ||||
} | } | ||||
}); | }); | ||||
@@ -20,7 +20,7 @@ | |||||
{% if (col.type==="Subject") { %} | {% if (col.type==="Subject") { %} | ||||
{%= subject %} | {%= subject %} | ||||
{% } else if (col.type==="Indicator") { %} | {% } else if (col.type==="Indicator") { %} | ||||
{%= me.get_indicator(data) %} | |||||
{%= list.get_indicator(data) %} | |||||
{% } else if (col.render) { %} | {% } else if (col.render) { %} | ||||
{%= col.render(data) %} | {%= col.render(data) %} | ||||
{% } else if (col.fieldtype==="Image") { %} | {% } else if (col.fieldtype==="Image") { %} | ||||
@@ -36,7 +36,12 @@ | |||||
<a class="filterable h6 text-muted grey" | <a class="filterable h6 text-muted grey" | ||||
data-filter="{%= col.fieldname %},=,{%= value %}">{%= value %}</a> | data-filter="{%= col.fieldname %},=,{%= value %}">{%= value %}</a> | ||||
{% } else { %} | {% } else { %} | ||||
{%= frappe.format(value, col.df, null, data) %} | |||||
{% if(list.settings.formatters | |||||
&& list.settings.formatters[col.fieldname]) { %} | |||||
{{ list.settings.formatters[col.fieldname](value, col.df, data) }} | |||||
{% } else { %} | |||||
{{ frappe.format(value, col.df, null, data) }} | |||||
{% } %} | |||||
{% } %} | {% } %} | ||||
{% if(col.type!=="Indicator") { %}</span>{% } %} | {% if(col.type!=="Indicator") { %}</span>{% } %} | ||||
</div> | </div> | ||||
@@ -1,6 +1,6 @@ | |||||
<div class="row doclist-row {% if (data._checkbox) { %} has-checkbox {% } %}"> | <div class="row doclist-row {% if (data._checkbox) { %} has-checkbox {% } %}"> | ||||
<div class="col-xs-10 | <div class="col-xs-10 | ||||
{% if (list.meta.title_field) { %} | |||||
{% if (list.meta.title_field && !list.settings.hide_name_column) { %} | |||||
col-sm-8 | col-sm-8 | ||||
{% } else { %} | {% } else { %} | ||||
col-sm-10 | col-sm-10 | ||||
@@ -10,7 +10,7 @@ | |||||
</div> | </div> | ||||
<!-- id --> | <!-- id --> | ||||
{% if (list.meta.title_field) { | |||||
{% if (list.meta.title_field && !list.settings.hide_name_column) { | |||||
var is_different = data.name !== data[list.meta.title_field]; | var is_different = data.name !== data[list.meta.title_field]; | ||||
%} | %} | ||||
<div class="list-col col-sm-2 hidden-xs text-right text-ellipsis rtl list-row-id"> | <div class="list-col col-sm-2 hidden-xs text-right text-ellipsis rtl list-row-id"> | ||||
@@ -1,7 +1,7 @@ | |||||
<div class="list-row list-row-head"> | <div class="list-row list-row-head"> | ||||
<div class="row doclist-row"> | <div class="row doclist-row"> | ||||
<div class="col-xs-10 | <div class="col-xs-10 | ||||
{% if (list.meta.title_field) { %} | |||||
{% if (list.meta.title_field && !list.settings.hide_name_column) { %} | |||||
col-sm-8 | col-sm-8 | ||||
{% } else { %} | {% } else { %} | ||||
col-sm-10 | col-sm-10 | ||||
@@ -11,7 +11,7 @@ | |||||
</div> | </div> | ||||
<!-- id --> | <!-- id --> | ||||
{% if (list.meta.title_field) { %} | |||||
{% if (list.meta.title_field && !list.settings.hide_name_column) { %} | |||||
<div class="list-col col-sm-2 hidden-xs text-right"> | <div class="list-col col-sm-2 hidden-xs text-right"> | ||||
</div> | </div> | ||||
{% } %} | {% } %} | ||||
@@ -1,14 +1,20 @@ | |||||
{% if (_checkbox) { %} | {% if (_checkbox) { %} | ||||
<input class="list-delete" type="checkbox" style="margin: 0 7px 0 0; vertical-align: middle;"> | |||||
<input class="list-delete" type="checkbox" | |||||
style="margin: 0 7px 0 0; vertical-align: middle;"> | |||||
{% } %} | {% } %} | ||||
<i class="icon-star {% if (_starred_by.indexOf(_user)===-1) { | |||||
%}text-extra-muted not-starred{% } else { %}{% }%} | |||||
icon-fixed-width star-action" data-name="{%= _name %}" data-doctype="{%= doctype %}"> | |||||
<i class="icon-star | |||||
{% if (_starred_by.indexOf(_user)===-1) { %} | |||||
text-extra-muted not-starred | |||||
{% }%} | |||||
icon-fixed-width star-action" | |||||
data-name="{{ _name }}" data-doctype="{{ doctype }}"> | |||||
</i> | </i> | ||||
<a class="grey list-id" style="margin-right: 7px;" | |||||
href="#Form/{%= _doctype_encoded %}/{%= _name_encoded %}" title="{%= _full_title %}">{%= _title %}</a> | |||||
<a class="grey list-id" | |||||
style="margin-right: 7px;" | |||||
href="#Form/{{ _doctype_encoded }}/{{ _name_encoded }}" | |||||
title="{{ _full_title }}">{{ _title }}</a> | |||||
{% if (_workflow && !_without_workflow) { %} | {% if (_workflow && !_without_workflow) { %} | ||||
<span class="label label-{%= _workflow.style %} filterable" | |||||
data-filter="{%= _workflow.fieldname %},=,{%= _workflow.value %}"> | |||||
<span class="label label-{{ _workflow.style }} filterable" | |||||
data-filter="{{ _workflow.fieldname }},=,{{ _workflow.value }}"> | |||||
{%= _workflow.value %}</span> | {%= _workflow.value %}</span> | ||||
{% } %} | {% } %} |
@@ -208,7 +208,7 @@ frappe.views.ListView = Class.extend({ | |||||
data: data, | data: data, | ||||
columns: this.columns, | columns: this.columns, | ||||
subject: this.get_avatar_and_id(data, true), | subject: this.get_avatar_and_id(data, true), | ||||
me: this, | |||||
list: this, | |||||
right_column: this.settings.right_column | right_column: this.settings.right_column | ||||
}); | }); | ||||
@@ -219,6 +219,10 @@ frappe.views.ListView = Class.extend({ | |||||
right_column: this.settings.right_column | right_column: this.settings.right_column | ||||
})).appendTo(row); | })).appendTo(row); | ||||
if(this.settings.post_render_item) { | |||||
this.settings.post_render_item(this, row, data); | |||||
} | |||||
this.render_tags(row, data); | this.render_tags(row, data); | ||||
}, | }, | ||||
@@ -489,5 +489,9 @@ frappe.utils = { | |||||
// reset the original title | // reset the original title | ||||
frappe.utils.set_title(frappe._original_title); | frappe.utils.set_title(frappe._original_title); | ||||
}, | |||||
is_image_file: function(filename) { | |||||
return (/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(filename); | |||||
} | } | ||||
}; | }; |
@@ -60,6 +60,20 @@ $.extend(frappe.perm, { | |||||
}); | }); | ||||
} | } | ||||
// if owner | |||||
if(!$.isEmptyObject(perm[0].if_owner)) { | |||||
if(doc.owner===user) { | |||||
$.extend(perm[0], perm[0].if_owner); | |||||
} else { | |||||
// not owner, remove permissions | |||||
$.each(perm[0].if_owner, function(ptype, value) { | |||||
if(perm[0].if_owner[ptype]) { | |||||
perm[0][ptype] = 0 | |||||
} | |||||
}) | |||||
} | |||||
} | |||||
// apply permissions from shared | // apply permissions from shared | ||||
if(docinfo.shared) { | if(docinfo.shared) { | ||||
for(var i=0; i<docinfo.shared; i++) { | for(var i=0; i<docinfo.shared; i++) { | ||||
@@ -78,6 +92,7 @@ $.extend(frappe.perm, { | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if(frappe.model.can_read(doctype) && !perm[0].read) { | if(frappe.model.can_read(doctype) && !perm[0].read) { | ||||
@@ -131,10 +146,6 @@ $.extend(frappe.perm, { | |||||
}); | }); | ||||
} | } | ||||
} | } | ||||
if (permlevel===0 && p["if_owner"]) { | |||||
perm[0]["if_owner"] = 1; | |||||
} | |||||
} | } | ||||
}); | }); | ||||
@@ -29,7 +29,7 @@ frappe.ui.FilterList = Class.extend({ | |||||
this.filters = []; | this.filters = []; | ||||
}, | }, | ||||
add_filter: function(tablename, fieldname, condition, value) { | |||||
add_filter: function(doctype, fieldname, condition, value, hidden) { | |||||
this.$w.find('.show_filters').toggle(true); | this.$w.find('.show_filters').toggle(true); | ||||
var is_new_filter = arguments.length===0; | var is_new_filter = arguments.length===0; | ||||
@@ -38,21 +38,26 @@ frappe.ui.FilterList = Class.extend({ | |||||
return; | return; | ||||
} | } | ||||
var filter = this.push_new_filter(tablename, fieldname, condition, value); | |||||
var filter = this.push_new_filter(doctype, fieldname, condition, value); | |||||
if (is_new_filter) { | |||||
if (filter && is_new_filter) { | |||||
filter.$w.addClass("is-new-filter"); | filter.$w.addClass("is-new-filter"); | ||||
} | } | ||||
if (filter && hidden) { | |||||
filter.freeze(); | |||||
filter.$btn_group.addClass("hide"); | |||||
} | |||||
return filter; | return filter; | ||||
}, | }, | ||||
push_new_filter: function(tablename, fieldname, condition, value) { | |||||
if(this.filter_exists(tablename, fieldname, condition, value)) return; | |||||
push_new_filter: function(doctype, fieldname, condition, value) { | |||||
if(this.filter_exists(doctype, fieldname, condition, value)) return; | |||||
var filter = new frappe.ui.Filter({ | var filter = new frappe.ui.Filter({ | ||||
flist: this, | flist: this, | ||||
tablename: tablename, | |||||
doctype: doctype, | |||||
fieldname: fieldname, | fieldname: fieldname, | ||||
condition: condition, | condition: condition, | ||||
value: value, | value: value, | ||||
@@ -63,11 +68,11 @@ frappe.ui.FilterList = Class.extend({ | |||||
return filter; | return filter; | ||||
}, | }, | ||||
filter_exists: function(tablename, fieldname, condition, value) { | |||||
filter_exists: function(doctype, fieldname, condition, value) { | |||||
for(var i in this.filters) { | for(var i in this.filters) { | ||||
if(this.filters[i].field) { | if(this.filters[i].field) { | ||||
var f = this.filters[i].get_value(); | var f = this.filters[i].get_value(); | ||||
if(f[0]==tablename && f[1]==fieldname && f[2]==condition | |||||
if(f[0]==doctype && f[1]==fieldname && f[2]==condition | |||||
&& f[3]==value) return true; | && f[3]==value) return true; | ||||
} | } | ||||
@@ -127,7 +132,7 @@ frappe.ui.Filter = Class.extend({ | |||||
} | } | ||||
}); | }); | ||||
if(this.fieldname) { | if(this.fieldname) { | ||||
this.fieldselect.set_value(this.tablename, this.fieldname); | |||||
this.fieldselect.set_value(this.doctype, this.fieldname); | |||||
} | } | ||||
}, | }, | ||||
set_events: function() { | set_events: function() { | ||||
@@ -163,7 +168,7 @@ frappe.ui.Filter = Class.extend({ | |||||
// set the field | // set the field | ||||
if(me.fieldname) { | if(me.fieldname) { | ||||
// pre-sets given (could be via tags!) | // pre-sets given (could be via tags!) | ||||
this.set_values(me.tablename, me.fieldname, me.condition, me.value); | |||||
this.set_values(me.doctype, me.fieldname, me.condition, me.value); | |||||
} else { | } else { | ||||
me.set_field(me.doctype, 'name'); | me.set_field(me.doctype, 'name'); | ||||
} | } | ||||
@@ -173,17 +178,17 @@ frappe.ui.Filter = Class.extend({ | |||||
this.$w.remove(); | this.$w.remove(); | ||||
this.$btn_group && this.$btn_group.remove(); | this.$btn_group && this.$btn_group.remove(); | ||||
this.field = null; | this.field = null; | ||||
this.flist.update_filters(); | |||||
if(!dont_run) { | if(!dont_run) { | ||||
this.flist.update_filters(); | |||||
this.flist.listobj.dirty = true; | this.flist.listobj.dirty = true; | ||||
this.flist.listobj.run(); | |||||
this.flist.listobj.refresh(); | |||||
} | } | ||||
}, | }, | ||||
set_values: function(tablename, fieldname, condition, value) { | |||||
set_values: function(doctype, fieldname, condition, value) { | |||||
// presents given (could be via tags!) | // presents given (could be via tags!) | ||||
this.set_field(tablename, fieldname); | |||||
this.set_field(doctype, fieldname); | |||||
if(condition) this.$w.find('.condition').val(condition).change(); | if(condition) this.$w.find('.condition').val(condition).change(); | ||||
if(value!=null) this.field.set_input(value); | if(value!=null) this.field.set_input(value); | ||||
}, | }, | ||||
@@ -118,8 +118,12 @@ frappe.ui.Listing = Class.extend({ | |||||
set_primary_action: function() { | set_primary_action: function() { | ||||
var me = this; | var me = this; | ||||
if(this.new_doctype) { | if(this.new_doctype) { | ||||
this.page.set_primary_action(__("New"), function() { | |||||
me.make_new_doc(); }, "octicon octicon-plus"); | |||||
if(this.listview.settings.set_primary_action){ | |||||
this.listview.settings.set_primary_action(this); | |||||
} else { | |||||
this.page.set_primary_action(__("New"), function() { | |||||
me.make_new_doc(me.new_doctype); }, "octicon octicon-plus"); | |||||
} | |||||
} else { | } else { | ||||
this.page.clear_primary_action(); | this.page.clear_primary_action(); | ||||
} | } | ||||
@@ -234,7 +234,7 @@ frappe.ui.Page = Class.extend({ | |||||
//-- Sidebar --// | //-- Sidebar --// | ||||
add_sidebar_item: function(label, action, insert_after) { | |||||
add_sidebar_item: function(label, action, insert_after, prepend) { | |||||
var parent = this.sidebar.find(".sidebar-menu.standard-actions"); | var parent = this.sidebar.find(".sidebar-menu.standard-actions"); | ||||
var li = $('<li>'); | var li = $('<li>'); | ||||
var link = $('<a>').html(label).on("click", action).appendTo(li); | var link = $('<a>').html(label).on("click", action).appendTo(li); | ||||
@@ -242,7 +242,11 @@ frappe.ui.Page = Class.extend({ | |||||
if(insert_after) { | if(insert_after) { | ||||
li.insertAfter(parent.find(insert_after)); | li.insertAfter(parent.find(insert_after)); | ||||
} else { | } else { | ||||
li.appendTo(parent); | |||||
if(prepend) { | |||||
li.prependTo(parent); | |||||
} else { | |||||
li.appendTo(parent); | |||||
} | |||||
} | } | ||||
return link; | return link; | ||||
}, | }, | ||||
@@ -145,7 +145,7 @@ frappe.search.verbs = [ | |||||
value: __('Find {0} in {1}', [txt, route[1]]), | value: __('Find {0} in {1}', [txt, route[1]]), | ||||
route_options: options, | route_options: options, | ||||
onclick: function() { | onclick: function() { | ||||
frappe.container.page.doclistview.set_route_options(); | |||||
cur_list.refresh(); | |||||
}, | }, | ||||
match: txt | match: txt | ||||
}); | }); | ||||
@@ -34,6 +34,10 @@ frappe.upload = { | |||||
$file_input.trigger("change"); | $file_input.trigger("change"); | ||||
}); | }); | ||||
if(opts.on_select) { | |||||
opts.on_select(); | |||||
} | |||||
} else { | } else { | ||||
$upload.find(".uploaded-filename").addClass("hidden") | $upload.find(".uploaded-filename").addClass("hidden") | ||||
$upload.find(".web-link-wrapper").removeClass("hidden"); | $upload.find(".web-link-wrapper").removeClass("hidden"); | ||||
@@ -119,13 +123,13 @@ frappe.upload = { | |||||
freader.onload = function() { | freader.onload = function() { | ||||
args.filename = fileobj.name; | args.filename = fileobj.name; | ||||
if(opts.options && opts.options.toLowerCase()=="image") { | if(opts.options && opts.options.toLowerCase()=="image") { | ||||
if(!(/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(args.filename)) { | |||||
if(!frappe.utils.is_image_file(args.filename)) { | |||||
msgprint(__("Only image extensions (.gif, .jpg, .jpeg, .tiff, .png, .svg) allowed")); | msgprint(__("Only image extensions (.gif, .jpg, .jpeg, .tiff, .png, .svg) allowed")); | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
if((opts.max_width || opts.max_height) && (/\.(gif|jpg|jpeg|tiff|png)$/i).test(args.filename)) { | |||||
if((opts.max_width || opts.max_height) && frappe.utils.is_image_file(args.filename)) { | |||||
frappe.utils.resize_image(freader, function(_dataurl) { | frappe.utils.resize_image(freader, function(_dataurl) { | ||||
dataurl = _dataurl; | dataurl = _dataurl; | ||||
args.filedata = _dataurl.split(",")[1]; | args.filedata = _dataurl.split(",")[1]; | ||||
@@ -4,7 +4,9 @@ | |||||
frappe.breadcrumbs = { | frappe.breadcrumbs = { | ||||
all: {}, | all: {}, | ||||
preferred: {}, | |||||
preferred: { | |||||
"File": "" | |||||
}, | |||||
set_doctype_module: function(doctype, module) { | set_doctype_module: function(doctype, module) { | ||||
localStorage["preferred_breadcrumbs:" + doctype] = module; | localStorage["preferred_breadcrumbs:" + doctype] = module; | ||||
@@ -33,7 +35,7 @@ frappe.breadcrumbs = { | |||||
if(from_module) { | if(from_module) { | ||||
breadcrumbs.module = from_module; | breadcrumbs.module = from_module; | ||||
} else if(frappe.breadcrumbs.preferred[breadcrumbs.doctype]) { | |||||
} else if(frappe.breadcrumbs.preferred[breadcrumbs.doctype]!==undefined) { | |||||
// get preferred module for breadcrumbs | // get preferred module for breadcrumbs | ||||
breadcrumbs.module = frappe.breadcrumbs.preferred[breadcrumbs.doctype]; | breadcrumbs.module = frappe.breadcrumbs.preferred[breadcrumbs.doctype]; | ||||
} | } | ||||
@@ -60,8 +62,9 @@ frappe.breadcrumbs = { | |||||
if(breadcrumbs.doctype==="User" && frappe.user.modules.indexOf("Setup")===-1) { | if(breadcrumbs.doctype==="User" && frappe.user.modules.indexOf("Setup")===-1) { | ||||
// no user listview for non-system managers | // no user listview for non-system managers | ||||
} else { | } else { | ||||
$(repl('<li><a href="#List/%(doctype)s">%(label)s</a></li>', | |||||
{doctype: breadcrumbs.doctype, label: __(breadcrumbs.doctype)})) | |||||
route = (cur_frm && cur_frm.list_route) || ("List/" + breadcrumbs.doctype) | |||||
$(repl('<li><a href="#%(route)s">%(label)s</a></li>', | |||||
{route: route, label: __(breadcrumbs.doctype)})) | |||||
.appendTo($breadcrumbs); | .appendTo($breadcrumbs); | ||||
} | } | ||||
} | } | ||||
@@ -13,7 +13,9 @@ frappe.views.Factory = Class.extend({ | |||||
me = this; | me = this; | ||||
if(frappe.pages[page_name] && page_name.indexOf("Form/")===-1) { | if(frappe.pages[page_name] && page_name.indexOf("Form/")===-1) { | ||||
frappe.container.change_to(frappe.pages[page_name]); | frappe.container.change_to(frappe.pages[page_name]); | ||||
if(me.on_show) me.on_show(); | |||||
if(me.on_show) { | |||||
me.on_show(); | |||||
} | |||||
} else { | } else { | ||||
var route = frappe.get_route(); | var route = frappe.get_route(); | ||||
if(route[1]) { | if(route[1]) { | ||||
@@ -23,14 +25,16 @@ frappe.views.Factory = Class.extend({ | |||||
} | } | ||||
} | } | ||||
}, | }, | ||||
make_page: function(double_column) { | |||||
return frappe.make_page(double_column); | |||||
make_page: function(double_column, page_name) { | |||||
return frappe.make_page(double_column, page_name); | |||||
} | } | ||||
}); | }); | ||||
frappe.make_page = function(double_column) { | |||||
var page_name = frappe.get_route_str(), | |||||
page = frappe.container.add_page(page_name); | |||||
frappe.make_page = function(double_column, page_name) { | |||||
if(!page_name) { | |||||
var page_name = frappe.get_route_str(); | |||||
} | |||||
var page = frappe.container.add_page(page_name); | |||||
frappe.ui.make_app_page({ | frappe.ui.make_app_page({ | ||||
parent: page, | parent: page, | ||||
@@ -377,3 +377,14 @@ ul.linked-with-list li { | |||||
height: 451px; | height: 451px; | ||||
font-family: Monaco, "Courier New", monospace; | font-family: Monaco, "Courier New", monospace; | ||||
} | } | ||||
.breadcrumb { | |||||
font-size: 12px; | |||||
background-color: #fff; | |||||
} | |||||
.breadcrumb.for-file-list { | |||||
margin-bottom: 0px; | |||||
border-bottom: 1px solid @border-color; | |||||
border-radius: 0px; | |||||
} |
@@ -18,7 +18,7 @@ def read_csv_content_from_uploaded_file(ignore_encoding=False): | |||||
return read_csv_content(fcontent, ignore_encoding) | return read_csv_content(fcontent, ignore_encoding) | ||||
def read_csv_content_from_attached_file(doc): | def read_csv_content_from_attached_file(doc): | ||||
fileid = frappe.db.get_value("File Data", {"attached_to_doctype": doc.doctype, | |||||
fileid = frappe.db.get_value("File", {"attached_to_doctype": doc.doctype, | |||||
"attached_to_name":doc.name}, "name") | "attached_to_name":doc.name}, "name") | ||||
if not fileid: | if not fileid: | ||||
@@ -14,13 +14,14 @@ from copy import copy | |||||
class MaxFileSizeReachedError(frappe.ValidationError): pass | class MaxFileSizeReachedError(frappe.ValidationError): pass | ||||
def get_file_url(file_data_name): | def get_file_url(file_data_name): | ||||
data = frappe.db.get_value("File Data", file_data_name, ["file_name", "file_url"], as_dict=True) | |||||
data = frappe.db.get_value("File", file_data_name, ["file_name", "file_url"], as_dict=True) | |||||
return data.file_url or data.file_name | return data.file_url or data.file_name | ||||
def upload(): | def upload(): | ||||
# get record details | # get record details | ||||
dt = frappe.form_dict.doctype | dt = frappe.form_dict.doctype | ||||
dn = frappe.form_dict.docname | dn = frappe.form_dict.docname | ||||
folder = frappe.form_dict.folder | |||||
file_url = frappe.form_dict.file_url | file_url = frappe.form_dict.file_url | ||||
filename = frappe.form_dict.filename | filename = frappe.form_dict.filename | ||||
@@ -30,11 +31,11 @@ def upload(): | |||||
# save | # save | ||||
if filename: | if filename: | ||||
filedata = save_uploaded(dt, dn) | |||||
filedata = save_uploaded(dt, dn, folder) | |||||
elif file_url: | elif file_url: | ||||
filedata = save_url(file_url, dt, dn) | |||||
filedata = save_url(file_url, dt, dn, folder) | |||||
comment = {} | |||||
if dt and dn: | if dt and dn: | ||||
comment = frappe.get_doc(dt, dn).add_comment("Attachment", | comment = frappe.get_doc(dt, dn).add_comment("Attachment", | ||||
_("Added {0}").format("<a href='{file_url}' target='_blank'>{file_name}</a>".format(**filedata.as_dict()))) | _("Added {0}").format("<a href='{file_url}' target='_blank'>{file_name}</a>".format(**filedata.as_dict()))) | ||||
@@ -43,32 +44,33 @@ def upload(): | |||||
"name": filedata.name, | "name": filedata.name, | ||||
"file_name": filedata.file_name, | "file_name": filedata.file_name, | ||||
"file_url": filedata.file_url, | "file_url": filedata.file_url, | ||||
"comment": comment.as_dict() | |||||
"comment": comment.as_dict() if comment else {} | |||||
} | } | ||||
def save_uploaded(dt, dn): | |||||
def save_uploaded(dt, dn, folder): | |||||
fname, content = get_uploaded_content() | fname, content = get_uploaded_content() | ||||
if content: | if content: | ||||
return save_file(fname, content, dt, dn); | |||||
return save_file(fname, content, dt, dn, folder); | |||||
else: | else: | ||||
raise Exception | raise Exception | ||||
def save_url(file_url, dt, dn): | |||||
def save_url(file_url, dt, dn, folder): | |||||
# if not (file_url.startswith("http://") or file_url.startswith("https://")): | # if not (file_url.startswith("http://") or file_url.startswith("https://")): | ||||
# frappe.msgprint("URL must start with 'http://' or 'https://'") | # frappe.msgprint("URL must start with 'http://' or 'https://'") | ||||
# return None, None | # return None, None | ||||
f = frappe.get_doc({ | f = frappe.get_doc({ | ||||
"doctype": "File Data", | |||||
"doctype": "File", | |||||
"file_url": file_url, | "file_url": file_url, | ||||
"attached_to_doctype": dt, | "attached_to_doctype": dt, | ||||
"attached_to_name": dn | |||||
"attached_to_name": dn, | |||||
"folder": folder | |||||
}) | }) | ||||
f.flags.ignore_permissions = True | f.flags.ignore_permissions = True | ||||
try: | try: | ||||
f.insert(); | f.insert(); | ||||
except frappe.DuplicateEntryError: | except frappe.DuplicateEntryError: | ||||
return frappe.get_doc("File Data", f.duplicate_entry) | |||||
return frappe.get_doc("File", f.duplicate_entry) | |||||
return f | return f | ||||
def get_uploaded_content(): | def get_uploaded_content(): | ||||
@@ -128,7 +130,7 @@ def get_random_filename(extn=None, content_type=None): | |||||
return random_string(7) + (extn or "") | return random_string(7) + (extn or "") | ||||
def save_file(fname, content, dt, dn, decode=False): | |||||
def save_file(fname, content, dt, dn, folder=None, decode=False): | |||||
if decode: | if decode: | ||||
if isinstance(content, unicode): | if isinstance(content, unicode): | ||||
content = content.encode("utf-8") | content = content.encode("utf-8") | ||||
@@ -148,9 +150,10 @@ def save_file(fname, content, dt, dn, decode=False): | |||||
file_data = copy(file_data) | file_data = copy(file_data) | ||||
file_data.update({ | file_data.update({ | ||||
"doctype": "File Data", | |||||
"doctype": "File", | |||||
"attached_to_doctype": dt, | "attached_to_doctype": dt, | ||||
"attached_to_name": dn, | "attached_to_name": dn, | ||||
"folder": folder, | |||||
"file_size": file_size, | "file_size": file_size, | ||||
"content_hash": content_hash, | "content_hash": content_hash, | ||||
}) | }) | ||||
@@ -160,12 +163,12 @@ def save_file(fname, content, dt, dn, decode=False): | |||||
try: | try: | ||||
f.insert(); | f.insert(); | ||||
except frappe.DuplicateEntryError: | except frappe.DuplicateEntryError: | ||||
return frappe.get_doc("File Data", f.duplicate_entry) | |||||
return frappe.get_doc("File", f.duplicate_entry) | |||||
return f | return f | ||||
def get_file_data_from_hash(content_hash): | def get_file_data_from_hash(content_hash): | ||||
for name in frappe.db.sql_list("select name from `tabFile Data` where content_hash=%s", content_hash): | |||||
b = frappe.get_doc('File Data', name) | |||||
for name in frappe.db.sql_list("select name from `tabFile` where content_hash=%s", content_hash): | |||||
b = frappe.get_doc('File', name) | |||||
return {k:b.get(k) for k in frappe.get_hooks()['write_file_keys']} | return {k:b.get(k) for k in frappe.get_hooks()['write_file_keys']} | ||||
return False | return False | ||||
@@ -201,7 +204,7 @@ def write_file(content, file_path, fname): | |||||
def remove_all(dt, dn): | def remove_all(dt, dn): | ||||
"""remove all files in a transaction""" | """remove all files in a transaction""" | ||||
try: | try: | ||||
for fid in frappe.db.sql_list("""select name from `tabFile Data` where | |||||
for fid in frappe.db.sql_list("""select name from `tabFile` where | |||||
attached_to_doctype=%s and attached_to_name=%s""", (dt, dn)): | attached_to_doctype=%s and attached_to_name=%s""", (dt, dn)): | ||||
remove_file(fid, dt, dn) | remove_file(fid, dt, dn) | ||||
except Exception, e: | except Exception, e: | ||||
@@ -209,19 +212,19 @@ def remove_all(dt, dn): | |||||
def remove_file_by_url(file_url, doctype=None, name=None): | def remove_file_by_url(file_url, doctype=None, name=None): | ||||
if doctype and name: | if doctype and name: | ||||
fid = frappe.db.get_value("File Data", {"file_url": file_url, | |||||
fid = frappe.db.get_value("File", {"file_url": file_url, | |||||
"attached_to_doctype": doctype, "attached_to_name": name}) | "attached_to_doctype": doctype, "attached_to_name": name}) | ||||
else: | else: | ||||
fid = frappe.db.get_value("File Data", {"file_url": file_url}) | |||||
fid = frappe.db.get_value("File", {"file_url": file_url}) | |||||
if fid: | if fid: | ||||
return remove_file(fid) | return remove_file(fid) | ||||
def remove_file(fid, attached_to_doctype=None, attached_to_name=None): | def remove_file(fid, attached_to_doctype=None, attached_to_name=None): | ||||
"""Remove file and File Data entry""" | |||||
"""Remove file and File entry""" | |||||
file_name = None | file_name = None | ||||
if not (attached_to_doctype and attached_to_name): | if not (attached_to_doctype and attached_to_name): | ||||
attached = frappe.db.get_value("File Data", fid, | |||||
attached = frappe.db.get_value("File", fid, | |||||
["attached_to_doctype", "attached_to_name", "file_name"]) | ["attached_to_doctype", "attached_to_name", "file_name"]) | ||||
if attached: | if attached: | ||||
attached_to_doctype, attached_to_name, file_name = attached | attached_to_doctype, attached_to_name, file_name = attached | ||||
@@ -231,10 +234,10 @@ def remove_file(fid, attached_to_doctype=None, attached_to_name=None): | |||||
doc = frappe.get_doc(attached_to_doctype, attached_to_name) | doc = frappe.get_doc(attached_to_doctype, attached_to_name) | ||||
ignore_permissions = doc.has_permission("write") or False | ignore_permissions = doc.has_permission("write") or False | ||||
if not file_name: | if not file_name: | ||||
file_name = frappe.db.get_value("File Data", fid, "file_name") | |||||
file_name = frappe.db.get_value("File", fid, "file_name") | |||||
comment = doc.add_comment("Attachment Removed", _("Removed {0}").format(file_name)) | comment = doc.add_comment("Attachment Removed", _("Removed {0}").format(file_name)) | ||||
frappe.delete_doc("File Data", fid, ignore_permissions=ignore_permissions) | |||||
frappe.delete_doc("File", fid, ignore_permissions=ignore_permissions) | |||||
return comment | return comment | ||||
@@ -256,7 +259,7 @@ def delete_file_from_filesystem(doc): | |||||
os.remove(path) | os.remove(path) | ||||
def get_file(fname): | def get_file(fname): | ||||
f = frappe.db.sql("""select file_name from `tabFile Data` | |||||
f = frappe.db.sql("""select file_name from `tabFile` | |||||
where name=%s or file_name=%s""", (fname, fname)) | where name=%s or file_name=%s""", (fname, fname)) | ||||
if f: | if f: | ||||
file_name = f[0][0] | file_name = f[0][0] | ||||
@@ -281,7 +284,7 @@ def get_file_name(fname, optional_suffix): | |||||
# convert to unicode | # convert to unicode | ||||
fname = cstr(fname) | fname = cstr(fname) | ||||
n_records = frappe.db.sql("select name from `tabFile Data` where file_name=%s", fname) | |||||
n_records = frappe.db.sql("select name from `tabFile` where file_name=%s", fname) | |||||
if len(n_records) > 0 or os.path.exists(encode(get_files_path(fname))): | if len(n_records) > 0 or os.path.exists(encode(get_files_path(fname))): | ||||
f = fname.rsplit('.', 1) | f = fname.rsplit('.', 1) | ||||
if len(f) == 1: | if len(f) == 1: | ||||
@@ -290,4 +293,3 @@ def get_file_name(fname, optional_suffix): | |||||
partial, extn = f[0], "." + f[1] | partial, extn = f[0], "." + f[1] | ||||
return '{partial}{suffix}{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix) | return '{partial}{suffix}{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix) | ||||
return fname | return fname | ||||
@@ -39,6 +39,9 @@ def after_install(): | |||||
{'doctype': "Email Account", "email_id": "replies@example.com", "default_incoming": 1} | {'doctype': "Email Account", "email_id": "replies@example.com", "default_incoming": 1} | ||||
] | ] | ||||
from frappe.core.doctype.file.file import make_home_folder | |||||
make_home_folder() | |||||
for d in install_docs: | for d in install_docs: | ||||
try: | try: | ||||
frappe.get_doc(d).insert() | frappe.get_doc(d).insert() | ||||
@@ -226,6 +226,9 @@ class NestedSet(Document): | |||||
(self.doctype, self.nsm_parent_field, '%s'), (self.name)): | (self.doctype, self.nsm_parent_field, '%s'), (self.name)): | ||||
frappe.throw(_("{0} {1} cannot be a leaf node as it has children").format(_(self.doctype), self.name)) | frappe.throw(_("{0} {1} cannot be a leaf node as it has children").format(_(self.doctype), self.name)) | ||||
def get_ancestors(self): | |||||
return get_ancestors_of(self.doctype, self.name) | |||||
def get_root_of(doctype): | def get_root_of(doctype): | ||||
"""Get root element of a DocType with a tree structure""" | """Get root element of a DocType with a tree structure""" | ||||
return frappe.db.sql("""select t1.name from `tab{0}` t1 where | return frappe.db.sql("""select t1.name from `tab{0}` t1 where | ||||