Browse Source

[fixes] file manager, attachments go to 'Attachments' folder and permission + cleanups

version-14
Rushabh Mehta 9 years ago
parent
commit
bedf04a5f4
9 changed files with 229 additions and 117 deletions
  1. +1
    -1
      frappe/config/desktop.py
  2. +19
    -17
      frappe/core/doctype/file/file.js
  3. +124
    -16
      frappe/core/doctype/file/file.json
  4. +17
    -54
      frappe/core/doctype/file/file.py
  5. +8
    -3
      frappe/core/doctype/file/file_list.js
  6. +1
    -1
      frappe/patches/v6_1/rename_file_data.py
  7. +37
    -17
      frappe/permissions.py
  8. +15
    -4
      frappe/public/js/frappe/model/perm.js
  9. +7
    -4
      frappe/public/js/frappe/views/breadcrumbs.js

+ 1
- 1
frappe/config/desktop.py View File

@@ -46,7 +46,7 @@ def get_data():
"type": "list" "type": "list"
}, },
"File Manager": { "File Manager": {
"color": "#95a5a6",
"color": "#905df5",
"doctype": "File", "doctype": "File",
"icon": "icon-folder-close", "icon": "icon-folder-close",
"icon": "octicon octicon-file-directory", "icon": "octicon octicon-file-directory",


+ 19
- 17
frappe/core/doctype/file/file.js View File

@@ -1,19 +1,21 @@
frappe.ui.form.on("File", "onload", function(frm) {
if(frappe.utils.is_image_file(frm.doc.file_url)){
frm.doc.preview = '<div class="img_preview">\
<img style="max-widht:130px;max-height:130px;" \
src="'+frm.doc.file_url+'"></img>\
</div>';
frm.refresh_field("preview");
}
})

frappe.ui.form.on("File", "refresh", function(frm) { frappe.ui.form.on("File", "refresh", function(frm) {
frm.add_custom_button(__('Download'), function(){
window.open(frm.doc.file_url);
}, "icon-download");
})
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);


// frappe.ui.form.on("File", "download", function(frm) {
// window.open(frm.doc.file_url);
// })
if(is_viewable){
wrapper.html('<div class="img_preview">\
<img class="img-responsive" src="'+frm.doc.file_url+'"></img>\
</div>');
} else {
wrapper.empty();
}
});

+ 124
- 16
frappe/core/doctype/file/file.json View File

@@ -1,7 +1,7 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1,
"allow_rename": 0,
"autoname": "hash", "autoname": "hash",
"creation": "2012-12-12 11:19:22", "creation": "2012-12-12 11:19:22",
"custom": 0, "custom": 0,
@@ -24,6 +24,28 @@
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "permlevel": 0,
"print_hide": 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, "read_only": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
@@ -35,13 +57,34 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"fieldname": "is_folder",
"fieldtype": "Check",
"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, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "Is Folder",
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
@@ -80,14 +123,35 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "file_url",
"fieldtype": "Data",
"hidden": 0,
"fieldname": "is_attachments_folder",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"label": "File URL",
"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, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
@@ -119,13 +183,35 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"fieldname": "folder", "fieldname": "folder",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"hidden": 1,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
@@ -135,7 +221,7 @@
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"read_only": 0,
"read_only": 1,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
@@ -146,15 +232,16 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"fieldname": "file_size",
"fieldtype": "Int",
"fieldname": "is_folder",
"fieldtype": "Check",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 1,
"label": "File Size",
"in_list_view": 0,
"label": "Is Folder",
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"read_only": 1, "read_only": 1,
"report_hide": 0, "report_hide": 0,
@@ -167,6 +254,7 @@
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"depends_on": "eval:!doc.is_folder",
"fieldname": "section_break_8", "fieldname": "section_break_8",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@@ -345,7 +433,7 @@
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"modified": "2015-09-08 15:57:09.103049",
"modified": "2015-09-18 06:22:10.902847",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "File", "name": "File",
@@ -370,6 +458,26 @@
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "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": 0,


+ 17
- 54
frappe/core/doctype/file/file.py View File

@@ -30,7 +30,7 @@ class File(NestedSet):
path = get_breadcrumbs(self.folder) path = get_breadcrumbs(self.folder)
folder_name = frappe.get_value("File", self.folder, "file_name") folder_name = frappe.get_value("File", self.folder, "file_name")
return "/".join([d.file_name for d in path] + [folder_name, self.file_name]) return "/".join([d.file_name for d in path] + [folder_name, self.file_name])
def set_name(self): def set_name(self):
"""Set name for folder""" """Set name for folder"""
if self.is_folder: if self.is_folder:
@@ -44,14 +44,14 @@ class File(NestedSet):


def after_insert(self): def after_insert(self):
self.update_parent_folder_size() self.update_parent_folder_size()
def after_rename(self, olddn, newdn, merge=False): def after_rename(self, olddn, newdn, merge=False):
for successor in self.get_successor(): for successor in self.get_successor():
setup_folder_path(successor, self.name) setup_folder_path(successor, self.name)
def get_successor(self): def get_successor(self):
return frappe.db.sql_list("select name from tabFile where folder='%s'"%self.name) or [] return frappe.db.sql_list("select name from tabFile where folder='%s'"%self.name) or []
def validate(self): def validate(self):
self.validate_duplicate_entry() self.validate_duplicate_entry()
self.validate_folder() self.validate_folder()
@@ -83,52 +83,7 @@ class File(NestedSet):
def set_folder_name(self): def set_folder_name(self):
"""Make parent folders if not exists based on reference doctype and name""" """Make parent folders if not exists based on reference doctype and name"""
if self.attached_to_doctype and not self.folder: if self.attached_to_doctype and not self.folder:
self.folder = self.get_parent_folder_name()

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()
doctype_folder_name = frappe.db.get_value("File", {"file_name": self.attached_to_doctype,
"is_folder": 1, "folder": module_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"""
if self.attached_to_doctype:
module = frappe.db.get_value("DocType", self.attached_to_doctype, "module")

home_folder_name = frappe.db.get_value("File", {"is_home_folder": 1})

module_folder_name = frappe.db.get_value("File", {"file_name": module,
"is_folder": 1, "folder": home_folder_name})

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": file_name,
"folder": folder
}).insert()

name = file.name

return name
self.folder = frappe.db.get_value("File", {"is_attachments_folder": 1})


def validate_folder(self): def validate_folder(self):
if not self.is_home_folder and not self.folder and \ if not self.is_home_folder and not self.folder and \
@@ -188,13 +143,21 @@ def on_doctype_update():
frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"]) frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"])


def make_home_folder(): def make_home_folder():
frappe.get_doc({
home = frappe.get_doc({
"doctype": "File", "doctype": "File",
"is_folder": 1, "is_folder": 1,
"is_home_folder": 1, "is_home_folder": 1,
"file_name": _("Home") "file_name": _("Home")
}).insert() }).insert()


frappe.get_doc({
"doctype": "File",
"folder": home.name,
"is_folder": 1,
"is_attachments_folder": 1,
"file_name": _("Attachments")
}).insert()

@frappe.whitelist() @frappe.whitelist()
def get_breadcrumbs(folder): def get_breadcrumbs(folder):
"""returns name, file_name of parent folder""" """returns name, file_name of parent folder"""
@@ -221,11 +184,11 @@ def move_file(file_list, new_parent, old_parent):
frappe.get_doc("File", new_parent).save() frappe.get_doc("File", new_parent).save()


return "File(s) has been moved successfully!!" return "File(s) has been moved successfully!!"
def setup_folder_path(filename, new_parent): def setup_folder_path(filename, new_parent):
file = frappe.get_doc("File", filename) file = frappe.get_doc("File", filename)
file.folder = new_parent file.folder = new_parent
file.save() file.save()
if file.is_folder: if file.is_folder:
frappe.rename_doc("File", file.name, file.get_name_based_on_parent_folder())
frappe.rename_doc("File", file.name, file.get_name_based_on_parent_folder())

+ 8
- 3
frappe/core/doctype/file/file_list.js View File

@@ -32,7 +32,7 @@ frappe.listview_settings['File'] = {
doclist.breadcrumb = $('<ol class="breadcrumb for-file-list"></ol>') doclist.breadcrumb = $('<ol class="breadcrumb for-file-list"></ol>')
.insertBefore(doclist.filter_area); .insertBefore(doclist.filter_area);


doclist.listview.settings.setup_new_folder(doclist);
doclist.listview.settings.setup_menu(doclist);
doclist.listview.settings.setup_dragdrop(doclist); doclist.listview.settings.setup_dragdrop(doclist);


doclist.$page.on("click", ".list-delete", function(event) { doclist.$page.on("click", ".list-delete", function(event) {
@@ -52,7 +52,7 @@ frappe.listview_settings['File'] = {
}); });
}); });
}, },
setup_new_folder: function(doclist) {
setup_menu: function(doclist) {
doclist.page.add_menu_item(__("New Folder"), function() { doclist.page.add_menu_item(__("New Folder"), function() {
var d = frappe.prompt(__("Name"), function(values) { var d = frappe.prompt(__("Name"), function(values) {
if((values.value.indexOf("/") > -1)){ if((values.value.indexOf("/") > -1)){
@@ -70,6 +70,10 @@ frappe.listview_settings['File'] = {
}) })
}, __('Enter folder name'), __("Create")); }, __('Enter folder name'), __("Create"));
}); });

doclist.page.add_menu_item(__("Edit Folder"), function() {
frappe.set_route("Form", "File", doclist.current_folder);
});
}, },
setup_dragdrop: function(doclist) { setup_dragdrop: function(doclist) {
$(doclist.$page).on('dragenter dragover', false) $(doclist.$page).on('dragenter dragover', false)
@@ -153,7 +157,9 @@ frappe.listview_settings['File'] = {


doclist.filter_list.add_filter("File", "folder", "=", doclist.current_folder, true); doclist.filter_list.add_filter("File", "folder", "=", doclist.current_folder, true);
doclist.dirty = true; doclist.dirty = true;
doclist.fresh = false;


doclist.page.set_title(doclist.current_folder_name);
frappe.utils.set_title(doclist.current_folder_name); frappe.utils.set_title(doclist.current_folder_name);
}, },
set_primary_action:function(doclist){ set_primary_action:function(doclist){
@@ -165,7 +171,6 @@ frappe.listview_settings['File'] = {
"from_form": 1 "from_form": 1
}, },
callback: function() { callback: function() {
console.log('here')
doclist.refresh(); doclist.refresh();
} }
}); });


+ 1
- 1
frappe/patches/v6_1/rename_file_data.py View File

@@ -7,7 +7,7 @@ def execute():
frappe.rename_doc("DocType", "File Data", "File") frappe.rename_doc("DocType", "File Data", "File")
frappe.reload_doctype("File") frappe.reload_doctype("File")


# DELETE THIS
# TODO: DELETE THIS
frappe.db.sql("""delete from tabFile where is_folder=1""") frappe.db.sql("""delete from tabFile where is_folder=1""")
frappe.db.sql("""update tabFile set folder=null""") frappe.db.sql("""update tabFile set folder=null""")




+ 37
- 17
frappe/permissions.py View File

@@ -52,6 +52,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
if doc_name in shared: if doc_name in shared:
if verbose: print "Shared" if verbose: print "Shared"
if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype): if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype):
if verbose: print "Is shared"
return True return True


elif shared: elif shared:
@@ -60,6 +61,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
if verbose: print "Has a shared document" if verbose: print "Has a shared document"
return True return True


if verbose: print "Not Shared"
return False return False


role_permissions = get_role_permissions(meta, user=user, verbose=verbose) role_permissions = get_role_permissions(meta, user=user, verbose=verbose)
@@ -67,28 +69,40 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None):
if not role_permissions.get(ptype): if not role_permissions.get(ptype):
return false_if_not_shared() return false_if_not_shared()


perm = True

if doc: if doc:
if isinstance(doc, basestring): if isinstance(doc, basestring):
doc = frappe.get_doc(meta.name, doc) doc = frappe.get_doc(meta.name, doc)


# if owner match, then return True
if doc.owner == frappe.session.user and role_permissions["if_owner"].get(ptype) and ptype!="create":
return True
owner_perm = user_perm = controller_perm = None

if role_permissions["if_owner"].get(ptype) and ptype!="create":
owner_perm = doc.owner == frappe.session.user
if verbose: print "Owner permission: {0}".format(owner_perm)


# check if user permission # check if user permission
if role_permissions["apply_user_permissions"].get(ptype):
if not user_has_permission(doc, verbose=verbose, user=user,
user_permission_doctypes=role_permissions.get("user_permission_doctypes", {}).get(ptype) or []):
if verbose: print "No user permission"
return false_if_not_shared()

if not has_controller_permissions(doc, ptype, user=user):
if verbose: print "No controller permission"
return false_if_not_shared()

if verbose:
print "Has Role"
return True
if not owner_perm and role_permissions["apply_user_permissions"].get(ptype):
user_perm = user_has_permission(doc, verbose=verbose, user=user,
user_permission_doctypes=role_permissions.get("user_permission_doctypes", {}).get(ptype) or [])

if verbose: print "User permission: {0}".format(user_perm)

if not owner_perm and not user_perm:
controller_perm = has_controller_permissions(doc, ptype, user=user)

if verbose: print "Controller permission: {0}".format(controller_perm)

# permission true if any one condition is explicitly True or all permissions are undefined (None)
perm = any([owner_perm, user_perm, controller_perm]) or \
all([owner_perm==None, user_perm==None, controller_perm==None])

if not perm:
perm = false_if_not_shared()

if verbose: print "Final Permission: {0}".format(perm)

return perm


def get_doc_permissions(doc, verbose=False, user=None): def get_doc_permissions(doc, verbose=False, user=None):
"""Returns a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`""" """Returns a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`"""
@@ -271,9 +285,15 @@ def user_has_permission(doc, verbose=True, user=None, user_permission_doctypes=N
return _user_has_permission return _user_has_permission


def has_controller_permissions(doc, ptype, user=None): def has_controller_permissions(doc, ptype, user=None):
"""Returns controller permissions if defined. None if not defined"""
if not user: user = frappe.session.user if not user: user = frappe.session.user


for method in frappe.get_hooks("has_permission").get(doc.doctype, []):
methods = frappe.get_hooks("has_permission").get(doc.doctype, [])

if not methods:
return None

for method in methods:
if not frappe.call(frappe.get_attr(method), doc=doc, ptype=ptype, user=user): if not frappe.call(frappe.get_attr(method), doc=doc, ptype=ptype, user=user):
return False return False




+ 15
- 4
frappe/public/js/frappe/model/perm.js View File

@@ -60,6 +60,20 @@ $.extend(frappe.perm, {
}); });
} }


// if owner
if(!$.isEmptyObject(perm[0].if_owner)) {
if(doc.owner===user) {
$.extend(perm[0], perm[0].if_owner);
} else {
// not owner, remove permissions
$.each(perm[0].if_owner, function(ptype, value) {
if(perm[0].if_owner[ptype]) {
perm[0][ptype] = 0
}
})
}
}

// apply permissions from shared // apply permissions from shared
if(docinfo.shared) { if(docinfo.shared) {
for(var i=0; i<docinfo.shared; i++) { for(var i=0; i<docinfo.shared; i++) {
@@ -78,6 +92,7 @@ $.extend(frappe.perm, {
} }
} }
} }

} }


if(frappe.model.can_read(doctype) && !perm[0].read) { if(frappe.model.can_read(doctype) && !perm[0].read) {
@@ -131,10 +146,6 @@ $.extend(frappe.perm, {
}); });
} }
} }

if (permlevel===0 && p["if_owner"]) {
perm[0]["if_owner"] = 1;
}
} }
}); });




+ 7
- 4
frappe/public/js/frappe/views/breadcrumbs.js View File

@@ -4,7 +4,9 @@
frappe.breadcrumbs = { frappe.breadcrumbs = {
all: {}, all: {},


preferred: {},
preferred: {
"File": ""
},


set_doctype_module: function(doctype, module) { set_doctype_module: function(doctype, module) {
localStorage["preferred_breadcrumbs:" + doctype] = module; localStorage["preferred_breadcrumbs:" + doctype] = module;
@@ -33,7 +35,7 @@ frappe.breadcrumbs = {


if(from_module) { if(from_module) {
breadcrumbs.module = from_module; breadcrumbs.module = from_module;
} else if(frappe.breadcrumbs.preferred[breadcrumbs.doctype]) {
} else if(frappe.breadcrumbs.preferred[breadcrumbs.doctype]!==undefined) {
// get preferred module for breadcrumbs // get preferred module for breadcrumbs
breadcrumbs.module = frappe.breadcrumbs.preferred[breadcrumbs.doctype]; breadcrumbs.module = frappe.breadcrumbs.preferred[breadcrumbs.doctype];
} }
@@ -60,8 +62,9 @@ frappe.breadcrumbs = {
if(breadcrumbs.doctype==="User" && frappe.user.modules.indexOf("Setup")===-1) { if(breadcrumbs.doctype==="User" && frappe.user.modules.indexOf("Setup")===-1) {
// no user listview for non-system managers // no user listview for non-system managers
} else { } else {
$(repl('<li><a href="#List/%(doctype)s">%(label)s</a></li>',
{doctype: breadcrumbs.doctype, label: __(breadcrumbs.doctype)}))
route = (cur_frm && cur_frm.list_route) || ("List/" + breadcrumbs.doctype)
$(repl('<li><a href="#%(route)s">%(label)s</a></li>',
{route: route, label: __(breadcrumbs.doctype)}))
.appendTo($breadcrumbs); .appendTo($breadcrumbs);
} }
} }


Loading…
Cancel
Save