Merge branch 'develop' into link_title_refactor

This commit is contained in:
Suraj Shetty 2022-02-14 11:03:55 +05:30 committed by GitHub
commit abec0d8664
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 635 additions and 540 deletions

View file

@ -17,6 +17,7 @@
"hide_days",
"hide_seconds",
"reqd",
"is_virtual",
"search_index",
"column_break_18",
"options",
@ -534,13 +535,19 @@
"fieldname": "show_dashboard",
"fieldtype": "Check",
"label": "Show Dashboard"
},
{
"default": "0",
"fieldname": "is_virtual",
"fieldtype": "Check",
"label": "Virtual"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-01-03 11:56:19.812863",
"modified": "2022-01-27 21:22:20.529072",
"modified_by": "Administrator",
"module": "Core",
"name": "DocField",

View file

@ -1078,6 +1078,9 @@ def validate_fields(meta):
field.fetch_from = field.fetch_from.strip('\n').strip()
def validate_data_field_type(docfield):
if docfield.get("is_virtual"):
return
if docfield.fieldtype == "Data" and not (docfield.oldfieldtype and docfield.oldfieldtype != "Data"):
if docfield.options and (docfield.options not in data_field_options):
df_str = frappe.bold(_(docfield.label))
@ -1323,10 +1326,9 @@ def make_module_and_roles(doc, perm_fieldname="permissions"):
else:
raise
def check_fieldname_conflicts(doctype, fieldname):
def check_fieldname_conflicts(docfield):
"""Checks if fieldname conflicts with methods or properties"""
doc = frappe.get_doc({"doctype": doctype})
doc = frappe.get_doc({"doctype": docfield.dt})
available_objects = [x for x in dir(doc) if isinstance(x, str)]
property_list = [
x for x in available_objects if isinstance(getattr(type(doc), x, None), property)
@ -1334,9 +1336,10 @@ def check_fieldname_conflicts(doctype, fieldname):
method_list = [
x for x in available_objects if x not in property_list and callable(getattr(doc, x))
]
msg = _("Fieldname {0} conflicting with meta object").format(docfield.fieldname)
if fieldname in method_list + property_list:
frappe.throw(_("Fieldname {0} conflicting with meta object").format(fieldname))
if docfield.fieldname in method_list + property_list:
frappe.msgprint(msg, raise_exception=not docfield.is_virtual)
def clear_linked_doctype_cache():
frappe.cache().delete_value('linked_doctypes_without_ignore_user_permissions_enabled')

View file

@ -1,458 +1,468 @@
{
"actions": [],
"allow_import": 1,
"creation": "2013-01-10 16:34:01",
"description": "Adds a custom field to a DocType",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"dt",
"module",
"label",
"label_help",
"fieldname",
"insert_after",
"length",
"column_break_6",
"fieldtype",
"precision",
"hide_seconds",
"hide_days",
"options",
"fetch_from",
"fetch_if_empty",
"options_help",
"section_break_11",
"collapsible",
"collapsible_depends_on",
"default",
"depends_on",
"mandatory_depends_on",
"read_only_depends_on",
"properties",
"non_negative",
"reqd",
"unique",
"read_only",
"ignore_user_permissions",
"hidden",
"print_hide",
"print_hide_if_no_value",
"print_width",
"no_copy",
"allow_on_submit",
"in_list_view",
"in_standard_filter",
"in_global_search",
"in_preview",
"bold",
"report_hide",
"search_index",
"allow_in_quick_entry",
"ignore_xss_filter",
"translatable",
"hide_border",
"description",
"permlevel",
"width",
"columns"
],
"fields": [{
"bold": 1,
"fieldname": "dt",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Document",
"oldfieldname": "dt",
"oldfieldtype": "Link",
"options": "DocType",
"reqd": 1,
"search_index": 1
},
{
"bold": 1,
"fieldname": "label",
"fieldtype": "Data",
"in_filter": 1,
"label": "Label",
"no_copy": 1,
"oldfieldname": "label",
"oldfieldtype": "Data"
},
{
"fieldname": "label_help",
"fieldtype": "HTML",
"label": "Label Help",
"oldfieldtype": "HTML"
},
{
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Fieldname",
"no_copy": 1,
"oldfieldname": "fieldname",
"oldfieldtype": "Data",
"read_only": 1
},
{
"description": "Select the label after which you want to insert new field.",
"fieldname": "insert_after",
"fieldtype": "Select",
"label": "Insert After",
"no_copy": 1,
"oldfieldname": "insert_after",
"oldfieldtype": "Select"
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"bold": 1,
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_filter": 1,
"in_list_view": 1,
"label": "Field Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break",
"reqd": 1
},
{
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
},
{
"fieldname": "options",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Options",
"oldfieldname": "options",
"oldfieldtype": "Text"
},
{
"fieldname": "fetch_from",
"fieldtype": "Small Text",
"label": "Fetch From"
},
{
"default": "0",
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
"fieldname": "fetch_if_empty",
"fieldtype": "Check",
"label": "Fetch If Empty"
},
{
"fieldname": "options_help",
"fieldtype": "HTML",
"label": "Options Help",
"oldfieldtype": "HTML"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
"label": "Collapsible"
},
{
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
"label": "Collapsible Depends On"
},
{
"fieldname": "default",
"fieldtype": "Text",
"label": "Default Value",
"oldfieldname": "default",
"oldfieldtype": "Text"
},
{
"fieldname": "depends_on",
"fieldtype": "Code",
"label": "Depends On",
"length": 255
},
{
"fieldname": "description",
"fieldtype": "Text",
"label": "Field Description",
"oldfieldname": "description",
"oldfieldtype": "Text",
"print_width": "300px",
"width": "300px"
},
{
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"label": "Permission Level",
"oldfieldname": "permlevel",
"oldfieldtype": "Int"
},
{
"fieldname": "width",
"fieldtype": "Data",
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data"
},
{
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"label": "Columns"
},
{
"fieldname": "properties",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
"width": "50%"
},
{
"default": "0",
"fieldname": "reqd",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Mandatory Field",
"oldfieldname": "reqd",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "unique",
"fieldtype": "Check",
"label": "Unique"
},
{
"default": "0",
"fieldname": "read_only",
"fieldtype": "Check",
"label": "Read Only"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype===\"Link\"",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore User Permissions"
},
{
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"label": "Hidden"
},
{
"default": "0",
"fieldname": "print_hide",
"fieldtype": "Check",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
"label": "Print Hide If No Value"
},
{
"fieldname": "print_width",
"fieldtype": "Data",
"hidden": 1,
"label": "Print Width",
"no_copy": 1,
"print_hide": 1
},
{
"default": "0",
"fieldname": "no_copy",
"fieldtype": "Check",
"label": "No Copy",
"oldfieldname": "no_copy",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View"
},
{
"default": "0",
"fieldname": "in_standard_filter",
"fieldtype": "Check",
"label": "In Standard Filter"
},
{
"default": "0",
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"label": "In Global Search"
},
{
"default": "0",
"fieldname": "bold",
"fieldtype": "Check",
"label": "Bold"
},
{
"default": "0",
"fieldname": "report_hide",
"fieldtype": "Check",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "search_index",
"fieldtype": "Check",
"hidden": 1,
"label": "Index",
"no_copy": 1,
"print_hide": 1
},
{
"default": "0",
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
"fieldname": "ignore_xss_filter",
"fieldtype": "Check",
"label": "Ignore XSS Filter"
},
{
"default": "1",
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
"fieldname": "translatable",
"fieldtype": "Check",
"label": "Translatable"
},
{
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"label": "Length"
},
{
"fieldname": "mandatory_depends_on",
"fieldtype": "Code",
"label": "Mandatory Depends On",
"length": 255
},
{
"fieldname": "read_only_depends_on",
"fieldtype": "Code",
"label": "Read Only Depends On",
"length": 255
},
{
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": "Allow in Quick Entry"
},
{
"default": "0",
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_seconds",
"fieldtype": "Check",
"label": "Hide Seconds"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_days",
"fieldtype": "Check",
"label": "Hide Days"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
"fieldname": "hide_border",
"fieldtype": "Check",
"label": "Hide Border"
},
{
"default": "0",
"depends_on": "eval:in_list([\"Int\", \"Float\", \"Currency\"], doc.fieldtype)",
"fieldname": "non_negative",
"fieldtype": "Check",
"label": "Non Negative"
},
{
"fieldname": "module",
"fieldtype": "Link",
"label": "Module (for export)",
"options": "Module Def"
}
],
"icon": "fa fa-glass",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-09-04 12:45:23.810120",
"modified_by": "Administrator",
"module": "Custom",
"name": "Custom Field",
"owner": "Administrator",
"permissions": [{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"search_fields": "dt,label,fieldtype,options",
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
"actions": [],
"allow_import": 1,
"creation": "2013-01-10 16:34:01",
"description": "Adds a custom field to a DocType",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
"dt",
"module",
"label",
"label_help",
"fieldname",
"insert_after",
"length",
"column_break_6",
"fieldtype",
"precision",
"hide_seconds",
"hide_days",
"options",
"fetch_from",
"fetch_if_empty",
"options_help",
"section_break_11",
"collapsible",
"collapsible_depends_on",
"default",
"depends_on",
"mandatory_depends_on",
"read_only_depends_on",
"properties",
"non_negative",
"reqd",
"unique",
"is_virtual",
"read_only",
"ignore_user_permissions",
"hidden",
"print_hide",
"print_hide_if_no_value",
"print_width",
"no_copy",
"allow_on_submit",
"in_list_view",
"in_standard_filter",
"in_global_search",
"in_preview",
"bold",
"report_hide",
"search_index",
"allow_in_quick_entry",
"ignore_xss_filter",
"translatable",
"hide_border",
"description",
"permlevel",
"width",
"columns"
],
"fields": [
{
"bold": 1,
"fieldname": "dt",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Document",
"oldfieldname": "dt",
"oldfieldtype": "Link",
"options": "DocType",
"reqd": 1,
"search_index": 1
},
{
"bold": 1,
"fieldname": "label",
"fieldtype": "Data",
"in_filter": 1,
"label": "Label",
"no_copy": 1,
"oldfieldname": "label",
"oldfieldtype": "Data"
},
{
"fieldname": "label_help",
"fieldtype": "HTML",
"label": "Label Help",
"oldfieldtype": "HTML"
},
{
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Fieldname",
"no_copy": 1,
"oldfieldname": "fieldname",
"oldfieldtype": "Data",
"read_only": 1
},
{
"description": "Select the label after which you want to insert new field.",
"fieldname": "insert_after",
"fieldtype": "Select",
"label": "Insert After",
"no_copy": 1,
"oldfieldname": "insert_after",
"oldfieldtype": "Select"
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"bold": 1,
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_filter": 1,
"in_list_view": 1,
"label": "Field Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nIcon\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break",
"reqd": 1
},
{
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
},
{
"fieldname": "options",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Options",
"oldfieldname": "options",
"oldfieldtype": "Text"
},
{
"fieldname": "fetch_from",
"fieldtype": "Small Text",
"label": "Fetch From"
},
{
"default": "0",
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
"fieldname": "fetch_if_empty",
"fieldtype": "Check",
"label": "Fetch If Empty"
},
{
"fieldname": "options_help",
"fieldtype": "HTML",
"label": "Options Help",
"oldfieldtype": "HTML"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
"label": "Collapsible"
},
{
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
"label": "Collapsible Depends On"
},
{
"fieldname": "default",
"fieldtype": "Text",
"label": "Default Value",
"oldfieldname": "default",
"oldfieldtype": "Text"
},
{
"fieldname": "depends_on",
"fieldtype": "Code",
"label": "Depends On",
"length": 255
},
{
"fieldname": "description",
"fieldtype": "Text",
"label": "Field Description",
"oldfieldname": "description",
"oldfieldtype": "Text",
"print_width": "300px",
"width": "300px"
},
{
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"label": "Permission Level",
"oldfieldname": "permlevel",
"oldfieldtype": "Int"
},
{
"fieldname": "width",
"fieldtype": "Data",
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data"
},
{
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"label": "Columns"
},
{
"fieldname": "properties",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
"width": "50%"
},
{
"default": "0",
"fieldname": "reqd",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Mandatory Field",
"oldfieldname": "reqd",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "unique",
"fieldtype": "Check",
"label": "Unique"
},
{
"default": "0",
"fieldname": "is_virtual",
"fieldtype": "Check",
"label": "Is Virtual"
},
{
"default": "0",
"fieldname": "read_only",
"fieldtype": "Check",
"label": "Read Only"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype===\"Link\"",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore User Permissions"
},
{
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"label": "Hidden"
},
{
"default": "0",
"fieldname": "print_hide",
"fieldtype": "Check",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
"label": "Print Hide If No Value"
},
{
"fieldname": "print_width",
"fieldtype": "Data",
"hidden": 1,
"label": "Print Width",
"no_copy": 1,
"print_hide": 1
},
{
"default": "0",
"fieldname": "no_copy",
"fieldtype": "Check",
"label": "No Copy",
"oldfieldname": "no_copy",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View"
},
{
"default": "0",
"fieldname": "in_standard_filter",
"fieldtype": "Check",
"label": "In Standard Filter"
},
{
"default": "0",
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"label": "In Global Search"
},
{
"default": "0",
"fieldname": "bold",
"fieldtype": "Check",
"label": "Bold"
},
{
"default": "0",
"fieldname": "report_hide",
"fieldtype": "Check",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "search_index",
"fieldtype": "Check",
"hidden": 1,
"label": "Index",
"no_copy": 1,
"print_hide": 1
},
{
"default": "0",
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
"fieldname": "ignore_xss_filter",
"fieldtype": "Check",
"label": "Ignore XSS Filter"
},
{
"default": "1",
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
"fieldname": "translatable",
"fieldtype": "Check",
"label": "Translatable"
},
{
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"label": "Length"
},
{
"fieldname": "mandatory_depends_on",
"fieldtype": "Code",
"label": "Mandatory Depends On",
"length": 255
},
{
"fieldname": "read_only_depends_on",
"fieldtype": "Code",
"label": "Read Only Depends On",
"length": 255
},
{
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": "Allow in Quick Entry"
},
{
"default": "0",
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_seconds",
"fieldtype": "Check",
"label": "Hide Seconds"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_days",
"fieldtype": "Check",
"label": "Hide Days"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
"fieldname": "hide_border",
"fieldtype": "Check",
"label": "Hide Border"
},
{
"default": "0",
"depends_on": "eval:in_list([\"Int\", \"Float\", \"Currency\"], doc.fieldtype)",
"fieldname": "non_negative",
"fieldtype": "Check",
"label": "Non Negative"
},
{
"fieldname": "module",
"fieldtype": "Link",
"label": "Module (for export)",
"options": "Module Def"
}
],
"icon": "fa fa-glass",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-01-27 21:47:01.065556",
"modified_by": "Administrator",
"module": "Custom",
"name": "Custom Field",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"search_fields": "dt,label,fieldtype,options",
"sort_field": "modified",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}

View file

@ -54,7 +54,7 @@ class CustomField(Document):
old_fieldtype = self.db_get('fieldtype')
is_fieldtype_changed = (not self.is_new()) and (old_fieldtype != self.fieldtype)
if is_fieldtype_changed and not CustomizeForm.allow_fieldtype_change(old_fieldtype, self.fieldtype):
if not self.is_virtual and is_fieldtype_changed and not CustomizeForm.allow_fieldtype_change(old_fieldtype, self.fieldtype):
frappe.throw(_("Fieldtype cannot be changed from {0} to {1}").format(old_fieldtype, self.fieldtype))
if not self.fieldname:
@ -65,7 +65,7 @@ class CustomField(Document):
if not self.flags.ignore_validate:
from frappe.core.doctype.doctype.doctype import check_fieldname_conflicts
check_fieldname_conflicts(self.dt, self.fieldname)
check_fieldname_conflicts(self)
def on_update(self):
if not frappe.flags.in_setup_wizard:

View file

@ -418,6 +418,9 @@ class CustomizeForm(Document):
return property_value
def validate_fieldtype_change(self, df, old_value, new_value):
if df.is_virtual:
return
allowed = self.allow_fieldtype_change(old_value, new_value)
if allowed:
old_value_length = cint(frappe.db.type_map.get(old_value)[1])
@ -430,7 +433,8 @@ class CustomizeForm(Document):
self.validate_fieldtype_length()
else:
self.flags.update_db = True
if not allowed:
else:
frappe.throw(_("Fieldtype cannot be changed from {0} to {1} in row {2}").format(old_value, new_value, df.idx))
def validate_fieldtype_length(self):
@ -559,7 +563,8 @@ docfield_properties = {
'allow_in_quick_entry': 'Check',
'hide_border': 'Check',
'hide_days': 'Check',
'hide_seconds': 'Check'
'hide_seconds': 'Check',
'is_virtual': 'Check',
}
doctype_link_properties = {

View file

@ -14,6 +14,7 @@
"non_negative",
"reqd",
"unique",
"is_virtual",
"in_list_view",
"in_standard_filter",
"in_global_search",
@ -115,6 +116,12 @@
"fieldtype": "Check",
"label": "Unique"
},
{
"default": "0",
"fieldname": "is_virtual",
"fieldtype": "Check",
"label": "Is Virtual"
},
{
"default": "0",
"fieldname": "in_list_view",
@ -436,7 +443,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-01-03 14:50:32.035768",
"modified": "2022-01-27 21:45:22.349776",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form Field",

View file

@ -1,4 +1,4 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
@ -18,53 +18,19 @@ class PropertySetter(Document):
def validate(self):
self.validate_fieldtype_change()
if self.is_new():
delete_property_setter(self.doc_type, self.property, self.field_name, self.row_name)
# clear cache
frappe.clear_cache(doctype = self.doc_type)
def validate_fieldtype_change(self):
if self.field_name in not_allowed_fieldtype_change and \
self.property == 'fieldtype':
frappe.throw(_("Field type cannot be changed for {0}").format(self.field_name))
def get_property_list(self, dt):
return frappe.db.get_all('DocField',
fields=['fieldname', 'label', 'fieldtype'],
filters={
'parent': dt,
'fieldtype': ['not in', ('Section Break', 'Column Break', 'Tab Break', 'HTML', 'Read Only', 'Fold') + frappe.model.table_fields],
'fieldname': ['!=', '']
},
order_by='label asc',
as_dict=1
)
def get_setup_data(self):
return {
'doctypes': frappe.get_all("DocType", pluck="name"),
'dt_properties': self.get_property_list('DocType'),
'df_properties': self.get_property_list('DocField')
}
def get_field_ids(self):
return frappe.db.get_values(
"DocField",
filters={"parent": self.doc_type},
fieldname=["name", "fieldtype", "label", "fieldname"],
as_dict=True,
)
def get_defaults(self):
if not self.field_name:
return frappe.get_all("DocType", filters={"name": self.doc_type}, fields="*")[0]
else:
return frappe.db.get_values(
"DocField",
filters={"fieldname": self.field_name, "parent": self.doc_type},
fieldname="*",
)[0]
if (
self.property == 'fieldtype'
and self.field_name in not_allowed_fieldtype_change
):
frappe.throw(
_("Field type cannot be changed for {0}").format(self.field_name)
)
def on_update(self):
if frappe.flags.in_patch:
@ -74,6 +40,7 @@ class PropertySetter(Document):
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
validate_fields_for_doctype(self.doc_type)
def make_property_setter(doctype, fieldname, property, value, property_type, for_doctype = False,
validate_fields_for_doctype=True):
# WARNING: Ignores Permissions
@ -91,6 +58,7 @@ def make_property_setter(doctype, fieldname, property, value, property_type, for
property_setter.insert()
return property_setter
def delete_property_setter(doc_type, property, field_name=None, row_name=None):
"""delete other property setters on this, if this is new"""
filters = dict(doc_type=doc_type, property=property)
@ -100,4 +68,3 @@ def delete_property_setter(doc_type, property, field_name=None, row_name=None):
filters["row_name"] = row_name
frappe.db.delete('Property Setter', filters)

View file

@ -67,7 +67,7 @@ class DBTable:
"""
get columns from docfields and custom fields
"""
fields = self.meta.get_fieldnames_with_value(True)
fields = self.meta.get_fieldnames_with_value(with_field_meta=True)
# optional fields like _comments
if not self.meta.get('istable'):
@ -85,6 +85,9 @@ class DBTable:
})
for field in fields:
if field.get("is_virtual"):
continue
self.columns[field.get('fieldname')] = DbColumn(
self,
field.get('fieldname'),

View file

@ -1,16 +1,14 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import datetime
import frappe
import datetime
from frappe import _
from frappe.model import default_fields, table_fields, child_table_fields
from frappe.model import child_table_fields, default_fields, display_fieldtypes, 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
from frappe.model import display_fieldtypes
from frappe.utils import (cint, flt, now, cstr, strip_html,
sanitize_html, sanitize_email, cast_fieldtype)
from frappe.utils import cast_fieldtype, cint, cstr, flt, now, sanitize_html, strip_html
from frappe.utils.html_utils import unescape_html
from frappe.model.docstatus import DocStatus
@ -254,7 +252,22 @@ class BaseDocument(object):
continue
df = self.meta.get_field(fieldname)
if df:
if df and df.get("is_virtual"):
from frappe.utils.safe_exec import get_safe_globals
if d[fieldname] is None:
if df.get("options"):
d[fieldname] = frappe.safe_eval(
code=df.get("options"),
eval_globals=get_safe_globals(),
eval_locals={"doc": self},
)
else:
_val = getattr(self, fieldname, None)
if _val and not callable(_val):
d[fieldname] = _val
elif df:
if df.fieldtype=="Check":
d[fieldname] = 1 if cint(d[fieldname]) else 0
@ -328,6 +341,7 @@ class BaseDocument(object):
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] = [

View file

@ -444,9 +444,16 @@ class Meta(Document):
self.permissions = [Document(d) for d in custom_perms]
def get_fieldnames_with_value(self, with_field_meta=False):
return [df if with_field_meta else df.fieldname \
for df in self.fields if df.fieldtype not in no_value_fields]
def is_value_field(docfield):
return not (
docfield.get("is_virtual")
or docfield.fieldtype in no_value_fields
)
if with_field_meta:
return [df for df in self.fields if is_value_field(df)]
return [df.fieldname for df in self.fields if is_value_field(df)]
def get_fields_to_check_permissions(self, user_permission_doctypes):
fields = self.get("fields", {

View file

@ -39,6 +39,9 @@ frappe.ui.form.Control = class BaseControl {
if (this.df.get_status) {
return this.df.get_status(this);
}
if (this.df.is_virtual) {
return "Read";
}
if ((!this.doctype && !this.docname) || this.df.parenttype === 'Web Form' || this.df.is_web_form) {
// like in case of a dialog box
@ -52,7 +55,7 @@ frappe.ui.form.Control = class BaseControl {
if(explain) console.log("By Hidden Dependency: None"); // eslint-disable-line no-console
return "None";
} else if (cint(this.df.read_only)) {
} else if (cint(this.df.read_only || this.df.is_virtual)) {
// eslint-disable-next-line
if (explain) console.log("By Read Only: Read"); // eslint-disable-line no-console
return "Read";

View file

@ -55,7 +55,7 @@ frappe.ui.form.QuickEntryForm = class QuickEntryForm {
// prepare a list of mandatory, bold and allow in quick entry fields
this.mandatory = fields.filter(df => {
return ((df.reqd || df.bold || df.allow_in_quick_entry) && !df.read_only);
return ((df.reqd || df.bold || df.allow_in_quick_entry) && !df.read_only && !df.is_virtual);
});
}

View file

@ -192,9 +192,18 @@ frappe.ui.form.ScriptManager = class ScriptManager {
}
function setup_add_fetch(df) {
if ((['Data', 'Read Only', 'Text', 'Small Text', 'Currency', 'Check', 'Attach Image',
'Text Editor', 'Code', 'Link', 'Float', 'Int', 'Date', 'Select', 'Duration'].includes(df.fieldtype) || df.read_only==1)
&& df.fetch_from && df.fetch_from.indexOf(".")!=-1) {
let is_read_only_field = (
['Data', 'Read Only', 'Text', 'Small Text', 'Currency', 'Check', 'Text Editor', 'Attach Image',
'Code', 'Link', 'Float', 'Int', 'Date', 'Select', 'Duration'].includes(df.fieldtype)
|| df.read_only == 1
|| df.is_virtual == 1
)
if (
is_read_only_field
&& df.fetch_from
&& df.fetch_from.indexOf(".") != -1
) {
var parts = df.fetch_from.split(".");
me.frm.add_fetch(parts[0], parts[1], df.fieldname, df.parent);
}

View file

@ -1672,7 +1672,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
frappe.model.is_value_type(field_doc) &&
field_doc.fieldtype !== "Read Only" &&
!field_doc.hidden &&
!field_doc.read_only
!field_doc.read_only &&
!field_doc.is_virtual
);
};

View file

@ -259,8 +259,16 @@ frappe.utils.xss_sanitise = function (string, options) {
'/': '/'
};
const REGEX_SCRIPT = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi; // used in jQuery 1.7.2 src/ajax.js Line 14
const REGEX_ALERT = /confirm\(.*\)|alert\(.*\)|prompt\(.*\)/gi; // captures alert, confirm, prompt
options = Object.assign({}, DEFAULT_OPTIONS, options); // don't deep copy, immutable beauty.
// Rule 3 - TODO: Check event handlers?
// script and alert should be checked first or else it will be escaped
if (options.strategies.includes('js')) {
sanitised = sanitised.replace(REGEX_SCRIPT, "");
sanitised = sanitised.replace(REGEX_ALERT, "");
}
// Rule 1
if (options.strategies.includes('html')) {
for (let char in HTML_ESCAPE_MAP) {
@ -270,11 +278,6 @@ frappe.utils.xss_sanitise = function (string, options) {
}
}
// Rule 3 - TODO: Check event handlers?
if (options.strategies.includes('js')) {
sanitised = sanitised.replace(REGEX_SCRIPT, "");
}
return sanitised;
}

View file

@ -648,6 +648,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
// not a cancelled doc
&& data.docstatus !== 2
&& !df.read_only
&& !df.is_virtual
&& !df.hidden
// not a standard field i.e., owner, modified_by, etc.
&& !frappe.model.std_fields_list.includes(df.fieldname))
@ -1029,7 +1030,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
title += ` (${__(doctype)})`;
}
const editable = frappe.model.is_non_std_field(fieldname) && !docfield.read_only;
const editable = frappe.model.is_non_std_field(fieldname) && !docfield.read_only && !docfield.is_virtual;
const align = (() => {
const is_numeric = frappe.model.is_numeric_field(docfield);

View file

@ -343,7 +343,7 @@ frappe.views.TreeView = class TreeView {
this.ignore_fields = this.opts.ignore_fields || [];
var mandatory_fields = $.map(me.opts.meta.fields, function(d) {
return (d.reqd || d.bold && !d.read_only) ? d : null });
return (d.reqd || d.bold && !d.read_only && !!d.is_virtual) ? d : null });
var opts_field_names = this.fields.map(function(d) {
return d.fieldname

View file

@ -1,11 +1,20 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import os
import unittest
from contextlib import contextmanager
from datetime import timedelta
from unittest.mock import patch
import frappe
from frappe.utils import cint
from frappe.model.naming import revert_series_if_last, make_autoname, parse_naming_series
from frappe.desk.doctype.note.note import Note
from frappe.model.naming import make_autoname, parse_naming_series, revert_series_if_last
from frappe.utils import cint, now_datetime
class CustomTestNote(Note):
@property
def age(self):
return now_datetime() - self.creation
class TestDocument(unittest.TestCase):
@ -256,4 +265,50 @@ class TestDocument(unittest.TestCase):
def test_limit_for_get(self):
doc = frappe.get_doc("DocType", "DocType")
# assuming DocType has more that 3 Data fields
self.assertEquals(len(doc.get("fields", filters={"fieldtype": "Data"}, limit=3)), 3)
self.assertEquals(len(doc.get("fields", filters={"fieldtype": "Data"}, limit=3)), 3)
def test_virtual_fields(self):
"""Virtual fields are accessible via API and Form views, whenever .as_dict is invoked
"""
frappe.db.delete("Custom Field", {"dt": "Note", "fieldname":"age"})
def patch_note():
return patch("frappe.controllers", new={frappe.local.site: {'Note': CustomTestNote}})
@contextmanager
def customize_note(with_options=False):
options = "frappe.utils.now_datetime() - doc.creation" if with_options else ""
custom_field = frappe.get_doc({
"doctype": "Custom Field",
"dt": "Note",
"fieldname": "age",
"fieldtype": "Data",
"read_only": True,
"is_virtual": True,
"options": options,
})
try:
yield custom_field.insert(ignore_if_duplicate=True)
finally:
custom_field.delete()
with patch_note():
doc = frappe.get_last_doc("Note")
self.assertIsInstance(doc, CustomTestNote)
self.assertIsInstance(doc.age, timedelta)
self.assertIsNone(doc.as_dict().get("age"))
self.assertIsNone(doc.get_valid_dict().get("age"))
with customize_note(), patch_note():
doc = frappe.get_last_doc("Note")
self.assertIsInstance(doc, CustomTestNote)
self.assertIsInstance(doc.age, timedelta)
self.assertIsInstance(doc.as_dict().get("age"), timedelta)
self.assertIsInstance(doc.get_valid_dict().get("age"), timedelta)
with customize_note(with_options=True):
doc = frappe.get_last_doc("Note")
self.assertIsInstance(doc, Note)
self.assertIsInstance(doc.as_dict().get("age"), timedelta)
self.assertIsInstance(doc.get_valid_dict().get("age"), timedelta)

View file

@ -60,7 +60,7 @@ frappe.ui.form.on("Web Form", {
options: field.options,
reqd: field.reqd,
default: field.default,
read_only: field.read_only,
read_only: field.read_only || field.is_virtual,
depends_on: field.depends_on,
mandatory_depends_on: field.mandatory_depends_on,
read_only_depends_on: field.read_only_depends_on,