Merge pull request #795 from anandpdoshi/user-permission-doctypes

Select Document Types for applying User Permissions
This commit is contained in:
Anand Doshi 2014-08-28 11:07:27 +05:30
commit d4891aa7c1
17 changed files with 338 additions and 96 deletions

View file

@ -20,7 +20,7 @@ def get_data():
{
"type": "page",
"name": "permission-manager",
"label": "Role Permissions Manager",
"label": _("Role Permissions Manager"),
"icon": "icon-lock",
"description": _("Set Permissions on Document Types and Roles")
},

View file

@ -53,6 +53,15 @@
"search_index": 0,
"width": "40px"
},
{
"depends_on": "",
"description": "JSON list of DocTypes used to apply User Permissions. If empty, all linked DocTypes will be used to apply User Permissions.",
"fieldname": "user_permission_doctypes",
"fieldtype": "Text",
"label": "User Permission DocTypes",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break",
@ -207,7 +216,7 @@
"idx": 1,
"issingle": 0,
"istable": 1,
"modified": "2014-05-26 03:46:53.737397",
"modified": "2014-08-26 01:43:31.499363",
"modified_by": "Administrator",
"module": "Core",
"name": "DocPerm",

View file

@ -41,13 +41,13 @@ frappe.PermissionEngine = Class.extend({
setup_appframe: function() {
var me = this;
this.doctype_select
= this.wrapper.appframe.add_select("doctypes",
= this.wrapper.appframe.add_select(__("Document Types"),
[{value: "", label: __("Select Document Type")+"..."}].concat(this.options.doctypes))
.change(function() {
frappe.set_route("permission-manager", $(this).val());
});
this.role_select
= this.wrapper.appframe.add_select("roles",
= this.wrapper.appframe.add_select(__("Roles"),
[__("Select Role")+"..."].concat(this.options.roles))
.change(function() {
me.refresh();
@ -95,7 +95,7 @@ frappe.PermissionEngine = Class.extend({
page:"permission_manager",
method:"reset",
args: {
doctype:me.get_doctype(),
doctype: me.get_doctype(),
},
callback: function() { me.refresh(); }
});
@ -130,7 +130,7 @@ frappe.PermissionEngine = Class.extend({
refresh: function() {
var me = this;
if(!me.doctype_select) {
this.body.html("<div class='alert alert-info'>Loading...</div>");
this.body.html("<div class='alert alert-info'>" + __("Loading") + "...</div>");
return;
}
if(!me.get_doctype() && !me.get_role()) {
@ -164,6 +164,7 @@ frappe.PermissionEngine = Class.extend({
this.make_reset_button();
},
show_permission_table: function(perm_list) {
var me = this;
this.table = $("<div class='table-responsive'>\
<table class='table table-bordered'>\
@ -172,77 +173,104 @@ frappe.PermissionEngine = Class.extend({
</table>\
</div>").appendTo(this.body);
$.each([["Document Type", 150], ["Role", 170], ["Level", 40],
["Permissions", 350], ["", 40]], function(i, col) {
$.each([[__("Document Type"), 150], [__("Role"), 170], [__("Level"), 40],
[__("Permissions"), 350], ["", 40]], function(i, col) {
$("<th>").html(col[0]).css("width", col[1]+"px")
.appendTo(me.table.find("thead tr"));
});
var add_cell = function(row, d, fieldname) {
return $("<td>").appendTo(row)
.attr("data-fieldname", fieldname)
.html(d[fieldname]);
};
var add_check = function(cell, d, fieldname, label, without_grid) {
if(!label) label = fieldname.replace(/_/g, " ");
if(d.permlevel > 0 && ["read", "write"].indexOf(fieldname)==-1) {
return;
}
var checkbox = $("<div class='col-md-4'><div class='checkbox'>\
<label><input type='checkbox'>"+__(label)+"</input></label>"
+ (d.help || "") + "</div></div>").appendTo(cell)
.attr("data-fieldname", fieldname)
.css("text-transform", "capitalize");
checkbox.find("input")
.prop("checked", d[fieldname] ? true: false)
.attr("data-ptype", fieldname)
.attr("data-name", d.name)
.attr("data-doctype", d.parent)
return checkbox;
};
$.each(perm_list, function(i, d) {
if(!d.permlevel) d.permlevel = 0;
var row = $("<tr>").appendTo(me.table.find("tbody"));
add_cell(row, d, "parent");
var role_cell = add_cell(row, d, "role");
me.add_cell(row, d, "parent");
var role_cell = me.add_cell(row, d, "role");
me.set_show_users(role_cell, d.role);
if (d.permlevel===0) {
d.help = '<div style="margin-left: 20px;">\
<a class="show-user-permissions small">Show User Pemissions</a></div>';
add_check(role_cell, d, "apply_user_permissions")
.removeClass("col-md-4")
.css({"margin-top": "15px"});
d.help = "";
me.setup_user_permissions(d, role_cell);
}
var cell = add_cell(row, d, "permlevel");
var cell = me.add_cell(row, d, "permlevel");
if(d.permlevel==0) {
cell.css("font-weight", "bold");
row.addClass("warning");
}
var perm_cell = add_cell(row, d, "permissions").css("padding-top", 0);
var perm_cell = me.add_cell(row, d, "permissions").css("padding-top", 0);
var perm_container = $("<div class='row'></div>").appendTo(perm_cell);
$.each(me.rights, function(i, r) {
add_check(perm_container, d, r);
me.add_check(perm_container, d, r);
});
// buttons
me.add_delete_button(row, d);
});
},
add_cell: function(row, d, fieldname) {
return $("<td>").appendTo(row)
.attr("data-fieldname", fieldname)
.html(__(d[fieldname]));
},
add_check: function(cell, d, fieldname, label) {
var me = this;
if(!label) label = toTitle(fieldname.replace(/_/g, " "));
if(d.permlevel > 0 && ["read", "write"].indexOf(fieldname)==-1) {
return;
}
var checkbox = $("<div class='col-md-4'><div class='checkbox'>\
<label><input type='checkbox'>"+__(label)+"</input></label>"
+ (d.help || "") + "</div></div>").appendTo(cell)
.attr("data-fieldname", fieldname);
checkbox.find("input")
.prop("checked", d[fieldname] ? true: false)
.attr("data-ptype", fieldname)
.attr("data-name", d.name)
.attr("data-doctype", d.parent)
checkbox.find("label")
.css("text-transform", "capitalize");
return checkbox;
},
setup_user_permissions: function(d, role_cell) {
var me = this;
d.help = frappe.render('<ul class="user-permission-help small hidden" style="margin-left: -10px;">\
<li style="margin-top: 7px;"><a class="show-user-permission-doctypes">{%= __("Select Document Types") %}</a></li>\
<li style="margin-top: 3px;"><a class="show-user-permissions">{%= __("Show User Permissions") %}</a></li>\
</ul>', {});
var checkbox = this.add_check(role_cell, d, "apply_user_permissions")
.removeClass("col-md-4")
.css({"margin-top": "15px"});
checkbox.find(".show-user-permission-doctypes").on("click", function() {
me.show_user_permission_doctypes(d);
});
var toggle_user_permissions = function() {
checkbox.find(".user-permission-help").toggleClass("hidden", !checkbox.find("input").prop("checked"));
};
toggle_user_permissions();
checkbox.find("input").on('click', function() {
toggle_user_permissions();
});
d.help = "";
},
rights: ["read", "write", "create", "delete", "submit", "cancel", "amend",
"print", "email", "report", "import", "export", "set_user_permissions"],
set_show_users: function(cell, role) {
cell.html("<a href='#'>"+role+"</a>")
cell.html("<a href='#'>"+__(role)+"</a>")
.find("a")
.attr("data-role", role)
.click(function() {
@ -331,14 +359,14 @@ frappe.PermissionEngine = Class.extend({
var d = new frappe.ui.Dialog({
title: __("Add New Permission Rule"),
fields: [
{fieldtype:"Select", label:"Document Type",
{fieldtype:"Select", label:__("Document Type"),
options:me.options.doctypes, reqd:1, fieldname:"parent"},
{fieldtype:"Select", label:"Role",
{fieldtype:"Select", label:__("Role"),
options:me.options.roles, reqd:1},
{fieldtype:"Select", label:"Permission Level",
{fieldtype:"Select", label:__("Permission Level"),
options:[0,1,2,3,4,5,6,7,8,9], reqd:1, fieldname: "permlevel",
description: __("Level 0 is for document level permissions, higher levels for field level permissions.")},
{fieldtype:"Button", label:"Add"},
{fieldtype:"Button", label:__("Add")},
]
});
if(me.get_doctype()) {
@ -374,10 +402,86 @@ frappe.PermissionEngine = Class.extend({
});
},
show_user_permission_doctypes: function(d) {
if (!d.dialog) {
var fields = [];
for (var i=0, l=d.linked_doctypes.length; i<l; i++) {
fields.push({
fieldtype: "Check",
label: __(d.linked_doctypes[i]),
fieldname: d.linked_doctypes[i]
});
}
fields.push({
fieldtype: "Button",
label: __("Set"),
fieldname: "set_user_permission_doctypes"
})
var dialog = new frappe.ui.Dialog({
title: __('Apply User Permissions of these Document Types'),
fields: fields
});
var fields_to_check = d.user_permission_doctypes
? JSON.parse(d.user_permission_doctypes) : d.linked_doctypes;
for (var i=0, l=fields_to_check.length; i<l; i++) {
dialog.set_value(fields_to_check[i], 1);
}
var btn = dialog.get_input("set_user_permission_doctypes");
btn.on("click", function() {
var values = dialog.get_values();
var user_permission_doctypes = [];
$.each(values, function(key, val) {
if (val) {
user_permission_doctypes.push(key);
}
});
if (!user_permission_doctypes || !user_permission_doctypes.length ||
user_permission_doctypes.length === d.linked_doctypes.length) {
// if all checked
user_permission_doctypes = undefined;
} else {
user_permission_doctypes.sort();
user_permission_doctypes = JSON.stringify(user_permission_doctypes);
}
frappe.call({
module: "frappe.core",
page: "permission_manager",
method: "update",
args: {
doctype: d.parent,
name: d.name,
ptype: "user_permission_doctypes",
value: user_permission_doctypes
},
callback: function(r) {
if(r.exc) {
msgprint(__("Did not set"));
} else {
var msg = msgprint(__("Saved!"));
setTimeout(function() { msg.hide(); }, 3000);
d.user_permission_doctypes = user_permission_doctypes;
dialog.hide();
}
}
});
});
d.dialog = dialog;
}
d.dialog.show();
},
make_reset_button: function() {
var me = this;
$('<button class="btn btn-default" style="margin-left: 10px;">\
<i class="icon-refresh"></i> Restore Original Permissions</button>')
<i class="icon-refresh"></i> ' + __("Restore Original Permissions") + '</button>')
.appendTo(this.body.find(".permission-toolbar"))
.on("click", function() {
me.get_standard_permissions(function(data) {
@ -478,6 +582,9 @@ var permissions_help = ['<table class="table table-bordered" style="background-c
__("To give acess to a role for only specific records, check the 'Apply User Permissions'. User Permissions are used to limit users with such role to specific records.")
+ ' (<a href="#user-permissions">' + __('Setup > User Permissions Manager') + '</a>)',
'</li>',
'<li>',
__("Select Document Types to set which User Permissions are used to limit access."),
'</li>',
'<li>',
__("Once you have set this, the users will only be able access documents (eg. Blog Post) where the link exists (eg. Blogger)."),
'</li>',

View file

@ -5,11 +5,13 @@ from __future__ import unicode_literals
import frappe
import frappe.defaults
from frappe.modules.import_file import get_file_path, read_doc_from_file
from frappe.translate import send_translations
from frappe.core.doctype.notification_count.notification_count import delete_notification_count_for
@frappe.whitelist()
def get_roles_and_doctypes():
frappe.only_for("System Manager")
send_translations(frappe.get_lang_dict("doctype", "DocPerm"))
return {
"doctypes": [d[0] for d in frappe.db.sql("""select name from `tabDocType` dt where
ifnull(istable,0)=0 and
@ -22,12 +24,27 @@ def get_roles_and_doctypes():
@frappe.whitelist()
def get_permissions(doctype=None, role=None):
frappe.only_for("System Manager")
return frappe.db.sql("""select * from tabDocPerm
out = frappe.db.sql("""select * from tabDocPerm
where %s%s order by parent, permlevel, role""" %
(doctype and (" parent='%s'" % doctype.replace("'", "\'")) or "",
role and ((doctype and " and " or "") + " role='%s'" % role.replace("'", "\'")) or ""),
as_dict=True)
def get_linked_doctypes(dt):
return list(set([dt] + [d.options for d in
frappe.get_meta(dt).get("fields", {
"fieldtype":"Link",
"ignore_user_permissions":("!=", 1),
"options": ("!=", "[Select]")
})
]))
linked_doctypes = {}
for d in out:
d.linked_doctypes = linked_doctypes.setdefault(d.parent, get_linked_doctypes(d.parent))
return out
@frappe.whitelist()
def remove(doctype, name):
frappe.only_for("System Manager")
@ -51,7 +68,7 @@ def add(parent, role, permlevel):
validate_and_reset(parent)
@frappe.whitelist()
def update(name, doctype, ptype, value=0):
def update(name, doctype, ptype, value=None):
frappe.only_for("System Manager")
frappe.db.sql("""update tabDocPerm set `%s`=%s where name=%s"""\
% (ptype, '%s', '%s'), (value, name))
@ -88,6 +105,7 @@ def get_users_with_role(role):
@frappe.whitelist()
def get_standard_permissions(doctype):
frappe.only_for("System Manager")
module = frappe.db.get_value("DocType", doctype, "module")
path = get_file_path(module, "DocType", doctype)
return read_doc_from_file(path).get("permissions")

View file

@ -15,7 +15,7 @@ class DatabaseQuery(object):
self.doctype = doctype
self.tables = []
self.conditions = []
self.fields = ["name"]
self.fields = ["`tab{0}`.`name`".format(doctype)]
self.user = None
self.ignore_permissions = False
@ -223,15 +223,18 @@ class DatabaseQuery(object):
if role_permissions.get("apply_user_permissions", {}).get("read"):
# get user permissions
user_permissions = frappe.defaults.get_user_permissions(self.user)
self.add_user_permissions(user_permissions)
self.add_user_permissions(user_permissions,
user_permission_doctypes=role_permissions.get("user_permission_doctypes"))
if as_condition:
return self.build_match_condition_string()
else:
return self.match_filters
def add_user_permissions(self, user_permissions):
fields_to_check = frappe.get_meta(self.doctype).get_fields_to_check_permissions(user_permissions.keys())
def add_user_permissions(self, user_permissions, user_permission_doctypes=None):
user_permission_doctypes = frappe.permissions.get_user_permission_doctypes(user_permission_doctypes,
user_permissions)
fields_to_check = frappe.get_meta(self.doctype).get_fields_to_check_permissions(user_permission_doctypes)
# check in links
for df in fields_to_check:

View file

@ -194,15 +194,15 @@ class Meta(Document):
self.set("fields", newlist)
def get_fields_to_check_permissions(self, user_permissions_doctypes):
def get_fields_to_check_permissions(self, user_permission_doctypes):
fields = self.get("fields", {
"fieldtype":"Link",
"parent": self.name,
"ignore_user_permissions":("!=", 1),
"options":("in", user_permissions_doctypes)
"options":("in", user_permission_doctypes)
})
if self.name in user_permissions_doctypes:
if self.name in user_permission_doctypes:
fields.append(frappe._dict({
"label":"Name",
"fieldname":"name",

View file

@ -2,7 +2,7 @@
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe, copy
import frappe, copy, json
from frappe import _, msgprint
from frappe.utils import cint
@ -42,7 +42,8 @@ def has_permission(doctype, ptype="read", doc=None, verbose=True, user=None):
doc = frappe.get_doc(meta.name, doc)
if role_permissions["apply_user_permissions"].get(ptype):
if not user_has_permission(doc, verbose=verbose, user=user):
if not user_has_permission(doc, verbose=verbose, user=user,
user_permission_doctypes=role_permissions.get("user_permission_doctypes")):
return False
if not has_controller_permissions(doc, ptype, user=user):
@ -66,7 +67,8 @@ def get_doc_permissions(doc, verbose=False, user=None):
if not cint(meta.allow_import):
role_permissions["import"] = 0
if not user_has_permission(doc, verbose=verbose, user=user):
if not user_has_permission(doc, verbose=verbose, user=user,
user_permission_doctypes=role_permissions.get("user_permission_doctypes")):
# no user permissions, switch off all user-level permissions
for ptype in role_permissions:
if role_permissions["apply_user_permissions"].get(ptype):
@ -88,7 +90,20 @@ def get_role_permissions(meta, user=None):
perms[ptype] = perms.get(ptype, 0) or cint(p.get(ptype))
if ptype != "set_user_permissions" and p.get(ptype):
perms["apply_user_permissions"][ptype] = perms["apply_user_permissions"].get(ptype, 1) and p.get("apply_user_permissions")
perms["apply_user_permissions"][ptype] = (perms["apply_user_permissions"].get(ptype, 1)
and p.get("apply_user_permissions"))
if p.apply_user_permissions and p.user_permission_doctypes:
# set user_permission_doctypes in perms
user_permission_doctypes = (json.loads(p.user_permission_doctypes)
if p.user_permission_doctypes else None)
if user_permission_doctypes and (not perms.get("user_permission_doctypes") or
len(user_permission_doctypes) <= len(perms["user_permission_doctypes"])):
# selecting the least no. of "user_permission_doctypes" for lesser filtering
# why? if there is a conflict of two user_permission_doctypes, the least restrictive should win
# hence, using the simplistic approach of less no. of "user_permission_doctypes" implies least restrictive!
perms["user_permission_doctypes"] = user_permission_doctypes
for key, value in perms.get("apply_user_permissions").items():
if not value:
@ -98,16 +113,17 @@ def get_role_permissions(meta, user=None):
return frappe.local.role_permissions[cache_key]
def user_has_permission(doc, verbose=True, user=None):
def user_has_permission(doc, verbose=True, user=None, user_permission_doctypes=None):
from frappe.defaults import get_user_permissions
user_permissions = get_user_permissions(user)
user_permissions_keys = user_permissions.keys()
user_permission_doctypes = get_user_permission_doctypes(user_permission_doctypes, user_permissions)
def check_user_permission(d):
result = True
meta = frappe.get_meta(d.get("doctype"))
for df in meta.get_fields_to_check_permissions(user_permissions_keys):
if d.get(df.fieldname) and d.get(df.fieldname) not in user_permissions[df.options]:
for df in meta.get_fields_to_check_permissions(user_permission_doctypes):
if (df.options in user_permissions and d.get(df.fieldname)
and d.get(df.fieldname) not in user_permissions[df.options]):
result = False
if verbose:
@ -192,3 +208,12 @@ def apply_user_permissions(doctype, ptype, user=None):
"""Check if apply_user_permissions is checked for a doctype, perm type, user combination"""
role_permissions = get_role_permissions(frappe.get_meta(doctype), user=user)
return role_permissions.get("apply_user_permissions", {}).get(ptype)
def get_user_permission_doctypes(user_permission_doctypes, user_permissions):
if user_permission_doctypes:
# select those user permission doctypes for which user permissions exist!
user_permission_doctypes = list(set(user_permission_doctypes).intersection(set(user_permissions.keys())))
else:
user_permission_doctypes = user_permissions.keys()
return user_permission_doctypes

View file

@ -62,14 +62,13 @@ $.extend(frappe.meta, {
return docfields;
},
get_fields_to_check_permissions: function(doctype, name, user_permissions) {
var user_permissions_doctypes = Object.keys(user_permissions);
get_fields_to_check_permissions: function(doctype, name, user_permission_doctypes) {
var fields = $.map(frappe.meta.get_docfields(doctype, name), function(df) {
return (df.fieldtype==="Link" && df.ignore_user_permissions!==1 &&
user_permissions_doctypes.indexOf(df.options)!==-1) ? df : null;
user_permission_doctypes.indexOf(df.options)!==-1) ? df : null;
});
if (user_permissions_doctypes.indexOf(doctype)!==-1) {
if (user_permission_doctypes.indexOf(doctype)!==-1) {
fields = fields.concat({label: "Name", fieldname: name, options: doctype});
}

View file

@ -82,6 +82,20 @@ $.extend(frappe.perm, {
apply_user_permissions[key] = current_value && p.apply_user_permissions;
}
});
if (permlevel===0 && p.apply_user_permissions && p.user_permission_doctypes) {
// set user_permission_doctypes in perms
var user_permission_doctypes = p.user_permission_doctypes
? JSON.parse(p.user_permission_doctypes) : null;
if (user_permission_doctypes && (!perm[permlevel].user_permission_doctypes ||
user_permission_doctypes.length <= perm[permlevel].user_permission_doctypes.length)) {
// selecting the least no. of "user_permission_doctypes" for lesser filtering
// why? if there is a conflict of two user_permission_doctypes, the least restrictive should win
// hence, using the simplistic approach of less no. of "user_permission_doctypes" implies least restrictive!
perm[permlevel]["user_permission_doctypes"] = user_permission_doctypes;
}
}
}
});
@ -94,6 +108,8 @@ $.extend(frappe.perm, {
},
get_match_rules: function(doctype, ptype) {
var me = this;
if (!ptype) ptype = "read";
var perm = frappe.perm.get_perm(doctype);
@ -105,7 +121,10 @@ $.extend(frappe.perm, {
var match_rules = {};
var user_permissions = frappe.defaults.get_user_permissions();
if(user_permissions && !$.isEmptyObject(user_permissions)) {
var fields_to_check = frappe.meta.get_fields_to_check_permissions(doctype, null, user_permissions);
var user_permission_doctypes = me.get_user_permission_doctypes(perm[0].user_permission_doctypes,
user_permissions);
var fields_to_check = frappe.meta.get_fields_to_check_permissions(doctype, null, user_permission_doctypes);
$.each(fields_to_check, function(i, df) {
match_rules[df.label] = user_permissions[df.options];
});
@ -114,6 +133,20 @@ $.extend(frappe.perm, {
return match_rules;
},
get_user_permission_doctypes: function(user_permission_doctypes, user_permissions) {
if (user_permission_doctypes) {
var out = [];
$.each(user_permission_doctypes, function(i, doctype) {
if (user_permissions[doctype]) {
out.push(doctype);
}
});
return out;
} else {
return Object.keys(user_permissions);
}
},
get_field_display_status: function(df, doc, perm, explain) {
if(!doc) return "Write";

View file

@ -116,7 +116,8 @@ frappe.ui.AppFrame = Class.extend({
views.push({
icon: module_info.icon,
route: "Module/" + meta.module,
type: "module"
type: "module",
label: __("Module")
})
}
@ -124,6 +125,7 @@ frappe.ui.AppFrame = Class.extend({
icon: "icon-file-alt",
route: "",
type: "form",
label: __("Form"),
set_route: function() {
console.log(me.doctype);
if(frappe.views.formview[me.doctype]) {
@ -140,7 +142,8 @@ frappe.ui.AppFrame = Class.extend({
views.push({
icon: "icon-list",
route: "List/" + doctype,
type: "list"
type: "list",
label: __("List")
});
}
@ -148,7 +151,8 @@ frappe.ui.AppFrame = Class.extend({
views.push({
icon: "icon-calendar",
route: "Calendar/" + doctype,
type: "calendar"
type: "calendar",
label: __("Calendar")
});
}
@ -156,7 +160,8 @@ frappe.ui.AppFrame = Class.extend({
views.push({
icon: "icon-tasks",
route: "Gantt/" + doctype,
type: "gantt"
type: "gantt",
label: __("Gantt Chart")
});
}
@ -164,7 +169,8 @@ frappe.ui.AppFrame = Class.extend({
views.push({
icon: "icon-table",
route: "Report/" + doctype,
type: "report"
type: "report",
label: __("Report")
});
}
@ -174,7 +180,7 @@ frappe.ui.AppFrame = Class.extend({
set_views: function(views, active_view) {
var me = this;
$.each(views, function(i, e) {
var btn = me.add_icon_btn("3", e.icon, __(toTitle(e.type)), e.set_route || function() {
var btn = me.add_icon_btn("3", e.icon, __(e.label) || __(toTitle(e.type)), e.set_route || function() {
window.location.hash = "#" + $(this).attr("data-route");
}).attr("data-route", e.route);

View file

@ -63,7 +63,7 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
this.appframe = this.page.appframe;
var module = locals.DocType[this.doctype].module;
this.appframe.set_title(__("{0} List", [this.doctype]));
this.appframe.set_title(__("{0} List", [__(this.doctype)]));
this.appframe.add_module_icon(module, this.doctype, null, true);
this.appframe.set_title_left(function() {
frappe.set_route(frappe.listview_parent_route[me.doctype]
@ -253,14 +253,11 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
make_no_result: function() {
var new_button = frappe.boot.user.can_create.indexOf(this.doctype)!=-1
? ('<hr><p><button class="btn btn-primary" \
list_view_doc="%(doctype)s">'+
__('Make a new') + ' %(doctype_label)s</button></p>')
list_view_doc="' + this.doctype + '">'+
__('Make a new {0}', [__(this.doctype)]) + '</button></p>')
: '';
var no_result_message = repl('<div class="well" style="margin-top: 20px;">\
<p>' + __("No") + ' %(doctype_label)s ' + __("found") + '</p>' + new_button + '</div>', {
doctype_label: __(this.doctype),
doctype: this.doctype,
});
var no_result_message = '<div class="well" style="margin-top: 20px;">\
<p>' + __("No {0} found", [__(this.doctype)]) + '</p>' + new_button + '</div>';
return no_result_message;
},

View file

@ -106,6 +106,7 @@ def get_dict(fortype, name=None):
elif fortype=="boot":
messages = get_messages_from_include_files()
messages += frappe.db.sql_list("select name from tabDocType")
messages += frappe.db.sql_list("select name from tabRole")
messages += frappe.db.sql_list("select name from `tabModule Def`")
translation_assets[asset_key] = make_dict_from_messages(messages)
@ -193,6 +194,7 @@ def get_messages_from_doctype(name):
messages = [meta.name, meta.module]
# translations of field labels, description and options
for d in meta.get("fields"):
messages.extend([d.label, d.description])
@ -202,6 +204,11 @@ def get_messages_from_doctype(name):
if not "icon" in options[0]:
messages.extend(options)
# translations of roles
for d in meta.get("permissions"):
if d.role:
messages.append(d.role)
# extract from js, py files
doctype_file_path = frappe.get_module_path(meta.module, "doctype", meta.name, meta.name)
messages.extend(get_messages_from_file(doctype_file_path + ".js"))
@ -301,6 +308,8 @@ def get_untranslated(lang, untranslated_file, get_all=False):
for app in apps:
messages.extend(get_messages_for_app(app))
messages = list(set(messages))
def escape_newlines(s):
return (s.replace("\\\n", "|||||")
.replace("\\n", "||||")
@ -366,3 +375,10 @@ def write_translations_file(app, lang, full_dict=None):
frappe.create_folder(tpath)
write_csv_file(os.path.join(tpath, lang + ".csv"),
app_messages, full_dict or get_full_dict(lang))
def send_translations(translation_dict):
"""send these translations in response"""
if "__messages" not in frappe.local.response:
frappe.local.response["__messages"] = {}
frappe.local.response["__messages"].update(translation_dict)

View file

@ -43,14 +43,14 @@ def build_response(response_type=None):
def as_csv():
response = Response()
response.headers[b"Content-Type"] = b"text/csv; charset: utf-8"
response.headers[b"Content-Disposition"] = ("attachment; filename=%s.csv" % frappe.response['doctype'].replace(' ', '_')).encode("utf-8")
response.headers[b"Content-Disposition"] = ("attachment; filename=\"%s.csv\"" % frappe.response['doctype'].replace(' ', '_')).encode("utf-8")
response.data = frappe.response['result']
return response
def as_raw():
response = Response()
response.headers[b"Content-Type"] = frappe.response.get("content_type") or mimetypes.guess_type(frappe.response['filename'])[0] or b"application/unknown"
response.headers[b"Content-Disposition"] = ("filename=%s" % frappe.response['filename'].replace(' ', '_')).encode("utf-8")
response.headers[b"Content-Disposition"] = ("filename=\"%s\"" % frappe.response['filename'].replace(' ', '_')).encode("utf-8")
response.data = frappe.response['filecontent']
return response

View file

@ -6,6 +6,8 @@
import frappe
import frappe.defaults
import unittest
import json
import frappe.model.meta
from frappe.core.page.user_permissions.user_permissions import add, remove, get_permissions
from frappe.permissions import clear_user_permissions_for_doctype, get_doc_permissions
@ -30,6 +32,9 @@ class TestBlogPost(unittest.TestCase):
clear_user_permissions_for_doctype("Blog Category")
clear_user_permissions_for_doctype("Blog Post")
clear_user_permissions_for_doctype("Blogger")
frappe.db.sql("""update `tabDocPerm` set user_permission_doctypes=null
where parent='Blog Post' and permlevel=0 and apply_user_permissions=1
and `read`=1""")
def test_basic_permission(self):
post = frappe.get_doc("Blog Post", "_test-blog-post")
@ -145,3 +150,25 @@ class TestBlogPost(unittest.TestCase):
doc.title = "New"
self.assertRaises(frappe.CannotChangeConstantError, doc.save)
blog_post.get_field("title").set_only_once = 0
def test_user_permission_doctypes(self):
frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1",
"test2@example.com")
frappe.permissions.add_user_permission("Blogger", "_Test Blogger 1",
"test2@example.com")
frappe.set_user("test2@example.com")
frappe.db.sql("""update `tabDocPerm` set user_permission_doctypes=%s
where parent='Blog Post' and permlevel=0 and apply_user_permissions=1
and `read`=1""", json.dumps(["Blogger"]))
frappe.model.meta.clear_cache("Blog Post")
doc = frappe.get_doc("Blog Post", "_test-blog-post")
self.assertFalse(doc.has_permission("read"))
doc = frappe.get_doc("Blog Post", "_test-blog-post-2")
self.assertTrue(doc.has_permission("read"))
frappe.model.meta.clear_cache("Blog Post")

View file

@ -1,8 +1,9 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.translate import send_translations
@frappe.whitelist()
def get(name):
@ -28,14 +29,14 @@ def getpage():
# load translations
if frappe.lang != "en":
frappe.response["__messages"] = frappe.get_lang_dict("page", page)
send_translations(frappe.get_lang_dict("page", page))
frappe.response.docs.append(doc)
def has_permission(page):
if frappe.user.name == "Administrator" or "System Manager" in frappe.user.get_roles():
return True
page_roles = [d.role for d in page.get("roles")]
if page_roles:
if frappe.session.user == "Guest" and "Guest" not in page_roles:
@ -43,7 +44,7 @@ def has_permission(page):
elif not set(page_roles).intersection(set(frappe.get_roles())):
# check if roles match
return False
if not frappe.has_permission("Page", ptype="read", doc=page):
# check if there are any user_permissions
return False

View file

@ -9,6 +9,7 @@ import os, json
from frappe import _
from frappe.modules import scrub, get_module_path
from frappe.utils import flt, cint, get_html_format
from frappe.translate import send_translations
import frappe.widgets.reportview
def get_report_doc(report_name):
@ -50,7 +51,7 @@ def get_script(report_name):
# load translations
if frappe.lang != "en":
frappe.response["__messages"] = frappe.get_lang_dict("report", report_name)
send_translations(frappe.get_lang_dict("report", report_name))
return {
"script": script,

View file

@ -74,7 +74,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0,
fields = get_std_fields_list(meta, searchfield or "name")
# find relevance as location of search term from the beginning of string `name`. used for sorting results.
fields.append("""locate("{_txt}", name) as `_relevance`""".format(
fields.append("""locate("{_txt}", `tab{doctype}`.`name`) as `_relevance`""".format(
_txt=frappe.db.escape((txt or "").replace("%", "")), doctype=doctype))
values = frappe.widgets.reportview.execute(doctype,