From 872620b8af916e82b80c64c95ab6dd616ae267f6 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 2 Jan 2013 15:29:35 +0530 Subject: [PATCH] fixes to permission manager / user property setter --- core/doctype/doctype/doctype.py | 32 ++-- core/doctype/profile/profile.js | 8 +- core/doctype/profile/profile.txt | 15 +- core/doctype/userrole/userrole.txt | 22 +-- .../permission_manager/permission_manager.js | 170 ++++++++++-------- .../permission_manager/permission_manager.py | 2 +- core/page/user_properties/user_properties.js | 27 ++- core/page/user_properties/user_properties.py | 19 +- public/build.json | 2 +- public/css/bootstrap.css | 154 ++++++++-------- public/js/legacy/wn/widgets/form/assign_to.js | 117 ------------ public/js/legacy/wn/widgets/form/sidebar.js | 11 +- public/js/wn/form/attachments.js | 2 +- public/js/wn/ui/dialog.js | 11 +- webnotes/db.py | 110 ++++-------- webnotes/widgets/form/assign_to.py | 10 +- 16 files changed, 304 insertions(+), 408 deletions(-) delete mode 100644 public/js/legacy/wn/widgets/form/assign_to.js diff --git a/core/doctype/doctype/doctype.py b/core/doctype/doctype/doctype.py index 5bbe6b093e..1825d2187b 100644 --- a/core/doctype/doctype/doctype.py +++ b/core/doctype/doctype/doctype.py @@ -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) \ No newline at end of file diff --git a/core/doctype/profile/profile.js b/core/doctype/profile/profile.js index 37a1a0be73..671a3407bb 100644 --- a/core/doctype/profile/profile.js +++ b/core/doctype/profile/profile.js @@ -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); } diff --git a/core/doctype/profile/profile.txt b/core/doctype/profile/profile.txt index c2333520f9..a9125cbfcc 100644 --- a/core/doctype/profile/profile.txt +++ b/core/doctype/profile/profile.txt @@ -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 } ] \ No newline at end of file diff --git a/core/doctype/userrole/userrole.txt b/core/doctype/userrole/userrole.txt index d6dc807957..4940fdd302 100644 --- a/core/doctype/userrole/userrole.txt +++ b/core/doctype/userrole/userrole.txt @@ -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" }, { diff --git a/core/page/permission_manager/permission_manager.js b/core/page/permission_manager/permission_manager.js index 834112abac..274a314032 100644 --- a/core/page/permission_manager/permission_manager.js +++ b/core/page/permission_manager/permission_manager.js @@ -4,6 +4,69 @@ wn.pages['permission-manager'].onload = function(wrapper) { title: 'Permission Manager', single_column: true }); + $(wrapper).find(".layout-main").html("
\ + \ + \ + \ + \ + \ +
\ +

Quick Help for Setting Permissions:

\ +
    \ +
  1. Permissions are set on Roles and Document Types (called DocTypes) by restricting \ + read, write, create, submit, cancel and amend rights.
  2. \ +
  3. Permissions translate to Users based on what Role they are assigned.
  4. \ +
  5. To set user roles, just go to Setup > Users \ + and click on the user to assign roles.
  6. \ +
  7. The system provides pre-defined roles, but you can add new roles\ + to set finer permissions.
  8. \ +
  9. Permissions are automatically translated to Standard Reports and Searches.
  10. \ +
  11. As a best practice, do not assign the same set of permission rule to different Roles\ + instead set multiple Roles to the User.
  12. \ +
\ +
\ +

Meaning of Submit, Cancel, Amend:

\ +
    \ +
  1. Certain documents should not be changed once final, like an\ + Invoice for example. The final state for such documents is called Submitted.\ + You can restrict which roles can Submit.
  2. \ +
  3. Cancel 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).
  4. \ +
  5. When you Amend 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.
  6. \ +
\ +
\ +

Restricting By User:

\ +
    \ +
  1. 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'.
  2. \ +
  3. 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.\ +
\ +
\ +

Advanced Settings:

\ +

To further restrict permissions based on certain values in a document, use the\ + 'Condition' settings.

For example: You want to restrict users to transactions marked\ + with a certain property called 'Territory':

\ +
    \ +
  1. Make sure that the transactions you want to restrict have a Link \ + field 'territory' that maps to a 'Territory' master. If not, create a\ + Custom Field of type Link.
  2. \ +
  3. In the Permission Manager, click on the button in the 'Condition' column\ + for the Role you want to restrict.
  4. \ +
  5. 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.
  6. \ +
  7. Go to Setup > User Properties to set \ + 'territory' for diffent Users.
  8. \ +
\ +

Once you have set this, the users will only be able access documents with that property

\ +
\ +

If these instructions where not helpful, please add in your suggestions at\ + GitHub Issues

\ +
"); 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 = $("").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(""+role+"") + .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 ''+p+''; + }) + msgprint("

Users with role "+role+":

" + + r.message.join("
")); + } + }) + 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 = $("
").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; - $('

For ' + doctype + '

Document Permissions

') - .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 = $('

').html("A user with role "+p.role + " can " - + perms + " a document of type " + doctype + ".") - - } else { - if(p.match=="owner") { - var _p = $('

