Browse Source

fixes to permission manager / user property setter

version-14
Rushabh Mehta 12 years ago
parent
commit
872620b8af
16 changed files with 304 additions and 408 deletions
  1. +13
    -19
      core/doctype/doctype/doctype.py
  2. +6
    -2
      core/doctype/profile/profile.js
  3. +14
    -1
      core/doctype/profile/profile.txt
  4. +9
    -13
      core/doctype/userrole/userrole.txt
  5. +91
    -79
      core/page/permission_manager/permission_manager.js
  6. +1
    -1
      core/page/permission_manager/permission_manager.py
  7. +19
    -8
      core/page/user_properties/user_properties.js
  8. +13
    -6
      core/page/user_properties/user_properties.py
  9. +1
    -1
      public/build.json
  10. +77
    -77
      public/css/bootstrap.css
  11. +0
    -117
      public/js/legacy/wn/widgets/form/assign_to.js
  12. +9
    -2
      public/js/legacy/wn/widgets/form/sidebar.js
  13. +1
    -1
      public/js/wn/form/attachments.js
  14. +9
    -2
      public/js/wn/ui/dialog.js
  15. +34
    -76
      webnotes/db.py
  16. +7
    -3
      webnotes/widgets/form/assign_to.py

+ 13
- 19
core/doctype/doctype/doctype.py View File

@@ -244,30 +244,24 @@ def validate_permissions(permissions, for_remove=False):
webnotes.msgprint(get_txt(d) + " Higher level permissions are meaningless if level 0 permission is not set.",
raise_exception=True)
if d.create:
webnotes.msgprint("Create Permission has no meaning at level " + d.permlevel,
raise_exception=True)

if d.submit:
webnotes.msgprint("Submit Permission has no meaning at level " + d.permlevel,
raise_exception=True)
if d.cancel:
webnotes.msgprint("Cancel Permission has no meaning at level " + d.permlevel,
raise_exception=True)

if d.amend:
webnotes.msgprint("Amend Permission has no meaning at level " + d.permlevel,
raise_exception=True)

if d.match:
webnotes.msgprint("Match rules have no meaning at level " + d.permlevel,
if d.create or d.submit or d.cancel or d.amend or d.match:
webnotes.msgprint("Create, Submit, Cancel, Amend, Match has no meaning at level " + d.permlevel,
raise_exception=True)
def check_permission_dependency(d):
if d.write and not d.read:
webnotes.msgprint(get_txt(d) + " Cannot set Write permission if Read is not set.",
raise_exception=True)
if (d.submit or d.cancel or d.amend) and not d.write:
webnotes.msgprint(get_txt(d) + " Cannot set Submit, Cancel, Amend permission if Write is not set.",
raise_exception=True)
for d in permissions:
if not d.permlevel: d.permlevel=0
if not d.permlevel:
d.permlevel=0
check_atleast_one_set(d)
if not for_remove:
check_double(d)
check_level_zero_is_set(d)
check_permission_dependency(d)

+ 6
- 2
core/doctype/profile/profile.js View File

@@ -22,18 +22,22 @@ cur_frm.cscript.refresh = function(doc) {
// update display settings
wn.ui.set_theme(doc.theme);
if(doc.background_image) {
wn.ui.set_user_background(doc.background_image);
wn.ui.set_user_background(doc.background_image);
}
if(doc.user_image) {
wn.boot.user_info[user].image = wn.utils.get_file_link(doc.user_image);
}
}
}
cur_frm.add_custom_button("Set Properties", function() {
wn.set_route("user-properties", doc.name);
})
}

cur_frm.cscript.enabled = function(doc) {
if(!doc.__islocal) {
cur_frm.toggle_display(['sb1', 'sb2', 'sb3'], doc.enabled);
cur_frm.toggle_display(['sb1', 'sb2'], doc.enabled);
cur_frm.toggle_enable('*', doc.enabled);
cur_frm.set_df_property('enabled', 'disabled', false);
}


+ 14
- 1
core/doctype/profile/profile.txt View File

@@ -4,7 +4,7 @@
"docstatus": 0,
"creation": "2012-12-07 15:15:20",
"modified_by": "Administrator",
"modified": "2012-12-21 12:35:01"
"modified": "2013-01-02 14:47:27"
},
{
"istable": 0,
@@ -111,6 +111,7 @@
"permlevel": 0
},
{
"print_width": "50%",
"oldfieldtype": "Column Break",
"doctype": "DocField",
"width": "50%",
@@ -297,12 +298,14 @@
},
{
"description": "These values will be automatically updated in transactions and also will be useful to restrict permissions for this user on transactions containing these values.",
"print_width": "50%",
"oldfieldtype": "Column Break",
"doctype": "DocField",
"label": "Defaults",
"width": "50%",
"fieldname": "sb2",
"fieldtype": "Section Break",
"hidden": 1,
"permlevel": 1
},
{
@@ -312,6 +315,7 @@
"options": "DefaultValue",
"fieldname": "defaults",
"fieldtype": "Table",
"hidden": 1,
"permlevel": 0
},
{
@@ -347,6 +351,7 @@
"permlevel": 1
},
{
"print_width": "50%",
"oldfieldtype": "Column Break",
"doctype": "DocField",
"width": "50%",
@@ -409,13 +414,21 @@
"match": "owner"
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"submit": 0,
"role": "Administrator",
"cancel": 0,
"permlevel": 1
},
{
"amend": 0,
"create": 0,
"doctype": "DocPerm",
"submit": 0,
"role": "System Manager",
"cancel": 0,
"permlevel": 1
}
]

+ 9
- 13
core/doctype/userrole/userrole.txt View File

