@@ -9,5 +9,7 @@ | |||||
"retries": { | "retries": { | ||||
"runMode": 2, | "runMode": 2, | ||||
"openMode": 2 | "openMode": 2 | ||||
} | |||||
}, | |||||
"integrationFolder": ".", | |||||
"testFiles": ["cypress/integration/*.js", "**/ui_test_*.js"] | |||||
} | } |
@@ -30,7 +30,7 @@ Cypress.Commands.add('login', (email, password) => { | |||||
email = 'Administrator'; | email = 'Administrator'; | ||||
} | } | ||||
if (!password) { | if (!password) { | ||||
password = Cypress.config('adminPassword'); | |||||
password = Cypress.env('adminPassword'); | |||||
} | } | ||||
cy.request({ | cy.request({ | ||||
url: '/api/method/login', | url: '/api/method/login', | ||||
@@ -161,7 +161,7 @@ Cypress.Commands.add('remove_doc', (doctype, name) => { | |||||
Cypress.Commands.add('create_records', doc => { | Cypress.Commands.add('create_records', doc => { | ||||
return cy | 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); | .then(r => r.message); | ||||
}); | }); | ||||
@@ -123,7 +123,7 @@ class AssignmentRule(Document): | |||||
user = d.user, | user = d.user, | ||||
count = frappe.db.count('ToDo', dict( | count = frappe.db.count('ToDo', dict( | ||||
reference_type = self.document_type, | reference_type = self.document_type, | ||||
owner = d.user, | |||||
allocated_to = d.user, | |||||
status = "Open")) | status = "Open")) | ||||
)) | )) | ||||
@@ -40,7 +40,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
reference_type = 'Note', | reference_type = 'Note', | ||||
reference_name = note.name, | reference_name = note.name, | ||||
status = 'Open' | status = 'Open' | ||||
), 'owner'), 'test@example.com') | |||||
), 'allocated_to'), 'test@example.com') | |||||
note = make_note(dict(public=1)) | note = make_note(dict(public=1)) | ||||
@@ -49,7 +49,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
reference_type = 'Note', | reference_type = 'Note', | ||||
reference_name = note.name, | reference_name = note.name, | ||||
status = 'Open' | status = 'Open' | ||||
), 'owner'), 'test1@example.com') | |||||
), 'allocated_to'), 'test1@example.com') | |||||
clear_assignments() | clear_assignments() | ||||
@@ -61,7 +61,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
reference_type = 'Note', | reference_type = 'Note', | ||||
reference_name = note.name, | reference_name = note.name, | ||||
status = 'Open' | status = 'Open' | ||||
), 'owner'), 'test2@example.com') | |||||
), 'allocated_to'), 'test2@example.com') | |||||
# check loop back to first user | # check loop back to first user | ||||
note = make_note(dict(public=1)) | note = make_note(dict(public=1)) | ||||
@@ -70,7 +70,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
reference_type = 'Note', | reference_type = 'Note', | ||||
reference_name = note.name, | reference_name = note.name, | ||||
status = 'Open' | status = 'Open' | ||||
), 'owner'), 'test@example.com') | |||||
), 'allocated_to'), 'test@example.com') | |||||
def test_load_balancing(self): | def test_load_balancing(self): | ||||
self.assignment_rule.rule = 'Load Balancing' | self.assignment_rule.rule = 'Load Balancing' | ||||
@@ -81,11 +81,11 @@ class TestAutoAssign(unittest.TestCase): | |||||
# check if each user has 10 assignments (?) | # check if each user has 10 assignments (?) | ||||
for user in ('test@example.com', 'test1@example.com', 'test2@example.com'): | 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 | # clear 5 assignments for first user | ||||
# can't do a limit in "delete" since postgres does not support it | # 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}) | frappe.db.delete("ToDo", {"name": d.name}) | ||||
# add 5 more assignments | # add 5 more assignments | ||||
@@ -94,7 +94,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
# check if each user still has 10 assignments | # check if each user still has 10 assignments | ||||
for user in ('test@example.com', 'test1@example.com', 'test2@example.com'): | 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): | def test_based_on_field(self): | ||||
self.assignment_rule.rule = 'Based on Field' | self.assignment_rule.rule = 'Based on Field' | ||||
@@ -129,7 +129,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
reference_type = 'Note', | reference_type = 'Note', | ||||
reference_name = note.name, | reference_name = note.name, | ||||
status = 'Open' | status = 'Open' | ||||
), 'owner'), None) | |||||
), 'allocated_to'), None) | |||||
def test_clear_assignment(self): | def test_clear_assignment(self): | ||||
note = make_note(dict(public=1)) | note = make_note(dict(public=1)) | ||||
@@ -142,7 +142,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
), limit=1)[0] | ), limit=1)[0] | ||||
todo = frappe.get_doc('ToDo', todo['name']) | 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 | # test auto unassign | ||||
note.public = 0 | note.public = 0 | ||||
@@ -164,7 +164,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
), limit=1)[0] | ), limit=1)[0] | ||||
todo = frappe.get_doc('ToDo', todo['name']) | 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.content="Closed" | ||||
note.save() | note.save() | ||||
@@ -174,7 +174,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
# check if todo is closed | # check if todo is closed | ||||
self.assertEqual(todo.status, 'Closed') | self.assertEqual(todo.status, 'Closed') | ||||
# check if closed todo retained assignment | # 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): | def check_multiple_rules(self): | ||||
note = make_note(dict(public=1, notify_on_login=1)) | note = make_note(dict(public=1, notify_on_login=1)) | ||||
@@ -184,7 +184,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
reference_type = 'Note', | reference_type = 'Note', | ||||
reference_name = note.name, | reference_name = note.name, | ||||
status = 'Open' | status = 'Open' | ||||
), 'owner'), 'test@example.com') | |||||
), 'allocated_to'), 'test@example.com') | |||||
def check_assignment_rule_scheduling(self): | def check_assignment_rule_scheduling(self): | ||||
frappe.db.delete("Assignment Rule") | frappe.db.delete("Assignment Rule") | ||||
@@ -202,7 +202,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
reference_type = 'Note', | reference_type = 'Note', | ||||
reference_name = note.name, | reference_name = note.name, | ||||
status = 'Open' | 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" | frappe.flags.assignment_day = "Friday" | ||||
note = make_note(dict(public=1)) | note = make_note(dict(public=1)) | ||||
@@ -211,7 +211,7 @@ class TestAutoAssign(unittest.TestCase): | |||||
reference_type = 'Note', | reference_type = 'Note', | ||||
reference_name = note.name, | reference_name = note.name, | ||||
status = 'Open' | status = 'Open' | ||||
), 'owner'), ['test3@example.com']) | |||||
), 'allocated_to'), ['test3@example.com']) | |||||
def test_assignment_rule_condition(self): | def test_assignment_rule_condition(self): | ||||
frappe.db.delete("Assignment Rule") | frappe.db.delete("Assignment Rule") | ||||
@@ -18,6 +18,7 @@ test_content2 = 'Hello World' | |||||
def make_test_doc(): | def make_test_doc(): | ||||
d = frappe.new_doc('ToDo') | d = frappe.new_doc('ToDo') | ||||
d.description = 'Test' | d.description = 'Test' | ||||
d.assigned_by = frappe.session.user | |||||
d.save() | d.save() | ||||
return d.doctype, d.name | return d.doctype, d.name | ||||
@@ -10,7 +10,8 @@ | |||||
"custom", | "custom", | ||||
"package", | "package", | ||||
"app_name", | "app_name", | ||||
"restrict_to_domain" | |||||
"restrict_to_domain", | |||||
"connections_tab" | |||||
], | ], | ||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
@@ -50,6 +51,12 @@ | |||||
"fieldtype": "Link", | "fieldtype": "Link", | ||||
"label": "Package", | "label": "Package", | ||||
"options": "Package" | "options": "Package" | ||||
}, | |||||
{ | |||||
"fieldname": "connections_tab", | |||||
"fieldtype": "Tab Break", | |||||
"label": "Connections", | |||||
"show_dashboard": 1 | |||||
} | } | ||||
], | ], | ||||
"icon": "fa fa-sitemap", | "icon": "fa fa-sitemap", | ||||
@@ -116,7 +123,7 @@ | |||||
"link_fieldname": "module" | "link_fieldname": "module" | ||||
} | } | ||||
], | ], | ||||
"modified": "2021-09-05 21:58:40.253909", | |||||
"modified": "2022-01-03 13:56:52.817954", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Module Def", | "name": "Module Def", | ||||
@@ -154,5 +161,6 @@ | |||||
"show_name_in_global_search": 1, | "show_name_in_global_search": 1, | ||||
"sort_field": "modified", | "sort_field": "modified", | ||||
"sort_order": "ASC", | "sort_order": "ASC", | ||||
"states": [], | |||||
"track_changes": 1 | "track_changes": 1 | ||||
} | } |
@@ -43,8 +43,7 @@ | |||||
{ | { | ||||
"fieldname": "context", | "fieldname": "context", | ||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"label": "Context", | |||||
"read_only": 1 | |||||
"label": "Context" | |||||
}, | }, | ||||
{ | { | ||||
"default": "0", | "default": "0", | ||||
@@ -83,7 +82,7 @@ | |||||
} | } | ||||
], | ], | ||||
"links": [], | "links": [], | ||||
"modified": "2020-03-12 13:28:48.223409", | |||||
"modified": "2021-12-31 10:19:52.541055", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "Translation", | "name": "Translation", | ||||
@@ -70,7 +70,7 @@ class TestUser(unittest.TestCase): | |||||
delete_contact("_test@example.com") | delete_contact("_test@example.com") | ||||
delete_doc("User", "_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",))) | ("_test@example.com",))) | ||||
from frappe.core.doctype.role.test_role import test_records as role_records | from frappe.core.doctype.role.test_role import test_records as role_records | ||||
@@ -10,15 +10,15 @@ | |||||
"enabled", | "enabled", | ||||
"section_break_3", | "section_break_3", | ||||
"email", | "email", | ||||
"first_name", | |||||
"middle_name", | |||||
"last_name", | "last_name", | ||||
"language", | |||||
"column_break0", | "column_break0", | ||||
"first_name", | |||||
"full_name", | "full_name", | ||||
"time_zone", | |||||
"column_break_11", | |||||
"middle_name", | |||||
"username", | "username", | ||||
"column_break_11", | |||||
"language", | |||||
"time_zone", | |||||
"send_welcome_email", | "send_welcome_email", | ||||
"unsubscribed", | "unsubscribed", | ||||
"user_image", | "user_image", | ||||
@@ -660,7 +660,7 @@ | |||||
{ | { | ||||
"group": "Activity", | "group": "Activity", | ||||
"link_doctype": "ToDo", | "link_doctype": "ToDo", | ||||
"link_fieldname": "owner" | |||||
"link_fieldname": "allocated_to" | |||||
}, | }, | ||||
{ | { | ||||
"group": "Integrations", | "group": "Integrations", | ||||
@@ -669,7 +669,7 @@ | |||||
} | } | ||||
], | ], | ||||
"max_attachments": 5, | "max_attachments": 5, | ||||
"modified": "2021-11-17 17:17:16.098457", | |||||
"modified": "2022-01-03 11:53:25.250822", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "User", | "name": "User", | ||||
@@ -702,6 +702,7 @@ | |||||
"show_name_in_global_search": 1, | "show_name_in_global_search": 1, | ||||
"sort_field": "modified", | "sort_field": "modified", | ||||
"sort_order": "DESC", | "sort_order": "DESC", | ||||
"states": [], | |||||
"title_field": "full_name", | "title_field": "full_name", | ||||
"track_changes": 1 | "track_changes": 1 | ||||
} | |||||
} |
@@ -363,7 +363,7 @@ class User(Document): | |||||
frappe.local.login_manager.logout(user=self.name) | frappe.local.login_manager.logout(user=self.name) | ||||
# delete todos | # delete todos | ||||
frappe.db.delete("ToDo", {"owner": self.name}) | |||||
frappe.db.delete("ToDo", {"allocated_to": self.name}) | |||||
todo_table = DocType("ToDo") | todo_table = DocType("ToDo") | ||||
( | ( | ||||
frappe.qb.update(todo_table) | frappe.qb.update(todo_table) | ||||
@@ -44,7 +44,7 @@ frappe.ui.form.on('User Permission', { | |||||
set_applicable_for_constraint: frm => { | set_applicable_for_constraint: frm => { | ||||
frm.toggle_reqd('applicable_for', !frm.doc.apply_to_all_doctypes); | 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); | frm.set_value('applicable_for', null); | ||||
} | } | ||||
}, | }, | ||||
@@ -8,8 +8,8 @@ | |||||
"field_order": [ | "field_order": [ | ||||
"user", | "user", | ||||
"allow", | "allow", | ||||
"column_break_3", | |||||
"for_value", | "for_value", | ||||
"column_break_3", | |||||
"is_default", | "is_default", | ||||
"advanced_control_section", | "advanced_control_section", | ||||
"apply_to_all_doctypes", | "apply_to_all_doctypes", | ||||
@@ -37,10 +37,6 @@ | |||||
"options": "DocType", | "options": "DocType", | ||||
"reqd": 1 | "reqd": 1 | ||||
}, | }, | ||||
{ | |||||
"fieldname": "column_break_3", | |||||
"fieldtype": "Column Break" | |||||
}, | |||||
{ | { | ||||
"fieldname": "for_value", | "fieldname": "for_value", | ||||
"fieldtype": "Dynamic Link", | "fieldtype": "Dynamic Link", | ||||
@@ -87,10 +83,14 @@ | |||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"hidden": 1, | "hidden": 1, | ||||
"label": "Hide Descendants" | "label": "Hide Descendants" | ||||
}, | |||||
{ | |||||
"fieldname": "column_break_3", | |||||
"fieldtype": "Column Break" | |||||
} | } | ||||
], | ], | ||||
"links": [], | "links": [], | ||||
"modified": "2021-01-21 18:14:10.839381", | |||||
"modified": "2022-01-03 11:25:03.726150", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Core", | "module": "Core", | ||||
"name": "User Permission", | "name": "User Permission", | ||||
@@ -111,6 +111,7 @@ | |||||
], | ], | ||||
"sort_field": "modified", | "sort_field": "modified", | ||||
"sort_order": "DESC", | "sort_order": "DESC", | ||||
"states": [], | |||||
"title_field": "user", | "title_field": "user", | ||||
"track_changes": 1 | "track_changes": 1 | ||||
} | } |
@@ -23,7 +23,7 @@ def get_things_todo(as_list=False): | |||||
data = frappe.get_list("ToDo", | data = frappe.get_list("ToDo", | ||||
fields=["name", "description"] if as_list else "count(*)", | fields=["name", "description"] if as_list else "count(*)", | ||||
filters=[["ToDo", "status", "=", "Open"]], | 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]], | ["ToDo", "assigned_by", "=", frappe.session.user]], | ||||
as_list=True) | as_list=True) | ||||
@@ -516,6 +516,7 @@ docfield_properties = { | |||||
'options': 'Text', | 'options': 'Text', | ||||
'fetch_from': 'Small Text', | 'fetch_from': 'Small Text', | ||||
'fetch_if_empty': 'Check', | 'fetch_if_empty': 'Check', | ||||
'show_dashboard': 'Check', | |||||
'permlevel': 'Int', | 'permlevel': 'Int', | ||||
'width': 'Data', | 'width': 'Data', | ||||
'print_width': 'Data', | 'print_width': 'Data', | ||||
@@ -28,6 +28,7 @@ | |||||
"options", | "options", | ||||
"fetch_from", | "fetch_from", | ||||
"fetch_if_empty", | "fetch_if_empty", | ||||
"show_dashboard", | |||||
"permissions", | "permissions", | ||||
"depends_on", | "depends_on", | ||||
"permlevel", | "permlevel", | ||||
@@ -82,7 +83,7 @@ | |||||
"label": "Type", | "label": "Type", | ||||
"oldfieldname": "fieldtype", | "oldfieldname": "fieldtype", | ||||
"oldfieldtype": "Select", | "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, | "reqd": 1, | ||||
"search_index": 1 | "search_index": 1 | ||||
}, | }, | ||||
@@ -422,18 +423,27 @@ | |||||
"fieldname": "non_negative", | "fieldname": "non_negative", | ||||
"fieldtype": "Check", | "fieldtype": "Check", | ||||
"label": "Non Negative" | "label": "Non Negative" | ||||
}, | |||||
{ | |||||
"default": "0", | |||||
"depends_on": "eval:doc.fieldtype=='Tab Break'", | |||||
"fieldname": "show_dashboard", | |||||
"fieldtype": "Check", | |||||
"label": "Show Dashboard" | |||||
} | } | ||||
], | ], | ||||
"idx": 1, | "idx": 1, | ||||
"index_web_pages_for_search": 1, | "index_web_pages_for_search": 1, | ||||
"istable": 1, | "istable": 1, | ||||
"links": [], | "links": [], | ||||
"modified": "2021-07-11 21:57:24.479749", | |||||
"modified": "2022-01-03 14:50:32.035768", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Custom", | "module": "Custom", | ||||
"name": "Customize Form Field", | "name": "Customize Form Field", | ||||
"naming_rule": "Random", | |||||
"owner": "Administrator", | "owner": "Administrator", | ||||
"permissions": [], | "permissions": [], | ||||
"sort_field": "modified", | "sort_field": "modified", | ||||
"sort_order": "ASC" | |||||
"sort_order": "ASC", | |||||
"states": [] | |||||
} | } |
@@ -4,6 +4,8 @@ | |||||
# Database Module | # Database Module | ||||
# -------------------- | # -------------------- | ||||
from frappe.database.database import savepoint | |||||
def setup_database(force, source_sql=None, verbose=None, no_mariadb_socket=False): | def setup_database(force, source_sql=None, verbose=None, no_mariadb_socket=False): | ||||
import frappe | import frappe | ||||
if frappe.conf.db_type == 'postgres': | if frappe.conf.db_type == 'postgres': | ||||
@@ -4,16 +4,18 @@ | |||||
# Database Module | # Database Module | ||||
# -------------------- | # -------------------- | ||||
import datetime | |||||
import random | |||||
import re | 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 frappe | ||||
import datetime | |||||
import frappe.defaults | import frappe.defaults | ||||
import frappe.model.meta | import frappe.model.meta | ||||
from frappe import _ | from frappe import _ | ||||
from time import time | |||||
from frappe.utils import now, getdate, cast, get_datetime | from frappe.utils import now, getdate, cast, get_datetime | ||||
from frappe.model.utils.link_count import flush_local_link_count | from frappe.model.utils.link_count import flush_local_link_count | ||||
from frappe.query_builder.functions import Count | from frappe.query_builder.functions import Count | ||||
@@ -811,6 +813,9 @@ class Database(object): | |||||
Avoid using savepoints when writing to filesystem.""" | Avoid using savepoints when writing to filesystem.""" | ||||
self.sql(f"savepoint {save_point}") | 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): | def rollback(self, *, save_point=None): | ||||
"""`ROLLBACK` current transaction. Optionally rollback to a known save_point.""" | """`ROLLBACK` current transaction. Optionally rollback to a known save_point.""" | ||||
if save_point: | if save_point: | ||||
@@ -1097,3 +1102,28 @@ def enqueue_jobs_after_commit(): | |||||
q.enqueue_call(execute_job, timeout=job.get("timeout"), | q.enqueue_call(execute_job, timeout=job.get("timeout"), | ||||
kwargs=job.get("queue_args")) | kwargs=job.get("queue_args")) | ||||
frappe.flags.enqueue_after_commit = [] | 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) |
@@ -25,6 +25,7 @@ CREATE TABLE `tabDocField` ( | |||||
`oldfieldtype` varchar(255) DEFAULT NULL, | `oldfieldtype` varchar(255) DEFAULT NULL, | ||||
`options` text, | `options` text, | ||||
`search_index` int(1) NOT NULL DEFAULT 0, | `search_index` int(1) NOT NULL DEFAULT 0, | ||||
`show_dashboard` int(1) NOT NULL DEFAULT 0, | |||||
`hidden` int(1) NOT NULL DEFAULT 0, | `hidden` int(1) NOT NULL DEFAULT 0, | ||||
`set_only_once` 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, | `allow_in_quick_entry` int(1) NOT NULL DEFAULT 0, | ||||
@@ -27,6 +27,7 @@ CREATE TABLE "tabDocField" ( | |||||
"search_index" smallint NOT NULL DEFAULT 0, | "search_index" smallint NOT NULL DEFAULT 0, | ||||
"hidden" smallint NOT NULL DEFAULT 0, | "hidden" smallint NOT NULL DEFAULT 0, | ||||
"set_only_once" 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, | "allow_in_quick_entry" smallint NOT NULL DEFAULT 0, | ||||
"print_hide" smallint NOT NULL DEFAULT 0, | "print_hide" smallint NOT NULL DEFAULT 0, | ||||
"report_hide" smallint NOT NULL DEFAULT 0, | "report_hide" smallint NOT NULL DEFAULT 0, | ||||
@@ -93,7 +93,7 @@ class TestEvent(unittest.TestCase): | |||||
# Remove an assignment | # Remove an assignment | ||||
todo = frappe.get_doc("ToDo", {"reference_type": ev.doctype, "reference_name": ev.name, | 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.status = "Cancelled" | ||||
todo.save() | todo.save() | ||||
@@ -13,7 +13,7 @@ | |||||
"column_break_2", | "column_break_2", | ||||
"color", | "color", | ||||
"date", | "date", | ||||
"owner", | |||||
"allocated_to", | |||||
"description_section", | "description_section", | ||||
"description", | "description", | ||||
"section_break_6", | "section_break_6", | ||||
@@ -69,15 +69,6 @@ | |||||
"oldfieldname": "date", | "oldfieldname": "date", | ||||
"oldfieldtype": "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", | "fieldname": "description_section", | ||||
"fieldtype": "Section Break" | "fieldtype": "Section Break" | ||||
@@ -153,12 +144,21 @@ | |||||
"label": "Assignment Rule", | "label": "Assignment Rule", | ||||
"options": "Assignment Rule", | "options": "Assignment Rule", | ||||
"read_only": 1 | "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", | "icon": "fa fa-check", | ||||
"idx": 2, | "idx": 2, | ||||
"links": [], | "links": [], | ||||
"modified": "2020-01-14 17:04:36.971002", | |||||
"modified": "2021-09-16 11:36:34.586898", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Desk", | "module": "Desk", | ||||
"name": "ToDo", | "name": "ToDo", | ||||
@@ -16,10 +16,10 @@ class ToDo(Document): | |||||
self._assignment = None | self._assignment = None | ||||
if self.is_new(): | 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) | assignment_message = frappe._("{0} self assigned this task: {1}").format(get_fullname(self.assigned_by), self.description) | ||||
else: | 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 = { | self._assignment = { | ||||
"text": assignment_message, | "text": assignment_message, | ||||
@@ -29,12 +29,12 @@ class ToDo(Document): | |||||
else: | else: | ||||
# NOTE the previous value is only available in validate method | # NOTE the previous value is only available in validate method | ||||
if self.get_db_value("status") != self.status: | 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( | removal_message = frappe._("{0} removed their assignment.").format( | ||||
get_fullname(frappe.session.user)) | get_fullname(frappe.session.user)) | ||||
else: | else: | ||||
removal_message = frappe._("Assignment of {0} removed by {1}").format( | 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 = { | self._assignment = { | ||||
"text": removal_message, | "text": removal_message, | ||||
@@ -75,7 +75,7 @@ class ToDo(Document): | |||||
"reference_name": self.reference_name, | "reference_name": self.reference_name, | ||||
"status": ("!=", "Cancelled") | "status": ("!=", "Cancelled") | ||||
}, | }, | ||||
fields=["owner"], as_list=True)] | |||||
fields=["allocated_to"], as_list=True)] | |||||
assignments.reverse() | assignments.reverse() | ||||
frappe.db.set_value(self.reference_type, self.reference_name, | frappe.db.set_value(self.reference_type, self.reference_name, | ||||
@@ -98,8 +98,8 @@ class ToDo(Document): | |||||
def get_owners(cls, filters=None): | def get_owners(cls, filters=None): | ||||
"""Returns list of owners after applying filters on todo's. | """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. | # 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(): | 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)): | if any(check in todo_roles for check in frappe.get_roles(user)): | ||||
return None | return None | ||||
else: | 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)) | .format(user=frappe.db.escape(user)) | ||||
def has_permission(doc, ptype="read", user=None): | 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)): | if any(check in todo_roles for check in frappe.get_roles(user)): | ||||
return True | return True | ||||
else: | else: | ||||
return doc.owner==user or doc.assigned_by==user | |||||
return doc.allocated_to==user or doc.assigned_by==user | |||||
@frappe.whitelist() | @frappe.whitelist() | ||||
def new_todo(description): | def new_todo(description): | ||||
@@ -19,7 +19,7 @@ def get(args=None): | |||||
if not args: | if not args: | ||||
args = frappe.local.form_dict | 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_type = args.get('doctype'), | ||||
reference_name = args.get('name'), | reference_name = args.get('name'), | ||||
status = ('!=', 'Cancelled') | status = ('!=', 'Cancelled') | ||||
@@ -48,7 +48,7 @@ def add(args=None): | |||||
"reference_type": args['doctype'], | "reference_type": args['doctype'], | ||||
"reference_name": args['name'], | "reference_name": args['name'], | ||||
"status": "Open", | "status": "Open", | ||||
"owner": assign_to | |||||
"allocated_to": assign_to | |||||
} | } | ||||
if frappe.get_all("ToDo", filters=filters): | if frappe.get_all("ToDo", filters=filters): | ||||
@@ -61,7 +61,7 @@ def add(args=None): | |||||
d = frappe.get_doc({ | d = frappe.get_doc({ | ||||
"doctype": "ToDo", | "doctype": "ToDo", | ||||
"owner": assign_to, | |||||
"allocated_to": assign_to, | |||||
"reference_type": args['doctype'], | "reference_type": args['doctype'], | ||||
"reference_name": args['name'], | "reference_name": args['name'], | ||||
"description": args.get('description'), | "description": args.get('description'), | ||||
@@ -87,7 +87,7 @@ def add(args=None): | |||||
follow_document(args['doctype'], args['name'], assign_to) | follow_document(args['doctype'], args['name'], assign_to) | ||||
# notify | # 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")) | description=args.get("description")) | ||||
if shared_with_users: | if shared_with_users: | ||||
@@ -112,13 +112,13 @@ def add_multiple(args=None): | |||||
add(args) | add(args) | ||||
def close_all_assignments(doctype, name): | 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'))) | dict(reference_type = doctype, reference_name = name, status=('!=', 'Cancelled'))) | ||||
if not assignments: | if not assignments: | ||||
return False | return False | ||||
for assign_to in assignments: | 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 | return True | ||||
@@ -130,13 +130,13 @@ def set_status(doctype, name, assign_to, status="Cancelled"): | |||||
"""remove from todo""" | """remove from todo""" | ||||
try: | try: | ||||
todo = frappe.db.get_value("ToDo", {"reference_type":doctype, | 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: | if todo: | ||||
todo = frappe.get_doc("ToDo", todo) | todo = frappe.get_doc("ToDo", todo) | ||||
todo.status = status | todo.status = status | ||||
todo.save(ignore_permissions=True) | 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: | except frappe.DoesNotExistError: | ||||
pass | pass | ||||
@@ -150,25 +150,26 @@ def clear(doctype, name): | |||||
''' | ''' | ||||
Clears assignments, return False if not assigned. | 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)) | dict(reference_type = doctype, reference_name = name)) | ||||
if not assignments: | if not assignments: | ||||
return False | return False | ||||
for assign_to in assignments: | for assign_to in assignments: | ||||
set_status(doctype, name, assign_to.owner, "Cancelled") | |||||
set_status(doctype, name, assign_to.allocated_to, "Cancelled") | |||||
return True | 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): | description=None): | ||||
""" | """ | ||||
Notify assignee that there is a change in assignment | 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 | # 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 | return | ||||
# Search for email address in description -- i.e. assignee | # 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 | 'email_content': description_html | ||||
} | } | ||||
enqueue_create_notification(owner, notification_doc) | |||||
enqueue_create_notification(allocated_to, notification_doc) | |||||
def format_message_for_assign_to(users): | def format_message_for_assign_to(users): | ||||
return "<br><br>" + "<br>".join(users) | return "<br><br>" + "<br>".join(users) |
@@ -29,16 +29,16 @@ def get_group_by_count(doctype, current_filters, field): | |||||
subquery = frappe.get_all(doctype, filters=current_filters, run=False) | subquery = frappe.get_all(doctype, filters=current_filters, run=False) | ||||
if field == 'assigned_to': | if field == 'assigned_to': | ||||
subquery_condition = ' and `tabToDo`.reference_name in ({subquery})'.format(subquery = subquery) | 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 | from | ||||
`tabToDo`, `tabUser` | `tabToDo`, `tabUser` | ||||
where | where | ||||
`tabToDo`.status!='Cancelled' and | `tabToDo`.status!='Cancelled' and | ||||
`tabToDo`.owner = `tabUser`.name and | |||||
`tabToDo`.allocated_to = `tabUser`.name and | |||||
`tabUser`.user_type = 'System User' | `tabUser`.user_type = 'System User' | ||||
{subquery_condition} | {subquery_condition} | ||||
group by | group by | ||||
`tabToDo`.owner | |||||
`tabToDo`.allocated_to | |||||
order by | order by | ||||
count desc | count desc | ||||
limit 50""".format(subquery_condition = subquery_condition), as_dict=True) | limit 50""".format(subquery_condition = subquery_condition), as_dict=True) | ||||
@@ -12,7 +12,6 @@ | |||||
"use_html", | "use_html", | ||||
"response_html", | "response_html", | ||||
"response", | "response", | ||||
"owner", | |||||
"section_break_4", | "section_break_4", | ||||
"email_reply_help" | "email_reply_help" | ||||
], | ], | ||||
@@ -32,14 +31,6 @@ | |||||
"label": "Response", | "label": "Response", | ||||
"mandatory_depends_on": "eval:!doc.use_html" | "mandatory_depends_on": "eval:!doc.use_html" | ||||
}, | }, | ||||
{ | |||||
"default": "user", | |||||
"fieldname": "owner", | |||||
"fieldtype": "Link", | |||||
"hidden": 1, | |||||
"label": "Owner", | |||||
"options": "User" | |||||
}, | |||||
{ | { | ||||
"fieldname": "section_break_4", | "fieldname": "section_break_4", | ||||
"fieldtype": "Section Break" | "fieldtype": "Section Break" | ||||
@@ -66,7 +57,7 @@ | |||||
], | ], | ||||
"icon": "fa fa-comment", | "icon": "fa fa-comment", | ||||
"links": [], | "links": [], | ||||
"modified": "2020-11-30 14:12:50.321633", | |||||
"modified": "2022-01-04 14:12:50.321633", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Email", | "module": "Email", | ||||
"name": "Email Template", | "name": "Email Template", | ||||
@@ -435,8 +435,8 @@ def get_context(doc): | |||||
def get_assignees(doc): | def get_assignees(doc): | ||||
assignees = [] | assignees = [] | ||||
assignees = frappe.get_all('ToDo', filters={'status': 'Open', 'reference_name': doc.name, | 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 | return recipients |
@@ -101,13 +101,10 @@ class BaseDocument(object): | |||||
"balance": 42000 | "balance": 42000 | ||||
}) | }) | ||||
""" | """ | ||||
if "doctype" in d: | |||||
self.set("doctype", d.get("doctype")) | |||||
# first set default field values of base document | # first set default field values of base document | ||||
for key in default_fields: | for key in default_fields: | ||||
if key in d: | if key in d: | ||||
self.set(key, d.get(key)) | |||||
self.set(key, d[key]) | |||||
for key, value in d.items(): | for key, value in d.items(): | ||||
self.set(key, value) | self.set(key, value) | ||||
@@ -504,6 +504,7 @@ class Document(BaseDocument): | |||||
self._sanitize_content() | self._sanitize_content() | ||||
self._save_passwords() | self._save_passwords() | ||||
self.validate_workflow() | self.validate_workflow() | ||||
self.validate_owner() | |||||
children = self.get_all_children() | children = self.get_all_children() | ||||
for d in children: | for d in children: | ||||
@@ -546,6 +547,11 @@ class Document(BaseDocument): | |||||
if not self._action == 'save': | if not self._action == 'save': | ||||
set_workflow_state_on_action(self, workflow, self._action) | 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): | def validate_set_only_once(self): | ||||
"""Validate that fields are not changed if not in insert""" | """Validate that fields are not changed if not in insert""" | ||||
set_only_once_fields = self.meta.get_set_only_once_fields() | set_only_once_fields = self.meta.get_set_only_once_fields() | ||||
@@ -1348,15 +1354,15 @@ class Document(BaseDocument): | |||||
), frappe.exceptions.InvalidDates) | ), frappe.exceptions.InvalidDates) | ||||
def get_assigned_users(self): | def get_assigned_users(self): | ||||
assignments = frappe.get_all('ToDo', | |||||
fields=['owner'], | |||||
assigned_users = frappe.get_all('ToDo', | |||||
fields=['allocated_to'], | |||||
filters={ | filters={ | ||||
'reference_type': self.doctype, | 'reference_type': self.doctype, | ||||
'reference_name': self.name, | 'reference_name': self.name, | ||||
'status': ('!=', 'Cancelled'), | 'status': ('!=', 'Cancelled'), | ||||
}) | |||||
}, pluck='allocated_to') | |||||
users = set([assignment.owner for assignment in assignments]) | |||||
users = set(assigned_users) | |||||
return users | return users | ||||
def add_tag(self, tag): | def add_tag(self, tag): | ||||
@@ -293,7 +293,7 @@ def update_link_field_values(link_fields, old, new, doctype): | |||||
if parent == new and doctype == "DocType": | if parent == new and doctype == "DocType": | ||||
parent = old | 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 | # update cached link_fields as per new | ||||
if doctype=='DocType' and field['parent'] == old: | if doctype=='DocType' and field['parent'] == old: | ||||
@@ -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.remove_db_aggregation | ||||
frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021 | 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.update_color_names_in_kanban_board_column | ||||
frappe.patches.v14_0.transform_todo_schema |
@@ -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() |
@@ -109,6 +109,7 @@ frappe.ui.form.ControlMultiSelectList = class ControlMultiSelectList extends fra | |||||
let value = decodeURIComponent($selectable_item.data().value); | let value = decodeURIComponent($selectable_item.data().value); | ||||
if ($selectable_item.hasClass('selected')) { | if ($selectable_item.hasClass('selected')) { | ||||
this.values = this.values.slice(); | |||||
this.values.push(value); | this.values.push(value); | ||||
} else { | } else { | ||||
this.values = this.values.filter(val => val !== value); | this.values = this.values.filter(val => val !== value); | ||||
@@ -215,7 +215,7 @@ frappe.ui.form.Form = class FrappeForm { | |||||
if (this.layout.tabs.length) { | if (this.layout.tabs.length) { | ||||
this.layout.tabs.every(tab => { | this.layout.tabs.every(tab => { | ||||
if (tab.df.options === 'Dashboard') { | |||||
if (tab.df.show_dashboard) { | |||||
tab.wrapper.prepend(dashboard_parent); | tab.wrapper.prepend(dashboard_parent); | ||||
dashboard_added = true; | dashboard_added = true; | ||||
return false; | return false; | ||||
@@ -1500,6 +1500,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||||
read_only: 1, | read_only: 1, | ||||
}, | }, | ||||
], | ], | ||||
primary_action_label: __("Copy to clipboard"), | |||||
primary_action: () => { | |||||
frappe.utils.copy_to_clipboard(this.get_share_url()); | |||||
d.hide(); | |||||
}, | |||||
}); | }); | ||||
d.show(); | d.show(); | ||||
} | } | ||||
@@ -105,15 +105,18 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { | |||||
this.toggle_nothing_to_show(true); | this.toggle_nothing_to_show(true); | ||||
return; | return; | ||||
} | } | ||||
let route_options = {}; | |||||
route_options = Object.assign(route_options, frappe.route_options); | |||||
if (this.report_name !== frappe.get_route()[1]) { | if (this.report_name !== frappe.get_route()[1]) { | ||||
// different report | // different report | ||||
this.load_report(); | |||||
this.load_report(route_options); | |||||
} | } | ||||
else if (frappe.has_route_options()) { | else if (frappe.has_route_options()) { | ||||
// filters passed through routes | // filters passed through routes | ||||
// so refresh report again | // so refresh report again | ||||
this.refresh_report(); | |||||
this.refresh_report(route_options); | |||||
} else { | } else { | ||||
// same report | // same report | ||||
// don't do anything to preserve state | // 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.page.clear_inner_toolbar(); | ||||
this.route = frappe.get_route(); | this.route = frappe.get_route(); | ||||
this.page_name = frappe.get_route_str(); | 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.get_report_settings(), | ||||
() => this.setup_progress_bar(), | () => this.setup_progress_bar(), | ||||
() => this.setup_page_head(), | () => this.setup_page_head(), | ||||
() => this.refresh_report(), | |||||
() => this.refresh_report(route_options), | |||||
() => this.add_chart_buttons_to_toolbar(true), | () => this.add_chart_buttons_to_toolbar(true), | ||||
() => this.add_card_button_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_message(true); | ||||
this.toggle_report(false); | this.toggle_report(false); | ||||
return frappe.run_serially([ | return frappe.run_serially([ | ||||
() => this.setup_filters(), | () => this.setup_filters(), | ||||
() => this.set_route_filters(), | |||||
() => this.set_route_filters(route_options), | |||||
() => this.page.clear_custom_actions(), | () => this.page.clear_custom_actions(), | ||||
() => this.report_settings.onload && this.report_settings.onload(this), | () => this.report_settings.onload && this.report_settings.onload(this), | ||||
() => this.refresh() | () => 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 filters_to_set = this.filters.filter(f => fields.includes(f.df.fieldname)); | ||||
const promises = filters_to_set.map(f => { | const promises = filters_to_set.map(f => { | ||||
return () => { | return () => { | ||||
const value = frappe.route_options[f.df.fieldname]; | |||||
const value = route_options[f.df.fieldname]; | |||||
f.set_value(value); | f.set_value(value); | ||||
}; | }; | ||||
}); | }); | ||||
@@ -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( | this.menu_items.push( | ||||
{ | { | ||||
label: __('Rebuild Tree'), | label: __('Rebuild Tree'), | ||||
@@ -13,7 +13,7 @@ class TestAssign(unittest.TestCase): | |||||
added = assign(todo, "test@example.com") | 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") | removed = frappe.desk.form.assign_to.remove(todo.doctype, todo.name, "test@example.com") | ||||
@@ -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 import random_string | ||||
from frappe.utils.testutils import clear_custom_fields | from frappe.utils.testutils import clear_custom_fields | ||||
from frappe.query_builder import Field | from frappe.query_builder import Field | ||||
from frappe.database import savepoint | |||||
from .test_query_builder import run_only_if, db_type_is | from .test_query_builder import run_only_if, db_type_is | ||||
from frappe.query_builder.functions import Concat_ws | from frappe.query_builder.functions import Concat_ws | ||||
@@ -267,6 +268,32 @@ class TestDB(unittest.TestCase): | |||||
for d in created_docs: | for d in created_docs: | ||||
self.assertTrue(frappe.db.exists("ToDo", d)) | 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) | @run_only_if(db_type_is.MARIADB) | ||||
class TestDDLCommandsMaria(unittest.TestCase): | class TestDDLCommandsMaria(unittest.TestCase): | ||||
@@ -252,3 +252,22 @@ class TestDocument(unittest.TestCase): | |||||
'currency': 100000 | 'currency': 100000 | ||||
}) | }) | ||||
self.assertEquals(d.get_formatted('currency', currency='INR', format="#,###.##"), '₹ 100,000.00') | 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) |
@@ -144,6 +144,11 @@ def rebuild_tree(doctype, parent_field): | |||||
if frappe.request and frappe.local.form_dict.cmd == 'rebuild_tree': | if frappe.request and frappe.local.form_dict.cmd == 'rebuild_tree': | ||||
frappe.only_for('System Manager') | 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 | # get all roots | ||||
right = 1 | right = 1 | ||||
table = DocType(doctype) | table = DocType(doctype) | ||||
@@ -159,10 +159,10 @@ class BlogPost(WebsiteGenerator): | |||||
like_count = 0 | like_count = 0 | ||||
if frappe.db.count('Feedback'): | if frappe.db.count('Feedback'): | ||||
like_count = frappe.db.count('Feedback', | |||||
like_count = frappe.db.count('Feedback', | |||||
filters = dict( | filters = dict( | ||||
reference_doctype = self.doctype, | |||||
reference_name = self.name, | |||||
reference_doctype = self.doctype, | |||||
reference_name = self.name, | |||||
like = True | like = True | ||||
) | ) | ||||
) | ) | ||||
@@ -183,7 +183,6 @@ def get_list_context(context=None): | |||||
get_list = get_blog_list, | get_list = get_blog_list, | ||||
no_breadcrumbs = True, | no_breadcrumbs = True, | ||||
hide_filters = True, | hide_filters = True, | ||||
children = get_children(), | |||||
# show_search = True, | # show_search = True, | ||||
title = _('Blog') | title = _('Blog') | ||||
) | ) | ||||
@@ -208,17 +207,34 @@ def get_list_context(context=None): | |||||
else: | else: | ||||
list_context.parents = [{"name": _("Home"), "route": "/"}] | 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 | 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(): | def clear_blog_cache(): | ||||
for blog in frappe.db.sql_list("""select route from | for blog in frappe.db.sql_list("""select route from | ||||
@@ -4,16 +4,34 @@ | |||||
{% block page_content %} | {% 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" | |||||
) }} | |||||
<div class="row py-8"> | |||||
<div class="col-md-8"> | |||||
<div class="hero"> | |||||
<div class="hero-content"> | |||||
<h1 class="hero-title">{{ blog_title or _('Blog') }}</h1> | |||||
<p class="hero-subtitle mb-0">{{ blog_introduction or '' }}</p> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="col-md-4 align-self-end"> | |||||
{%- if browse_by_category -%} | |||||
<label for="category-select" class="sr-only">{{ _("Browse by category") }}</label> | |||||
<select id="category-select" class="custom-select" onchange="window.location.pathname = this.value"> | |||||
<option value="" {{ not frappe.form_dict.category and "selected" or "" }} disabled> | |||||
{{ _("Browse by category") }} | |||||
</option> | |||||
{%- if frappe.form_dict.category -%} | |||||
<option value="blog">{{ _("Show all blogs") }}</option> | |||||
{%- endif -%} | |||||
{%- for category in blog_categories -%} | |||||
<option value="{{ category.route }}" {{ frappe.form_dict.category == category.name and "selected" or "" }}> | |||||
{{ _(category.title) }} | |||||
</option> | |||||
{%- endfor -%} | |||||
</select> | |||||
{%- endif -%} | |||||
</div> | |||||
</div> | |||||
<div class="blog-list-content"> | <div class="blog-list-content"> | ||||
<div class="website-list" data-doctype="{{ doctype }}" data-txt="{{ txt or '[notxt]' | e }}"> | <div class="website-list" data-doctype="{{ doctype }}" data-txt="{{ txt or '[notxt]' | e }}"> | ||||
@@ -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'); | |||||
}); | |||||
}); |
@@ -11,6 +11,7 @@ | |||||
"enable_social_sharing", | "enable_social_sharing", | ||||
"show_cta_in_blog", | "show_cta_in_blog", | ||||
"allow_guest_to_comment", | "allow_guest_to_comment", | ||||
"browse_by_category", | |||||
"cta_section", | "cta_section", | ||||
"title", | "title", | ||||
"subtitle", | "subtitle", | ||||
@@ -110,14 +111,20 @@ | |||||
"default": "1", | "default": "1", | ||||
"fieldname": "allow_guest_to_comment", | "fieldname": "allow_guest_to_comment", | ||||
"fieldtype": "Check", | "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", | "icon": "fa fa-cog", | ||||
"idx": 1, | "idx": 1, | ||||
"issingle": 1, | "issingle": 1, | ||||
"links": [], | "links": [], | ||||
"modified": "2021-10-28 20:44:44.143193", | |||||
"modified": "2021-12-20 13:40:32.312459", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Blog Settings", | "name": "Blog Settings", | ||||
@@ -142,5 +149,6 @@ | |||||
], | ], | ||||
"sort_field": "modified", | "sort_field": "modified", | ||||
"sort_order": "DESC", | "sort_order": "DESC", | ||||
"states": [], | |||||
"track_changes": 1 | "track_changes": 1 | ||||
} | } |
@@ -15,8 +15,7 @@ | |||||
"section_break_7", | "section_break_7", | ||||
"content", | "content", | ||||
"likes", | "likes", | ||||
"route", | |||||
"owner" | |||||
"route" | |||||
], | ], | ||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
@@ -79,13 +78,6 @@ | |||||
"fieldtype": "Data", | "fieldtype": "Data", | ||||
"in_global_search": 1, | "in_global_search": 1, | ||||
"label": "Route" | "label": "Route" | ||||
}, | |||||
{ | |||||
"default": "user", | |||||
"fieldname": "owner", | |||||
"fieldtype": "Link", | |||||
"label": "Owner", | |||||
"options": "User" | |||||
} | } | ||||
], | ], | ||||
"has_web_view": 1, | "has_web_view": 1, | ||||
@@ -93,7 +85,7 @@ | |||||
"index_web_pages_for_search": 1, | "index_web_pages_for_search": 1, | ||||
"is_published_field": "published", | "is_published_field": "published", | ||||
"links": [], | "links": [], | ||||
"modified": "2020-07-21 16:25:18.577325", | |||||
"modified": "2022-01-04 16:25:18.577325", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Help Article", | "name": "Help Article", | ||||
@@ -11,12 +11,10 @@ | |||||
"section_title", | "section_title", | ||||
"title", | "title", | ||||
"route", | "route", | ||||
"published", | |||||
"dynamic_route", | "dynamic_route", | ||||
"cb1", | "cb1", | ||||
"published", | |||||
"module", | "module", | ||||
"start_date", | |||||
"end_date", | |||||
"sb1", | "sb1", | ||||
"content_type", | "content_type", | ||||
"slideshow", | "slideshow", | ||||
@@ -25,6 +23,7 @@ | |||||
"main_section_md", | "main_section_md", | ||||
"main_section_html", | "main_section_html", | ||||
"page_blocks", | "page_blocks", | ||||
"scripting_tab", | |||||
"context_section", | "context_section", | ||||
"context_script", | "context_script", | ||||
"custom_javascript", | "custom_javascript", | ||||
@@ -34,28 +33,32 @@ | |||||
"text_align", | "text_align", | ||||
"css", | "css", | ||||
"full_width", | "full_width", | ||||
"settings", | |||||
"show_title", | "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", | "section_break_17", | ||||
"show_sidebar", | "show_sidebar", | ||||
"idx", | |||||
"website_sidebar", | "website_sidebar", | ||||
"column_break_20", | "column_break_20", | ||||
"enable_comments", | "enable_comments", | ||||
"idx", | |||||
"sb2", | "sb2", | ||||
"header", | "header", | ||||
"breadcrumbs", | |||||
"metatags_section", | |||||
"meta_title", | |||||
"meta_description", | |||||
"meta_image", | |||||
"set_meta_tags" | |||||
"breadcrumbs" | |||||
], | ], | ||||
"fields": [ | "fields": [ | ||||
{ | { | ||||
"fieldname": "section_title", | "fieldname": "section_title", | ||||
"fieldtype": "Section Break", | |||||
"label": "Title" | |||||
"fieldtype": "Tab Break", | |||||
"label": "Content" | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "title", | "fieldname": "title", | ||||
@@ -161,7 +164,7 @@ | |||||
"collapsible": 1, | "collapsible": 1, | ||||
"collapsible_depends_on": "insert_style", | "collapsible_depends_on": "insert_style", | ||||
"fieldname": "custom_css", | "fieldname": "custom_css", | ||||
"fieldtype": "Section Break", | |||||
"fieldtype": "Tab Break", | |||||
"label": "Style" | "label": "Style" | ||||
}, | }, | ||||
{ | { | ||||
@@ -185,7 +188,7 @@ | |||||
}, | }, | ||||
{ | { | ||||
"fieldname": "settings", | "fieldname": "settings", | ||||
"fieldtype": "Section Break", | |||||
"fieldtype": "Tab Break", | |||||
"label": "Settings" | "label": "Settings" | ||||
}, | }, | ||||
{ | { | ||||
@@ -267,7 +270,6 @@ | |||||
"label": "Full Width" | "label": "Full Width" | ||||
}, | }, | ||||
{ | { | ||||
"collapsible": 1, | |||||
"fieldname": "metatags_section", | "fieldname": "metatags_section", | ||||
"fieldtype": "Section Break", | "fieldtype": "Section Break", | ||||
"label": "Meta Tags" | "label": "Meta Tags" | ||||
@@ -313,6 +315,21 @@ | |||||
"fieldtype": "Link", | "fieldtype": "Link", | ||||
"label": "Module (for export)", | "label": "Module (for export)", | ||||
"options": "Module Def" | "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, | "has_web_view": 1, | ||||
@@ -322,7 +339,7 @@ | |||||
"is_published_field": "published", | "is_published_field": "published", | ||||
"links": [], | "links": [], | ||||
"max_attachments": 20, | "max_attachments": 20, | ||||
"modified": "2021-09-04 12:11:56.070994", | |||||
"modified": "2022-01-03 13:01:48.182645", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"module": "Website", | "module": "Website", | ||||
"name": "Web Page", | "name": "Web Page", | ||||
@@ -342,6 +359,7 @@ | |||||
"show_name_in_global_search": 1, | "show_name_in_global_search": 1, | ||||
"sort_field": "modified", | "sort_field": "modified", | ||||
"sort_order": "ASC", | "sort_order": "ASC", | ||||
"states": [], | |||||
"title_field": "title", | "title_field": "title", | ||||
"track_changes": 1 | "track_changes": 1 | ||||
} | } |