From 8cde7bfc234495beca920ee94bd1a3eb0f5e015b Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 4 Feb 2015 17:01:31 +0530 Subject: [PATCH] [print] tables & [feature] share #992 --- frappe/core/doctype/docperm/docperm.json | 9 +- frappe/core/doctype/docshare/__init__.py | 0 frappe/core/doctype/docshare/docshare.json | 171 ++++++++++++++++++ frappe/core/doctype/docshare/docshare.py | 33 ++++ frappe/core/doctype/docshare/test_docshare.py | 69 +++++++ .../core/doctype/docshare/test_records.json | 1 + frappe/core/doctype/file_data/file_data.py | 2 +- frappe/data/Framework.sql | 1 + frappe/database.py | 18 ++ frappe/model/base_document.py | 73 ++++++-- frappe/model/db_query.py | 37 +++- frappe/model/delete_doc.py | 7 +- frappe/model/document.py | 36 +--- frappe/model/meta.py | 4 - frappe/permissions.py | 21 ++- frappe/public/css/desk.css | 4 +- frappe/public/js/frappe/form/print.js | 1 + frappe/public/js/legacy/clientscriptAPI.js | 4 +- frappe/public/less/desk.less | 4 +- frappe/share.py | 42 +++++ frappe/templates/includes/login.css | 13 +- frappe/templates/pages/print.py | 37 +++- .../print_formats/standard_macros.html | 7 +- 23 files changed, 497 insertions(+), 97 deletions(-) create mode 100644 frappe/core/doctype/docshare/__init__.py create mode 100644 frappe/core/doctype/docshare/docshare.json create mode 100644 frappe/core/doctype/docshare/docshare.py create mode 100644 frappe/core/doctype/docshare/test_docshare.py create mode 100644 frappe/core/doctype/docshare/test_records.json create mode 100644 frappe/share.py diff --git a/frappe/core/doctype/docperm/docperm.json b/frappe/core/doctype/docperm/docperm.json index 4a57765d3b..70a1ca8cb2 100644 --- a/frappe/core/doctype/docperm/docperm.json +++ b/frappe/core/doctype/docperm/docperm.json @@ -198,6 +198,13 @@ "fieldtype": "Column Break", "permlevel": 0 }, + { + "fieldname": "share", + "fieldtype": "Check", + "label": "Share", + "permlevel": 0, + "precision": "" + }, { "fieldname": "print", "fieldtype": "Check", @@ -216,7 +223,7 @@ "idx": 1, "issingle": 0, "istable": 1, - "modified": "2014-08-26 01:43:31.499363", + "modified": "2015-02-04 04:34:26.227213", "modified_by": "Administrator", "module": "Core", "name": "DocPerm", diff --git a/frappe/core/doctype/docshare/__init__.py b/frappe/core/doctype/docshare/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/docshare/docshare.json b/frappe/core/doctype/docshare/docshare.json new file mode 100644 index 0000000000..383788c61d --- /dev/null +++ b/frappe/core/doctype/docshare/docshare.json @@ -0,0 +1,171 @@ +{ + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "hash", + "creation": "2015-02-04 04:33:36.330477", + "custom": 0, + "description": "Internal record of document shares", + "docstatus": 0, + "doctype": "DocType", + "document_type": "System", + "fields": [ + { + "allow_on_submit": 0, + "fieldname": "user", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "User", + "no_copy": 0, + "options": "User", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "fieldname": "share_doctype", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Document Type", + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "fieldname": "share_name", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 1, + "label": "Document Name", + "no_copy": 0, + "options": "share_doctype", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "default": "0", + "fieldname": "read", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Read", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "default": "0", + "fieldname": "write", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Write", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + }, + { + "allow_on_submit": 0, + "default": "0", + "fieldname": "share", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Share", + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "modified": "2015-02-04 04:44:55.131126", + "modified_by": "Administrator", + "module": "Core", + "name": "DocShare", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 0, + "export": 1, + "import": 1, + "permlevel": 0, + "print": 0, + "read": 1, + "report": 1, + "role": "Administrator", + "set_user_permissions": 0, + "submit": 0, + "write": 1 + } + ], + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/frappe/core/doctype/docshare/docshare.py b/frappe/core/doctype/docshare/docshare.py new file mode 100644 index 0000000000..025a6762ab --- /dev/null +++ b/frappe/core/doctype/docshare/docshare.py @@ -0,0 +1,33 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe import _ +from frappe.utils import get_fullname + +class DocShare(Document): + no_feed_on_delete = True + + def validate(self): + if not frappe.has_permission(self.share_doctype, "share", + frappe.get_doc(self.share_doctype, self.share_name)): + frappe.throw(_("Not Allowed"), frappe.PermissionError) + self.cascade_permissions_downwards() + + def cascade_permissions_downwards(self): + if self.share: + self.write = 1 + if self.write: + self.read = 1 + + def after_insert(self): + self.add_comment(_("{0} shared this document with {0}").format(get_fullname(self.owner), get_fullname(self.user))) + + def on_trash(self): + self.add_comment(_("{0} un-shared this document with {0}").format(get_fullname(self.owner), get_fullname(self.user))) + +def on_doctype_update(): + """Add index in `tabDocShare` for `(user, share_doctype)`""" + frappe.db.add_index("DocShare", ["user", "share_doctype"]) diff --git a/frappe/core/doctype/docshare/test_docshare.py b/frappe/core/doctype/docshare/test_docshare.py new file mode 100644 index 0000000000..8a8199b76f --- /dev/null +++ b/frappe/core/doctype/docshare/test_docshare.py @@ -0,0 +1,69 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +import frappe.share +import unittest + +class TestDocShare(unittest.TestCase): + def setUp(self): + self.user = "test@example.com" + self.event = frappe.get_doc({"doctype": "Event", + "subject": "test share event", + "starts_on": "2015-01-01 10:00:00", + "event_type": "Private"}).insert() + + def tearDown(self): + frappe.set_user("Administrator") + self.event.delete() + + def test_add(self): + # user not shared + self.assertTrue(self.event.name not in frappe.db.get_shared("Event", self.user)) + frappe.share.add("Event", self.event.name, self.user) + self.assertTrue(self.event.name in frappe.db.get_shared("Event", self.user)) + + def test_doc_permission(self): + frappe.set_user(self.user) + self.assertFalse(self.event.has_permission()) + + frappe.set_user("Administrator") + frappe.share.add("Event", self.event.name, self.user) + + frappe.set_user(self.user) + self.assertTrue(self.event.has_permission()) + + def test_share_permission(self): + frappe.share.add("Event", self.event.name, self.user, share=1) + + frappe.set_user(self.user) + self.assertTrue(self.event.has_permission("share")) + + # test cascade + self.assertTrue(self.event.has_permission("read")) + self.assertTrue(self.event.has_permission("write")) + + def test_set_permission(self): + frappe.share.add("Event", self.event.name, self.user) + + frappe.set_user(self.user) + self.assertFalse(self.event.has_permission("share")) + + frappe.set_user("Administrator") + frappe.share.set_permission("Event", self.event.name, self.user, "share") + + frappe.set_user(self.user) + self.assertTrue(self.event.has_permission("share")) + + def test_permission_to_share(self): + frappe.set_user(self.user) + self.assertRaises(frappe.PermissionError, frappe.share.add, "Event", self.event.name, self.user) + + frappe.set_user("Administrator") + frappe.share.add("Event", self.event.name, self.user, share=1) + + # test not raises + frappe.set_user(self.user) + frappe.share.add("Event", self.event.name, "test1@example.com", share=1) + + diff --git a/frappe/core/doctype/docshare/test_records.json b/frappe/core/doctype/docshare/test_records.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/frappe/core/doctype/docshare/test_records.json @@ -0,0 +1 @@ +[] diff --git a/frappe/core/doctype/file_data/file_data.py b/frappe/core/doctype/file_data/file_data.py index 62c0ef47e8..7cc41c16b7 100644 --- a/frappe/core/doctype/file_data/file_data.py +++ b/frappe/core/doctype/file_data/file_data.py @@ -14,7 +14,7 @@ from frappe.model.document import Document from frappe.utils.file_manager import delete_file_data_content class FileData(Document): - ignore_feed = True + no_feed_on_delete = True def before_insert(self): frappe.local.rollback_observers.append(self) diff --git a/frappe/data/Framework.sql b/frappe/data/Framework.sql index 00b17a2aa4..196943e71b 100644 --- a/frappe/data/Framework.sql +++ b/frappe/data/Framework.sql @@ -83,6 +83,7 @@ CREATE TABLE `tabDocPerm` ( `report` int(1) DEFAULT NULL, `export` int(1) DEFAULT NULL, `import` int(1) DEFAULT NULL, + `share` int(1) DEFAULT NULL, `print` int(1) DEFAULT NULL, `email` int(1) DEFAULT NULL, `restrict` int(1) DEFAULT NULL, diff --git a/frappe/database.py b/frappe/database.py index b6685d074f..cb9329d049 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -598,6 +598,24 @@ class Database: else: return frappe.defaults.get_defaults(parent) + def get_shared(self, doctype, user=None, rights=None): + """Get list of shared document names for given user and DocType. + + :param doctype: DocType of which shared names are queried. + :param user: User for which shared names are queried. + :param rights: List of rights for which the document is shared. List of `read`, `write`, `share`""" + + if not user: + user = frappe.session.user + + if not rights: + rights = ["read"] + + condition = " and ".join(["`{0}`=1".format(right) for right in rights]) + + return self.sql_list("select share_name from tabDocShare where user=%s and share_doctype=%s and {0}".format(condition), + (user, doctype)) + def begin(self): return # not required diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 39a82ad7a2..026f4d8dbc 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -7,6 +7,35 @@ from frappe import _ from frappe.utils import cint, flt, now, cstr, strip_html from frappe.model import default_fields from frappe.model.naming import set_new_name +from frappe.modules import load_doctype_module + +_classes = {} + +def get_controller(doctype): + """Returns the **class** object of the given DocType. + For `custom` type, returns `frappe.model.document.Document`. + + :param doctype: DocType name as string.""" + if not doctype in _classes: + module_name, custom = frappe.db.get_value("DocType", doctype, ["module", "custom"]) \ + or ["Core", False] + + if custom: + _class = BaseDocument + else: + module = load_doctype_module(doctype, module_name) + classname = doctype.replace(" ", "").replace("-", "") + if hasattr(module, classname): + _class = getattr(module, classname) + if issubclass(_class, BaseDocument): + _class = getattr(module, classname) + else: + raise ImportError, doctype + else: + raise ImportError, doctype + _classes[doctype] = _class + + return _classes[doctype] class BaseDocument(object): ignore_in_getter = ("doctype", "_meta", "meta", "_table_fields", "_valid_columns") @@ -15,6 +44,9 @@ class BaseDocument(object): self.update(d) self.dont_update_if_missing = [] + if hasattr(self, "__setup__"): + self.__setup__() + @property def meta(self): if not hasattr(self, "_meta"): @@ -95,6 +127,9 @@ class BaseDocument(object): self.__dict__[key] = [] value = self._init_child(value, key) self.__dict__[key].append(value) + + # reference parent document + value.parent_doc = self return value else: raise ValueError, "Document attached to child table must be a dict or BaseDocument, not " + str(type(value))[1:-1] @@ -117,7 +152,7 @@ class BaseDocument(object): value["doctype"] = self.get_table_field_doctype(key) if not value["doctype"]: raise AttributeError, key - value = BaseDocument(value) + value = get_controller(value["doctype"])(value) value.init_valid_columns() value.parent = self.name @@ -364,28 +399,30 @@ class BaseDocument(object): return format_value(self.get(fieldname), self.meta.get_field(fieldname), doc=doc or self, currency=currency) - def get_print_template(self, fieldname, parent_doc=None): - """Returns print template for given fieldname if specified in controller - or parent controller. + def is_print_hide(self, fieldname, for_print=True): + """Returns true if fieldname is to be hidden for print. - Templates must be specified as: + Print Hide can be set via the Print Format Builder or in the controller as a list + of hidden fields. Example - class MyDocType(Document): + class MyDoc(Document): def __setup__(self): - self.print_templates = { - "[fieldname]": "templates/includes/template_name.html", - "[table fieldname].[fieldname]": "templates/includes/template_name.html" - } + self.print_hide = ["field1", "field2"] - :param fieldname: Field for which template is queried. - :param parent_doc: Parent Document, if child doc.""" - src = self - if parent_doc: - src = parent_doc - fieldname = self.parentfield + "." + fieldname - if hasattr(src, "print_templates"): - return src.print_templates.get(fieldname) + :param fieldname: Fieldname to be checked if hidden. + """ + df = self.meta.get_field(fieldname) + return df and (df.get("__print_hide") or df.print_hide) + def in_format_data(self, fieldname): + """Returns True if shown via Print Format::`format_data` property. + Called from within standard print format.""" + doc = getattr(self, "parent_doc", self) + + if hasattr(doc, "format_data_map"): + return fieldname in doc.format_data_map + else: + return True def _filter(data, filters, limit=None): """pass filters as: diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 0b485bcd63..494d6ea7dc 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -240,19 +240,36 @@ class DatabaseQuery(object): self.match_filters = [] self.match_conditions = [] + only_if_shared = False + if not self.tables: self.extract_tables() meta = frappe.get_meta(self.doctype) role_permissions = frappe.permissions.get_role_permissions(meta, user=self.user) - if not meta.istable and not role_permissions.get("read") and not getattr(self, "ignore_permissions", False): - frappe.throw(_("No permission to read {0}").format(self.doctype)) - # apply user permissions? - if role_permissions.get("apply_user_permissions", {}).get("read"): - # get user permissions - user_permissions = frappe.defaults.get_user_permissions(self.user) - self.add_user_permissions(user_permissions, - user_permission_doctypes=role_permissions.get("user_permission_doctypes")) + self.shared = frappe.db.get_shared(self.doctype, self.user) + + if not meta.istable and not role_permissions.get("read") and not getattr(self, + "ignore_permissions", False): + only_if_shared = True + if not self.shared: + frappe.throw(_("No permission to read {0}").format(self.doctype)) + else: + self.conditions.append(self.get_share_condition()) + + else: + # share is an OR condition, if there is a role permission + if not only_if_shared and self.shared: + self.or_conditions.append(self.get_share_condition()) + + # apply user permissions? + if not role_permissions.get("apply_user_permissions", {}).get("read"): + # get user permissions + user_permissions = frappe.defaults.get_user_permissions(self.user) + self.add_user_permissions(user_permissions, + user_permission_doctypes=role_permissions.get("user_permission_doctypes")) + + if as_condition: conditions = "" @@ -269,6 +286,10 @@ class DatabaseQuery(object): else: return self.match_filters + def get_share_condition(self): + return """`tab{0}`.name in ({1})""".format(self.doctype, ", ".join(["%s"] * len(self.shared))) % \ + [frappe.db.escape(s) for s in self.shared] + def add_user_permissions(self, user_permissions, user_permission_doctypes=None): user_permission_doctypes = frappe.permissions.get_user_permission_doctypes(user_permission_doctypes, user_permissions) diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 792249e128..4ccd4c64b9 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -62,6 +62,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa doc.run_method("on_trash") delete_linked_todos(doc) + delete_shared(doc) # check if links exist if not force: check_if_doc_is_linked(doc) @@ -166,7 +167,7 @@ def delete_linked_todos(doc): def insert_feed(doc): from frappe.utils import get_fullname - if frappe.flags.in_install or frappe.flags.in_import or doc.get("ignore_feed"): + if frappe.flags.in_install or frappe.flags.in_import or getattr(doc, "no_feed_on_delete", False): return frappe.get_doc({ @@ -177,3 +178,7 @@ def insert_feed(doc): "subject": _("Deleted"), "full_name": get_fullname(doc.owner) }).insert(ignore_permissions=True) + +def delete_shared(doc): + delete_doc("DocShare", frappe.db.sql_list("""select name from `tabDocShare` + where share_doctype=%s and share_name=%s""", (doc.doctype, doc.name))) diff --git a/frappe/model/document.py b/frappe/model/document.py index 4eaa7ef5a7..031640cd66 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -5,8 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _, msgprint from frappe.utils import flt, cint, cstr, now -from frappe.modules import load_doctype_module -from frappe.model.base_document import BaseDocument +from frappe.model.base_document import BaseDocument, get_controller from frappe.model.naming import set_new_name from werkzeug.exceptions import NotFound, Forbidden import hashlib @@ -47,34 +46,6 @@ def get_doc(arg1, arg2=None): raise ImportError, arg1 -_classes = {} - -def get_controller(doctype): - """Returns the **class** object of the given DocType. - For `custom` type, returns `frappe.model.document.Document`. - - :param doctype: DocType name as string.""" - if not doctype in _classes: - module_name, custom = frappe.db.get_value("DocType", doctype, ["module", "custom"]) \ - or ["Core", False] - - if custom: - _class = Document - else: - module = load_doctype_module(doctype, module_name) - classname = doctype.replace(" ", "").replace("-", "") - if hasattr(module, classname): - _class = getattr(module, classname) - if issubclass(_class, Document): - _class = getattr(module, classname) - else: - raise ImportError, doctype - else: - raise ImportError, doctype - _classes[doctype] = _class - - return _classes[doctype] - class Document(BaseDocument): """All controllers inherit from `Document`.""" def __init__(self, arg1, arg2=None): @@ -112,9 +83,6 @@ class Document(BaseDocument): # incorrect arguments. let's not proceed. raise frappe.DataError("Document({0}, {1})".format(arg1, arg2)) - if hasattr(self, "__setup__"): - self.__setup__() - self.dont_update_if_missing = [] def load_from_db(self): @@ -157,7 +125,7 @@ class Document(BaseDocument): if not self.has_permission(permtype): self.raise_no_permission_to(permlabel or permtype) - def has_permission(self, permtype): + def has_permission(self, permtype="read"): """Call `frappe.has_permission` if `self.ignore_permissions` is not set. diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 5f5a13b550..d2ad2a3ac2 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -211,10 +211,6 @@ class Meta(Document): return fields - def is_print_hide(self, fieldname): - df = self.get_field(fieldname) - return df and (df.get("__print_hide") or df.print_hide) - doctype_table_fields = [ frappe._dict({"fieldname": "fields", "options": "DocField"}), frappe._dict({"fieldname": "permissions", "options": "DocPerm"}) diff --git a/frappe/permissions.py b/frappe/permissions.py index c384689a0d..12d08d5c90 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -7,7 +7,7 @@ from frappe import _, msgprint from frappe.utils import cint rights = ("read", "write", "create", "delete", "submit", "cancel", "amend", - "print", "email", "report", "import", "export", "set_user_permissions") + "print", "email", "report", "import", "export", "set_user_permissions", "share") def check_admin_or_system_manager(user=None): if not user: user = frappe.session.user @@ -33,9 +33,17 @@ def has_permission(doctype, ptype="read", doc=None, verbose=True, user=None): if user=="Administrator": return True + def false_if_not_shared(): + if doc and ptype in ("read", "write", "share"): + shared = frappe.db.get_shared(meta.name, user, [ptype]) + if doc.name in shared: + return True + + return False + role_permissions = get_role_permissions(meta, user=user) if not role_permissions.get(ptype): - return False + return false_if_not_shared() if doc: if isinstance(doc, basestring): @@ -44,10 +52,10 @@ def has_permission(doctype, ptype="read", doc=None, verbose=True, user=None): if role_permissions["apply_user_permissions"].get(ptype): if not user_has_permission(doc, verbose=verbose, user=user, user_permission_doctypes=role_permissions.get("user_permission_doctypes")): - return False + return false_if_not_shared() if not has_controller_permissions(doc, ptype, user=user): - return False + return false_if_not_shared() return True @@ -74,6 +82,11 @@ def get_doc_permissions(doc, verbose=False, user=None): if role_permissions["apply_user_permissions"].get(ptype): role_permissions[ptype] = 0 + # update share permissions + role_permissions.update(frappe.db.get_value("DocShare", + {"share_type": doc.doctype, "share_name": doc.name, "user": user}, + ["read", "write", "share"], as_dict=True)) + return role_permissions def get_role_permissions(meta, user=None): diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css index 5fbb5625a0..0e4638a12f 100644 --- a/frappe/public/css/desk.css +++ b/frappe/public/css/desk.css @@ -19,8 +19,8 @@ a, .badge, .btn, .ui-menu .ui-menu-item { - transition: 0.2s; - -webkit-transition: 0.2s; + transition: background-color 0.2s; + -webkit-transition: background-color 0.2s; } a.disabled, a.disabled:hover { diff --git a/frappe/public/js/frappe/form/print.js b/frappe/public/js/frappe/form/print.js index 95e03cd960..f455559fc4 100644 --- a/frappe/public/js/frappe/form/print.js +++ b/frappe/public/js/frappe/form/print.js @@ -82,6 +82,7 @@ frappe.ui.form.PrintPreview = Class.extend({ }, preview: function() { var me = this; + this.wrapper.find(".btn-print-edit").toggle(this.get_print_format().name); this.get_print_html(function(html) { me.wrapper.find(".print-format").html(html); }); diff --git a/frappe/public/js/legacy/clientscriptAPI.js b/frappe/public/js/legacy/clientscriptAPI.js index df2efd33e8..bbf9998823 100644 --- a/frappe/public/js/legacy/clientscriptAPI.js +++ b/frappe/public/js/legacy/clientscriptAPI.js @@ -217,7 +217,9 @@ _f.Frm.prototype.set_value = function(field, value, if_missing) { _set(field, value) } else if($.isPlainObject(field)) { $.each(field, function(f, v) { - _set(f, v); + if(me.get_field(f)) { + _set(f, v); + } }) } } diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less index 7ecaae015d..9551bc2315 100644 --- a/frappe/public/less/desk.less +++ b/frappe/public/less/desk.less @@ -26,8 +26,8 @@ a, .badge, .btn, .ui-menu .ui-menu-item { - transition: 0.2s; - -webkit-transition: 0.2s; + transition: background-color 0.2s; + -webkit-transition: background-color 0.2s; } a.disabled, a.disabled:hover { diff --git a/frappe/share.py b/frappe/share.py new file mode 100644 index 0000000000..8551d69ec1 --- /dev/null +++ b/frappe/share.py @@ -0,0 +1,42 @@ +# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe + +def add(doctype, name, user=None, read=1, write=0, share=0): + """Share the given document with a user.""" + if not user: + user = frappe.session.user + + return frappe.get_doc({ + "doctype": "DocShare", + "user": user, + "share_doctype": doctype, + "share_name": name, + "read": read, + "write": write, + "share": share + }).insert(ignore_permissions=True) + +def set_permission(doctype, name, user, permission_to, remove=False): + """Set share right.""" + share_name = frappe.db.get_value("DocShare", {"user": user, "share_name": name, + "share_doctype": doctype}) + if not share_name: + if not remove: + share = add(doctype, name, user, **{permission_to: 1}) + else: + # no share found, nothing to remove + share = {} + pass + else: + share = frappe.get_doc("DocShare", share_name) + share.set(permission_to, 0 if remove else 1) + share.save() + + if not (share.read or share.write or share.share): + share.delete() + share = {} + + return share diff --git a/frappe/templates/includes/login.css b/frappe/templates/includes/login.css index f128c29c9f..56687b354e 100644 --- a/frappe/templates/includes/login.css +++ b/frappe/templates/includes/login.css @@ -32,22 +32,13 @@ -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - padding: 10px; + padding: 6px; font-size: 16px; + margin-bottom: 10px; } .form-signin .form-control:focus { z-index: 2; } -input#login_email, input#signup_fullname { - margin-bottom: -1px; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -} -input#login_password, input#signup_email { - margin-bottom: 10px; - border-top-left-radius: 0; - border-top-right-radius: 0; -} .btn-social { margin: 10px; diff --git a/frappe/templates/pages/print.py b/frappe/templates/pages/print.py index c85e449d85..02ac7d86fe 100644 --- a/frappe/templates/pages/print.py +++ b/frappe/templates/pages/print.py @@ -68,7 +68,7 @@ def get_html(doc, name=None, print_format=None, meta=None, meta = frappe.get_meta(doc.doctype) jenv = frappe.get_jenv() - format_data = {} + format_data, format_data_map = [], {} # determine template if print_format in ("Standard", standard_format): @@ -76,7 +76,16 @@ def get_html(doc, name=None, print_format=None, meta=None, else: print_format = frappe.get_doc("Print Format", print_format) if print_format.format_data: + # set format data format_data = json.loads(print_format.format_data) + for df in format_data: + format_data_map[df.get("fieldname")] = df + if "visible_columns" in df: + for _df in df.get("visible_columns"): + format_data_map[_df.get("fieldname")] = _df + + doc.format_data_map = format_data_map + template = "standard" else: template = jenv.from_string(get_print_format(doc.doctype, @@ -180,7 +189,7 @@ def make_layout(doc, meta, format_data=None): if df.fieldtype=="HTML" and df.options: doc.set(df.fieldname, True) # show this field - if is_visible(df) and has_value(df, doc): + if is_visible(df, doc) and has_value(df, doc): page[-1][-1].append(df) # if table, add the row info in the field @@ -208,9 +217,16 @@ def make_layout(doc, meta, format_data=None): layout = [filter(lambda s: any(filter(lambda c: any(c), s)), page) for page in layout] return layout -def is_visible(df): - no_display = ("Section Break", "Column Break", "Button") - return (df.fieldtype not in no_display) and not df.get("__print_hide") and not df.print_hide +def is_visible(df, doc): + """Returns True if docfield is visible in print layout and does not have print_hide set.""" + if df.fieldtype in ("Section Break", "Column Break", "Button"): + return False + + if hasattr(doc, "hide_in_print_layout"): + if df.fieldname in doc.hide_in_print_layout: + return False + + return not doc.is_print_hide(df.fieldname) def has_value(df, doc): value = doc.get(df.fieldname) @@ -251,15 +267,22 @@ def get_print_style(style=None): def get_visible_columns(data, table_meta, df): """Returns list of visible columns based on print_hide and if all columns have value.""" columns = [] + doc = data[0] or frappe.new_doc(df.options) + def add_column(col_df): + return is_visible(col_df, doc) \ + and column_has_value(data, col_df.get("fieldname")) + if df.get("visible_columns"): # columns specified by column builder for col_df in df.get("visible_columns"): + # load default docfield properties newdf = table_meta.get_field(col_df.get("fieldname")).as_dict().copy() newdf.update(col_df) - columns.append(newdf) + if add_column(newdf): + columns.append(newdf) else: for col_df in table_meta.fields: - if is_visible(col_df) and column_has_value(data, col_df.get("fieldname")): + if add_column(col_df): columns.append(col_df) return columns diff --git a/frappe/templates/print_formats/standard_macros.html b/frappe/templates/print_formats/standard_macros.html index d4493e2dff..f1f8e37212 100644 --- a/frappe/templates/print_formats/standard_macros.html +++ b/frappe/templates/print_formats/standard_macros.html @@ -76,9 +76,10 @@ {%- endif -%} {%- endmacro -%} -{%- macro print_value(df, doc, parent_doc=None) -%} - {% if doc.get_print_template(df.fieldname, parent_doc) %} - {% include doc.get_print_template(df.fieldname, parent_doc) %} +{%- macro print_value(df, doc, meta, parent_doc=None) -%} + {% if doc.print_templates and + doc.print_templates.get(df.fieldname) %} + {% include doc.print_templates[df.fieldname] %} {% elif df.fieldtype=="Check" %} {% elif df.fieldtype=="Image" %}