From 9fbd12f5aff4cec858fb8ab37ecee4773dacc775 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 3 Sep 2015 17:37:52 +0530 Subject: [PATCH] [file-manager] fixed breadcrumbs and routing --- frappe/__init__.py | 19 ++- frappe/core/doctype/file/file.json | 111 ++++++++++++++---- frappe/core/doctype/file/file.py | 68 +++++++---- frappe/core/doctype/file/file_list.js | 55 ++++----- frappe/data/Framework.sql | 6 +- frappe/desk/search.py | 18 +-- frappe/model/document.py | 4 + frappe/patches/v6_1/rename_file_data.py | 7 +- frappe/public/css/desk.css | 7 +- frappe/public/js/frappe/list/doclistview.js | 33 ++++-- frappe/public/js/frappe/ui/filters/filters.js | 2 +- .../js/frappe/ui/toolbar/awesome_bar.js | 2 +- frappe/public/js/frappe/views/factory.js | 16 ++- frappe/public/less/desk.less | 8 +- frappe/utils/install.py | 3 + 15 files changed, 250 insertions(+), 109 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 2ab0c18038..dede506209 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -38,8 +38,11 @@ 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 not lang: + lang = local.lang + if local.lang == "en": return msg @@ -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.""" diff --git a/frappe/core/doctype/file/file.json b/frappe/core/doctype/file/file.json index 9a6c5d0cc8..a8ff6310c9 100644 --- a/frappe/core/doctype/file/file.json +++ b/frappe/core/doctype/file/file.json @@ -8,6 +8,29 @@ "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": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -34,9 +57,10 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "depends_on": "", "fieldname": "is_home_folder", "fieldtype": "Check", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, @@ -52,6 +76,49 @@ "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": "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, @@ -79,16 +146,14 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "file_name", - "fieldtype": "Data", + "fieldname": "file_size", + "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, "in_filter": 0, - "in_list_view": 0, - "label": "File Name", + "in_list_view": 1, + "label": "File Size", "no_copy": 0, - "oldfieldname": "file_name", - "oldfieldtype": "Data", "permlevel": 0, "print_hide": 0, "read_only": 1, @@ -102,17 +167,17 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "file_url", - "fieldtype": "Data", + "fieldname": "section_break_8", + "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, - "label": "File URL", "no_copy": 0, "permlevel": 0, + "precision": "", "print_hide": 0, - "read_only": 1, + "read_only": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -145,20 +210,20 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "attached_to_name", - "fieldtype": "Data", + "fieldname": "column_break_10", + "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, "in_filter": 0, "in_list_view": 0, - "label": "Attached To Name", "no_copy": 0, "permlevel": 0, + "precision": "", "print_hide": 0, - "read_only": 1, + "read_only": 0, "report_hide": 0, "reqd": 0, - "search_index": 1, + "search_index": 0, "set_only_once": 0, "unique": 0 }, @@ -166,20 +231,20 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "file_size", - "fieldtype": "Int", + "fieldname": "attached_to_name", + "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, "in_filter": 0, - "in_list_view": 1, - "label": "File Size", + "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": 0, + "search_index": 1, "set_only_once": 0, "unique": 0 }, @@ -275,12 +340,12 @@ "hide_toolbar": 0, "icon": "icon-file", "idx": 1, - "in_create": 1, + "in_create": 0, "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, - "modified": "2015-09-02 06:17:57.856863", + "modified": "2015-09-03 05:17:44.827959", "modified_by": "Administrator", "module": "Core", "name": "File", diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index ed775ed621..e234bc8c9d 100644 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -21,6 +21,20 @@ class File(NestedSet): def before_insert(self): frappe.local.rollback_observers.append(self) self.set_folder_name() + self.set_name() + + def set_name(self): + """Set name for folder""" + if self.is_folder: + if self.folder: + path = get_breadcrumbs(self.folder) + folder_name = frappe.get_value("File", self.folder, "file_name") + self.name = "/".join([d.file_name for d in path] + [folder_name, self.file_name]) + else: + # home + self.name = self.file_name + else: + self.name = self.file_url def after_insert(self): self.update_parent_folder_size() @@ -32,7 +46,7 @@ class File(NestedSet): def set_folder_size(self): """Set folder size if folder""" - if self.is_folder: + if self.is_folder and not self.is_new(): self.fize_size = self.get_folder_size() for folder in self.get_ancestors(): @@ -45,33 +59,33 @@ class File(NestedSet): return frappe.db.sql("""select sum(ifnull(file_size,0)) from tabFile where folder=%s""", folder)[0][0] + 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 = self.get_parent_folder_name() - def update_parent_folder_size(self): - """Update size of parent folder""" - if self.folder: # it not home - frappe.get_doc("File", self.folder).save() - def get_parent_folder_name(self): """Returns parent folder name. If not exists, then make""" + doctype_folder_name = self.get_doctype_folder_name() + parent_folder_name = frappe.db.get_value("File", {"file_name": self.attached_to_name, + "is_folder": 1, "folder": doctype_folder_name}) + + return self.make_folder(parent_folder_name, doctype_folder_name, + self.attached_to_name) + + def get_doctype_folder_name(self): + """Returns doctype folder name. If not exists, then make""" module_folder_name = self.get_module_folder_name() - parent_folder_name = frappe.db.get_value("File", {"file_name": self.attached_to_doctype, + doctype_folder_name = frappe.db.get_value("File", {"file_name": self.attached_to_doctype, "is_folder": 1, "folder": module_folder_name}) - if not parent_folder_name: - # parent folder - parent_folder = frappe.get_doc({ - "doctype": "File", - "is_folder": 1, - "file_name": _(self.attached_to_doctype), - "folder": module_folder_name - }).insert() - - parent_folder_name = parent_folder.name - return parent_folder_name + return self.make_folder(doctype_folder_name, module_folder_name, + _(self.attached_to_doctype, frappe.db.get_default("lang"))) def get_module_folder_name(self): """Returns module folder name. If not exists, then make""" @@ -83,18 +97,22 @@ class File(NestedSet): module_folder_name = frappe.db.get_value("File", {"file_name": module, "is_folder": 1, "folder": home_folder_name}) - if not module_folder_name: - module_folder = frappe.get_doc({ + return self.make_folder(module_folder_name, home_folder_name, _(module, + frappe.db.get_default("lang"))) + + def make_folder(self, name, folder, file_name): + if not name: + # parent folder + file = frappe.get_doc({ "doctype": "File", "is_folder": 1, - "file_name": _(module), - "folder": home_folder_name + "file_name": file_name, + "folder": folder }).insert() - module_folder_name = module_folder.name - - return module_folder_name + name = file.name + return name def validate_folder(self): if not self.is_home_folder and not self.folder and \ diff --git a/frappe/core/doctype/file/file_list.js b/frappe/core/doctype/file/file_list.js index 1fe15e0038..3b75b210de 100644 --- a/frappe/core/doctype/file/file_list.js +++ b/frappe/core/doctype/file/file_list.js @@ -1,14 +1,7 @@ -//TODO - -// show breadcrumbs -// search bar to add search filter -// back button -// new - // if file, attach - // if folder, set name frappe.listview_settings['File'] = { hide_name_column: true, + use_route: true, add_fields: ["is_folder", "file_name"], formatters: { file_size: function(value) { @@ -32,12 +25,23 @@ frappe.listview_settings['File'] = { } }, onload: function(doclist) { - doclist.breadcrumb = $('') - .insertBefore(doclist.wrapper.find(".show_filters")); + doclist.filter_area = doclist.wrapper.find(".show_filters"); + doclist.breadcrumb = $('') + .insertBefore(doclist.filter_area); + }, + 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("name"); + var name_filter = doclist.filter_list.get_filter("file_name"); var folder_filter = doclist.filter_list.get_filter("folder"); if(folder_filter) { @@ -46,6 +50,12 @@ frappe.listview_settings['File'] = { 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"); @@ -58,14 +68,12 @@ frappe.listview_settings['File'] = { }, post_render_item: function(list, row, data) { if(data.is_folder) { - $(row).find(".list-id").on("click", function() { - list.doclistview.current_folder = data.name; - list.doclistview.current_folder_name = data.file_name; - list.doclistview.refresh(); - return false; - }); + $(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", @@ -76,16 +84,9 @@ frappe.listview_settings['File'] = { doclist.breadcrumb.empty(); if(r.message && r.message.length) { $.each(r.message, function(i, folder) { - $('
  • '+ folder.file_name+'
  • ') - .appendTo(doclist.breadcrumb) - .attr("name", folder.name) - .attr("file_name", folder.file_name) - .on("click", function() { - doclist.current_folder = $(this).attr("name"); - doclist.current_folder_name = $(this).attr("file_name"); - doclist.refresh(); - return false; - }); + $('
  • ' + + folder.file_name+'
  • ') + .appendTo(doclist.breadcrumb); }); } $('
  • '+ doclist.current_folder_name+'
  • ') diff --git a/frappe/data/Framework.sql b/frappe/data/Framework.sql index 7f101fcb9d..624bea642d 100644 --- a/frappe/data/Framework.sql +++ b/frappe/data/Framework.sql @@ -200,11 +200,11 @@ CREATE TABLE `__Auth` ( ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- --- Table structure for table `tabFile Data` +-- 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, diff --git a/frappe/desk/search.py b/frappe/desk/search.py index a4554f0ab7..9e070f1570 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -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]) diff --git a/frappe/model/document.py b/frappe/model/document.py index d90f5d1309..2ed431d668 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -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""" diff --git a/frappe/patches/v6_1/rename_file_data.py b/frappe/patches/v6_1/rename_file_data.py index d698fdb862..6e7c163182 100644 --- a/frappe/patches/v6_1/rename_file_data.py +++ b/frappe/patches/v6_1/rename_file_data.py @@ -7,6 +7,10 @@ def execute(): frappe.rename_doc("DocType", "File Data", "File") frappe.reload_doctype("File") + # DELETE THIS + frappe.db.sql("""delete from tabFile where is_folder=1""") + frappe.db.sql("""update tabFile set folder=null""") + if not frappe.db.exists("File", {"is_home_folder": 1}): make_home_folder() @@ -14,6 +18,7 @@ def execute(): 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 @@ -22,8 +27,6 @@ def execute(): # 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"""): - print folder[0] - folder = frappe.get_doc("File", folder[0]) folder.save() diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css index 83bc5331b2..1cee95245e 100644 --- a/frappe/public/css/desk.css +++ b/frappe/public/css/desk.css @@ -523,5 +523,10 @@ ul.linked-with-list li { } .breadcrumb { font-size: 12px; - margin-bottom: 10px; + background-color: #fff; +} +.breadcrumb.for-file-list { + margin-bottom: 0px; + border-bottom: 1px solid #d1d8dd; + border-radius: 0px; } diff --git a/frappe/public/js/frappe/list/doclistview.js b/frappe/public/js/frappe/list/doclistview.js index e655cdc48f..9919d9c939 100644 --- a/frappe/public/js/frappe/list/doclistview.js +++ b/frappe/public/js/frappe/list/doclistview.js @@ -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(); } }); @@ -65,6 +69,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') ? @@ -236,6 +241,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ if(frappe.route_options) { me.set_route_options(); + me.run(); } else if(me.dirty) { me.run(); } else { @@ -265,17 +271,22 @@ 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.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") diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js index ca5dfffa81..8688d26b77 100644 --- a/frappe/public/js/frappe/ui/filters/filters.js +++ b/frappe/public/js/frappe/ui/filters/filters.js @@ -182,7 +182,7 @@ frappe.ui.Filter = Class.extend({ if(!dont_run) { this.flist.listobj.dirty = true; - this.flist.listobj.run(); + this.flist.listobj.refresh(); } }, diff --git a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js index c236e226c5..c019aa9d35 100644 --- a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js +++ b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js @@ -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 }); diff --git a/frappe/public/js/frappe/views/factory.js b/frappe/public/js/frappe/views/factory.js index 6a3670c18e..79d629b277 100644 --- a/frappe/public/js/frappe/views/factory.js +++ b/frappe/public/js/frappe/views/factory.js @@ -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, diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less index 1cfae64520..915b112ed0 100644 --- a/frappe/public/less/desk.less +++ b/frappe/public/less/desk.less @@ -380,5 +380,11 @@ ul.linked-with-list li { .breadcrumb { font-size: 12px; - margin-bottom: 10px; + background-color: #fff; +} + +.breadcrumb.for-file-list { + margin-bottom: 0px; + border-bottom: 1px solid @border-color; + border-radius: 0px; } diff --git a/frappe/utils/install.py b/frappe/utils/install.py index 10d30f5a2b..19f90ab30d 100644 --- a/frappe/utils/install.py +++ b/frappe/utils/install.py @@ -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()