seitime-frappe/frappe/desk/form/linked_with.py
Faris Ansari 3b86f16b73 New Control: Table MultiSelect (#6675)
* feat(control): Add Table MultiSelect control

* fix: Use btn-group instead of span

* fix: Remove functionality

* fix: Add 'Table MultiSelect' to Field doctypes

* fix: Replace usage of string 'Table' with array `table_fields`

* fix: Use internal array to store values instead of building from HTML elements

* fix(style): Add semicolon

* fix: Read only mode and click to navigate to form

* style: indent

* fix: fallback to empty array

* fix: Add formatters in js and py

* style: missing semicolon

* fix: Add docfield validation
2018-12-28 15:36:59 +05:30

206 lines
No EOL
7 KiB
Python

# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe, json
from frappe.model.meta import is_single
from frappe.modules import load_doctype_module
import frappe.desk.form.meta
import frappe.desk.form.load
from six import string_types
from collections import defaultdict
@frappe.whitelist()
def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
if isinstance(linkinfo, string_types):
# additional fields are added in linkinfo
linkinfo = json.loads(linkinfo)
results = {}
if not linkinfo:
return results
if for_doctype:
links = frappe.get_doc(doctype, name).get_link_filters(for_doctype)
if links:
linkinfo = links
if for_doctype in linkinfo:
# only get linked with for this particular doctype
linkinfo = { for_doctype: linkinfo.get(for_doctype) }
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
link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt)
linkmeta = link_meta_bundle[0]
if not linkmeta.get("issingle"):
fields = [d.fieldname for d in linkmeta.get("fields", {
"in_list_view": 1,
"fieldtype": ["not in", ("Image", "HTML", "Button") + frappe.model.table_fields]
})] + ["name", "modified", "docstatus"]
if link.get("add_fields"):
fields += link["add_fields"]
fields = ["`tab{dt}`.`{fn}`".format(dt=dt, fn=sf.strip()) for sf in fields if sf
and "`tab" not in sf]
try:
if link.get("filters"):
ret = frappe.get_list(doctype=dt, fields=fields, filters=link.get("filters"))
elif link.get("get_parent"):
if me and me.parent and me.parenttype == dt:
ret = frappe.get_list(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")]
# dynamic link
if link.get("doctype_fieldname"):
filters.append([link.get('child_doctype'), link.get("doctype_fieldname"), "=", doctype])
ret = frappe.get_list(doctype=dt, fields=fields, filters=filters, or_filters=or_filters, distinct=True)
else:
link_fieldnames = link.get("fieldname")
if link_fieldnames:
if isinstance(link_fieldnames, string_types): link_fieldnames = [link_fieldnames]
or_filters = [[dt, fieldname, '=', name] for fieldname in link_fieldnames]
# dynamic link
if link.get("doctype_fieldname"):
filters.append([dt, link.get("doctype_fieldname"), "=", doctype])
ret = frappe.get_list(doctype=dt, fields=fields, filters=filters, or_filters=or_filters)
else:
ret = None
except frappe.PermissionError:
if frappe.local.message_log:
frappe.local.message_log.pop()
continue
if ret:
results[dt] = ret
return results
@frappe.whitelist()
def get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False):
"""add list of doctypes this doctype is 'linked' with.
Example, for Customer:
{"Address": {"fieldname": "customer"}..}
"""
if(without_ignore_user_permissions_enabled):
return frappe.cache().hget("linked_doctypes_without_ignore_user_permissions_enabled",
doctype, lambda: _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled))
else:
return frappe.cache().hget("linked_doctypes", doctype, lambda: _get_linked_doctypes(doctype))
def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False):
ret = {}
# find fields where this doctype is linked
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]]
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)
links+= frappe.get_all("Custom Field", fields=["dt"], filters=filters)
for dt, in links:
if dt in ret: continue
ret[dt] = {"get_parent": True}
for dt in list(ret):
try:
doctype_module = load_doctype_module(dt)
except (ImportError, KeyError):
# in case of Custom DocType
# or in case of module rename eg. (Schools -> Education)
continue
if getattr(doctype_module, "exclude_from_linked_with", False):
del ret[dt]
return ret
def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
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)
ret = {}
if not links: return ret
links_dict = defaultdict(list)
for doctype, fieldname in links:
links_dict[doctype].append(fieldname)
for doctype_name in links_dict:
ret[doctype_name] = { "fieldname": links_dict.get(doctype_name) }
table_doctypes = frappe.get_all("DocType", filters=[["istable", "=", "1"], ["name", "in", tuple(links_dict)]])
child_filters = [['fieldtype','in', frappe.model.table_fields], ['options', 'in', tuple(doctype.name for doctype in table_doctypes)]]
if without_ignore_user_permissions_enabled: child_filters.append(['ignore_user_permissions', '!=', 1])
# find out if linked in a child table
for parent, options in frappe.get_all("DocField", fields=["parent", "options"], filters=child_filters, as_list=1):
ret[parent] = { "child_doctype": options, "fieldname": links_dict[options]}
if options in ret: del ret[options]
return ret
def get_dynamic_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
ret = {}
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)
for df in links:
if is_single(df.doctype): continue
# optimized to get both link exists and parenttype
possible_link = frappe.db.sql("""select distinct `{doctype_fieldname}`, parenttype
from `tab{doctype}` where `{doctype_fieldname}`=%s""".format(**df), doctype, as_dict=True)
if not possible_link: continue
for d in possible_link:
# is child
if d.parenttype:
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
}
return ret