From 32d3cc7277cbe6a5479f4bee893b41d9634a6470 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 17 Mar 2023 14:49:24 +0530 Subject: [PATCH 01/61] fix: Consider user perimission default in get_user_default function --- frappe/public/js/frappe/defaults.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/defaults.js b/frappe/public/js/frappe/defaults.js index 03258f4691..ea1d4b4920 100644 --- a/frappe/public/js/frappe/defaults.js +++ b/frappe/public/js/frappe/defaults.js @@ -3,10 +3,14 @@ frappe.defaults = { get_user_default: function (key) { - var defaults = frappe.boot.user.defaults; - var d = defaults[key]; - if (!d && frappe.defaults.is_a_user_permission_key(key)) + let defaults = frappe.boot.user.defaults; + let d = defaults[key]; + if (!d && frappe.defaults.is_a_user_permission_key(key)) { d = defaults[frappe.model.scrub(key)]; + // Check for default user permission values + user_default = this.get_user_permission_default(key); + if (user_default) d = user_default; + } if ($.isArray(d)) d = d[0]; if (!frappe.defaults.in_user_permission(key, d)) { @@ -15,6 +19,21 @@ frappe.defaults = { return d; }, + + get_user_permission_default: function (key) { + let permissions = this.get_user_permissions(); + let user_default = null; + if (permissions[key]) { + permissions[key].forEach((item) => { + if (item.is_default) { + user_default = item.doc; + } + }); + } + + return user_default; + }, + get_user_defaults: function (key) { var defaults = frappe.boot.user.defaults; var d = defaults[key]; From ed5ce1241ffbc99c55c69f2504e9b9ca89a927ff Mon Sep 17 00:00:00 2001 From: AHasanin Date: Mon, 27 Mar 2023 16:48:09 +0200 Subject: [PATCH 02/61] fix(grid row): fix update_docfield_property function not updating grid row --- frappe/public/js/frappe/form/grid.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 7d224f9881..43c7977312 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -1188,6 +1188,10 @@ export default class Grid { // update the parent too (for new rows) this.docfields.find((d) => d.fieldname === fieldname)[property] = value; + if ( this.user_defined_columns && this.user_defined_columns.length > 0 ){ + this.user_defined_columns.find((d) => d.fieldname === fieldname)[property] = value; + } + this.debounced_refresh(); } } From 60a5dc82c3b7b3637addd0c803ea51b38cb1039c Mon Sep 17 00:00:00 2001 From: AHasanin Date: Mon, 27 Mar 2023 17:09:50 +0200 Subject: [PATCH 03/61] fix(grid row): fix prettier hook check --- frappe/public/js/frappe/form/grid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index 43c7977312..ee3b4508bf 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -1188,7 +1188,7 @@ export default class Grid { // update the parent too (for new rows) this.docfields.find((d) => d.fieldname === fieldname)[property] = value; - if ( this.user_defined_columns && this.user_defined_columns.length > 0 ){ + if (this.user_defined_columns && this.user_defined_columns.length > 0) { this.user_defined_columns.find((d) => d.fieldname === fieldname)[property] = value; } From 96395d60dbc405b1fda1be3fe37ec7968aa8c1b9 Mon Sep 17 00:00:00 2001 From: Yash Jain Date: Fri, 31 Mar 2023 19:33:15 +0530 Subject: [PATCH 04/61] fix: change z-index of freeze component to make it appear above all components changed the z-index value of #freeze id to 9999 so that it appears above all UI components fixes #20538 --- frappe/public/scss/desk/global.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index 5294779990..9e5d7ef6fe 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -418,7 +418,7 @@ kbd { // freeze backdrop text #freeze { - z-index: 1020; + z-index: 9999; bottom: 0; opacity: 0; background-color: var(--bg-color); From 090c91b44fb116c28d52e440599d9e95d4d00b21 Mon Sep 17 00:00:00 2001 From: Vincent Vrithof <98533401+vvrithof@users.noreply.github.com> Date: Thu, 6 Apr 2023 09:00:58 +0200 Subject: [PATCH 05/61] fix: virtual fields in child tables not displaying (#20528) * fix: virtual fields in child tables * Update frappe/model/base_document.py Co-authored-by: Ankush Menat * fix: virtual fields in child tables not displaying --------- Co-authored-by: Ankush Menat --- frappe/model/base_document.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 93fddcf686..811ba5894c 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -304,7 +304,9 @@ class BaseDocument: self, sanitize=True, convert_dates_to_str=False, ignore_nulls=False, ignore_virtual=False ) -> dict: d = _dict() - permitted_fields = get_permitted_fields(doctype=self.doctype) + permitted_fields = get_permitted_fields( + doctype=self.doctype, parenttype=getattr(self, "parenttype", None) + ) for fieldname in self.meta.get_valid_columns(): field_value = getattr(self, fieldname, None) From 0a55ac8f0774baac1fb06fbd60e5529ecf9561e3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 6 Apr 2023 15:53:32 +0530 Subject: [PATCH 06/61] chore: Update server side method --- frappe/defaults.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frappe/defaults.py b/frappe/defaults.py index fcfef0b2fc..40813da83e 100644 --- a/frappe/defaults.py +++ b/frappe/defaults.py @@ -28,6 +28,9 @@ def get_user_default(key, user=None): else: d = user_defaults.get(frappe.scrub(key), None) + if not d: + # If no default value is found, use the User Permission value + d = get_user_permission_default(key) value = isinstance(d, (list, tuple)) and d[0] or d if not_in_user_permission(key, value, user): @@ -36,6 +39,18 @@ def get_user_default(key, user=None): return value +def get_user_permission_default(key): + permissions = get_user_permissions() + user_default = "" + if permissions.get(key): + for item in permissions.get(key): + if item.get("is_default"): + user_default = item.get("doc") + break + + return user_default + + def get_user_default_as_list(key, user=None): user_defaults = get_defaults(user or frappe.session.user) d = user_defaults.get(key, None) From 9d094c67d779b65cc27629c6d28a673e43a0872e Mon Sep 17 00:00:00 2001 From: Devin Slauenwhite Date: Thu, 6 Apr 2023 14:17:19 -0400 Subject: [PATCH 07/61] fix: expose DataTable globally --- frappe/public/js/frappe/views/reports/query_report.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index d6c50380f2..d877f47f21 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -2,6 +2,9 @@ // MIT License. See license.txt import DataTable from "frappe-datatable"; +// Expose DataTable globally to allow customizations. +window.DataTable = DataTable; + frappe.provide("frappe.widget.utils"); frappe.provide("frappe.views"); frappe.provide("frappe.query_reports"); @@ -933,7 +936,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (this.report_settings.get_datatable_options) { datatable_options = this.report_settings.get_datatable_options(datatable_options); } - this.datatable = new DataTable(this.$report[0], datatable_options); + this.datatable = new window.DataTable(this.$report[0], datatable_options); } if (typeof this.report_settings.initial_depth == "number") { From a3bc93e92899a22f53d3919376026661879e6877 Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Fri, 7 Apr 2023 01:55:51 +0530 Subject: [PATCH 08/61] fix: dont create communication for auto repeat notification (#20606) * fix: dont create communication upon auto repeat notification creating communication for notification doesnt make sense * test: rename test_notification_is_attached -> test_email_notification now tests for email queue creation --- frappe/automation/doctype/auto_repeat/auto_repeat.py | 12 +++++------- .../doctype/auto_repeat/test_auto_repeat.py | 8 ++++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index 4c09cb01bd..92fff13bed 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -12,8 +12,6 @@ from frappe.contacts.doctype.contact.contact import ( get_contacts_linked_from, get_contacts_linking_to, ) -from frappe.core.doctype.communication.email import make -from frappe.desk.form import assign_to from frappe.model.document import Document from frappe.utils import ( add_days, @@ -365,7 +363,7 @@ class AutoRepeat(Document): error_string += _( "{0}: Failed to attach new recurring document. To enable attaching document in the auto repeat notification email, enable {1} in Print Settings" ).format(frappe.bold(_("Note")), frappe.bold(_("Allow Print for Draft"))) - attachments = "[]" + attachments = None if error_string: message = error_string @@ -376,14 +374,14 @@ class AutoRepeat(Document): recipients = self.recipients.split("\n") - make( - doctype=new_doc.doctype, - name=new_doc.name, + frappe.sendmail( + reference_doctype=new_doc.doctype, + reference_name=new_doc.name, recipients=recipients, subject=subject, content=message, attachments=attachments, - send_email=1, + expose_recipients="header", ) @frappe.whitelist() diff --git a/frappe/automation/doctype/auto_repeat/test_auto_repeat.py b/frappe/automation/doctype/auto_repeat/test_auto_repeat.py index 2754da879f..969c68fbb8 100644 --- a/frappe/automation/doctype/auto_repeat/test_auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/test_auto_repeat.py @@ -163,7 +163,7 @@ class TestAutoRepeat(FrappeTestCase): docnames = frappe.get_all(doc.reference_doctype, {"auto_repeat": doc.name}) self.assertEqual(len(docnames), months) - def test_notification_is_attached(self): + def test_email_notification(self): todo = frappe.get_doc( dict( doctype="ToDo", @@ -187,10 +187,10 @@ class TestAutoRepeat(FrappeTestCase): "ToDo", {"auto_repeat": doc.name, "name": ("!=", todo.name)}, "name" ) - linked_comm = frappe.db.exists( - "Communication", dict(reference_doctype="ToDo", reference_name=new_todo) + email_queue = frappe.db.exists( + "Email Queue", dict(reference_doctype="ToDo", reference_name=new_todo) ) - self.assertTrue(linked_comm) + self.assertTrue(email_queue) def test_next_schedule_date(self): current_date = getdate(today()) From e805497681382f0b8734fd6cfd56cd5856f64888 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Apr 2023 11:32:49 +0530 Subject: [PATCH 09/61] fix: Consider global default in user perm --- frappe/defaults.py | 12 +++++++++--- frappe/public/js/frappe/defaults.js | 10 ++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/frappe/defaults.py b/frappe/defaults.py index 40813da83e..edbf784200 100644 --- a/frappe/defaults.py +++ b/frappe/defaults.py @@ -25,12 +25,12 @@ def get_user_default(key, user=None): if d and isinstance(d, (list, tuple)) and len(d) == 1: # Use User Permission value when only when it has a single value d = d[0] - else: d = user_defaults.get(frappe.scrub(key), None) + user_permission_default = get_user_permission_default(key, user_defaults) if not d: # If no default value is found, use the User Permission value - d = get_user_permission_default(key) + d = user_permission_default value = isinstance(d, (list, tuple)) and d[0] or d if not_in_user_permission(key, value, user): @@ -39,10 +39,16 @@ def get_user_default(key, user=None): return value -def get_user_permission_default(key): +def get_user_permission_default(key, defaults): permissions = get_user_permissions() user_default = "" if permissions.get(key): + # global default in user permission + for item in permissions.get(key): + doc = item.get("doc") + if defaults.get(key) == doc: + user_default = doc + for item in permissions.get(key): if item.get("is_default"): user_default = item.get("doc") diff --git a/frappe/public/js/frappe/defaults.js b/frappe/public/js/frappe/defaults.js index ea1d4b4920..a96f6c3167 100644 --- a/frappe/public/js/frappe/defaults.js +++ b/frappe/public/js/frappe/defaults.js @@ -8,7 +8,7 @@ frappe.defaults = { if (!d && frappe.defaults.is_a_user_permission_key(key)) { d = defaults[frappe.model.scrub(key)]; // Check for default user permission values - user_default = this.get_user_permission_default(key); + user_default = this.get_user_permission_default(key, defaults); if (user_default) d = user_default; } if ($.isArray(d)) d = d[0]; @@ -20,10 +20,16 @@ frappe.defaults = { return d; }, - get_user_permission_default: function (key) { + get_user_permission_default: function (key, defaults) { let permissions = this.get_user_permissions(); let user_default = null; if (permissions[key]) { + permissions[key].forEach((item) => { + if (defaults[key] == item.doc) { + user_default = item.doc; + } + }); + permissions[key].forEach((item) => { if (item.is_default) { user_default = item.doc; From 087caff4cdb889c61ed32ac83dc7b9756ef77c5f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Apr 2023 11:33:22 +0530 Subject: [PATCH 10/61] test: Add test for user perm defaults --- frappe/tests/test_defaults.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/frappe/tests/test_defaults.py b/frappe/tests/test_defaults.py index 3c04f16ec8..b5185a642d 100644 --- a/frappe/tests/test_defaults.py +++ b/frappe/tests/test_defaults.py @@ -71,3 +71,37 @@ class TestDefaults(FrappeTestCase): frappe.delete_doc("User Permission", perm_doc.name) frappe.set_user(old_user) + + def test_user_permission_defaults(self): + # Create user permission + frappe.set_user("user_default_test@example.com") + set_global_default("Language", "") + clear_user_default("Language") + + perm_doc = frappe.get_doc( + dict( + doctype="User Permission", + user=frappe.session.user, + allow="Language", + for_value="en-US", + ) + ).insert(ignore_permissions=True) + + frappe.db.set_value("User Permission", perm_doc.name, "is_default", 1) + set_global_default("Language", "en-GB") + self.assertEqual(get_user_default("Language"), "en-US") + + frappe.db.set_value("User Permission", perm_doc.name, "is_default", 0) + clear_user_default("Language") + self.assertEqual(get_user_default("Language"), None) + + perm_doc = frappe.get_doc( + dict( + doctype="User Permission", + user=frappe.session.user, + allow="Language", + for_value="en-GB", + ) + ).insert(ignore_permissions=True) + + self.assertEqual(get_user_default("Language"), "en-GB") From 5527048592048d9e1dd5c9908bf9a29332451735 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 7 Apr 2023 11:49:25 +0530 Subject: [PATCH 11/61] test: Create user --- frappe/tests/test_defaults.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/tests/test_defaults.py b/frappe/tests/test_defaults.py index b5185a642d..9e8368a0d5 100644 --- a/frappe/tests/test_defaults.py +++ b/frappe/tests/test_defaults.py @@ -1,6 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE import frappe +from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.defaults import * from frappe.tests.utils import FrappeTestCase @@ -74,6 +75,7 @@ class TestDefaults(FrappeTestCase): def test_user_permission_defaults(self): # Create user permission + create_user("user_default_test@example.com", "Blogger") frappe.set_user("user_default_test@example.com") set_global_default("Language", "") clear_user_default("Language") From a48b0f3cf8559d056804ae9500ea12fd19147dd0 Mon Sep 17 00:00:00 2001 From: Mohammad Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com> Date: Fri, 7 Apr 2023 14:33:41 +0530 Subject: [PATCH 12/61] fix: minor oauth doctypes enhancements (#20613) * fix: remove extra`/` from description * fix(ux): show bearer token status in list view --- .../doctype/oauth_bearer_token/oauth_bearer_token.json | 7 +++++-- frappe/integrations/doctype/oauth_client/oauth_client.json | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.json b/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.json index 083f1c9c54..2060c48fb9 100644 --- a/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.json +++ b/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.json @@ -67,6 +67,7 @@ { "fieldname": "status", "fieldtype": "Select", + "in_list_view": 1, "in_standard_filter": 1, "label": "Status", "options": "Active\nRevoked", @@ -74,10 +75,11 @@ } ], "links": [], - "modified": "2021-04-26 06:40:34.922441", + "modified": "2023-04-07 07:08:00.249740", "modified_by": "Administrator", "module": "Integrations", "name": "OAuth Bearer Token", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { @@ -92,5 +94,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/frappe/integrations/doctype/oauth_client/oauth_client.json b/frappe/integrations/doctype/oauth_client/oauth_client.json index f4ccde8174..8b863f62ad 100644 --- a/frappe/integrations/doctype/oauth_client/oauth_client.json +++ b/frappe/integrations/doctype/oauth_client/oauth_client.json @@ -76,7 +76,7 @@ "fieldtype": "Column Break" }, { - "description": "URIs for receiving authorization code once the user allows access, as well as failure responses. Typically a REST endpoint exposed by the Client App.\n
e.g. http://hostname//api/method/frappe.www.login.login_via_facebook", + "description": "URIs for receiving authorization code once the user allows access, as well as failure responses. Typically a REST endpoint exposed by the Client App.\n
e.g. http://hostname/api/method/frappe.www.login.login_via_facebook", "fieldname": "redirect_uris", "fieldtype": "Text", "label": "Redirect URIs" @@ -117,7 +117,7 @@ } ], "links": [], - "modified": "2022-08-03 12:21:52.062755", + "modified": "2023-04-07 07:06:35.765981", "modified_by": "Administrator", "module": "Integrations", "name": "OAuth Client", From 3a76a771a9a19bed7b87c33729e38a92bb97cc71 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Fri, 7 Apr 2023 11:26:53 +0200 Subject: [PATCH 13/61] fix: german translation of workflow state (#20609) --- frappe/translations/de.csv | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv index 2dd903ee93..834281bbab 100644 --- a/frappe/translations/de.csv +++ b/frappe/translations/de.csv @@ -383,7 +383,7 @@ Align Labels to the Right,Etiketten rechts ausrichten, Align Value,Wert anordnen, All Images attached to Website Slideshow should be public,"Alle Bilder, die an die Website-Slideshow angehängt werden, sollten öffentlich sein.", All customizations will be removed. Please confirm.,Alle Anpassungen werden entfernt. Bitte bestätigen., -"All possible Workflow States and roles of the workflow. Docstatus Options: 0 is""Saved"", 1 is ""Submitted"" and 2 is ""Cancelled""","Alle möglichen Zustände und Rollen des Workflows. Dokumentenstatus-Optionen sind: 0 ist ""Gespeichert"", 1 ist ""Übertragen"" und 2 ist ""Abgebrochen""", +"All possible Workflow States and roles of the workflow. Docstatus Options: 0 is""Saved"", 1 is ""Submitted"" and 2 is ""Cancelled""","Alle möglichen Stati und Rollen des Workflows. Dokumentenstatus-Optionen sind: 0 ist ""Gespeichert"", 1 ist ""Übertragen"" und 2 ist ""Abgebrochen""", All-uppercase is almost as easy to guess as all-lowercase.,Ausschließlich Großbuchstaben sind fast so einfach zu erraten wie ausschließlich Kleinbuchstaben., Allocated To,Zugewiesen zu, Allow,Zulassen, @@ -851,7 +851,7 @@ Default Value,Standardwert, DefaultValue,Standardwert, Define workflows for forms.,Workflows für Formulare definieren, Defines actions on states and the next step and allowed roles.,"Definiert Maßnahmen bei bestimmten Zuständen, den nächsten Schritt und erlaubte Rollen.", -Defines workflow states and rules for a document.,Definiert Workflow-Zustände und Regeln für ein Dokument., +Defines workflow states and rules for a document.,Definiert Workflow-Stati und Regeln für ein Dokument., Delayed,Verzögert, Delete Data,Daten löschen, Delete comment?,Kommentar löschen?, @@ -1080,7 +1080,7 @@ Fetch attached images from document,Holen Sie angehängte Bilder aus dem Dokumen Field Description,Feldbeschreibung, Field Maps,Feldkarten, Field Type,Feldtyp, -"Field that represents the Workflow State of the transaction (if field is not present, a new hidden Custom Field will be created)","Feld, das den Status des Workflows der einzelnen Transaktionen wiedergibt (wenn das Feld nicht vorhanden ist, wird ein neues verstecktes, benutzerdefiniertes Feld erstellt)", +"Field that represents the Workflow State of the transaction (if field is not present, a new hidden Custom Field will be created)","Feldc für den Workflow-Status der einzelnen Transaktionen (wenn das Feld nicht vorhanden ist, wird ein neues verstecktes, benutzerdefiniertes Feld erstellt)", Field to Track,Zu verfolgendes Feld, Field type cannot be changed for {0},Feldtyp kann nicht für {0} geändert werden, Field {0} not found.,Feld {0} nicht gefunden, @@ -2777,11 +2777,11 @@ Workflow Action Master,Stammdaten zu Workflow-Aktionen, Workflow Action Name,Workflow-Aktionsname, Workflow Document State,Workflow-Dokumentenstatus, Workflow Name,Workflow-Name, -Workflow State,Workflow-Zustand, -Workflow State Field,Workflow-Zustandsfeld, +Workflow State,Workflow-Status, +Workflow State Field,Workflow-Status-Feld, Workflow State not set,Workflow-Status nicht festgelegt, Workflow Transition,Workflow-Übergang, -Workflow state represents the current state of a document.,Workflow-Zustand stellt den aktuellen Status eines Dokuments dar., +Workflow state represents the current state of a document.,Der Workflow-Status steht für den aktuellen Zustand eines Dokuments., Write,Schreiben, Wrong fieldname {0} in add_fetch configuration of custom script,Falscher Feldname {0} in der add_fetch-Konfiguration des benutzerdefinierten Skripts, X Axis Field,X-Achsenfeld, @@ -3070,7 +3070,7 @@ zoom-out,verkleinern, {0} is an invalid email address in 'Recipients',"{0} ist eine ungültige E-Mail-Adresse in ""Empfänger""", {0} is not a raw printing format.,{0} ist kein unformatiertes Druckformat., {0} is not a valid Email Address,{0} ist keine gültige E-Mail-Adresse, -{0} is not a valid Workflow State. Please update your Workflow and try again.,{0} ist kein gültiger Workflow-Zustand. Bitte aktualisieren Sie Ihren Workflow und versuchen Sie es erneut., +{0} is not a valid Workflow State. Please update your Workflow and try again.,{0} ist kein gültiger Workflow-Status. Bitte aktualisieren Sie Ihren Workflow und versuchen Sie es erneut., {0} is now default print format for {1} doctype,{0} ist jetzt das Standard-Druckformat für den DocType {1}, {0} is saved,{0} ist gespeichert, {0} items selected,{0} Elemente ausgewählt, @@ -3131,7 +3131,7 @@ Force User to Reset Password,Benutzer zum Zurücksetzen des Kennworts zwingen, In Days,In Tagen, Last Password Reset Date,Datum der letzten Kennwortrücksetzung, The password of your account has expired.,Das Passwort Ihres Kontos ist abgelaufen., -Workflow State transition not allowed from {0} to {1},Workflow-Statusübergang von {0} nach {1} nicht zulässig, +Workflow State transition not allowed from {0} to {1},Eine Veränderung des Workflow-Status von {0} nach {1} ist nicht zulässig, {0} must be after {1},{0} muss nach {1} liegen, {0}: Field '{1}' cannot be set as Unique as it has non-unique values,"{0}: Feld '{1}' kann nicht als eindeutig festgelegt werden, da es nicht eindeutige Werte enthält", {0}: Field {1} in row {2} cannot be hidden and mandatory without default,{0}: Das Feld {1} in Zeile {2} kann ohne Vorgabe nicht ausgeblendet und obligatorisch sein, @@ -4645,8 +4645,8 @@ Hide Traceback,Traceback ausblenden, Value from this field will be set as the due date in the ToDo,Der Wert aus diesem Feld wird im Fälligkeitsdatum als Fälligkeitsdatum festgelegt, New module created {0},Neues Modul erstellt {0}, "Report has no numeric fields, please change the Report Name",Der Bericht enthält keine numerischen Felder. Bitte ändern Sie den Berichtsnamen, -There are documents which have workflow states that do not exist in this Workflow. It is recommended that you add these states to the Workflow and change their states before removing these states.,"Es gibt Dokumente mit Workflow-Status, die in diesem Workflow nicht vorhanden sind. Es wird empfohlen, diese Status zum Workflow hinzuzufügen und ihre Status zu ändern, bevor Sie diese Status entfernen.", -Worflow States Don't Exist,Worflow-Zustände existieren nicht, +There are documents which have workflow states that do not exist in this Workflow. It is recommended that you add these states to the Workflow and change their states before removing these states.,"Es gibt Dokumente mit Workflow-Status, die in diesem Workflow nicht vorhanden sind. Es wird empfohlen, diese Stati zum Workflow hinzuzufügen oder den Status der Dokumente ändern, bevor Sie einen Status entfernen.", +Worflow States Don't Exist,Worflow-Stati existieren nicht, Save Anyway,Auf jeden Fall speichern, Energy Points:,Energiepunkte:, Review Points:,Bewertungspunkte:, From cd670bf78fc49d8c0c48319516f456ee08ff3c8f Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Fri, 7 Apr 2023 11:28:02 +0200 Subject: [PATCH 14/61] feat: make report name translatable (#20608) --- frappe/public/js/frappe/views/reports/report_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 86d5705011..83411f0ddf 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -33,7 +33,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { this.filters = this.report_doc.json.filters; this.order_by = this.report_doc.json.order_by; this.add_totals_row = this.report_doc.json.add_totals_row; - this.page_title = this.report_name; + this.page_title = __(this.report_name); this.page_length = this.report_doc.json.page_length || 20; this.order_by = this.report_doc.json.order_by || "modified desc"; this.chart_args = this.report_doc.json.chart_args; From e5b1b8d681240cf8d67bfeb25e2ed508afe30cec Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Sun, 9 Apr 2023 13:10:51 +0200 Subject: [PATCH 15/61] fix: improved validation in `add_comment` (#20520) --- frappe/core/doctype/comment/test_comment.py | 38 ++++++++++++++++--- .../templates/includes/comments/comments.py | 18 ++++++--- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/frappe/core/doctype/comment/test_comment.py b/frappe/core/doctype/comment/test_comment.py index f40f1659b3..ee2d473210 100644 --- a/frappe/core/doctype/comment/test_comment.py +++ b/frappe/core/doctype/comment/test_comment.py @@ -3,7 +3,10 @@ import json import frappe -from frappe.tests.utils import FrappeTestCase +from frappe.templates.includes.comments.comments import add_comment +from frappe.tests.test_model_utils import set_user +from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.website.doctype.blog_post.test_blog_post import make_test_blog class TestComment(FrappeTestCase): @@ -39,14 +42,10 @@ class TestComment(FrappeTestCase): # test via blog def test_public_comment(self): - from frappe.website.doctype.blog_post.test_blog_post import make_test_blog - test_blog = make_test_blog() frappe.db.delete("Comment", {"reference_doctype": "Blog Post"}) - from frappe.templates.includes.comments.comments import add_comment - frappe.form_dict.comment = "Good comment with 10 chars" frappe.form_dict.comment_email = "test@test.com" frappe.form_dict.comment_by = "Good Tester" @@ -102,3 +101,32 @@ class TestComment(FrappeTestCase): ) test_blog.delete() + + @change_settings("Blog Settings", {"allow_guest_to_comment": 0}) + 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) + + def test_user_not_logged_in(self): + some_system_user = frappe.db.get_value("User", {}) + + 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) diff --git a/frappe/templates/includes/comments/comments.py b/frappe/templates/includes/comments/comments.py index 3a056761f3..d76b6d1a48 100644 --- a/frappe/templates/includes/comments/comments.py +++ b/frappe/templates/includes/comments/comments.py @@ -18,10 +18,17 @@ EMAIL_PATTERN = re.compile(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)" @frappe.whitelist(allow_guest=True) @rate_limit(key="reference_name", limit=get_comment_limit, seconds=60 * 60) def add_comment(comment, comment_email, comment_by, reference_doctype, reference_name, route): - doc = frappe.get_doc(reference_doctype, reference_name) + if frappe.session.user == "Guest": + if reference_doctype not in ("Blog Post", "Web Page"): + return - if frappe.session.user == "Guest" and doc.doctype not in ["Blog Post", "Web Page"]: - return + if reference_doctype == "Blog Post" and not frappe.db.get_single_value( + "Blog Settings", "allow_guest_to_comment" + ): + return + + if frappe.db.exists("User", comment_email): + frappe.throw(_("Please login to post a comment.")) if not comment.strip(): frappe.msgprint(_("The comment cannot be empty")) @@ -31,6 +38,7 @@ def add_comment(comment, comment_email, comment_by, reference_doctype, reference frappe.msgprint(_("Comments cannot have links or email addresses")) return False + doc = frappe.get_doc(reference_doctype, reference_name) comment = doc.add_comment( text=clean_html(comment), comment_email=comment_email, comment_by=comment_by ) @@ -50,9 +58,7 @@ def add_comment(comment, comment_email, comment_by, reference_doctype, reference url, _("View Comment") ) - if doc.doctype == "Blog Post" and not doc.enable_email_notification: - pass - else: + if doc.doctype != "Blog Post" or doc.enable_email_notification: # notify creator creator_email = frappe.db.get_value("User", doc.owner, "email") or doc.owner subject = _("New Comment on {0}: {1}").format(doc.doctype, doc.get_title()) From 68b1051f69430d934f08fe7725370ed504d0daeb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 9 Apr 2023 20:43:10 +0530 Subject: [PATCH 16/61] test: Set defualts --- frappe/tests/test_defaults.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/tests/test_defaults.py b/frappe/tests/test_defaults.py index 9e8368a0d5..0c58e59c78 100644 --- a/frappe/tests/test_defaults.py +++ b/frappe/tests/test_defaults.py @@ -49,6 +49,9 @@ class TestDefaults(FrappeTestCase): self.assertEqual(get_user_default("key6"), None) def test_user_permission_on_defaults(self): + add_global_default("language", "en") + set_user_default("language", "en") + self.assertEqual(get_global_default("language"), "en") self.assertEqual(get_user_default("language"), "en") self.assertEqual(get_user_default_as_list("language"), ["en"]) @@ -91,6 +94,7 @@ class TestDefaults(FrappeTestCase): frappe.db.set_value("User Permission", perm_doc.name, "is_default", 1) set_global_default("Language", "en-GB") + clear_user_default("Language") self.assertEqual(get_user_default("Language"), "en-US") frappe.db.set_value("User Permission", perm_doc.name, "is_default", 0) From 5d61ed2d8f268345cf0fc457c9124778befef4d5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 9 Apr 2023 21:26:35 +0530 Subject: [PATCH 17/61] test: Update test --- frappe/tests/test_defaults.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/frappe/tests/test_defaults.py b/frappe/tests/test_defaults.py index 0c58e59c78..17f5f9098e 100644 --- a/frappe/tests/test_defaults.py +++ b/frappe/tests/test_defaults.py @@ -49,9 +49,6 @@ class TestDefaults(FrappeTestCase): self.assertEqual(get_user_default("key6"), None) def test_user_permission_on_defaults(self): - add_global_default("language", "en") - set_user_default("language", "en") - self.assertEqual(get_global_default("language"), "en") self.assertEqual(get_user_default("language"), "en") self.assertEqual(get_user_default_as_list("language"), ["en"]) @@ -80,34 +77,33 @@ class TestDefaults(FrappeTestCase): # Create user permission create_user("user_default_test@example.com", "Blogger") frappe.set_user("user_default_test@example.com") - set_global_default("Language", "") - clear_user_default("Language") + set_global_default("Country", "") + clear_user_default("Country") perm_doc = frappe.get_doc( dict( doctype="User Permission", user=frappe.session.user, - allow="Language", - for_value="en-US", + allow="Country", + for_value="India", ) ).insert(ignore_permissions=True) frappe.db.set_value("User Permission", perm_doc.name, "is_default", 1) - set_global_default("Language", "en-GB") - clear_user_default("Language") - self.assertEqual(get_user_default("Language"), "en-US") + set_global_default("Country", "United States") + self.assertEqual(get_user_default("Country"), "India") frappe.db.set_value("User Permission", perm_doc.name, "is_default", 0) - clear_user_default("Language") - self.assertEqual(get_user_default("Language"), None) + clear_user_default("Country") + self.assertEqual(get_user_default("Country"), None) perm_doc = frappe.get_doc( dict( doctype="User Permission", user=frappe.session.user, - allow="Language", - for_value="en-GB", + allow="Country", + for_value="United States", ) ).insert(ignore_permissions=True) - self.assertEqual(get_user_default("Language"), "en-GB") + self.assertEqual(get_user_default("Country"), "United States") From 55867282adf798a4089062905fe29c069799d63b Mon Sep 17 00:00:00 2001 From: Mohammad Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com> Date: Mon, 10 Apr 2023 09:57:34 +0530 Subject: [PATCH 18/61] fix: also build jsx files (#20624) --- esbuild/esbuild.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esbuild/esbuild.js b/esbuild/esbuild.js index cfd6b1a1b6..3c5c305665 100644 --- a/esbuild/esbuild.js +++ b/esbuild/esbuild.js @@ -183,7 +183,7 @@ function get_all_files_to_build(apps) { for (let app of apps) { let public_path = get_public_path(app); include_patterns.push( - path.resolve(public_path, "**", "*.bundle.{js,ts,css,sass,scss,less,styl}") + path.resolve(public_path, "**", "*.bundle.{js,ts,css,sass,scss,less,styl,jsx}") ); ignore_patterns.push( path.resolve(public_path, "node_modules"), From 75ba9b40d8a0e18cf267d8b42cafc6053a85b574 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 10 Apr 2023 11:42:06 +0530 Subject: [PATCH 19/61] fix: child row form should be above freeze screen sidebar overlay was also getting affected --- frappe/public/scss/common/grid.scss | 2 +- frappe/public/scss/desk/global.scss | 2 +- frappe/public/scss/desk/sidebar.scss | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index 775bf90704..f25c7ef66c 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -321,7 +321,7 @@ overflow: hidden; height: 0; opacity: 0; - z-index: 1021; + z-index: 1051; border-radius: var(--border-radius-md); @include base-grid(); diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index 9e5d7ef6fe..cb66de9920 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -418,7 +418,7 @@ kbd { // freeze backdrop text #freeze { - z-index: 9999; + z-index: 1050; bottom: 0; opacity: 0; background-color: var(--bg-color); diff --git a/frappe/public/scss/desk/sidebar.scss b/frappe/public/scss/desk/sidebar.scss index 25dcceec5b..b3772f47b1 100644 --- a/frappe/public/scss/desk/sidebar.scss +++ b/frappe/public/scss/desk/sidebar.scss @@ -242,7 +242,7 @@ body[data-route^="Module"] .main-menu { right: 0; opacity: 0.3; background: #000; - z-index: 1041; + z-index: 9998; height: 100%; width: 100%; } From 796250577987579bb1f2eb7699be9baabe0bad18 Mon Sep 17 00:00:00 2001 From: "Patrick.St" <72972659+pstuhlmueller@users.noreply.github.com> Date: Mon, 10 Apr 2023 10:19:48 +0200 Subject: [PATCH 20/61] fix(ListView): Evaluate sort_field sort_order within listviews based on DocTypes Definition (#20482) --- frappe/public/js/frappe/list/base_list.js | 4 ++-- frappe/public/js/frappe/list/list_view.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 8852a0df5d..ab0ca87b0d 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -53,8 +53,8 @@ frappe.views.BaseList = class BaseList { this.fields = []; this.filters = []; - this.sort_by = "modified"; - this.sort_order = "desc"; + this.sort_by = this.meta.sort_field || "modified"; + this.sort_order = this.meta.sort_order || "desc"; // Setup buttons this.primary_action = null; diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 6944c4df7c..63aff539e7 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -80,8 +80,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.view = "List"; // initialize with saved order by - this.sort_by = this.view_user_settings.sort_by || "modified"; - this.sort_order = this.view_user_settings.sort_order || "desc"; + this.sort_by = this.view_user_settings.sort_by || this.sort_by || "modified"; + this.sort_order = this.view_user_settings.sort_order || this.sort_order || "desc"; // build menu items this.menu_items = this.menu_items.concat(this.get_menu_items()); From c0ae00168dfac16f8b12cd2ea7985fc4281d65fb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 10 Apr 2023 13:58:06 +0530 Subject: [PATCH 21/61] chore: Only run on mariadb --- frappe/tests/test_defaults.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/tests/test_defaults.py b/frappe/tests/test_defaults.py index 17f5f9098e..a8c8ed2697 100644 --- a/frappe/tests/test_defaults.py +++ b/frappe/tests/test_defaults.py @@ -3,6 +3,8 @@ import frappe from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.defaults import * +from frappe.query_builder.utils import db_type_is +from frappe.tests.test_query_builder import run_only_if from frappe.tests.utils import FrappeTestCase @@ -73,6 +75,7 @@ class TestDefaults(FrappeTestCase): frappe.delete_doc("User Permission", perm_doc.name) frappe.set_user(old_user) + @run_only_if(db_type_is.MARIADB) def test_user_permission_defaults(self): # Create user permission create_user("user_default_test@example.com", "Blogger") From a3a9e40aa4f78a1c66457a9c25f4194f42c003f2 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Apr 2023 07:29:14 +0530 Subject: [PATCH 22/61] fix: log requests even if no response (#20638) --- frappe/monitor.py | 7 +++++-- frappe/tests/test_monitor.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/frappe/monitor.py b/frappe/monitor.py index 8db1e25d32..b93ba1d3bb 100644 --- a/frappe/monitor.py +++ b/frappe/monitor.py @@ -89,8 +89,11 @@ class Monitor: self.data.duration = int(timediff.total_seconds() * 1000000) if self.data.transaction_type == "request": - self.data.request.status_code = response.status_code - self.data.request.response_length = int(response.headers.get("Content-Length", 0)) + if response: + self.data.request.status_code = response.status_code + self.data.request.response_length = int(response.headers.get("Content-Length", 0)) + else: + self.data.request.status_code = 500 if hasattr(frappe.local, "rate_limiter"): limiter = frappe.local.rate_limiter diff --git a/frappe/tests/test_monitor.py b/frappe/tests/test_monitor.py index 7536c6a75a..e59ebcde31 100644 --- a/frappe/tests/test_monitor.py +++ b/frappe/tests/test_monitor.py @@ -33,6 +33,20 @@ class TestMonitor(FrappeTestCase): self.assertEqual(log.transaction_type, "request") self.assertEqual(log.request["method"], "GET") + def test_no_response(self): + set_request(method="GET", path="/api/method/frappe.ping") + + frappe.monitor.start() + frappe.monitor.stop(response=None) + + logs = frappe.cache().lrange(MONITOR_REDIS_KEY, 0, -1) + self.assertEqual(len(logs), 1) + + log = frappe.parse_json(logs[0].decode()) + self.assertEqual(log.request["status_code"], 500) + self.assertEqual(log.transaction_type, "request") + self.assertEqual(log.request["method"], "GET") + def test_job(self): frappe.utils.background_jobs.execute_job( frappe.local.site, "frappe.ping", None, None, {}, is_async=False From 6248d8f06283ed6079c33dfe115c19aa93c0d7c9 Mon Sep 17 00:00:00 2001 From: Bread Genie <63963181+BreadGenie@users.noreply.github.com> Date: Tue, 11 Apr 2023 10:56:52 +0530 Subject: [PATCH 23/61] fix(pretty-date): plural form when the value is 1 (#20619) --- frappe/utils/data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 460ca26d85..d3ffb8b749 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1551,15 +1551,15 @@ def pretty_date(iso_datetime: datetime.datetime | str) -> str: return _("Yesterday") elif dt_diff_days < 7.0: return _("{0} days ago").format(cint(dt_diff_days)) - elif dt_diff_days < 12: + elif dt_diff_days < 14: return _("1 week ago") elif dt_diff_days < 31.0: return _("{0} weeks ago").format(dt_diff_days // 7) - elif dt_diff_days < 46: + elif dt_diff_days < 61.0: return _("1 month ago") elif dt_diff_days < 365.0: return _("{0} months ago").format(dt_diff_days // 30) - elif dt_diff_days < 550.0: + elif dt_diff_days < 730.0: return _("1 year ago") else: return _("{0} years ago").format(dt_diff_days // 365) From 26e73208d596ebc8c1f6b8cad82528ba616778c8 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 11 Apr 2023 11:24:03 +0530 Subject: [PATCH 24/61] fix: validate if doctype exists before syncing customisations (#20598) * fix: validate if doctype exists before syncing customisations * refactor: rename and add default --------- Co-authored-by: Ankush Menat --- frappe/modules/utils.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 0e124ee3aa..e23684d5c3 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -114,10 +114,10 @@ def sync_customizations(app=None): with open(os.path.join(folder, fname)) as f: data = json.loads(f.read()) if data.get("sync_on_migrate"): - sync_customizations_for_doctype(data, folder) + sync_customizations_for_doctype(data, folder, fname) -def sync_customizations_for_doctype(data: dict, folder: str): +def sync_customizations_for_doctype(data: dict, folder: str, filename: str = ""): """Sync doctype customzations for a particular data set""" from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype @@ -158,6 +158,11 @@ def sync_customizations_for_doctype(data: dict, folder: str): if doc_type == doctype or not os.path.exists(os.path.join(folder, scrub(doc_type) + ".json")): sync_single_doctype(doc_type) + if not frappe.db.exists("DocType", doctype): + print(_("DocType {0} does not exist.").format(doctype)) + print(_("Skipping fixture syncing for doctyoe {0} from file {1} ").format(doctype, filename)) + return + if data["custom_fields"]: sync("custom_fields", "Custom Field", "dt") update_schema = True @@ -168,7 +173,6 @@ def sync_customizations_for_doctype(data: dict, folder: str): if data.get("custom_perms"): sync("custom_perms", "Custom DocPerm", "parent") - print(f"Updating customizations for {doctype}") validate_fields_for_doctype(doctype) if update_schema and not frappe.db.get_value("DocType", doctype, "issingle"): From c36dabbc63274af242ac8cddbc5ade8bcf4c2499 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Apr 2023 11:26:47 +0530 Subject: [PATCH 25/61] chore: add removed message Not sure why this was removed --- frappe/modules/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index e23684d5c3..8488328da4 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -170,6 +170,7 @@ def sync_customizations_for_doctype(data: dict, folder: str, filename: str = "") if data["property_setters"]: sync("property_setters", "Property Setter", "doc_type") + print(f"Updating customizations for {doctype}") if data.get("custom_perms"): sync("custom_perms", "Custom DocPerm", "parent") From 08732e50dbbe881b20254667b97d67bc5b7b8d15 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Mon, 10 Apr 2023 23:12:18 -0700 Subject: [PATCH 26/61] fix: better permission error for query_report (#20643) --- frappe/desk/query_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 7abd6657e5..bb845cae95 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -443,7 +443,7 @@ def add_total_row(result, columns, meta=None, is_tree=False, parent_field=None): def get_data_for_custom_field(doctype, field): if not frappe.has_permission(doctype, "read"): - frappe.throw(_("Not Permitted"), frappe.PermissionError) + frappe.throw(_("Not Permitted to read {0}").format(doctype), frappe.PermissionError) value_map = frappe._dict(frappe.get_all(doctype, fields=["name", field], as_list=1)) From 361e44de1db9aac2844b0389cd244bd3506c72af Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Apr 2023 11:50:26 +0530 Subject: [PATCH 27/61] fix(translations)!: load translation in installed order - translations are loaded in apps.txt order this doesnt make much sense. - translations are loaded from apps which aren't even installed, again doesn't make sense. Breaking but necessary change. --- .../core/doctype/translation/test_translation.py | 4 ++-- frappe/tests/test_translate.py | 5 ----- frappe/translate.py | 14 +++++++------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/frappe/core/doctype/translation/test_translation.py b/frappe/core/doctype/translation/test_translation.py index 5602fa2c2d..13ce711c66 100644 --- a/frappe/core/doctype/translation/test_translation.py +++ b/frappe/core/doctype/translation/test_translation.py @@ -3,7 +3,7 @@ import frappe from frappe import _ from frappe.tests.utils import FrappeTestCase -from frappe.translate import clear_cache +from frappe.translate import APP_TRANSLATION_KEY, clear_cache class TestTranslation(FrappeTestCase): @@ -115,4 +115,4 @@ def create_translation(key, val): def clear_translation_cache(): - frappe.cache().delete_key("translations_from_apps", shared=True) + frappe.cache().delete_key(APP_TRANSLATION_KEY) diff --git a/frappe/tests/test_translate.py b/frappe/tests/test_translate.py index a5e2876241..c605810837 100644 --- a/frappe/tests/test_translate.py +++ b/frappe/tests/test_translate.py @@ -8,7 +8,6 @@ from unittest.mock import patch import frappe import frappe.translate from frappe import _ -from frappe.core.doctype.translation.test_translation import clear_translation_cache from frappe.tests.utils import FrappeTestCase from frappe.translate import ( extract_javascript, @@ -39,15 +38,11 @@ class TestTranslate(FrappeTestCase): if self._testMethodName in self.guest_sessions_required: frappe.set_user("Guest") - clear_translation_cache() - def tearDown(self): frappe.form_dict.pop("_lang", None) if self._testMethodName in self.guest_sessions_required: frappe.set_user("Administrator") - clear_translation_cache() - def test_extract_message_from_file(self): data = frappe.translate.get_messages_from_file(translation_string_file) exp_filename = "apps/frappe/frappe/tests/translation_test_file.txt" diff --git a/frappe/translate.py b/frappe/translate.py index 5179daa545..03bd49e4c6 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -171,7 +171,7 @@ def get_dict(fortype: str, name: str | None = None) -> dict[str, str]: fortype = fortype.lower() cache = frappe.cache() asset_key = fortype + ":" + (name or "-") - translation_assets = cache.hget("translation_assets", frappe.local.lang, shared=True) or {} + translation_assets = cache.hget("translation_assets", frappe.local.lang) or {} if asset_key not in translation_assets: messages = [] @@ -211,7 +211,7 @@ def get_dict(fortype: str, name: str | None = None) -> dict[str, str]: # remove untranslated message_dict = {k: v for k, v in message_dict.items() if k != v} translation_assets[asset_key] = message_dict - cache.hset("translation_assets", frappe.local.lang, translation_assets, shared=True) + cache.hset("translation_assets", frappe.local.lang, translation_assets) translation_map: dict = translation_assets[asset_key] @@ -310,7 +310,7 @@ def get_translations_from_apps(lang, apps=None): def _get_from_disk(): translations = {} - for app in apps or frappe.get_all_apps(True): + for app in apps or frappe.get_installed_apps(_ensure_on_bench=True): path = os.path.join(frappe.get_pymodule_path(app), "translations", lang + ".csv") translations.update(get_translation_dict_from_file(path, lang, app) or {}) if "-" in lang: @@ -321,7 +321,7 @@ def get_translations_from_apps(lang, apps=None): return translations - return frappe.cache().hget(APP_TRANSLATION_KEY, lang, shared=True, generator=_get_from_disk) + return frappe.cache().hget(APP_TRANSLATION_KEY, lang, generator=_get_from_disk) def get_translation_dict_from_file(path, lang, app, throw=False) -> dict[str, str]: @@ -375,8 +375,8 @@ def clear_cache(): # clear translations saved in boot cache cache.delete_key("bootinfo") - cache.delete_key("translation_assets", shared=True) - cache.delete_key(APP_TRANSLATION_KEY, shared=True) + cache.delete_key("translation_assets") + cache.delete_key(APP_TRANSLATION_KEY) cache.delete_key(USER_TRANSLATION_KEY) cache.delete_key(MERGED_TRANSLATION_KEY) @@ -687,7 +687,7 @@ def get_messages_from_include_files(app_name=None): def get_all_messages_from_js_files(app_name=None): """Extracts all translatable strings from app `.js` files""" messages = [] - for app in [app_name] if app_name else frappe.get_installed_apps(): + for app in [app_name] if app_name else frappe.get_installed_apps(_ensure_on_bench=True): if os.path.exists(frappe.get_app_path(app, "public")): for basepath, folders, files in os.walk(frappe.get_app_path(app, "public")): if "frappe/public/js/lib" in basepath: From 0b8b8294836dfc949ec552e89d571b64d909dbea Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Apr 2023 11:59:19 +0530 Subject: [PATCH 28/61] perf: dont cache intermediate translation files Just caching final files for each language is enough, duplicating doesn't help much. --- .../doctype/translation/test_translation.py | 10 +------- frappe/translate.py | 25 ++++++++----------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/frappe/core/doctype/translation/test_translation.py b/frappe/core/doctype/translation/test_translation.py index 13ce711c66..a64715a32e 100644 --- a/frappe/core/doctype/translation/test_translation.py +++ b/frappe/core/doctype/translation/test_translation.py @@ -3,7 +3,7 @@ import frappe from frappe import _ from frappe.tests.utils import FrappeTestCase -from frappe.translate import APP_TRANSLATION_KEY, clear_cache +from frappe.translate import clear_cache class TestTranslation(FrappeTestCase): @@ -37,20 +37,16 @@ class TestTranslation(FrappeTestCase): frappe.local.lang = "es" - clear_translation_cache() self.assertTrue(_(data[0][0]), data[0][1]) - clear_translation_cache() self.assertTrue(_(data[1][0]), data[1][1]) frappe.local.lang = "es-MX" # different translation for es-MX - clear_translation_cache() self.assertTrue(_(data[2][0]), data[2][1]) # from spanish (general) - clear_translation_cache() self.assertTrue(_(data[1][0]), data[1][1]) def test_multi_language_translations(self): @@ -112,7 +108,3 @@ def create_translation(key, val): translation.translated_text = val[1] translation.save() return translation - - -def clear_translation_cache(): - frappe.cache().delete_key(APP_TRANSLATION_KEY) diff --git a/frappe/translate.py b/frappe/translate.py index 03bd49e4c6..041e983432 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -56,7 +56,6 @@ CSV_STRIP_WHITESPACE_PATTERN = re.compile(r"{\s?([0-9]+)\s?}") # Cache keys MERGED_TRANSLATION_KEY = "merged_translations" -APP_TRANSLATION_KEY = "translations_from_apps" USER_TRANSLATION_KEY = "lang_user_translations" @@ -308,20 +307,17 @@ def get_translations_from_apps(lang, apps=None): if lang == "en": return {} - def _get_from_disk(): - translations = {} - for app in apps or frappe.get_installed_apps(_ensure_on_bench=True): - path = os.path.join(frappe.get_pymodule_path(app), "translations", lang + ".csv") - translations.update(get_translation_dict_from_file(path, lang, app) or {}) - if "-" in lang: - parent = lang.split("-", 1)[0] - parent_translations = get_translations_from_apps(parent) - parent_translations.update(translations) - return parent_translations + translations = {} + for app in apps or frappe.get_installed_apps(_ensure_on_bench=True): + path = os.path.join(frappe.get_pymodule_path(app), "translations", lang + ".csv") + translations.update(get_translation_dict_from_file(path, lang, app) or {}) + if "-" in lang: + parent = lang.split("-", 1)[0] + parent_translations = get_translations_from_apps(parent) + parent_translations.update(translations) + return parent_translations - return translations - - return frappe.cache().hget(APP_TRANSLATION_KEY, lang, generator=_get_from_disk) + return translations def get_translation_dict_from_file(path, lang, app, throw=False) -> dict[str, str]: @@ -376,7 +372,6 @@ def clear_cache(): # clear translations saved in boot cache cache.delete_key("bootinfo") cache.delete_key("translation_assets") - cache.delete_key(APP_TRANSLATION_KEY) cache.delete_key(USER_TRANSLATION_KEY) cache.delete_key(MERGED_TRANSLATION_KEY) From 6fa732e2f711835bc06d9a9c8ac3c094da405dc6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Apr 2023 12:29:15 +0530 Subject: [PATCH 29/61] test: flake in redis caching test [skip ci] --- frappe/tests/test_caching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/tests/test_caching.py b/frappe/tests/test_caching.py index 4faade331c..37f1583097 100644 --- a/frappe/tests/test_caching.py +++ b/frappe/tests/test_caching.py @@ -107,7 +107,7 @@ class TestRedisCache(FrappeAPITestCase): self.assertEqual(calculate_area(10), 314) self.assertEqual(function_call_count, 1) - time.sleep(CACHE_TTL) + time.sleep(CACHE_TTL * 1.5) self.assertEqual(calculate_area(10), 314) self.assertEqual(function_call_count, 2) From 75b32f90abe71924a1e63ec87fd4f5c97eab5bca Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Apr 2023 14:35:48 +0530 Subject: [PATCH 30/61] fix: track workspace changes (#20649) --- frappe/desk/doctype/workspace/workspace.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/workspace/workspace.json b/frappe/desk/doctype/workspace/workspace.json index af5f9c4184..6412c626cb 100644 --- a/frappe/desk/doctype/workspace/workspace.json +++ b/frappe/desk/doctype/workspace/workspace.json @@ -198,7 +198,7 @@ ], "in_create": 1, "links": [], - "modified": "2023-02-15 01:16:56.035205", + "modified": "2023-04-11 14:34:24.829366", "modified_by": "Administrator", "module": "Desk", "name": "Workspace", @@ -220,5 +220,6 @@ ], "sort_field": "modified", "sort_order": "DESC", - "states": [] -} + "states": [], + "track_changes": 1 +} \ No newline at end of file From b8980298944b33445a6bd07fb59e9f34c7ca3f13 Mon Sep 17 00:00:00 2001 From: Andrew McLeod Date: Tue, 11 Apr 2023 10:15:35 +0100 Subject: [PATCH 31/61] fix(workflow): add whitelisted functions permissions checks (#20575) Add permissions checks to workflow.py whitelisted functions. --- frappe/workflow/doctype/workflow/workflow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index 9ab903b518..1b26d0c771 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -126,6 +126,7 @@ class Workflow(Document): @frappe.whitelist() def get_fieldnames_for(doctype): + frappe.has_permission(doctype=doctype, ptype='read', throw=True) return [ f.fieldname for f in frappe.get_meta(doctype).fields if f.fieldname not in no_value_fields ] @@ -133,6 +134,7 @@ def get_fieldnames_for(doctype): @frappe.whitelist() def get_workflow_state_count(doctype, workflow_state_field, states): + frappe.has_permission(doctype=doctype, ptype='read', throw=True) states = frappe.parse_json(states) result = frappe.get_all( doctype, From 82ec29702e61ac6f36398f2045b94cd9d78a526f Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Tue, 11 Apr 2023 16:24:47 +0530 Subject: [PATCH 32/61] fix: consider user email if send me a copy is checked (#20627) * fix: consider user email if send me a copy is checked * test(communication): test cc with include sender --- frappe/core/doctype/communication/mixins.py | 13 ++++++--- .../communication/test_communication.py | 29 ++++++++++++++----- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/frappe/core/doctype/communication/mixins.py b/frappe/core/doctype/communication/mixins.py index 24b6a8fafb..73e94fad09 100644 --- a/frappe/core/doctype/communication/mixins.py +++ b/frappe/core/doctype/communication/mixins.py @@ -66,11 +66,16 @@ class CommunicationEmailMixin: cc = self.cc_list() - # Need to inform parent document owner incase communication is created through inbound mail if include_sender: - cc.append(self.sender_mailid) + sender = self.sender_mailid + # if user has selected send_me_a_copy, use their email as sender + if frappe.session.user not in frappe.STANDARD_USERS: + sender = frappe.db.get_value("User", frappe.session.user, "email") + cc.append(sender) + if is_inbound_mail_communcation: - if (doc_owner := self.get_owner()) and (doc_owner not in frappe.STANDARD_USERS): + # inform parent document owner incase communication is created through inbound mail + if doc_owner := self.get_owner(): cc.append(doc_owner) cc = set(cc) - {self.sender_mailid} cc.update(self.get_assignees()) @@ -82,7 +87,7 @@ class CommunicationEmailMixin: if is_inbound_mail_communcation: cc = cc - set(self.cc_list() + self.to_list()) - self._final_cc = [m for m in cc if m not in frappe.STANDARD_USERS] + self._final_cc = [m for m in cc if m and m not in frappe.STANDARD_USERS] return self._final_cc def get_mail_cc_with_displayname(self, is_inbound_mail_communcation=False, include_sender=False): diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py index 04e57f10cf..7f2d36d60a 100644 --- a/frappe/core/doctype/communication/test_communication.py +++ b/frappe/core/doctype/communication/test_communication.py @@ -308,6 +308,7 @@ class TestCommunicationEmailMixin(FrappeTestCase): "recipients": recipients, "cc": cc, "bcc": bcc, + "sender": "sender@test.com", } ).insert(ignore_permissions=True) @@ -327,14 +328,26 @@ class TestCommunicationEmailMixin(FrappeTestCase): comm.delete() def test_cc(self): - to_list = ["to@test.com"] - cc_list = ["cc+1@test.com", "cc ", "to@test.com"] - user = self.new_user(email="cc+1@test.com", thread_notify=0) - comm = self.new_communication(recipients=to_list, cc=cc_list) - res = comm.get_mail_cc_with_displayname() - self.assertCountEqual(res, ["cc "]) - user.delete() - comm.delete() + def test(assertion, cc_list=None, set_user_as=None, include_sender=False, thread_notify=False): + if set_user_as: + frappe.set_user(set_user_as) + + user = self.new_user(email="cc+1@test.com", thread_notify=thread_notify) + comm = self.new_communication(recipients=["to@test.com"], cc=cc_list) + res = comm.get_mail_cc_with_displayname(include_sender=include_sender) + + frappe.set_user("Administrator") + user.delete() + comm.delete() + + self.assertEqual(res, assertion) + + # test filter_thread_notification_disbled_users and filter_mail_recipients + test(["cc "], cc_list=["cc+1@test.com", "cc ", "to@test.com"]) + + # test include_sender + test(["sender@test.com"], include_sender=True, thread_notify=True) + test(["cc+1@test.com"], include_sender=True, thread_notify=True, set_user_as="cc+1@test.com") def test_bcc(self): bcc_list = [ From d5a9f198ec62821a383d4a70869e00cb80bc1d79 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 11 Apr 2023 18:08:19 +0530 Subject: [PATCH 33/61] refactor: set docfield options without method (#20653) --- frappe/workflow/doctype/workflow/workflow.js | 24 ++++++++++++-------- frappe/workflow/doctype/workflow/workflow.py | 8 ------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index dd9fa4798b..6abdf9eb09 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -40,17 +40,21 @@ frappe.ui.form.on("Workflow", { }, update_field_options: function (frm) { var doc = frm.doc; - if (doc.document_type) { - const get_field_method = - "frappe.workflow.doctype.workflow.workflow.get_fieldnames_for"; - frappe.xcall(get_field_method, { doctype: doc.document_type }).then((resp) => { - frm.fields_dict.states.grid.update_docfield_property( - "update_field", - "options", - [""].concat(resp) - ); - }); + if (!doc.document_type) { + return; } + frappe.model.with_doctype(doc.document_type, () => { + const fieldnames = frappe + .get_meta(doc.document_type) + .fields.filter((field) => !frappe.model.no_value_type.includes(field.fieldtype)) + .map((field) => field.fieldname); + + frm.fields_dict.states.grid.update_docfield_property( + "update_field", + "options", + [""].concat(fieldnames) + ); + }); }, create_warning_dialog: function (frm) { const warning_html = `

diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index 1b26d0c771..54c8c6f61b 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -124,14 +124,6 @@ class Workflow(Document): ) -@frappe.whitelist() -def get_fieldnames_for(doctype): - frappe.has_permission(doctype=doctype, ptype='read', throw=True) - return [ - f.fieldname for f in frappe.get_meta(doctype).fields if f.fieldname not in no_value_fields - ] - - @frappe.whitelist() def get_workflow_state_count(doctype, workflow_state_field, states): frappe.has_permission(doctype=doctype, ptype='read', throw=True) From 9f1feaab025ea386d94e7ebbed51779cf2d1eb79 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 11 Apr 2023 15:44:03 +0200 Subject: [PATCH 34/61] refactor: pretty_date --- frappe/utils/data.py | 43 ++++--------------------------------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index d3ffb8b749..f2bcc8e4df 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1518,51 +1518,16 @@ def pretty_date(iso_datetime: datetime.datetime | str) -> str: long ago the date represents. Ported from PrettyDate by John Resig """ - from frappe import _ - if not iso_datetime: return "" - import math + + from babel.dates import format_timedelta if isinstance(iso_datetime, str): iso_datetime = datetime.datetime.strptime(iso_datetime, DATETIME_FORMAT) now_dt = datetime.datetime.strptime(now(), DATETIME_FORMAT) - dt_diff = now_dt - iso_datetime - - # available only in python 2.7+ - # dt_diff_seconds = dt_diff.total_seconds() - - dt_diff_seconds = dt_diff.days * 86400.0 + dt_diff.seconds - - dt_diff_days = math.floor(dt_diff_seconds / 86400.0) - - # differnt cases - if dt_diff_seconds < 60.0: - return _("just now") - elif dt_diff_seconds < 120.0: - return _("1 minute ago") - elif dt_diff_seconds < 3600.0: - return _("{0} minutes ago").format(cint(math.floor(dt_diff_seconds / 60.0))) - elif dt_diff_seconds < 7200.0: - return _("1 hour ago") - elif dt_diff_seconds < 86400.0: - return _("{0} hours ago").format(cint(math.floor(dt_diff_seconds / 3600.0))) - elif dt_diff_days == 1.0: - return _("Yesterday") - elif dt_diff_days < 7.0: - return _("{0} days ago").format(cint(dt_diff_days)) - elif dt_diff_days < 14: - return _("1 week ago") - elif dt_diff_days < 31.0: - return _("{0} weeks ago").format(dt_diff_days // 7) - elif dt_diff_days < 61.0: - return _("1 month ago") - elif dt_diff_days < 365.0: - return _("{0} months ago").format(dt_diff_days // 30) - elif dt_diff_days < 730.0: - return _("1 year ago") - else: - return _("{0} years ago").format(dt_diff_days // 365) + locale = frappe.local.lang.replace("-", "_") if frappe.local.lang else None + return format_timedelta(iso_datetime - now_dt, add_direction=True, locale=locale) def comma_or(some_list, add_quotes=True): From fcb705b41da51230e280df00c1aad08ed1f83f5a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 11 Apr 2023 15:50:17 +0200 Subject: [PATCH 35/61] chore: docstring for pretty_date --- frappe/utils/data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index f2bcc8e4df..a51cdee04a 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1514,9 +1514,9 @@ def escape_html(text: str) -> str: def pretty_date(iso_datetime: datetime.datetime | str) -> str: """ - Takes an ISO time and returns a string representing how - long ago the date represents. - Ported from PrettyDate by John Resig + Return a localized string representation of the delta to the current system time. + + For example, "1 hour ago", "2 days ago", "in 5 seconds", etc. """ if not iso_datetime: return "" From 44bb745035d6e558d8878b4a582ef78fd4346a3a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 11 Apr 2023 15:56:05 +0200 Subject: [PATCH 36/61] fix: change pretty date expectations in test --- frappe/tests/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py index dce2a159ac..4b362e7b47 100644 --- a/frappe/tests/test_utils.py +++ b/frappe/tests/test_utils.py @@ -611,12 +611,12 @@ class TestDateUtils(FrappeTestCase): now = get_datetime() test_cases = { - now: _("just now"), + now: _("1 second ago"), add_to_date(now, minutes=-1): _("1 minute ago"), add_to_date(now, minutes=-3): _("3 minutes ago"), add_to_date(now, hours=-1): _("1 hour ago"), add_to_date(now, hours=-2): _("2 hours ago"), - add_to_date(now, days=-1): _("Yesterday"), + add_to_date(now, days=-1): _("1 day ago"), add_to_date(now, days=-5): _("5 days ago"), add_to_date(now, days=-8): _("1 week ago"), add_to_date(now, days=-14): _("2 weeks ago"), From a1396349fe798d4414fb95cc00f6437918289815 Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Wed, 12 Apr 2023 00:00:53 +0530 Subject: [PATCH 37/61] chore: translate successful redirection message in web_form --- frappe/website/doctype/web_form/templates/web_form.html | 7 ++----- 1 file changed, 2 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 f02acd5b07..9caf1983b3 100644 --- a/frappe/website/doctype/web_form/templates/web_form.html +++ b/frappe/website/doctype/web_form/templates/web_form.html @@ -137,11 +137,8 @@ {% if success_url %}

- Click on this - {{_("URL")}} - if you are not redirected within - 5 - seconds. + {% set success_link = "link".format(success_url) %} + {{ _("Click on this {0} if you are not redirected within 5 seconds").format(success_link) }}

{% else %} From 66df0e9abcc62e1a538af20a9ff5ef16dc9f3109 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 12 Apr 2023 10:53:34 +0530 Subject: [PATCH 38/61] fix: update property if field exist --- frappe/public/js/frappe/form/grid.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index ee3b4508bf..69378d3c30 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -1189,7 +1189,10 @@ export default class Grid { this.docfields.find((d) => d.fieldname === fieldname)[property] = value; if (this.user_defined_columns && this.user_defined_columns.length > 0) { - this.user_defined_columns.find((d) => d.fieldname === fieldname)[property] = value; + let field = this.user_defined_columns.find((d) => d.fieldname === fieldname); + if (field && Object.keys(field).includes(property)) { + field[property] = value; + } } this.debounced_refresh(); From c5f36a49799658655fd4c0ec64692120e2a2817b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 12 Apr 2023 10:56:05 +0530 Subject: [PATCH 39/61] fix(UX): workspace breadcrumbs based on history (#20529) * fix(UX): Resolve breadcrumb conflicts from history Same report can be part of 2 workspace, in which case use breadcrumbs from last workspace. * fix: make sure last workspace belongs to same module at least --- frappe/boot.py | 2 +- frappe/desk/doctype/workspace/workspace.py | 19 +++++-- frappe/public/js/frappe/views/breadcrumbs.js | 54 ++++++++++++++------ 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 0cab7a060c..83c9902020 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -130,7 +130,7 @@ def load_desktop_data(bootinfo): from frappe.desk.desktop import get_workspace_sidebar_items bootinfo.allowed_workspaces = get_workspace_sidebar_items().get("pages") - bootinfo.module_page_map = get_controller("Workspace").get_module_page_map() + bootinfo.module_wise_workspaces = get_controller("Workspace").get_module_wise_workspaces() bootinfo.dashboards = frappe.get_all("Dashboard") diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index f7d9e8ac3e..0866795538 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -1,6 +1,7 @@ # Copyright (c) 2020, Frappe Technologies and contributors # License: MIT. See LICENSE +from collections import defaultdict from json import loads import frappe @@ -49,12 +50,22 @@ class Workspace(Document): delete_folder(self.module, "Workspace", self.title) @staticmethod - def get_module_page_map(): - pages = frappe.get_all( - "Workspace", fields=["name", "module"], filters={"for_user": ""}, as_list=1 + def get_module_wise_workspaces(): + workspaces = frappe.get_all( + "Workspace", + fields=["name", "module"], + filters={"for_user": "", "public": 1}, + order_by="creation", ) - return {page[1]: page[0] for page in pages if page[1]} + module_workspaces = defaultdict(list) + + for workspace in workspaces: + if not workspace.module: + continue + module_workspaces[workspace.module].append(workspace.name) + + return module_workspaces def get_link_groups(self): cards = [] diff --git a/frappe/public/js/frappe/views/breadcrumbs.js b/frappe/public/js/frappe/views/breadcrumbs.js index 74560cebbc..9b1107de1c 100644 --- a/frappe/public/js/frappe/views/breadcrumbs.js +++ b/frappe/public/js/frappe/views/breadcrumbs.js @@ -82,25 +82,33 @@ frappe.breadcrumbs = { this.$breadcrumbs.append(html); }, + get last_route() { + return frappe.route_history.slice(-2)[0]; + }, + set_workspace_breadcrumb(breadcrumbs) { - // get preferred module for breadcrumbs, based on sent via module + // get preferred module for breadcrumbs, based on history and module if (!breadcrumbs.workspace) { this.set_workspace(breadcrumbs); } - - if (breadcrumbs.workspace) { - if ( - !breadcrumbs.module_info.blocked && - frappe.visible_modules.includes(breadcrumbs.module_info.module) - ) { - $( - `
  • ${__( - breadcrumbs.workspace - )}
  • ` - ).appendTo(this.$breadcrumbs); - } + if (!breadcrumbs.workspace) { + return; } + + if ( + breadcrumbs.module_info && + (breadcrumbs.module_info.blocked || + !frappe.visible_modules.includes(breadcrumbs.module_info.module)) + ) { + return; + } + + $( + `
  • ${__( + breadcrumbs.workspace + )}
  • ` + ).appendTo(this.$breadcrumbs); }, set_workspace(breadcrumbs) { @@ -117,6 +125,19 @@ frappe.breadcrumbs = { breadcrumbs.module = this.preferred[breadcrumbs.doctype]; } + // guess from last route + if (this.last_route?.[0] == "Workspaces") { + let last_workspace = this.last_route[1]; + + if ( + breadcrumbs.module && + frappe.boot.module_wise_workspaces[breadcrumbs.module]?.includes(last_workspace) + ) { + breadcrumbs.workspace = last_workspace; + return; + } + } + if (breadcrumbs.module) { if (this.module_map[breadcrumbs.module]) { breadcrumbs.module = this.module_map[breadcrumbs.module]; @@ -125,8 +146,11 @@ frappe.breadcrumbs = { breadcrumbs.module_info = frappe.get_module(breadcrumbs.module); // set workspace - if (breadcrumbs.module_info && frappe.boot.module_page_map[breadcrumbs.module]) { - breadcrumbs.workspace = frappe.boot.module_page_map[breadcrumbs.module]; + if ( + breadcrumbs.module_info && + frappe.boot.module_wise_workspaces[breadcrumbs.module] + ) { + breadcrumbs.workspace = frappe.boot.module_wise_workspaces[breadcrumbs.module][0]; } } }, From 3e95c00fd0c01dd1e970c79297fa46432d576e16 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 12 Apr 2023 11:51:24 +0530 Subject: [PATCH 40/61] fix: dont track webhook request log (#20663) logs dont change [skip ci] --- .../doctype/webhook_request_log/webhook_request_log.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json b/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json index ed5201df1f..676df48f3b 100644 --- a/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json +++ b/frappe/integrations/doctype/webhook_request_log/webhook_request_log.json @@ -79,7 +79,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2023-02-24 14:59:24.743552", + "modified": "2023-04-12 11:50:01.702862", "modified_by": "Administrator", "module": "Integrations", "name": "Webhook Request Log", @@ -101,6 +101,5 @@ ], "sort_field": "modified", "sort_order": "DESC", - "states": [], - "track_changes": 1 + "states": [] } \ No newline at end of file From c7632d0e51b4092e0138fbb48d1ef3d26112c616 Mon Sep 17 00:00:00 2001 From: phot0n Date: Wed, 12 Apr 2023 13:20:32 +0530 Subject: [PATCH 41/61] fix: use frappe.log_error in EmailServer exception handling --- frappe/email/receive.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 59d41b543f..27b8867f84 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -151,7 +151,7 @@ class EmailServer: except _socket.error: # log performs rollback and logs error in Error Log - self.log_error("POP: Unable to connect") + frappe.log_error("POP: Unable to connect") # Invalid mail server -- due to refusing connection frappe.msgprint(_("Invalid Mail Server. Please rectify and try again.")) @@ -332,7 +332,7 @@ class EmailServer: else: # log performs rollback and logs error in Error Log - self.log_error("Unable to fetch email", self.make_error_msg(msg_num, incoming_mail)) + frappe.log_error("Unable to fetch email", self.make_error_msg(msg_num, incoming_mail)) self.errors = True frappe.db.rollback() From f46d1aefa9fce9f5ef04e6712a85a41f9bdfbb35 Mon Sep 17 00:00:00 2001 From: Mohammad Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com> Date: Wed, 12 Apr 2023 13:38:23 +0530 Subject: [PATCH 42/61] refactor: use urljoin to build picture url (#20664) --- frappe/oauth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/oauth.py b/frappe/oauth.py index 8099bdab45..8955078342 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -3,7 +3,7 @@ import datetime import hashlib import re from http import cookies -from urllib.parse import unquote, urlparse +from urllib.parse import unquote, urlparse, urljoin import jwt import pytz @@ -575,7 +575,7 @@ def get_userinfo(user): if frappe.utils.validate_url(user.user_image, valid_schemes=valid_url_schemes): picture = user.user_image else: - picture = frappe_server_url + "/" + user.user_image + picture = urljoin(frappe_server_url, user.user_image) userinfo = frappe._dict( { From 398fe0d84f6bc8407d088b8964fdc757b0228f9d Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 12 Apr 2023 16:34:20 +0530 Subject: [PATCH 43/61] fix: group buttons are overlapping --- frappe/public/scss/common/css_variables.scss | 2 ++ frappe/public/scss/desk/dark.scss | 2 ++ frappe/public/scss/desk/global.scss | 15 +++++++++++ frappe/public/scss/desk/list.scss | 27 ++++---------------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/frappe/public/scss/common/css_variables.scss b/frappe/public/scss/common/css_variables.scss index f22b587405..ae53e99518 100644 --- a/frappe/public/scss/common/css_variables.scss +++ b/frappe/public/scss/common/css_variables.scss @@ -243,6 +243,8 @@ $input-height: 28px !default; --highlight-color: var(--gray-50); --yellow-highlight-color: var(--yellow-50); + --btn-group-border-color: var(--gray-300); + --field-placeholder-color: var(--gray-50); --highlight-shadow: 1px 1px 10px var(--blue-50), 0px 0px 4px var(--blue-600); diff --git a/frappe/public/scss/desk/dark.scss b/frappe/public/scss/desk/dark.scss index e1f210c440..ced95d7f69 100644 --- a/frappe/public/scss/desk/dark.scss +++ b/frappe/public/scss/desk/dark.scss @@ -100,6 +100,8 @@ --highlight-color: var(--gray-700); --yellow-highlight-color: var(--yellow-700); + --btn-group-border-color: var(--gray-800); + --field-placeholder-color: var(--gray-700); --highlight-shadow: 1px 1px 10px var(--blue-900), 0px 0px 4px var(--blue-500); diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index cb66de9920..1bf5e37b3e 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -234,6 +234,21 @@ h2 { font-size: var(--text-md); } +.btn-group { + .btn { + box-shadow: none; + outline: 1px solid var(--btn-group-border-color); + + &:not(:first-child) { + margin-left: 1px; + } + + &:focus { + outline: 2px solid var(--dark-border-color); + } + } +} + .btn-xs { @extend .btn-sm; line-height: 1.2; diff --git a/frappe/public/scss/desk/list.scss b/frappe/public/scss/desk/list.scss index 31d1661abb..30bf1d6499 100644 --- a/frappe/public/scss/desk/list.scss +++ b/frappe/public/scss/desk/list.scss @@ -189,29 +189,12 @@ $level-margin-right: 8px; .list-paging-area, .footnote-area { border-top: 1px solid var(--border-color); - .btn-group { - box-shadow: var(--drop-shadow); - border-radius: var(--border-radius-md); - - &> .btn:nth-child(2) { - border-left: none; - border-right: none; - } - - .btn-paging { - box-shadow: none; - margin-left: 0px !important; - border: 1px solid var(--dark-border-color); - - &.btn-info { - background-color: var(--gray-600); - border-color: var(--gray-600); - color: var(--white); - font-weight: var(--text-bold); - } - } + .btn-group .btn-paging.btn-info { + background-color: var(--gray-600); + border-color: var(--gray-600); + color: var(--white); + font-weight: var(--text-bold); } - } .frappe-card { From 0cab0b830da637984f46bfa4af246d3c9098eca0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 13 Apr 2023 12:19:08 +0530 Subject: [PATCH 44/61] refactor: replace imghdr with filetype (#20680) * refactor: replace `imaghdr` with `filetype` ``` 11:52:06 worker.1 | /home/ankush/benches/develop/apps/frappe/frappe/core/doctype/file/utils.py:2: DeprecationWarning: 'imghdr' is deprecated and slated for removal in Python 3.13 ``` * feat: improved extension guessing using filecontent --- frappe/core/doctype/file/test_file.py | 8 ++++++++ frappe/core/doctype/file/utils.py | 10 ++++++---- frappe/oauth.py | 2 +- frappe/workflow/doctype/workflow/workflow.py | 2 +- pyproject.toml | 1 + 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 86bd69eb5f..57c9d9a11f 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -17,6 +17,7 @@ from frappe.core.api.file import ( move_file, unzip_file, ) +from frappe.core.doctype.file.utils import get_extension from frappe.exceptions import ValidationError from frappe.tests.utils import FrappeTestCase from frappe.utils import get_files_path @@ -739,3 +740,10 @@ class TestFileOptimization(FrappeTestCase): size_after_rollback = os.stat(image_path).st_size self.assertEqual(size_before_optimization, size_after_rollback) + + def test_image_header_guessing(self): + file_path = frappe.get_app_path("frappe", "tests/data/sample_image_for_optimization.jpg") + with open(file_path, "rb") as f: + file_content = f.read() + + self.assertEqual(get_extension("", None, file_content), "jpeg") diff --git a/frappe/core/doctype/file/utils.py b/frappe/core/doctype/file/utils.py index 17a092e340..1d0d145303 100644 --- a/frappe/core/doctype/file/utils.py +++ b/frappe/core/doctype/file/utils.py @@ -1,5 +1,4 @@ import hashlib -import imghdr import mimetypes import os import re @@ -7,6 +6,7 @@ from io import BytesIO from typing import TYPE_CHECKING, Optional from urllib.parse import unquote +import filetype import requests import requests.exceptions from PIL import Image @@ -76,9 +76,11 @@ def get_extension( mimetype = mimetypes.guess_type(filename + "." + extn)[0] - if mimetype is None or not mimetype.startswith("image/") and content: - # detect file extension by reading image header properties - extn = imghdr.what(filename + "." + (extn or ""), h=content) + if mimetype is None and extn is None and content: + # detect file extension by using filetype matchers + _type_info = filetype.match(content) + if _type_info: + extn = _type_info.extension return extn diff --git a/frappe/oauth.py b/frappe/oauth.py index 8955078342..2d25b5dfb5 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -3,7 +3,7 @@ import datetime import hashlib import re from http import cookies -from urllib.parse import unquote, urlparse, urljoin +from urllib.parse import unquote, urljoin, urlparse import jwt import pytz diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index 54c8c6f61b..018b567ee9 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -126,7 +126,7 @@ class Workflow(Document): @frappe.whitelist() def get_workflow_state_count(doctype, workflow_state_field, states): - frappe.has_permission(doctype=doctype, ptype='read', throw=True) + frappe.has_permission(doctype=doctype, ptype="read", throw=True) states = frappe.parse_json(states) result = frappe.get_all( doctype, diff --git a/pyproject.toml b/pyproject.toml index daa0748e5f..5429682a33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "Babel~=2.12.1", "Click~=8.1.3", "filelock~=3.8.0", + "filetype~=1.2.0", "GitPython~=3.1.30", "Jinja2~=3.1.2", "Pillow~=9.3.0", From ac351166b24782066c5ee22bb09c3a00d2083fb7 Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Thu, 13 Apr 2023 12:36:14 +0530 Subject: [PATCH 45/61] fix: pull from email account every 10 mins instead of 4 mins (by default) (#20675) --- frappe/hooks.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index effc84a873..b055f9dc8e 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -186,11 +186,13 @@ scheduler_events = { "frappe.oauth.delete_oauth2_data", "frappe.website.doctype.web_page.web_page.check_publish_status", "frappe.twofactor.delete_all_barcodes_for_users", - ] + ], + "0/10 * * * *": [ + "frappe.email.doctype.email_account.email_account.pull", + ], }, "all": [ "frappe.email.queue.flush", - "frappe.email.doctype.email_account.email_account.pull", "frappe.email.doctype.email_account.email_account.notify_unreplied", "frappe.utils.global_search.sync_global_search", "frappe.monitor.flush", From cbb7c4a91cdf84c88629deda1d740ba3c7df2eea Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 13 Apr 2023 12:46:27 +0530 Subject: [PATCH 46/61] test: fix imghdr test --- frappe/core/doctype/file/test_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 57c9d9a11f..51e065f710 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -462,7 +462,7 @@ class TestFile(FrappeTestCase): ).insert(ignore_permissions=True) test_file.make_thumbnail() - self.assertTrue(test_file.thumbnail_url.endswith("_small.jpeg")) + self.assertTrue(test_file.thumbnail_url.endswith("_small.jpg")) # test local image test_file.db_set("thumbnail_url", None) @@ -746,4 +746,4 @@ class TestFileOptimization(FrappeTestCase): with open(file_path, "rb") as f: file_content = f.read() - self.assertEqual(get_extension("", None, file_content), "jpeg") + self.assertEqual(get_extension("", None, file_content), "jpg") From 6b2b1618760dc39e4cb50ecfa48a4c25ef39564f Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 13 Apr 2023 14:11:52 +0530 Subject: [PATCH 47/61] fix: filter with capitalized operator was getting bypassed --- frappe/desk/reportview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 00ae27d145..b450b734e9 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -678,7 +678,7 @@ def get_filters_cond( for f in filters: if isinstance(f[1], str) and f[1][0] == "!": flt.append([doctype, f[0], "!=", f[1][1:]]) - elif isinstance(f[1], (list, tuple)) and f[1][0] in ( + elif isinstance(f[1], (list, tuple)) and f[1][0].lower() in ( ">", "<", ">=", From b62bb8b0ecfeee5ffd2a1bba3a936c99d1e42ca3 Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Thu, 13 Apr 2023 17:42:21 +0530 Subject: [PATCH 48/61] fix: allow filter values to be saved in custom report (#20623) --- frappe/core/doctype/report/report.json | 6 ++++- frappe/core/doctype/report/report.py | 2 +- frappe/core/doctype/report/test_report.py | 5 ++-- frappe/desk/query_report.py | 18 ++++++++++--- .../js/frappe/views/reports/query_report.js | 26 +++++++++++++++++-- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index 37dce73dda..9b6b04afcc 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -148,11 +148,13 @@ { "collapsible": 1, "collapsible_depends_on": "filters", + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "filters_section", "fieldtype": "Section Break", "label": "Filters" }, { + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "filters", "fieldtype": "Table", "label": "Filters", @@ -161,11 +163,13 @@ { "collapsible": 1, "collapsible_depends_on": "columns", + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "columns_section", "fieldtype": "Section Break", "label": "Columns" }, { + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "columns", "fieldtype": "Table", "label": "Columns", @@ -182,7 +186,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2022-11-20 14:56:36.578412", + "modified": "2023-04-07 18:18:11.782178", "modified_by": "Administrator", "module": "Core", "name": "Report", diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index ef38387e57..ca1e7724c1 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -169,7 +169,7 @@ class Report(Document): return columns, result - def run_query_report(self, filters, user, ignore_prepared_report=False): + def run_query_report(self, filters=None, user=None, ignore_prepared_report=False): columns, result = [], [] data = frappe.desk.query_report.run( self.name, filters=filters, user=user, ignore_prepared_report=ignore_prepared_report diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 0e1ed80eda..670b6b7410 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -118,11 +118,10 @@ class TestReport(FrappeTestCase): } ] ), + json.dumps({"user": "Administrator", "doctype": "User"}), ) custom_report = frappe.get_doc("Report", custom_report_name) - columns, result = custom_report.run_query_report( - filters={"user": "Administrator", "doctype": "User"}, user=frappe.session.user - ) + columns, result = custom_report.run_query_report(user=frappe.session.user) self.assertListEqual(["email"], [column.get("fieldname") for column in columns]) admin_dict = frappe.core.utils.find(result, lambda d: d["name"] == "Administrator") diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index bb845cae95..3f906d8f12 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -15,12 +15,13 @@ from frappe.model.utils import render_include from frappe.modules import get_module_path, scrub from frappe.monitor import add_data_to_monitor from frappe.permissions import get_role_permissions -from frappe.utils import cint, cstr, flt, format_duration, get_html_format +from frappe.utils import cint, cstr, flt, format_duration, get_html_format, sbool def get_report_doc(report_name): doc = frappe.get_doc("Report", report_name) doc.custom_columns = [] + doc.custom_filters = [] if doc.report_type == "Custom Report": custom_report_doc = doc @@ -30,7 +31,8 @@ def get_report_doc(report_name): if custom_report_doc.json: data = json.loads(custom_report_doc.json) if data: - doc.custom_columns = data["columns"] + doc.custom_columns = data.get("columns") + doc.custom_filters = data.get("filters") doc.is_custom_report = True if not doc.is_permitted(): @@ -182,6 +184,7 @@ def run( custom_columns=None, is_tree=False, parent_field=None, + are_default_filters=True, ): report = get_report_doc(report_name) if not user: @@ -194,6 +197,9 @@ def run( result = None + if sbool(are_default_filters) and report.custom_filters: + filters = report.custom_filters + if report.prepared_report and not ignore_prepared_report and not custom_columns: if filters: if isinstance(filters, str): @@ -209,6 +215,9 @@ def run( result["add_total_row"] = report.add_total_row and not result.get("skip_total_row", False) + if sbool(are_default_filters) and report.custom_filters: + result["custom_filters"] = report.custom_filters + return result @@ -463,7 +472,7 @@ def get_data_for_custom_report(columns): @frappe.whitelist() -def save_report(reference_report, report_name, columns): +def save_report(reference_report, report_name, columns, filters): report_doc = get_report_doc(reference_report) docname = frappe.db.exists( @@ -479,6 +488,7 @@ def save_report(reference_report, report_name, columns): report = frappe.get_doc("Report", docname) existing_jd = json.loads(report.json) existing_jd["columns"] = json.loads(columns) + existing_jd["filters"] = json.loads(filters) report.update({"json": json.dumps(existing_jd, separators=(",", ":"))}) report.save() frappe.msgprint(_("Report updated successfully")) @@ -489,7 +499,7 @@ def save_report(reference_report, report_name, columns): { "doctype": "Report", "report_name": report_name, - "json": f'{{"columns":{columns}}}', + "json": f'{{"columns":{columns},"filters":{filters}}}', "ref_doctype": report_doc.ref_doctype, "is_standard": "No", "report_type": "Custom Report", diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index d877f47f21..ee255032bb 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -542,7 +542,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (this.prepared_report) { this.reset_report_view(); } else if (!this._no_refresh) { - this.refresh(); + this.refresh(true); } } }; @@ -598,10 +598,25 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.page.clear_fields(); } - refresh() { + refresh(have_filters_changed) { this.toggle_message(true); this.toggle_report(false); let filters = this.get_filter_values(true); + + // for custom reports, + // are_default_filters is true if the filters haven't been modified and for all filters, + // the filter value is the default value or there's no default value for the filter and the current value is empty. + // are_default_filters is false otherwise. + + let are_default_filters = this.filters + .map((filter) => { + return ( + !have_filters_changed && + (filter.default === filter.value || (!filter.default && !filter.value)) + ); + }) + .every((res) => res === true); + this.show_loading_screen(); // only one refresh at a time @@ -624,6 +639,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { filters: filters, is_tree: this.report_settings.tree, parent_field: this.report_settings.parent_field, + are_default_filters: are_default_filters, }, callback: resolve, always: () => this.page.btn_secondary.prop("disabled", false), @@ -636,6 +652,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.execution_time = data.execution_time || 0.1; + if (data.custom_filters) { + this.set_filters(data.custom_filters); + this.previous_filters = data.custom_filters; + } + if (data.prepared_report) { this.prepared_report = true; this.prepared_report_document = data.doc; @@ -1715,6 +1736,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { reference_report: this.report_name, report_name: values.report_name, columns: this.get_visible_columns(), + filters: this.get_filter_values(), }, callback: function (r) { this.show_save = false; From 652202132d4e1d86fe12585541ccee65385c4402 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Thu, 13 Apr 2023 14:37:28 +0200 Subject: [PATCH 49/61] fix: remove guest permission from language (#20677) * fix: remove guest permission from language * fix: allow "All" to select a Language * fix: allow "All" to read a Language --- frappe/core/doctype/language/language.json | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/frappe/core/doctype/language/language.json b/frappe/core/doctype/language/language.json index 7e9bbb1038..c9110bb998 100644 --- a/frappe/core/doctype/language/language.json +++ b/frappe/core/doctype/language/language.json @@ -51,7 +51,7 @@ "icon": "fa fa-globe", "in_create": 1, "links": [], - "modified": "2022-08-14 18:54:03.490836", + "modified": "2023-04-13 13:48:38.127995", "modified_by": "Administrator", "module": "Core", "name": "Language", @@ -66,13 +66,8 @@ "write": 1 }, { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Guest", - "share": 1 + "role": "All", + "read": 1 } ], "search_fields": "language_name", From 76e576c83ec4a686998a356e6cca8f91b79110c0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 14 Apr 2023 09:07:00 +0530 Subject: [PATCH 50/61] test: fix print view test from lang --- frappe/tests/test_website.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index 7af2bfda8e..01f6e4f7cc 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -236,6 +236,7 @@ class TestWebsite(FrappeTestCase): def test_printview_page(self): frappe.db.value_cache[("DocType", "Language", "name")] = (("Language",),) + frappe.set_user("Administrator") content = get_response_content("/Language/ru") self.assertIn('