From f0a9d506715d0e30dc1e398e43dd7487bdd03629 Mon Sep 17 00:00:00 2001 From: hrwx Date: Fri, 31 Dec 2021 14:51:18 +0530 Subject: [PATCH 01/26] fix: unhide context field --- frappe/core/doctype/translation/translation.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/translation/translation.json b/frappe/core/doctype/translation/translation.json index e91ffc2941..560f3b2ce2 100644 --- a/frappe/core/doctype/translation/translation.json +++ b/frappe/core/doctype/translation/translation.json @@ -43,8 +43,7 @@ { "fieldname": "context", "fieldtype": "Data", - "label": "Context", - "read_only": 1 + "label": "Context" }, { "default": "0", @@ -83,7 +82,7 @@ } ], "links": [], - "modified": "2020-03-12 13:28:48.223409", + "modified": "2021-12-31 10:19:52.541055", "modified_by": "Administrator", "module": "Core", "name": "Translation", From 0345954b65de61cd930d74d33f7e3e684218f6f2 Mon Sep 17 00:00:00 2001 From: Lev Date: Fri, 31 Dec 2021 20:01:40 +0300 Subject: [PATCH 02/26] perf: BaseDocument improvements (#15398) * perf: BaseDocument improvements * Add `default_fields_set`, use default placeholder to lookup fields in update(), refactor get_valid_columns() * Revert all changes except "update()" Co-authored-by: Gavin D'souza --- frappe/model/base_document.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 1fd3784fcc..eeef552a8a 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -101,13 +101,10 @@ class BaseDocument(object): "balance": 42000 }) """ - if "doctype" in d: - self.set("doctype", d.get("doctype")) - # first set default field values of base document for key in default_fields: if key in d: - self.set(key, d.get(key)) + self.set(key, d[key]) for key, value in d.items(): self.set(key, value) From 2a8f07d6fb6de74ed79e01ba40ef2227a24e8463 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Sun, 2 Jan 2022 18:53:15 +0530 Subject: [PATCH 03/26] fix(minor): List data types alphabetically in DocType --- frappe/core/doctype/docfield/docfield.json | 1084 ++++++++++---------- 1 file changed, 543 insertions(+), 541 deletions(-) diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 6910d615d3..7a8b52c972 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -1,543 +1,545 @@ { - "actions": [], - "autoname": "hash", - "creation": "2013-02-22 01:27:33", - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "label_and_type", - "label", - "fieldtype", - "fieldname", - "precision", - "length", - "non_negative", - "hide_days", - "hide_seconds", - "reqd", - "search_index", - "column_break_18", - "options", - "defaults_section", - "default", - "column_break_6", - "fetch_from", - "fetch_if_empty", - "visibility_section", - "hidden", - "bold", - "allow_in_quick_entry", - "translatable", - "print_hide", - "print_hide_if_no_value", - "report_hide", - "column_break_28", - "depends_on", - "collapsible", - "collapsible_depends_on", - "hide_border", - "list__search_settings_section", - "in_list_view", - "in_standard_filter", - "in_preview", - "column_break_35", - "in_filter", - "in_global_search", - "permissions", - "read_only", - "allow_on_submit", - "ignore_user_permissions", - "allow_bulk_edit", - "column_break_13", - "permlevel", - "ignore_xss_filter", - "constraints_section", - "unique", - "no_copy", - "set_only_once", - "remember_last_selected_value", - "column_break_38", - "mandatory_depends_on", - "read_only_depends_on", - "display", - "print_width", - "width", - "max_height", - "columns", - "column_break_22", - "description", - "oldfieldname", - "oldfieldtype" - ], - "fields": [{ - "fieldname": "label_and_type", - "fieldtype": "Section Break" - }, - { - "bold": 1, - "fieldname": "label", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Label", - "oldfieldname": "label", - "oldfieldtype": "Data", - "print_width": "163", - "search_index": 1, - "width": "163" - }, - { - "bold": 1, - "default": "Data", - "fieldname": "fieldtype", - "fieldtype": "Select", - "in_list_view": 1, - "label": "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, - "search_index": 1 - }, - { - "bold": 1, - "fieldname": "fieldname", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Name", - "oldfieldname": "fieldname", - "oldfieldtype": "Data", - "search_index": 1 - }, - { - "default": "0", - "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)", - "fieldname": "reqd", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Mandatory", - "oldfieldname": "reqd", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "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", - "print_hide": 1 - }, - { - "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" - }, - { - "default": "0", - "fieldname": "search_index", - "fieldtype": "Check", - "label": "Index", - "oldfieldname": "search_index", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "in_list_view", - "fieldtype": "Check", - "label": "In List View", - "print_width": "70px", - "width": "70px" - }, - { - "default": "0", - "fieldname": "in_standard_filter", - "fieldtype": "Check", - "label": "In List 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", - "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", - "fieldname": "in_preview", - "fieldtype": "Check", - "label": "In Preview" - }, - { - "default": "0", - "fieldname": "allow_in_quick_entry", - "fieldtype": "Check", - "label": "Allow in Quick Entry" - }, - { - "default": "0", - "fieldname": "bold", - "fieldtype": "Check", - "label": "Bold" - }, - { - "default": "0", - "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", - "fieldname": "translatable", - "fieldtype": "Check", - "label": "Translatable" - }, - { - "default": "0", - "depends_on": "eval:doc.fieldtype===\"Section Break\"", - "fieldname": "collapsible", - "fieldtype": "Check", - "label": "Collapsible", - "length": 255 - }, - { - "depends_on": "eval:doc.fieldtype==\"Section Break\" && doc.collapsible", - "fieldname": "collapsible_depends_on", - "fieldtype": "Code", - "label": "Collapsible Depends On (JS)", - "max_height": "3rem", - "options": "JS" - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.", - "fieldname": "options", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Options", - "oldfieldname": "options", - "oldfieldtype": "Text" - }, - { - "fieldname": "default", - "fieldtype": "Small Text", - "label": "Default", - "max_height": "3rem", - "oldfieldname": "default", - "oldfieldtype": "Text" - }, - { - "fieldname": "fetch_from", - "fieldtype": "Small Text", - "label": "Fetch From" - }, - { - "default": "0", - "fieldname": "fetch_if_empty", - "fieldtype": "Check", - "label": "Fetch only if value is not set" - }, - { - "fieldname": "permissions", - "fieldtype": "Section Break", - "label": "Permissions" - }, - { - "fieldname": "depends_on", - "fieldtype": "Code", - "label": "Display Depends On (JS)", - "length": 255, - "max_height": "3rem", - "oldfieldname": "depends_on", - "oldfieldtype": "Data", - "options": "JS" - }, - { - "default": "0", - "fieldname": "hidden", - "fieldtype": "Check", - "label": "Hidden", - "oldfieldname": "hidden", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "read_only", - "fieldtype": "Check", - "label": "Read Only", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "unique", - "fieldtype": "Check", - "label": "Unique" - }, - { - "default": "0", - "fieldname": "set_only_once", - "fieldtype": "Check", - "label": "Set only once" - }, - { - "default": "0", - "depends_on": "eval: doc.fieldtype == \"Table\"", - "fieldname": "allow_bulk_edit", - "fieldtype": "Check", - "label": "Allow Bulk Edit" - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "permlevel", - "fieldtype": "Int", - "label": "Perm Level", - "oldfieldname": "permlevel", - "oldfieldtype": "Int", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "ignore_user_permissions", - "fieldtype": "Check", - "label": "Ignore User Permissions" - }, - { - "default": "0", - "depends_on": "eval: parent.is_submittable", - "fieldname": "allow_on_submit", - "fieldtype": "Check", - "label": "Allow on Submit", - "oldfieldname": "allow_on_submit", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "report_hide", - "fieldtype": "Check", - "label": "Report Hide", - "oldfieldname": "report_hide", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "depends_on": "eval:(doc.fieldtype == 'Link')", - "fieldname": "remember_last_selected_value", - "fieldtype": "Check", - "label": "Remember Last Selected Value" - }, - { - "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" - }, - { - "fieldname": "display", - "fieldtype": "Section Break", - "label": "Display" - }, - { - "default": "0", - "fieldname": "in_filter", - "fieldtype": "Check", - "label": "In Filter", - "oldfieldname": "in_filter", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "no_copy", - "fieldtype": "Check", - "label": "No Copy", - "oldfieldname": "no_copy", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "default": "0", - "fieldname": "print_hide", - "fieldtype": "Check", - "label": "Print Hide", - "oldfieldname": "print_hide", - "oldfieldtype": "Check", - "print_width": "50px", - "width": "50px" - }, - { - "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", - "label": "Print Width", - "length": 10 - }, - { - "fieldname": "width", - "fieldtype": "Data", - "label": "Width", - "length": 10, - "oldfieldname": "width", - "oldfieldtype": "Data", - "print_width": "50px", - "width": "50px" - }, - { - "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": "column_break_22", - "fieldtype": "Column Break" - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Description", - "oldfieldname": "description", - "oldfieldtype": "Text", - "print_width": "300px", - "width": "300px" - }, - { - "fieldname": "oldfieldname", - "fieldtype": "Data", - "hidden": 1, - "oldfieldname": "oldfieldname", - "oldfieldtype": "Data" - }, - { - "fieldname": "oldfieldtype", - "fieldtype": "Data", - "hidden": 1, - "oldfieldname": "oldfieldtype", - "oldfieldtype": "Data" - }, - { - "fieldname": "mandatory_depends_on", - "fieldtype": "Code", - "label": "Mandatory Depends On (JS)", - "max_height": "3rem", - "options": "JS" - }, - { - "fieldname": "read_only_depends_on", - "fieldtype": "Code", - "label": "Read Only Depends On (JS)", - "max_height": "3rem", - "options": "JS" - }, - { - "fieldname": "column_break_38", - "fieldtype": "Column Break" - }, - { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_days", - "fieldtype": "Check", - "label": "Hide Days" - }, - { - "default": "0", - "depends_on": "eval:doc.fieldtype=='Duration'", - "fieldname": "hide_seconds", - "fieldtype": "Check", - "label": "Hide Seconds" - }, - { - "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": "column_break_18", - "fieldtype": "Column Break" - }, - { - "fieldname": "defaults_section", - "fieldtype": "Section Break", - "label": "Defaults", - "max_height": "2rem" - }, - { - "fieldname": "visibility_section", - "fieldtype": "Section Break", - "label": "Visibility" - }, - { - "fieldname": "column_break_28", - "fieldtype": "Column Break" - }, - { - "fieldname": "constraints_section", - "fieldtype": "Section Break", - "label": "Constraints" - }, - { - "fieldname": "max_height", - "fieldtype": "Data", - "label": "Max Height", - "length": 10 - }, - { - "fieldname": "list__search_settings_section", - "fieldtype": "Section Break", - "label": "List / Search Settings" - }, - { - "fieldname": "column_break_35", - "fieldtype": "Column Break" - } - ], - "idx": 1, - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-09-04 19:41:23.684094", - "modified_by": "Administrator", - "module": "Core", - "name": "DocField", - "naming_rule": "Random", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "ASC" + "actions": [], + "autoname": "hash", + "creation": "2013-02-22 01:27:33", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "label_and_type", + "label", + "fieldtype", + "fieldname", + "precision", + "length", + "non_negative", + "hide_days", + "hide_seconds", + "reqd", + "search_index", + "column_break_18", + "options", + "defaults_section", + "default", + "column_break_6", + "fetch_from", + "fetch_if_empty", + "visibility_section", + "hidden", + "bold", + "allow_in_quick_entry", + "translatable", + "print_hide", + "print_hide_if_no_value", + "report_hide", + "column_break_28", + "depends_on", + "collapsible", + "collapsible_depends_on", + "hide_border", + "list__search_settings_section", + "in_list_view", + "in_standard_filter", + "in_preview", + "column_break_35", + "in_filter", + "in_global_search", + "permissions", + "read_only", + "allow_on_submit", + "ignore_user_permissions", + "allow_bulk_edit", + "column_break_13", + "permlevel", + "ignore_xss_filter", + "constraints_section", + "unique", + "no_copy", + "set_only_once", + "remember_last_selected_value", + "column_break_38", + "mandatory_depends_on", + "read_only_depends_on", + "display", + "print_width", + "width", + "max_height", + "columns", + "column_break_22", + "description", + "oldfieldname", + "oldfieldtype" + ], + "fields": [ + { + "fieldname": "label_and_type", + "fieldtype": "Section Break" + }, + { + "bold": 1, + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "oldfieldname": "label", + "oldfieldtype": "Data", + "print_width": "163", + "search_index": 1, + "width": "163" + }, + { + "bold": 1, + "default": "Data", + "fieldname": "fieldtype", + "fieldtype": "Select", + "in_list_view": 1, + "label": "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\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", + "reqd": 1, + "search_index": 1 + }, + { + "bold": 1, + "fieldname": "fieldname", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Name", + "oldfieldname": "fieldname", + "oldfieldtype": "Data", + "search_index": 1 + }, + { + "default": "0", + "depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)", + "fieldname": "reqd", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Mandatory", + "oldfieldname": "reqd", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "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", + "print_hide": 1 + }, + { + "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" + }, + { + "default": "0", + "fieldname": "search_index", + "fieldtype": "Check", + "label": "Index", + "oldfieldname": "search_index", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "in_list_view", + "fieldtype": "Check", + "label": "In List View", + "print_width": "70px", + "width": "70px" + }, + { + "default": "0", + "fieldname": "in_standard_filter", + "fieldtype": "Check", + "label": "In List 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", + "depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);", + "fieldname": "in_preview", + "fieldtype": "Check", + "label": "In Preview" + }, + { + "default": "0", + "fieldname": "allow_in_quick_entry", + "fieldtype": "Check", + "label": "Allow in Quick Entry" + }, + { + "default": "0", + "fieldname": "bold", + "fieldtype": "Check", + "label": "Bold" + }, + { + "default": "0", + "depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)", + "fieldname": "translatable", + "fieldtype": "Check", + "label": "Translatable" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype===\"Section Break\"", + "fieldname": "collapsible", + "fieldtype": "Check", + "label": "Collapsible", + "length": 255 + }, + { + "depends_on": "eval:doc.fieldtype==\"Section Break\" && doc.collapsible", + "fieldname": "collapsible_depends_on", + "fieldtype": "Code", + "label": "Collapsible Depends On (JS)", + "max_height": "3rem", + "options": "JS" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.", + "fieldname": "options", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Options", + "oldfieldname": "options", + "oldfieldtype": "Text" + }, + { + "fieldname": "default", + "fieldtype": "Small Text", + "label": "Default", + "max_height": "3rem", + "oldfieldname": "default", + "oldfieldtype": "Text" + }, + { + "fieldname": "fetch_from", + "fieldtype": "Small Text", + "label": "Fetch From" + }, + { + "default": "0", + "fieldname": "fetch_if_empty", + "fieldtype": "Check", + "label": "Fetch only if value is not set" + }, + { + "fieldname": "permissions", + "fieldtype": "Section Break", + "label": "Permissions" + }, + { + "fieldname": "depends_on", + "fieldtype": "Code", + "label": "Display Depends On (JS)", + "length": 255, + "max_height": "3rem", + "oldfieldname": "depends_on", + "oldfieldtype": "Data", + "options": "JS" + }, + { + "default": "0", + "fieldname": "hidden", + "fieldtype": "Check", + "label": "Hidden", + "oldfieldname": "hidden", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "read_only", + "fieldtype": "Check", + "label": "Read Only", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "unique", + "fieldtype": "Check", + "label": "Unique" + }, + { + "default": "0", + "fieldname": "set_only_once", + "fieldtype": "Check", + "label": "Set only once" + }, + { + "default": "0", + "depends_on": "eval: doc.fieldtype == \"Table\"", + "fieldname": "allow_bulk_edit", + "fieldtype": "Check", + "label": "Allow Bulk Edit" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "permlevel", + "fieldtype": "Int", + "label": "Perm Level", + "oldfieldname": "permlevel", + "oldfieldtype": "Int", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "ignore_user_permissions", + "fieldtype": "Check", + "label": "Ignore User Permissions" + }, + { + "default": "0", + "depends_on": "eval: parent.is_submittable", + "fieldname": "allow_on_submit", + "fieldtype": "Check", + "label": "Allow on Submit", + "oldfieldname": "allow_on_submit", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "report_hide", + "fieldtype": "Check", + "label": "Report Hide", + "oldfieldname": "report_hide", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "depends_on": "eval:(doc.fieldtype == 'Link')", + "fieldname": "remember_last_selected_value", + "fieldtype": "Check", + "label": "Remember Last Selected Value" + }, + { + "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" + }, + { + "fieldname": "display", + "fieldtype": "Section Break", + "label": "Display" + }, + { + "default": "0", + "fieldname": "in_filter", + "fieldtype": "Check", + "label": "In Filter", + "oldfieldname": "in_filter", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "no_copy", + "fieldtype": "Check", + "label": "No Copy", + "oldfieldname": "no_copy", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "default": "0", + "fieldname": "print_hide", + "fieldtype": "Check", + "label": "Print Hide", + "oldfieldname": "print_hide", + "oldfieldtype": "Check", + "print_width": "50px", + "width": "50px" + }, + { + "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", + "label": "Print Width", + "length": 10 + }, + { + "fieldname": "width", + "fieldtype": "Data", + "label": "Width", + "length": 10, + "oldfieldname": "width", + "oldfieldtype": "Data", + "print_width": "50px", + "width": "50px" + }, + { + "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": "column_break_22", + "fieldtype": "Column Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", + "width": "300px" + }, + { + "fieldname": "oldfieldname", + "fieldtype": "Data", + "hidden": 1, + "oldfieldname": "oldfieldname", + "oldfieldtype": "Data" + }, + { + "fieldname": "oldfieldtype", + "fieldtype": "Data", + "hidden": 1, + "oldfieldname": "oldfieldtype", + "oldfieldtype": "Data" + }, + { + "fieldname": "mandatory_depends_on", + "fieldtype": "Code", + "label": "Mandatory Depends On (JS)", + "max_height": "3rem", + "options": "JS" + }, + { + "fieldname": "read_only_depends_on", + "fieldtype": "Code", + "label": "Read Only Depends On (JS)", + "max_height": "3rem", + "options": "JS" + }, + { + "fieldname": "column_break_38", + "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_days", + "fieldtype": "Check", + "label": "Hide Days" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Duration'", + "fieldname": "hide_seconds", + "fieldtype": "Check", + "label": "Hide Seconds" + }, + { + "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": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "defaults_section", + "fieldtype": "Section Break", + "label": "Defaults", + "max_height": "2rem" + }, + { + "fieldname": "visibility_section", + "fieldtype": "Section Break", + "label": "Visibility" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" + }, + { + "fieldname": "constraints_section", + "fieldtype": "Section Break", + "label": "Constraints" + }, + { + "fieldname": "max_height", + "fieldtype": "Data", + "label": "Max Height", + "length": 10 + }, + { + "fieldname": "list__search_settings_section", + "fieldtype": "Section Break", + "label": "List / Search Settings" + }, + { + "fieldname": "column_break_35", + "fieldtype": "Column Break" + } + ], + "idx": 1, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2022-01-02 18:49:21.322889", + "modified_by": "Administrator", + "module": "Core", + "name": "DocField", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "ASC", + "states": [] } \ No newline at end of file From b77d08d26a3ab6e3a1192f9fc6315c23c860174e Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 3 Jan 2022 11:30:03 +0530 Subject: [PATCH 04/26] fix: Check if value for `applicable_for` exists before setting it null To avoid "Not Saved" state even after saving the document --- frappe/core/doctype/user_permission/user_permission.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user_permission/user_permission.js b/frappe/core/doctype/user_permission/user_permission.js index 4c3f5b4eb8..8d5c5c1a23 100644 --- a/frappe/core/doctype/user_permission/user_permission.js +++ b/frappe/core/doctype/user_permission/user_permission.js @@ -44,7 +44,7 @@ frappe.ui.form.on('User Permission', { set_applicable_for_constraint: frm => { frm.toggle_reqd('applicable_for', !frm.doc.apply_to_all_doctypes); - if (frm.doc.apply_to_all_doctypes) { + if (frm.doc.apply_to_all_doctypes && frm.doc.applicable_for) { frm.set_value('applicable_for', null); } }, From b2d16e7db8af27319bcbeed37e594483dabafe9c Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 3 Jan 2022 11:31:06 +0530 Subject: [PATCH 05/26] refactor: Re-arraange fields in user permission to avoid confusion --- .../doctype/user_permission/user_permission.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/user_permission/user_permission.json b/frappe/core/doctype/user_permission/user_permission.json index 9cea0856c9..60b6779bfd 100644 --- a/frappe/core/doctype/user_permission/user_permission.json +++ b/frappe/core/doctype/user_permission/user_permission.json @@ -8,8 +8,8 @@ "field_order": [ "user", "allow", - "column_break_3", "for_value", + "column_break_3", "is_default", "advanced_control_section", "apply_to_all_doctypes", @@ -37,10 +37,6 @@ "options": "DocType", "reqd": 1 }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, { "fieldname": "for_value", "fieldtype": "Dynamic Link", @@ -87,10 +83,14 @@ "fieldtype": "Check", "hidden": 1, "label": "Hide Descendants" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" } ], "links": [], - "modified": "2021-01-21 18:14:10.839381", + "modified": "2022-01-03 11:25:03.726150", "modified_by": "Administrator", "module": "Core", "name": "User Permission", @@ -111,6 +111,7 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "user", "track_changes": 1 } \ No newline at end of file From 0868ad02f84ce5a3593fa9d89fbcf597887ac6fc Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 3 Jan 2022 11:59:11 +0530 Subject: [PATCH 06/26] refactor: Re-arrange user form - To fix order of fields in quick entry form --- frappe/core/doctype/user/user.json | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index cf05ce0c15..48af4d3beb 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -10,15 +10,15 @@ "enabled", "section_break_3", "email", + "first_name", + "middle_name", "last_name", - "language", "column_break0", - "first_name", "full_name", - "time_zone", - "column_break_11", - "middle_name", "username", + "column_break_11", + "language", + "time_zone", "send_welcome_email", "unsubscribed", "user_image", @@ -669,7 +669,7 @@ } ], "max_attachments": 5, - "modified": "2021-11-17 17:17:16.098457", + "modified": "2022-01-03 11:53:25.250822", "modified_by": "Administrator", "module": "Core", "name": "User", @@ -702,6 +702,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "title_field": "full_name", "track_changes": 1 -} +} \ No newline at end of file From 66a8e125d43040960f159784ddca79620169b45a Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 3 Jan 2022 13:57:36 +0530 Subject: [PATCH 07/26] fix(ux): add "Show Dashboard" check for tab breaks --- frappe/core/doctype/docfield/docfield.json | 10 +++- .../core/doctype/module_def/module_def.json | 12 ++++- frappe/database/mariadb/framework_mariadb.sql | 1 + .../database/postgres/framework_postgres.sql | 1 + frappe/public/js/frappe/form/form.js | 2 +- frappe/website/doctype/web_page/web_page.json | 52 +++++++++++++------ 6 files changed, 57 insertions(+), 21 deletions(-) diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 7a8b52c972..26ddce7d35 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -20,6 +20,7 @@ "search_index", "column_break_18", "options", + "show_dashboard", "defaults_section", "default", "column_break_6", @@ -526,13 +527,20 @@ { "fieldname": "column_break_35", "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype===\"Tab Break\"", + "fieldname": "show_dashboard", + "fieldtype": "Check", + "label": "Show Dashboard" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-01-02 18:49:21.322889", + "modified": "2022-01-03 11:56:19.812863", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/core/doctype/module_def/module_def.json b/frappe/core/doctype/module_def/module_def.json index 7ddc55fce5..12830c8b4f 100644 --- a/frappe/core/doctype/module_def/module_def.json +++ b/frappe/core/doctype/module_def/module_def.json @@ -10,7 +10,8 @@ "custom", "package", "app_name", - "restrict_to_domain" + "restrict_to_domain", + "connections_tab" ], "fields": [ { @@ -50,6 +51,12 @@ "fieldtype": "Link", "label": "Package", "options": "Package" + }, + { + "fieldname": "connections_tab", + "fieldtype": "Tab Break", + "label": "Connections", + "show_dashboard": 1 } ], "icon": "fa fa-sitemap", @@ -116,7 +123,7 @@ "link_fieldname": "module" } ], - "modified": "2021-09-05 21:58:40.253909", + "modified": "2022-01-03 13:56:52.817954", "modified_by": "Administrator", "module": "Core", "name": "Module Def", @@ -154,5 +161,6 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "ASC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql index 73b98f0ff3..cfb4e243a2 100644 --- a/frappe/database/mariadb/framework_mariadb.sql +++ b/frappe/database/mariadb/framework_mariadb.sql @@ -25,6 +25,7 @@ CREATE TABLE `tabDocField` ( `oldfieldtype` varchar(255) DEFAULT NULL, `options` text, `search_index` int(1) NOT NULL DEFAULT 0, + `show_dashboard` int(1) NOT NULL DEFAULT 0, `hidden` int(1) NOT NULL DEFAULT 0, `set_only_once` int(1) NOT NULL DEFAULT 0, `allow_in_quick_entry` int(1) NOT NULL DEFAULT 0, diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql index e8e047f194..f911e34650 100644 --- a/frappe/database/postgres/framework_postgres.sql +++ b/frappe/database/postgres/framework_postgres.sql @@ -27,6 +27,7 @@ CREATE TABLE "tabDocField" ( "search_index" smallint NOT NULL DEFAULT 0, "hidden" smallint NOT NULL DEFAULT 0, "set_only_once" smallint NOT NULL DEFAULT 0, + "show_dashboard" smallint NOT NULL DEFAULT 0, "allow_in_quick_entry" smallint NOT NULL DEFAULT 0, "print_hide" smallint NOT NULL DEFAULT 0, "report_hide" smallint NOT NULL DEFAULT 0, diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 9a75e510da..57e3f576a1 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -215,7 +215,7 @@ frappe.ui.form.Form = class FrappeForm { if (this.layout.tabs.length) { this.layout.tabs.every(tab => { - if (tab.df.options === 'Dashboard') { + if (tab.df.show_dashboard) { tab.wrapper.prepend(dashboard_parent); dashboard_added = true; return false; diff --git a/frappe/website/doctype/web_page/web_page.json b/frappe/website/doctype/web_page/web_page.json index 2852d93705..b1fdd02af7 100644 --- a/frappe/website/doctype/web_page/web_page.json +++ b/frappe/website/doctype/web_page/web_page.json @@ -11,12 +11,10 @@ "section_title", "title", "route", - "published", "dynamic_route", "cb1", + "published", "module", - "start_date", - "end_date", "sb1", "content_type", "slideshow", @@ -25,6 +23,7 @@ "main_section_md", "main_section_html", "page_blocks", + "scripting_tab", "context_section", "context_script", "custom_javascript", @@ -34,28 +33,32 @@ "text_align", "css", "full_width", - "settings", "show_title", + "settings", + "publishing_dates_section", + "start_date", + "column_break_30", + "end_date", + "metatags_section", + "meta_title", + "meta_description", + "meta_image", + "set_meta_tags", "section_break_17", "show_sidebar", + "idx", "website_sidebar", "column_break_20", "enable_comments", - "idx", "sb2", "header", - "breadcrumbs", - "metatags_section", - "meta_title", - "meta_description", - "meta_image", - "set_meta_tags" + "breadcrumbs" ], "fields": [ { "fieldname": "section_title", - "fieldtype": "Section Break", - "label": "Title" + "fieldtype": "Tab Break", + "label": "Content" }, { "fieldname": "title", @@ -161,7 +164,7 @@ "collapsible": 1, "collapsible_depends_on": "insert_style", "fieldname": "custom_css", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Style" }, { @@ -185,7 +188,7 @@ }, { "fieldname": "settings", - "fieldtype": "Section Break", + "fieldtype": "Tab Break", "label": "Settings" }, { @@ -267,7 +270,6 @@ "label": "Full Width" }, { - "collapsible": 1, "fieldname": "metatags_section", "fieldtype": "Section Break", "label": "Meta Tags" @@ -313,6 +315,21 @@ "fieldtype": "Link", "label": "Module (for export)", "options": "Module Def" + }, + { + "fieldname": "scripting_tab", + "fieldtype": "Tab Break", + "label": "Scripting", + "show_dashboard": 1 + }, + { + "fieldname": "publishing_dates_section", + "fieldtype": "Section Break", + "label": "Publishing Dates" + }, + { + "fieldname": "column_break_30", + "fieldtype": "Column Break" } ], "has_web_view": 1, @@ -322,7 +339,7 @@ "is_published_field": "published", "links": [], "max_attachments": 20, - "modified": "2021-09-04 12:11:56.070994", + "modified": "2022-01-03 13:01:48.182645", "modified_by": "Administrator", "module": "Website", "name": "Web Page", @@ -342,6 +359,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "ASC", + "states": [], "title_field": "title", "track_changes": 1 } \ No newline at end of file From 9258941151616c7099c09564975a70c9cd3bc642 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 3 Jan 2022 14:25:42 +0530 Subject: [PATCH 08/26] fix: Do not allow rebuilding of tree for doctypes without `lft` & `rgt` --- frappe/utils/nestedset.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/utils/nestedset.py b/frappe/utils/nestedset.py index 5c4beda71e..bdab81bcaa 100644 --- a/frappe/utils/nestedset.py +++ b/frappe/utils/nestedset.py @@ -144,6 +144,11 @@ def rebuild_tree(doctype, parent_field): if frappe.request and frappe.local.form_dict.cmd == 'rebuild_tree': frappe.only_for('System Manager') + meta = frappe.get_meta(doctype) + if not meta.has_field("lft") or not meta.has_field("rgt"): + frappe.throw(_("Rebuilding of tree is not supported for {}").format(frappe.bold(doctype)), + title=_("Invalid Action")) + # get all roots right = 1 table = DocType(doctype) From 1fcd6cb2a43482a34191310b9c41734627e374e1 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 3 Jan 2022 14:51:57 +0530 Subject: [PATCH 09/26] fix(ux): Add "Show Dashboard" in Customize Form" --- .../doctype/customize_form/customize_form.py | 1 + .../customize_form_field.json | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 0b17200c6f..24a5d1358b 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -516,6 +516,7 @@ docfield_properties = { 'options': 'Text', 'fetch_from': 'Small Text', 'fetch_if_empty': 'Check', + 'show_dashboard': 'Check', 'permlevel': 'Int', 'width': 'Data', 'print_width': 'Data', diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index 986b99a7af..a545cd9fe1 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -28,6 +28,7 @@ "options", "fetch_from", "fetch_if_empty", + "show_dashboard", "permissions", "depends_on", "permlevel", @@ -82,7 +83,7 @@ "label": "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\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nTab Break", + "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\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTab Break\nTable\nTable MultiSelect\nText\nText Editor\nTime", "reqd": 1, "search_index": 1 }, @@ -422,18 +423,27 @@ "fieldname": "non_negative", "fieldtype": "Check", "label": "Non Negative" + }, + { + "default": "0", + "depends_on": "eval:doc.fieldtype=='Tab Break'", + "fieldname": "show_dashboard", + "fieldtype": "Check", + "label": "Show Dashboard" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-07-11 21:57:24.479749", + "modified": "2022-01-03 14:50:32.035768", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "states": [] } \ No newline at end of file From d1ca3fa78c69aefb3da20470cc0537fc83e0b179 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 3 Jan 2022 15:26:16 +0530 Subject: [PATCH 10/26] fix: Hide "Rebuild Tree" option if treeview is loaded for non-nestedset doctype --- frappe/public/js/frappe/views/treeview.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/treeview.js b/frappe/public/js/frappe/views/treeview.js index cc0a233003..7179e4ab56 100644 --- a/frappe/public/js/frappe/views/treeview.js +++ b/frappe/public/js/frappe/views/treeview.js @@ -409,7 +409,9 @@ frappe.views.TreeView = class TreeView { }, ]; - if (frappe.user.has_role('System Manager')) { + if (frappe.user.has_role('System Manager') && + frappe.meta.has_field(me.doctype, "lft") && + frappe.meta.has_field(me.doctype, "rgt")) { this.menu_items.push( { label: __('Rebuild Tree'), From 3064b6138539d54c1d9e76043282cae03f4a7272 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 20 Dec 2021 13:38:06 +0530 Subject: [PATCH 11/26] feat(blog): Browse by category Add option to filter blogs by category on blog list --- frappe/website/doctype/blog_post/blog_post.py | 34 +++++++++++------ .../blog_post/templates/blog_post_list.html | 38 ++++++++++++++----- .../doctype/blog_settings/blog_settings.json | 12 +++++- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index 3536896a5f..5d5e9e11c4 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -159,10 +159,10 @@ class BlogPost(WebsiteGenerator): like_count = 0 if frappe.db.count('Feedback'): - like_count = frappe.db.count('Feedback', + like_count = frappe.db.count('Feedback', filters = dict( - reference_doctype = self.doctype, - reference_name = self.name, + reference_doctype = self.doctype, + reference_name = self.name, like = True ) ) @@ -183,7 +183,6 @@ def get_list_context(context=None): get_list = get_blog_list, no_breadcrumbs = True, hide_filters = True, - children = get_children(), # show_search = True, title = _('Blog') ) @@ -208,17 +207,28 @@ def get_list_context(context=None): else: list_context.parents = [{"name": _("Home"), "route": "/"}] - list_context.update(frappe.get_doc("Blog Settings").as_dict(no_default_fields=True)) + blog_settings = frappe.get_doc("Blog Settings").as_dict(no_default_fields=True) + list_context.update(blog_settings) + + if blog_settings.browse_by_category: + list_context.blog_categories = get_blog_categories() return list_context -def get_children(): - return frappe.db.sql("""select route as name, - title from `tabBlog Category` - where published = 1 - and exists (select name from `tabBlog Post` - where `tabBlog Post`.blog_category=`tabBlog Category`.name and published=1) - order by title asc""", as_dict=1) + +def get_blog_categories(): + return frappe.db.sql(""" + SELECT `name`, `route`, `title` + FROM `tabBlog Category` + WHERE `published` = 1 + AND EXISTS ( + SELECT `name` + FROM `tabBlog Post` + WHERE `tabBlog Post`.`blog_category` = `tabBlog Category`.`name` + AND `published` = 1 + ) + ORDER BY `title` asc + """, as_dict=1) def clear_blog_cache(): for blog in frappe.db.sql_list("""select route from diff --git a/frappe/website/doctype/blog_post/templates/blog_post_list.html b/frappe/website/doctype/blog_post/templates/blog_post_list.html index ba1f95faff..2b3d5e250c 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post_list.html +++ b/frappe/website/doctype/blog_post/templates/blog_post_list.html @@ -4,16 +4,34 @@ {% block page_content %} -{{ web_block("Hero", - values={ - 'title': blog_title or _("Blog"), - 'subtitle': blog_introduction or '', - }, - add_container=0, - add_top_padding=0, - add_bottom_padding=0, - css_class="py-5" -) }} +
+
+
+
+

{{ blog_title or _('Blog') }}

+

{{ blog_introduction or '' }}

+
+
+
+
+ {%- if browse_by_category -%} + + + {%- endif -%} +
+
diff --git a/frappe/website/doctype/blog_settings/blog_settings.json b/frappe/website/doctype/blog_settings/blog_settings.json index 46f6126581..aed1e77969 100644 --- a/frappe/website/doctype/blog_settings/blog_settings.json +++ b/frappe/website/doctype/blog_settings/blog_settings.json @@ -11,6 +11,7 @@ "enable_social_sharing", "show_cta_in_blog", "allow_guest_to_comment", + "browse_by_category", "cta_section", "title", "subtitle", @@ -110,14 +111,20 @@ "default": "1", "fieldname": "allow_guest_to_comment", "fieldtype": "Check", - "label": "Allow guest to comment" + "label": "Allow Guest to comment" + }, + { + "default": "0", + "fieldname": "browse_by_category", + "fieldtype": "Check", + "label": "Browse by category" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2021-10-28 20:44:44.143193", + "modified": "2021-12-20 13:40:32.312459", "modified_by": "Administrator", "module": "Website", "name": "Blog Settings", @@ -142,5 +149,6 @@ ], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From 84be09f93af75702cdb748cacff5a3eebe409f3e Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 20 Dec 2021 16:50:53 +0530 Subject: [PATCH 12/26] fix: convert raw sql to frappe.qb --- frappe/website/doctype/blog_post/blog_post.py | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index 5d5e9e11c4..9ac51133fa 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -217,18 +217,24 @@ def get_list_context(context=None): def get_blog_categories(): - return frappe.db.sql(""" - SELECT `name`, `route`, `title` - FROM `tabBlog Category` - WHERE `published` = 1 - AND EXISTS ( - SELECT `name` - FROM `tabBlog Post` - WHERE `tabBlog Post`.`blog_category` = `tabBlog Category`.`name` - AND `published` = 1 + from pypika import Order + from pypika.terms import ExistsCriterion + + post, category = frappe.qb.DocType("Blog Post"), frappe.qb.DocType("Blog Category") + return ( + frappe.qb.from_(category) + .select(category.name, category.route, category.title) + .where( + (category.published == 1) + & ExistsCriterion( + frappe.qb.from_(post) + .select("name") + .where((post.published == 1) & (post.blog_category == category.name)) ) - ORDER BY `title` asc - """, as_dict=1) + ) + .orderby(category.title, order=Order.asc) + .run(as_dict=1) + ) def clear_blog_cache(): for blog in frappe.db.sql_list("""select route from From b73ed8ffbc4421f0a308837bc2d50b6c2e7469fa Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 3 Jan 2022 16:17:37 +0530 Subject: [PATCH 13/26] test: UI test for blog category dropdown --- .../doctype/blog_post/ui_test_blog_post.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 frappe/website/doctype/blog_post/ui_test_blog_post.js diff --git a/frappe/website/doctype/blog_post/ui_test_blog_post.js b/frappe/website/doctype/blog_post/ui_test_blog_post.js new file mode 100644 index 0000000000..3029a5c6f1 --- /dev/null +++ b/frappe/website/doctype/blog_post/ui_test_blog_post.js @@ -0,0 +1,36 @@ +context('Blog Post', () => { + before(() => { + cy.login(); + cy.visit('/app'); + }); + + it('Blog Category dropdown works as expected', () => { + cy.create_records([ + { + doctype: 'Blog Category', + title: 'Category 1', + published: 1 + }, + { + doctype: 'Blogger', + short_name: 'John', + full_name: 'John Doe' + }, + { + doctype: 'Blog Post', + title: 'Test Blog Post', + content: 'Test Blog Post Content', + blog_category: 'category-1', + blogger: 'John', + published: 1 + } + ]); + cy.set_value('Blog Settings', 'Blog Settings', {browse_by_category: 1}); + cy.visit('/blog'); + cy.findByLabelText('Browse by category').select('Category 1'); + cy.location('pathname').should('eq', '/blog/category-1'); + cy.set_value('Blog Settings', 'Blog Settings', {browse_by_category: 0}); + cy.visit('/blog'); + cy.findByLabelText('Browse by category').should('not.exist'); + }); +}); From f7e8135a0cad87f08912dc61fc30f7d76d4c514b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Mon, 3 Jan 2022 16:18:43 +0530 Subject: [PATCH 14/26] chore: colocate ui tests UI tests can now be colocated where they make more sense. The filename must start with ui_test_ and end with .js to be picked up by cypress. --- cypress.json | 4 +++- cypress/support/commands.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cypress.json b/cypress.json index ae4495cfa8..15f8f230fa 100644 --- a/cypress.json +++ b/cypress.json @@ -9,5 +9,7 @@ "retries": { "runMode": 2, "openMode": 2 - } + }, + "integrationFolder": ".", + "testFiles": ["cypress/integration/*.js", "**/ui_test_*.js"] } diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 933f6a1758..4fe315c372 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -30,7 +30,7 @@ Cypress.Commands.add('login', (email, password) => { email = 'Administrator'; } if (!password) { - password = Cypress.config('adminPassword'); + password = Cypress.env('adminPassword'); } cy.request({ url: '/api/method/login', @@ -161,7 +161,7 @@ Cypress.Commands.add('remove_doc', (doctype, name) => { Cypress.Commands.add('create_records', doc => { return cy - .call('frappe.tests.ui_test_helpers.create_if_not_exists', {doc}) + .call('frappe.tests.ui_test_helpers.create_if_not_exists', {doc: JSON.stringify(doc)}) .then(r => r.message); }); From 3e009e9bdb95ab8b4f684e4bc8a30829fb8ae6b5 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 3 Jan 2022 17:39:23 +0530 Subject: [PATCH 15/26] fix: Use copy of an array while adding new content for proper change trigger --- frappe/public/js/frappe/form/controls/multiselect_list.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/form/controls/multiselect_list.js b/frappe/public/js/frappe/form/controls/multiselect_list.js index 8c79071762..5b25b75279 100644 --- a/frappe/public/js/frappe/form/controls/multiselect_list.js +++ b/frappe/public/js/frappe/form/controls/multiselect_list.js @@ -109,6 +109,7 @@ frappe.ui.form.ControlMultiSelectList = class ControlMultiSelectList extends fra let value = decodeURIComponent($selectable_item.data().value); if ($selectable_item.hasClass('selected')) { + this.values = this.values.slice(); this.values.push(value); } else { this.values = this.values.filter(val => val !== value); From b0b19d7a09d6233e0b14c28b805fe893c528ff99 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 3 Jan 2022 18:25:12 +0530 Subject: [PATCH 16/26] fix: renaming a document updates the modified timestamp --- frappe/model/rename_doc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 1db89493f2..0ecf78ff54 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -284,7 +284,7 @@ def update_link_field_values(link_fields, old, new, doctype): if parent == new and doctype == "DocType": parent = old - frappe.db.set_value(parent, {docfield: old}, docfield, new) + frappe.db.set_value(parent, {docfield: old}, docfield, new, update_modified=False) # update cached link_fields as per new if doctype=='DocType' and field['parent'] == old: From e08b41964cf284442b8bc888f0f56dca45f098a7 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 3 Jan 2022 21:38:34 +0530 Subject: [PATCH 17/26] feat: savepoint contextmanager syntactic sugar around frappe.db.savepoint and rollback --- frappe/database/__init__.py | 2 ++ frappe/database/database.py | 33 ++++++++++++++++++++++++++++++++- frappe/tests/test_db.py | 27 +++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/frappe/database/__init__.py b/frappe/database/__init__.py index b0e3183d4f..7b26ac31b3 100644 --- a/frappe/database/__init__.py +++ b/frappe/database/__init__.py @@ -4,6 +4,8 @@ # Database Module # -------------------- +from frappe.database.database import savepoint + def setup_database(force, source_sql=None, verbose=None, no_mariadb_socket=False): import frappe if frappe.conf.db_type == 'postgres': diff --git a/frappe/database/database.py b/frappe/database/database.py index 7c147cd1d0..2cb3098f79 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -6,11 +6,14 @@ import re import time -from typing import Dict, List, Union +import string +import random +from typing import Dict, List, Union, Tuple, Optional import frappe import datetime import frappe.defaults import frappe.model.meta +from contextlib import contextmanager from frappe import _ from time import time @@ -811,6 +814,9 @@ class Database(object): Avoid using savepoints when writing to filesystem.""" self.sql(f"savepoint {save_point}") + def release_savepoint(self, save_point): + self.sql(f"release savepoint {save_point}") + def rollback(self, *, save_point=None): """`ROLLBACK` current transaction. Optionally rollback to a known save_point.""" if save_point: @@ -1097,3 +1103,28 @@ def enqueue_jobs_after_commit(): q.enqueue_call(execute_job, timeout=job.get("timeout"), kwargs=job.get("queue_args")) frappe.flags.enqueue_after_commit = [] + +@contextmanager +def savepoint(catch: Union[type, Tuple[type, ...]] = Exception): + """ Wrapper for wrapping blocks of DB operations in a savepoint. + + as contextmanager: + + for doc in docs: + with savepoint(catch=DuplicateError): + doc.insert() + + as decorator (wraps FULL function call): + + @savepoint(catch=DuplicateError) + def process_doc(doc): + doc.insert() + """ + try: + savepoint = ''.join(random.sample(string.ascii_lowercase, 10)) + frappe.db.savepoint(savepoint) + yield # control back to calling function + except catch: + frappe.db.rollback(save_point=savepoint) + else: + frappe.db.release_savepoint(savepoint) diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index dec55b4714..cdef4354ed 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -12,6 +12,7 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.utils import random_string from frappe.utils.testutils import clear_custom_fields from frappe.query_builder import Field +from frappe.database import savepoint from .test_query_builder import run_only_if, db_type_is from frappe.query_builder.functions import Concat_ws @@ -267,6 +268,32 @@ class TestDB(unittest.TestCase): for d in created_docs: self.assertTrue(frappe.db.exists("ToDo", d)) + def test_savepoints_wrapper(self): + frappe.db.rollback() + + class SpecificExc(Exception): + pass + + created_docs = [] + failed_docs = [] + + for _ in range(5): + with savepoint(catch=SpecificExc): + doc_kept = frappe.get_doc(doctype="ToDo", description="nope").save() + created_docs.append(doc_kept.name) + + with savepoint(catch=SpecificExc): + doc_gone = frappe.get_doc(doctype="ToDo", description="nope").save() + failed_docs.append(doc_gone.name) + raise SpecificExc + + frappe.db.commit() + + for d in failed_docs: + self.assertFalse(frappe.db.exists("ToDo", d)) + for d in created_docs: + self.assertTrue(frappe.db.exists("ToDo", d)) + @run_only_if(db_type_is.MARIADB) class TestDDLCommandsMaria(unittest.TestCase): From 3c830433015f39ae0e8cbbfd289d56aef05123b0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 4 Jan 2022 10:53:31 +0530 Subject: [PATCH 18/26] chore: sort imports also fix `time` import --- frappe/database/database.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frappe/database/database.py b/frappe/database/database.py index 2cb3098f79..a157343be6 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -4,19 +4,18 @@ # Database Module # -------------------- +import datetime +import random import re -import time import string -import random -from typing import Dict, List, Union, Tuple, Optional +from contextlib import contextmanager +from time import time +from typing import Dict, List, Union, Tuple + import frappe -import datetime import frappe.defaults import frappe.model.meta -from contextlib import contextmanager - from frappe import _ -from time import time from frappe.utils import now, getdate, cast, get_datetime from frappe.model.utils.link_count import flush_local_link_count from frappe.query_builder.functions import Count From a2b808075b9df7501663a5a773dc160b93759a23 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 29 Dec 2021 20:10:47 +0530 Subject: [PATCH 19/26] fix: Route options not getting set while navigating from one report to another (cherry picked from commit 124ae9138891a1caf8908b2c9ab578d52992f410) --- .../js/frappe/views/reports/query_report.js | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 448b3f6fd2..d89447118f 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -105,15 +105,18 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.toggle_nothing_to_show(true); return; } + + let route_options = {} + route_options = Object.assign(route_options, frappe.route_options); if (this.report_name !== frappe.get_route()[1]) { // different report - this.load_report(); + this.load_report(route_options); } else if (frappe.has_route_options()) { // filters passed through routes // so refresh report again - this.refresh_report(); + this.refresh_report(route_options); } else { // same report // don't do anything to preserve state @@ -121,7 +124,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } } - load_report() { + load_report(route_options) { this.page.clear_inner_toolbar(); this.route = frappe.get_route(); this.page_name = frappe.get_route_str(); @@ -137,7 +140,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { () => this.get_report_settings(), () => this.setup_progress_bar(), () => this.setup_page_head(), - () => this.refresh_report(), + () => this.refresh_report(route_options), () => this.add_chart_buttons_to_toolbar(true), () => this.add_card_button_to_toolbar(true), ]); @@ -343,13 +346,13 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }); } - refresh_report() { + refresh_report(route_options) { this.toggle_message(true); this.toggle_report(false); return frappe.run_serially([ () => this.setup_filters(), - () => this.set_route_filters(), + () => this.set_route_filters(route_options), () => this.page.clear_custom_actions(), () => this.report_settings.onload && this.report_settings.onload(this), () => this.refresh() @@ -525,15 +528,17 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { }); } - set_route_filters() { - if(frappe.route_options) { - const fields = Object.keys(frappe.route_options); + set_route_filters(route_options) { + if (!route_options) route_options = frappe.route_options; + + if(route_options) { + const fields = Object.keys(route_options); const filters_to_set = this.filters.filter(f => fields.includes(f.df.fieldname)); const promises = filters_to_set.map(f => { return () => { - const value = frappe.route_options[f.df.fieldname]; + const value = route_options[f.df.fieldname]; f.set_value(value); }; }); From f21d81a58a64faae450757b6ff0944f8face3627 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 3 Jan 2022 12:22:13 +0530 Subject: [PATCH 20/26] fix: Linting Issues (cherry picked from commit b28c75a82fcef86af25a47768e050558825612ee) --- frappe/public/js/frappe/views/reports/query_report.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index d89447118f..920a252b56 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -106,7 +106,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return; } - let route_options = {} + let route_options = {}; route_options = Object.assign(route_options, frappe.route_options); if (this.report_name !== frappe.get_route()[1]) { @@ -531,7 +531,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { set_route_filters(route_options) { if (!route_options) route_options = frappe.route_options; - if(route_options) { + if (route_options) { const fields = Object.keys(route_options); const filters_to_set = this.filters.filter(f => fields.includes(f.df.fieldname)); From bb004fc33bb03764056efd2549a99736cfea685a Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 4 Jan 2022 13:32:44 +0530 Subject: [PATCH 21/26] fix: Remove redundant `owner` fields in Email Template, Help Article --- .../email/doctype/email_template/email_template.json | 11 +---------- .../website/doctype/help_article/help_article.json | 12 ++---------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/frappe/email/doctype/email_template/email_template.json b/frappe/email/doctype/email_template/email_template.json index dc73acacc1..c6ec971da4 100644 --- a/frappe/email/doctype/email_template/email_template.json +++ b/frappe/email/doctype/email_template/email_template.json @@ -12,7 +12,6 @@ "use_html", "response_html", "response", - "owner", "section_break_4", "email_reply_help" ], @@ -32,14 +31,6 @@ "label": "Response", "mandatory_depends_on": "eval:!doc.use_html" }, - { - "default": "user", - "fieldname": "owner", - "fieldtype": "Link", - "hidden": 1, - "label": "Owner", - "options": "User" - }, { "fieldname": "section_break_4", "fieldtype": "Section Break" @@ -66,7 +57,7 @@ ], "icon": "fa fa-comment", "links": [], - "modified": "2020-11-30 14:12:50.321633", + "modified": "2022-01-04 14:12:50.321633", "modified_by": "Administrator", "module": "Email", "name": "Email Template", diff --git a/frappe/website/doctype/help_article/help_article.json b/frappe/website/doctype/help_article/help_article.json index 2c69de6ff2..acb24f7083 100644 --- a/frappe/website/doctype/help_article/help_article.json +++ b/frappe/website/doctype/help_article/help_article.json @@ -15,8 +15,7 @@ "section_break_7", "content", "likes", - "route", - "owner" + "route" ], "fields": [ { @@ -79,13 +78,6 @@ "fieldtype": "Data", "in_global_search": 1, "label": "Route" - }, - { - "default": "user", - "fieldname": "owner", - "fieldtype": "Link", - "label": "Owner", - "options": "User" } ], "has_web_view": 1, @@ -93,7 +85,7 @@ "index_web_pages_for_search": 1, "is_published_field": "published", "links": [], - "modified": "2020-07-21 16:25:18.577325", + "modified": "2022-01-04 16:25:18.577325", "modified_by": "Administrator", "module": "Website", "name": "Help Article", From 6c6ff2c16dc7b05bc256416417d74f91bc48984b Mon Sep 17 00:00:00 2001 From: shadrak gurupnor Date: Tue, 31 Aug 2021 19:41:22 +0530 Subject: [PATCH 22/26] fix: overriding of owner of doc feat: Fixed the dilemma of owner field in ToDo document --- .../assignment_rule/assignment_rule.py | 2 +- .../assignment_rule/test_assignment_rule.py | 28 +++++++++---------- frappe/core/doctype/user/test_user.py | 2 +- frappe/core/doctype/user/user.py | 2 +- frappe/core/notifications.py | 2 +- frappe/desk/doctype/event/test_event.py | 2 +- frappe/desk/doctype/todo/todo.json | 22 +++++++-------- frappe/desk/doctype/todo/todo.py | 18 ++++++------ frappe/desk/form/assign_to.py | 28 +++++++++---------- frappe/desk/listview.py | 6 ++-- frappe/model/document.py | 12 ++++++-- frappe/tests/test_assign.py | 2 +- frappe/tests/test_document.py | 19 +++++++++++++ 13 files changed, 85 insertions(+), 60 deletions(-) diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index a3e27d4da5..50a6f8b17e 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -109,7 +109,7 @@ class AssignmentRule(Document): user = d.user, count = frappe.db.count('ToDo', dict( reference_type = self.document_type, - owner = d.user, + allocated_to = d.user, status = "Open")) )) diff --git a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py index 1c9e177f94..84b8dbd3c8 100644 --- a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py @@ -30,7 +30,7 @@ class TestAutoAssign(unittest.TestCase): reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), 'test@example.com') + ), 'allocated_to'), 'test@example.com') note = make_note(dict(public=1)) @@ -39,7 +39,7 @@ class TestAutoAssign(unittest.TestCase): reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), 'test1@example.com') + ), 'allocated_to'), 'test1@example.com') clear_assignments() @@ -51,7 +51,7 @@ class TestAutoAssign(unittest.TestCase): reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), 'test2@example.com') + ), 'allocated_to'), 'test2@example.com') # check loop back to first user note = make_note(dict(public=1)) @@ -60,7 +60,7 @@ class TestAutoAssign(unittest.TestCase): reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), 'test@example.com') + ), 'allocated_to'), 'test@example.com') def test_load_balancing(self): self.assignment_rule.rule = 'Load Balancing' @@ -71,11 +71,11 @@ class TestAutoAssign(unittest.TestCase): # check if each user has 10 assignments (?) for user in ('test@example.com', 'test1@example.com', 'test2@example.com'): - self.assertEqual(len(frappe.get_all('ToDo', dict(owner = user, reference_type = 'Note'))), 10) + self.assertEqual(len(frappe.get_all('ToDo', dict(allocated_to = user, reference_type = 'Note'))), 10) # clear 5 assignments for first user # can't do a limit in "delete" since postgres does not support it - for d in frappe.get_all('ToDo', dict(reference_type = 'Note', owner = 'test@example.com'), limit=5): + for d in frappe.get_all('ToDo', dict(reference_type = 'Note', allocated_to = 'test@example.com'), limit=5): frappe.db.delete("ToDo", {"name": d.name}) # add 5 more assignments @@ -84,7 +84,7 @@ class TestAutoAssign(unittest.TestCase): # check if each user still has 10 assignments for user in ('test@example.com', 'test1@example.com', 'test2@example.com'): - self.assertEqual(len(frappe.get_all('ToDo', dict(owner = user, reference_type = 'Note'))), 10) + self.assertEqual(len(frappe.get_all('ToDo', dict(allocated_to = user, reference_type = 'Note'))), 10) def test_based_on_field(self): self.assignment_rule.rule = 'Based on Field' @@ -119,7 +119,7 @@ class TestAutoAssign(unittest.TestCase): reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), None) + ), 'allocated_to'), None) def test_clear_assignment(self): note = make_note(dict(public=1)) @@ -132,7 +132,7 @@ class TestAutoAssign(unittest.TestCase): ))[0] todo = frappe.get_doc('ToDo', todo['name']) - self.assertEqual(todo.owner, 'test@example.com') + self.assertEqual(todo.allocated_to, 'test@example.com') # test auto unassign note.public = 0 @@ -154,7 +154,7 @@ class TestAutoAssign(unittest.TestCase): ))[0] todo = frappe.get_doc('ToDo', todo['name']) - self.assertEqual(todo.owner, 'test@example.com') + self.assertEqual(todo.allocated_to, 'test@example.com') note.content="Closed" note.save() @@ -164,7 +164,7 @@ class TestAutoAssign(unittest.TestCase): # check if todo is closed self.assertEqual(todo.status, 'Closed') # check if closed todo retained assignment - self.assertEqual(todo.owner, 'test@example.com') + self.assertEqual(todo.allocated_to, 'test@example.com') def check_multiple_rules(self): note = make_note(dict(public=1, notify_on_login=1)) @@ -174,7 +174,7 @@ class TestAutoAssign(unittest.TestCase): reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), 'test@example.com') + ), 'allocated_to'), 'test@example.com') def check_assignment_rule_scheduling(self): frappe.db.delete("Assignment Rule") @@ -192,7 +192,7 @@ class TestAutoAssign(unittest.TestCase): reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), ['test@example.com', 'test1@example.com', 'test2@example.com']) + ), 'allocated_to'), ['test@example.com', 'test1@example.com', 'test2@example.com']) frappe.flags.assignment_day = "Friday" note = make_note(dict(public=1)) @@ -201,7 +201,7 @@ class TestAutoAssign(unittest.TestCase): reference_type = 'Note', reference_name = note.name, status = 'Open' - ), 'owner'), ['test3@example.com']) + ), 'allocated_to'), ['test3@example.com']) def test_assignment_rule_condition(self): frappe.db.delete("Assignment Rule") diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index b3c85b22a1..d1291acfc4 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -70,7 +70,7 @@ class TestUser(unittest.TestCase): delete_contact("_test@example.com") delete_doc("User", "_test@example.com") - self.assertTrue(not frappe.db.sql("""select * from `tabToDo` where owner=%s""", + self.assertTrue(not frappe.db.sql("""select * from `tabToDo` where allocated_to=%s""", ("_test@example.com",))) from frappe.core.doctype.role.test_role import test_records as role_records diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 2d2ad1fed9..ef7845d3b0 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -363,7 +363,7 @@ class User(Document): frappe.local.login_manager.logout(user=self.name) # delete todos - frappe.db.delete("ToDo", {"owner": self.name}) + frappe.db.delete("ToDo", {"allocated_to": self.name}) todo_table = DocType("ToDo") ( frappe.qb.update(todo_table) diff --git a/frappe/core/notifications.py b/frappe/core/notifications.py index 939cf52911..be3e723af6 100644 --- a/frappe/core/notifications.py +++ b/frappe/core/notifications.py @@ -23,7 +23,7 @@ def get_things_todo(as_list=False): data = frappe.get_list("ToDo", fields=["name", "description"] if as_list else "count(*)", filters=[["ToDo", "status", "=", "Open"]], - or_filters=[["ToDo", "owner", "=", frappe.session.user], + or_filters=[["ToDo", "allocated_to", "=", frappe.session.user], ["ToDo", "assigned_by", "=", frappe.session.user]], as_list=True) diff --git a/frappe/desk/doctype/event/test_event.py b/frappe/desk/doctype/event/test_event.py index 6b7f6ee471..b0269a80cc 100644 --- a/frappe/desk/doctype/event/test_event.py +++ b/frappe/desk/doctype/event/test_event.py @@ -93,7 +93,7 @@ class TestEvent(unittest.TestCase): # Remove an assignment todo = frappe.get_doc("ToDo", {"reference_type": ev.doctype, "reference_name": ev.name, - "owner": self.test_user}) + "allocated_to": self.test_user}) todo.status = "Cancelled" todo.save() diff --git a/frappe/desk/doctype/todo/todo.json b/frappe/desk/doctype/todo/todo.json index 15e0e4abe1..e6a1671c19 100644 --- a/frappe/desk/doctype/todo/todo.json +++ b/frappe/desk/doctype/todo/todo.json @@ -13,7 +13,7 @@ "column_break_2", "color", "date", - "owner", + "allocated_to", "description_section", "description", "section_break_6", @@ -69,15 +69,6 @@ "oldfieldname": "date", "oldfieldtype": "Date" }, - { - "fieldname": "owner", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_global_search": 1, - "in_standard_filter": 1, - "label": "Allocated To", - "options": "User" - }, { "fieldname": "description_section", "fieldtype": "Section Break" @@ -153,12 +144,21 @@ "label": "Assignment Rule", "options": "Assignment Rule", "read_only": 1 + }, + { + "fieldname": "allocated_to", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Allocated To", + "options": "User" } ], "icon": "fa fa-check", "idx": 2, "links": [], - "modified": "2020-01-14 17:04:36.971002", + "modified": "2021-09-02 16:27:32.173875", "modified_by": "Administrator", "module": "Desk", "name": "ToDo", diff --git a/frappe/desk/doctype/todo/todo.py b/frappe/desk/doctype/todo/todo.py index 6f3f4160e6..eabb28a6f3 100644 --- a/frappe/desk/doctype/todo/todo.py +++ b/frappe/desk/doctype/todo/todo.py @@ -16,10 +16,10 @@ class ToDo(Document): self._assignment = None if self.is_new(): - if self.assigned_by == self.owner: + if self.assigned_by == self.allocated_to: assignment_message = frappe._("{0} self assigned this task: {1}").format(get_fullname(self.assigned_by), self.description) else: - assignment_message = frappe._("{0} assigned {1}: {2}").format(get_fullname(self.assigned_by), get_fullname(self.owner), self.description) + assignment_message = frappe._("{0} assigned {1}: {2}").format(get_fullname(self.assigned_by), get_fullname(self.allocated_to), self.description) self._assignment = { "text": assignment_message, @@ -29,12 +29,12 @@ class ToDo(Document): else: # NOTE the previous value is only available in validate method if self.get_db_value("status") != self.status: - if self.owner == frappe.session.user: + if self.allocated_to == frappe.session.user: removal_message = frappe._("{0} removed their assignment.").format( get_fullname(frappe.session.user)) else: removal_message = frappe._("Assignment of {0} removed by {1}").format( - get_fullname(self.owner), get_fullname(frappe.session.user)) + get_fullname(self.allocated_to), get_fullname(frappe.session.user)) self._assignment = { "text": removal_message, @@ -75,7 +75,7 @@ class ToDo(Document): "reference_name": self.reference_name, "status": ("!=", "Cancelled") }, - fields=["owner"], as_list=True)] + fields=["allocated_to"], as_list=True)] assignments.reverse() frappe.db.set_value(self.reference_type, self.reference_name, @@ -98,8 +98,8 @@ class ToDo(Document): def get_owners(cls, filters=None): """Returns list of owners after applying filters on todo's. """ - rows = frappe.get_all(cls.DocType, filters=filters or {}, fields=['owner']) - return [parse_addr(row.owner)[1] for row in rows if row.owner] + rows = frappe.get_all(cls.DocType, filters=filters or {}, fields=['allocated_to']) + return [parse_addr(row.allocated_to)[1] for row in rows if row.allocated_to] # NOTE: todo is viewable if a user is an owner, or set as assigned_to value, or has any role that is allowed to access ToDo doctype. def on_doctype_update(): @@ -115,7 +115,7 @@ def get_permission_query_conditions(user): if any(check in todo_roles for check in frappe.get_roles(user)): return None else: - return """(`tabToDo`.owner = {user} or `tabToDo`.assigned_by = {user})"""\ + return """(`tabToDo`.allocated_to = {user} or `tabToDo`.assigned_by = {user})"""\ .format(user=frappe.db.escape(user)) def has_permission(doc, ptype="read", user=None): @@ -127,7 +127,7 @@ def has_permission(doc, ptype="read", user=None): if any(check in todo_roles for check in frappe.get_roles(user)): return True else: - return doc.owner==user or doc.assigned_by==user + return doc.allocated_to==user or doc.assigned_by==user @frappe.whitelist() def new_todo(description): diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index bf77170eeb..50e86b75d6 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -19,7 +19,7 @@ def get(args=None): if not args: args = frappe.local.form_dict - return frappe.get_all('ToDo', fields=['owner', 'name'], filters=dict( + return frappe.get_all('ToDo', fields=['allocated_to', 'name'], filters=dict( reference_type = args.get('doctype'), reference_name = args.get('name'), status = ('!=', 'Cancelled') @@ -48,7 +48,7 @@ def add(args=None): "reference_type": args['doctype'], "reference_name": args['name'], "status": "Open", - "owner": assign_to + "allocated_to": assign_to } if frappe.get_all("ToDo", filters=filters): @@ -61,7 +61,7 @@ def add(args=None): d = frappe.get_doc({ "doctype": "ToDo", - "owner": assign_to, + "allocated_to": assign_to, "reference_type": args['doctype'], "reference_name": args['name'], "description": args.get('description'), @@ -87,7 +87,7 @@ def add(args=None): follow_document(args['doctype'], args['name'], assign_to) # notify - notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN', + notify_assignment(d.assigned_by, d.allocated_to, d.reference_type, d.reference_name, action='ASSIGN', description=args.get("description")) if shared_with_users: @@ -112,13 +112,13 @@ def add_multiple(args=None): add(args) def close_all_assignments(doctype, name): - assignments = frappe.db.get_all('ToDo', fields=['owner'], filters = + assignments = frappe.db.get_all('ToDo', fields=['allocated_to'], filters = dict(reference_type = doctype, reference_name = name, status=('!=', 'Cancelled'))) if not assignments: return False for assign_to in assignments: - set_status(doctype, name, assign_to.owner, status="Closed") + set_status(doctype, name, assign_to.allocated_to, status="Closed") return True @@ -130,13 +130,13 @@ def set_status(doctype, name, assign_to, status="Cancelled"): """remove from todo""" try: todo = frappe.db.get_value("ToDo", {"reference_type":doctype, - "reference_name":name, "owner":assign_to, "status": ('!=', status)}) + "reference_name":name, "allocated_to":assign_to, "status": ('!=', status)}) if todo: todo = frappe.get_doc("ToDo", todo) todo.status = status todo.save(ignore_permissions=True) - notify_assignment(todo.assigned_by, todo.owner, todo.reference_type, todo.reference_name) + notify_assignment(todo.assigned_by, todo.allocated_to, todo.reference_type, todo.reference_name) except frappe.DoesNotExistError: pass @@ -150,25 +150,25 @@ def clear(doctype, name): ''' Clears assignments, return False if not assigned. ''' - assignments = frappe.db.get_all('ToDo', fields=['owner'], filters = + assignments = frappe.db.get_all('ToDo', fields=['allocated_to'], filters = dict(reference_type = doctype, reference_name = name)) if not assignments: return False for assign_to in assignments: - set_status(doctype, name, assign_to.owner, "Cancelled") + set_status(doctype, name, assign_to.allocated_to, "Cancelled") return True -def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE', +def notify_assignment(assigned_by, allocated_to, doc_type, doc_name, action='CLOSE', description=None): """ Notify assignee that there is a change in assignment """ - if not (assigned_by and owner and doc_type and doc_name): return + if not (assigned_by and allocated_to and doc_type and doc_name): return # return if self assigned or user disabled - if assigned_by == owner or not frappe.db.get_value('User', owner, 'enabled'): + if assigned_by == allocated_to or not frappe.db.get_value('User', allocated_to, 'enabled'): return # Search for email address in description -- i.e. assignee @@ -194,7 +194,7 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE', 'email_content': description_html } - enqueue_create_notification(owner, notification_doc) + enqueue_create_notification(allocated_to, notification_doc) def format_message_for_assign_to(users): return "

" + "
".join(users) \ No newline at end of file diff --git a/frappe/desk/listview.py b/frappe/desk/listview.py index 43ad104f0d..3d6f1254a2 100644 --- a/frappe/desk/listview.py +++ b/frappe/desk/listview.py @@ -29,16 +29,16 @@ def get_group_by_count(doctype, current_filters, field): subquery = frappe.get_all(doctype, filters=current_filters, run=False) if field == 'assigned_to': subquery_condition = ' and `tabToDo`.reference_name in ({subquery})'.format(subquery = subquery) - return frappe.db.sql("""select `tabToDo`.owner as name, count(*) as count + return frappe.db.sql("""select `tabToDo`.allocated_to as name, count(*) as count from `tabToDo`, `tabUser` where `tabToDo`.status!='Cancelled' and - `tabToDo`.owner = `tabUser`.name and + `tabToDo`.allocated_to = `tabUser`.name and `tabUser`.user_type = 'System User' {subquery_condition} group by - `tabToDo`.owner + `tabToDo`.allocated_to order by count desc limit 50""".format(subquery_condition = subquery_condition), as_dict=True) diff --git a/frappe/model/document.py b/frappe/model/document.py index 1f079feedc..4312378995 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -470,8 +470,8 @@ class Document(BaseDocument): self.modified_by = frappe.session.user if not self.creation: self.creation = self.modified - if not self.owner: - self.owner = self.modified_by + if self.is_new(): + self.owner = self.flags.owner or self.modified_by for d in self.get_all_children(): d.modified = self.modified @@ -501,6 +501,7 @@ class Document(BaseDocument): self._sanitize_content() self._save_passwords() self.validate_workflow() + self.validate_owner() children = self.get_all_children() for d in children: @@ -543,6 +544,11 @@ class Document(BaseDocument): if not self._action == 'save': set_workflow_state_on_action(self, workflow, self._action) + def validate_owner(self): + """Validate if the owner of the Document has changed""" + if not self.is_new() and self.has_value_changed('owner'): + frappe.throw(_('Document owner cannot be changed')) + def validate_set_only_once(self): """Validate that fields are not changed if not in insert""" set_only_once_fields = self.meta.get_set_only_once_fields() @@ -1342,7 +1348,7 @@ class Document(BaseDocument): def get_assigned_users(self): assignments = frappe.get_all('ToDo', - fields=['owner'], + fields=['allocated_to'], filters={ 'reference_type': self.doctype, 'reference_name': self.name, diff --git a/frappe/tests/test_assign.py b/frappe/tests/test_assign.py index 05bf7e2fb3..971f9ce071 100644 --- a/frappe/tests/test_assign.py +++ b/frappe/tests/test_assign.py @@ -13,7 +13,7 @@ class TestAssign(unittest.TestCase): added = assign(todo, "test@example.com") - self.assertTrue("test@example.com" in [d.owner for d in added]) + self.assertTrue("test@example.com" in [d.allocated_to for d in added]) removed = frappe.desk.form.assign_to.remove(todo.doctype, todo.name, "test@example.com") diff --git a/frappe/tests/test_document.py b/frappe/tests/test_document.py index 29cec8b230..46638f5bf2 100644 --- a/frappe/tests/test_document.py +++ b/frappe/tests/test_document.py @@ -252,3 +252,22 @@ class TestDocument(unittest.TestCase): 'currency': 100000 }) self.assertEquals(d.get_formatted('currency', currency='INR', format="#,###.##"), '₹ 100,000.00') + + def test_owner_changed(self): + frappe.delete_doc_if_exists("User", "hello@example.com") + frappe.set_user("Administrator") + + d = frappe.get_doc({ + "doctype": "User", + "email": "hello@example.com", + "first_name": "John" + }) + d.insert() + self.assertEqual(frappe.db.get_value("User", d.owner), d.owner) + + d.set("owner", "johndoe@gmail.com") + self.assertRaises(frappe.ValidationError, d.save) + + d.reload() + d.save() + self.assertEqual(frappe.db.get_value("User", d.owner), d.owner) From 1832df06ad6d753c2235f988fbd2c5e86ad0620a Mon Sep 17 00:00:00 2001 From: shadrak gurupnor Date: Tue, 7 Sep 2021 13:30:04 +0530 Subject: [PATCH 23/26] fix: replace owner with allocated_to --- frappe/email/doctype/notification/notification.py | 4 ++-- frappe/model/document.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 6b4ee92043..77979f9735 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -435,8 +435,8 @@ def get_context(doc): def get_assignees(doc): assignees = [] assignees = frappe.get_all('ToDo', filters={'status': 'Open', 'reference_name': doc.name, - 'reference_type': doc.doctype}, fields=['owner']) + 'reference_type': doc.doctype}, fields=['allocated_to']) - recipients = [d.owner for d in assignees] + recipients = [d.allocated_to for d in assignees] return recipients diff --git a/frappe/model/document.py b/frappe/model/document.py index 4312378995..e519ab257b 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -1347,15 +1347,15 @@ class Document(BaseDocument): ), frappe.exceptions.InvalidDates) def get_assigned_users(self): - assignments = frappe.get_all('ToDo', + assigned_users = frappe.get_all('ToDo', fields=['allocated_to'], filters={ 'reference_type': self.doctype, 'reference_name': self.name, 'status': ('!=', 'Cancelled'), - }) + }, pluck='allocated_to') - users = set([assignment.owner for assignment in assignments]) + users = set(assigned_users) return users def add_tag(self, tag): From a4aad4dc7aa5d49d7eefaf5bcf9f3445ad7a000c Mon Sep 17 00:00:00 2001 From: shadrak gurupnor Date: Thu, 16 Sep 2021 11:47:28 +0530 Subject: [PATCH 24/26] fix: failed test cases & sider issue --- frappe/core/doctype/file/test_file.py | 1 + frappe/core/doctype/user/user.json | 2 +- frappe/desk/doctype/todo/todo.json | 2 +- frappe/desk/form/assign_to.py | 3 ++- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 9a758b53f5..2c1042e104 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -18,6 +18,7 @@ test_content2 = 'Hello World' def make_test_doc(): d = frappe.new_doc('ToDo') d.description = 'Test' + d.assigned_by = frappe.session.user d.save() return d.doctype, d.name diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 48af4d3beb..a47f539466 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -660,7 +660,7 @@ { "group": "Activity", "link_doctype": "ToDo", - "link_fieldname": "owner" + "link_fieldname": "allocated_to" }, { "group": "Integrations", diff --git a/frappe/desk/doctype/todo/todo.json b/frappe/desk/doctype/todo/todo.json index e6a1671c19..518ca00374 100644 --- a/frappe/desk/doctype/todo/todo.json +++ b/frappe/desk/doctype/todo/todo.json @@ -158,7 +158,7 @@ "icon": "fa fa-check", "idx": 2, "links": [], - "modified": "2021-09-02 16:27:32.173875", + "modified": "2021-09-16 11:36:34.586898", "modified_by": "Administrator", "module": "Desk", "name": "ToDo", diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 50e86b75d6..7ea87b8d15 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -165,7 +165,8 @@ def notify_assignment(assigned_by, allocated_to, doc_type, doc_name, action='CLO """ Notify assignee that there is a change in assignment """ - if not (assigned_by and allocated_to and doc_type and doc_name): return + if not (assigned_by and allocated_to and doc_type and doc_name): + return # return if self assigned or user disabled if assigned_by == allocated_to or not frappe.db.get_value('User', allocated_to, 'enabled'): From ff13b12be58edb3278d3d68c1e4d4b4454296a5c Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 4 Jan 2022 13:59:27 +0530 Subject: [PATCH 25/26] fix: Populate ToDo.allocate_to column --- frappe/patches.txt | 1 + frappe/patches/v14_0/transform_todo_schema.py | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 frappe/patches/v14_0/transform_todo_schema.py diff --git a/frappe/patches.txt b/frappe/patches.txt index 27ba1a145d..39d60d9496 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -190,3 +190,4 @@ frappe.patches.v14_0.update_github_endpoints #08-11-2021 frappe.patches.v14_0.remove_db_aggregation frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021 frappe.patches.v14_0.update_color_names_in_kanban_board_column +frappe.patches.v14_0.transform_todo_schema diff --git a/frappe/patches/v14_0/transform_todo_schema.py b/frappe/patches/v14_0/transform_todo_schema.py new file mode 100644 index 0000000000..73b06569a5 --- /dev/null +++ b/frappe/patches/v14_0/transform_todo_schema.py @@ -0,0 +1,12 @@ +import frappe +from frappe.query_builder.utils import DocType + + +def execute(): + # Email Template & Help Article have owner field that doesn't have any additional functionality + # Only ToDo has to be updated. + + ToDo = DocType("ToDo") + frappe.reload_doctype("ToDo", force=True) + + frappe.qb.update(ToDo).set(ToDo.allocated_to, ToDo.owner).run() From 06926cf123caa93c66ef20ba29e994b1ff17cdb9 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 4 Jan 2022 14:25:23 +0530 Subject: [PATCH 26/26] feat(minor): copy link to clipboard --- frappe/public/js/frappe/list/list_view.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 938531865d..22f8377a57 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -1500,6 +1500,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { read_only: 1, }, ], + primary_action_label: __("Copy to clipboard"), + primary_action: () => { + frappe.utils.copy_to_clipboard(this.get_share_url()); + d.hide(); + }, }); d.show(); }