Merge pull request #15411 from phot0n/remove-unnecessary-fields
refactor: remove parent, parenttype and parentfield columns from non-child table doctypes
This commit is contained in:
commit
7f3ace8a4e
34 changed files with 305 additions and 199 deletions
|
|
@ -94,7 +94,8 @@ def handle():
|
|||
"data": doc.save().as_dict()
|
||||
})
|
||||
|
||||
if doc.parenttype and doc.parent:
|
||||
# check for child table doctype
|
||||
if doc.get("parenttype"):
|
||||
frappe.get_doc(doc.parenttype, doc.parent).save()
|
||||
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ def set_value(doctype, name, fieldname, value=None):
|
|||
:param fieldname: fieldname string or JSON / dict with key value pair
|
||||
:param value: value if fieldname is JSON / dict'''
|
||||
|
||||
if fieldname!="idx" and fieldname in frappe.model.default_fields:
|
||||
if fieldname in (frappe.model.default_fields + frappe.model.child_table_fields):
|
||||
frappe.throw(_("Cannot edit standard fields"))
|
||||
|
||||
if not value:
|
||||
|
|
@ -141,14 +141,15 @@ def set_value(doctype, name, fieldname, value=None):
|
|||
else:
|
||||
values = {fieldname: value}
|
||||
|
||||
doc = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True)
|
||||
if doc and doc.parent and doc.parenttype:
|
||||
# check for child table doctype
|
||||
if not frappe.get_meta(doctype).istable:
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
doc.update(values)
|
||||
else:
|
||||
doc = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True)
|
||||
doc = frappe.get_doc(doc.parenttype, doc.parent)
|
||||
child = doc.getone({"doctype": doctype, "name": name})
|
||||
child.update(values)
|
||||
else:
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
doc.update(values)
|
||||
|
||||
doc.save()
|
||||
|
||||
|
|
@ -162,10 +163,10 @@ def insert(doc=None):
|
|||
if isinstance(doc, str):
|
||||
doc = json.loads(doc)
|
||||
|
||||
if doc.get("parent") and doc.get("parenttype"):
|
||||
if doc.get("parenttype"):
|
||||
# inserting a child record
|
||||
parent = frappe.get_doc(doc.get("parenttype"), doc.get("parent"))
|
||||
parent.append(doc.get("parentfield"), doc)
|
||||
parent = frappe.get_doc(doc.parenttype, doc.parent)
|
||||
parent.append(doc.parentfield, doc)
|
||||
parent.save()
|
||||
return parent.as_dict()
|
||||
else:
|
||||
|
|
@ -186,10 +187,10 @@ def insert_many(docs=None):
|
|||
frappe.throw(_('Only 200 inserts allowed in one request'))
|
||||
|
||||
for doc in docs:
|
||||
if doc.get("parent") and doc.get("parenttype"):
|
||||
if doc.get("parenttype"):
|
||||
# inserting a child record
|
||||
parent = frappe.get_doc(doc.get("parenttype"), doc.get("parent"))
|
||||
parent.append(doc.get("parentfield"), doc)
|
||||
parent = frappe.get_doc(doc.parenttype, doc.parent)
|
||||
parent.append(doc.parentfield, doc)
|
||||
parent.save()
|
||||
out.append(parent.name)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -618,7 +618,7 @@ class Row:
|
|||
)
|
||||
|
||||
# remove standard fields and __islocal
|
||||
for key in frappe.model.default_fields + ("__islocal",):
|
||||
for key in frappe.model.default_fields + frappe.model.child_table_fields + ("__islocal",):
|
||||
doc.pop(key, None)
|
||||
|
||||
for col, value in zip(columns, values):
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ from frappe.cache_manager import clear_user_cache, clear_controller_cache
|
|||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import now, cint
|
||||
from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields, data_field_options
|
||||
from frappe.model import (
|
||||
no_value_fields, default_fields, table_fields, data_field_options, child_table_fields
|
||||
)
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.base_document import get_controller
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
|
|
@ -74,6 +76,7 @@ class DocType(Document):
|
|||
self.make_amendable()
|
||||
self.make_repeatable()
|
||||
self.validate_nestedset()
|
||||
self.validate_child_table()
|
||||
self.validate_website()
|
||||
self.ensure_minimum_max_attachment_limit()
|
||||
validate_links_table_fieldnames(self)
|
||||
|
|
@ -689,6 +692,22 @@ class DocType(Document):
|
|||
})
|
||||
self.nsm_parent_field = parent_field_name
|
||||
|
||||
def validate_child_table(self):
|
||||
if not self.get("istable") or self.is_new():
|
||||
# if the doctype is not a child table then return
|
||||
# if the doctype is a new doctype and also a child table then
|
||||
# don't move forward as it will be handled via schema
|
||||
return
|
||||
|
||||
self.add_child_table_fields()
|
||||
|
||||
def add_child_table_fields(self):
|
||||
from frappe.database.schema import add_column
|
||||
|
||||
add_column(self.name, "parent", "Data")
|
||||
add_column(self.name, "parenttype", "Data")
|
||||
add_column(self.name, "parentfield", "Data")
|
||||
|
||||
def get_max_idx(self):
|
||||
"""Returns the highest `idx`"""
|
||||
max_idx = frappe.db.sql("""select max(idx) from `tabDocField` where parent = %s""",
|
||||
|
|
@ -1016,7 +1035,7 @@ def validate_fields(meta):
|
|||
sort_fields = [d.split()[0] for d in meta.sort_field.split(',')]
|
||||
|
||||
for fieldname in sort_fields:
|
||||
if not fieldname in fieldname_list + list(default_fields):
|
||||
if fieldname not in (fieldname_list + list(default_fields) + list(child_table_fields)):
|
||||
frappe.throw(_("Sort field {0} must be a valid fieldname").format(fieldname),
|
||||
InvalidFieldNameError)
|
||||
|
||||
|
|
|
|||
|
|
@ -878,8 +878,9 @@ def extract_images_from_html(doc, content, is_private=False):
|
|||
else:
|
||||
filename = get_random_filename(content_type=mtype)
|
||||
|
||||
doctype = doc.parenttype if doc.parent else doc.doctype
|
||||
name = doc.parent or doc.name
|
||||
# attaching a file to a child table doc, attaches it to the parent doc
|
||||
doctype = doc.parenttype if doc.get("parent") else doc.doctype
|
||||
name = doc.get("parent") or doc.name
|
||||
|
||||
_file = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class Report(Document):
|
|||
delete_permanently=True)
|
||||
|
||||
def get_columns(self):
|
||||
return [d.as_dict(no_default_fields = True) for d in self.columns]
|
||||
return [d.as_dict(no_default_fields=True, no_child_table_fields=True) for d in self.columns]
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_doctype_roles(self):
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ def get_user_linked_doctypes(doctype, txt, searchfield, start, page_len, filters
|
|||
['DocType', 'read_only', '=', 0], ['DocType', 'name', 'like', '%{0}%'.format(txt)]]
|
||||
|
||||
doctypes = frappe.get_all('DocType', fields = ['`tabDocType`.`name`'], filters=filters,
|
||||
order_by = '`tabDocType`.`idx` desc', limit_start=start, limit_page_length=page_len, as_list=1)
|
||||
order_by='`tabDocType`.`idx` desc', limit_start=start, limit_page_length=page_len, as_list=1)
|
||||
|
||||
custom_dt_filters = [['Custom Field', 'dt', 'like', '%{0}%'.format(txt)],
|
||||
['Custom Field', 'options', '=', 'User'], ['Custom Field', 'fieldtype', '=', 'Link']]
|
||||
|
|
|
|||
|
|
@ -39,43 +39,3 @@ def get_todays_events(as_list=False):
|
|||
today = nowdate()
|
||||
events = get_events(today, today)
|
||||
return events if as_list else len(events)
|
||||
|
||||
def get_unseen_likes():
|
||||
"""Returns count of unseen likes"""
|
||||
|
||||
comment_doctype = DocType("Comment")
|
||||
return frappe.db.count(comment_doctype,
|
||||
filters=(
|
||||
(comment_doctype.comment_type == "Like")
|
||||
& (comment_doctype.modified >= Now() - Interval(years=1))
|
||||
& (comment_doctype.owner.notnull())
|
||||
& (comment_doctype.owner != frappe.session.user)
|
||||
& (comment_doctype.reference_owner == frappe.session.user)
|
||||
& (comment_doctype.seen == 0)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def get_unread_emails():
|
||||
"returns count of unread emails for a user"
|
||||
|
||||
communication_doctype = DocType("Communication")
|
||||
user_doctype = DocType("User")
|
||||
distinct_email_accounts = (
|
||||
frappe.qb.from_(user_doctype)
|
||||
.select(user_doctype.email_account)
|
||||
.where(user_doctype.parent == frappe.session.user)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
return frappe.db.count(communication_doctype,
|
||||
filters=(
|
||||
(communication_doctype.communication_type == "Communication")
|
||||
& (communication_doctype.communication_medium == "Email")
|
||||
& (communication_doctype.sent_or_received == "Received")
|
||||
& (communication_doctype.email_status.notin(["spam", "Trash"]))
|
||||
& (communication_doctype.email_account.isin(distinct_email_accounts))
|
||||
& (communication_doctype.modified >= Now() - Interval(years=1))
|
||||
& (communication_doctype.seen == 0)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ class Database(object):
|
|||
|
||||
OPTIONAL_COLUMNS = ["_user_tags", "_comments", "_assign", "_liked_by"]
|
||||
DEFAULT_SHORTCUTS = ['_Login', '__user', '_Full Name', 'Today', '__today', "now", "Now"]
|
||||
STANDARD_VARCHAR_COLUMNS = ('name', 'owner', 'modified_by', 'parent', 'parentfield', 'parenttype')
|
||||
DEFAULT_COLUMNS = ['name', 'creation', 'modified', 'modified_by', 'owner', 'docstatus', 'parent',
|
||||
'parentfield', 'parenttype', 'idx']
|
||||
STANDARD_VARCHAR_COLUMNS = ('name', 'owner', 'modified_by')
|
||||
DEFAULT_COLUMNS = ['name', 'creation', 'modified', 'modified_by', 'owner', 'docstatus', 'idx']
|
||||
CHILD_TABLE_COLUMNS = ('parent', 'parenttype', 'parentfield')
|
||||
MAX_WRITES_PER_TRANSACTION = 200_000
|
||||
|
||||
class InvalidColumnName(frappe.ValidationError): pass
|
||||
|
|
@ -435,11 +435,9 @@ class Database(object):
|
|||
|
||||
else:
|
||||
fields = fieldname
|
||||
if fieldname!="*":
|
||||
if fieldname != "*":
|
||||
if isinstance(fieldname, str):
|
||||
fields = [fieldname]
|
||||
else:
|
||||
fields = fieldname
|
||||
|
||||
if (filters is not None) and (filters!=doctype or doctype=="DocType"):
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -171,9 +171,6 @@ CREATE TABLE `tabDocType` (
|
|||
`modified_by` varchar(255) DEFAULT NULL,
|
||||
`owner` varchar(255) DEFAULT NULL,
|
||||
`docstatus` int(1) NOT NULL DEFAULT 0,
|
||||
`parent` varchar(255) DEFAULT NULL,
|
||||
`parentfield` varchar(255) DEFAULT NULL,
|
||||
`parenttype` varchar(255) DEFAULT NULL,
|
||||
`idx` int(8) NOT NULL DEFAULT 0,
|
||||
`search_fields` varchar(255) DEFAULT NULL,
|
||||
`issingle` int(1) NOT NULL DEFAULT 0,
|
||||
|
|
@ -228,8 +225,7 @@ CREATE TABLE `tabDocType` (
|
|||
`subject_field` varchar(255) DEFAULT NULL,
|
||||
`sender_field` varchar(255) DEFAULT NULL,
|
||||
`migration_hash` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`name`),
|
||||
KEY `parent` (`parent`)
|
||||
PRIMARY KEY (`name`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
--
|
||||
|
|
|
|||
|
|
@ -18,6 +18,17 @@ class MariaDBTable(DBTable):
|
|||
if index_defs:
|
||||
additional_definitions += ',\n'.join(index_defs) + ',\n'
|
||||
|
||||
# child table columns
|
||||
if self.meta.get("istable") or 0:
|
||||
additional_definitions += ',\n'.join(
|
||||
(
|
||||
f"parent varchar({varchar_len})",
|
||||
f"parentfield varchar({varchar_len})",
|
||||
f"parenttype varchar({varchar_len})",
|
||||
"index parent(parent)"
|
||||
)
|
||||
) + ',\n'
|
||||
|
||||
# create table
|
||||
query = f"""create table `{self.table_name}` (
|
||||
name varchar({varchar_len}) not null primary key,
|
||||
|
|
@ -26,12 +37,8 @@ class MariaDBTable(DBTable):
|
|||
modified_by varchar({varchar_len}),
|
||||
owner varchar({varchar_len}),
|
||||
docstatus int(1) not null default '0',
|
||||
parent varchar({varchar_len}),
|
||||
parentfield varchar({varchar_len}),
|
||||
parenttype varchar({varchar_len}),
|
||||
idx int(8) not null default '0',
|
||||
{additional_definitions}
|
||||
index parent(parent),
|
||||
index modified(modified))
|
||||
ENGINE={engine}
|
||||
ROW_FORMAT=DYNAMIC
|
||||
|
|
|
|||
|
|
@ -176,9 +176,6 @@ CREATE TABLE "tabDocType" (
|
|||
"modified_by" varchar(255) DEFAULT NULL,
|
||||
"owner" varchar(255) DEFAULT NULL,
|
||||
"docstatus" smallint NOT NULL DEFAULT 0,
|
||||
"parent" varchar(255) DEFAULT NULL,
|
||||
"parentfield" varchar(255) DEFAULT NULL,
|
||||
"parenttype" varchar(255) DEFAULT NULL,
|
||||
"idx" bigint NOT NULL DEFAULT 0,
|
||||
"search_fields" varchar(255) DEFAULT NULL,
|
||||
"issingle" smallint NOT NULL DEFAULT 0,
|
||||
|
|
|
|||
|
|
@ -5,26 +5,37 @@ from frappe.database.schema import DBTable, get_definition
|
|||
|
||||
class PostgresTable(DBTable):
|
||||
def create(self):
|
||||
add_text = ''
|
||||
add_text = ""
|
||||
|
||||
# columns
|
||||
column_defs = self.get_column_definitions()
|
||||
if column_defs: add_text += ',\n'.join(column_defs)
|
||||
if column_defs:
|
||||
add_text += ",\n".join(column_defs)
|
||||
|
||||
# child table columns
|
||||
if self.meta.get("istable") or 0:
|
||||
if column_defs:
|
||||
add_text += ",\n"
|
||||
|
||||
add_text += ",\n".join(
|
||||
(
|
||||
"parent varchar({varchar_len})",
|
||||
"parentfield varchar({varchar_len})",
|
||||
"parenttype varchar({varchar_len})"
|
||||
)
|
||||
)
|
||||
|
||||
# TODO: set docstatus length
|
||||
# create table
|
||||
frappe.db.sql("""create table `%s` (
|
||||
frappe.db.sql(("""create table `%s` (
|
||||
name varchar({varchar_len}) not null primary key,
|
||||
creation timestamp(6),
|
||||
modified timestamp(6),
|
||||
modified_by varchar({varchar_len}),
|
||||
owner varchar({varchar_len}),
|
||||
docstatus smallint not null default '0',
|
||||
parent varchar({varchar_len}),
|
||||
parentfield varchar({varchar_len}),
|
||||
parenttype varchar({varchar_len}),
|
||||
idx bigint not null default '0',
|
||||
%s)""".format(varchar_len=frappe.db.VARCHAR_LEN) % (self.table_name, add_text))
|
||||
%s)""" % (self.table_name, add_text)).format(varchar_len=frappe.db.VARCHAR_LEN))
|
||||
|
||||
self.create_indexes()
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -106,6 +106,9 @@ class DBTable:
|
|||
|
||||
columns = [frappe._dict({"fieldname": f, "fieldtype": "Data"}) for f in
|
||||
frappe.db.STANDARD_VARCHAR_COLUMNS]
|
||||
if self.meta.get("istable"):
|
||||
columns += [frappe._dict({"fieldname": f, "fieldtype": "Data"}) for f in
|
||||
frappe.db.CHILD_TABLE_COLUMNS]
|
||||
columns += self.columns.values()
|
||||
|
||||
for col in columns:
|
||||
|
|
@ -300,11 +303,12 @@ def validate_column_length(fieldname):
|
|||
def get_definition(fieldtype, precision=None, length=None):
|
||||
d = frappe.db.type_map.get(fieldtype)
|
||||
|
||||
# convert int to long int if the length of the int is greater than 11
|
||||
if fieldtype == "Int" and length and length > 11:
|
||||
d = frappe.db.type_map.get("Long Int")
|
||||
if not d:
|
||||
return
|
||||
|
||||
if not d: return
|
||||
if fieldtype == "Int" and length and length > 11:
|
||||
# convert int to long int if the length of the int is greater than 11
|
||||
d = frappe.db.type_map.get("Long Int")
|
||||
|
||||
coltype = d[0]
|
||||
size = d[1] if d[1] else None
|
||||
|
|
@ -315,19 +319,44 @@ def get_definition(fieldtype, precision=None, length=None):
|
|||
if fieldtype in ["Float", "Currency", "Percent"] and cint(precision) > 6:
|
||||
size = '21,9'
|
||||
|
||||
if coltype == "varchar" and length:
|
||||
size = length
|
||||
if length:
|
||||
if coltype == "varchar":
|
||||
size = length
|
||||
elif coltype == "int" and length < 11:
|
||||
# allow setting custom length for int if length provided is less than 11
|
||||
# NOTE: this will only be applicable for mariadb as frappe implements int
|
||||
# in postgres as bigint (as seen in type_map)
|
||||
size = length
|
||||
|
||||
if size is not None:
|
||||
coltype = "{coltype}({size})".format(coltype=coltype, size=size)
|
||||
|
||||
return coltype
|
||||
|
||||
def add_column(doctype, column_name, fieldtype, precision=None):
|
||||
def add_column(
|
||||
doctype,
|
||||
column_name,
|
||||
fieldtype,
|
||||
precision=None,
|
||||
length=None,
|
||||
default=None,
|
||||
not_null=False
|
||||
):
|
||||
if column_name in frappe.db.get_table_columns(doctype):
|
||||
# already exists
|
||||
return
|
||||
|
||||
frappe.db.commit()
|
||||
frappe.db.sql("alter table `tab%s` add column %s %s" % (doctype,
|
||||
column_name, get_definition(fieldtype, precision)))
|
||||
|
||||
query = "alter table `tab%s` add column %s %s" % (
|
||||
doctype,
|
||||
column_name,
|
||||
get_definition(fieldtype, precision, length)
|
||||
)
|
||||
|
||||
if not_null:
|
||||
query += " not null"
|
||||
if default:
|
||||
query += f" default '{default}'"
|
||||
|
||||
frappe.db.sql(query)
|
||||
|
|
|
|||
|
|
@ -148,8 +148,6 @@ def update_tags(doc, tags):
|
|||
"doctype": "Tag Link",
|
||||
"document_type": doc.doctype,
|
||||
"document_name": doc.name,
|
||||
"parenttype": doc.doctype,
|
||||
"parent": doc.name,
|
||||
"title": doc.get_title() or '',
|
||||
"tag": tag
|
||||
}).insert(ignore_permissions=True)
|
||||
|
|
|
|||
|
|
@ -389,8 +389,6 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
|
|||
else:
|
||||
return results
|
||||
|
||||
me = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True)
|
||||
|
||||
for dt, link in linkinfo.items():
|
||||
filters = []
|
||||
link["doctype"] = dt
|
||||
|
|
@ -413,11 +411,16 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
|
|||
ret = frappe.get_all(doctype=dt, fields=fields, filters=link.get("filters"))
|
||||
|
||||
elif link.get("get_parent"):
|
||||
if me and me.parent and me.parenttype == dt:
|
||||
ret = None
|
||||
|
||||
# check for child table
|
||||
if not frappe.get_meta(doctype).istable:
|
||||
continue
|
||||
|
||||
me = frappe.db.get_value(doctype, name, ["parenttype", "parent"], as_dict=True)
|
||||
if me and me.parenttype == dt:
|
||||
ret = frappe.get_all(doctype=dt, fields=fields,
|
||||
filters=[[dt, "name", '=', me.parent]])
|
||||
else:
|
||||
ret = None
|
||||
|
||||
elif link.get("child_doctype"):
|
||||
or_filters = [[link.get('child_doctype'), link_fieldnames, '=', name] for link_fieldnames in link.get("fieldname")]
|
||||
|
|
@ -473,7 +476,7 @@ def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False)
|
|||
ret.update(get_linked_fields(doctype, without_ignore_user_permissions_enabled))
|
||||
ret.update(get_dynamic_linked_fields(doctype, without_ignore_user_permissions_enabled))
|
||||
|
||||
filters=[['fieldtype', 'in', frappe.model.table_fields], ['options', '=', doctype]]
|
||||
filters = [['fieldtype', 'in', frappe.model.table_fields], ['options', '=', doctype]]
|
||||
if without_ignore_user_permissions_enabled: filters.append(['ignore_user_permissions', '!=', 1])
|
||||
# find links of parents
|
||||
links = frappe.get_all("DocField", fields=["parent as dt"], filters=filters)
|
||||
|
|
@ -498,12 +501,12 @@ def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False)
|
|||
|
||||
def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
|
||||
|
||||
filters=[['fieldtype','=', 'Link'], ['options', '=', doctype]]
|
||||
filters = [['fieldtype','=', 'Link'], ['options', '=', doctype]]
|
||||
if without_ignore_user_permissions_enabled: filters.append(['ignore_user_permissions', '!=', 1])
|
||||
|
||||
# find links of parents
|
||||
links = frappe.get_all("DocField", fields=["parent", "fieldname"], filters=filters, as_list=1)
|
||||
links+= frappe.get_all("Custom Field", fields=["dt as parent", "fieldname"], filters=filters, as_list=1)
|
||||
links += frappe.get_all("Custom Field", fields=["dt as parent", "fieldname"], filters=filters, as_list=1)
|
||||
|
||||
ret = {}
|
||||
|
||||
|
|
@ -529,34 +532,37 @@ def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
|
|||
def get_dynamic_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
|
||||
ret = {}
|
||||
|
||||
filters=[['fieldtype','=', 'Dynamic Link']]
|
||||
filters = [['fieldtype','=', 'Dynamic Link']]
|
||||
if without_ignore_user_permissions_enabled: filters.append(['ignore_user_permissions', '!=', 1])
|
||||
|
||||
# find dynamic links of parents
|
||||
links = frappe.get_all("DocField", fields=["parent as doctype", "fieldname", "options as doctype_fieldname"], filters=filters)
|
||||
links+= frappe.get_all("Custom Field", fields=["dt as doctype", "fieldname", "options as doctype_fieldname"], filters=filters)
|
||||
links += frappe.get_all("Custom Field", fields=["dt as doctype", "fieldname", "options as doctype_fieldname"], filters=filters)
|
||||
|
||||
for df in links:
|
||||
if is_single(df.doctype): continue
|
||||
|
||||
# optimized to get both link exists and parenttype
|
||||
possible_link = frappe.get_all(df.doctype, filters={df.doctype_fieldname: doctype},
|
||||
fields=['parenttype'], distinct=True)
|
||||
is_child = frappe.get_meta(df.doctype).istable
|
||||
possible_link = frappe.get_all(
|
||||
df.doctype,
|
||||
filters={df.doctype_fieldname: doctype},
|
||||
fields=["parenttype"] if is_child else None,
|
||||
distinct=True
|
||||
)
|
||||
|
||||
if not possible_link: continue
|
||||
|
||||
for d in possible_link:
|
||||
# is child
|
||||
if d.parenttype:
|
||||
if is_child:
|
||||
for d in possible_link:
|
||||
ret[d.parenttype] = {
|
||||
"child_doctype": df.doctype,
|
||||
"fieldname": [df.fieldname],
|
||||
"doctype_fieldname": df.doctype_fieldname
|
||||
}
|
||||
else:
|
||||
ret[df.doctype] = {
|
||||
"fieldname": [df.fieldname],
|
||||
"doctype_fieldname": df.doctype_fieldname
|
||||
}
|
||||
else:
|
||||
ret[df.doctype] = {
|
||||
"fieldname": [df.fieldname],
|
||||
"doctype_fieldname": df.doctype_fieldname
|
||||
}
|
||||
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import frappe, json
|
||||
import frappe.permissions
|
||||
from frappe.model.db_query import DatabaseQuery
|
||||
from frappe.model import default_fields, optional_fields
|
||||
from frappe.model import default_fields, optional_fields, child_table_fields
|
||||
from frappe import _
|
||||
from io import StringIO
|
||||
from frappe.core.doctype.access_log.access_log import make_access_log
|
||||
|
|
@ -156,7 +156,7 @@ def raise_invalid_field(fieldname):
|
|||
def is_standard(fieldname):
|
||||
if '.' in fieldname:
|
||||
parenttype, fieldname = get_parenttype_and_fieldname(fieldname, None)
|
||||
return fieldname in default_fields or fieldname in optional_fields
|
||||
return fieldname in default_fields or fieldname in optional_fields or fieldname in child_table_fields
|
||||
|
||||
def extract_fieldname(field):
|
||||
for text in (',', '/*', '#'):
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ def make_links(columns, data):
|
|||
if col.options and row.get(col.options):
|
||||
row[col.fieldname] = get_link_to_form(row[col.options], row[col.fieldname])
|
||||
elif col.fieldtype == "Currency":
|
||||
doc = frappe.get_doc(col.parent, doc_name) if doc_name and col.parent else None
|
||||
doc = frappe.get_doc(col.parent, doc_name) if doc_name and col.get("parent") else None
|
||||
# Pass the Document to get the currency based on docfield option
|
||||
row[col.fieldname] = frappe.format_value(row[col.fieldname], col, doc=doc)
|
||||
return columns, data
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import frappe
|
|||
import json
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.model import default_fields
|
||||
from frappe.model import default_fields, child_table_fields
|
||||
|
||||
class DocumentTypeMapping(Document):
|
||||
def validate(self):
|
||||
|
|
@ -14,7 +14,7 @@ class DocumentTypeMapping(Document):
|
|||
def validate_inner_mapping(self):
|
||||
meta = frappe.get_meta(self.local_doctype)
|
||||
for field_map in self.field_mapping:
|
||||
if field_map.local_fieldname not in default_fields:
|
||||
if field_map.local_fieldname not in (default_fields + child_table_fields):
|
||||
field = meta.get_field(field_map.local_fieldname)
|
||||
if not field:
|
||||
frappe.throw(_('Row #{0}: Invalid Local Fieldname').format(field_map.idx))
|
||||
|
|
|
|||
|
|
@ -90,11 +90,14 @@ default_fields = (
|
|||
'creation',
|
||||
'modified',
|
||||
'modified_by',
|
||||
'docstatus',
|
||||
'idx'
|
||||
)
|
||||
|
||||
child_table_fields = (
|
||||
'parent',
|
||||
'parentfield',
|
||||
'parenttype',
|
||||
'idx',
|
||||
'docstatus'
|
||||
'parenttype'
|
||||
)
|
||||
|
||||
optional_fields = (
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import frappe
|
||||
import datetime
|
||||
from frappe import _
|
||||
from frappe.model import default_fields, table_fields
|
||||
from frappe.model import default_fields, table_fields, child_table_fields
|
||||
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
|
||||
|
|
@ -104,6 +104,10 @@ class BaseDocument(object):
|
|||
"balance": 42000
|
||||
})
|
||||
"""
|
||||
|
||||
# QUESTION: why do we need the 1st for loop?
|
||||
# we're essentially setting the values in d, in the 2nd for loop (?)
|
||||
|
||||
# first set default field values of base document
|
||||
for key in default_fields:
|
||||
if key in d:
|
||||
|
|
@ -208,7 +212,10 @@ class BaseDocument(object):
|
|||
raise ValueError
|
||||
|
||||
def remove(self, doc):
|
||||
self.get(doc.parentfield).remove(doc)
|
||||
# Usage: from the parent doc, pass the child table doc
|
||||
# to remove that child doc from the child table, thus removing it from the parent doc
|
||||
if doc.get("parentfield"):
|
||||
self.get(doc.parentfield).remove(doc)
|
||||
|
||||
def _init_child(self, value, key):
|
||||
if not self.doctype:
|
||||
|
|
@ -318,12 +325,19 @@ class BaseDocument(object):
|
|||
def docstatus(self, value):
|
||||
self.__dict__["docstatus"] = DocStatus(cint(value))
|
||||
|
||||
def as_dict(self, no_nulls=False, no_default_fields=False, convert_dates_to_str=False):
|
||||
def as_dict(self, no_nulls=False, no_default_fields=False, convert_dates_to_str=False, no_child_table_fields=False):
|
||||
doc = self.get_valid_dict(convert_dates_to_str=convert_dates_to_str)
|
||||
doc["doctype"] = self.doctype
|
||||
for df in self.meta.get_table_fields():
|
||||
children = self.get(df.fieldname) or []
|
||||
doc[df.fieldname] = [d.as_dict(convert_dates_to_str=convert_dates_to_str, no_nulls=no_nulls, no_default_fields=no_default_fields) for d in children]
|
||||
doc[df.fieldname] = [
|
||||
d.as_dict(
|
||||
convert_dates_to_str=convert_dates_to_str,
|
||||
no_nulls=no_nulls,
|
||||
no_default_fields=no_default_fields,
|
||||
no_child_table_fields=no_child_table_fields
|
||||
) for d in children
|
||||
]
|
||||
|
||||
if no_nulls:
|
||||
for k in list(doc):
|
||||
|
|
@ -335,6 +349,11 @@ class BaseDocument(object):
|
|||
if k in default_fields:
|
||||
del doc[k]
|
||||
|
||||
if no_child_table_fields:
|
||||
for k in list(doc):
|
||||
if k in child_table_fields:
|
||||
del doc[k]
|
||||
|
||||
for key in ("_user_tags", "__islocal", "__onload", "_liked_by", "__run_link_triggers", "__unsaved"):
|
||||
if self.get(key):
|
||||
doc[key] = self.get(key)
|
||||
|
|
@ -514,12 +533,12 @@ class BaseDocument(object):
|
|||
if df.fieldtype in table_fields:
|
||||
return "{}: {}: {}".format(_("Error"), _("Data missing in table"), _(df.label))
|
||||
|
||||
elif self.parentfield:
|
||||
# check if parentfield exists (only applicable for child table doctype)
|
||||
elif self.get("parentfield"):
|
||||
return "{}: {} {} #{}: {}: {}".format(_("Error"), frappe.bold(_(self.doctype)),
|
||||
_("Row"), self.idx, _("Value missing for"), _(df.label))
|
||||
|
||||
else:
|
||||
return _("Error: Value missing for {0}: {1}").format(_(df.parent), _(df.label))
|
||||
return _("Error: Value missing for {0}: {1}").format(_(df.parent), _(df.label))
|
||||
|
||||
missing = []
|
||||
|
||||
|
|
@ -538,10 +557,11 @@ class BaseDocument(object):
|
|||
def get_invalid_links(self, is_submittable=False):
|
||||
"""Returns list of invalid links and also updates fetch values if not set"""
|
||||
def get_msg(df, docname):
|
||||
if self.parentfield:
|
||||
# check if parentfield exists (only applicable for child table doctype)
|
||||
if self.get("parentfield"):
|
||||
return "{} #{}: {}: {}".format(_("Row"), self.idx, _(df.label), docname)
|
||||
else:
|
||||
return "{}: {}".format(_(df.label), docname)
|
||||
|
||||
return "{}: {}".format(_(df.label), docname)
|
||||
|
||||
invalid_links = []
|
||||
cancelled_links = []
|
||||
|
|
@ -615,11 +635,8 @@ class BaseDocument(object):
|
|||
fetch_from_fieldname = df.fetch_from.split('.')[-1]
|
||||
value = values[fetch_from_fieldname]
|
||||
if df.fieldtype in ['Small Text', 'Text', 'Data']:
|
||||
if fetch_from_fieldname in default_fields:
|
||||
from frappe.model.meta import get_default_df
|
||||
fetch_from_df = get_default_df(fetch_from_fieldname)
|
||||
else:
|
||||
fetch_from_df = frappe.get_meta(doctype).get_field(fetch_from_fieldname)
|
||||
from frappe.model.meta import get_default_df
|
||||
fetch_from_df = get_default_df(fetch_from_fieldname) or frappe.get_meta(doctype).get_field(fetch_from_fieldname)
|
||||
|
||||
if not fetch_from_df:
|
||||
frappe.throw(
|
||||
|
|
@ -754,9 +771,9 @@ class BaseDocument(object):
|
|||
|
||||
|
||||
def throw_length_exceeded_error(self, df, max_length, value):
|
||||
if self.parentfield and self.idx:
|
||||
# check if parentfield exists (only applicable for child table doctype)
|
||||
if self.get("parentfield"):
|
||||
reference = _("{0}, Row {1}").format(_(self.doctype), self.idx)
|
||||
|
||||
else:
|
||||
reference = "{0} {1}".format(_(self.doctype), self.name)
|
||||
|
||||
|
|
@ -867,7 +884,7 @@ class BaseDocument(object):
|
|||
:param parentfield: If fieldname is in child table."""
|
||||
from frappe.model.meta import get_field_precision
|
||||
|
||||
if parentfield and not isinstance(parentfield, str):
|
||||
if parentfield and not isinstance(parentfield, str) and parentfield.get("parentfield"):
|
||||
parentfield = parentfield.parentfield
|
||||
|
||||
cache_key = parentfield or "main"
|
||||
|
|
@ -894,7 +911,7 @@ class BaseDocument(object):
|
|||
from frappe.utils.formatters import format_value
|
||||
|
||||
df = self.meta.get_field(fieldname)
|
||||
if not df and fieldname in default_fields:
|
||||
if not df:
|
||||
from frappe.model.meta import get_default_df
|
||||
df = get_default_df(fieldname)
|
||||
|
||||
|
|
|
|||
|
|
@ -222,32 +222,35 @@ def check_if_doc_is_linked(doc, method="Delete"):
|
|||
"""
|
||||
from frappe.model.rename_doc import get_link_fields
|
||||
link_fields = get_link_fields(doc.doctype)
|
||||
link_fields = [[lf['parent'], lf['fieldname'], lf['issingle']] for lf in link_fields]
|
||||
ignore_linked_doctypes = doc.get('ignore_linked_doctypes') or []
|
||||
|
||||
for lf in link_fields:
|
||||
link_dt, link_field, issingle = lf['parent'], lf['fieldname'], lf['issingle']
|
||||
|
||||
for link_dt, link_field, issingle in link_fields:
|
||||
if not issingle:
|
||||
for item in frappe.db.get_values(link_dt, {link_field:doc.name},
|
||||
["name", "parent", "parenttype", "docstatus"], as_dict=True):
|
||||
linked_doctype = item.parenttype if item.parent else link_dt
|
||||
fields = ["name", "docstatus"]
|
||||
if frappe.get_meta(link_dt).istable:
|
||||
fields.extend(["parent", "parenttype"])
|
||||
|
||||
ignore_linked_doctypes = doc.get('ignore_linked_doctypes') or []
|
||||
for item in frappe.db.get_values(link_dt, {link_field:doc.name}, fields , as_dict=True):
|
||||
# available only in child table cases
|
||||
item_parent = getattr(item, "parent", None)
|
||||
linked_doctype = item.parenttype if item_parent else link_dt
|
||||
|
||||
if linked_doctype in doctypes_to_skip or (linked_doctype in ignore_linked_doctypes and method == 'Cancel'):
|
||||
# don't check for communication and todo!
|
||||
continue
|
||||
|
||||
if not item:
|
||||
continue
|
||||
elif method != "Delete" and (method != "Cancel" or item.docstatus != 1):
|
||||
if method != "Delete" and (method != "Cancel" or item.docstatus != 1):
|
||||
# don't raise exception if not
|
||||
# linked to a non-cancelled doc when deleting or to a submitted doc when cancelling
|
||||
continue
|
||||
elif link_dt == doc.doctype and (item.parent or item.name) == doc.name:
|
||||
elif link_dt == doc.doctype and (item_parent or item.name) == doc.name:
|
||||
# don't raise exception if not
|
||||
# linked to same item or doc having same name as the item
|
||||
continue
|
||||
else:
|
||||
reference_docname = item.parent or item.name
|
||||
reference_docname = item_parent or item.name
|
||||
raise_link_exists_exception(doc, linked_doctype, reference_docname)
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -527,7 +527,7 @@ class Document(BaseDocument):
|
|||
|
||||
def _validate_non_negative(self):
|
||||
def get_msg(df):
|
||||
if self.parentfield:
|
||||
if self.get("parentfield"):
|
||||
return "{} {} #{}: {} {}".format(frappe.bold(_(self.doctype)),
|
||||
_("Row"), self.idx, _("Value cannot be negative for"), frappe.bold(_(df.label)))
|
||||
else:
|
||||
|
|
@ -1202,7 +1202,7 @@ class Document(BaseDocument):
|
|||
if not frappe.compare(val1, condition, val2):
|
||||
label = doc.meta.get_label(fieldname)
|
||||
condition_str = error_condition_map.get(condition, condition)
|
||||
if doc.parentfield:
|
||||
if doc.get("parentfield"):
|
||||
msg = _("Incorrect value in row {0}: {1} must be {2} {3}").format(doc.idx, label, condition_str, val2)
|
||||
else:
|
||||
msg = _("Incorrect value: {0} must be {1} {2}").format(label, condition_str, val2)
|
||||
|
|
@ -1226,7 +1226,7 @@ class Document(BaseDocument):
|
|||
doc.meta.get("fields", {"fieldtype": ["in", ["Currency", "Float", "Percent"]]}))
|
||||
|
||||
for fieldname in fieldnames:
|
||||
doc.set(fieldname, flt(doc.get(fieldname), self.precision(fieldname, doc.parentfield)))
|
||||
doc.set(fieldname, flt(doc.get(fieldname), self.precision(fieldname, doc.get("parentfield"))))
|
||||
|
||||
def get_url(self):
|
||||
"""Returns Desk URL for this document."""
|
||||
|
|
@ -1379,9 +1379,11 @@ class Document(BaseDocument):
|
|||
doctype = self.__class__.__name__
|
||||
|
||||
docstatus = f" docstatus={self.docstatus}" if self.docstatus else ""
|
||||
parent = f" parent={self.parent}" if self.parent else ""
|
||||
repr_str = f"<{doctype}: {name}{docstatus}"
|
||||
|
||||
return f"<{doctype}: {name}{docstatus}{parent}>"
|
||||
if not hasattr(self, "parent"):
|
||||
return repr_str + ">"
|
||||
return f"{repr_str} parent={self.parent}>"
|
||||
|
||||
def __str__(self):
|
||||
name = self.name or "unsaved"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import json
|
|||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model import default_fields, table_fields
|
||||
from frappe.model import default_fields, table_fields, child_table_fields
|
||||
from frappe.utils import cstr
|
||||
|
||||
|
||||
|
|
@ -149,6 +149,7 @@ def map_fields(source_doc, target_doc, table_map, source_parent):
|
|||
no_copy_fields = set([d.fieldname for d in source_doc.meta.get("fields") if (d.no_copy==1 or d.fieldtype in table_fields)]
|
||||
+ [d.fieldname for d in target_doc.meta.get("fields") if (d.no_copy==1 or d.fieldtype in table_fields)]
|
||||
+ list(default_fields)
|
||||
+ list(child_table_fields)
|
||||
+ list(table_map.get("field_no_map", [])))
|
||||
|
||||
for df in target_doc.meta.get("fields"):
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from datetime import datetime
|
|||
import click
|
||||
import frappe, json, os
|
||||
from frappe.utils import cstr, cint, cast
|
||||
from frappe.model import default_fields, no_value_fields, optional_fields, data_fieldtypes, table_fields
|
||||
from frappe.model import default_fields, no_value_fields, optional_fields, data_fieldtypes, table_fields, child_table_fields
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.base_document import BaseDocument
|
||||
from frappe.modules import load_doctype_module
|
||||
|
|
@ -191,6 +191,8 @@ class Meta(Document):
|
|||
else:
|
||||
self._valid_columns = self.default_fields + \
|
||||
[df.fieldname for df in self.get("fields") if df.fieldtype in data_fieldtypes]
|
||||
if self.istable:
|
||||
self._valid_columns += list(child_table_fields)
|
||||
|
||||
return self._valid_columns
|
||||
|
||||
|
|
@ -520,7 +522,7 @@ class Meta(Document):
|
|||
'''add `links` child table in standard link dashboard format'''
|
||||
dashboard_links = []
|
||||
|
||||
if hasattr(self, 'links') and self.links:
|
||||
if getattr(self, 'links', None):
|
||||
dashboard_links.extend(self.links)
|
||||
|
||||
if not data.transactions:
|
||||
|
|
@ -625,9 +627,9 @@ def get_field_currency(df, doc=None):
|
|||
frappe.local.field_currency = frappe._dict()
|
||||
|
||||
if not (frappe.local.field_currency.get((doc.doctype, doc.name), {}).get(df.fieldname) or
|
||||
(doc.parent and frappe.local.field_currency.get((doc.doctype, doc.parent), {}).get(df.fieldname))):
|
||||
(doc.get("parent") and frappe.local.field_currency.get((doc.doctype, doc.parent), {}).get(df.fieldname))):
|
||||
|
||||
ref_docname = doc.parent or doc.name
|
||||
ref_docname = doc.get("parent") or doc.name
|
||||
|
||||
if ":" in cstr(df.get("options")):
|
||||
split_opts = df.get("options").split(":")
|
||||
|
|
@ -635,7 +637,7 @@ def get_field_currency(df, doc=None):
|
|||
currency = frappe.get_cached_value(split_opts[0], doc.get(split_opts[1]), split_opts[2])
|
||||
else:
|
||||
currency = doc.get(df.get("options"))
|
||||
if doc.parent:
|
||||
if doc.get("parenttype"):
|
||||
if currency:
|
||||
ref_docname = doc.name
|
||||
else:
|
||||
|
|
@ -648,7 +650,7 @@ def get_field_currency(df, doc=None):
|
|||
.setdefault(df.fieldname, currency)
|
||||
|
||||
return frappe.local.field_currency.get((doc.doctype, doc.name), {}).get(df.fieldname) or \
|
||||
(doc.parent and frappe.local.field_currency.get((doc.doctype, doc.parent), {}).get(df.fieldname))
|
||||
(doc.get("parent") and frappe.local.field_currency.get((doc.doctype, doc.parent), {}).get(df.fieldname))
|
||||
|
||||
def get_field_precision(df, doc=None, currency=None):
|
||||
"""get precision based on DocField options and fieldvalue in doc"""
|
||||
|
|
@ -669,19 +671,25 @@ def get_field_precision(df, doc=None, currency=None):
|
|||
|
||||
|
||||
def get_default_df(fieldname):
|
||||
if fieldname in default_fields:
|
||||
if fieldname in (default_fields + child_table_fields):
|
||||
if fieldname in ("creation", "modified"):
|
||||
return frappe._dict(
|
||||
fieldname = fieldname,
|
||||
fieldtype = "Datetime"
|
||||
)
|
||||
|
||||
else:
|
||||
elif fieldname in ("idx", "docstatus"):
|
||||
return frappe._dict(
|
||||
fieldname = fieldname,
|
||||
fieldtype = "Data"
|
||||
fieldtype = "Int"
|
||||
)
|
||||
|
||||
return frappe._dict(
|
||||
fieldname = fieldname,
|
||||
fieldtype = "Data"
|
||||
)
|
||||
|
||||
|
||||
def trim_tables(doctype=None, dry_run=False, quiet=False):
|
||||
"""
|
||||
Removes database fields that don't exist in the doctype (json or custom field). This may be needed
|
||||
|
|
@ -713,7 +721,7 @@ def trim_tables(doctype=None, dry_run=False, quiet=False):
|
|||
|
||||
def trim_table(doctype, dry_run=True):
|
||||
frappe.cache().hdel('table_columns', f"tab{doctype}")
|
||||
ignore_fields = default_fields + optional_fields
|
||||
ignore_fields = default_fields + optional_fields + child_table_fields
|
||||
columns = frappe.db.get_table_columns(doctype)
|
||||
fields = frappe.get_meta(doctype, cached=False).get_fieldnames_with_value()
|
||||
is_internal = lambda f: f not in ignore_fields and not f.startswith("_")
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ def strip_default_fields(doc, doc_export):
|
|||
|
||||
for df in doc.meta.get_table_fields():
|
||||
for d in doc_export.get(df.fieldname):
|
||||
for fieldname in frappe.model.default_fields:
|
||||
for fieldname in (frappe.model.default_fields + frappe.model.child_table_fields):
|
||||
if fieldname in d:
|
||||
del d[fieldname]
|
||||
|
||||
|
|
|
|||
|
|
@ -119,7 +119,6 @@ execute:frappe.delete_doc_if_exists('DocType', 'GSuite Settings')
|
|||
execute:frappe.delete_doc_if_exists('DocType', 'GSuite Templates')
|
||||
execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Account')
|
||||
execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings')
|
||||
frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats
|
||||
frappe.patches.v12_0.remove_example_email_thread_notify
|
||||
execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders()
|
||||
frappe.patches.v12_0.set_correct_url_in_files
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.db.sql("""
|
||||
UPDATE
|
||||
`tabPrint Format`
|
||||
SET
|
||||
`tabPrint Format`.`parent`='',
|
||||
`tabPrint Format`.`parenttype`='',
|
||||
`tabPrint Format`.parentfield=''
|
||||
WHERE
|
||||
`tabPrint Format`.parent != ''
|
||||
OR `tabPrint Format`.parenttype != ''
|
||||
""")
|
||||
|
|
@ -10,8 +10,7 @@ $.extend(frappe.model, {
|
|||
layout_fields: ['Section Break', 'Column Break', 'Tab Break', 'Fold'],
|
||||
|
||||
std_fields_list: ['name', 'owner', 'creation', 'modified', 'modified_by',
|
||||
'_user_tags', '_comments', '_assign', '_liked_by', 'docstatus',
|
||||
'parent', 'parenttype', 'parentfield', 'idx'],
|
||||
'_user_tags', '_comments', '_assign', '_liked_by', 'docstatus', 'idx'],
|
||||
|
||||
core_doctypes_list: ['DocType', 'DocField', 'DocPerm', 'User', 'Role', 'Has Role',
|
||||
'Page', 'Module Def', 'Print Format', 'Report', 'Customize Form',
|
||||
|
|
|
|||
66
frappe/tests/test_child_table.py
Normal file
66
frappe/tests/test_child_table.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import frappe
|
||||
from frappe.model import child_table_fields
|
||||
|
||||
import unittest
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class TestChildTable(unittest.TestCase):
|
||||
def tearDown(self) -> None:
|
||||
try:
|
||||
frappe.delete_doc("DocType", self.doctype_name, force=1)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def test_child_table_doctype_creation_and_transitioning(self) -> None:
|
||||
'''
|
||||
This method tests the creation of child table doctype
|
||||
as well as it's transitioning from child table to normal and normal to child table doctype
|
||||
'''
|
||||
|
||||
self.doctype_name = "Test Newy Child Table"
|
||||
|
||||
try:
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "DocType",
|
||||
"name": self.doctype_name,
|
||||
"istable": 1,
|
||||
"custom": 1,
|
||||
"module": "Integrations",
|
||||
"fields": [{
|
||||
"label": "Some Field",
|
||||
"fieldname": "some_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"reqd": 1
|
||||
}]
|
||||
}).insert(ignore_permissions=True)
|
||||
except Exception:
|
||||
self.fail("Not able to create Child Table Doctype")
|
||||
|
||||
|
||||
for column in child_table_fields:
|
||||
self.assertTrue(frappe.db.has_column(self.doctype_name, column))
|
||||
|
||||
# check transitioning from child table to normal doctype
|
||||
doc.istable = 0
|
||||
try:
|
||||
doc.save(ignore_permissions=True)
|
||||
except Exception:
|
||||
self.fail("Not able to transition from Child Table Doctype to Normal Doctype")
|
||||
|
||||
self.check_valid_columns(self.assertFalse)
|
||||
|
||||
# check transitioning from normal to child table doctype
|
||||
doc.istable = 1
|
||||
try:
|
||||
doc.save(ignore_permissions=True)
|
||||
except Exception:
|
||||
self.fail("Not able to transition from Normal Doctype to Child Table Doctype")
|
||||
|
||||
self.check_valid_columns(self.assertTrue)
|
||||
|
||||
|
||||
def check_valid_columns(self, assertion_method: Callable) -> None:
|
||||
valid_columns = frappe.get_meta(self.doctype_name).get_valid_columns()
|
||||
for column in child_table_fields:
|
||||
assertion_method(column in valid_columns)
|
||||
|
|
@ -103,10 +103,7 @@ def get_other_fields_meta(meta):
|
|||
default_fields_map = {
|
||||
'name': ('Data', 0),
|
||||
'owner': ('Data', 0),
|
||||
'parent': ('Data', 0),
|
||||
'parentfield': ('Data', 0),
|
||||
'modified_by': ('Data', 0),
|
||||
'parenttype': ('Data', 0),
|
||||
'creation': ('Datetime', 0),
|
||||
'modified': ('Datetime', 0),
|
||||
'idx': ('Int', 8),
|
||||
|
|
@ -117,8 +114,12 @@ def get_other_fields_meta(meta):
|
|||
if meta.track_seen:
|
||||
optional_fields.append('_seen')
|
||||
|
||||
child_table_fields_map = {}
|
||||
if meta.istable:
|
||||
child_table_fields_map.update({field: ('Data', 0) for field in frappe.db.CHILD_TABLE_COLUMNS})
|
||||
|
||||
optional_fields_map = {field: ('Text', 0) for field in optional_fields}
|
||||
fields = dict(default_fields_map, **optional_fields_map)
|
||||
fields = dict(default_fields_map, **optional_fields_map, **child_table_fields_map)
|
||||
field_map = [frappe._dict({'fieldname': field, 'fieldtype': _type, 'length': _length}) for field, (_type, _length) in fields.items()]
|
||||
|
||||
return field_map
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@ import frappe
|
|||
from frappe.utils.data import *
|
||||
from frappe.utils.html_utils import sanitize_html
|
||||
|
||||
default_fields = ['doctype', 'name', 'owner', 'creation', 'modified', 'modified_by',
|
||||
'parent', 'parentfield', 'parenttype', 'idx', 'docstatus']
|
||||
|
||||
|
||||
def get_fullname(user=None):
|
||||
"""get the full name (first name + last name) of the user from User"""
|
||||
|
|
|
|||
|
|
@ -1362,7 +1362,7 @@ def get_filter(doctype: str, f: Union[Dict, List, Tuple], filters_config=None) -
|
|||
"fieldtype":
|
||||
}
|
||||
"""
|
||||
from frappe.model import default_fields, optional_fields
|
||||
from frappe.model import default_fields, optional_fields, child_table_fields
|
||||
|
||||
if isinstance(f, dict):
|
||||
key, value = next(iter(f.items()))
|
||||
|
|
@ -1400,7 +1400,7 @@ def get_filter(doctype: str, f: Union[Dict, List, Tuple], filters_config=None) -
|
|||
frappe.throw(frappe._("Operator must be one of {0}").format(", ".join(valid_operators)))
|
||||
|
||||
|
||||
if f.doctype and (f.fieldname not in default_fields + optional_fields):
|
||||
if f.doctype and (f.fieldname not in default_fields + optional_fields + child_table_fields):
|
||||
# verify fieldname belongs to the doctype
|
||||
meta = frappe.get_meta(f.doctype)
|
||||
if not meta.has_field(f.fieldname):
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class WebForm(WebsiteGenerator):
|
|||
|
||||
for prop in docfield_properties:
|
||||
if df.fieldtype==meta_df.fieldtype and prop not in ("idx",
|
||||
"reqd", "default", "description", "default", "options",
|
||||
"reqd", "default", "description", "options",
|
||||
"hidden", "read_only", "label"):
|
||||
df.set(prop, meta_df.get(prop))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue