[enhancement] document management systemversion-14
@@ -38,9 +38,12 @@ class _dict(dict): | |||
def copy(self): | |||
return _dict(dict(self).copy()) | |||
def _(msg): | |||
def _(msg, lang=None): | |||
"""Returns translated string in current lang, if exists.""" | |||
if local.lang == "en": | |||
if not lang: | |||
lang = local.lang | |||
if lang == "en": | |||
return msg | |||
from frappe.translate import get_full_dict | |||
@@ -905,6 +908,20 @@ def get_all(doctype, *args, **kwargs): | |||
kwargs["limit_page_length"] = 0 | |||
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): | |||
"""Insert a new **Version** of the given document. | |||
A **Version** is a JSON dump of the current document state.""" | |||
@@ -53,6 +53,7 @@ def get_bootinfo(): | |||
load_conf_settings(bootinfo) | |||
load_print(bootinfo, doclist) | |||
doclist.extend(get_meta_bundle("Page")) | |||
bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1}) | |||
# ipinfo | |||
if frappe.session['data'].get('ipinfo'): | |||
@@ -45,6 +45,15 @@ def get_data(): | |||
"link": "List/Note", | |||
"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": { | |||
"color": "#16a085", | |||
"icon": "icon-globe", | |||
@@ -100,7 +100,7 @@ def get_data(): | |||
}, | |||
{ | |||
"type": "doctype", | |||
"name": "File Data", | |||
"name": "File", | |||
"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 | |||
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 | |||
fpath = os.path.join(path, scrub(self.name) + '.js') | |||
fpath = os.path.join(path, page_name + '.js') | |||
if os.path.exists(fpath): | |||
with open(fpath, 'r') as f: | |||
self.script = unicode(f.read(), "utf-8") | |||
# css | |||
fpath = os.path.join(path, scrub(self.name) + '.css') | |||
fpath = os.path.join(path, page_name + '.css') | |||
if os.path.exists(fpath): | |||
with open(fpath, 'r') as f: | |||
self.style = unicode(f.read(), "utf-8") | |||
@@ -109,3 +111,5 @@ class Page(Document): | |||
if frappe.lang != 'en': | |||
from frappe.translate import get_lang_js | |||
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; | |||
-- | |||
-- 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, | |||
`creation` datetime(6) DEFAULT NULL, | |||
`modified` datetime(6) DEFAULT NULL, | |||
@@ -100,7 +100,7 @@ def get_user_permissions(meta): | |||
return out | |||
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}) | |||
def get_comments(dt, dn, limit=100): | |||
@@ -121,7 +121,7 @@ def get_comments(dt, dn, limit=100): | |||
as_dict=True) | |||
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"], | |||
filters={"attached_to_doctype": "Communication", | |||
"attached_to_name": c.name} | |||
@@ -59,14 +59,18 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, | |||
# build from doctype | |||
if txt: | |||
search_fields = ["name"] | |||
if meta.title_field: | |||
search_fields.append(meta.title_field) | |||
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"}): | |||
filters.append([doctype, "enabled", "=", 1]) | |||
@@ -41,8 +41,8 @@ class TestEmailAccount(unittest.TestCase): | |||
def test_incoming_with_attach(self): | |||
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) | |||
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"]}, | |||
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) | |||
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) | |||
delete_from_table(doctype, name, ignore_doctypes, doc) | |||
doc.run_method("after_delete") | |||
if doc: | |||
try: | |||
@@ -86,6 +86,10 @@ class Document(BaseDocument): | |||
self._default_new_docs = {} | |||
self.flags = frappe._dict() | |||
def reload(self): | |||
"""Reload document from database""" | |||
self.load_from_db() | |||
def load_from_db(self): | |||
"""Load document and children from database and create properties | |||
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): | |||
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: | |||
if e.args[0]!=1054: # in patch? | |||
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")): | |||
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) | |||
# 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_2.ignore_user_permissions_if_missing | |||
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(): | |||
frappe.reload_doc('core', 'doctype', 'file_data') | |||
for name, file_name, file_url in frappe.db.sql( | |||
"""select name, file_name, file_url from `tabFile Data` | |||
"""select name, file_name, file_url from `tabFile` | |||
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 | |||
b.file_name = os.path.basename(old_file_name) | |||
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 | |||
# a backup from a time before version 3 migration | |||
# | |||
# * Patch remaining unpatched file data records. | |||
# * Patch remaining unpatched File records. | |||
def execute(): | |||
frappe.db.auto_commit_on_many_writes = True | |||
rename_replacing_files() | |||
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, '')=''"""): | |||
b = frappe.get_doc('File Data', name) | |||
b = frappe.get_doc('File', name) | |||
old_file_name = b.file_name | |||
b.file_name = os.path.basename(old_file_name) | |||
if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): | |||
@@ -45,8 +45,8 @@ def execute(): | |||
def get_replaced_files(): | |||
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) | |||
for nname, nfilename in new_files.iteritems(): | |||
@@ -63,7 +63,7 @@ def rename_replacing_files(): | |||
for file_name, file_datas in replaced_files: | |||
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: | |||
continue | |||
new_file_name = get_file_name(file_name, content_hash) | |||
@@ -75,7 +75,7 @@ def rename_replacing_files(): | |||
except OSError: | |||
print 'Error renaming ', file_name | |||
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_url = '/files/' + new_file_name | |||
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 verbose: print "Shared" | |||
if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype): | |||
if verbose: print "Is shared" | |||
return True | |||
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" | |||
return True | |||
if verbose: print "Not Shared" | |||
return False | |||
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): | |||
return false_if_not_shared() | |||
perm = True | |||
if doc: | |||
if isinstance(doc, basestring): | |||
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 | |||
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): | |||
"""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 | |||
def has_controller_permissions(doc, ptype, user=None): | |||
"""Returns controller permissions if defined. None if not defined""" | |||
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): | |||
return False | |||
@@ -521,3 +521,12 @@ ul.linked-with-list li { | |||
height: 451px; | |||
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) { | |||
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() { | |||
return { | |||
@@ -213,3 +199,38 @@ frappe.ui.form.Attachments = Class.extend({ | |||
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) { | |||
frappe.set_re_route("Form", doctype); | |||
} 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(); | |||
} | |||
}); | |||
@@ -44,13 +48,18 @@ $(document).on("save", function(event, doc) { | |||
frappe.views.set_list_as_dirty = function(doctype) { | |||
var list_page = "List/" + doctype; | |||
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; | |||
} | |||
} | |||
var route = frappe.get_route(); | |||
if(route[0]==="List" && route[1]===doctype) { | |||
setTimeout(function() { | |||
frappe.pages[list_page].doclistview.run(); | |||
frappe.pages[list_page].doclistview.refresh(); | |||
}, 100); | |||
} | |||
} | |||
@@ -65,6 +74,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
}; | |||
this.label = __(this.doctype); | |||
this.page_name = "List/" + this.doctype; | |||
this.dirty = true; | |||
this.tags_shown = false; | |||
this.label = (this.label.toLowerCase().substr(-4) == 'list') ? | |||
@@ -118,7 +128,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
&& !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")); | |||
}, | |||
@@ -153,7 +163,9 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
added = true; | |||
} | |||
}); | |||
added && me.run(); | |||
if(added) { | |||
me.refresh(true); | |||
} | |||
}); | |||
this.$page.find(".result-list").on("click", ".list-row-left", function(e) { | |||
// 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 | |||
// 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)) | |||
this.refresh(); | |||
}, | |||
refresh: function() { | |||
var me = this; | |||
refresh: function(dirty) { | |||
if(dirty!==undefined) this.dirty = dirty; | |||
this.init_stats(); | |||
if(this.listview.settings.refresh) { | |||
@@ -235,13 +252,14 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
} | |||
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 { | |||
if(new Date() - (me.last_updated_on || 0) > 30000) { | |||
if(new Date() - (this.last_updated_on || 0) > 30000) { | |||
// older than 5 mins, refresh | |||
me.run(); | |||
this.run(); | |||
} | |||
} | |||
}, | |||
@@ -265,17 +283,28 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
} | |||
}); | |||
frappe.route_options = null; | |||
me.run(); | |||
}, | |||
run: function(more) { | |||
// set filter from 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); | |||
}); | |||
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") | |||
@@ -283,7 +312,25 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
this.last_updated_on = new Date(); | |||
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); | |||
if(this.listview.settings.post_render) { | |||
this.listview.settings.post_render(this); | |||
} | |||
}, | |||
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.set_secondary_action(__("Refresh"), function() { | |||
me.run(); | |||
me.dirty = true; | |||
me.refresh(); | |||
}, "octicon octicon-sync"); | |||
this.page.btn_secondary.addClass("hidden-xs"); | |||
this.page.add_menu_item(__("Refresh"), function() { | |||
me.run(); | |||
me.dirty = true; | |||
me.refresh(); | |||
}, "octicon octicon-sync").addClass("visible-xs"); | |||
if(frappe.model.can_import(this.doctype)) { | |||
@@ -526,7 +575,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
callback: function(r) { | |||
if(!r.exc) { | |||
me.list_header.find(".list-select-all").prop("checked", false); | |||
me.run(); | |||
me.refresh(); | |||
} | |||
} | |||
}); | |||
@@ -20,7 +20,7 @@ | |||
{% if (col.type==="Subject") { %} | |||
{%= subject %} | |||
{% } else if (col.type==="Indicator") { %} | |||
{%= me.get_indicator(data) %} | |||
{%= list.get_indicator(data) %} | |||
{% } else if (col.render) { %} | |||
{%= col.render(data) %} | |||
{% } else if (col.fieldtype==="Image") { %} | |||
@@ -36,7 +36,12 @@ | |||
<a class="filterable h6 text-muted grey" | |||
data-filter="{%= col.fieldname %},=,{%= value %}">{%= value %}</a> | |||
{% } 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>{% } %} | |||
</div> | |||
@@ -1,6 +1,6 @@ | |||
<div class="row doclist-row {% if (data._checkbox) { %} has-checkbox {% } %}"> | |||
<div class="col-xs-10 | |||
{% if (list.meta.title_field) { %} | |||
{% if (list.meta.title_field && !list.settings.hide_name_column) { %} | |||
col-sm-8 | |||
{% } else { %} | |||
col-sm-10 | |||
@@ -10,7 +10,7 @@ | |||
</div> | |||
<!-- 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]; | |||
%} | |||
<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="row doclist-row"> | |||
<div class="col-xs-10 | |||
{% if (list.meta.title_field) { %} | |||
{% if (list.meta.title_field && !list.settings.hide_name_column) { %} | |||
col-sm-8 | |||
{% } else { %} | |||
col-sm-10 | |||
@@ -11,7 +11,7 @@ | |||
</div> | |||
<!-- 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> | |||
{% } %} | |||
@@ -1,14 +1,20 @@ | |||
{% 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> | |||
<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) { %} | |||
<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> | |||
{% } %} |
@@ -208,7 +208,7 @@ frappe.views.ListView = Class.extend({ | |||
data: data, | |||
columns: this.columns, | |||
subject: this.get_avatar_and_id(data, true), | |||
me: this, | |||
list: this, | |||
right_column: this.settings.right_column | |||
}); | |||
@@ -219,6 +219,10 @@ frappe.views.ListView = Class.extend({ | |||
right_column: this.settings.right_column | |||
})).appendTo(row); | |||
if(this.settings.post_render_item) { | |||
this.settings.post_render_item(this, row, data); | |||
} | |||
this.render_tags(row, data); | |||
}, | |||
@@ -489,5 +489,9 @@ frappe.utils = { | |||
// reset the 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 | |||
if(docinfo.shared) { | |||
for(var i=0; i<docinfo.shared; i++) { | |||
@@ -78,6 +92,7 @@ $.extend(frappe.perm, { | |||
} | |||
} | |||
} | |||
} | |||
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 = []; | |||
}, | |||
add_filter: function(tablename, fieldname, condition, value) { | |||
add_filter: function(doctype, fieldname, condition, value, hidden) { | |||
this.$w.find('.show_filters').toggle(true); | |||
var is_new_filter = arguments.length===0; | |||
@@ -38,21 +38,26 @@ frappe.ui.FilterList = Class.extend({ | |||
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"); | |||
} | |||
if (filter && hidden) { | |||
filter.freeze(); | |||
filter.$btn_group.addClass("hide"); | |||
} | |||
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({ | |||
flist: this, | |||
tablename: tablename, | |||
doctype: doctype, | |||
fieldname: fieldname, | |||
condition: condition, | |||
value: value, | |||
@@ -63,11 +68,11 @@ frappe.ui.FilterList = Class.extend({ | |||
return filter; | |||
}, | |||
filter_exists: function(tablename, fieldname, condition, value) { | |||
filter_exists: function(doctype, fieldname, condition, value) { | |||
for(var i in this.filters) { | |||
if(this.filters[i].field) { | |||
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; | |||
} | |||
@@ -127,7 +132,7 @@ frappe.ui.Filter = Class.extend({ | |||
} | |||
}); | |||
if(this.fieldname) { | |||
this.fieldselect.set_value(this.tablename, this.fieldname); | |||
this.fieldselect.set_value(this.doctype, this.fieldname); | |||
} | |||
}, | |||
set_events: function() { | |||
@@ -163,7 +168,7 @@ frappe.ui.Filter = Class.extend({ | |||
// set the field | |||
if(me.fieldname) { | |||
// 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 { | |||
me.set_field(me.doctype, 'name'); | |||
} | |||
@@ -173,17 +178,17 @@ frappe.ui.Filter = Class.extend({ | |||
this.$w.remove(); | |||
this.$btn_group && this.$btn_group.remove(); | |||
this.field = null; | |||
this.flist.update_filters(); | |||
if(!dont_run) { | |||
this.flist.update_filters(); | |||
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!) | |||
this.set_field(tablename, fieldname); | |||
this.set_field(doctype, fieldname); | |||
if(condition) this.$w.find('.condition').val(condition).change(); | |||
if(value!=null) this.field.set_input(value); | |||
}, | |||
@@ -118,8 +118,12 @@ frappe.ui.Listing = Class.extend({ | |||
set_primary_action: function() { | |||
var me = this; | |||
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 { | |||
this.page.clear_primary_action(); | |||
} | |||
@@ -234,7 +234,7 @@ frappe.ui.Page = Class.extend({ | |||
//-- 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 li = $('<li>'); | |||
var link = $('<a>').html(label).on("click", action).appendTo(li); | |||
@@ -242,7 +242,11 @@ frappe.ui.Page = Class.extend({ | |||
if(insert_after) { | |||
li.insertAfter(parent.find(insert_after)); | |||
} else { | |||
li.appendTo(parent); | |||
if(prepend) { | |||
li.prependTo(parent); | |||
} else { | |||
li.appendTo(parent); | |||
} | |||
} | |||
return link; | |||
}, | |||
@@ -145,7 +145,7 @@ frappe.search.verbs = [ | |||
value: __('Find {0} in {1}', [txt, route[1]]), | |||
route_options: options, | |||
onclick: function() { | |||
frappe.container.page.doclistview.set_route_options(); | |||
cur_list.refresh(); | |||
}, | |||
match: txt | |||
}); | |||
@@ -34,6 +34,10 @@ frappe.upload = { | |||
$file_input.trigger("change"); | |||
}); | |||
if(opts.on_select) { | |||
opts.on_select(); | |||
} | |||
} else { | |||
$upload.find(".uploaded-filename").addClass("hidden") | |||
$upload.find(".web-link-wrapper").removeClass("hidden"); | |||
@@ -119,13 +123,13 @@ frappe.upload = { | |||
freader.onload = function() { | |||
args.filename = fileobj.name; | |||
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")); | |||
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) { | |||
dataurl = _dataurl; | |||
args.filedata = _dataurl.split(",")[1]; | |||
@@ -4,7 +4,9 @@ | |||
frappe.breadcrumbs = { | |||
all: {}, | |||
preferred: {}, | |||
preferred: { | |||
"File": "" | |||
}, | |||
set_doctype_module: function(doctype, module) { | |||
localStorage["preferred_breadcrumbs:" + doctype] = module; | |||
@@ -33,7 +35,7 @@ frappe.breadcrumbs = { | |||
if(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 | |||
breadcrumbs.module = frappe.breadcrumbs.preferred[breadcrumbs.doctype]; | |||
} | |||
@@ -60,8 +62,9 @@ frappe.breadcrumbs = { | |||
if(breadcrumbs.doctype==="User" && frappe.user.modules.indexOf("Setup")===-1) { | |||
// no user listview for non-system managers | |||
} 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); | |||
} | |||
} | |||
@@ -13,7 +13,9 @@ frappe.views.Factory = Class.extend({ | |||
me = this; | |||
if(frappe.pages[page_name] && page_name.indexOf("Form/")===-1) { | |||
frappe.container.change_to(frappe.pages[page_name]); | |||
if(me.on_show) me.on_show(); | |||
if(me.on_show) { | |||
me.on_show(); | |||
} | |||
} else { | |||
var route = frappe.get_route(); | |||
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({ | |||
parent: page, | |||
@@ -377,3 +377,14 @@ ul.linked-with-list li { | |||
height: 451px; | |||
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) | |||
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") | |||
if not fileid: | |||
@@ -14,13 +14,14 @@ from copy import copy | |||
class MaxFileSizeReachedError(frappe.ValidationError): pass | |||
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 | |||
def upload(): | |||
# get record details | |||
dt = frappe.form_dict.doctype | |||
dn = frappe.form_dict.docname | |||
folder = frappe.form_dict.folder | |||
file_url = frappe.form_dict.file_url | |||
filename = frappe.form_dict.filename | |||
@@ -30,11 +31,11 @@ def upload(): | |||
# save | |||
if filename: | |||
filedata = save_uploaded(dt, dn) | |||
filedata = save_uploaded(dt, dn, folder) | |||
elif file_url: | |||
filedata = save_url(file_url, dt, dn) | |||
filedata = save_url(file_url, dt, dn, folder) | |||
comment = {} | |||
if dt and dn: | |||
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()))) | |||
@@ -43,32 +44,33 @@ def upload(): | |||
"name": filedata.name, | |||
"file_name": filedata.file_name, | |||
"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() | |||
if content: | |||
return save_file(fname, content, dt, dn); | |||
return save_file(fname, content, dt, dn, folder); | |||
else: | |||
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://")): | |||
# frappe.msgprint("URL must start with 'http://' or 'https://'") | |||
# return None, None | |||
f = frappe.get_doc({ | |||
"doctype": "File Data", | |||
"doctype": "File", | |||
"file_url": file_url, | |||
"attached_to_doctype": dt, | |||
"attached_to_name": dn | |||
"attached_to_name": dn, | |||
"folder": folder | |||
}) | |||
f.flags.ignore_permissions = True | |||
try: | |||
f.insert(); | |||
except frappe.DuplicateEntryError: | |||
return frappe.get_doc("File Data", f.duplicate_entry) | |||
return frappe.get_doc("File", f.duplicate_entry) | |||
return f | |||
def get_uploaded_content(): | |||
@@ -128,7 +130,7 @@ def get_random_filename(extn=None, content_type=None): | |||
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 isinstance(content, unicode): | |||
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.update({ | |||
"doctype": "File Data", | |||
"doctype": "File", | |||
"attached_to_doctype": dt, | |||
"attached_to_name": dn, | |||
"folder": folder, | |||
"file_size": file_size, | |||
"content_hash": content_hash, | |||
}) | |||
@@ -160,12 +163,12 @@ def save_file(fname, content, dt, dn, decode=False): | |||
try: | |||
f.insert(); | |||
except frappe.DuplicateEntryError: | |||
return frappe.get_doc("File Data", f.duplicate_entry) | |||
return frappe.get_doc("File", f.duplicate_entry) | |||
return f | |||
def get_file_data_from_hash(content_hash): | |||
for name in frappe.db.sql_list("select name from `tabFile Data` where content_hash=%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 False | |||
@@ -201,7 +204,7 @@ def write_file(content, file_path, fname): | |||
def remove_all(dt, dn): | |||
"""remove all files in a transaction""" | |||
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)): | |||
remove_file(fid, dt, dn) | |||
except Exception, e: | |||
@@ -209,19 +212,19 @@ def remove_all(dt, dn): | |||
def remove_file_by_url(file_url, doctype=None, name=None): | |||
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}) | |||
else: | |||
fid = frappe.db.get_value("File Data", {"file_url": file_url}) | |||
fid = frappe.db.get_value("File", {"file_url": file_url}) | |||
if fid: | |||
return remove_file(fid) | |||
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 | |||
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"]) | |||
if 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) | |||
ignore_permissions = doc.has_permission("write") or False | |||
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)) | |||
frappe.delete_doc("File Data", fid, ignore_permissions=ignore_permissions) | |||
frappe.delete_doc("File", fid, ignore_permissions=ignore_permissions) | |||
return comment | |||
@@ -256,7 +259,7 @@ def delete_file_from_filesystem(doc): | |||
os.remove(path) | |||
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)) | |||
if f: | |||
file_name = f[0][0] | |||
@@ -281,7 +284,7 @@ def get_file_name(fname, optional_suffix): | |||
# convert to unicode | |||
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))): | |||
f = fname.rsplit('.', 1) | |||
if len(f) == 1: | |||
@@ -290,4 +293,3 @@ def get_file_name(fname, optional_suffix): | |||
partial, extn = f[0], "." + f[1] | |||
return '{partial}{suffix}{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix) | |||
return fname | |||
@@ -39,6 +39,9 @@ def after_install(): | |||
{'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: | |||
try: | |||
frappe.get_doc(d).insert() | |||
@@ -226,6 +226,9 @@ class NestedSet(Document): | |||
(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)) | |||
def get_ancestors(self): | |||
return get_ancestors_of(self.doctype, self.name) | |||
def get_root_of(doctype): | |||
"""Get root element of a DocType with a tree structure""" | |||
return frappe.db.sql("""select t1.name from `tab{0}` t1 where | |||