From d70968dbc5e007bc377674313111ba3a513bbc7f Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Fri, 17 Jul 2015 19:41:32 +0530 Subject: [PATCH] [feature] Share with Everyone --- .../change_log/current/share_with_everyone.md | 1 + frappe/core/doctype/docshare/docshare.json | 11 +++- frappe/core/doctype/docshare/docshare.py | 7 +++ frappe/core/doctype/docshare/test_docshare.py | 12 +++++ frappe/desk/form/load.py | 2 +- frappe/permissions.py | 7 +-- frappe/public/css/avatar.css | 1 + frappe/public/css/sidebar.css | 10 +++- frappe/public/js/frappe/form/set_sharing.html | 52 +++++++++++-------- frappe/public/js/frappe/form/share.js | 49 ++++++++++++++--- frappe/public/less/avatar.less | 1 + frappe/public/less/sidebar.less | 12 ++++- frappe/share.py | 33 ++++++++---- 13 files changed, 150 insertions(+), 48 deletions(-) create mode 100644 frappe/change_log/current/share_with_everyone.md diff --git a/frappe/change_log/current/share_with_everyone.md b/frappe/change_log/current/share_with_everyone.md new file mode 100644 index 0000000000..befa5be85c --- /dev/null +++ b/frappe/change_log/current/share_with_everyone.md @@ -0,0 +1 @@ +- Ability to **Share with Everyone** (except Guest) using **Share With** diff --git a/frappe/core/doctype/docshare/docshare.json b/frappe/core/doctype/docshare/docshare.json index 68ebe45c7b..ed649f2930 100644 --- a/frappe/core/doctype/docshare/docshare.json +++ b/frappe/core/doctype/docshare/docshare.json @@ -26,7 +26,7 @@ "print_hide": 0, "read_only": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 1, "set_only_once": 0 }, @@ -129,6 +129,13 @@ "reqd": 0, "search_index": 0, "set_only_once": 0 + }, + { + "fieldname": "everyone", + "fieldtype": "Check", + "label": "Everyone", + "permlevel": 0, + "precision": "" } ], "hide_heading": 0, @@ -138,7 +145,7 @@ "is_submittable": 0, "issingle": 0, "istable": 0, - "modified": "2015-02-12 11:30:52.968078", + "modified": "2015-07-17 07:02:10.632582", "modified_by": "Administrator", "module": "Core", "name": "DocShare", diff --git a/frappe/core/doctype/docshare/docshare.py b/frappe/core/doctype/docshare/docshare.py index cf79e0531c..5f2b3ae1bd 100644 --- a/frappe/core/doctype/docshare/docshare.py +++ b/frappe/core/doctype/docshare/docshare.py @@ -11,6 +11,7 @@ class DocShare(Document): no_feed_on_delete = True def validate(self): + self.validate_user() self.check_share_permission() self.cascade_permissions_downwards() self.get_doc().run_method("validate_share", self) @@ -26,6 +27,12 @@ class DocShare(Document): self._doc = frappe.get_doc(self.share_doctype, self.share_name) return self._doc + def validate_user(self): + if self.everyone: + self.user = None + elif not self.user: + frappe.throw(_("User is mandatory for Share"), frappe.MandatoryError) + def check_share_permission(self): if (not self.flags.ignore_share_permission and not frappe.has_permission(self.share_doctype, "share", self.get_doc())): diff --git a/frappe/core/doctype/docshare/test_docshare.py b/frappe/core/doctype/docshare/test_docshare.py index 4fb522e267..e4a41ac0d2 100644 --- a/frappe/core/doctype/docshare/test_docshare.py +++ b/frappe/core/doctype/docshare/test_docshare.py @@ -78,3 +78,15 @@ class TestDocShare(unittest.TestCase): frappe.set_user(self.user) self.assertFalse(self.event.has_permission("share")) + def test_share_with_everyone(self): + self.assertTrue(self.event.name not in frappe.share.get_shared("Event", self.user)) + + frappe.share.set_permission("Event", self.event.name, None, "read", everyone=1) + self.assertTrue(self.event.name in frappe.share.get_shared("Event", self.user)) + self.assertTrue(self.event.name in frappe.share.get_shared("Event", "test1@example.com")) + self.assertTrue(self.event.name not in frappe.share.get_shared("Event", "Guest")) + + frappe.share.set_permission("Event", self.event.name, None, "read", value=0, everyone=1) + self.assertTrue(self.event.name not in frappe.share.get_shared("Event", self.user)) + self.assertTrue(self.event.name not in frappe.share.get_shared("Event", "test1@example.com")) + self.assertTrue(self.event.name not in frappe.share.get_shared("Event", "Guest")) diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 2d99fb130e..1cab39bed2 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -89,7 +89,7 @@ def get_docinfo(doc=None, doctype=None, name=None): "assignments": get_assignments(doc.doctype, doc.name), "permissions": get_doc_permissions(doc), "shared": frappe.share.get_users(doc.doctype, doc.name, - fields=["user", "read", "write", "share"]) + fields=["user", "read", "write", "share", "everyone"]) } def get_user_permissions(meta): diff --git a/frappe/permissions.py b/frappe/permissions.py index 690ea63ff8..c3183d2cd0 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -40,15 +40,16 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): def false_if_not_shared(): if ptype in ("read", "write", "share", "email", "print"): - shared = frappe.share.get_shared(doctype, user, - ["read" if ptype in ("email", "print") else ptype]) if doc: doc_name = doc if isinstance(doc, basestring) else doc.name + shared = frappe.share.get_shared(doctype, user, + ["read" if ptype in ("email", "print") else ptype]) + if doc_name in shared: if verbose: print "Shared" if ptype in ("read", "write", "share") or meta.permissions[0].get(ptype): return True - + else: if verbose: print "Has a shared document" return True diff --git a/frappe/public/css/avatar.css b/frappe/public/css/avatar.css index 636cc1baf3..7bc44cacd9 100644 --- a/frappe/public/css/avatar.css +++ b/frappe/public/css/avatar.css @@ -12,6 +12,7 @@ } .avatar-empty { border: 1px dashed #d1d8dd; + border-radius: 4px; } .avatar-small { margin-right: 5px; diff --git a/frappe/public/css/sidebar.css b/frappe/public/css/sidebar.css index 9dfe252d58..c9a03858a6 100644 --- a/frappe/public/css/sidebar.css +++ b/frappe/public/css/sidebar.css @@ -140,7 +140,7 @@ body[data-route^="Module"] .main-menu .form-sidebar { .form-sidebar .form-shared .share-doc-btn { cursor: pointer; } -.form-sidebar .form-shared .octicon-plus { +.form-sidebar .form-shared .octicon { position: relative; top: 2px; left: 7px; @@ -148,6 +148,14 @@ body[data-route^="Module"] .main-menu .form-sidebar { .form-sidebar .form-shared .avatar { margin-top: 5px; } +.form-sidebar .form-shared .shared-with-everyone { + border-style: solid; + border-color: #f0f4f7; + background-color: #f0f4f7; +} +.form-sidebar .form-shared .shared-with-everyone .octicon { + color: #36414c !important; +} .form-sidebar .form-shared .share-doc-btn:hover, .form-sidebar .form-shared .share-doc-btn:focus, .form-sidebar .form-shared .share-doc-btn:active { diff --git a/frappe/public/js/frappe/form/set_sharing.html b/frappe/public/js/frappe/form/set_sharing.html index 6088b7a15b..969e0c1461 100644 --- a/frappe/public/js/frappe/form/set_sharing.html +++ b/frappe/public/js/frappe/form/set_sharing.html @@ -1,29 +1,35 @@
- {% if(!shared.length) { %} -

{%= __("Not shared with anyone yet.") %}

- {% } else { %} -
-
{%= __("User") %}
-
{%= __("Can Read") %}
-
{%= __("Can Write") %}
-
{%= __("Can Share") %}
-
+
+
{%= __("User") %}
+
{%= __("Can Read") %}
+
{%= __("Can Write") %}
+
{%= __("Can Share") %}
+
- {% for (var i=0, l=shared.length; i < l; i++) { - var s = shared[i]; %} - {% if(s) { %} -
-
{%= s.user %}
-
-
-
-
- {% } %} - {% } %} +
+ +
+
+
+
+ + {% for (var i=0, l=shared.length; i < l; i++) { + var s = shared[i]; %} + {% if(s && !s.everyone) { %} +
+
{%= s.user %}
+
+
+
+
+ {% } %} {% } %} {% if(frappe.model.can_share(null, frm)) { %} diff --git a/frappe/public/js/frappe/form/share.js b/frappe/public/js/frappe/form/share.js index b15c8086c2..202a884ae5 100644 --- a/frappe/public/js/frappe/form/share.js +++ b/frappe/public/js/frappe/form/share.js @@ -10,26 +10,42 @@ frappe.ui.form.Share = Class.extend({ refresh: function() { var me = this; this.parent.empty(); + + var everyone = null; var shared = $.map(this.shared || this.frm.get_docinfo().shared, function(s) { + if (s.everyone) { + everyone = s; + } + return s ? s.user : null; }); + if (everyone) { + $(repl('', {title: __("Shared with everyone")})) + .appendTo(this.parent) + .on("click", function() { me.frm.share_doc(); }); + } + for(var i=0; i\ %(fullname)s', {image: user_info.image, fullname: user_info.fullname})) .appendTo(this.parent) - .on("click", function() { me.frm.share_doc(); });; + .on("click", function() { me.frm.share_doc(); }); } + // share if(!me.frm.doc.__islocal) { $(repl('', {title: __("Share")})) .appendTo(this.parent) .on("click", function() { me.frm.share_doc(); }); + } + }, show: function() { var me = this; @@ -65,7 +81,16 @@ frappe.ui.form.Share = Class.extend({ this.shared = shared; var d = this.dialog; $(d.body).empty(); - $(frappe.render_template("set_sharing", {frm: this.frm, shared: this.shared})) + + var everyone = {}; + $.each(this.shared, function(i, s) { + // pullout everyone record from shared list + if (s && s.everyone) { + everyone = s; + } + }); + + $(frappe.render_template("set_sharing", {frm: this.frm, shared: this.shared, everyone: everyone})) .appendTo(d.body); if(frappe.model.can_share(null, this.frm)) { @@ -113,6 +138,7 @@ frappe.ui.form.Share = Class.extend({ write: $(d.body).find(".add-share-write").prop("checked") ? 1 : 0, share: $(d.body).find(".add-share-share").prop("checked") ? 1 : 0 }, + btn: this, callback: function(r) { $.each(me.shared, function(i, s) { if(s && s.user===r.message.user) { @@ -131,9 +157,10 @@ frappe.ui.form.Share = Class.extend({ set_edit_share_events: function() { var me = this, d = this.dialog; $(d.body).find(".edit-share").on("click", function() { - var user = $(this).parents(".shared-user:first").attr("data-user"), + var user = $(this).parents(".shared-user:first").attr("data-user") || "", value = $(this).prop("checked") ? 1 : 0, - property = $(this).attr("name"); + property = $(this).attr("name") + everyone = cint($(this).parents(".shared-user:first").attr("data-everyone")); frappe.call({ method: "frappe.share.set_permission", @@ -142,20 +169,28 @@ frappe.ui.form.Share = Class.extend({ name: me.frm.doc.name, user: user, permission_to: property, - value: value + value: value, + everyone: everyone }, callback: function(r) { + var found = null; $.each(me.shared, function(i, s) { // update shared object - if(s && s.user===user) { + if(s && (s.user===user || (everyone && s.everyone===1))) { if(!r.message) { delete me.shared[i]; } else { me.shared[i] = $.extend(s, r.message); } + found = true; return false; } }); + + if (!found) { + me.shared.push(r.message); + } + me.dirty = true; me.render_shared(); me.frm.shared.refresh(); diff --git a/frappe/public/less/avatar.less b/frappe/public/less/avatar.less index 2fbd3e0ec1..896ab6bc73 100644 --- a/frappe/public/less/avatar.less +++ b/frappe/public/less/avatar.less @@ -14,6 +14,7 @@ .avatar-empty { border: 1px dashed #d1d8dd; + border-radius: 4px; } .avatar-small { diff --git a/frappe/public/less/sidebar.less b/frappe/public/less/sidebar.less index 98bb33a0c6..fde38c0b14 100644 --- a/frappe/public/less/sidebar.less +++ b/frappe/public/less/sidebar.less @@ -120,7 +120,7 @@ body[data-route^="Module"] .main-menu { cursor: pointer; } - .octicon-plus { + .octicon { position: relative; top: 2px; left: 7px; @@ -129,6 +129,16 @@ body[data-route^="Module"] .main-menu { .avatar { margin-top: 5px; } + + .shared-with-everyone { + border-style: solid; + border-color: @btn-bg; + background-color: @btn-bg; + + .octicon { + color: @text-color !important; + } + } } } diff --git a/frappe/share.py b/frappe/share.py index f6f92535be..a7789468d8 100644 --- a/frappe/share.py +++ b/frappe/share.py @@ -6,13 +6,12 @@ import frappe from frappe.utils import cint @frappe.whitelist() -def add(doctype, name, user=None, read=1, write=0, share=0, flags=None): +def add(doctype, name, user=None, read=1, write=0, share=0, everyone=0, flags=None): """Share the given document with a user.""" if not user: user = frappe.session.user - share_name = frappe.db.get_value("DocShare", {"user": user, "share_name": name, - "share_doctype": doctype}) + share_name = get_share_name(doctype, name, user, everyone) if share_name: doc = frappe.get_doc("DocShare", share_name) @@ -21,7 +20,8 @@ def add(doctype, name, user=None, read=1, write=0, share=0, flags=None): doc.update({ "user": user, "share_doctype": doctype, - "share_name": name + "share_name": name, + "everyone": cint(everyone) }) if flags: @@ -46,14 +46,14 @@ def remove(doctype, name, user, flags=None): frappe.delete_doc("DocShare", share_name) @frappe.whitelist() -def set_permission(doctype, name, user, permission_to, value=1): +def set_permission(doctype, name, user, permission_to, value=1, everyone=0): """Set share permission.""" - share_name = frappe.db.get_value("DocShare", {"user": user, "share_name": name, - "share_doctype": doctype}) + share_name = get_share_name(doctype, name, user, everyone) value = int(value) + if not share_name: if value: - share = add(doctype, name, user, **{permission_to: 1}) + share = add(doctype, name, user, everyone=everyone, **{permission_to: 1}) else: # no share found, nothing to remove share = {} @@ -102,7 +102,9 @@ def get_shared(doctype, user=None, rights=None): condition = " and ".join(["`{0}`=1".format(right) for right in rights]) - return frappe.db.sql_list("select share_name from tabDocShare where user=%s and share_doctype=%s and {0}".format(condition), + return frappe.db.sql_list("""select share_name from tabDocShare + where (user=%s {everyone}) and share_doctype=%s and {condition}""".format( + condition=condition, everyone="or everyone=1" if user!="Guest" else ""), (user, doctype)) def get_shared_doctypes(user=None): @@ -110,4 +112,15 @@ def get_shared_doctypes(user=None): if not user: user = frappe.session.user - return frappe.db.sql_list("select distinct share_doctype from tabDocShare where user=%s", user) + return frappe.db.sql_list("select distinct share_doctype from tabDocShare where (user=%s or everyone=1)", user) + +def get_share_name(doctype, name, user, everyone): + if cint(everyone): + share_name = frappe.db.get_value("DocShare", {"everyone": 1, "share_name": name, + "share_doctype": doctype}) + else: + share_name = frappe.db.get_value("DocShare", {"user": user, "share_name": name, + "share_doctype": doctype}) + + return share_name +