diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 31bef96f92..d798b42d45 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -447,7 +447,7 @@ def get_title_values_for_table_and_multiselect_fields(doc, table_fields=None): if not table_fields: meta = frappe.get_meta(doc.doctype) - table_fields = meta.get_table_fields(ignore_virtual=False) + table_fields = meta.get_table_fields(include_computed=True) for field in table_fields: if not doc.get(field.fieldname): diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 31951ef414..7c4688500c 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -138,27 +138,27 @@ def import_controller(doctype): raise ImportError(f"{doctype}: {classname} is not a subclass of BaseDocument") class_ = _get_extended_class(class_, doctype) - return _update_virtual_ct_props(class_, doctype) + return _update_computed_ct_props(class_, doctype) -def _update_virtual_ct_props(class_, doctype): - if doctype in DOCTYPES_FOR_DOCTYPE or getattr(class_, "_virtual_ct_props_updated", False): +def _update_computed_ct_props(class_, doctype): + if doctype in DOCTYPES_FOR_DOCTYPE or getattr(class_, "_computed_ct_props_updated", False): return class_ meta = frappe.get_meta(doctype) - for df in meta.get_table_fields(ignore_virtual=False): + for df in meta.get_table_fields(include_computed=True): if df.is_virtual: - _update_virtual_ct_prop(class_, df) + _update_computed_ct_prop(class_, df) - class_._virtual_ct_props_updated = True + class_._computed_ct_props_updated = True return class_ -def _update_virtual_ct_prop(class_, df): +def _update_computed_ct_prop(class_, df): fieldname = df.fieldname original_prop = getattr(class_, fieldname, None) - def virtual_ct_prop(self): + def computed_ct_prop(self): if original_prop and is_a_property(original_prop): value = original_prop.__get__(self, type(self)) @@ -174,7 +174,7 @@ def _update_virtual_ct_prop(class_, df): self.set(fieldname, value) return self.__dict__[fieldname] - setattr(class_, fieldname, property(virtual_ct_prop)) + setattr(class_, fieldname, property(computed_ct_prop)) def _get_extended_class(base_class, doctype): @@ -216,22 +216,6 @@ def _get_extended_class(base_class, doctype): ) -RESERVED_KEYWORDS = frozenset( - ( - "doctype", - "meta", - "flags", - "_weakref", - "_parent_doc", - "_table_fields", - "_doc_before_save", - "_table_fieldnames", - "permitted_fieldnames", - "dont_update_if_missing", - ) -) - - class BaseDocument: def __init__(self, d): if d.get("doctype"): @@ -450,7 +434,7 @@ class BaseDocument: controller = get_controller(doctype) child = controller.__new__(controller) child._table_fieldnames = TABLE_DOCTYPES_FOR_CHILD_TABLES - child._non_virtual_table_fieldnames = TABLE_DOCTYPES_FOR_CHILD_TABLES + child._non_computed_table_fieldnames = TABLE_DOCTYPES_FOR_CHILD_TABLES child.__init__(value) __dict = child.__dict__ @@ -478,13 +462,13 @@ class BaseDocument: return self.meta._table_doctypes @cached_property - def _non_virtual_table_fieldnames(self) -> dict: + def _non_computed_table_fieldnames(self) -> dict: if self.doctype in DOCTYPES_FOR_DOCTYPE: return self._table_fieldnames - return self.meta._non_virtual_table_doctypes + return self.meta._non_computed_table_doctypes - def _get_table_fields(self, ignore_virtual=True): + def _get_table_fields(self, include_computed=False): """ To get table fields during Document init Meta.get_table_fields goes into recursion for special doctypes @@ -497,7 +481,7 @@ class BaseDocument: if self.doctype in DOCTYPES_FOR_DOCTYPE: return () - return self.meta.get_table_fields(ignore_virtual=ignore_virtual) + return self.meta.get_table_fields(include_computed=include_computed) def _evaluate_virtual_field_options(self, options): from frappe.utils.safe_exec import get_safe_globals @@ -581,12 +565,12 @@ class BaseDocument: without worrying about whether or not they have values """ - if not self._non_virtual_table_fieldnames: + if not self._non_computed_table_fieldnames: return __dict = self.__dict__ - for fieldname in self._non_virtual_table_fieldnames: + for fieldname in self._non_computed_table_fieldnames: if __dict.get(fieldname) is None: __dict[fieldname] = [] @@ -646,13 +630,13 @@ class BaseDocument: no_child_table_fields=False, no_private_properties=False, *, - ignore_virtual_child_tables=False, + ignore_computed_child_tables=False, ) -> dict: doc = self.get_valid_dict(convert_dates_to_str=convert_dates_to_str, ignore_nulls=no_nulls) doc["doctype"] = self.doctype table_fieldnames = ( - self._non_virtual_table_fieldnames if ignore_virtual_child_tables else self._table_fieldnames + self._non_computed_table_fieldnames if ignore_computed_child_tables else self._table_fieldnames ) for fieldname in table_fieldnames: children = getattr(self, fieldname, None) or [] @@ -811,7 +795,7 @@ class BaseDocument: """Raw update parent + children DOES NOT VALIDATE AND CALL TRIGGERS""" self.db_update() - for fieldname in self._non_virtual_table_fieldnames: + for fieldname in self._non_computed_table_fieldnames: for doc in self.get(fieldname): doc.db_update() @@ -1531,9 +1515,22 @@ def _filter(data, filters, limit=None): return out +CACHED_PROPERTIES = (prop for prop, value in vars(BaseDocument).items() if isinstance(value, cached_property)) + UNPICKLABLE_KEYS = frozenset( ( "_parent_doc", - *(prop for prop, value in vars(BaseDocument).items() if isinstance(value, cached_property)), + *CACHED_PROPERTIES, + ) +) + +RESERVED_KEYWORDS = frozenset( + ( + "doctype", + "flags", + "_parent_doc", + "_doc_before_save", + "dont_update_if_missing", + *CACHED_PROPERTIES, ) ) diff --git a/frappe/model/document.py b/frappe/model/document.py index dd412dbef1..95c9301741 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -431,7 +431,7 @@ class Document(BaseDocument): for d in self.get_all_children(): d.db_insert() - self.reset_virtual_child_tables() + self.reset_computed_child_tables() self.run_method("after_insert") self.flags.in_insert = True @@ -537,7 +537,7 @@ class Document(BaseDocument): self.db_update() self.update_children() - self.reset_virtual_child_tables() + self.reset_computed_child_tables() self.run_post_save_methods() # clear unsaved flag @@ -616,9 +616,9 @@ class Document(BaseDocument): d: Document d.db_update() - def reset_virtual_child_tables(self): - """Reset virtual child tables so that they are reloaded next time""" - for df in self.meta.get_table_fields(ignore_virtual=False): + def reset_computed_child_tables(self): + """Reset computed child tables so that they are reloaded next time""" + for df in self.meta.get_table_fields(include_computed=True): if df.is_virtual: self.__dict__.pop(df.fieldname, None) @@ -886,7 +886,7 @@ class Document(BaseDocument): return all_fields = self.meta.fields.copy() - for table_field in self.meta.get_table_fields(ignore_virtual=False): + for table_field in self.meta.get_table_fields(include_computed=True): all_fields += frappe.get_meta(table_field.options).fields or [] if all(df.permlevel == 0 for df in all_fields): @@ -902,7 +902,7 @@ class Document(BaseDocument): # hasattr might return True for class attribute which can't be delattr-ed. continue - for table_field in self.meta.get_table_fields(ignore_virtual=False): + for table_field in self.meta.get_table_fields(include_computed=True): for df in frappe.get_meta(table_field.options).fields or []: if df.permlevel and df.permlevel not in has_access_to: for child in self.get(table_field.fieldname) or []: @@ -1115,11 +1115,11 @@ class Document(BaseDocument): msg = ", ".join(each[2] for each in cancelled_links) frappe.throw(_("Cannot link cancelled document: {0}").format(msg), frappe.CancelledLinkError) - def get_all_children(self, parenttype=None, *, ignore_virtual=True) -> list["Document"]: + def get_all_children(self, parenttype=None, *, include_computed=False) -> list["Document"]: """Return all children documents from **Table** type fields in a list.""" children = [] - table_fieldnames = self._non_virtual_table_fieldnames if ignore_virtual else self._table_fieldnames + table_fieldnames = self._table_fieldnames if include_computed else self._non_computed_table_fieldnames for fieldname, child_doctype in table_fieldnames.items(): if parenttype and child_doctype != parenttype: @@ -1321,7 +1321,7 @@ class Document(BaseDocument): return frappe.clear_last_message() - for fieldname in self._non_virtual_table_fieldnames: + for fieldname in self._non_computed_table_fieldnames: for row in self.get(fieldname): row._doc_before_save = next( (d for d in (self._doc_before_save.get(fieldname) or []) if d.name == row.name), None diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 1fd4012973..39e1f68ec2 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -221,8 +221,8 @@ class Meta(Document): return set_only_once_fields - def get_table_fields(self, ignore_virtual=True): - return self._non_virtual_table_fields if ignore_virtual else self._table_fields + def get_table_fields(self, include_computed=False): + return self._table_fields if include_computed else self._non_computed_table_fields def get_global_search_fields(self): """Return list of fields with `in_global_search` set and `name` if set.""" @@ -490,16 +490,14 @@ class Meta(Document): def _table_fields(self): if self.name == "DocType": return DOCTYPE_TABLE_FIELDS + return self.get("fields", {"fieldtype": ["in", table_fields]}) @cached_property - def _non_virtual_table_fields(self): + def _non_computed_table_fields(self): if self.name == "DocType": return self._table_fields - if self.get("is_virtual"): - return [] - return self.get("fields", {"fieldtype": ["in", table_fields], "is_virtual": 0}) @cached_property @@ -507,15 +505,15 @@ class Meta(Document): return {field.fieldname: field.options for field in self._table_fields} @cached_property - def _non_virtual_table_doctypes(self): - return {field.fieldname: field.options for field in self._non_virtual_table_fields} + def _non_computed_table_doctypes(self): + return {field.fieldname: field.options for field in self._non_computed_table_fields} def init_field_caches(self): self._fields self._table_fields - self._non_virtual_table_fields + self._non_computed_table_fields self._table_doctypes - self._non_virtual_table_doctypes + self._non_computed_table_doctypes def sort_fields(self): """ diff --git a/frappe/modules/export_file.py b/frappe/modules/export_file.py index 2cf19094d3..bebee6cfb9 100644 --- a/frappe/modules/export_file.py +++ b/frappe/modules/export_file.py @@ -33,7 +33,7 @@ def export_to_files(record_list=None, record_module=None, verbose=0, create_init def write_document_file(doc, record_module=None, create_init=True, folder_name=None): - doc_export = doc.as_dict(no_nulls=True, ignore_virtual_child_tables=True) + doc_export = doc.as_dict(no_nulls=True, ignore_computed_child_tables=True) doc.run_method("before_export", doc_export) doc_export = strip_default_fields(doc, doc_export) diff --git a/frappe/permissions.py b/frappe/permissions.py index d00d556fa8..2ae9d25d13 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -803,7 +803,7 @@ def has_child_permission( if parent_meta.istable or not ( valid_parentfields := [ df.fieldname - for df in parent_meta.get_table_fields(ignore_virtual=False) + for df in parent_meta.get_table_fields(include_computed=True) if df.options == child_doctype ] ): diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index e649857fe8..667ea2d5c3 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1866,7 +1866,7 @@ frappe.ui.form.Form = class FrappeForm { // returns list of children that are selected. returns [parentfield, name] for each var selected = {}, me = this; - frappe.meta.get_table_fields(this.doctype, false).forEach(function (df) { + frappe.meta.get_table_fields(this.doctype, true).forEach(function (df) { // handle TableMultiselect child fields let _selected = []; @@ -1887,7 +1887,7 @@ frappe.ui.form.Form = class FrappeForm { if (frappe.meta.docfield_map[this.doctype][fieldname]) { doctype = this.doctype; } else { - frappe.meta.get_table_fields(this.doctype, false).every(function (df) { + frappe.meta.get_table_fields(this.doctype, true).every(function (df) { if (frappe.meta.docfield_map[df.options][fieldname]) { doctype = df.options; return false; diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index 08c6c54517..58291e2924 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -149,13 +149,13 @@ $.extend(frappe.meta, { return docfield_map && docfield_map[fn]; }, - get_table_fields: function (dt, ignore_virtual = true) { + get_table_fields: function (dt, include_computed = false) { return $.map(frappe.meta.docfield_list[dt], function (d) { if (!frappe.model.table_fields.includes(d.fieldtype)) { return null; } - if (ignore_virtual && d.is_virtual) { + if (!include_computed && d.is_virtual) { return null; } @@ -172,7 +172,7 @@ $.extend(frappe.meta, { // found in parent out = doctype; } else { - frappe.meta.get_table_fields(doctype).every(function (d) { + frappe.meta.get_table_fields(doctype, true).every(function (d) { if ( frappe.meta.has_field(d.options, key) || frappe.model.child_table_field_list.includes(key) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 999c41d166..a42efe679b 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -2150,7 +2150,7 @@ def get_filter(doctype: str, filters: FilterSignature, filters_config=None) -> " meta = frappe.get_meta(f.doctype) if not meta.has_field(f.fieldname): # try and match the doctype name from child tables - for df in meta.get_table_fields(ignore_virtual=False): + for df in meta.get_table_fields(include_computed=True): if frappe.get_meta(df.options).has_field(f.fieldname): f.doctype = df.options break diff --git a/frappe/www/printview.py b/frappe/www/printview.py index 0c275f34e8..3913465b3a 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -308,7 +308,7 @@ def set_title_values_for_link_and_dynamic_link_fields( def set_title_values_for_table_and_multiselect_fields(meta: "Meta", doc: "Document") -> None: - for field in meta.get_table_fields(ignore_virtual=False): + for field in meta.get_table_fields(include_computed=True): if not doc.get(field.fieldname): continue