From 04acd0bda4415ee93c6b52f0bea65517a7c078f2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 3 Jan 2024 17:48:06 +0530 Subject: [PATCH 01/33] fix: don't add fallback for child table (#24105) --- frappe/permissions.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/frappe/permissions.py b/frappe/permissions.py index a334cc5722..f6728998bd 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -163,9 +163,6 @@ def get_doc_permissions(doc, user=None, ptype=None): if not user: user = frappe.session.user - if frappe.is_table(doc.doctype): - return {"read": 1, "write": 1} - meta = frappe.get_meta(doc.doctype) def is_user_owner(): From 3349f2b6e6e74f6f5ca7120737923653f461ea68 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 3 Jan 2024 20:18:43 +0530 Subject: [PATCH 02/33] fix: nested has_permission calls erase messages --- frappe/permissions.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/frappe/permissions.py b/frappe/permissions.py index f6728998bd..c54637a77c 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -1,6 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import copy +import functools import frappe import frappe.share @@ -37,17 +38,23 @@ AUTOMATIC_ROLES = (GUEST_ROLE, ALL_USER_ROLE, SYSTEM_USER_ROLE, ADMIN_ROLE) def print_has_permission_check_logs(func): + @functools.wraps(func) def inner(*args, **kwargs): - frappe.flags["has_permission_check_logs"] = [] - result = func(*args, **kwargs) - self_perm_check = True if not kwargs.get("user") else kwargs.get("user") == frappe.session.user raise_exception = kwargs.get("raise_exception", True) + self_perm_check = True if not kwargs.get("user") else kwargs.get("user") == frappe.session.user + + if raise_exception: + frappe.flags["has_permission_check_logs"] = [] + + result = func(*args, **kwargs) # print only if access denied # and if user is checking his own permission if not result and self_perm_check and raise_exception: msgprint(("
").join(frappe.flags.get("has_permission_check_logs", []))) - frappe.flags.pop("has_permission_check_logs", None) + + if raise_exception: + frappe.flags.pop("has_permission_check_logs", None) return result return inner From 94f53d3f9149ad6f7e26d3b3c94ef62025a82452 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 4 Jan 2024 10:33:09 +0530 Subject: [PATCH 03/33] fix!: frappe.has_permission(throw=True) works as expected - This wasn't throwing PermissionError - It was erasing all perm check messages --- frappe/__init__.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 16db0a4477..3c83ed335f 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1002,19 +1002,11 @@ def has_permission( ) if throw and not out: - # mimics frappe.throw document_label = ( f"{_(doctype)} {doc if isinstance(doc, str) else doc.name}" if doc else _(doctype) ) - msgprint( - _("No permission for {0}").format(document_label), - raise_exception=ValidationError, - title=None, - indicator="red", - is_minimizable=None, - wide=None, - as_list=False, - ) + frappe.flags.error_message = _("No permission for {0}").format(document_label) + raise frappe.PermissionError return out From 35ea093b51d397c683d01dcd68b0d136a113f55a Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Wed, 3 Jan 2024 16:24:43 +0530 Subject: [PATCH 04/33] feat: support background jobs in recorder Signed-off-by: Akhil Narang --- frappe/core/doctype/recorder/recorder.json | 13 ++++++++++++- frappe/core/doctype/recorder/recorder.py | 2 +- frappe/hooks.py | 2 ++ frappe/recorder.py | 10 ++++++++++ frappe/utils/background_jobs.py | 2 +- 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/recorder/recorder.json b/frappe/core/doctype/recorder/recorder.json index aa0d782811..391f808f31 100644 --- a/frappe/core/doctype/recorder/recorder.json +++ b/frappe/core/doctype/recorder/recorder.json @@ -14,6 +14,7 @@ "cmd", "time", "duration", + "event_type", "section_break_1skt", "request_headers", "section_break_sgro", @@ -30,6 +31,7 @@ "label": "Path" }, { + "depends_on": "eval:doc.event_type==\"HTTP Request\"", "fieldname": "cmd", "fieldtype": "Data", "in_standard_filter": 1, @@ -67,6 +69,7 @@ "fieldtype": "Section Break" }, { + "depends_on": "eval:doc.event_type==\"HTTP Request\"", "fieldname": "request_headers", "fieldtype": "Code", "label": "Request Headers" @@ -76,11 +79,13 @@ "fieldtype": "Section Break" }, { + "depends_on": "eval:doc.event_type==\"HTTP Request\"", "fieldname": "form_dict", "fieldtype": "Code", "label": "Form Dict" }, { + "depends_on": "eval:doc.event_type==\"HTTP Request\"", "fieldname": "method", "fieldtype": "Select", "in_standard_filter": 1, @@ -96,6 +101,12 @@ { "fieldname": "section_break_9jhm", "fieldtype": "Section Break" + }, + { + "fieldname": "event_type", + "fieldtype": "Data", + "hidden": 1, + "label": "Event Type" } ], "hide_toolbar": 1, @@ -103,7 +114,7 @@ "index_web_pages_for_search": 1, "is_virtual": 1, "links": [], - "modified": "2023-08-10 12:01:03.456643", + "modified": "2024-01-03 16:45:47.110048", "modified_by": "Administrator", "module": "Core", "name": "Recorder", diff --git a/frappe/core/doctype/recorder/recorder.py b/frappe/core/doctype/recorder/recorder.py index f5ef909a2a..347a237743 100644 --- a/frappe/core/doctype/recorder/recorder.py +++ b/frappe/core/doctype/recorder/recorder.py @@ -19,6 +19,7 @@ class Recorder(Document): cmd: DF.Data | None duration: DF.Float + event_type: DF.Data | None form_dict: DF.Code | None method: DF.Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"] number_of_queries: DF.Int @@ -27,7 +28,6 @@ class Recorder(Document): sql_queries: DF.Table[RecorderQuery] time: DF.Datetime | None time_in_queries: DF.Float - # end: auto-generated types def load_from_db(self): diff --git a/frappe/hooks.py b/frappe/hooks.py index e82714389b..c7bcd9895c 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -428,6 +428,7 @@ before_request = [ # Background Job Hooks before_job = [ + "frappe.recorder.record", "frappe.monitor.start", ] @@ -438,6 +439,7 @@ if os.getenv("FRAPPE_SENTRY_DSN") and ( before_job.append("frappe.utils.sentry.set_sentry_context") after_job = [ + "frappe.recorder.dump", "frappe.monitor.stop", "frappe.utils.file_lock.release_document_locks", "frappe.utils.telemetry.flush", diff --git a/frappe/recorder.py b/frappe/recorder.py index 9e67ca9ff0..f689cc9e7c 100644 --- a/frappe/recorder.py +++ b/frappe/recorder.py @@ -151,7 +151,16 @@ class Recorder: self.method = frappe.request.method self.headers = dict(frappe.local.request.headers) self.form_dict = frappe.local.form_dict + self.event_type = "HTTP Request" + elif frappe.job: + self.event_type = "Background Job" + self.path = frappe.job.method + self.cmd = None + self.method = None + self.headers = None + self.form_dict = None else: + self.event_type = None self.path = None self.cmd = None self.method = None @@ -173,6 +182,7 @@ class Recorder: "time_queries": float("{:0.3f}".format(sum(call["duration"] for call in self.calls))), "duration": float(f"{(datetime.datetime.now() - self.time).total_seconds() * 1000:0.3f}"), "method": self.method, + "event_type": self.event_type, } frappe.cache.hset(RECORDER_REQUEST_SPARSE_HASH, self.uuid, request_data) frappe.publish_realtime( diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py index 839c196966..71097a3a58 100755 --- a/frappe/utils/background_jobs.py +++ b/frappe/utils/background_jobs.py @@ -199,7 +199,7 @@ def execute_job(site, method, event, job_name, kwargs, user=None, is_async=True, method_name = method method = frappe.get_attr(method) else: - method_name = cstr(method.__name__) + method_name = f"{method.__module__}.{method.__qualname__}" actual_func_name = kwargs.get("job_type") if "run_scheduled_job" in method_name else method_name setproctitle.setproctitle(f"rq: Started running {actual_func_name} at {time.time()}") From dd690c79d168fe4c9b3f8cff9ca50a6e35bb2a67 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Thu, 4 Jan 2024 11:37:39 +0100 Subject: [PATCH 05/33] fix: use correct parent field in rebuild_tree() (#24107) * fix: use correct parent field in rebuild tree * refactor!: ignore parent_field parameter to rebuild_tree --- frappe/public/js/frappe/views/treeview.js | 1 - frappe/utils/nestedset.py | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/treeview.js b/frappe/public/js/frappe/views/treeview.js index 0096a333bd..c255bca9dc 100644 --- a/frappe/public/js/frappe/views/treeview.js +++ b/frappe/public/js/frappe/views/treeview.js @@ -235,7 +235,6 @@ frappe.views.TreeView = class TreeView { method: "frappe.utils.nestedset.rebuild_tree", args: { doctype: me.doctype, - parent_field: "parent_" + me.doctype.toLowerCase().replace(/ /g, "_"), }, callback: function (r) { if (!r.exc) { diff --git a/frappe/utils/nestedset.py b/frappe/utils/nestedset.py index 2beaa63447..59f68d7afc 100644 --- a/frappe/utils/nestedset.py +++ b/frappe/utils/nestedset.py @@ -168,9 +168,10 @@ def update_move_node(doc: Document, parent_field: str): @frappe.whitelist() -def rebuild_tree(doctype, parent_field): - """ - call rebuild_node for all root nodes +def rebuild_tree(doctype, parent_field=None): + """Call rebuild_node for all root nodes. + + The `parent_field` parameter is ignored and will be removed in v16+ (kept for backward compatibility). """ # Check for perm if called from client-side @@ -184,6 +185,8 @@ def rebuild_tree(doctype, parent_field): title=_("Invalid Action"), ) + parent_field = meta.nsm_parent_field or f"parent_{frappe.scrub(doctype)}" + # get all roots right = 1 table = DocType(doctype) From 807cfd66f1efd5aa14766382b6c7c217d855afb0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 4 Jan 2024 15:42:18 +0530 Subject: [PATCH 06/33] fix: Reset failed attempts ONLY if succesful connection is made --- frappe/email/doctype/email_account/email_account.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 2f4c23eac6..e5f21bbc47 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -268,15 +268,15 @@ class EmailAccount(Document): if not in_receive and self.use_imap: email_server.imap.logout() - # reset failed attempts count - self.set_failed_attempts_count(0) - return email_server def check_email_server_connection(self, email_server, in_receive): # tries to connect to email server and handles failure try: email_server.connect() + + # reset failed attempts count - do it after succesful connection + self.set_failed_attempts_count(0) except (error_proto, imaplib.IMAP4.error) as e: message = cstr(e).lower().replace(" ", "") auth_error_codes = [ From 71908f1e00459c937af8a2dacc799980c050cf00 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 4 Jan 2024 15:43:10 +0530 Subject: [PATCH 07/33] refactor: respect multi-tenancy redis set/get don't consider site --- frappe/email/doctype/email_account/email_account.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index e5f21bbc47..3c1d0d85a7 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -510,10 +510,10 @@ class EmailAccount(Document): self.set_failed_attempts_count(self.get_failed_attempts_count() + 1) def set_failed_attempts_count(self, value): - frappe.cache.set(f"{self.name}:email-account-failed-attempts", value) + frappe.cache.set_value(f"{self.name}:email-account-failed-attempts", value) def get_failed_attempts_count(self): - return cint(frappe.cache.get(f"{self.name}:email-account-failed-attempts")) + return cint(frappe.cache.get_value(f"{self.name}:email-account-failed-attempts")) def receive(self): """Called by scheduler to receive emails from this EMail account using POP3/IMAP.""" From 3836d942ac98ffd5250c57d99a4d3f000adc3d82 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 4 Jan 2024 15:50:25 +0530 Subject: [PATCH 08/33] fix: logic to disable broken account This doesn't work because when email is broken we still continue and down the line changes get rolled back. --- .../doctype/email_account/email_account.py | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 3c1d0d85a7..f4d7cbf8a2 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -490,25 +490,29 @@ class EmailAccount(Document): def handle_incoming_connect_error(self, description): if self.get_failed_attempts_count() > 2: - self.db_set("enable_incoming", 0) - - for user in get_system_managers(only_name=True): - try: - assign_to.add( - { - "assign_to": user, - "doctype": self.doctype, - "name": self.name, - "description": description, - "priority": "High", - "notify": 1, - } - ) - except assign_to.DuplicateToDoError: - frappe.clear_last_message() + # This is done in background to avoid committing here. + frappe.enqueue(self._disable_broken_incoming_account, description=description) else: self.set_failed_attempts_count(self.get_failed_attempts_count() + 1) + def _disable_broken_incoming_account(self, description): + self.db_set("enable_incoming", 0) + + for user in get_system_managers(only_name=True): + try: + assign_to.add( + { + "assign_to": user, + "doctype": self.doctype, + "name": self.name, + "description": description, + "priority": "High", + "notify": 1, + } + ) + except assign_to.DuplicateToDoError: + pass + def set_failed_attempts_count(self, value): frappe.cache.set_value(f"{self.name}:email-account-failed-attempts", value) From edac879d7e2b6061a2f79d423d585c7347327841 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:43:12 +0100 Subject: [PATCH 09/33] refactor!: remove unused parameter from rebuild_tree, add type hints (#24123) * refactor!: remove unused parameter from rebuild_tree, add type hints * fix: remove parent_field parameter from backend calls --- frappe/tests/test_nestedset.py | 2 +- frappe/utils/nestedset.py | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/frappe/tests/test_nestedset.py b/frappe/tests/test_nestedset.py index 53304a5bb3..d308388646 100644 --- a/frappe/tests/test_nestedset.py +++ b/frappe/tests/test_nestedset.py @@ -145,7 +145,7 @@ class TestNestedSet(FrappeTestCase): leaf_node.reload() def test_rebuild_tree(self): - rebuild_tree(TEST_DOCTYPE, "parent_test_tree_doctype") + rebuild_tree(TEST_DOCTYPE) self.test_basic_tree() def test_move_group_into_another(self): diff --git a/frappe/utils/nestedset.py b/frappe/utils/nestedset.py index 59f68d7afc..60565581c9 100644 --- a/frappe/utils/nestedset.py +++ b/frappe/utils/nestedset.py @@ -168,12 +168,8 @@ def update_move_node(doc: Document, parent_field: str): @frappe.whitelist() -def rebuild_tree(doctype, parent_field=None): - """Call rebuild_node for all root nodes. - - The `parent_field` parameter is ignored and will be removed in v16+ (kept for backward compatibility). - """ - +def rebuild_tree(doctype: str) -> None: + """Call rebuild_node for all root nodes.""" # Check for perm if called from client-side if frappe.request and frappe.local.form_dict.cmd == "rebuild_tree": frappe.only_for("System Manager") @@ -330,7 +326,7 @@ class NestedSet(Document): ) if merge: - rebuild_tree(self.doctype, parent_field) + rebuild_tree(self.doctype) def validate_one_root(self): if not self.get(self.nsm_parent_field): From cc2bb854841a1e13d43face6e32d97fab4d4f9bf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 4 Jan 2024 15:53:59 +0530 Subject: [PATCH 10/33] fix: correct arg type assign_to expects a list of strings of users not a single string. --- frappe/email/doctype/email_account/email_account.py | 6 ++++-- frappe/tests/test_email.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index f4d7cbf8a2..0fa328b42d 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -294,6 +294,8 @@ class EmailAccount(Document): error_message = _( "Authentication failed while receiving emails from Email Account: {0}." ).format(self.name) + + error_message = _("Email Account Disabled.") + " " + error_message error_message += "
" + _("Message from server: {0}").format(cstr(e)) self.handle_incoming_connect_error(description=error_message) return None @@ -489,7 +491,7 @@ class EmailAccount(Document): state.pop("_smtp_server_instance", None) def handle_incoming_connect_error(self, description): - if self.get_failed_attempts_count() > 2: + if self.get_failed_attempts_count() > 5: # This is done in background to avoid committing here. frappe.enqueue(self._disable_broken_incoming_account, description=description) else: @@ -502,7 +504,7 @@ class EmailAccount(Document): try: assign_to.add( { - "assign_to": user, + "assign_to": [user], "doctype": self.doctype, "name": self.name, "description": description, diff --git a/frappe/tests/test_email.py b/frappe/tests/test_email.py index 13f385a22d..bd17a523ba 100644 --- a/frappe/tests/test_email.py +++ b/frappe/tests/test_email.py @@ -361,8 +361,10 @@ class TestEmailIntegrationTest(FrappeTestCase): subject = "checking if email works" content = "is email working?" - frappe.sendmail(sender=sender, recipients=recipients, subject=subject, content=content, now=True) - email = frappe.get_last_doc("Email Queue") + email = frappe.sendmail( + sender=sender, recipients=recipients, subject=subject, content=content, now=True + ) + email.reload() self.assertEqual(email.sender, sender) self.assertEqual(len(email.recipients), 2) self.assertEqual(email.status, "Sent") From 3916398f1ad7a871d6e8afd67644e1cf43310cf1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 4 Jan 2024 19:08:59 +0530 Subject: [PATCH 11/33] fix(recorder): Normalize `IN` in SQL queries (#24132) --- frappe/recorder.py | 5 ++++- frappe/tests/test_recorder.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/recorder.py b/frappe/recorder.py index f689cc9e7c..be0a1e4969 100644 --- a/frappe/recorder.py +++ b/frappe/recorder.py @@ -121,7 +121,10 @@ def normalize_query(query: str) -> str: for token in q.flatten(): if "Token.Literal" in str(token.ttype): token.value = "?" - return str(q) + + # Transform IN parts like this: IN (?, ?, ?) -> IN (?) + q = re.sub(r"( IN )\(\?[\s\n\?\,]*\)", r"\1(?)", str(q), flags=re.IGNORECASE) + return q except Exception as e: print("Failed to normalize query ", e) diff --git a/frappe/tests/test_recorder.py b/frappe/tests/test_recorder.py index 5349f15dbf..40e8bdc127 100644 --- a/frappe/tests/test_recorder.py +++ b/frappe/tests/test_recorder.py @@ -152,6 +152,7 @@ class TestQueryNormalization(FrappeTestCase): "select * from `user` where a > 5": "select * from `user` where a > ?", "select `name` from `user`": "select `name` from `user`", "select `name` from `user` limit 10": "select `name` from `user` limit ?", + "select `name` from `user` where name in ('a', 'b', 'c')": "select `name` from `user` where name in (?)", } for query, normalized in test_cases.items(): From 7f9331fc8d0dcca1cd18c197b14bf129e45809d7 Mon Sep 17 00:00:00 2001 From: Corentin Flr <10946971+cogk@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:12:45 +0100 Subject: [PATCH 12/33] fix(router): Don't capture clicks on links with target attr (#24110) --- frappe/public/js/frappe/router.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 448bb9c08b..d40322c649 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -37,6 +37,10 @@ $("body").on("click", "a", function (e) { const href = target_element.getAttribute("href"); const is_on_same_host = target_element.hostname === window.location.hostname; + if (target_element.getAttribute("target") === "_blank") { + return; + } + const override = (route) => { e.preventDefault(); frappe.set_route(route); From 941b5faf65749667e5b78a312e64cae1a130bc0f Mon Sep 17 00:00:00 2001 From: Fadil SIddique <48912196+fadilsiddique@users.noreply.github.com> Date: Thu, 4 Jan 2024 18:17:45 +0400 Subject: [PATCH 13/33] fix: number card only gives integer (#24047) * fix: number card only gives integer * fix: replace float with flt * fix: minor fix --------- Co-authored-by: fadilsid --- frappe/desk/doctype/number_card/number_card.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index 9fc12a1e0d..eec086f3bb 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -10,7 +10,7 @@ from frappe.model.naming import append_number_if_name_exists from frappe.modules.export_file import export_to_files from frappe.query_builder import Criterion from frappe.query_builder.utils import DocType -from frappe.utils import cint +from frappe.utils import cint, flt class NumberCard(Document): @@ -165,7 +165,7 @@ def get_result(doc, filters, to_date=None): ) number = res[0]["result"] if res else 0 - return cint(number) + return flt(number) @frappe.whitelist() From f7ff072b5a02ed49c6e73d41e2b073160fcd63d6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 4 Jan 2024 20:08:24 +0530 Subject: [PATCH 14/33] test: fix ui tests (#24136) caused by https://github.com/frappe/frappe/pull/24077 --- cypress/integration/view_routing.js | 6 +++--- cypress/integration/workspace.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cypress/integration/view_routing.js b/cypress/integration/view_routing.js index 72fb6836ec..5942d4f005 100644 --- a/cypress/integration/view_routing.js +++ b/cypress/integration/view_routing.js @@ -224,8 +224,8 @@ context("View", () => { }); }); - it("Route to Settings Workspace", () => { - cy.visit("/app/settings"); - cy.get(".title-text").should("contain", "Settings"); + it("Route to Website Workspace", () => { + cy.visit("/app/website"); + cy.get(".title-text").should("contain", "Website"); }); }); diff --git a/cypress/integration/workspace.js b/cypress/integration/workspace.js index 1499c8772e..4079877c9c 100644 --- a/cypress/integration/workspace.js +++ b/cypress/integration/workspace.js @@ -7,8 +7,8 @@ context("Workspace 2.0", () => { it("Navigate to page from sidebar", () => { cy.visit("/app/build"); cy.get(".codex-editor__redactor .ce-block"); - cy.get('.sidebar-item-container[item-name="Settings"]').first().click(); - cy.location("pathname").should("eq", "/app/settings"); + cy.get('.sidebar-item-container[item-name="Website"]').first().click(); + cy.location("pathname").should("eq", "/app/website"); }); it("Create Private Page", () => { From 3fd26775b33399d3f20ae03e8c89b5e409dec770 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Fri, 5 Jan 2024 19:02:53 +0100 Subject: [PATCH 15/33] fix(Web Form): make button label translatable (#24122) * fix(web Form): make button label translatable * fix: context in web form translations --- .../website/doctype/web_form/templates/web_form.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/website/doctype/web_form/templates/web_form.html b/frappe/website/doctype/web_form/templates/web_form.html index e30284e1ca..9b0f23e185 100644 --- a/frappe/website/doctype/web_form/templates/web_form.html +++ b/frappe/website/doctype/web_form/templates/web_form.html @@ -17,7 +17,7 @@ {% block header_buttons %} {% if allow_edit and in_view_mode %} - {{ _("Edit Response", null, "Button in web form") }} + {{ _("Edit Response", context="Button in web form") }} {% endif %} {% if allow_print and in_view_mode %} @@ -38,10 +38,10 @@ {% if not in_view_mode %} - + {% endif %} {% endblock %} @@ -147,10 +147,10 @@ {% else %} {% if show_list %} - {{ _("See previous responses", null, "Button in web form") }} + {{ _("See previous responses", context="Button in web form") }} {% endif %} {% if not login_required or allow_multiple %} - {{ _("Submit another response", null, "Button in web form") }} + {{ _("Submit another response", context="Button in web form") }} {% endif %} {% endif %} From 745080c56e7466791837bdf10c2d099981b72031 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Sat, 6 Jan 2024 09:42:53 +0530 Subject: [PATCH 16/33] fix(test_recorder): get the correct request (#24143) Signed-off-by: Akhil Narang --- frappe/recorder.py | 13 ++++++++++--- frappe/tests/test_recorder.py | 8 +++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frappe/recorder.py b/frappe/recorder.py index be0a1e4969..079251997f 100644 --- a/frappe/recorder.py +++ b/frappe/recorder.py @@ -8,6 +8,7 @@ import re import time from collections import Counter from collections.abc import Callable +from enum import Enum import sqlparse @@ -21,6 +22,12 @@ RECORDER_REQUEST_HASH = "recorder-requests" TRACEBACK_PATH_PATTERN = re.compile(".*/apps/") +class RecorderEvent(str, Enum): + HTTP_REQUEST = "HTTP Request" + BACKGROUND_JOB = "Background Job" + INVALID = "Invalid" + + def sql(*args, **kwargs): start_time = time.monotonic() result = frappe.db._sql(*args, **kwargs) @@ -154,16 +161,16 @@ class Recorder: self.method = frappe.request.method self.headers = dict(frappe.local.request.headers) self.form_dict = frappe.local.form_dict - self.event_type = "HTTP Request" + self.event_type = RecorderEvent.HTTP_REQUEST elif frappe.job: - self.event_type = "Background Job" + self.event_type = RecorderEvent.BACKGROUND_JOB self.path = frappe.job.method self.cmd = None self.method = None self.headers = None self.form_dict = None else: - self.event_type = None + self.event_type = RecorderEvent.INVALID self.path = None self.cmd = None self.method = None diff --git a/frappe/tests/test_recorder.py b/frappe/tests/test_recorder.py index 40e8bdc127..817b5319f7 100644 --- a/frappe/tests/test_recorder.py +++ b/frappe/tests/test_recorder.py @@ -122,7 +122,13 @@ class TestRecorder(FrappeTestCase): frappe.recorder.post_process() requests = frappe.recorder.get() - request = frappe.recorder.get(requests[0]["uuid"]) + request = frappe.recorder.get( + next( + request + for request in requests + if request["event_type"] == frappe.recorder.RecorderEvent.HTTP_REQUEST + )["uuid"] + ) for query, call in zip(queries, request["calls"]): self.assertEqual(call["exact_copies"], query[1]) From c6bdc7fe459db93b5b29deed6fc32c29d06d7c86 Mon Sep 17 00:00:00 2001 From: Corentin Flr <10946971+cogk@users.noreply.github.com> Date: Sun, 7 Jan 2024 08:06:55 +0100 Subject: [PATCH 17/33] fix(workspace): Fix widget rendering for EditorJS v2.28 (#23918) * fix(workspace): Ensure Block.render() always return an element If `has_data` is false, then `this.wrapper` is still empty (because it was just created and `this.make` did nothing), so it's safe to give it back to EditorJS for rendering. * build(deps): Bump EditorJS --- .../js/frappe/views/workspace/blocks/card.js | 2 +- .../js/frappe/views/workspace/blocks/chart.js | 2 +- .../views/workspace/blocks/custom_block.js | 2 +- .../views/workspace/blocks/number_card.js | 2 +- .../views/workspace/blocks/onboarding.js | 2 +- .../views/workspace/blocks/quick_list.js | 2 +- .../frappe/views/workspace/blocks/shortcut.js | 2 +- package.json | 2 +- yarn.lock | 36 +++---------------- 9 files changed, 13 insertions(+), 39 deletions(-) diff --git a/frappe/public/js/frappe/views/workspace/blocks/card.js b/frappe/public/js/frappe/views/workspace/blocks/card.js index 840339dab8..6d3507c454 100644 --- a/frappe/public/js/frappe/views/workspace/blocks/card.js +++ b/frappe/public/js/frappe/views/workspace/blocks/card.js @@ -32,7 +32,7 @@ export default class Card extends Block { if (this.data && this.data.card_name) { let has_data = this.make("card", this.data.card_name, "links"); - if (!has_data) return; + if (!has_data) return this.wrapper; } if (!this.readOnly) { diff --git a/frappe/public/js/frappe/views/workspace/blocks/chart.js b/frappe/public/js/frappe/views/workspace/blocks/chart.js index a396472da9..924ad39dfb 100644 --- a/frappe/public/js/frappe/views/workspace/blocks/chart.js +++ b/frappe/public/js/frappe/views/workspace/blocks/chart.js @@ -33,7 +33,7 @@ export default class Chart extends Block { if (this.data && this.data.chart_name) { let has_data = this.make("chart", this.data.chart_name); - if (!has_data) return; + if (!has_data) return this.wrapper; } if (!this.readOnly) { diff --git a/frappe/public/js/frappe/views/workspace/blocks/custom_block.js b/frappe/public/js/frappe/views/workspace/blocks/custom_block.js index b0b6a77b25..35bf69a084 100644 --- a/frappe/public/js/frappe/views/workspace/blocks/custom_block.js +++ b/frappe/public/js/frappe/views/workspace/blocks/custom_block.js @@ -32,7 +32,7 @@ export default class CustomBlock extends Block { if (this.data && this.data.custom_block_name) { let has_data = this.make("custom_block", this.data.custom_block_name); - if (!has_data) return; + if (!has_data) return this.wrapper; } if (!this.readOnly) { diff --git a/frappe/public/js/frappe/views/workspace/blocks/number_card.js b/frappe/public/js/frappe/views/workspace/blocks/number_card.js index e560f85a51..28e964fe1b 100644 --- a/frappe/public/js/frappe/views/workspace/blocks/number_card.js +++ b/frappe/public/js/frappe/views/workspace/blocks/number_card.js @@ -33,7 +33,7 @@ export default class NumberCard extends Block { if (this.data && this.data.number_card_name) { let has_data = this.make("number_card", this.data.number_card_name); - if (!has_data) return; + if (!has_data) return this.wrapper; } if (!this.readOnly) { diff --git a/frappe/public/js/frappe/views/workspace/blocks/onboarding.js b/frappe/public/js/frappe/views/workspace/blocks/onboarding.js index c279da79e5..534fe9eb37 100644 --- a/frappe/public/js/frappe/views/workspace/blocks/onboarding.js +++ b/frappe/public/js/frappe/views/workspace/blocks/onboarding.js @@ -113,7 +113,7 @@ export default class Onboarding extends Block { if (this.data && this.data.onboarding_name) { let has_data = this.make("onboarding", this.data.onboarding_name); - if (!has_data) return; + if (!has_data) return this.wrapper; } if (!this.readOnly) { diff --git a/frappe/public/js/frappe/views/workspace/blocks/quick_list.js b/frappe/public/js/frappe/views/workspace/blocks/quick_list.js index 5dcb8d66ec..1edaf43c34 100644 --- a/frappe/public/js/frappe/views/workspace/blocks/quick_list.js +++ b/frappe/public/js/frappe/views/workspace/blocks/quick_list.js @@ -33,7 +33,7 @@ export default class QuickList extends Block { if (this.data && this.data.quick_list_name) { let has_data = this.make("quick_list", this.data.quick_list_name); - if (!has_data) return; + if (!has_data) return this.wrapper; } if (!this.readOnly) { diff --git a/frappe/public/js/frappe/views/workspace/blocks/shortcut.js b/frappe/public/js/frappe/views/workspace/blocks/shortcut.js index 56b4d18d62..6322cb22b7 100644 --- a/frappe/public/js/frappe/views/workspace/blocks/shortcut.js +++ b/frappe/public/js/frappe/views/workspace/blocks/shortcut.js @@ -52,7 +52,7 @@ export default class Shortcut extends Block { if (this.data && this.data.shortcut_name) { let has_data = this.make("shortcut", this.data.shortcut_name); - if (!has_data) return; + if (!has_data) return this.wrapper; } if (!this.readOnly) { diff --git a/package.json b/package.json index 9968ba36dc..6c382dab28 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "homepage": "https://frappeframework.com", "dependencies": { - "@editorjs/editorjs": "~2.26.3", + "@editorjs/editorjs": "^2.28.2", "@frappe/esbuild-plugin-postcss2": "^0.1.3", "@headlessui/vue": "^1.7.16", "@popperjs/core": "^2.11.2", diff --git a/yarn.lock b/yarn.lock index cac489329f..7c648aa992 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31,21 +31,10 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" -"@codexteam/icons@0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.1.0.tgz#a02885fe8699f69902d05b077b5f1cd48a2ca6b9" - integrity sha512-jW1fWnwtWzcP4FBGsaodbJY3s1ZaRU+IJy1pvJ7ygNQxkQinybJcwXoyt0a5mWwu/4w30A42EWhCrZn8lp4fdw== - -"@editorjs/editorjs@~2.26.3": - version "2.26.5" - resolved "https://registry.yarnpkg.com/@editorjs/editorjs/-/editorjs-2.26.5.tgz#ee0f1dbd3a3c6ba97d3ed30f13ab7d2e7b29dbe4" - integrity sha512-imwXZi9NmzxKjNosa1xQf286liJYsTe2J2DWCiV5TwKhvYZ1INg5Y+FietcM2v65QmeLqP7wgBUhoI7wiCB+yQ== - dependencies: - "@codexteam/icons" "0.1.0" - codex-notifier "^1.1.2" - codex-tooltip "^1.0.5" - html-janitor "^2.0.4" - nanoid "^3.1.22" +"@editorjs/editorjs@^2.28.2": + version "2.28.2" + resolved "https://registry.yarnpkg.com/@editorjs/editorjs/-/editorjs-2.28.2.tgz#a265c7d10e83adef81813e4dc0f01fe3464dff50" + integrity sha512-g6V0Nd3W9IIWMpvxDNTssQ6e4kxBp1Y0W4GIf8cXRlmcBp3TUjrgCYJQmNy3l2a6ZzhyBAoVSe8krJEq4g7PQw== "@esbuild/linux-loong64@0.14.54": version "0.14.54" @@ -680,16 +669,6 @@ cluster-key-slot@1.1.2: resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== -codex-notifier@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/codex-notifier/-/codex-notifier-1.1.2.tgz#a733079185f4c927fa296f1d71eb8753fe080895" - integrity sha512-DCp6xe/LGueJ1N5sXEwcBc3r3PyVkEEDNWCVigfvywAkeXcZMk9K41a31tkEFBW0Ptlwji6/JlAb49E3Yrxbtg== - -codex-tooltip@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/codex-tooltip/-/codex-tooltip-1.0.5.tgz#ba25fd5b3a58ba2f73fd667c2b46987ffd1edef2" - integrity sha512-IuA8LeyLU5p1B+HyhOsqR6oxyFQ11k3i9e9aXw40CrHFTRO2Y1npNBVU3W1SvhKAbUU7R/YikUBdcYFP0RcJag== - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1704,11 +1683,6 @@ homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: dependencies: parse-passwd "^1.0.0" -html-janitor@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/html-janitor/-/html-janitor-2.0.4.tgz#ae5a115cdf3331cd5501edd7b5471b18ea44cdbb" - integrity sha512-92J5h9jNZRk30PMHapjHEJfkrBWKCOy0bq3oW2pBungky6lzYSoboBGPMvxl1XRKB2q+kniQmsLsPbdpY7RM2g== - html5-qrcode@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/html5-qrcode/-/html5-qrcode-2.3.8.tgz#0b0cdf7a9926cfd4be530e13a51db47592adfa0d" @@ -2263,7 +2237,7 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.1.22, nanoid@^3.3.6: +nanoid@^3.3.6: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== From d0157f78624b7c275d2bc6648ea05473ff471178 Mon Sep 17 00:00:00 2001 From: Safwan Samsudeen <62411302+safwansamsudeen@users.noreply.github.com> Date: Sun, 7 Jan 2024 18:17:30 +0530 Subject: [PATCH 18/33] fix: mobile issue with form pop up (#24125) * fix: mobile configure columns issue * fix: correct grid view issue * fix header too --- frappe/public/js/frappe/form/grid_row.js | 12 ++++++------ frappe/public/js/frappe/list/list_settings.js | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index ba913e53f0..d403becaeb 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -429,10 +429,10 @@ export default class GridRow { $(`
-
+
${__("Fieldname").bold()}
-
+
${__("Column Width").bold()}
@@ -522,13 +522,13 @@ export default class GridRow { data-label='${docfield.label}' data-type='${docfield.fieldtype}'>
-
+ -
+
${__(docfield.label)}
-
-
+
diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index d81e722ebc..aa1cb31427 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -114,13 +114,13 @@ export default class ListSettings { data-label="${me.fields[idx].label}" data-type="${me.fields[idx].type}">
-
+
${frappe.utils.icon("drag", "xs", "", "", "sortable-handle " + show_sortable_handle)}
-
+
${me.fields[idx].label}
-
+
${frappe.utils.icon("delete", "xs")} From cb2e803d0c217fcf5a3463930a5a983dd2257bcc Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 8 Jan 2024 11:39:02 +0530 Subject: [PATCH 19/33] fix(minor): Unpublish article when category is unpublished #15361 --- .../doctype/help_article/test_help_article.py | 12 ++++++++++++ .../website/doctype/help_category/help_category.py | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/frappe/website/doctype/help_article/test_help_article.py b/frappe/website/doctype/help_article/test_help_article.py index a7576c7168..b17a8eda8f 100644 --- a/frappe/website/doctype/help_article/test_help_article.py +++ b/frappe/website/doctype/help_article/test_help_article.py @@ -39,6 +39,18 @@ class TestHelpArticle(FrappeTestCase): self.assertEqual(self.help_article.helpful, 1) self.assertEqual(self.help_article.not_helpful, 1) + def test_category_disable(self): + self.help_article.load_from_db() + self.help_article.published = 1 + self.help_article.save() + + self.help_category.load_from_db() + self.help_category.published = 0 + self.help_category.save() + + self.help_article.load_from_db() + self.assertEqual(self.help_article.published, 0) + @classmethod def tearDownClass(cls) -> None: frappe.delete_doc(cls.help_article.doctype, cls.help_article.name) diff --git a/frappe/website/doctype/help_category/help_category.py b/frappe/website/doctype/help_category/help_category.py index 7c78ff3d46..10efcb30e7 100644 --- a/frappe/website/doctype/help_category/help_category.py +++ b/frappe/website/doctype/help_category/help_category.py @@ -32,6 +32,11 @@ class HelpCategory(WebsiteGenerator): def validate(self): self.set_route() + # disable help articles of this category + if not self.published: + for d in frappe.get_all("Help Article", dict(category=self.name)): + frappe.db.set_value("Help Article", d.name, "published", 0) + def set_route(self): if not self.route: self.route = "kb/" + self.scrub(self.category_name) From 04646024cb5fa9109e4273b9f7ec2a70e4ac36d1 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 8 Jan 2024 11:56:51 +0530 Subject: [PATCH 20/33] fix(minor): show footer on login via Website Settings, fixes #24153 --- frappe/public/scss/login.bundle.scss | 1 + frappe/templates/includes/login/login.js | 5 +++ .../website_settings/website_settings.json | 33 +++++++++++-------- .../website_settings/website_settings.py | 3 +- frappe/www/login.py | 1 + 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/frappe/public/scss/login.bundle.scss b/frappe/public/scss/login.bundle.scss index 66834017f4..25c6c5e70f 100644 --- a/frappe/public/scss/login.bundle.scss +++ b/frappe/public/scss/login.bundle.scss @@ -5,6 +5,7 @@ body { background-color: var(--bg-light-gray); } .web-footer { + margin-top: 3rem; display: none; } } diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index 90e12cf3d0..b61d4c6e61 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -2,6 +2,7 @@ // don't remove this line (used in test) window.disable_signup = {{ disable_signup and "true" or "false" }}; +window.show_footer_on_login = {{ show_footer_on_login and "true" or "false" }}; window.login = {}; @@ -305,6 +306,10 @@ frappe.ready(function () { $(window).trigger("hashchange"); } + if (window.show_footer_on_login) { + $("body .web-footer").show(); + } + $(".form-signup, .form-forgot, .form-login-with-email-link").removeClass("hide"); $(document).trigger('login_rendered'); }); diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json index 4707eef8df..fddd929380 100644 --- a/frappe/website/doctype/website_settings/website_settings.json +++ b/frappe/website/doctype/website_settings/website_settings.json @@ -19,6 +19,7 @@ "misc_section", "app_name", "disable_signup", + "show_footer_on_login", "column_break_9", "app_logo", "section_break_6", @@ -49,9 +50,9 @@ "footer", "footer_items", "footer_details_section", - "hide_footer_signup", "copyright", "footer_logo", + "hide_footer_signup", "column_break_37", "address", "footer_powered", @@ -126,7 +127,7 @@ "fieldname": "website_theme_image_link", "fieldtype": "Code", "hidden": 1, - "label": "Website Theme Image Link" + "label": "Website Theme image link" }, { "collapsible": 1, @@ -211,7 +212,7 @@ "default": "0", "fieldname": "hide_footer_signup", "fieldtype": "Check", - "label": "Hide Footer Signup" + "label": "Hide footer signup" }, { "collapsible": 1, @@ -248,10 +249,10 @@ }, { "default": "1", - "description": "Disable Signups on site. New users will have to be manually registered by system managers.", + "description": "New users will have to be manually registered by system managers.", "fieldname": "disable_signup", "fieldtype": "Check", - "label": "Disable Signup" + "label": "Disable signups" }, { "collapsible": 1, @@ -282,20 +283,20 @@ "description": "To use Google Indexing, enable Google Settings.", "fieldname": "enable_google_indexing", "fieldtype": "Check", - "label": "Enable Google Indexing" + "label": "Enable Google indexing" }, { "fieldname": "indexing_refresh_token", "fieldtype": "Data", "hidden": 1, - "label": "Indexing Refresh Token", + "label": "Indexing refresh token", "read_only": 1 }, { "fieldname": "indexing_authorization_code", "fieldtype": "Data", "hidden": 1, - "label": "Indexing Authorization Code", + "label": "Indexing authorization code", "read_only": 1 }, { @@ -308,7 +309,7 @@ "default": "0", "fieldname": "enable_view_tracking", "fieldtype": "Check", - "label": "Enable In App Website Tracking" + "label": "Enable in-app website tracking" }, { "fieldname": "footer_logo", @@ -373,7 +374,7 @@ "default": "1", "fieldname": "google_analytics_anonymize_ip", "fieldtype": "Check", - "label": "Google Analytics Anonymize IP" + "label": "Google Analytics anonymise IP" }, { "default": "0", @@ -408,13 +409,13 @@ "default": "0", "fieldname": "show_account_deletion_link", "fieldtype": "Check", - "label": "Show Account Deletion Link in My Account Page" + "label": "Show account deletion link in My Account page" }, { "default": "72", "fieldname": "auto_account_deletion", "fieldtype": "Int", - "label": "Auto Account Deletion within (Hours)" + "label": "Automatically delete account within (hours)" }, { "fieldname": "footer_powered", @@ -469,6 +470,12 @@ "fieldname": "analytics_section", "fieldtype": "Section Break", "label": "Analytics" + }, + { + "default": "0", + "fieldname": "show_footer_on_login", + "fieldtype": "Check", + "label": "Show footer on login" } ], "icon": "fa fa-cog", @@ -476,7 +483,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2023-12-08 15:52:37.525003", + "modified": "2024-01-08 11:50:34.750809", "modified_by": "Administrator", "module": "Website", "name": "Website Settings", diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index d19ac375a5..70c5b04295 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -56,6 +56,7 @@ class WebsiteSettings(Document): robots_txt: DF.Code | None route_redirects: DF.Table[WebsiteRouteRedirect] show_account_deletion_link: DF.Check + show_footer_on_login: DF.Check show_language_picker: DF.Check splash_image: DF.AttachImage | None subdomain: DF.SmallText | None @@ -63,8 +64,8 @@ class WebsiteSettings(Document): top_bar_items: DF.Table[TopBarItem] website_theme: DF.Link | None website_theme_image_link: DF.Code | None - # end: auto-generated types + def validate(self): self.validate_top_bar_items() self.validate_footer_items() diff --git a/frappe/www/login.py b/frappe/www/login.py index 7cf89e7c3c..ff9c0d2854 100644 --- a/frappe/www/login.py +++ b/frappe/www/login.py @@ -37,6 +37,7 @@ def get_context(context): context["hide_login"] = True # dont show login link on login page again. context["provider_logins"] = [] context["disable_signup"] = cint(frappe.get_website_settings("disable_signup")) + context["show_footer_on_login"] = cint(frappe.get_website_settings("show_footer_on_login")) context["disable_user_pass_login"] = cint(frappe.get_system_settings("disable_user_pass_login")) context["logo"] = frappe.get_website_settings("app_logo") or frappe.get_hooks("app_logo_url")[-1] context["app_name"] = ( From 5275f393c7b619a04b7d56ef6d830072bc7fc314 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 8 Jan 2024 12:07:10 +0530 Subject: [PATCH 21/33] fix(minor): drop icon column, unsupported, fixes #23826 --- .../website/doctype/top_bar_item/top_bar_item.json | 14 ++++---------- .../website/doctype/top_bar_item/top_bar_item.py | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/frappe/website/doctype/top_bar_item/top_bar_item.json b/frappe/website/doctype/top_bar_item/top_bar_item.json index 3d23141c91..899b881b8a 100644 --- a/frappe/website/doctype/top_bar_item/top_bar_item.json +++ b/frappe/website/doctype/top_bar_item/top_bar_item.json @@ -11,8 +11,7 @@ "open_in_new_tab", "right", "column_break_5", - "parent_label", - "icon" + "parent_label" ], "fields": [ { @@ -50,12 +49,6 @@ "fieldname": "column_break_5", "fieldtype": "Column Break" }, - { - "description": "If Icon is set, it will be shown instead of Label", - "fieldname": "icon", - "fieldtype": "Attach Image", - "label": "Icon" - }, { "default": "0", "depends_on": "eval:doc.url", @@ -68,12 +61,13 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-26 20:59:42.142208", + "modified": "2024-01-08 12:05:25.782635", "modified_by": "Administrator", "module": "Website", "name": "Top Bar Item", "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "states": [] } \ No newline at end of file diff --git a/frappe/website/doctype/top_bar_item/top_bar_item.py b/frappe/website/doctype/top_bar_item/top_bar_item.py index 13eaf77113..61401458d8 100644 --- a/frappe/website/doctype/top_bar_item/top_bar_item.py +++ b/frappe/website/doctype/top_bar_item/top_bar_item.py @@ -14,7 +14,6 @@ class TopBarItem(Document): if TYPE_CHECKING: from frappe.types import DF - icon: DF.AttachImage | None label: DF.Data open_in_new_tab: DF.Check parent: DF.Data @@ -24,4 +23,5 @@ class TopBarItem(Document): right: DF.Check url: DF.Data | None # end: auto-generated types + pass From c1d687ddbb85c1ba2ceaa8ad0f23434ed9aa5308 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 8 Jan 2024 12:42:13 +0530 Subject: [PATCH 22/33] fix: ignore user settings on report doc (#24162) --- frappe/public/js/frappe/views/reports/report_view.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 10f3143dac..ad9ce770f7 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -111,10 +111,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { //Setup groupby for reports this.group_by_control = new frappe.ui.GroupBy(this); - if (this.report_doc && this.report_doc.json.group_by) { + if (this.report_doc?.json?.group_by) { this.group_by_control.apply_settings(this.report_doc.json.group_by); - } - if (this.view_user_settings && this.view_user_settings.group_by) { + } else if (this.view_user_settings?.group_by) { this.group_by_control.apply_settings(this.view_user_settings.group_by); } } From f6388f6357b39b6942d00564254f945b97f99704 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 8 Jan 2024 12:50:34 +0530 Subject: [PATCH 23/33] fix: Skip no-value fields for rename (#24163) --- frappe/custom/doctype/custom_field/custom_field.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index e84e0dd712..ce86da5e91 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -362,7 +362,8 @@ def rename_fieldname(custom_field: str, fieldname: str): frappe.msgprint(_("Old and new fieldnames are same."), alert=True) return - frappe.db.rename_column(parent_doctype, old_fieldname, new_fieldname) + if frappe.db.has_column(field.dt, old_fieldname): + frappe.db.rename_column(parent_doctype, old_fieldname, new_fieldname) # Update in DB after alter column is successful, alter column will implicitly commit, so it's # best to commit change on field too to avoid any possible mismatch between two. From eb0e8262a795694e790b217ac58360006d82d7aa Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 8 Jan 2024 12:51:32 +0530 Subject: [PATCH 24/33] fix(minor): Don't load user_info for Administrator twice. fixes #22211 (#24160) --- frappe/boot.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 2ce950a55a..d0e6204e78 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -270,9 +270,6 @@ def get_user_info(): user_info = frappe._dict() add_user_info(frappe.session.user, user_info) - if frappe.session.user == "Administrator" and user_info.Administrator.email: - user_info[user_info.Administrator.email] = user_info.Administrator - return user_info From 2969b6eff13eb79aeb38bc08cbc8fa89fa3dd1ea Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 8 Jan 2024 14:24:16 +0530 Subject: [PATCH 25/33] fix: added precision field in webform field --- .../doctype/web_form_field/web_form_field.json | 11 ++++++++++- .../website/doctype/web_form_field/web_form_field.py | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frappe/website/doctype/web_form_field/web_form_field.json b/frappe/website/doctype/web_form_field/web_form_field.json index 90ec99c3b8..860029cce2 100644 --- a/frappe/website/doctype/web_form_field/web_form_field.json +++ b/frappe/website/doctype/web_form_field/web_form_field.json @@ -17,6 +17,7 @@ "options", "max_length", "max_value", + "precision", "property_depends_on_section", "depends_on", "mandatory_depends_on", @@ -143,11 +144,19 @@ "fieldtype": "Code", "label": "Read Only Depends On", "options": "JS" + }, + { + "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" } ], "istable": 1, "links": [], - "modified": "2022-11-21 17:41:52.139191", + "modified": "2024-01-08 13:21:06.272248", "modified_by": "Administrator", "module": "Website", "name": "Web Form Field", diff --git a/frappe/website/doctype/web_form_field/web_form_field.py b/frappe/website/doctype/web_form_field/web_form_field.py index c6fa4f5dc9..208d31096a 100644 --- a/frappe/website/doctype/web_form_field/web_form_field.py +++ b/frappe/website/doctype/web_form_field/web_form_field.py @@ -56,6 +56,7 @@ class WebFormField(Document): parent: DF.Data parentfield: DF.Data parenttype: DF.Data + precision: DF.Literal["", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] read_only: DF.Check read_only_depends_on: DF.Code | None reqd: DF.Check From 20986060ca1b57831a65f2df2e0d72ecdca6cc68 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 8 Jan 2024 14:32:25 +0530 Subject: [PATCH 26/33] fix(style): set min height correctly --- frappe/public/scss/login.bundle.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/public/scss/login.bundle.scss b/frappe/public/scss/login.bundle.scss index 25c6c5e70f..15cae7debf 100644 --- a/frappe/public/scss/login.bundle.scss +++ b/frappe/public/scss/login.bundle.scss @@ -4,8 +4,10 @@ body { @include media-breakpoint-up(sm) { background-color: var(--bg-light-gray); } + .page-content-wrapper { + min-height: calc(100vh - 220px); + } .web-footer { - margin-top: 3rem; display: none; } } From 50e43df26b2ba757d690b61d9b5a822a366a2b35 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 8 Jan 2024 14:32:56 +0530 Subject: [PATCH 27/33] fix: include Web Form Field in core_doctypes --- frappe/core/doctype/doctype/doctype.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index f8720eb5be..3c3008ecd6 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -234,6 +234,7 @@ class DocType(Document): "DocPerm", "Custom Field", "Customize Form Field", + "Web Form Field", "DocField", ] From e2bc379b239cbb0e87ab79e2ad78b6d608586b9b Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 8 Jan 2024 14:33:31 +0530 Subject: [PATCH 28/33] fix: get precision data while getting fields --- frappe/website/doctype/web_form/web_form.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/website/doctype/web_form/web_form.js b/frappe/website/doctype/web_form/web_form.js index b725c8db6c..dd73f09a0c 100644 --- a/frappe/website/doctype/web_form/web_form.js +++ b/frappe/website/doctype/web_form/web_form.js @@ -107,6 +107,7 @@ frappe.ui.form.on("Web Form", { reqd: df.reqd, default: df.default, read_only: df.read_only, + precision: df.precision, depends_on: df.depends_on, mandatory_depends_on: df.mandatory_depends_on, read_only_depends_on: df.read_only_depends_on, From c1ebfc64b60499739d4f0bba61500314b2b916e7 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 8 Jan 2024 15:08:13 +0530 Subject: [PATCH 29/33] fix: copy content on reply --- frappe/core/doctype/communication/communication.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/core/doctype/communication/communication.js b/frappe/core/doctype/communication/communication.js index a36af705a7..86ef59f994 100644 --- a/frappe/core/doctype/communication/communication.js +++ b/frappe/core/doctype/communication/communication.js @@ -267,6 +267,7 @@ frappe.ui.form.on("Communication", { $.extend(args, { subject: __("Re: {0}", [frm.doc.subject]), recipients: frm.doc.sender, + is_a_reply: true, }); new frappe.views.CommunicationComposer(args); @@ -278,6 +279,7 @@ frappe.ui.form.on("Communication", { subject: __("Res: {0}", [frm.doc.subject]), recipients: frm.doc.sender, cc: frm.doc.cc, + is_a_reply: true, }); new frappe.views.CommunicationComposer(args); }, @@ -287,6 +289,7 @@ frappe.ui.form.on("Communication", { $.extend(args, { forward: true, subject: __("Fw: {0}", [frm.doc.subject]), + is_a_reply: true, }); new frappe.views.CommunicationComposer(args); From 216b5fa9bfd9fd6e3cfb63b7b255f87b3184d6cf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 8 Jan 2024 15:12:59 +0530 Subject: [PATCH 30/33] chore: Skip explicit opt out We dont load posthog js if user opts out, so this starts failing. --- frappe/public/js/telemetry/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/telemetry/index.js b/frappe/public/js/telemetry/index.js index d15101b3d1..548a7c7ce5 100644 --- a/frappe/public/js/telemetry/index.js +++ b/frappe/public/js/telemetry/index.js @@ -39,7 +39,6 @@ class TelemetryManager { disable() { this.enabled = false; - posthog.opt_out_capturing(); } can_enable() { From e948afe938b17e739f56bcf99db59bd1cf0040f4 Mon Sep 17 00:00:00 2001 From: RitvikSardana Date: Mon, 8 Jan 2024 15:31:21 +0530 Subject: [PATCH 31/33] build(deps): bump frappe-datable to v1.17.14 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6c382dab28..83887161f3 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "fast-deep-equal": "^2.0.1", "fast-glob": "^3.2.5", "frappe-charts": "2.0.0-rc22", - "frappe-datatable": "1.17.13", + "frappe-datatable": "1.17.14", "frappe-gantt": "^0.6.0", "highlight.js": "^10.4.1", "html5-qrcode": "^2.3.8", diff --git a/yarn.lock b/yarn.lock index 7c648aa992..fea6ebf608 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1476,10 +1476,10 @@ frappe-charts@2.0.0-rc22: resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-2.0.0-rc22.tgz#9a5a747febdc381a1d4d7af96e89cf519dfba8c0" integrity sha512-N7f/8979wJCKjusOinaUYfMxB80YnfuVLrSkjpj4LtyqS0BGS6SuJxUnb7Jl4RWUFEIs7zEhideIKnyLeFZF4Q== -frappe-datatable@1.17.13: - version "1.17.13" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.17.13.tgz#349a15fd102b8abe55ab903c65de074aa66a26d6" - integrity sha512-rHzLjuAbWdbqEZbU6RCcimyeTswdIKtl8WJLjctj5zze7w5DFSQX0iDTbKNBG7TehS7YqP4k+ySIhvkHRELq3w== +frappe-datatable@1.17.14: + version "1.17.14" + resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-1.17.14.tgz#5fe99fa45089d6f2a54d13215608ef777bc947ec" + integrity sha512-rrUyk+8ueX9ADDXwaHobBGmAWK86lF3P3yc3KYGHyhNiNTwKpUW08zQeuTUzJnWv0OSZ/zXYePzrjFKG7ZR4Wg== dependencies: hyperlist "^1.0.0-beta" lodash "^4.17.5" From 4e188ec531ce3764f128da3bc406b60141e06a45 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 8 Jan 2024 15:37:04 +0530 Subject: [PATCH 32/33] fix: webhook error logging fails (#24172) These variables can be unset if webhook fails while creating them. --- frappe/integrations/doctype/webhook/webhook.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index 3ecfe6cd61..64d38a2ae7 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -153,15 +153,14 @@ def get_context(doc): def enqueue_webhook(doc, webhook) -> None: + request_url = headers = data = None try: webhook: Webhook = frappe.get_doc("Webhook", webhook.get("name")) - headers = get_webhook_headers(doc, webhook) - data = get_webhook_data(doc, webhook) - + request_url = webhook.request_url if webhook.is_dynamic_url: request_url = frappe.render_template(webhook.request_url, get_context(doc)) - else: - request_url = webhook.request_url + headers = get_webhook_headers(doc, webhook) + data = get_webhook_data(doc, webhook) except Exception as e: frappe.logger().debug({"enqueue_webhook_error": e}) From b58f6a7420bc8cd56c142cf391f76c23acfb2fa1 Mon Sep 17 00:00:00 2001 From: V Shankar Date: Mon, 8 Jan 2024 15:59:34 +0530 Subject: [PATCH 33/33] fix: Make Autocomplete option cache like link field (#23888) * fix: execute query even though input cache exists * fix: remove invalid autocomplete caching This cache can get stale when document value changes. --------- Co-authored-by: Shankar Co-authored-by: Ankush Menat --- .../js/frappe/form/controls/autocomplete.js | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/autocomplete.js b/frappe/public/js/frappe/form/controls/autocomplete.js index 825cfb5320..b7bf5687d6 100644 --- a/frappe/public/js/frappe/form/controls/autocomplete.js +++ b/frappe/public/js/frappe/form/controls/autocomplete.js @@ -85,33 +85,15 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui }; } - init_option_cache() { - if (!this.$input.cache) { - this.$input.cache = {}; - } - if (!this.$input.cache[this.doctype]) { - this.$input.cache[this.doctype] = {}; - } - if (!this.$input.cache[this.doctype][this.df.fieldname]) { - this.$input.cache[this.doctype][this.df.fieldname] = {}; - } - } - setup_awesomplete() { this.awesomplete = new Awesomplete(this.input, this.get_awesomplete_settings()); $(this.input_area).find(".awesomplete ul").css("min-width", "100%"); - this.init_option_cache(); - this.$input.on( "input", frappe.utils.debounce((e) => { - const cached_options = - this.$input.cache[this.doctype][this.df.fieldname][e.target.value]; - if (cached_options && cached_options.length) { - this.set_data(cached_options); - } else if (this.get_query || this.df.get_query) { + if (this.get_query || this.df.get_query) { this.execute_query_if_exists(e.target.value); } else { this.awesomplete.list = this.get_data(); @@ -245,7 +227,6 @@ frappe.ui.form.ControlAutocomplete = class ControlAutoComplete extends frappe.ui if (!this.$input.is(":focus")) { return; } - this.$input.cache[this.doctype][this.df.fieldname][term] = message; this.set_data(message); }, });