diff --git a/frappe/__init__.py b/frappe/__init__.py index fbb49f254d..1f55bc9d57 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -108,6 +108,7 @@ def init(site, sites_path=None): local.user = None local.role_permissions = {} local.valid_columns = {} + local.new_doc_templates = {} local.jenv = None local.jloader =None @@ -429,14 +430,14 @@ def reset_metadata_version(): cache().set_value("metadata_version", v) return v -def new_doc(doctype, parent_doc=None, parentfield=None): +def new_doc(doctype, parent_doc=None, parentfield=None, as_dict=False): """Returns a new document of the given DocType with defaults set. :param doctype: DocType of the new document. :param parent_doc: [optional] add to parent document. :param parentfield: [optional] add against this `parentfield`.""" from frappe.model.create_new import get_new_doc - return get_new_doc(doctype, parent_doc, parentfield) + return get_new_doc(doctype, parent_doc, parentfield, as_dict=as_dict) def set_value(doctype, docname, fieldname, value): """Set document value. Calls `frappe.client.set_value`""" diff --git a/frappe/core/doctype/user/test_records.json b/frappe/core/doctype/user/test_records.json index a8ffe46fbd..83c06214b6 100644 --- a/frappe/core/doctype/user/test_records.json +++ b/frappe/core/doctype/user/test_records.json @@ -1,52 +1,60 @@ [ { - "doctype": "User", - "email": "test@example.com", - "enabled": 1, - "first_name": "_Test", - "new_password": "testpassword", + "doctype": "User", + "email": "test@example.com", + "enabled": 1, + "first_name": "_Test", + "new_password": "testpassword", "user_roles": [ { - "doctype": "UserRole", - "parentfield": "user_roles", + "doctype": "UserRole", + "parentfield": "user_roles", "role": "_Test Role" - }, + }, { - "doctype": "UserRole", - "parentfield": "user_roles", + "doctype": "UserRole", + "parentfield": "user_roles", "role": "System Manager" } ] - }, + }, { - "doctype": "User", - "email": "test1@example.com", - "first_name": "_Test1", + "doctype": "User", + "email": "test1@example.com", + "first_name": "_Test1", "new_password": "testpassword" - }, + }, { - "doctype": "User", - "email": "test2@example.com", - "first_name": "_Test2", - "new_password": "testpassword" - }, + "doctype": "User", + "email": "test2@example.com", + "first_name": "_Test2", + "new_password": "testpassword", + "enabled": 1 + }, + { + "doctype": "User", + "email": "testperm@example.com", + "first_name": "_Test Perm", + "new_password": "testpassword", + "enabled": 1 + }, { - "doctype": "User", - "email": "testdelete@example.com", - "enabled": 1, - "first_name": "_Test", - "new_password": "testpassword", + "doctype": "User", + "email": "testdelete@example.com", + "enabled": 1, + "first_name": "_Test", + "new_password": "testpassword", "user_roles": [ { - "doctype": "UserRole", - "parentfield": "user_roles", + "doctype": "UserRole", + "parentfield": "user_roles", "role": "_Test Role 2" - }, + }, { - "doctype": "UserRole", - "parentfield": "user_roles", + "doctype": "UserRole", + "parentfield": "user_roles", "role": "System Manager" } ] } -] \ No newline at end of file +] diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index dab4ce8b05..35ce75fa82 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -15,6 +15,9 @@ class TestUser(unittest.TestCase): frappe.db.sql("""delete from tabUserRole where role='_Test Role 2'""") delete_doc("Role","_Test Role 2") + if frappe.db.exists("User", "_test@example.com"): + delete_doc("User", "_test@example.com") + user = frappe.copy_doc(test_records[1]) user.email = "_test@example.com" user.insert() @@ -50,3 +53,21 @@ class TestUser(unittest.TestCase): frappe.db.set_value("Website Settings", "Website Settings", "_test", "_test_val") self.assertEquals(frappe.db.get_value("Website Settings", None, "_test"), "_test_val") self.assertEquals(frappe.db.get_value("Website Settings", "Website Settings", "_test"), "_test_val") + + def test_high_permlevel_validations(self): + user = frappe.get_meta("User") + self.assertTrue("user_roles" in [d.fieldname for d in user.get_high_permlevel_fields()]) + + frappe.set_user("testperm@example.com") + + me = frappe.get_doc("User", "testperm@example.com") + me.add_roles("System Manager") + + self.assertTrue("System Manager" not in [d.role for d in me.get("user_roles")]) + + frappe.set_user("Administrator") + + me = frappe.get_doc("User", "testperm@example.com") + me.add_roles("System Manager") + + self.assertTrue("System Manager" in [d.role for d in me.get("user_roles")]) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 69cc36ced9..159c917986 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -2,12 +2,13 @@ # MIT License. See license.txt from __future__ import unicode_literals -import frappe, json, sys +import frappe, sys 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 +from frappe.model import display_fieldtypes _classes = {} @@ -471,6 +472,29 @@ class BaseDocument(object): else: return True + def reset_values_if_no_permlevel_access(self, has_access_to, high_permlevel_fields): + """If the user does not have permissions at permlevel > 0, then reset the values to original / default""" + to_reset = [] + + for df in high_permlevel_fields: + if df.permlevel not in has_access_to and df.fieldtype not in display_fieldtypes: + to_reset.append(df) + + if to_reset: + if self.is_new(): + # if new, set default value + ref_doc = frappe.new_doc(self.doctype) + else: + # get values from old doc + if self.parent: + self.parent_doc.get_latest() + ref_doc = [d for d in self.parent_doc.get(self.parentfield) if d.name == self.name][0] + else: + ref_doc = self.get_latest() + + for df in to_reset: + self.set(df.fieldname, ref_doc.get(df.fieldname)) + def _filter(data, filters, limit=None): """pass filters as: {"key": "val", "key": ["!=", "val"], diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py index 8090fe5031..afaec6780c 100644 --- a/frappe/model/create_new.py +++ b/frappe/model/create_new.py @@ -7,37 +7,51 @@ Create a new document with defaults set """ import frappe -from frappe.utils import nowdate, nowtime, cint, flt, now_datetime +from frappe.utils import nowdate, nowtime, now_datetime import frappe.defaults from frappe.model.db_schema import type_map +import copy -def get_new_doc(doctype, parent_doc = None, parentfield = None): - doc = frappe.get_doc({ - "doctype": doctype, - "__islocal": 1, - "owner": frappe.session.user, - "docstatus": 0 - }) +def get_new_doc(doctype, parent_doc = None, parentfield = None, as_dict=False): + if not doctype in frappe.local.new_doc_templates: + # cache a copy of new doc as it is called + # frequently for inserts + doc = frappe.get_doc({ + "doctype": doctype, + "__islocal": 1, + "owner": frappe.session.user, + "docstatus": 0 + }) - user_permissions = frappe.defaults.get_user_permissions() + user_permissions = frappe.defaults.get_user_permissions() - if parent_doc: - doc.parent = parent_doc.name - doc.parenttype = parent_doc.doctype + defaults = frappe.defaults.get_defaults() - if parentfield: - doc.parentfield = parentfield + for df in doc.meta.get("fields"): + if df.fieldtype in type_map: + default_value = get_default_value(df, defaults, user_permissions, parent_doc) + doc.set(df.fieldname, default_value) + + doc._fix_numeric_types() + doc = doc.get_valid_dict() + doc["doctype"] = doctype + doc["__islocal"] = 1 - defaults = frappe.defaults.get_defaults() + frappe.local.new_doc_templates[doctype] = doc - for df in doc.meta.get("fields"): - if df.fieldtype in type_map: - default_value = get_default_value(df, defaults, user_permissions, parent_doc) - doc.set(df.fieldname, default_value) + doc = copy.deepcopy(frappe.local.new_doc_templates[doctype]) - doc._fix_numeric_types() + if parent_doc: + doc["parent"] = parent_doc.name + doc["parenttype"] = parent_doc.doctype + + if parentfield: + doc["parentfield"] = parentfield - return doc + if as_dict: + return doc + else: + return frappe.get_doc(doc) def get_default_value(df, defaults, user_permissions, parent_doc): user_permissions_exist = (df.fieldtype=="Link" diff --git a/frappe/model/document.py b/frappe/model/document.py index 7d0237c4d0..d758c24406 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -8,7 +8,6 @@ from frappe.utils import flt, cint, cstr, now, get_datetime_str from frappe.model.base_document import BaseDocument, get_controller from frappe.model.naming import set_new_name from werkzeug.exceptions import NotFound, Forbidden -from frappe.model import display_fieldtypes import hashlib, json # once_only validation @@ -84,7 +83,7 @@ class Document(BaseDocument): # incorrect arguments. let's not proceed. raise frappe.DataError("Document({0}, {1})".format(arg1, arg2)) - self.dont_update_if_missing = [] + self._default_new_docs = {} self.flags = frappe._dict() def load_from_db(self): @@ -122,6 +121,11 @@ class Document(BaseDocument): else: self.set(df.fieldname, []) + def get_latest(self): + if not getattr(self, "latest", None): + self.latest = frappe.get_doc(self.doctype, self.name) + return self.latest + def check_permission(self, permtype, permlabel=None): """Raise `frappe.PermissionError` if not permitted""" if not self.has_permission(permtype): @@ -301,37 +305,18 @@ class Document(BaseDocument): if self.flags.ignore_permissions or frappe.flags.in_install: return - self.get_high_permlevel_fields() - if not self.high_permlevel_fields: - return - has_access_to = self.get_permlevel_access() - to_reset = [] - for df in self.high_permlevel_fields: - if df.permlevel not in has_access_to and df.fieldtype not in display_fieldtypes: - to_reset.append(df) + high_permlevel_fields = self.meta.get_high_permlevel_fields() - if to_reset: - if self.is_new(): - # if new, set default value - for df in to_reset: - self.set(df.fieldname, df.default or None) + if high_permlevel_fields: + self.reset_values_if_no_permlevel_access(has_access_to, high_permlevel_fields) - else: - # get values from old doc - old = frappe.get_doc(self.doctype, self.name) - for df in to_reset: - self.set(df.fieldname, old.get(df.fieldname)) - - def get_high_permlevel_fields(self): - """Build list of fields with high perm level and all the higher perm levels defined.""" - self.high_permlevel_fields = [] - self.high_permlevels = [] - for df in self.meta.fields: - if df.permlevel > 0: - self.high_permlevel_fields.append(df) - if not df.permlevel in self.high_permlevels: - self.high_permlevels.append(df.permlevel) + # check for child tables + for df in self.meta.get_table_fields(): + high_permlevel_fields = frappe.get_meta(df.options).meta.get_high_permlevel_fields() + if high_permlevel_fields: + for d in self.get(df.fieldname): + d.reset_values_if_no_permlevel_access(has_access_to, high_permlevel_fields) def get_permlevel_access(self): user_roles = frappe.get_roles() @@ -347,12 +332,12 @@ class Document(BaseDocument): if frappe.flags.in_import: return - new_doc = frappe.new_doc(self.doctype) + new_doc = frappe.new_doc(self.doctype, as_dict=True) self.update_if_missing(new_doc) # children for df in self.meta.get_table_fields(): - new_doc = frappe.new_doc(df.options) + new_doc = frappe.new_doc(df.options, as_dict=True) value = self.get(df.fieldname) if isinstance(value, list): for d in value: @@ -578,7 +563,7 @@ class Document(BaseDocument): self.run_method("on_update_after_submit") frappe.cache().set_value("last_modified:" + self.doctype, self.modified) - + self.latest = None def check_no_back_links_exist(self): """Check if document links to any active document before Cancel.""" diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 6d223e1846..b0ccdbe488 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -213,6 +213,16 @@ class Meta(Document): return fields + def get_high_permlevel_fields(self): + """Build list of fields with high perm level and all the higher perm levels defined.""" + if not hasattr(self, "high_permlevel_fields"): + self.high_permlevel_fields = [] + for df in self.fields: + if df.permlevel > 0: + self.high_permlevel_fields.append(df) + + return self.high_permlevel_fields + doctype_table_fields = [ frappe._dict({"fieldname": "fields", "options": "DocField"}), frappe._dict({"fieldname": "permissions", "options": "DocPerm"}) diff --git a/frappe/test_runner.py b/frappe/test_runner.py index eac8023060..6b5a9be63f 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -193,14 +193,14 @@ def make_test_records_for_doctype(doctype, verbose=0, force=False): def make_test_objects(doctype, test_records, verbose=None): records = [] - if not frappe.get_meta(doctype).issingle: - existing = frappe.get_all(doctype, filters={"name":("like", "_T-" + doctype + "-%")}) - if existing: - return [d.name for d in existing] - - existing = frappe.get_all(doctype, filters={"name":("like", "_Test " + doctype + "%")}) - if existing: - return [d.name for d in existing] + # if not frappe.get_meta(doctype).issingle: + # existing = frappe.get_all(doctype, filters={"name":("like", "_T-" + doctype + "-%")}) + # if existing: + # return [d.name for d in existing] + # + # existing = frappe.get_all(doctype, filters={"name":("like", "_Test " + doctype + "%")}) + # if existing: + # return [d.name for d in existing] for doc in test_records: if not doc.get("doctype"): diff --git a/frappe/tests/test_document.py b/frappe/tests/test_document.py index 39d5e37b34..9c12556f8b 100644 --- a/frappe/tests/test_document.py +++ b/frappe/tests/test_document.py @@ -146,4 +146,3 @@ class TestDocument(unittest.TestCase): d.validate_update_after_submit() d.meta.get_field("starts_on").allow_on_submit = 0 -