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);
});
diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py
index 724c453972..a8c75bffd9 100644
--- a/frappe/automation/doctype/assignment_rule/assignment_rule.py
+++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py
@@ -123,7 +123,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 f20253b779..63dbf69d3b 100644
--- a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py
+++ b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py
@@ -40,7 +40,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))
@@ -49,7 +49,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()
@@ -61,7 +61,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))
@@ -70,7 +70,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'
@@ -81,11 +81,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
@@ -94,7 +94,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'
@@ -129,7 +129,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))
@@ -142,7 +142,7 @@ class TestAutoAssign(unittest.TestCase):
), limit=1)[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
@@ -164,7 +164,7 @@ class TestAutoAssign(unittest.TestCase):
), limit=1)[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()
@@ -174,7 +174,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))
@@ -184,7 +184,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")
@@ -202,7 +202,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))
@@ -211,7 +211,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/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json
index 6910d615d3..26ddce7d35 100644
--- a/frappe/core/doctype/docfield/docfield.json
+++ b/frappe/core/doctype/docfield/docfield.json
@@ -1,543 +1,553 @@
{
- "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",
+ "show_dashboard",
+ "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"
+ },
+ {
+ "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-03 11:56:19.812863",
+ "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
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/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/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",
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.json b/frappe/core/doctype/user/user.json
index cf05ce0c15..a47f539466 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",
@@ -660,7 +660,7 @@
{
"group": "Activity",
"link_doctype": "ToDo",
- "link_fieldname": "owner"
+ "link_fieldname": "allocated_to"
},
{
"group": "Integrations",
@@ -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
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/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);
}
},
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
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/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
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..a157343be6 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -4,16 +4,18 @@
# Database Module
# --------------------
+import datetime
+import random
import re
-import time
-from typing import Dict, List, Union
+import string
+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 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
@@ -811,6 +813,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 +1102,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/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/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..518ca00374 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-16 11:36:34.586898",
"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..7ea87b8d15 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,26 @@ 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 +195,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/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/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/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)
diff --git a/frappe/model/document.py b/frappe/model/document.py
index 16a2d35290..f199c96acd 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -504,6 +504,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:
@@ -546,6 +547,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()
@@ -1348,15 +1354,15 @@ class Document(BaseDocument):
), frappe.exceptions.InvalidDates)
def get_assigned_users(self):
- assignments = frappe.get_all('ToDo',
- fields=['owner'],
+ 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):
diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py
index f84242626b..2cc5818414 100644
--- a/frappe/model/rename_doc.py
+++ b/frappe/model/rename_doc.py
@@ -293,7 +293,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:
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()
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);
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/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();
}
diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js
index 448b3f6fd2..920a252b56 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);
};
});
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'),
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_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):
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)
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)
diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py
index 3536896a5f..9ac51133fa 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,34 @@ 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():
+ 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))
+ )
+ )
+ .orderby(category.title, order=Order.asc)
+ .run(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_introduction or '' }}
+