seitime-frappe/frappe/model/mapper.py
Gavin D'souza 3446026555 chore: Update header: license.txt => LICENSE
The license.txt file has been replaced with LICENSE for quite a while
now. INAL but it didn't seem accurate to say "hey, checkout license.txt
although there's no such file". Apart from this, there were
inconsistencies in the headers altogether...this change brings
consistency.
2021-09-03 12:02:59 +05:30

226 lines
7.5 KiB
Python

# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import json
import frappe
from frappe import _
from frappe.model import default_fields, table_fields
from frappe.utils import cstr
@frappe.whitelist()
def make_mapped_doc(method, source_name, selected_children=None, args=None):
'''Returns the mapped document calling the given mapper method.
Sets selected_children as flags for the `get_mapped_doc` method.
Called from `open_mapped_doc` from create_new.js'''
for hook in frappe.get_hooks("override_whitelisted_methods", {}).get(method, []):
# override using the first hook
method = hook
break
method = frappe.get_attr(method)
if method not in frappe.whitelisted:
raise frappe.PermissionError
if selected_children:
selected_children = json.loads(selected_children)
if args:
frappe.flags.args = frappe._dict(json.loads(args))
frappe.flags.selected_children = selected_children or None
return method(source_name)
@frappe.whitelist()
def map_docs(method, source_names, target_doc, args=None):
''' Returns the mapped document calling the given mapper method
with each of the given source docs on the target doc
:param args: Args as string to pass to the mapper method
E.g. args: "{ 'supplier': 'XYZ' }" '''
method = frappe.get_attr(method)
if method not in frappe.whitelisted:
raise frappe.PermissionError
for src in json.loads(source_names):
_args = (src, target_doc, json.loads(args)) if args else (src, target_doc)
target_doc = method(*_args)
return target_doc
def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None,
postprocess=None, ignore_permissions=False, ignore_child_tables=False):
apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions")
# main
if not target_doc:
target_doc = frappe.new_doc(table_maps[from_doctype]["doctype"])
elif isinstance(target_doc, str):
target_doc = frappe.get_doc(json.loads(target_doc))
if (not apply_strict_user_permissions
and not ignore_permissions and not target_doc.has_permission("create")):
target_doc.raise_no_permission_to("create")
source_doc = frappe.get_doc(from_doctype, from_docname)
if not ignore_permissions:
if not source_doc.has_permission("read"):
source_doc.raise_no_permission_to("read")
map_doc(source_doc, target_doc, table_maps[source_doc.doctype])
row_exists_for_parentfield = {}
# children
if not ignore_child_tables:
for df in source_doc.meta.get_table_fields():
source_child_doctype = df.options
table_map = table_maps.get(source_child_doctype)
# if table_map isn't explicitly specified check if both source and target have the same fieldname and same table options and both of them don't have no_copy
if not table_map:
target_df = target_doc.meta.get_field(df.fieldname)
if target_df:
target_child_doctype = target_df.options
if target_df and target_child_doctype==source_child_doctype and not df.no_copy and not target_df.no_copy:
table_map = {
"doctype": target_child_doctype
}
if table_map:
for source_d in source_doc.get(df.fieldname):
if "condition" in table_map:
if not table_map["condition"](source_d):
continue
# if children are selected (checked from UI) for this table type,
# and this record is not in the selected children, then continue
if (frappe.flags.selected_children
and (df.fieldname in frappe.flags.selected_children)
and source_d.name not in frappe.flags.selected_children[df.fieldname]):
continue
target_child_doctype = table_map["doctype"]
target_parentfield = target_doc.get_parentfield_of_doctype(target_child_doctype)
# does row exist for a parentfield?
if target_parentfield not in row_exists_for_parentfield:
row_exists_for_parentfield[target_parentfield] = (True
if target_doc.get(target_parentfield) else False)
if table_map.get("add_if_empty") and \
row_exists_for_parentfield.get(target_parentfield):
continue
if table_map.get("filter") and table_map.get("filter")(source_d):
continue
map_child_doc(source_d, target_doc, table_map, source_doc)
if postprocess:
postprocess(source_doc, target_doc)
target_doc.set_onload("load_after_mapping", True)
if (apply_strict_user_permissions
and not ignore_permissions and not target_doc.has_permission("create")):
target_doc.raise_no_permission_to("create")
return target_doc
def map_doc(source_doc, target_doc, table_map, source_parent=None):
if table_map.get("validation"):
for key, condition in table_map["validation"].items():
if condition[0] == "=" and source_doc.get(key) != condition[1]:
frappe.throw(_("Cannot map because following condition fails:") + f" {key}={cstr(condition[1])}")
map_fields(source_doc, target_doc, table_map, source_parent)
if "postprocess" in table_map:
table_map["postprocess"](source_doc, target_doc, source_parent)
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(table_map.get("field_no_map", [])))
for df in target_doc.meta.get("fields"):
if df.fieldname not in no_copy_fields:
# map same fields
val = source_doc.get(df.fieldname)
if val not in (None, ""):
target_doc.set(df.fieldname, val)
elif df.fieldtype == "Link":
if not target_doc.get(df.fieldname):
# map link fields having options == source doctype
if df.options == source_doc.doctype:
target_doc.set(df.fieldname, source_doc.name)
elif source_parent and df.options == source_parent.doctype:
target_doc.set(df.fieldname, source_parent.name)
# map other fields
field_map = table_map.get("field_map")
if field_map:
if isinstance(field_map, dict):
for source_key, target_key in field_map.items():
val = source_doc.get(source_key)
if val not in (None, ""):
target_doc.set(target_key, val)
else:
for fmap in field_map:
val = source_doc.get(fmap[0])
if val not in (None, ""):
target_doc.set(fmap[1], val)
# map idx
if source_doc.idx:
target_doc.idx = source_doc.idx
# add fetch
for df in target_doc.meta.get("fields", {"fieldtype": "Link"}):
if target_doc.get(df.fieldname):
map_fetch_fields(target_doc, df, no_copy_fields)
def map_fetch_fields(target_doc, df, no_copy_fields):
linked_doc = None
# options should be like "link_fieldname.fieldname_in_liked_doc"
for fetch_df in target_doc.meta.get("fields", {"fetch_from": "^{0}.".format(df.fieldname)}):
if not (fetch_df.fieldtype == "Read Only" or fetch_df.read_only):
continue
if ((not target_doc.get(fetch_df.fieldname) or fetch_df.fieldtype == "Read Only")
and fetch_df.fieldname not in no_copy_fields):
source_fieldname = fetch_df.fetch_from.split(".")[1]
if not linked_doc:
try:
linked_doc = frappe.get_doc(df.options, target_doc.get(df.fieldname))
except:
return
val = linked_doc.get(source_fieldname)
if val not in (None, ""):
target_doc.set(fetch_df.fieldname, val)
def map_child_doc(source_d, target_parent, table_map, source_parent=None):
target_child_doctype = table_map["doctype"]
target_parentfield = target_parent.get_parentfield_of_doctype(target_child_doctype)
target_d = frappe.new_doc(target_child_doctype, target_parent, target_parentfield)
map_doc(source_d, target_d, table_map, source_parent)
target_d.idx = None
target_parent.append(target_parentfield, target_d)
return target_d