').html("A user with role "+p.role + " can " - + perms + " " + doctype + " only if that document is created by that user."); - } else if(p.match.indexOf(":")!=-1) { - var field = p.match.split(":")[0]; - var _p = $('

').html("A user with role "+p.role + " can " - + perms + " " + doctype + " only if "+field+" equals User's Id."); - } else { - var _p = $('

').html("A user with role "+p.role + " can " - + perms + " " + doctype + " only for records with user's "+p.match+" property."); - } - } - - } else { - if(!core_finished) { - core_finished = true; - $("

Field Level Permissions

").appendTo(wrapper); - } - var perms = $.map(["read", "write"], function(type) { - if(p[type]) return type; - }).join(", "); - var _p = $('

').html("A user with role "+p.role + " can only " - + perms + " fields at level "+ p.permlevel +" in a " + doctype + ".") - } - - $("Show Users").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("" - + wn.user_info(uid).fullname + " ("+ - uid+")"); - }); - cur_dialog.set_title("Users with role " - + link.attr("data-role")); - } - }); - }); - _p.appendTo(wrapper); - }) } }) \ No newline at end of file diff --git a/core/page/permission_manager/permission_manager.py b/core/page/permission_manager/permission_manager.py index f44bdb5092..74c8f32055 100644 --- a/core/page/permission_manager/permission_manager.py +++ b/core/page/permission_manager/permission_manager.py @@ -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')""")] } diff --git a/core/page/user_properties/user_properties.js b/core/page/user_properties/user_properties.js index 84f54added..8cb5d032e2 100644 --- a/core/page/user_properties/user_properties.js +++ b/core/page/user_properties/user_properties.js @@ -4,6 +4,21 @@ wn.pages['user-properties'].onload = function(wrapper) { title: 'User Properties', single_column: true }); + $(wrapper).find(".layout-main").html("

\ + \ + \ +
\ +

Quick Help for User Properties:

\ +
    \ +
  1. You can set various 'properties' to Users.
  2. \ +
  3. These properties are Link Type fields from all Documents.
  4. \ +
  5. These properties will appear as values in forms that contain them.
  6. \ +
  7. 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 Permission Manager
  8. \ +
  9. A user can have multiple values for a property.
  10. \ +
\ +
"); 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(); - $("
User Properties appear as default values in forms.\ -
They are also used to restrict permissions \ - in the Permission Manager\ -
You can also set multiple values for one property. \ - If so, the permission rules will apply if any of the values match.
").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 = $("").appendTo(me.table.find("tbody")); - $("").html(d.parent).appendTo(row); + $("").html('' + +d.parent+'').appendTo(row); $("").html(d.defkey).appendTo(row); $("").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(); diff --git a/core/page/user_properties/user_properties.py b/core/page/user_properties/user_properties.py index aa56d19dac..038b663e35 100644 --- a/core/page/user_properties/user_properties.py +++ b/core/page/user_properties/user_properties.py @@ -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): diff --git a/public/build.json b/public/build.json index 04888f72cc..fa914df27d 100644 --- a/public/build.json +++ b/public/build.json @@ -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', diff --git a/public/css/bootstrap.css b/public/css/bootstrap.css index 4ab0dedd85..91ec27bb67 100644 --- a/public/css/bootstrap.css +++ b/public/css/bootstrap.css @@ -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 { diff --git a/public/js/legacy/wn/widgets/form/assign_to.js b/public/js/legacy/wn/widgets/form/assign_to.js deleted file mode 100644 index 093f69c793..0000000000 --- a/public/js/legacy/wn/widgets/form/assign_to.js +++ /dev/null @@ -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 = $("") - .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%(owner)s \ - ×', 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(); - } -}); - diff --git a/public/js/legacy/wn/widgets/form/sidebar.js b/public/js/legacy/wn/widgets/form/sidebar.js index 4bdbde9fa9..b4f7c1f582 100644 --- a/public/js/legacy/wn/widgets/form/sidebar.js +++ b/public/js/legacy/wn/widgets/form/sidebar.js @@ -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 } diff --git a/public/js/wn/form/attachments.js b/public/js/wn/form/attachments.js index 0ba31c9d26..e268c4be6c 100644 --- a/public/js/wn/form/attachments.js +++ b/public/js/wn/form/attachments.js @@ -77,7 +77,7 @@ wn.ui.form.Attachments = Class.extend({ var me = this; $(repl('', { filename: filename, diff --git a/public/js/wn/ui/dialog.js b/public/js/wn/ui/dialog.js index c055d7dd68..7b2218a84b 100644 --- a/public/js/wn/ui/dialog.js +++ b/public/js/wn/ui/dialog.js @@ -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 diff --git a/webnotes/db.py b/webnotes/db.py index 1ef32dfaab..114ffe8cdb 100644 --- a/webnotes/db.py +++ b/webnotes/db.py @@ -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() diff --git a/webnotes/widgets/form/assign_to.py b/webnotes/widgets/form/assign_to.py index b7c196ac84..d077d6c24d 100644 --- a/webnotes/widgets/form/assign_to.py +++ b/webnotes/widgets/form/assign_to.py @@ -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])