From 96c987870ec60a1f9455255e873f0d1cad03748f Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Fri, 30 Jun 2023 14:27:10 +0530 Subject: [PATCH 01/81] fix: support timespan filters for custom dashboard chart source --- frappe/desk/doctype/dashboard_chart/dashboard_chart.js | 1 - frappe/public/js/frappe/widgets/chart_widget.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 6d23be79d7..5ed71e91a8 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -48,7 +48,6 @@ frappe.ui.form.on("Dashboard Chart", { frm.set_df_property("dynamic_filters_section", "hidden", 1); frm.trigger("set_parent_document_type"); - frm.trigger("set_time_series"); frm.set_query("document_type", function () { return { filters: { diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 18f7459a6c..ed40789173 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -103,7 +103,7 @@ export default class ChartWidget extends Widget { this.action_area.empty(); this.prepare_chart_actions(); - if (this.chart_doc.timeseries && this.chart_doc.chart_type !== "Custom") { + if (this.chart_doc.timeseries) { this.render_time_series_filters(); } } From 8b7ed91c39aeef833b34d22a611bef5ab4a91e1d Mon Sep 17 00:00:00 2001 From: Ritvik Sardana Date: Fri, 28 Jul 2023 14:52:45 +0530 Subject: [PATCH 02/81] fix: Made Set Password Page Better --- frappe/www/update-password.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/www/update-password.html b/frappe/www/update-password.html index 7351311def..707bc21e09 100644 --- a/frappe/www/update-password.html +++ b/frappe/www/update-password.html @@ -84,6 +84,7 @@ frappe.ready(function() { if (args.new_password !== confirm_password) { $('.password-mismatch-message').text("{{ _('Passwords do not match') }}") .removeClass('hidden text-muted').addClass('text-danger'); + $('.password-strength-message').addClass('hidden'); return false; } @@ -198,6 +199,7 @@ frappe.ready(function() { message.push("{{ _('Success! You are good to go 👍') }}"); } } + $('.password-mismatch-message').text("").addClass('hidden'); strength_message.html(message.join(' ') || '').removeClass('hidden'); } From d0c1152d0e2bb102521bffb24fab540f1bcdfd42 Mon Sep 17 00:00:00 2001 From: Ritvik Sardana Date: Fri, 28 Jul 2023 15:02:40 +0530 Subject: [PATCH 03/81] refactor: refactored the code --- frappe/www/update-password.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/www/update-password.html b/frappe/www/update-password.html index 707bc21e09..b78913cc97 100644 --- a/frappe/www/update-password.html +++ b/frappe/www/update-password.html @@ -199,7 +199,8 @@ frappe.ready(function() { message.push("{{ _('Success! You are good to go 👍') }}"); } } - $('.password-mismatch-message').text("").addClass('hidden'); + $('.password-mismatch-message').addClass('hidden'); + strength_message.html(message.join(' ') || '').removeClass('hidden'); } From 213d744661651ec8bb12066c56754c9b6858ce13 Mon Sep 17 00:00:00 2001 From: Ritvik Sardana Date: Sun, 30 Jul 2023 18:25:10 +0530 Subject: [PATCH 04/81] fix: better UX --- frappe/www/update-password.html | 47 ++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/frappe/www/update-password.html b/frappe/www/update-password.html index b78913cc97..5a50d8d71d 100644 --- a/frappe/www/update-password.html +++ b/frappe/www/update-password.html @@ -26,7 +26,7 @@ - {%- if not disable_signup -%} @@ -43,6 +43,7 @@ Comment" - frappe.form_dict.comment_by = "hacker" - - add_comment() - + add_comment_args.update(comment="Comment", comment_by="hacker") + add_comment(**add_comment_args) self.assertEqual( frappe.get_all( "Comment", @@ -106,27 +93,30 @@ class TestComment(FrappeTestCase): def test_guest_cannot_comment(self): test_blog = make_test_blog() with set_user("Guest"): - frappe.form_dict.comment = "Good comment with 10 chars" - frappe.form_dict.comment_email = "mail@example.org" - frappe.form_dict.comment_by = "Good Tester" - frappe.form_dict.reference_doctype = "Blog Post" - frappe.form_dict.reference_name = test_blog.name - frappe.form_dict.route = test_blog.route - frappe.local.request_ip = "127.0.0.1" - - self.assertEqual(add_comment(), None) + self.assertEqual( + add_comment( + comment="Good comment with 10 chars", + comment_email="mail@example.org", + comment_by="Good Tester", + reference_doctype="Blog Post", + reference_name=test_blog.name, + route=test_blog.route, + ), + None, + ) def test_user_not_logged_in(self): - some_system_user = frappe.db.get_value("User", {}) + some_system_user = frappe.db.get_value("User", {"name": ("not in", frappe.STANDARD_USERS)}) test_blog = make_test_blog() with set_user("Guest"): - frappe.form_dict.comment = "Good comment with 10 chars" - frappe.form_dict.comment_email = some_system_user - frappe.form_dict.comment_by = "Good Tester" - frappe.form_dict.reference_doctype = "Blog Post" - frappe.form_dict.reference_name = test_blog.name - frappe.form_dict.route = test_blog.route - frappe.local.request_ip = "127.0.0.1" - - self.assertRaises(frappe.ValidationError, add_comment) + self.assertRaises( + frappe.ValidationError, + add_comment, + comment="Good comment with 10 chars", + comment_email=some_system_user, + comment_by="Good Tester", + reference_doctype="Blog Post", + reference_name=test_blog.name, + route=test_blog.route, + ) diff --git a/frappe/tests/utils.py b/frappe/tests/utils.py index c6c53e6bf5..a98801c503 100644 --- a/frappe/tests/utils.py +++ b/frappe/tests/utils.py @@ -149,6 +149,9 @@ def _restore_thread_locals(flags): frappe.local.lang = "en" frappe.local.preload_assets = {"style": [], "script": []} + if hasattr(frappe.local, "request"): + delattr(frappe.local, "request") + @contextmanager def change_settings(doctype, settings_dict): diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py index 3ea447d90c..f0a5fb0295 100644 --- a/frappe/website/doctype/blog_post/test_blog_post.py +++ b/frappe/website/doctype/blog_post/test_blog_post.py @@ -20,6 +20,10 @@ class TestBlogPost(FrappeTestCase): def setUp(self): reset_customization("Blog Post") + def tearDown(self): + if hasattr(frappe.local, "request"): + delattr(frappe.local, "request") + def test_generator_view(self): pages = frappe.get_all( "Blog Post", fields=["name", "route"], filters={"published": 1, "route": ("!=", "")}, limit=1 @@ -159,17 +163,10 @@ class TestBlogPost(FrappeTestCase): from frappe.templates.includes.likes.likes import like - frappe.form_dict.reference_doctype = "Blog Post" - frappe.form_dict.reference_name = test_blog.name - frappe.form_dict.like = True - frappe.local.request_ip = "127.0.0.1" - - liked = like() + liked = like("Blog Post", test_blog.name, True) self.assertEqual(liked, True) - frappe.form_dict.like = False - - disliked = like() + disliked = like("Blog Post", test_blog.name, False) self.assertEqual(disliked, False) frappe.db.delete("Comment", {"comment_type": "Like", "reference_doctype": "Blog Post"}) diff --git a/frappe/website/doctype/web_form/test_web_form.py b/frappe/website/doctype/web_form/test_web_form.py index f86aa21735..2da24bc776 100644 --- a/frappe/website/doctype/web_form/test_web_form.py +++ b/frappe/website/doctype/web_form/test_web_form.py @@ -14,15 +14,9 @@ test_dependencies = ["Web Form"] class TestWebForm(FrappeTestCase): def setUp(self): frappe.conf.disable_website_cache = True - frappe.local.path = None def tearDown(self): frappe.conf.disable_website_cache = False - frappe.local.path = None - frappe.local.request_ip = None - frappe.form_dict.web_form = None - frappe.form_dict.data = None - frappe.form_dict.docname = None def test_accept(self): frappe.set_user("Administrator") @@ -34,10 +28,6 @@ class TestWebForm(FrappeTestCase): "starts_on": "2014-09-09", } - frappe.form_dict.web_form = "manage-events" - frappe.form_dict.data = json.dumps(doc) - frappe.local.request_ip = "127.0.0.1" - accept(web_form="manage-events", data=json.dumps(doc)) self.event_name = frappe.db.get_value("Event", {"subject": "_Test Event Web Form"}) @@ -58,11 +48,7 @@ class TestWebForm(FrappeTestCase): frappe.db.get_value("Event", self.event_name, "description"), doc.get("description") ) - frappe.form_dict.web_form = "manage-events" - frappe.form_dict.docname = self.event_name - frappe.form_dict.data = json.dumps(doc) - - accept(web_form="manage-events", docname=self.event_name, data=json.dumps(doc)) + accept("manage-events", json.dumps(doc)) self.assertEqual( frappe.db.get_value("Event", self.event_name, "description"), doc.get("description") From 7aed89ae4d41252964f0febc53f0e3eb1185e305 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 5 Aug 2023 14:43:45 +0530 Subject: [PATCH 55/81] test: set request before verifying --- frappe/email/queue.py | 5 ----- frappe/tests/test_email.py | 8 +++++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/frappe/email/queue.py b/frappe/email/queue.py index cae5f76b3d..b481fd21cd 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -88,11 +88,6 @@ def get_unsubcribed_url( if unsubscribe_params: params.update(unsubscribe_params) - query_string = get_signed_params(params) - - # for test - frappe.local.flags.signed_query_string = query_string - return get_url(unsubscribe_method + "?" + get_signed_params(params)) diff --git a/frappe/tests/test_email.py b/frappe/tests/test_email.py index 4cde7f9ace..6c6b0a86cf 100644 --- a/frappe/tests/test_email.py +++ b/frappe/tests/test_email.py @@ -156,7 +156,7 @@ class TestEmail(FrappeTestCase): frappe.conf.use_ssl = False def test_expose(self): - + from frappe.utils import set_request from frappe.utils.verified_command import verify_request frappe.sendmail( @@ -199,9 +199,11 @@ class TestEmail(FrappeTestCase): if content: eol = "\r\n" - frappe.local.flags.signed_query_string = re.search( + query_string = re.search( r"(?<=/api/method/frappe.email.queue.unsubscribe\?).*(?=" + eol + ")", content.decode() ).group(0) + + set_request(method="GET", query_string=query_string) self.assertTrue(verify_request()) break @@ -320,6 +322,6 @@ class TestVerifiedRequests(FrappeTestCase): for params in test_cases: signed_url = get_signed_params(params) - set_request(method="GET", path="?" + signed_url) + set_request(method="GET", query_string=signed_url) self.assertTrue(verify_request()) frappe.local.request = None From fd2efdb0e1a5a4c9338f8bb21b3ee8d5986b3822 Mon Sep 17 00:00:00 2001 From: Michelle Alva <50285544+michellealva@users.noreply.github.com> Date: Sat, 5 Aug 2023 20:15:55 +0530 Subject: [PATCH 56/81] chore: whitelisted typo (#21930) --- frappe/utils/safe_exec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index c0bfd3cbbb..be105b329d 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -247,7 +247,7 @@ def safe_enqueue(function, **kwargs): Accepts frappe.enqueue params like job_name, queue, timeout, etc. in addition to params to be passed to function - :param function: whitelised function or API Method set in Server Script + :param function: whitelisted function or API Method set in Server Script """ return enqueue("frappe.utils.safe_exec.call_whitelisted_function", function=function, **kwargs) From 2b96324c311d140b0a5285e1cab98f2d1106f7cd Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 5 Aug 2023 17:02:55 +0000 Subject: [PATCH 57/81] fix: rate limit for all HTTP methods (#21929) --- frappe/core/doctype/user/user.py | 2 +- frappe/website/doctype/web_form/web_form.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index b1f8777777..3a4fa12e91 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1010,7 +1010,7 @@ def sign_up(email: str, full_name: str, redirect_to: str) -> tuple[int, str]: @frappe.whitelist(allow_guest=True) -@rate_limit(limit=get_password_reset_limit, seconds=24 * 60 * 60, methods=["POST"]) +@rate_limit(limit=get_password_reset_limit, seconds=24 * 60 * 60) def reset_password(user: str) -> str: if user == "Administrator": return "not allowed" diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 619692cc1d..0a2be0c1b8 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -429,7 +429,7 @@ def get_web_form_module(doc): @frappe.whitelist(allow_guest=True) -@rate_limit(key="web_form", limit=5, seconds=60, methods=["POST"]) +@rate_limit(key="web_form", limit=5, seconds=60) def accept(web_form, data): """Save the web form""" data = frappe._dict(json.loads(data)) From 4fae798ad1798296531bb3d06c2bbfb5f6aa8096 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sun, 6 Aug 2023 13:51:22 +0200 Subject: [PATCH 58/81] fix: check file permission before zipping (#21934) --- frappe/core/doctype/file/file.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index cc3c2c228e..d334eaad1e 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -734,6 +734,8 @@ class File(Document): continue if _file.is_folder: continue + if not has_permission(_file, "read"): + continue zf.writestr(_file.file_name, _file.get_content()) zf.close() return zip_file.getvalue() From 5fce1a57c0a55e68bebe67e6f766d11b9d25a111 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sun, 6 Aug 2023 14:03:34 +0200 Subject: [PATCH 59/81] fix: validate fieldname in get_group_by_count (#21932) * fix: validate fieldname in get_group_by_count * test: call get_group_by_count with invalid field * test: is_default_field --- frappe/desk/listview.py | 4 ++++ frappe/model/__init__.py | 4 ++++ frappe/tests/test_listview.py | 9 +++++++++ frappe/tests/test_model_utils.py | 12 +++++++++++- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/frappe/desk/listview.py b/frappe/desk/listview.py index a1db82810e..cc32e4ab06 100644 --- a/frappe/desk/listview.py +++ b/frappe/desk/listview.py @@ -2,6 +2,7 @@ # License: MIT. See LICENSE import frappe +from frappe.model import is_default_field from frappe.query_builder import Order from frappe.query_builder.functions import Count from frappe.query_builder.terms import SubQuery @@ -59,6 +60,9 @@ def get_group_by_count(doctype: str, current_filters: str, field: str) -> list[d .run(as_dict=True) ) + if not frappe.get_meta(doctype).has_field(field) and not is_default_field(field): + raise ValueError("Field does not belong to doctype") + return frappe.get_list( doctype, filters=current_filters, diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index ff6ad36c42..32e46c83c2 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -225,3 +225,7 @@ def get_permitted_fields( return meta_fields + permitted_fields + optional_meta_fields return [] + + +def is_default_field(fieldname: str) -> bool: + return fieldname in default_fields diff --git a/frappe/tests/test_listview.py b/frappe/tests/test_listview.py index df2299875e..f5d0b857ba 100644 --- a/frappe/tests/test_listview.py +++ b/frappe/tests/test_listview.py @@ -71,6 +71,15 @@ class TestListView(FrappeTestCase): } self.assertEqual(data["Administrator"], 1) + def test_get_group_by_invalid_field(self): + self.assertRaises( + ValueError, + get_group_by_count, + "Note", + '[["Note Seen By","user","=","Administrator"]]', + "invalid_field", + ) + def test_list_view_comment_count(self): frappe.form_dict.doctype = "DocType" frappe.form_dict.limit = "1" diff --git a/frappe/tests/test_model_utils.py b/frappe/tests/test_model_utils.py index 25523012e9..61828ef500 100644 --- a/frappe/tests/test_model_utils.py +++ b/frappe/tests/test_model_utils.py @@ -2,7 +2,7 @@ from contextlib import contextmanager from random import choice import frappe -from frappe.model import core_doctypes_list, get_permitted_fields +from frappe.model import core_doctypes_list, get_permitted_fields, is_default_field from frappe.model.utils import get_fetch_values from frappe.tests.utils import FrappeTestCase @@ -66,6 +66,16 @@ class TestModelUtils(FrappeTestCase): get_permitted_fields("Installed Application", parenttype="Installed Applications"), [] ) + def test_is_default_field(self): + self.assertTrue(is_default_field("doctype")) + self.assertTrue(is_default_field("name")) + self.assertTrue(is_default_field("owner")) + + self.assertFalse(is_default_field({})) + self.assertFalse(is_default_field("qwerty1234")) + self.assertFalse(is_default_field(True)) + self.assertFalse(is_default_field(42)) + @contextmanager def set_user(user: str): From a2b2998684ae4e941210b51469e9f795ebae7e2f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 6 Aug 2023 18:55:16 +0530 Subject: [PATCH 60/81] fix(DX): Wrap print format errors (#21944) [skip ci] --- frappe/exceptions.py | 4 ++++ frappe/utils/error.py | 2 +- frappe/utils/pdf.py | 26 +++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 8dbd778a7d..f4bcb661f1 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -252,6 +252,10 @@ class SessionBootFailed(ValidationError): http_status_code = 500 +class PrintFormatError(ValidationError): + pass + + class TooManyWritesError(Exception): pass diff --git a/frappe/utils/error.py b/frappe/utils/error.py index 47902176ea..a3d39bbe7d 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -157,5 +157,5 @@ def guess_exception_source(exception: str) -> str | None: app_name = matches.group("app_name") apps[app_name] += app_priority.get(app_name, 0) - if probably_source := apps.most_common(1): + if (probably_source := apps.most_common(1)) and probably_source[0][0] != "frappe": return f"{probably_source[0][0]} (app)" diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 721b061257..6751364edc 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -1,5 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE +import contextlib import io import os import re @@ -43,7 +44,30 @@ def pdf_header_html(soup, head, content, styles, html_id, css): def pdf_body_html(template, args, **kwargs): - return template.render(args, filters={"len": len}) + try: + return template.render(args, filters={"len": len}) + except Exception as e: + # Guess line number ? + frappe.throw( + _("Error in print format on line {0}: {1}").format( + _guess_template_error_line_number(template), e + ), + exc=frappe.PrintFormatError, + title=_("Print Format Error"), + ) + + +def _guess_template_error_line_number(template) -> int | None: + """Guess line on which exception occured from current traceback.""" + with contextlib.suppress(Exception): + import sys + import traceback + + _, _, tb = sys.exc_info() + + for frame in reversed(traceback.extract_tb(tb)): + if template.filename in frame.filename: + return frame.lineno def pdf_footer_html(soup, head, content, styles, html_id, css): From 27426f7ceb13c59779bb225438a0ce60fc296c10 Mon Sep 17 00:00:00 2001 From: Ritvik Sardana Date: Mon, 7 Aug 2023 00:34:08 +0530 Subject: [PATCH 61/81] chore: code cleanup --- frappe/www/update-password.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frappe/www/update-password.html b/frappe/www/update-password.html index 2510aa4bd4..e42a7804b3 100644 --- a/frappe/www/update-password.html +++ b/frappe/www/update-password.html @@ -23,9 +23,9 @@ - - + + @@ -46,7 +46,7 @@ frappe.ready(function() { // URL args const key = frappe.utils.get_url_arg('key'); const pasword_expired = frappe.utils.get_url_arg('password_expired'); - // inputs and button elements + // inputs, paragraphs and button elements const old_password = $('#old_password'); const new_password = $('#new_password'); const confirm_password = $('#confirm_password'); @@ -54,7 +54,7 @@ frappe.ready(function() { const password_strength_indicator = $('.password-strength-indicator'); const password_strength_message =$('.password-strength-message'); const password_mismatch_message = $('.password-mismatch-message'); - // Span text + // Info text const password_not_same_as_old_password = "{{ _('New password cannot be same as old password') }}"; const password_mismatch = "{{ _('Passwords do not match') }}"; const password_strength_message_success = "{{ _('Success! You are good to go 👍') }}"; @@ -80,6 +80,7 @@ frappe.ready(function() { key: key || "", old_password: old_password.val(), new_password: new_password.val(), + confirm_password: confirm_password.val(), logout_all_sessions: 1 } if (!args.old_password && !args.key) { @@ -105,12 +106,11 @@ frappe.ready(function() { return; } - if (args.new_password !== confirm_password.val()) { + if (args.new_password !== args.confirm_password) { password_mismatch_message.text(password_mismatch) .removeClass('hidden text-muted').addClass('text-danger'); password_strength_message.addClass('hidden'); - return false; - + return; } frappe.call({ From dbc61cc1373907c15f1fd0b693b8ee3b08f6e681 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 7 Aug 2023 10:17:04 +0530 Subject: [PATCH 62/81] fix: workflow help closes https://github.com/frappe/frappe/issues/21923 --- frappe/public/js/frappe/form/workflow.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/workflow.js b/frappe/public/js/frappe/form/workflow.js index e2a7d2091c..e3532fddc8 100644 --- a/frappe/public/js/frappe/form/workflow.js +++ b/frappe/public/js/frappe/form/workflow.js @@ -36,8 +36,9 @@ frappe.ui.form.States = class FormStates { ).join(", ") || __("None: End of Workflow").bold(); const document_editable_by = frappe.workflow - .get_document_state(me.frm.doctype, state) - .allow_edit.bold(); + .get_document_state_roles(me.frm.doctype, state) + .map((role) => role.bold()) + .join(", "); $(d.body) .html( From 3f971f37c178aff2ad0186d1022092733bcad25b Mon Sep 17 00:00:00 2001 From: RitvikSardana <65544983+RitvikSardana@users.noreply.github.com> Date: Mon, 7 Aug 2023 11:33:41 +0530 Subject: [PATCH 63/81] feat: Added Re-run in Console Button in Console Log Doctype (#21825) * feat: Added Reload in Console Button in Console Log Doctype * refactor: simpler doc mapping * fix: added type of script * fix: renamed button name * fix: new solution for Re-run in console * refactor: use set_route [skip ci] --------- Co-authored-by: Ritvik Sardana Co-authored-by: Ankush Menat --- frappe/desk/doctype/console_log/console_log.js | 9 +++++++-- frappe/desk/doctype/console_log/console_log.json | 12 ++++++++++-- frappe/desk/doctype/console_log/console_log.py | 1 + frappe/desk/doctype/system_console/system_console.js | 10 +++++++++- frappe/desk/doctype/system_console/system_console.py | 3 +-- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/frappe/desk/doctype/console_log/console_log.js b/frappe/desk/doctype/console_log/console_log.js index 9a980667ac..822fbb466e 100644 --- a/frappe/desk/doctype/console_log/console_log.js +++ b/frappe/desk/doctype/console_log/console_log.js @@ -2,6 +2,11 @@ // For license information, please see license.txt frappe.ui.form.on("Console Log", { - // refresh: function(frm) { - // } + refresh: function (frm) { + frm.add_custom_button(__("Re-Run in Console"), () => { + localStorage.setItem("system_console_code", frm.doc.script); + localStorage.setItem("system_console_type", frm.doc.type); + frappe.set_route("Form", "System Console"); + }); + }, }); diff --git a/frappe/desk/doctype/console_log/console_log.json b/frappe/desk/doctype/console_log/console_log.json index b8ccf8c9b5..7531d97991 100644 --- a/frappe/desk/doctype/console_log/console_log.json +++ b/frappe/desk/doctype/console_log/console_log.json @@ -6,7 +6,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "script" + "script", + "type" ], "fields": [ { @@ -15,11 +16,18 @@ "in_list_view": 1, "label": "Script", "read_only": 1 + }, + { + "fieldname": "type", + "fieldtype": "Data", + "hidden": 1, + "label": "Type", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-07-05 22:16:02.823955", + "modified": "2023-07-27 22:52:37.239039", "modified_by": "Administrator", "module": "Desk", "name": "Console Log", diff --git a/frappe/desk/doctype/console_log/console_log.py b/frappe/desk/doctype/console_log/console_log.py index 9e243ee19a..bed829c5b8 100644 --- a/frappe/desk/doctype/console_log/console_log.py +++ b/frappe/desk/doctype/console_log/console_log.py @@ -15,5 +15,6 @@ class ConsoleLog(Document): from frappe.types import DF script: DF.Code | None + type: DF.Data | None # end: auto-generated types pass diff --git a/frappe/desk/doctype/system_console/system_console.js b/frappe/desk/doctype/system_console/system_console.js index dc73f33b67..bd993824ce 100644 --- a/frappe/desk/doctype/system_console/system_console.js +++ b/frappe/desk/doctype/system_console/system_console.js @@ -10,7 +10,15 @@ frappe.ui.form.on("System Console", { description: __("Execute Console script"), ignore_inputs: true, }); - frm.set_value("type", "Python"); + if ( + localStorage.getItem("system_console_code") && + localStorage.getItem("system_console_type") + ) { + frm.set_value("type", localStorage.getItem("system_console_type")); + frm.set_value("console", localStorage.getItem("system_console_code")); + localStorage.removeItem("system_console_code"); + localStorage.removeItem("system_console_type"); + } }, refresh: function (frm) { diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py index 540936581a..14576d3860 100644 --- a/frappe/desk/doctype/system_console/system_console.py +++ b/frappe/desk/doctype/system_console/system_console.py @@ -40,8 +40,7 @@ class SystemConsole(Document): frappe.db.commit() else: frappe.db.rollback() - - frappe.get_doc(dict(doctype="Console Log", script=self.console)).insert() + frappe.get_doc(dict(doctype="Console Log", script=self.console, type=self.type)).insert() frappe.db.commit() From e877d926ebd564e15b4abd8438500dbe917eb34e Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Aug 2023 06:07:23 +0000 Subject: [PATCH 64/81] test: set `request_ip` when testing `reset_password` (#21937) * test: set `request_ip` when testing `reset_password` * test: increase rate limit temporarily while testing `reset_password` --- frappe/core/doctype/user/test_user.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index b4d69d23d5..9be996e8c8 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -367,6 +367,9 @@ class TestUser(FrappeTestCase): set_request(path="/random") frappe.local.cookie_manager = CookieManager() frappe.local.login_manager = LoginManager() + # used by rate limiter when calling reset_password + frappe.local.request_ip = "127.0.0.1" + frappe.db.set_single_value("System Settings", "password_reset_limit", 6) frappe.set_user("testpassword@example.com") test_user = frappe.get_doc("User", "testpassword@example.com") From b385fae2dc9caff4acb27cc366e58cf01342a3de Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 7 Aug 2023 13:49:52 +0530 Subject: [PATCH 65/81] test: use unique IP to prevent future conflict with other tests --- frappe/core/doctype/user/test_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index 9be996e8c8..1ca0a56ec0 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -368,7 +368,7 @@ class TestUser(FrappeTestCase): frappe.local.cookie_manager = CookieManager() frappe.local.login_manager = LoginManager() # used by rate limiter when calling reset_password - frappe.local.request_ip = "127.0.0.1" + frappe.local.request_ip = "127.0.0.69" frappe.db.set_single_value("System Settings", "password_reset_limit", 6) frappe.set_user("testpassword@example.com") From f6326b6145be8ed1ea2f4b4cbe80fad4b0b82205 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 7 Aug 2023 14:00:34 +0530 Subject: [PATCH 66/81] fix: check before deleting prepared report (#21950) --- frappe/core/doctype/prepared_report/prepared_report.py | 6 +++--- frappe/model/document.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index ef8ccce9c1..067cd728b2 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -211,9 +211,9 @@ def expire_stalled_report(): def delete_prepared_reports(reports): reports = frappe.parse_json(reports) for report in reports: - frappe.delete_doc( - "Prepared Report", report["name"], ignore_permissions=True, delete_permanently=True - ) + prepared_report = frappe.get_doc("Prepared Report", report["name"]) + if prepared_report.has_permission(): + prepared_report.delete(ignore_permissions=True, delete_permanently=True) def create_json_gz_file(data, dt, dn): diff --git a/frappe/model/document.py b/frappe/model/document.py index 9768200164..cedbe9ad71 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -1039,7 +1039,7 @@ class Document(BaseDocument): """Rename the document to `name`. This transforms the current object.""" return self._rename(name=name, merge=merge, force=force, validate_rename=validate_rename) - def delete(self, ignore_permissions=False, force=False): + def delete(self, ignore_permissions=False, force=False, *, delete_permanently=False): """Delete document.""" return frappe.delete_doc( self.doctype, @@ -1047,6 +1047,7 @@ class Document(BaseDocument): ignore_permissions=ignore_permissions, flags=self.flags, force=force, + delete_permanently=delete_permanently, ) def run_before_save_methods(self): From 49a9f53d24ee125b28ff75c3cd11acd7a8f4cfe2 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Mon, 7 Aug 2023 20:23:53 +0530 Subject: [PATCH 67/81] feat: text editor and mentions for discussions (#21886) --- cypress/integration/discussions.js | 40 ++--- frappe/public/js/frappe-web.bundle.js | 1 + .../js/frappe/form/controls/text_editor.js | 8 +- frappe/templates/discussions/button.html | 2 +- frappe/templates/discussions/comment_box.html | 7 +- frappe/templates/discussions/discussions.js | 150 ++++++++++++------ .../discussions/discussions_section.html | 7 +- frappe/templates/discussions/reply_card.html | 27 +++- .../templates/discussions/reply_section.html | 14 +- frappe/templates/discussions/sidebar.html | 2 +- frappe/templates/styles/discussion_style.css | 98 +++++++----- .../discussion_reply/discussion_reply.json | 5 +- .../discussion_reply/discussion_reply.py | 2 +- 13 files changed, 219 insertions(+), 144 deletions(-) diff --git a/cypress/integration/discussions.js b/cypress/integration/discussions.js index 55bcabce19..9caeddeb1f 100644 --- a/cypress/integration/discussions.js +++ b/cypress/integration/discussions.js @@ -24,9 +24,9 @@ context("Discussions", () => { .should("have.value", "Discussion from tests"); // Enter comment - cy.get(".modal .comment-field") - .type("This is a discussion from the cypress ui tests.") - .should("have.value", "This is a discussion from the cypress ui tests."); + cy.get(".modal .discussions-comment").type( + "This is a discussion from the cypress ui tests." + ); // Submit cy.get(".modal .submit-discussion").click(); @@ -38,21 +38,16 @@ context("Discussions", () => { "Discussion from tests" ); cy.get(".discussion-on-page:visible").should("have.class", "show"); - cy.get(".discussion-on-page:visible .reply-card .reply-text").should( + cy.get(".discussion-on-page:visible .reply-card .reply-text .ql-editor p").should( "have.text", - "This is a discussion from the cypress ui tests.\n" + "This is a discussion from the cypress ui tests." ); }; const reply_through_comment_box = () => { - cy.get(".discussion-form:visible .comment-field") - .type( - "This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page." - ) - .should( - "have.value", - "This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page." - ); + cy.get(".discussion-form:visible .discussions-comment").type( + "This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page." + ); cy.get(".discussion-form:visible .submit-discussion").click(); cy.wait(3000); @@ -63,28 +58,18 @@ context("Discussions", () => { .find(".reply-text") .should( "have.text", - "This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.\n" + "This is a discussion from the cypress ui tests. This comment was entered through the commentbox on the page.\n" ); }; - const cancel_and_clear_comment_box = () => { - cy.get(".discussion-form:visible .comment-field") - .type("This is a discussion from the cypress ui tests.") - .should("have.value", "This is a discussion from the cypress ui tests."); - - cy.get(".discussion-form:visible .cancel-comment").click(); - cy.get(".discussion-form:visible .comment-field").should("have.value", ""); - }; - const single_thread_discussion = () => { cy.visit("/test-single-thread"); cy.get(".discussions-sidebar").should("have.length", 0); cy.get(".reply").should("have.length", 0); - cy.get(".discussion-form:visible .comment-field") - .type("This comment is being made on a single thread discussion.") - .should("have.value", "This comment is being made on a single thread discussion."); - + cy.get(".discussion-form:visible .discussions-comment").type( + "This comment is being made on a single thread discussion." + ); cy.get(".discussion-form:visible .submit-discussion").click(); cy.wait(3000); cy.get(".discussion-on-page") @@ -96,6 +81,5 @@ context("Discussions", () => { it("reply through modal", reply_through_modal); it("reply through comment box", reply_through_comment_box); - it("cancel and clear comment box", cancel_and_clear_comment_box); it("single thread discussion", single_thread_discussion); }); diff --git a/frappe/public/js/frappe-web.bundle.js b/frappe/public/js/frappe-web.bundle.js index 36064767fb..30cf552c82 100644 --- a/frappe/public/js/frappe-web.bundle.js +++ b/frappe/public/js/frappe-web.bundle.js @@ -24,3 +24,4 @@ import "./bootstrap-4-web.bundle"; import "../../website/js/website.js"; import "./frappe/socketio_client.js"; +import "./frappe/form/controls/control.js"; diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js index fd0e878567..15e11cd9e4 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -198,14 +198,18 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for get_quill_options() { return { modules: { - toolbar: this.get_toolbar_options(), + toolbar: Object.keys(this.df).includes("get_toolbar_options") + ? this.df.get_toolbar_options() + : this.get_toolbar_options(), table: true, imageResize: {}, magicUrl: true, mention: this.get_mention_options(), }, - theme: "snow", + theme: this.df.theme || "snow", readOnly: this.disabled, + bounds: this.quill_container[0], + placeholder: this.df.placeholder || "", }; } diff --git a/frappe/templates/discussions/button.html b/frappe/templates/discussions/button.html index 746227aa0b..dfc206b0b9 100644 --- a/frappe/templates/discussions/button.html +++ b/frappe/templates/discussions/button.html @@ -1,6 +1,6 @@ {% if frappe.session.user != "Guest" and (condition is not defined or (condition is defined and condition )) %} - + {{ _(cta_title) }} {% endif %} diff --git a/frappe/templates/discussions/comment_box.html b/frappe/templates/discussions/comment_box.html index 23c1bbecf1..eb27f6623a 100644 --- a/frappe/templates/discussions/comment_box.html +++ b/frappe/templates/discussions/comment_box.html @@ -15,19 +15,16 @@
- +