@@ -2,45 +2,41 @@
{
"owner": "Administrator",
"docstatus": 0,
"creation": "2012-07-03 13:30:34",
"creation": "2012-07-13 16:53:46",
"modified_by": "Administrator",
"modified": "2012-07-13 12:25:07"
"modified": "2013-01-02 12:46:21"
},
{
"section_style": "Simple",
"istable": 0,
"istable": 1,
"allow_print": 0,
"module": "Core",
"server_code_error": " ",
"issingle": 0,
"autoname": "UR.#####",
"read_only": 0,
"allow_email": 0,
"hide_heading": 0,
"autoname": "UR.#####",
"issingle": 0,
"name": "__common__",
"colour": "White:FFF",
"doctype": "DocType",
"show_in_menu": 0,
"version": 1,
"hide_toolbar": 0,
"allow_copy": 0
},
{
"parent": "UserRole",
"permlevel": 0,
"print_width": "200px",
"oldfieldtype": "Link",
"doctype": "DocField",
"oldfieldname": "role",
"reqd": 0,
"name": "__common__",
"doctype": "DocField",
"label": "Role",
"width": "200px",
"parenttype": "DocType",
"options": "Role",
"fieldname": "role",
"fieldtype": "Link",
"search_index": 0,
"hidden": 0,
"options": "Role",
"permlevel": 0,
"parentfield": "fields"
},
{


+ 91
- 79
core/page/permission_manager/permission_manager.js View File

@@ -4,6 +4,69 @@ wn.pages['permission-manager'].onload = function(wrapper) {
title: 'Permission Manager',
single_column: true
});
$(wrapper).find(".layout-main").html("<div class='perm-engine'></div>\
<table class='table table-bordered' style='background-color: #f9f9f9;'>\
<tr><td>\
<h4><i class='icon-question-sign'></i> Quick Help for Setting Permissions:</h4>\
<ol>\
<li>Permissions are set on Roles and Document Types (called DocTypes) by restricting \
read, write, create, submit, cancel and amend rights.</li>\
<li>Permissions translate to Users based on what Role they are assigned.</li>\
<li>To set user roles, just go to <a href='#List/Profile'>Setup > Users</a> \
and click on the user to assign roles.</li>\
<li>The system provides pre-defined roles, but you can <a href='#List/Role'>add new roles</a>\
to set finer permissions.</li>\
<li>Permissions are automatically translated to Standard Reports and Searches.</li>\
<li>As a best practice, do not assign the same set of permission rule to different Roles\
instead set multiple Roles to the User.</li>\
</ol>\
</tr></td>\
<tr><td>\
<h4><i class='icon-hand-right'></i> Meaning of Submit, Cancel, Amend:</h4>\
<ol>\
<li>Certain documents should not be changed once final, like an\
Invoice for example. The final state for such documents is called <b>Submitted</b>.\
You can restrict which roles can Submit.</li>\
<li><b>Cancel</b> allows you change Submitted documents by cancelling them and amending them.\
Cancel permissions also allows the user to delete a document (if it is not linked to any other document).</li>\
<li>When you <b>Amend</b> a document after cancel and save it, it will get a new number that is\
a version of the old number. For example if you cancel and amend 'INV004' it will become a new\
document 'INV004-1'. This helps you to keep track of each amendment.</li>\
</ol>\
</tr></td>\
<tr><td>\
<h4><i class='icon-user'></i> Restricting By User:</h4>\
<ol>\
<li>To restrict a User of a particular Role to documents that are only self-created,\
Click on button in the 'Condition' column and select the option 'User is the creator of the document'.</li>\
<li>To restrict a User of a particular Role to documents that are explicitly assigned to them,\
create a Custom Field of type Link (Profile) and then use the 'Condition' settings\
to map that field to the Permission rule.\
</ol>\
</tr></td>\
<tr><td>\
<h4><i class='icon-cog'></i> Advanced Settings:</h4>\
<p>To further restrict permissions based on certain values in a document, use the\
'Condition' settings. <br><br>For example: You want to restrict users to transactions marked\
with a certain property called 'Territory':</p>\
<ol>\
<li>Make sure that the transactions you want to restrict have a Link \
field 'territory' that maps to a 'Territory' master. If not, create a\
<a href='#List/Custom Field'>Custom Field</a> of type Link.</li>\
<li>In the Permission Manager, click on the button in the 'Condition' column\
for the Role you want to restrict.</li>\
<li>A new pop will open that will ask you to select further conditions. \
If the 'territory' Link Field exists, it will give you an option to select \
it.</li>\
<li>Go to Setup > <a href='#user-properties'>User Properties</a> to set \
'territory' for diffent Users.</li>\
</ol>\
<p>Once you have set this, the users will only be able access documents with that property</p>\
<hr>\
<p>If these instructions where not helpful, please add in your suggestions at\
<a href='https://github.com/webnotes/wnframework/issues'>GitHub Issues</a></p>\
</tr></td>\
</table>");
wrapper.permission_engine = new wn.PermissionEngine(wrapper);
}
wn.pages['permission-manager'].refresh = function(wrapper) {
@@ -13,7 +76,7 @@ wn.pages['permission-manager'].refresh = function(wrapper) {
wn.PermissionEngine = Class.extend({
init: function(wrapper) {
this.wrapper = wrapper;
this.body = $(this.wrapper).find(".layout-main");
this.body = $(this.wrapper).find(".perm-engine");
this.make();
this.refresh();
this.add_check_events();
@@ -68,7 +131,7 @@ wn.PermissionEngine = Class.extend({
callback: function() { me.refresh(); }
});
}));
}, 'icon-retweet');
}, 'icon-retweet').toggle(false);
},
get_doctype: function() {
var doctype = this.doctype_select.val();
@@ -112,7 +175,6 @@ wn.PermissionEngine = Class.extend({
this.show_permission_table(perm_list);
}
this.show_add_rule();
this.show_explain();
},
show_permission_table: function(perm_list) {
var me = this;
@@ -152,7 +214,8 @@ wn.PermissionEngine = Class.extend({
if(!d.permlevel) d.permlevel = 0;
var row = $("<tr>").appendTo(me.table.find("tbody"));
add_cell(row, d, "parent");
add_cell(row, d, "role");
me.set_show_users(add_cell(row, d, "role"), d.role);
var cell = add_cell(row, d, "permlevel");
if(d.permlevel==0) {
cell.css("font-weight", "bold");
@@ -170,6 +233,30 @@ wn.PermissionEngine = Class.extend({
me.add_delete_button(row, d);
});
},
set_show_users: function(cell, role) {
cell.html("<a href='#'>"+role+"</a>")
.find("a")
.attr("data-role", role)
.click(function() {
var role = $(this).attr("data-role");
wn.call({
module: "core",
page: "permission_manager",
method: "get_users_with_role",
args: {
role: role
},
callback: function(r) {
r.message = $.map(r.message, function(p) {
return '<a href="#Form/Profile/'+p+'">'+p+'</a>';
})
msgprint("<h4>Users with role "+role+":</h4>"
+ r.message.join("<br>"));
}
})
return false;
})
},
add_match_button: function(row, d) {
var me = this;
if(d.permlevel > 0) {
@@ -231,7 +318,6 @@ wn.PermissionEngine = Class.extend({
chk.attr("checked", chk.is(":checked") ? null : "checked");
} else {
me.get_perm(args.name)[args.ptype]=args.value;
me.show_explain();
}
}
})
@@ -367,79 +453,5 @@ wn.PermissionEngine = Class.extend({
get_link_fields: function(doctype) {
return link_fields = wn.model.get("DocField", {parent:doctype,
fieldtype:"Link", options:["not in", ["Profile", '[Select]']]});
},
show_explain: function() {
$(".perm-explain").remove();
if(!this.get_doctype()) return;
var wrapper = $("<div class='perm-explain well'></div>").appendTo(this.body);
var doctype = null;
var core_finished = false;
$.each(this.perm_list, function(i, p) {
if(p.parent != doctype) {
core_finished = false;
doctype = p.parent;
$('<h3>For ' + doctype + '</h3><h4>Document Permissions</h4>')
.appendTo(wrapper);
}

if(p.permlevel==0) {
var perms = $.map(["read", "write", "create", "submit", "cancel", "amend"], function(type) {
if(p[type]) return type;
}).join(", ");
if(!p.match) {
var _p = $('<p>').html("A user with role <b>"+p.role + "</b> can "
+ perms + " a document of type <b>" + doctype + "</b>.")
} else {
if(p.match=="owner") {
var _p = $('<p>').html("A user with role <b>"+p.role + "</b> can "
+ perms + " <b>" + doctype + "</b> <u>only if</u> that document is created by that user.");
} else if(p.match.indexOf(":")!=-1) {
var field = p.match.split(":")[0];
var _p = $('<p>').html("A user with role <b>"+p.role + "</b> can "
+ perms + " <b>" + doctype + "</b> <u>only if</u> <b>"+field+"</b> equals User's Id.");
} else {
var _p = $('<p>').html("A user with role <b>"+p.role + "</b> can "
+ perms + " <b>" + doctype + "</b> <u>only for</u> records with user's <b>"+p.match+"</b> property.");
}
}

} else {
if(!core_finished) {
core_finished = true;
$("<br><h4>Field Level Permissions</h4>").appendTo(wrapper);
}
var perms = $.map(["read", "write"], function(type) {
if(p[type]) return type;
}).join(", ");
var _p = $('<p>').html("A user with role <b>"+p.role + "</b> can only <u>"
+ perms + "</u> fields at level <b>"+ p.permlevel +"</b> in a <b>" + doctype + "</b>.")
}

$("<a>Show Users</a>").appendTo(_p).attr("data-role", p.role)
.css("margin-left", "7px")
.click(function() {
var link = $(this);
wn.call({
module: "core",
page: "permission_manager",
method: "get_users_with_role",
args: {
role: link.attr("data-role"),
},
callback: function(r) {
$.each(r.message, function(i, uid) {
msgprint("<a href='#Form/Profile/"+uid+"'>"
+ wn.user_info(uid).fullname + "</a> ("+
uid+")");
});
cur_dialog.set_title("Users with role "
+ link.attr("data-role"));
}
});
});
_p.appendTo(wrapper);
})
}
})

+ 1
- 1
core/page/permission_manager/permission_manager.py View File

@@ -7,7 +7,7 @@ def get_roles_and_doctypes():
"doctypes": [d[0] for d in webnotes.conn.sql("""select name from tabDocType where
ifnull(istable,0)=0 and
ifnull(issingle,0)=0 and
module != 'Core' """)],
name not in ('DocType')""")],
"roles": [d[0] for d in webnotes.conn.sql("""select name from tabRole where name not in
('All', 'Guest', 'Administrator')""")]
}


+ 19
- 8
core/page/user_properties/user_properties.js View File

@@ -4,6 +4,21 @@ wn.pages['user-properties'].onload = function(wrapper) {
title: 'User Properties',
single_column: true
});
$(wrapper).find(".layout-main").html("<div class='user-settings'></div>\
<table class='table table-bordered' style='background-color: #f9f9f9;'>\
<tr><td>\
<h4><i class='icon-question-sign'></i> Quick Help for User Properties:</h4>\
<ol>\
<li>You can set various 'properties' to Users.</li>\
<li>These properties are Link Type fields from all Documents.</li>\
<li>These properties will appear as values in forms that contain them.</li>\
<li>These properties can also be used to 'assign' a particular document, \
whose property matches with the User's property to a User. These can be set\
using the <a href='#permission-manager'>Permission Manager</a></li>\
<li>A user can have multiple values for a property.</li>\
</ol>\
</tr></td>\
</table>");
wrapper.user_properties = new wn.UserProperties(wrapper);
}

@@ -14,7 +29,7 @@ wn.pages['user-properties'].refresh = function(wrapper) {
wn.UserProperties = Class.extend({
init: function(wrapper) {
this.wrapper = wrapper;
this.body = $(this.wrapper).find(".layout-main");
this.body = $(this.wrapper).find(".user-settings");
this.make();
this.refresh();
},
@@ -72,11 +87,6 @@ wn.UserProperties = Class.extend({
this.show_property_table();
}
this.show_add_property();
$("<div class='well'>User Properties appear as default values in forms.\
<br>They are also used to restrict permissions \
in the <a href='#permission-manager'>Permission Manager</a>\
<br>You can also set multiple values for one property. \
If so, the permission rules will apply if any of the values match.</div>").appendTo(this.body);
},
refresh: function() {
var me = this;
@@ -119,7 +129,8 @@ wn.UserProperties = Class.extend({
$.each(this.prop_list, function(i, d) {
var row = $("<tr>").appendTo(me.table.find("tbody"));
$("<td>").html(d.parent).appendTo(row);
$("<td>").html('<a href="#Form/Profile/'+d.parent+'">'
+d.parent+'</a>').appendTo(row);
$("<td>").html(d.defkey).appendTo(row);
$("<td>").html(d.defvalue).appendTo(row);
@@ -184,7 +195,7 @@ wn.UserProperties = Class.extend({
if(l[0]==key) return l[1];
})[0];
return 'select name from `tab'+doctype
+'` where name like "%s"'
+'` where name like "%s" limit 20'
}
d.get_input("add").click(function() {
var args = d.get_values();


+ 13
- 6
core/page/user_properties/user_properties.py View File

@@ -3,11 +3,17 @@ import webnotes

@webnotes.whitelist(allow_roles=["System Manager", "Administrator"])
def get_users_and_links():
links = list(set(webnotes.conn.sql("""select fieldname, options
links, all_fields = [], []

for l in webnotes.conn.sql("""select fieldname, options
from tabDocField where fieldtype='Link'
and parent not in ('[Select]', 'DocType', 'Module Def')
""") + webnotes.conn.sql("""select fieldname, options
from `tabCustom Field` where fieldtype='Link'""")))
from `tabCustom Field` where fieldtype='Link'"""):
if not l[0] in all_fields:
links.append([l[0], l[1]])
all_fields.append(l[0])
links.sort()

return {
@@ -21,10 +27,11 @@ def get_users_and_links():
def get_properties(user=None, key=None):
return webnotes.conn.sql("""select name, parent, defkey, defvalue
from tabDefaultValue
where %s%s and parent!='Control Panel' order by parent, defkey""" % (\
user and (" parent='%s'" % user) or "",
key and ((user and " and " or "") + " defkey='%s'" % key) or "",
), as_dict=True)
where parent!='Control Panel'
and substr(defkey,0,1)!='_'
%s%s order by parent, defkey""" % (\
user and (" and parent='%s'" % user) or "",
key and (" and defkey='%s'" % key) or ""), as_dict=True)

@webnotes.whitelist(allow_roles=["System Manager", "Administrator"])
def remove(user, name):


+ 1
- 1
public/build.json View File

@@ -150,10 +150,10 @@
"lib/public/js/legacy/widgets/form/form_comments.js",
"lib/public/js/legacy/wn/widgets/form/sidebar.js",
"lib/public/js/legacy/wn/widgets/form/comments.js",
"lib/public/js/legacy/wn/widgets/form/assign_to.js",
"lib/public/js/wn/form/attachments.js",
"lib/public/js/wn/form/linked_with.js",
"lib/public/js/wn/form/states.js",
"lib/public/js/wn/form/assign_to.js",
"lib/public/js/wn/print/print_table.js",

'lib/public/js/lib/jquery/jquery.ui.interactions.min.js',


+ 77
- 77
public/css/bootstrap.css View File

@@ -215,7 +215,7 @@ textarea {
.input-block-level {
display: block;
width: 100%;
min-height: 28px;
min-height: 30px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
@@ -225,7 +225,7 @@ body {
margin: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
line-height: 18px;
line-height: 20px;
color: #333333;
background-color: #ffffff;
}
@@ -407,7 +407,7 @@ a:hover {
display: block;
float: left;
width: 100%;
min-height: 28px;
min-height: 30px;
margin-left: 2.127659574468085%;
*margin-left: 2.074468085106383%;
-webkit-box-sizing: border-box;
@@ -648,14 +648,14 @@ a:hover {
}

p {
margin: 0 0 9px;
margin: 0 0 10px;
}

.lead {
margin-bottom: 18px;
margin-bottom: 20px;
font-size: 19.5px;
font-weight: 200;
line-height: 27px;
line-height: 30px;
}

small {
@@ -720,10 +720,10 @@ h3,
h4,
h5,
h6 {
margin: 9px 0;
margin: 10px 0;
font-family: inherit;
font-weight: bold;
line-height: 18px;
line-height: 20px;
color: inherit;
text-rendering: optimizelegibility;
}
@@ -742,7 +742,7 @@ h6 small {
h1,
h2,
h3 {
line-height: 36px;
line-height: 40px;
}

h1 {
@@ -786,15 +786,15 @@ h4 small {
}

.page-header {
padding-bottom: 8px;
margin: 18px 0 27px;
padding-bottom: 9px;
margin: 20px 0 30px;
border-bottom: 1px solid #eeeeee;
}

ul,
ol {
padding: 0;
margin: 0 0 9px 25px;
margin: 0 0 10px 25px;
}

ul ul,
@@ -805,7 +805,7 @@ ol ul {
}

li {
line-height: 18px;
line-height: 20px;
}

ul.unstyled,
@@ -828,12 +828,12 @@ ol.inline > li {
}

dl {
margin-bottom: 18px;
margin-bottom: 20px;
}

dt,
dd {
line-height: 18px;
line-height: 20px;
}

dt {
@@ -841,7 +841,7 @@ dt {
}

dd {
margin-left: 9px;
margin-left: 10px;
}

.dl-horizontal {
@@ -874,7 +874,7 @@ dd {
}

hr {
margin: 18px 0;
margin: 20px 0;
border: 0;
border-top: 1px solid #eeeeee;
border-bottom: 1px solid #ffffff;
@@ -893,7 +893,7 @@ abbr.initialism {

blockquote {
padding: 0 0 0 15px;
margin: 0 0 18px;
margin: 0 0 20px;
border-left: 5px solid #eeeeee;
}

@@ -901,12 +901,12 @@ blockquote p {
margin-bottom: 0;
font-size: 16px;
font-weight: 300;
line-height: 22.5px;
line-height: 25px;
}

blockquote small {
display: block;
line-height: 18px;
line-height: 20px;
color: #999999;
}

@@ -944,9 +944,9 @@ blockquote:after {

address {
display: block;
margin-bottom: 18px;
margin-bottom: 20px;
font-style: normal;
line-height: 18px;
line-height: 20px;
}

code,
@@ -970,10 +970,10 @@ code {

pre {
display: block;
padding: 8.5px;
margin: 0 0 9px;
padding: 9.5px;
margin: 0 0 10px;
font-size: 12px;
line-height: 18px;
line-height: 20px;
word-break: break-all;
word-wrap: break-word;
white-space: pre;
@@ -987,7 +987,7 @@ pre {
}

pre.prettyprint {
margin-bottom: 18px;
margin-bottom: 20px;
}

pre code {
@@ -1005,7 +1005,7 @@ pre code {
}

form {
margin: 0 0 18px;
margin: 0 0 20px;
}

fieldset {
@@ -1018,16 +1018,16 @@ legend {
display: block;
width: 100%;
padding: 0;
margin-bottom: 18px;
margin-bottom: 20px;
font-size: 19.5px;
line-height: 36px;
line-height: 40px;
color: #333333;
border: 0;
border-bottom: 1px solid #e5e5e5;
}

legend small {
font-size: 13.5px;
font-size: 15px;
color: #999999;
}

@@ -1038,7 +1038,7 @@ select,
textarea {
font-size: 13px;
font-weight: normal;
line-height: 18px;
line-height: 20px;
}

input,
@@ -1071,11 +1071,11 @@ input[type="tel"],
input[type="color"],
.uneditable-input {
display: inline-block;
height: 18px;
height: 20px;
padding: 4px 6px;
margin-bottom: 9px;
margin-bottom: 10px;
font-size: 13px;
line-height: 18px;
line-height: 20px;
color: #555555;
vertical-align: middle;
-webkit-border-radius: 4px;
@@ -1166,13 +1166,13 @@ input[type="checkbox"] {

select,
input[type="file"] {
height: 28px;
height: 30px;
/* In IE7, the height of the select element cannot be changed by height, only font-size */

*margin-top: 4px;
/* For IE7, add top margin to align select with labels */

line-height: 28px;
line-height: 30px;
}

select {
@@ -1233,7 +1233,7 @@ textarea::-webkit-input-placeholder {

.radio,
.checkbox {
min-height: 18px;
min-height: 20px;
padding-left: 20px;
}

@@ -1607,9 +1607,9 @@ select:focus:invalid:focus {
}

.form-actions {
padding: 17px 20px 18px;
margin-top: 18px;
margin-bottom: 18px;
padding: 19px 20px 20px;
margin-top: 20px;
margin-bottom: 20px;
background-color: #f5f5f5;
border-top: 1px solid #e5e5e5;
*zoom: 1;
@@ -1633,7 +1633,7 @@ select:focus:invalid:focus {

.help-block {
display: block;
margin-bottom: 9px;
margin-bottom: 10px;
}

.help-inline {
@@ -1690,12 +1690,12 @@ select:focus:invalid:focus {
.input-prepend .add-on {
display: inline-block;
width: auto;
height: 18px;
height: 20px;
min-width: 16px;
padding: 4px 5px;
font-size: 13px;
font-weight: normal;
line-height: 18px;
line-height: 20px;
text-align: center;
text-shadow: 0 1px 0 #ffffff;
background-color: #eeeeee;
@@ -1911,16 +1911,16 @@ input.search-query {
}

.control-group {
margin-bottom: 9px;
margin-bottom: 10px;
}

legend + .control-group {
margin-top: 18px;
margin-top: 20px;
-webkit-margin-top-collapse: separate;
}

.form-horizontal .control-group {
margin-bottom: 18px;
margin-bottom: 20px;
*zoom: 1;
}

@@ -1963,7 +1963,7 @@ legend + .control-group {
.form-horizontal .uneditable-input + .help-block,
.form-horizontal .input-prepend + .help-block,
.form-horizontal .input-append + .help-block {
margin-top: 9px;
margin-top: 10px;
}

.form-horizontal .form-actions {
@@ -1979,13 +1979,13 @@ table {

.table {
width: 100%;
margin-bottom: 18px;
margin-bottom: 20px;
}

.table th,
.table td {
padding: 8px;
line-height: 18px;
line-height: 20px;
text-align: left;
vertical-align: top;
border-top: 1px solid #dddddd;
@@ -2308,7 +2308,7 @@ table th[class*="span"],
.dropdown-menu .divider {
*width: 100%;
height: 1px;
margin: 8px 1px;
margin: 9px 1px;
*margin: -5px 0 5px;
overflow: hidden;
background-color: #e5e5e5;
@@ -2320,7 +2320,7 @@ table th[class*="span"],
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 18px;
line-height: 20px;
color: #333333;
white-space: nowrap;
}
@@ -2529,7 +2529,7 @@ table th[class*="span"],
float: right;
font-size: 20px;
font-weight: bold;
line-height: 18px;
line-height: 20px;
color: #000000;
text-shadow: 0 1px 0 #ffffff;
opacity: 0.2;
@@ -2559,7 +2559,7 @@ button.close {
margin-bottom: 0;
*margin-left: .3em;
font-size: 13px;
line-height: 18px;
line-height: 20px;
color: #333333;
text-align: center;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
@@ -2989,8 +2989,8 @@ input[type="submit"].btn.btn-mini {
}

.btn-toolbar {
margin-top: 9px;
margin-bottom: 9px;
margin-top: 10px;
margin-bottom: 10px;
font-size: 0;
}

@@ -3224,7 +3224,7 @@ input[type="submit"].btn.btn-mini {

.alert {
padding: 8px 35px 8px 14px;
margin-bottom: 18px;
margin-bottom: 20px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
background-color: #fcf8e3;
border: 1px solid #fbeed5;
@@ -3246,7 +3246,7 @@ input[type="submit"].btn.btn-mini {
position: relative;
top: -2px;
right: -21px;
line-height: 18px;
line-height: 20px;
}

.alert-success {
@@ -3296,7 +3296,7 @@ input[type="submit"].btn.btn-mini {
}

.nav {
margin-bottom: 18px;
margin-bottom: 20px;
margin-left: 0;
list-style: none;
}
@@ -3323,7 +3323,7 @@ input[type="submit"].btn.btn-mini {
padding: 3px 15px;
font-size: 11px;
font-weight: bold;
line-height: 18px;
line-height: 20px;
color: #999999;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-transform: uppercase;
@@ -3365,7 +3365,7 @@ input[type="submit"].btn.btn-mini {
.nav-list .divider {
*width: 100%;
height: 1px;
margin: 8px 1px;
margin: 9px 1px;
*margin: -5px 0 5px;
overflow: hidden;
background-color: #e5e5e5;
@@ -3415,7 +3415,7 @@ input[type="submit"].btn.btn-mini {
.nav-tabs > li > a {
padding-top: 8px;
padding-bottom: 8px;
line-height: 18px;
line-height: 20px;
border: 1px solid transparent;
-webkit-border-radius: 4px 4px 0 0;
-moz-border-radius: 4px 4px 0 0;
@@ -3696,7 +3696,7 @@ input[type="submit"].btn.btn-mini {
.navbar {
*position: relative;
*z-index: 2;
margin-bottom: 18px;
margin-bottom: 20px;
overflow: visible;
}

@@ -3745,7 +3745,7 @@ input[type="submit"].btn.btn-mini {
.navbar .brand {
display: block;
float: left;
padding: 11px 20px 11px;
padding: 10px 20px 10px;
margin-left: -20px;
font-size: 20px;
font-weight: 200;
@@ -3939,7 +3939,7 @@ input[type="submit"].btn.btn-mini {

.navbar .nav > li > a {
float: none;
padding: 11px 15px 11px;
padding: 10px 15px 10px;
color: #777777;
text-decoration: none;
text-shadow: 0 1px 0 #ffffff;
@@ -4268,7 +4268,7 @@ input[type="submit"].btn.btn-mini {

.breadcrumb {
padding: 8px 15px;
margin: 0 0 18px;
margin: 0 0 20px;
list-style: none;
background-color: #f5f5f5;
-webkit-border-radius: 4px;
@@ -4293,7 +4293,7 @@ input[type="submit"].btn.btn-mini {
}

.pagination {
margin: 18px 0;
margin: 20px 0;
}

.pagination ul {
@@ -4318,7 +4318,7 @@ input[type="submit"].btn.btn-mini {
.pagination ul > li > span {
float: left;
padding: 4px 12px;
line-height: 18px;
line-height: 20px;
text-decoration: none;
background-color: #ffffff;
border: 1px solid #dddddd;
@@ -4437,7 +4437,7 @@ input[type="submit"].btn.btn-mini {
}

.pager {
margin: 18px 0;
margin: 20px 0;
text-align: center;
list-style: none;
*zoom: 1;
@@ -4858,14 +4858,14 @@ input[type="submit"].btn.btn-mini {

.thumbnails > li {
float: left;
margin-bottom: 18px;
margin-bottom: 20px;
margin-left: 20px;
}

.thumbnail {
display: block;
padding: 4px;
line-height: 18px;
line-height: 20px;
border: 1px solid #ddd;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
@@ -5082,8 +5082,8 @@ a.badge:hover {
}

.progress {
height: 18px;
margin-bottom: 18px;
height: 20px;
margin-bottom: 20px;
overflow: hidden;
background-color: #f7f7f7;
background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
@@ -5245,7 +5245,7 @@ a.badge:hover {
}

.accordion {
margin-bottom: 18px;
margin-bottom: 20px;
}

.accordion-group {
@@ -5276,7 +5276,7 @@ a.badge:hover {

.carousel {
position: relative;
margin-bottom: 18px;
margin-bottom: 20px;
line-height: 1;
}

@@ -5383,7 +5383,7 @@ a.badge:hover {

.carousel-caption h4,
.carousel-caption p {
line-height: 18px;
line-height: 20px;
color: #ffffff;
}

@@ -5400,7 +5400,7 @@ a.badge:hover {
margin-bottom: 30px;
font-size: 18px;
font-weight: 200;
line-height: 27px;
line-height: 30px;
color: inherit;
background-color: #eeeeee;
-webkit-border-radius: 6px;
@@ -5417,7 +5417,7 @@ a.badge:hover {
}

.hero-unit li {
line-height: 27px;
line-height: 30px;
}

.pull-right {


+ 0
- 117
public/js/legacy/wn/widgets/form/assign_to.js View File

@@ -1,117 +0,0 @@
// Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
//
// MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

// assign to is lined to todo
// refresh - load todos
// create - new todo
// delete to do
wn.widgets.form.sidebar.AssignTo = Class.extend({
init: function(parent, sidebar, doctype, docname) {
var me = this;
this.doctype = doctype;
this.name = docname;
this.wrapper = $a(parent, 'div', 'sidebar-comment-wrapper');
this.body = $a(this.wrapper, 'div');
this.add_btn = $("<button class='btn btn-small'>\
<i class='icon-plus'></i></button>")
.click(function() { me.add(); }).appendTo(this.wrapper).get(0);
this.refresh();
},
refresh: function() {
var me = this;
$c('webnotes.widgets.form.assign_to.get', {
doctype: me.doctype,
name: me.name
}, function(r,rt) {
me.render(r.message)
})
},
render: function(d) {
var me = this;
$(this.body).empty();
if(this.dialog) {
this.dialog.hide();
}
for(var i=0; i<d.length; i++) {
$(this.body).append(repl('<div>%(owner)s \
<a class="close" href="#" data-owner="%(owner)s">&#215</a></div>', d[i]))
}

// set remove
$(this.body).find('a.close').click(function() {
$c('webnotes.widgets.form.assign_to.remove', {
doctype: me.doctype,
name: me.name,
assign_to: $(this).attr('data-owner')
}, function(r,rt) {me.render(r.message);});
return false;
});
},
add: function() {
var me = this;
if(!me.dialog) {
me.dialog = new wn.ui.Dialog({
title: 'Add to To Do',
width: 350,
fields: [
{fieldtype:'Link', fieldname:'assign_to', options:'Profile',
label:'Assign To',
description:'Add to To Do List of', reqd:true},
{fieldtype:'Data', fieldname:'description', label:'Comment'},
{fieldtype:'Date', fieldname:'date', label:'Complete By'},
{fieldtype:'Select', fieldname:'priority', label:'Priority',
options:'Low\nMedium\nHigh', 'default':'Medium'},
{fieldtype:'Check', fieldname:'notify', label:'Notify By Email'},
{fieldtype:'Button', label:'Add', fieldname:'add_btn'}
]
});
me.dialog.fields_dict.add_btn.input.onclick = function() {
var assign_to = me.dialog.fields_dict.assign_to.get_value();
if(assign_to) {
$c('webnotes.widgets.form.assign_to.add', {
doctype: me.doctype,
name: me.name,
assign_to: assign_to,
description: me.dialog.fields_dict.description.get_value(),
priority: me.dialog.fields_dict.priority.get_value(),
date: me.dialog.fields_dict.date.get_value(),
notify: me.dialog.fields_dict.notify.get_value()
}, function(r,rt) {me.render(r.message);});
}
};
me.dialog.fields_dict.assign_to.get_query = function() {
return "select name, concat_ws(' ', first_name, middle_name, last_name) \
from `tabProfile` where ifnull(enabled, 0)=1 and docstatus < 2 and \
(%(key)s like \"%s\" or \
concat_ws(' ', first_name, middle_name, last_name) like \"%%%s\") \
limit 50";
};
}
me.dialog.clear();
me.dialog.show();
}
});


+ 9
- 2
public/js/legacy/wn/widgets/form/sidebar.js View File

@@ -115,7 +115,11 @@ wn.widgets.form.sidebar = { Sidebar: function(form) {
{
title: 'Assign',
render: function(wrapper) {
me.form.assign_to = new wn.widgets.form.sidebar.AssignTo(wrapper, me, me.form.doctype, me.form.docname);
me.form.assign_to = new wn.ui.form.AssignTo({
parent: $(wrapper),
frm: me.form
});
me.form.assign_to.refresh();
},
display: function() { return !me.form.doc.__islocal }
},
@@ -123,7 +127,10 @@ wn.widgets.form.sidebar = { Sidebar: function(form) {
{
title: 'Attachments',
render: function(wrapper) {
me.form.attachments = new wn.ui.form.Attachments({parent: $(wrapper), frm:me.form});
me.form.attachments = new wn.ui.form.Attachments({
parent: $(wrapper),
frm:me.form
});
me.form.attachments.refresh();
},
display: function() { return me.form.meta.allow_attach }


+ 1
- 1
public/js/wn/form/attachments.js View File

@@ -77,7 +77,7 @@ wn.ui.form.Attachments = Class.extend({
var me = this;
$(repl('<div class="alert alert-info"><span style="display: inline-block; width: 90%;\
text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">\
<i class="icon icon-file"></i> <a href="%(href)s"\
<i class="icon-file"></i> <a href="%(href)s"\
target="_blank" title="%(filename)s">%(filename)s</a></span><a href="#" class="close">&times;</a>\
</div>', {
filename: filename,


+ 9
- 2
public/js/wn/ui/dialog.js View File

@@ -63,9 +63,16 @@ wn.ui.Dialog = wn.ui.FieldGroup.extend({
this.appframe.$titlebar.find('.appframe-title').html(t || '');
},
set_postion: function() {
this.zindex = 1;
if(cur_dialog) {
this.zindex = cur_dialog.zindex + 1;
}
// place it at the center
this.wrapper.style.left = (($(window).width() - cint(this.wrapper.style.width))/2) + 'px';
this.wrapper.style.top = ($(window).scrollTop() + 60) + 'px';
$(this.wrapper).css({
left: (($(window).width() - cint(this.wrapper.style.width))/2) + 'px',
top: ($(window).scrollTop() + 60) + 'px',
"z-index": this.zindex
})
},
show: function() {
// already live, do nothing


+ 34
- 76
webnotes/db.py View File

@@ -72,53 +72,11 @@ class Database:
self._conn.select_db(db_name)
self.cur_db_name = db_name
def check_transaction_status(self, query):
"""
Update *in_transaction* and check if "START TRANSACTION" is not called twice
"""
if self.in_transaction and query and query.strip().split()[0].lower() in ['start', 'alter', 'drop', 'create']:
raise Exception, 'This statement can cause implicit commit'

if query and query.strip().lower()=='start transaction':
self.in_transaction = 1
self.transaction_writes = 0
if query and query.strip().split()[0].lower() in ['commit', 'rollback']:
self.in_transaction = 0

if self.in_transaction and query[:6].lower() in ['update', 'insert']:
self.transaction_writes += 1
if self.transaction_writes > 10000:
if self.auto_commit_on_many_writes:
webnotes.conn.commit()
webnotes.conn.begin()
else:
webnotes.msgprint('A very long query was encountered. If you are trying to import data, please do so using smaller files')
raise Exception, 'Bad Query!!! Too many writes'
def fetch_as_dict(self, formatted=0, as_utf8=0):
"""
Internal - get results as dictionary
"""
result = self._cursor.fetchall()
ret = []
for r in result:
row_dict = webnotes._dict({})
for i in range(len(r)):
val = self.convert_to_simple_type(r[i], formatted)
if as_utf8 and type(val) is unicode:
val = val.encode('utf-8')
row_dict[self._cursor.description[i][0]] = val
ret.append(row_dict)
return ret
def validate_query(self, q):
cmd = q.strip().lower().split()[0]
if cmd in ['alter', 'drop', 'truncate'] and webnotes.user.name != 'Administrator':
webnotes.msgprint('Not allowed to execute query')
raise Execption

# ======================================================================================
def sql(self, query, values=(), as_dict = 0, as_list = 0, formatted = 0,
debug=0, ignore_ddl=0, as_utf8=0, auto_commit=0, update=None):
@@ -168,15 +126,43 @@ class Database:
else:
return self._cursor.fetchall()

def check_transaction_status(self, query):
if self.in_transaction and query and query.strip().split()[0].lower() in ['start', 'alter', 'drop', 'create']:
raise Exception, 'This statement can cause implicit commit'

if query and query.strip().lower()=='start transaction':
self.in_transaction = 1
self.transaction_writes = 0
if query and query.strip().split()[0].lower() in ['commit', 'rollback']:
self.in_transaction = 0

if self.in_transaction and query[:6].lower() in ['update', 'insert']:
self.transaction_writes += 1
if self.transaction_writes > 10000:
if self.auto_commit_on_many_writes:
webnotes.conn.commit()
webnotes.conn.begin()
else:
webnotes.msgprint('A very long query was encountered. If you are trying to import data, please do so using smaller files')
raise Exception, 'Bad Query!!! Too many writes'

def fetch_as_dict(self, formatted=0, as_utf8=0):
result = self._cursor.fetchall()
ret = []
for r in result:
row_dict = webnotes._dict({})
for i in range(len(r)):
val = self.convert_to_simple_type(r[i], formatted)
if as_utf8 and type(val) is unicode:
val = val.encode('utf-8')
row_dict[self._cursor.description[i][0]] = val
ret.append(row_dict)
return ret
def get_description(self):
"""
Get metadata of the last query
"""
return self._cursor.description

# ======================================================================================

def convert_to_simple_type(self, v, formatted=0):
import datetime
from webnotes.utils import formatdate, fmt_money
@@ -211,12 +197,7 @@ class Database:
return v

# ======================================================================================

def convert_to_lists(self, res, formatted=0, as_utf8=0):
"""
Convert the given result set to a list of lists (with cleaned up dates and decimals)
"""
nres = []
for r in res:
nr = []
@@ -227,13 +208,8 @@ class Database:
nr.append(val)
nres.append(nr)
return nres
# ======================================================================================

def convert_to_utf8(self, res, formatted=0):
"""
Convert the given result set to a list of lists and as utf8 (with cleaned up dates and decimals)
"""
nres = []
for r in res:
nr = []
@@ -311,8 +287,6 @@ class Database:
self.set_value(doc.doctype, doc.name, field, val, doc.modified, doc.modified_by)
doc.fields[field] = val

# ======================================================================================

def set_global(self, key, val, user='__global'):
res = self.sql('select defkey from `tabDefaultValue` where defkey=%s and parent=%s', (key, user))
if res:
@@ -324,8 +298,6 @@ class Database:
g = self.sql("select defvalue from tabDefaultValue where defkey=%s and parent=%s", (key, user))
return g and g[0][0] or None

# ======================================================================================

def set_default(self, key, val, parent="Control Panel"):
"""set control panel default (tabDefaultVal)"""

@@ -338,7 +310,6 @@ class Database:
else:
self.add_default(key, val, parent)
def add_default(self, key, val, parent="Control Panel"):
d = webnotes.doc('DefaultValue')
d.parent = parent
@@ -374,22 +345,13 @@ class Database:
def commit(self):
self.sql("commit")


def rollback(self):
self.sql("ROLLBACK")

# ======================================================================================

def field_exists(self, dt, fn):
"""
Returns True if `fn` exists in `DocType` `dt`
"""
return self.sql("select name from tabDocField where fieldname=%s and parent=%s", (dt, fn))

def exists(self, dt, dn=None):
"""
Returns true if the record exists
"""
if isinstance(dt, basestring):
try:
return self.sql('select name from `tab%s` where name=%s' % (dt, '%s'), dn)
@@ -409,11 +371,7 @@ class Database:
def get_table_columns(self, doctype):
return [r[0] for r in self.sql("DESC `tab%s`" % doctype)]

# ======================================================================================
def close(self):
"""
Close my connection
"""
if self._conn:
self._cursor.close()
self._conn.close()


+ 7
- 3
webnotes/widgets/form/assign_to.py View File

@@ -76,7 +76,7 @@ def add(args=None):
return get(args)

@webnotes.whitelist()
def remove(args=None):
def remove(doctype, name, assign_to):
"""remove from todo"""
if not args:
args = webnotes.form_dict
@@ -84,11 +84,15 @@ def remove(args=None):
res = webnotes.conn.sql("""\
select assigned_by, owner, reference_type, reference_name from `tabToDo`
where reference_type=%(doctype)s and reference_name=%(name)s
and owner=%(assign_to)s""", args)
and owner=%(assign_to)s""", locals())

webnotes.conn.sql("""delete from `tabToDo`
where reference_type=%(doctype)s and reference_name=%(name)s
and owner=%(assign_to)s""", args)
and owner=%(assign_to)s""", locals())
# clear assigned_to if field exists
if "assigned_to" in webnotes.conn.get_columns(doctype):
webnotes.conn.set_value(doctype, name, "assigned_to", None)

if res and res[0]: notify_assignment(res[0][0], res[0][1], res[0][2], res[0][3])



Loading…
Cancel
Save