diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index b90ae39506..d428373c05 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -33,7 +33,7 @@ from frappe.model.meta import Meta from frappe.modules import get_doc_path, make_boilerplate from frappe.modules.import_file import get_file_path from frappe.query_builder.functions import Concat -from frappe.utils import cint, flt, get_table_name, random_string +from frappe.utils import cint, flt, is_a_property, random_string from frappe.website.utils import clear_cache if TYPE_CHECKING: @@ -1820,13 +1820,6 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): raise -def is_a_property(x) -> bool: - """Get properties (@property, @cached_property) in a controller class""" - from functools import cached_property - - return isinstance(x, (property, cached_property)) - - def check_fieldname_conflicts(docfield): """Checks if fieldname conflicts with methods or properties""" doc = frappe.get_doc({"doctype": docfield.dt}) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index edf74e3dca..59cdea8031 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -19,7 +19,17 @@ from frappe.model.docstatus import DocStatus from frappe.model.naming import set_new_name from frappe.model.utils.link_count import notify_link_count from frappe.modules import load_doctype_module -from frappe.utils import cast_fieldtype, cint, compare, cstr, flt, now, sanitize_html, strip_html +from frappe.utils import ( + cast_fieldtype, + cint, + compare, + cstr, + flt, + is_a_property, + now, + sanitize_html, + strip_html, +) from frappe.utils.html_utils import unescape_html if TYPE_CHECKING: @@ -344,14 +354,18 @@ class BaseDocument: if ignore_virtual or fieldname not in self.permitted_fieldnames: continue - if value is None and (options := getattr(df, "options", None)): - from frappe.utils.safe_exec import get_safe_globals + if value is None: + if (prop := getattr(type(self), fieldname, None)) and is_a_property(prop): + value = getattr(self, fieldname) - value = frappe.safe_eval( - code=options, - eval_globals=get_safe_globals(), - eval_locals={"doc": self}, - ) + elif options := getattr(df, "options", None): + from frappe.utils.safe_exec import get_safe_globals + + value = frappe.safe_eval( + code=options, + eval_globals=get_safe_globals(), + eval_locals={"doc": self}, + ) if isinstance(value, list) and df.fieldtype not in table_fields: frappe.throw(_("Value for {0} cannot be a list").format(_(df.label))) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 4ad4c0536b..00f2a8726c 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -655,6 +655,11 @@ def is_markdown(text): return not NON_MD_HTML_PATTERN.search(text) +def is_a_property(x) -> bool: + """Get properties (@property, @cached_property) in a controller class""" + return isinstance(x, (property, functools.cached_property)) + + def get_sites(sites_path=None): if not sites_path: sites_path = getattr(frappe.local, "sites_path", None) or "."