fix: correct table_fields references
This commit is contained in:
parent
d93c177f45
commit
2c9c6c0fd5
11 changed files with 87 additions and 54 deletions
|
|
@ -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()
|
||||
table_fields = meta.get_table_fields(ignore_virtual=False)
|
||||
|
||||
for field in table_fields:
|
||||
if not doc.get(field.fieldname):
|
||||
|
|
|
|||
|
|
@ -61,14 +61,6 @@ TABLE_DOCTYPES_FOR_CHILD_TABLES = MappingProxyType({})
|
|||
|
||||
DOCTYPES_FOR_DOCTYPE = {"DocType", *TABLE_DOCTYPES_FOR_DOCTYPE.values()}
|
||||
|
||||
UNPICKLABLE_KEYS = (
|
||||
"meta",
|
||||
"permitted_fieldnames",
|
||||
"_parent_doc",
|
||||
"_weakref",
|
||||
"_table_fieldnames",
|
||||
)
|
||||
|
||||
|
||||
def _reduce_extended_instance(doc):
|
||||
"""Make extended class instances pickle-able.
|
||||
|
|
@ -458,6 +450,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.__init__(value)
|
||||
|
||||
__dict = child.__dict__
|
||||
|
|
@ -484,7 +477,14 @@ class BaseDocument:
|
|||
|
||||
return self.meta._table_doctypes
|
||||
|
||||
def _get_table_fields(self):
|
||||
@cached_property
|
||||
def _non_virtual_table_fieldnames(self) -> dict:
|
||||
if self.doctype in DOCTYPES_FOR_DOCTYPE:
|
||||
return self._table_fieldnames
|
||||
|
||||
return self.meta._non_virtual_table_doctypes
|
||||
|
||||
def _get_table_fields(self, ignore_virtual=True):
|
||||
"""
|
||||
To get table fields during Document init
|
||||
Meta.get_table_fields goes into recursion for special doctypes
|
||||
|
|
@ -497,7 +497,7 @@ class BaseDocument:
|
|||
if self.doctype in DOCTYPES_FOR_DOCTYPE:
|
||||
return ()
|
||||
|
||||
return self.meta.get_table_fields()
|
||||
return self.meta.get_table_fields(ignore_virtual=ignore_virtual)
|
||||
|
||||
def _evaluate_virtual_field_options(self, options):
|
||||
from frappe.utils.safe_exec import get_safe_globals
|
||||
|
|
@ -581,12 +581,12 @@ class BaseDocument:
|
|||
without worrying about whether or not they have values
|
||||
"""
|
||||
|
||||
if not self._table_fieldnames:
|
||||
if not self._non_virtual_table_fieldnames:
|
||||
return
|
||||
|
||||
__dict = self.__dict__
|
||||
|
||||
for fieldname in self._table_fieldnames:
|
||||
for fieldname in self._non_virtual_table_fieldnames:
|
||||
if __dict.get(fieldname) is None:
|
||||
__dict[fieldname] = []
|
||||
|
||||
|
|
@ -645,11 +645,16 @@ class BaseDocument:
|
|||
convert_dates_to_str=False,
|
||||
no_child_table_fields=False,
|
||||
no_private_properties=False,
|
||||
*,
|
||||
ignore_virtual_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
|
||||
|
||||
for fieldname in self._table_fieldnames:
|
||||
table_fieldnames = (
|
||||
self._non_virtual_table_fieldnames if ignore_virtual_child_tables else self._table_fieldnames
|
||||
)
|
||||
for fieldname in table_fieldnames:
|
||||
children = getattr(self, fieldname) or []
|
||||
doc[fieldname] = [
|
||||
d.as_dict(
|
||||
|
|
@ -806,7 +811,7 @@ class BaseDocument:
|
|||
"""Raw update parent + children
|
||||
DOES NOT VALIDATE AND CALL TRIGGERS"""
|
||||
self.db_update()
|
||||
for fieldname in self._table_fieldnames:
|
||||
for fieldname in self._non_virtual_table_fieldnames:
|
||||
for doc in self.get(fieldname):
|
||||
doc.db_update()
|
||||
|
||||
|
|
@ -1524,3 +1529,8 @@ def _filter(data, filters, limit=None):
|
|||
break
|
||||
|
||||
return out
|
||||
|
||||
|
||||
UNPICKLABLE_KEYS = frozenset(
|
||||
prop for prop, value in vars(BaseDocument).items() if isinstance(value, cached_property)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -276,6 +276,8 @@ class Document(BaseDocument):
|
|||
for fieldname, child_doctype in self._table_fieldnames.items():
|
||||
# Make sure not to query the DB for a child table, if it is a virtual one.
|
||||
if not is_doctype and is_virtual_doctype(child_doctype):
|
||||
# Remove cache so that the virtual field loads again
|
||||
self.__dict__.pop(fieldname, None)
|
||||
continue
|
||||
|
||||
if is_doctype:
|
||||
|
|
@ -876,7 +878,7 @@ class Document(BaseDocument):
|
|||
return
|
||||
|
||||
all_fields = self.meta.fields.copy()
|
||||
for table_field in self.meta.get_table_fields():
|
||||
for table_field in self.meta.get_table_fields(ignore_virtual=False):
|
||||
all_fields += frappe.get_meta(table_field.options).fields or []
|
||||
|
||||
if all(df.permlevel == 0 for df in all_fields):
|
||||
|
|
@ -892,7 +894,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():
|
||||
for table_field in self.meta.get_table_fields(ignore_virtual=False):
|
||||
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 []:
|
||||
|
|
@ -1105,12 +1107,13 @@ 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) -> list["Document"]:
|
||||
def get_all_children(self, parenttype=None, *, ignore_virtual=True) -> 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
|
||||
|
||||
for fieldname, child_doctype in self._table_fieldnames.items():
|
||||
for fieldname, child_doctype in table_fieldnames.items():
|
||||
if parenttype and child_doctype != parenttype:
|
||||
continue
|
||||
|
||||
|
|
@ -1310,7 +1313,7 @@ class Document(BaseDocument):
|
|||
|
||||
return frappe.clear_last_message()
|
||||
|
||||
for fieldname in self._table_fieldnames:
|
||||
for fieldname in self._non_virtual_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
|
||||
|
|
@ -1972,7 +1975,7 @@ def get_lazy_controller(doctype):
|
|||
|
||||
# Dynamically construct a class that subclasses LazyDocument and original controller.
|
||||
lazy_controller = type(f"Lazy{original_controller.__name__}", (LazyDocument, original_controller), {})
|
||||
for df in meta.get_table_fields(include_virtual=False):
|
||||
for df in meta.get_table_fields():
|
||||
setattr(lazy_controller, df.fieldname, LazyChildTable(df.fieldname, df.options))
|
||||
|
||||
lazy_controllers[doctype] = lazy_controller
|
||||
|
|
|
|||
|
|
@ -221,8 +221,8 @@ class Meta(Document):
|
|||
|
||||
return set_only_once_fields
|
||||
|
||||
def get_table_fields(self, include_virtual=True):
|
||||
return self._table_fields if include_virtual else self._non_virtual_table_fields
|
||||
def get_table_fields(self, ignore_virtual=True):
|
||||
return self._non_virtual_table_fields if ignore_virtual else self._table_fields
|
||||
|
||||
def get_global_search_fields(self):
|
||||
"""Return list of fields with `in_global_search` set and `name` if set."""
|
||||
|
|
@ -482,23 +482,40 @@ class Meta(Document):
|
|||
if get_datetime(recent_change) > add_to_date(None, days=-1 * LARGE_TABLE_RECENCY_THRESHOLD):
|
||||
self.is_large_table = True
|
||||
|
||||
def init_field_caches(self):
|
||||
# field map
|
||||
self._fields = {field.fieldname: field for field in self.fields}
|
||||
@cached_property
|
||||
def _fields(self):
|
||||
return {field.fieldname: field for field in self.fields}
|
||||
|
||||
# table fields
|
||||
@cached_property
|
||||
def _table_fields(self):
|
||||
if self.name == "DocType":
|
||||
self._table_fields = DOCTYPE_TABLE_FIELDS
|
||||
else:
|
||||
self._table_fields = self.get("fields", {"fieldtype": ["in", table_fields]})
|
||||
self._non_virtual_table_fields = (
|
||||
[]
|
||||
if self.get("is_virtual")
|
||||
else self.get("fields", {"fieldtype": ["in", table_fields], "is_virtual": 0})
|
||||
)
|
||||
return DOCTYPE_TABLE_FIELDS
|
||||
return self.get("fields", {"fieldtype": ["in", table_fields]})
|
||||
|
||||
# table fieldname: doctype map
|
||||
self._table_doctypes = {field.fieldname: field.options for field in self._table_fields}
|
||||
@cached_property
|
||||
def _non_virtual_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
|
||||
def _table_doctypes(self):
|
||||
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 init_field_caches(self):
|
||||
self._fields
|
||||
self._table_fields
|
||||
self._non_virtual_table_fields
|
||||
self._table_doctypes
|
||||
self._non_virtual_table_doctypes
|
||||
|
||||
def sort_fields(self):
|
||||
"""
|
||||
|
|
@ -961,14 +978,7 @@ def _update_field_order_based_on_insert_after(field_order, insert_after_map):
|
|||
field_order.extend(fields)
|
||||
|
||||
|
||||
CACHE_PROPERTIES = frozenset(
|
||||
(
|
||||
"_fields",
|
||||
"_table_fields",
|
||||
"_table_doctypes",
|
||||
*(prop for prop, value in vars(Meta).items() if isinstance(value, cached_property)),
|
||||
)
|
||||
)
|
||||
CACHE_PROPERTIES = frozenset(prop for prop, value in vars(Meta).items() if isinstance(value, cached_property))
|
||||
|
||||
|
||||
def _serialize(doc, no_nulls=False, *, is_child=False):
|
||||
|
|
|
|||
|
|
@ -416,7 +416,7 @@ def rename_doctype(doctype: str, old: str, new: str) -> None:
|
|||
|
||||
def update_child_docs(old: str, new: str, meta: "Meta") -> None:
|
||||
# update "parent"
|
||||
for df in meta.get_table_fields(include_virtual=False):
|
||||
for df in meta.get_table_fields():
|
||||
(
|
||||
frappe.qb.update(df.options)
|
||||
.set("parent", new)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
doc_export = doc.as_dict(no_nulls=True, ignore_virtual_child_tables=True)
|
||||
doc.run_method("before_export", doc_export)
|
||||
|
||||
doc_export = strip_default_fields(doc, doc_export)
|
||||
|
|
|
|||
|
|
@ -802,7 +802,9 @@ def has_child_permission(
|
|||
|
||||
if parent_meta.istable or not (
|
||||
valid_parentfields := [
|
||||
df.fieldname for df in parent_meta.get_table_fields() if df.options == child_doctype
|
||||
df.fieldname
|
||||
for df in parent_meta.get_table_fields(ignore_virtual=False)
|
||||
if df.options == child_doctype
|
||||
]
|
||||
):
|
||||
push_perm_check_log(
|
||||
|
|
|
|||
|
|
@ -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).forEach(function (df) {
|
||||
frappe.meta.get_table_fields(this.doctype, false).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).every(function (df) {
|
||||
frappe.meta.get_table_fields(this.doctype, false).every(function (df) {
|
||||
if (frappe.meta.docfield_map[df.options][fieldname]) {
|
||||
doctype = df.options;
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -149,9 +149,17 @@ $.extend(frappe.meta, {
|
|||
return docfield_map && docfield_map[fn];
|
||||
},
|
||||
|
||||
get_table_fields: function (dt) {
|
||||
get_table_fields: function (dt, ignore_virtual = true) {
|
||||
return $.map(frappe.meta.docfield_list[dt], function (d) {
|
||||
return frappe.model.table_fields.includes(d.fieldtype) ? d : null;
|
||||
if (!frappe.model.table_fields.includes(d.fieldtype)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ignore_virtual && d.is_virtual) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return d;
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
for df in meta.get_table_fields(ignore_virtual=False):
|
||||
if frappe.get_meta(df.options).has_field(f.fieldname):
|
||||
f.doctype = df.options
|
||||
break
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
for field in meta.get_table_fields(ignore_virtual=False):
|
||||
if not doc.get(field.fieldname):
|
||||
continue
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue