From 2a80bb01ac1a41d5e5fcd70c7a37f62c5c93e8a9 Mon Sep 17 00:00:00 2001
From: "Patrick.St" <72972659+pstuhlmueller@users.noreply.github.com>
Date: Tue, 21 Feb 2023 16:13:07 +0100
Subject: [PATCH 001/120] fix: Incorrect use of the Walrus operator
Incorrect use of the Walrus operator leads to unintended behavior for if-condition: "None" will be appended to cc.
---
frappe/core/doctype/communication/mixins.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/core/doctype/communication/mixins.py b/frappe/core/doctype/communication/mixins.py
index 7b6427d1c2..7b34208019 100644
--- a/frappe/core/doctype/communication/mixins.py
+++ b/frappe/core/doctype/communication/mixins.py
@@ -70,7 +70,7 @@ class CommunicationEmailMixin:
if include_sender:
cc.append(self.sender_mailid)
if is_inbound_mail_communcation:
- if (doc_owner := self.get_owner()) not in frappe.STANDARD_USERS:
+ if (doc_owner := self.get_owner()) and (doc_owner not in frappe.STANDARD_USERS):
cc.append(doc_owner)
cc = set(cc) - {self.sender_mailid}
cc.update(self.get_assignees())
From 841557338b69d455a683ce4f02e8c76a12016b49 Mon Sep 17 00:00:00 2001
From: "Patrick.St" <72972659+pstuhlmueller@users.noreply.github.com>
Date: Tue, 21 Feb 2023 16:24:02 +0100
Subject: [PATCH 002/120] fix: sending mails to unintended recipients as cc
Security vulnerability: Unintentionally, all incoming emails are sent as CC to all users in a ToDo as "allocated_to" with the status "Open"
---
frappe/core/doctype/communication/mixins.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/frappe/core/doctype/communication/mixins.py b/frappe/core/doctype/communication/mixins.py
index 7b34208019..22b7e8a0fc 100644
--- a/frappe/core/doctype/communication/mixins.py
+++ b/frappe/core/doctype/communication/mixins.py
@@ -216,7 +216,11 @@ class CommunicationEmailMixin:
"reference_name": self.reference_name,
"reference_type": self.reference_doctype,
}
- return ToDo.get_owners(filters)
+
+ if self.reference_doctype == "ToDo" and self.reference_name != None:
+ return ToDo.get_owners(filters)
+ else:
+ return []
@staticmethod
def filter_thread_notification_disbled_users(emails):
From 735e50e7621bd5465e806f22ad2d849be7f48cce Mon Sep 17 00:00:00 2001
From: Florian HENRY
Date: Thu, 9 Mar 2023 11:10:28 +0100
Subject: [PATCH 003/120] chores: update French translation
---
frappe/translations/fr.csv | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/frappe/translations/fr.csv b/frappe/translations/fr.csv
index d27e2cf49b..b2e419dc2b 100644
--- a/frappe/translations/fr.csv
+++ b/frappe/translations/fr.csv
@@ -1370,7 +1370,7 @@ Invalid fieldname '{0}' in autoname,Champ invalide '{0}' dans nom automatique,
Invalid file path: {0},Chemin de fichier invalide : {0},
Invalid login or password,Identifiant ou mot de passe invalide,
Invalid module path,Chemin de module invalide,
-Invalid naming series (. missing),Nom de série invalide (. manquant),
+Invalid naming series (. missing),Masque de numérotation invalide (. manquant),
Invalid payment gateway credentials,Identifiant de la Passerelle de Paiement Invalides,
Invalid recipient address,Adresse de destinataire invalide,
Invalid {0} condition,Condition {0} invalide,
@@ -1601,7 +1601,7 @@ Name of {0} cannot be {1},Nom de {0} ne peut pas être {1},
Names and surnames by themselves are easy to guess.,Les noms et prénoms par eux-mêmes sont faciles à deviner.,
Naming,Nom,
"Naming Options:\n
field:[fieldname] - By Field
naming_series: - By Naming Series (field called naming_series must be present
Prompt - Prompt user for a name
[series] - Series by prefix (separated by a dot); for example PRE.#####
\n
format:EXAMPLE-{MM}morewords{fieldname1}-{fieldname2}-{#####} - Replace all braced words (fieldnames, date words (DD, MM, YY), series) with their value. Outside braces, any characters can be used.
","Options de nommage:
field: [fieldname] - Par champ
naming_series: - En nommant les séries (le champ appelé naming_series doit être présent
Invite - Demander à l'utilisateur un nom
[série] - Série par préfixe (séparé par un point); par exemple PRE. #####
format: EXEMPLE- {MM} morewords {fieldname1} - {fieldname2} - {#####} - Remplace tous les mots accolés (noms de champs, mots de date (JJ, MM, AA), séries) par leur valeur. Des accolades extérieures, des caractères peuvent être utilisés.
",
-Naming Series mandatory,Nom de série obligatoire,
+Naming Series mandatory,Masque de numérotation obligatoire,
Nested set error. Please contact the Administrator.,Erreur d'ensemble imbriqué. Veuillez contacter l'Administrateur.,
New Activity,Nouvelle activité,
New Chat,Nouveau chat,
@@ -3428,7 +3428,7 @@ Me,Moi,
Mention,Mention,
Modules,Modules,
Monthly Long,Long mensuel,
-Naming Series,Nom de série,
+Naming Series,Masque de numérotation,
Navigate Home,Naviguer à l'accueil,
Navigate list down,Naviguer dans la liste,
Navigate list up,Naviguer dans la liste en haut,
@@ -4147,7 +4147,7 @@ Collapse,Réduire,
{0} is not a valid Name,{0} n'est pas un nom valide,
Your system is being updated. Please refresh again after a few moments.,Votre système est en cours de mise à jour. Veuillez actualiser à nouveau après quelques instants.,
{0} {1}: Submitted Record cannot be deleted. You must {2} Cancel {3} it first.,{0} {1}: l'enregistrement validé ne peut pas être supprimé. Vous devez d'abord {2} l'annuler {3}.,
-Invalid naming series (. missing) for {0},Série de noms non valide (. Manquante) pour {0},
+Invalid naming series (. missing) for {0},Masque de numérotation non valide (. Manquante) pour {0},
Error has occurred in {0},Une erreur s'est produite dans {0},
Status Updated,Statut mis à jour,
You can also copy-paste this {0} to your browser,Vous pouvez également copier-coller ce {0} dans votre navigateur,
@@ -4739,3 +4739,8 @@ Auto Reply,Réponse automatique
Footer Content,Contenue du pied de page
Brand Logo,Logo de la marque
Folder Name,Nom du dossier
+Document Naming Settings,Masque de numérotation des documents
+Setup Series for transactions,Paramétrage des masques de numérotation
+Set Naming Series options on your transactions.,Définir les masques de numerotation pour vos documents
+Update Series Counter,Mettre à jour le compteur d'un masque de numérotation
+Change the starting / current sequence number of an existing series. \n\nWarning: Incorrectly updating counters can prevent documents from getting created.,Mettre à jour le compteur d'un masque de numérotation \n\nAttention: Mettre à jour de maniére inconsistance le compteur peux empêcher la création d'un document
From ce5ab746338515a72963b542277279b9274cd841 Mon Sep 17 00:00:00 2001
From: Florian HENRY
Date: Thu, 9 Mar 2023 11:11:04 +0100
Subject: [PATCH 004/120] chores: update French translation
---
frappe/translations/fr.csv | 1 +
1 file changed, 1 insertion(+)
diff --git a/frappe/translations/fr.csv b/frappe/translations/fr.csv
index b2e419dc2b..0f8192b445 100644
--- a/frappe/translations/fr.csv
+++ b/frappe/translations/fr.csv
@@ -4744,3 +4744,4 @@ Setup Series for transactions,Paramétrage des masques de numérotation
Set Naming Series options on your transactions.,Définir les masques de numerotation pour vos documents
Update Series Counter,Mettre à jour le compteur d'un masque de numérotation
Change the starting / current sequence number of an existing series. \n\nWarning: Incorrectly updating counters can prevent documents from getting created.,Mettre à jour le compteur d'un masque de numérotation \n\nAttention: Mettre à jour de maniére inconsistance le compteur peux empêcher la création d'un document
+No Results found,Aucun résultat trouvé
From 8086baff8d7fdef5075da26eba029ef1bacbdb0e Mon Sep 17 00:00:00 2001
From: Florian HENRY
Date: Thu, 9 Mar 2023 12:20:58 +0100
Subject: [PATCH 005/120] chores: update French translation
---
frappe/translations/fr.csv | 2 ++
1 file changed, 2 insertions(+)
diff --git a/frappe/translations/fr.csv b/frappe/translations/fr.csv
index 0f8192b445..a33f7adcef 100644
--- a/frappe/translations/fr.csv
+++ b/frappe/translations/fr.csv
@@ -4745,3 +4745,5 @@ Set Naming Series options on your transactions.,Définir les masques de numerota
Update Series Counter,Mettre à jour le compteur d'un masque de numérotation
Change the starting / current sequence number of an existing series. \n\nWarning: Incorrectly updating counters can prevent documents from getting created.,Mettre à jour le compteur d'un masque de numérotation \n\nAttention: Mettre à jour de maniére inconsistance le compteur peux empêcher la création d'un document
No Results found,Aucun résultat trouvé
+Shelf Life in Days,Durée de conservation (en jours)
+Batch Expiry Date,Date d'expiration du Lot
From 2223f97f888a1de89b87a4b1a9a7a7838ef92e7e Mon Sep 17 00:00:00 2001
From: Devin Slauenwhite
Date: Fri, 10 Mar 2023 01:40:22 -0500
Subject: [PATCH 006/120] fix: print preview when no letterhead is selected
(#20286)
---
frappe/printing/page/print/print.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js
index 54704467c0..f9881fd76e 100644
--- a/frappe/printing/page/print/print.js
+++ b/frappe/printing/page/print/print.js
@@ -657,7 +657,7 @@ frappe.ui.form.PrintView = class {
}
get_letterhead() {
- return this.letterhead_selector.val();
+ return this.letterhead_selector.val() || __("No Letterhead");
}
get_no_preview_html() {
From 43b5d9532a52d2dbcbb57a1e6703ee24d492b10d Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Mon, 27 Feb 2023 16:30:31 +0530
Subject: [PATCH 007/120] fix!: create property setters for system generated
custom fields
---
.../doctype/custom_field/custom_field.js | 21 ++++++++++++++++
.../doctype/customize_form/customize_form.js | 19 +++++++++++---
.../doctype/customize_form/customize_form.py | 25 ++++++++++++-------
.../customize_form/test_customize_form.py | 22 ++++++++++++++++
4 files changed, 75 insertions(+), 12 deletions(-)
diff --git a/frappe/custom/doctype/custom_field/custom_field.js b/frappe/custom/doctype/custom_field/custom_field.js
index fba19ca45e..be416cb49a 100644
--- a/frappe/custom/doctype/custom_field/custom_field.js
+++ b/frappe/custom/doctype/custom_field/custom_field.js
@@ -25,6 +25,27 @@ frappe.ui.form.on("Custom Field", {
frm.toggle_enable("dt", frm.doc.__islocal);
frm.trigger("dt");
frm.toggle_reqd("label", !frm.doc.fieldname);
+
+ if (frm.doc.is_system_generated) {
+ frm.dashboard.add_comment(
+ __(
+ "Warning: This field is system generated and may be overwritten by a future update. Modify it using {0} instead.",
+ [
+ frappe.utils.get_form_link(
+ "Customize Form",
+ "Customize Form",
+ true,
+ __("Customize Form"),
+ {
+ doc_type: frm.doc.dt,
+ }
+ ),
+ ]
+ ),
+ "yellow",
+ true
+ );
+ }
},
dt: function (frm) {
if (!frm.doc.dt) {
diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js
index 4ab693b415..dbcc9b17ce 100644
--- a/frappe/custom/doctype/customize_form/customize_form.js
+++ b/frappe/custom/doctype/customize_form/customize_form.js
@@ -251,10 +251,23 @@ frappe.ui.form.on("Customize Form", {
// can't delete standard fields
frappe.ui.form.on("Customize Form Field", {
before_fields_remove: function (frm, doctype, name) {
- var row = frappe.get_doc(doctype, name);
+ let row = frappe.get_doc(doctype, name);
+
+ if (row.is_system_generated) {
+ frappe.throw(
+ __(
+ "Cannot delete system generated field {0}. You can hide it instead.",
+ [__(row.label) || row.fieldname]
+ )
+ );
+ }
+
if (!(row.is_custom_field || row.__islocal)) {
- frappe.msgprint(__("Cannot delete standard field. You can hide it if you want"));
- throw "cannot delete standard field";
+ frappe.throw(
+ __("Cannot delete standard field {0}. You can hide it instead.", [
+ __(row.label) || row.fieldname,
+ ])
+ );
}
},
fields_add: function (frm, cdt, cdn) {
diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py
index bdd18cddfa..42cbf33f4f 100644
--- a/frappe/custom/doctype/customize_form/customize_form.py
+++ b/frappe/custom/doctype/customize_form/customize_form.py
@@ -193,8 +193,9 @@ class CustomizeForm(Document):
# docfield
for df in self.get("fields"):
meta_df = meta.get("fields", {"fieldname": df.fieldname})
- if not meta_df or meta_df[0].get("is_custom_field"):
+ if not meta_df or not is_standard_or_system_generated_field(meta_df[0]):
continue
+
self.set_property_setters_for_docfield(meta, df, meta_df)
# action and links
@@ -350,12 +351,14 @@ class CustomizeForm(Document):
def update_custom_fields(self):
for i, df in enumerate(self.get("fields")):
- if df.get("is_custom_field"):
- if not frappe.db.exists("Custom Field", {"dt": self.doc_type, "fieldname": df.fieldname}):
- self.add_custom_field(df, i)
- self.flags.update_db = True
- else:
- self.update_in_custom_field(df, i)
+ if is_standard_or_system_generated_field(df):
+ continue
+
+ if not frappe.db.exists("Custom Field", {"dt": self.doc_type, "fieldname": df.fieldname}):
+ self.add_custom_field(df, i)
+ self.flags.update_db = True
+ else:
+ self.update_in_custom_field(df, i)
self.delete_custom_fields()
@@ -380,7 +383,7 @@ class CustomizeForm(Document):
def update_in_custom_field(self, df, i):
meta = frappe.get_meta(self.doc_type)
meta_df = meta.get("fields", {"fieldname": df.fieldname})
- if not (meta_df and meta_df[0].get("is_custom_field")):
+ if not meta_df or is_standard_or_system_generated_field(meta_df[0]):
# not a custom field
return
@@ -416,7 +419,7 @@ class CustomizeForm(Document):
}
for fieldname in fields_to_remove:
df = meta.get("fields", {"fieldname": fieldname})[0]
- if df.get("is_custom_field"):
+ if not is_standard_or_system_generated_field(df):
frappe.delete_doc("Custom Field", df.name)
def make_property_setter(
@@ -561,6 +564,10 @@ def reset_customization(doctype):
frappe.clear_cache(doctype=doctype)
+def is_standard_or_system_generated_field(df):
+ return not df.get("is_custom_field") or df.get("is_system_generated")
+
+
doctype_properties = {
"search_fields": "Data",
"title_field": "Data",
diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py
index 661652c74c..8d98dc4149 100644
--- a/frappe/custom/doctype/customize_form/test_customize_form.py
+++ b/frappe/custom/doctype/customize_form/test_customize_form.py
@@ -403,3 +403,25 @@ class TestCustomizeForm(FrappeTestCase):
with self.assertRaises(frappe.ValidationError):
d.run_method("save_customization")
+
+ def test_system_generated_fields(self):
+ doctype = "Event"
+ custom_field_name = "test_custom_field"
+
+ custom_field = frappe.get_doc("Custom Field", {"dt": doctype, "fieldname": custom_field_name})
+ custom_field.is_system_generated = 1
+ custom_field.save()
+
+ d = self.get_customize_form(doctype)
+ custom_field = d.getone("fields", {"fieldname": custom_field_name})
+ custom_field.description = "Test Description"
+ d.run_method("save_customization")
+
+ property_setter_filters = {
+ "doc_type": doctype,
+ "field_name": custom_field_name,
+ "property": "description",
+ }
+ self.assertEqual(
+ frappe.db.get_value("Property Setter", property_setter_filters, "value"), "Test Description"
+ )
From 9a88acfce49ca4937d5785fc618cac3d500dede7 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Mon, 27 Feb 2023 16:48:21 +0530
Subject: [PATCH 008/120] fix!: make system generated fields unsortable
---
frappe/custom/doctype/customize_form/customize_form.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js
index dbcc9b17ce..40c8dad088 100644
--- a/frappe/custom/doctype/customize_form/customize_form.js
+++ b/frappe/custom/doctype/customize_form/customize_form.js
@@ -89,7 +89,7 @@ frappe.ui.form.on("Customize Form", {
setup_sortable: function (frm) {
frm.doc.fields.forEach(function (f) {
- if (!f.is_custom_field) {
+ if (!f.is_custom_field || f.is_system_generated) {
f._sortable = false;
}
From 1ed4b2d30e9b0141e6d5417c4e4262541bc412fc Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Wed, 1 Mar 2023 15:19:36 +0530
Subject: [PATCH 009/120] style: use `const` instead of `let`
---
frappe/custom/doctype/customize_form/customize_form.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js
index 40c8dad088..c420dca3e4 100644
--- a/frappe/custom/doctype/customize_form/customize_form.js
+++ b/frappe/custom/doctype/customize_form/customize_form.js
@@ -251,7 +251,7 @@ frappe.ui.form.on("Customize Form", {
// can't delete standard fields
frappe.ui.form.on("Customize Form Field", {
before_fields_remove: function (frm, doctype, name) {
- let row = frappe.get_doc(doctype, name);
+ const row = frappe.get_doc(doctype, name);
if (row.is_system_generated) {
frappe.throw(
From 97ca92e3d18771e0e782c2ccbdfd752a6b106514 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 10 Mar 2023 14:57:15 +0530
Subject: [PATCH 010/120] refactor: change rounding method names (#20299)
These are easy to understand.
Added third method for corrected banker's rounding.
---
cypress/integration/rounding.js | 2 +-
frappe/core/doctype/system_settings/system_settings.json | 6 +++---
frappe/public/js/frappe/utils/number_format.js | 6 +++---
frappe/tests/test_utils.py | 6 +++---
frappe/utils/data.py | 6 +++---
5 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/cypress/integration/rounding.js b/cypress/integration/rounding.js
index 647b32a6a5..daad2def8f 100644
--- a/cypress/integration/rounding.js
+++ b/cypress/integration/rounding.js
@@ -8,7 +8,7 @@ context("Rounding behaviour", () => {
cy.window()
.its("flt")
.then((flt) => {
- let rounding_method = "Rounding Half Away From Zero";
+ let rounding_method = "Commercial Rounding";
expect(flt("0.5", 0, null, rounding_method)).eq(1);
expect(flt("0.3", null, null, rounding_method)).eq(0.3);
diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json
index 2c9e92d943..642e454717 100644
--- a/frappe/core/doctype/system_settings/system_settings.json
+++ b/frappe/core/doctype/system_settings/system_settings.json
@@ -523,17 +523,17 @@
"label": "Login with email link expiry (in minutes)"
},
{
- "default": "Round Half Even",
+ "default": "Banker's Rounding (legacy)",
"fieldname": "rounding_method",
"fieldtype": "Select",
"label": "Rounding Method",
- "options": "Round Half Even\nRounding Half Away From Zero"
+ "options": "Banker's Rounding (legacy)\nCommercial Rounding"
}
],
"icon": "fa fa-cog",
"issingle": 1,
"links": [],
- "modified": "2023-03-06 11:31:19.144956",
+ "modified": "2023-03-10 12:23:45.248125",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",
diff --git a/frappe/public/js/frappe/utils/number_format.js b/frappe/public/js/frappe/utils/number_format.js
index 2c457e75fe..52f9ac251b 100644
--- a/frappe/public/js/frappe/utils/number_format.js
+++ b/frappe/public/js/frappe/utils/number_format.js
@@ -175,11 +175,11 @@ function get_number_format_info(format) {
function _round(num, precision, rounding_method) {
rounding_method =
- rounding_method || frappe.boot.sysdefaults.rounding_method || "Round Half Even";
+ rounding_method || frappe.boot.sysdefaults.rounding_method || "Banker's Rounding (legacy)";
let is_negative = num < 0 ? true : false;
- if (rounding_method == "Round Half Even") {
+ if (rounding_method == "Banker's Rounding (legacy)") {
var d = cint(precision);
var m = Math.pow(10, d);
var n = +(d ? Math.abs(num) * m : Math.abs(num)).toFixed(8); // Avoid rounding errors
@@ -188,7 +188,7 @@ function _round(num, precision, rounding_method) {
var r = !precision && f == 0.5 ? (i % 2 == 0 ? i : i + 1) : Math.round(n);
r = d ? r / m : r;
return is_negative ? -r : r;
- } else if (rounding_method == "Rounding Half Away From Zero") {
+ } else if (rounding_method == "Commerical Rounding") {
if (num == 0) return 0.0;
let digits = cint(precision);
diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py
index ce13ee65cd..52e91b9500 100644
--- a/frappe/tests/test_utils.py
+++ b/frappe/tests/test_utils.py
@@ -1007,7 +1007,7 @@ class TestTBSanitization(FrappeTestCase):
class TestRounding(FrappeTestCase):
- @change_settings("System Settings", {"rounding_method": "Rounding Half Away From Zero"})
+ @change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
def test_normal_rounding(self):
self.assertEqual(flt("what"), 0)
@@ -1041,7 +1041,7 @@ class TestRounding(FrappeTestCase):
self.assertEqual(flt(-0.15, 1), -0.2)
def test_normal_rounding_as_argument(self):
- rounding_method = "Rounding Half Away From Zero"
+ rounding_method = "Commercial Rounding"
self.assertEqual(flt("0.5", 0, rounding_method=rounding_method), 1)
self.assertEqual(flt("0.3", rounding_method=rounding_method), 0.3)
@@ -1073,7 +1073,7 @@ class TestRounding(FrappeTestCase):
self.assertEqual(flt(-1.25, 1, rounding_method=rounding_method), -1.3)
self.assertEqual(flt(-0.15, 1, rounding_method=rounding_method), -0.2)
- @change_settings("System Settings", {"rounding_method": "Rounding Half Away From Zero"})
+ @change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
@given(st.decimals(min_value=-1e8, max_value=1e8), st.integers(min_value=-2, max_value=4))
def test_normal_rounding_property(self, number, precision):
with localcontext() as ctx:
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index eeae737144..8a6037d718 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -1055,12 +1055,12 @@ def rounded(num, precision=0, rounding_method=None):
precision = cint(precision)
rounding_method = (
- rounding_method or frappe.get_system_settings("rounding_method") or "Round Half Even"
+ rounding_method or frappe.get_system_settings("rounding_method") or "Banker's Rounding (legacy)"
)
- if rounding_method == "Round Half Even":
+ if rounding_method == "Banker's Rounding (legacy)":
return _round_half_even(num, precision)
- elif rounding_method == "Rounding Half Away From Zero":
+ elif rounding_method == "Commercial Rounding":
return _round_away_from_zero(num, precision)
else:
frappe.throw(
From 90be13e7ee7e7abb5a4772b4dfdd3b35fbfc939a Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 13 Mar 2023 11:06:15 +0530
Subject: [PATCH 011/120] fix: dont respond to login api call if email login is
disabled (#20311)
---
frappe/www/login.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/frappe/www/login.py b/frappe/www/login.py
index 8529b03bf6..3cd9edf7ce 100644
--- a/frappe/www/login.py
+++ b/frappe/www/login.py
@@ -121,6 +121,9 @@ def login_via_token(login_token: str):
@rate_limit(limit=5, seconds=60 * 60)
def send_login_link(email: str):
+ if not frappe.get_system_settings("login_with_email_link"):
+ return
+
expiry = frappe.get_system_settings("login_with_email_link_expiry") or 10
link = _generate_temporary_login_link(email, expiry)
From 032889f9137eb05d6d9d59654bba644e2629133a Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 13 Mar 2023 12:28:20 +0530
Subject: [PATCH 012/120] chore: typo
---
frappe/public/js/frappe/utils/number_format.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/utils/number_format.js b/frappe/public/js/frappe/utils/number_format.js
index 52f9ac251b..2c520805f2 100644
--- a/frappe/public/js/frappe/utils/number_format.js
+++ b/frappe/public/js/frappe/utils/number_format.js
@@ -188,7 +188,7 @@ function _round(num, precision, rounding_method) {
var r = !precision && f == 0.5 ? (i % 2 == 0 ? i : i + 1) : Math.round(n);
r = d ? r / m : r;
return is_negative ? -r : r;
- } else if (rounding_method == "Commerical Rounding") {
+ } else if (rounding_method == "Commercial Rounding") {
if (num == 0) return 0.0;
let digits = cint(precision);
From 139d4a87b464071cf9d97df3d2b9cd877ee56887 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 10 Mar 2023 12:26:51 +0530
Subject: [PATCH 013/120] fix: corrected banker's rounding
closes https://github.com/frappe/frappe/issues/19570
---
cypress/integration/rounding.js | 62 +++++++++++++++-
.../system_settings/system_settings.json | 2 +-
.../public/js/frappe/utils/number_format.js | 19 +++++
frappe/tests/test_utils.py | 71 +++++++++++++++++++
frappe/utils/data.py | 22 +++++-
5 files changed, 172 insertions(+), 4 deletions(-)
diff --git a/cypress/integration/rounding.js b/cypress/integration/rounding.js
index daad2def8f..1a9cfa685b 100644
--- a/cypress/integration/rounding.js
+++ b/cypress/integration/rounding.js
@@ -4,7 +4,7 @@ context("Rounding behaviour", () => {
cy.visit("/app/");
});
- it("Rounds floats accurately", () => {
+ it("Commercial Rounding", () => {
cy.window()
.its("flt")
.then((flt) => {
@@ -41,4 +41,64 @@ context("Rounding behaviour", () => {
expect(flt(-0.15, 1, null, rounding_method)).eq(-0.2);
});
});
+
+ it("Banker's Rounding", () => {
+ cy.window()
+ .its("flt")
+ .then((flt) => {
+ let rounding_method = "Banker's Rounding";
+
+ expect(flt("0.5", 0, null, rounding_method)).eq(0);
+ expect(flt("0.3", null, rounding_method)).eq(0.3);
+
+ expect(flt("1.5", 0, null, rounding_method)).eq(2);
+
+ // positive rounding to integers
+ expect(flt(0.4, 0, null, rounding_method)).eq(0);
+ expect(flt(0.5, 0, null, rounding_method)).eq(0);
+ expect(flt(1.455, 0, null, rounding_method)).eq(1);
+ expect(flt(1.5, 0, null, rounding_method)).eq(2);
+
+ // negative rounding to integers
+ expect(flt(-0.5, 0, null, rounding_method)).eq(0);
+ expect(flt(-1.5, 0, null, rounding_method)).eq(-2);
+
+ // negative precision i.e. round to nearest 10th
+ expect(flt(123, -1, null, rounding_method)).eq(120);
+ expect(flt(125, -1, null, rounding_method)).eq(120);
+ expect(flt(134.45, -1, null, rounding_method)).eq(130);
+ expect(flt(135, -1, null, rounding_method)).eq(140);
+
+ // positive multiple digit rounding
+ expect(flt(1.25, 1, null, rounding_method)).eq(1.2);
+ expect(flt(0.15, 1, null, rounding_method)).eq(0.2);
+ expect(flt(2.675, 2, null, rounding_method)).eq(2.68);
+ expect(flt(-2.675, 2, null, rounding_method)).eq(-2.68);
+
+ // negative multiple digit rounding
+ expect(flt(-1.25, 1, null, rounding_method)).eq(-1.2);
+ expect(flt(-0.15, 1, null, rounding_method)).eq(-0.2);
+
+ // Nearest number and not even (the default behaviour)
+ expect(flt(0.5, 0, null, rounding_method)).eq(0);
+ expect(flt(1.5, 0, null, rounding_method)).eq(2);
+ expect(flt(2.5, 0, null, rounding_method)).eq(2);
+ expect(flt(3.5, 0, null, rounding_method)).eq(4);
+
+ expect(flt(0.05, 1, null, rounding_method)).eq(0.0);
+ expect(flt(1.15, 1, null, rounding_method)).eq(1.2);
+ expect(flt(2.25, 1, null, rounding_method)).eq(2.2);
+ expect(flt(3.35, 1, null, rounding_method)).eq(3.4);
+
+ expect(flt(-0.5, 0, null, rounding_method)).eq(0);
+ expect(flt(-1.5, 0, null, rounding_method)).eq(-2);
+ expect(flt(-2.5, 0, null, rounding_method)).eq(-2);
+ expect(flt(-3.5, 0, null, rounding_method)).eq(-4);
+
+ expect(flt(-0.05, 1, null, rounding_method)).eq(0.0);
+ expect(flt(-1.15, 1, null, rounding_method)).eq(-1.2);
+ expect(flt(-2.25, 1, null, rounding_method)).eq(-2.2);
+ expect(flt(-3.35, 1, null, rounding_method)).eq(-3.4);
+ });
+ });
});
diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json
index 642e454717..8ebcb493de 100644
--- a/frappe/core/doctype/system_settings/system_settings.json
+++ b/frappe/core/doctype/system_settings/system_settings.json
@@ -527,7 +527,7 @@
"fieldname": "rounding_method",
"fieldtype": "Select",
"label": "Rounding Method",
- "options": "Banker's Rounding (legacy)\nCommercial Rounding"
+ "options": "Banker's Rounding (legacy)\nBanker's Rounding\nCommercial Rounding"
}
],
"icon": "fa fa-cog",
diff --git a/frappe/public/js/frappe/utils/number_format.js b/frappe/public/js/frappe/utils/number_format.js
index 2c520805f2..179c186908 100644
--- a/frappe/public/js/frappe/utils/number_format.js
+++ b/frappe/public/js/frappe/utils/number_format.js
@@ -188,6 +188,25 @@ function _round(num, precision, rounding_method) {
var r = !precision && f == 0.5 ? (i % 2 == 0 ? i : i + 1) : Math.round(n);
r = d ? r / m : r;
return is_negative ? -r : r;
+ } else if (rounding_method == "Banker's Rounding") {
+ precision = cint(precision);
+
+ let multiplier = Math.pow(10, precision);
+ num = Math.abs(num) * multiplier;
+
+ let floor_num = Math.floor(num);
+ let decimal_part = num - floor_num;
+
+ // For explanation of this method read python flt implementation notes.
+ let epsilon = 2.0 ** (Math.log2(Math.abs(num)) - 52.0);
+
+ if (Math.abs(decimal_part - 0.5) < epsilon) {
+ num = floor_num % 2 == 0 ? floor_num : floor_num + 1;
+ } else {
+ num = Math.round(num);
+ }
+ num = num / multiplier;
+ return is_negative ? -num : num;
} else if (rounding_method == "Commercial Rounding") {
if (num == 0) return 0.0;
diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py
index 52e91b9500..b897c96133 100644
--- a/frappe/tests/test_utils.py
+++ b/frappe/tests/test_utils.py
@@ -1073,9 +1073,80 @@ class TestRounding(FrappeTestCase):
self.assertEqual(flt(-1.25, 1, rounding_method=rounding_method), -1.3)
self.assertEqual(flt(-0.15, 1, rounding_method=rounding_method), -0.2)
+ # Nearest number and not even (the default behaviour)
+ self.assertEqual(flt(0.5, 0, rounding_method=rounding_method), 1)
+ self.assertEqual(flt(1.5, 0, rounding_method=rounding_method), 2)
+ self.assertEqual(flt(2.5, 0, rounding_method=rounding_method), 3)
+ self.assertEqual(flt(3.5, 0, rounding_method=rounding_method), 4)
+
+ self.assertEqual(flt(0.05, 1, rounding_method=rounding_method), 0.1)
+ self.assertEqual(flt(1.15, 1, rounding_method=rounding_method), 1.2)
+ self.assertEqual(flt(2.25, 1, rounding_method=rounding_method), 2.3)
+ self.assertEqual(flt(3.35, 1, rounding_method=rounding_method), 3.4)
+
@change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
@given(st.decimals(min_value=-1e8, max_value=1e8), st.integers(min_value=-2, max_value=4))
def test_normal_rounding_property(self, number, precision):
with localcontext() as ctx:
ctx.rounding = ROUND_HALF_UP
self.assertEqual(Decimal(str(flt(float(number), precision))), round(number, precision))
+
+ def test_bankers_rounding(self):
+ rounding_method = "Banker's Rounding"
+
+ self.assertEqual(flt("0.5", 0, rounding_method=rounding_method), 0)
+ self.assertEqual(flt("0.3", rounding_method=rounding_method), 0.3)
+
+ self.assertEqual(flt("1.5", 0, rounding_method=rounding_method), 2)
+
+ # positive rounding to integers
+ self.assertEqual(flt(0.4, 0, rounding_method=rounding_method), 0)
+ self.assertEqual(flt(0.5, 0, rounding_method=rounding_method), 0)
+ self.assertEqual(flt(1.455, 0, rounding_method=rounding_method), 1)
+ self.assertEqual(flt(1.5, 0, rounding_method=rounding_method), 2)
+
+ # negative rounding to integers
+ self.assertEqual(flt(-0.5, 0, rounding_method=rounding_method), 0)
+ self.assertEqual(flt(-1.5, 0, rounding_method=rounding_method), -2)
+
+ # negative precision i.e. round to nearest 10th
+ self.assertEqual(flt(123, -1, rounding_method=rounding_method), 120)
+ self.assertEqual(flt(125, -1, rounding_method=rounding_method), 120)
+ self.assertEqual(flt(134.45, -1, rounding_method=rounding_method), 130)
+ self.assertEqual(flt(135, -1, rounding_method=rounding_method), 140)
+
+ # positive multiple digit rounding
+ self.assertEqual(flt(1.25, 1, rounding_method=rounding_method), 1.2)
+ self.assertEqual(flt(0.15, 1, rounding_method=rounding_method), 0.2)
+ self.assertEqual(flt(2.675, 2, rounding_method=rounding_method), 2.68)
+ self.assertEqual(flt(-2.675, 2, rounding_method=rounding_method), -2.68)
+
+ # negative multiple digit rounding
+ self.assertEqual(flt(-1.25, 1, rounding_method=rounding_method), -1.2)
+ self.assertEqual(flt(-0.15, 1, rounding_method=rounding_method), -0.2)
+
+ # Nearest number and not even (the default behaviour)
+ self.assertEqual(flt(0.5, 0, rounding_method=rounding_method), 0)
+ self.assertEqual(flt(1.5, 0, rounding_method=rounding_method), 2)
+ self.assertEqual(flt(2.5, 0, rounding_method=rounding_method), 2)
+ self.assertEqual(flt(3.5, 0, rounding_method=rounding_method), 4)
+
+ self.assertEqual(flt(0.05, 1, rounding_method=rounding_method), 0.0)
+ self.assertEqual(flt(1.15, 1, rounding_method=rounding_method), 1.2)
+ self.assertEqual(flt(2.25, 1, rounding_method=rounding_method), 2.2)
+ self.assertEqual(flt(3.35, 1, rounding_method=rounding_method), 3.4)
+
+ self.assertEqual(flt(-0.5, 0, rounding_method=rounding_method), 0)
+ self.assertEqual(flt(-1.5, 0, rounding_method=rounding_method), -2)
+ self.assertEqual(flt(-2.5, 0, rounding_method=rounding_method), -2)
+ self.assertEqual(flt(-3.5, 0, rounding_method=rounding_method), -4)
+
+ self.assertEqual(flt(-0.05, 1, rounding_method=rounding_method), 0.0)
+ self.assertEqual(flt(-1.15, 1, rounding_method=rounding_method), -1.2)
+ self.assertEqual(flt(-2.25, 1, rounding_method=rounding_method), -2.2)
+ self.assertEqual(flt(-3.35, 1, rounding_method=rounding_method), -3.4)
+
+ @change_settings("System Settings", {"rounding_method": "Banker's Rounding"})
+ @given(st.decimals(min_value=-1e8, max_value=1e8), st.integers(min_value=-2, max_value=4))
+ def test_bankers_rounding_property(self, number, precision):
+ self.assertEqual(Decimal(str(flt(float(number), precision))), round(number, precision))
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index 8a6037d718..26fb50c31b 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -1059,7 +1059,9 @@ def rounded(num, precision=0, rounding_method=None):
)
if rounding_method == "Banker's Rounding (legacy)":
- return _round_half_even(num, precision)
+ return _bankers_rounding_legacy(num, precision)
+ elif rounding_method == "Banker's Rounding":
+ return _bankers_rounding(num, precision)
elif rounding_method == "Commercial Rounding":
return _round_away_from_zero(num, precision)
else:
@@ -1069,7 +1071,7 @@ def rounded(num, precision=0, rounding_method=None):
)
-def _round_half_even(num, precision):
+def _bankers_rounding_legacy(num, precision):
# avoid rounding errors
multiplier = 10**precision
num = round(num * multiplier if precision else num, 8)
@@ -1114,6 +1116,22 @@ def _round_away_from_zero(num, precision):
return round(num + math.copysign(epsilon, num), precision)
+def _bankers_rounding(num, precision):
+ multiplier = 10**precision
+ num = round(num * multiplier, 12)
+
+ floor_num = math.floor(num)
+ decimal_part = num - floor_num
+
+ epsilon = 2.0 ** (math.log(abs(num), 2) - 52.0)
+ if abs(decimal_part - 0.5) < epsilon:
+ num = floor_num if (floor_num % 2 == 0) else floor_num + 1
+ else:
+ num = round(num)
+
+ return num / multiplier
+
+
def remainder(numerator: NumericType, denominator: NumericType, precision: int = 2) -> NumericType:
precision = cint(precision)
multiplier = 10**precision
From 709edf1f559ba30ffe8015542eb7b4de6c6e7ceb Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 13 Mar 2023 13:00:50 +0530
Subject: [PATCH 014/120] fix: Make corrected bankers rounding default method
---
.../doctype/system_settings/system_settings.js | 15 +++++++++++++++
frappe/desk/page/setup_wizard/setup_wizard.py | 1 +
frappe/tests/test_utils.py | 3 +++
3 files changed, 19 insertions(+)
diff --git a/frappe/core/doctype/system_settings/system_settings.js b/frappe/core/doctype/system_settings/system_settings.js
index 1d5ba7ddb0..bf8988d64c 100644
--- a/frappe/core/doctype/system_settings/system_settings.js
+++ b/frappe/core/doctype/system_settings/system_settings.js
@@ -16,6 +16,8 @@ frappe.ui.form.on("System Settings", {
}
},
});
+
+ frm.trigger("set_rounding_method_options");
},
enable_password_policy: function (frm) {
if (frm.doc.enable_password_policy == 0) {
@@ -56,4 +58,17 @@ frappe.ui.form.on("System Settings", {
}
);
},
+
+ set_rounding_method_options: function (frm) {
+ if (frm.doc.rounding_method != "Banker's Rounding (legacy)") {
+ let field = frm.fields_dict.rounding_method;
+
+ field.df.options = field.df.options
+ .split("\n")
+ .filter((o) => o != "Banker's Rounding (legacy)")
+ .join("\n");
+
+ field.refresh();
+ }
+ },
});
diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py
index 4d4d76207a..cdb25b81ba 100755
--- a/frappe/desk/page/setup_wizard/setup_wizard.py
+++ b/frappe/desk/page/setup_wizard/setup_wizard.py
@@ -165,6 +165,7 @@ def update_system_settings(args):
"language": get_language_code(args.get("language")) or "en",
"time_zone": args.get("timezone"),
"float_precision": 3,
+ "rounding_method": "Banker's Rounding",
"date_format": frappe.db.get_value("Country", args.get("country"), "date_format"),
"time_format": frappe.db.get_value("Country", args.get("country"), "time_format"),
"number_format": number_format,
diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py
index b897c96133..ffc8e80acf 100644
--- a/frappe/tests/test_utils.py
+++ b/frappe/tests/test_utils.py
@@ -1150,3 +1150,6 @@ class TestRounding(FrappeTestCase):
@given(st.decimals(min_value=-1e8, max_value=1e8), st.integers(min_value=-2, max_value=4))
def test_bankers_rounding_property(self, number, precision):
self.assertEqual(Decimal(str(flt(float(number), precision))), round(number, precision))
+
+ def test_default_rounding(self):
+ self.assertEqual(frappe.get_system_settings("rounding_method"), "Banker's Rounding")
From dd2ac72a9a799139befb4025c458c181e911bd03 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 13 Mar 2023 14:14:17 +0530
Subject: [PATCH 015/120] fix: skip 0 for rounding
---
frappe/public/js/frappe/utils/number_format.js | 1 +
frappe/tests/test_utils.py | 2 ++
frappe/utils/data.py | 3 +++
3 files changed, 6 insertions(+)
diff --git a/frappe/public/js/frappe/utils/number_format.js b/frappe/public/js/frappe/utils/number_format.js
index 179c186908..ef4c7366f7 100644
--- a/frappe/public/js/frappe/utils/number_format.js
+++ b/frappe/public/js/frappe/utils/number_format.js
@@ -189,6 +189,7 @@ function _round(num, precision, rounding_method) {
r = d ? r / m : r;
return is_negative ? -r : r;
} else if (rounding_method == "Banker's Rounding") {
+ if (num == 0) return 0.0;
precision = cint(precision);
let multiplier = Math.pow(10, precision);
diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py
index ffc8e80acf..dce2a159ac 100644
--- a/frappe/tests/test_utils.py
+++ b/frappe/tests/test_utils.py
@@ -63,6 +63,7 @@ from frappe.utils.data import (
now_datetime,
nowtime,
pretty_date,
+ rounded,
to_timedelta,
validate_python_code,
)
@@ -1094,6 +1095,7 @@ class TestRounding(FrappeTestCase):
def test_bankers_rounding(self):
rounding_method = "Banker's Rounding"
+ self.assertEqual(rounded(0, 0, rounding_method=rounding_method), 0)
self.assertEqual(flt("0.5", 0, rounding_method=rounding_method), 0)
self.assertEqual(flt("0.3", rounding_method=rounding_method), 0.3)
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index 26fb50c31b..d76d97f7e0 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -1117,6 +1117,9 @@ def _round_away_from_zero(num, precision):
def _bankers_rounding(num, precision):
+ if num == 0:
+ return 0.0
+
multiplier = 10**precision
num = round(num * multiplier, 12)
From 41685ff014e6caf04ce11a3454bde3df98a918a8 Mon Sep 17 00:00:00 2001
From: Ritwik Puri
Date: Mon, 13 Mar 2023 15:20:57 +0530
Subject: [PATCH 016/120] fix: dont use get_datetime for enddate when startdate
is in future for google calendar event (#20309)
---
.../integrations/doctype/google_calendar/google_calendar.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py
index a663c9c593..65a4a2bccd 100644
--- a/frappe/integrations/doctype/google_calendar/google_calendar.py
+++ b/frappe/integrations/doctype/google_calendar/google_calendar.py
@@ -403,7 +403,7 @@ def insert_event_in_google_calendar(doc, method=None):
event = {"summary": doc.subject, "description": doc.description, "google_calendar_event": 1}
event.update(
format_date_according_to_google_calendar(
- doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on)
+ doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on) if doc.ends_on else None
)
)
@@ -484,7 +484,7 @@ def update_event_in_google_calendar(doc, method=None):
)
event.update(
format_date_according_to_google_calendar(
- doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on)
+ doc.all_day, get_datetime(doc.starts_on), get_datetime(doc.ends_on) if doc.ends_on else None
)
)
From 985a8ca18a8be1e18c745b6b3af3bbcdd891eb0a Mon Sep 17 00:00:00 2001
From: Ritwik Puri
Date: Mon, 13 Mar 2023 21:05:48 +0530
Subject: [PATCH 017/120] fix: proper errror translation for finding outgoing
email account (#20316)
---
frappe/email/doctype/email_account/email_account.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py
index aa0935e028..2e5dbe2e24 100755
--- a/frappe/email/doctype/email_account/email_account.py
+++ b/frappe/email/doctype/email_account/email_account.py
@@ -19,7 +19,6 @@ from frappe.email.utils import get_port
from frappe.model.document import Document
from frappe.utils import cint, comma_or, cstr, parse_addr, validate_email_address
from frappe.utils.background_jobs import enqueue, get_jobs
-from frappe.utils.error import raise_error_on_no_output
from frappe.utils.jinja import render_template
from frappe.utils.user import get_system_managers
@@ -301,11 +300,6 @@ class EmailAccount(Document):
return cls.from_record({"sender": "notifications@example.com"})
@classmethod
- @raise_error_on_no_output(
- keep_quiet=lambda: not cint(frappe.get_system_settings("setup_complete")),
- error_message=_("Please setup default Email Account from Setup > Email > Email Account"),
- error_type=frappe.OutgoingEmailError,
- ) # noqa
@cache_email_account("outgoing_email_account")
def find_outgoing(cls, match_by_email=None, match_by_doctype=None, _raise_error=False):
"""Find the outgoing Email account to use.
@@ -329,6 +323,12 @@ class EmailAccount(Document):
if doc:
return {"default": doc}
+ if _raise_error:
+ frappe.throw(
+ _("Please setup default Email Account from Setup > Email > Email Account"),
+ frappe.OutgoingEmailError,
+ )
+
@classmethod
def find_default_outgoing(cls):
"""Find default outgoing account."""
From 5538d7fbbeb1e0a69baa737e953fd38951fd9b46 Mon Sep 17 00:00:00 2001
From: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Date: Tue, 14 Mar 2023 07:07:22 +0100
Subject: [PATCH 018/120] chore: translate read only alert to german (#20324)
[skip ci]
---
frappe/translations/de.csv | 1 +
1 file changed, 1 insertion(+)
diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv
index e6a8fa4a35..b3f84cb072 100644
--- a/frappe/translations/de.csv
+++ b/frappe/translations/de.csv
@@ -4832,3 +4832,4 @@ CSV Preview,Vorschau,
Non-numeric,Nicht-numerische,
Minimal,Minimal,
This value is fetched from {0}'s {1} field,Dieser Wert ergibt sich aus dem Feld {1} von {0},
+This form is not editable due to a Workflow.,Dieses Formular kann in diesem Workflow-Status nicht bearbeitet werden.,
From da086a44107b6ccff769c4b9fb66c60dfcf1787e Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 14 Mar 2023 12:43:41 +0530
Subject: [PATCH 019/120] feat: allow configuring ttl for RQ job retention
(#20331)
Some might want to keep them around for long, others might wanna get rid
of it to free up memory. Hardcoded defaults dont work for everyone hence
make it configurable.
`no-docs`
[skip ci]
---
frappe/core/doctype/rq_job/test_rq_job.py | 6 ++++++
frappe/database/database.py | 4 ++--
frappe/utils/background_jobs.py | 4 ++--
3 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/frappe/core/doctype/rq_job/test_rq_job.py b/frappe/core/doctype/rq_job/test_rq_job.py
index 2fbefda056..3b8e2d5cc3 100644
--- a/frappe/core/doctype/rq_job/test_rq_job.py
+++ b/frappe/core/doctype/rq_job/test_rq_job.py
@@ -44,6 +44,12 @@ class TestRQJob(FrappeTestCase):
)
self.check_status(job, "finished")
+ def test_configurable_ttl(self):
+ frappe.conf.rq_job_failure_ttl = 600
+ job = frappe.enqueue(method=self.BG_JOB, queue="short")
+
+ self.assertEqual(job.failure_ttl, 600)
+
def test_func_obj_serialization(self):
job = frappe.enqueue(method=test_func, queue="short")
rq_job = frappe.get_doc("RQ Job", job.id)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 7e0cb83454..c51a8f10a7 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -1300,8 +1300,8 @@ def enqueue_jobs_after_commit():
execute_job,
timeout=job.get("timeout"),
kwargs=job.get("queue_args"),
- failure_ttl=RQ_JOB_FAILURE_TTL,
- result_ttl=RQ_RESULTS_TTL,
+ failure_ttl=frappe.conf.get("rq_job_failure_ttl") or RQ_JOB_FAILURE_TTL,
+ result_ttl=frappe.conf.get("rq_results_ttl") or RQ_RESULTS_TTL,
)
frappe.flags.enqueue_after_commit = []
diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py
index fed822700c..29098dce3b 100755
--- a/frappe/utils/background_jobs.py
+++ b/frappe/utils/background_jobs.py
@@ -126,8 +126,8 @@ def enqueue(
timeout=timeout,
kwargs=queue_args,
at_front=at_front,
- failure_ttl=RQ_JOB_FAILURE_TTL,
- result_ttl=RQ_RESULTS_TTL,
+ failure_ttl=frappe.conf.get("rq_job_failure_ttl") or RQ_JOB_FAILURE_TTL,
+ result_ttl=frappe.conf.get("rq_results_ttl") or RQ_RESULTS_TTL,
)
From e429101370b85a7457f79f237c477df1a2a33896 Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Tue, 14 Mar 2023 15:13:35 +0530
Subject: [PATCH 020/120] fix(print): Language set in document should have
higher precedence
---
frappe/printing/page/print/print.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js
index f9881fd76e..8e5e165c78 100644
--- a/frappe/printing/page/print/print.js
+++ b/frappe/printing/page/print/print.js
@@ -347,7 +347,7 @@ frappe.ui.form.PrintView = class {
set_default_print_language() {
let print_format = this.get_print_format();
this.lang_code =
- print_format.default_print_language || this.frm.doc.language || frappe.boot.lang;
+ this.frm.doc.language || print_format.default_print_language || frappe.boot.lang;
this.language_selector.val(this.lang_code);
}
From 9430b6a8c2c6e75cb36c00bd87342cf05d281e84 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 14 Mar 2023 17:27:30 +0530
Subject: [PATCH 021/120] fix: Permission error while processing role based
notifications (#20315)
---
frappe/core/doctype/role/role.py | 3 ++-
frappe/email/doctype/notification/notification.py | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/frappe/core/doctype/role/role.py b/frappe/core/doctype/role/role.py
index 97a0e9b581..31b82501cb 100644
--- a/frappe/core/doctype/role/role.py
+++ b/frappe/core/doctype/role/role.py
@@ -64,13 +64,14 @@ class Role(Document):
user.save()
-def get_info_based_on_role(role, field="email"):
+def get_info_based_on_role(role, field="email", ignore_permissions=False):
"""Get information of all users that have been assigned this role"""
users = frappe.get_list(
"Has Role",
filters={"role": role, "parenttype": "User"},
parent_doctype="User",
fields=["parent as user_name"],
+ ignore_permissions=ignore_permissions,
)
return get_user_info(users, field)
diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py
index d5071e23a0..2efbf597ec 100644
--- a/frappe/email/doctype/notification/notification.py
+++ b/frappe/email/doctype/notification/notification.py
@@ -298,7 +298,7 @@ def get_context(context):
# For sending emails to specified role
if recipient.receiver_by_role:
- emails = get_info_based_on_role(recipient.receiver_by_role, "email")
+ emails = get_info_based_on_role(recipient.receiver_by_role, "email", ignore_permissions=True)
for email in emails:
recipients = recipients + email.split("\n")
From 1f01eccb9b8b156d4e17f9e0ca541bbc415c7215 Mon Sep 17 00:00:00 2001
From: Wolfram Schmidt
Date: Tue, 14 Mar 2023 21:21:13 +0100
Subject: [PATCH 022/120] chore: update de translations (#20341)
added translations for new fields (v14)
---
frappe/translations/de.csv | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv
index b3f84cb072..a6aef52b1c 100644
--- a/frappe/translations/de.csv
+++ b/frappe/translations/de.csv
@@ -1611,6 +1611,7 @@ Name of the new Print Format,Name des neuen Druckformats,
Name of {0} cannot be {1},Name von {0} kann nicht {1} sein,
Names and surnames by themselves are easy to guess.,Namen und Vornamen von Ihnen selbst sind leicht zu erraten.,
Naming,Bezeichnung,
+Naming Rule, Benennungsregel,
"Naming Options:\n
field:[fieldname] - By Field
naming_series: - By Naming Series (field called naming_series must be present
Prompt - Prompt user for a name
[series] - Series by prefix (separated by a dot); for example PRE.#####
\n
format:EXAMPLE-{MM}morewords{fieldname1}-{fieldname2}-{#####} - Replace all braced words (fieldnames, date words (DD, MM, YY), series) with their value. Outside braces, any characters can be used.
","Namensoptionen:
Feld: [Feldname] - Nach Feld
naming_series: - Nach der Namensreihe (das Feld naming_series muss vorhanden sein)
Eingabeaufforderung - Benutzer nach einem Namen fragen
[Serie] - Reihe nach Präfix (getrennt durch einen Punkt); zum Beispiel PRE. #####
Format: BEISPIEL- {MM} morewords {Feldname1} - {Feldname2} - {#####} - Ersetzt alle verspannten Wörter (Feldnamen, Datumsworte (DD, MM, YY), Serien) durch ihren Wert. Außerhalb von Klammern können beliebige Zeichen verwendet werden.
",
Naming Series mandatory,Nummernkreis zwingend erforderlich,
Nested set error. Please contact the Administrator.,Schachtelfehler. Bitte den Administrator kontaktieren.,
@@ -2311,6 +2312,7 @@ Show Report,Bericht zeigen,
Show Section Headings,Zeige Abschnittsüberschriften,
Show Sidebar,anzeigen Sidebar,
Show Title,Bezeichnung anzeigen,
+Show Title in Link Fields,Bezeichnung in Verknüpfungsfeld anzeigen,
Show Totals,Summen anzeigen,
Show Weekends,Wochenenden anzeigen,
Show all Versions,Alle Versionen,
@@ -2574,7 +2576,7 @@ Track Changes,Änderungen verfolgen,
Track Email Status,E-Mail-Status verfolgen,
Track Field,Track Field,
Track Seen,Track gesehen,
-Track Views,Trackansichten,
+Track Views,Ansichten verfolgen,
"Track if your email has been opened by the recipient.\n \nNote: If you're sending to multiple recipients, even if 1 recipient reads the email, it'll be considered ""Opened""","Verfolgen Sie, ob Ihre E-Mail vom Empfänger geöffnet wurde. Hinweis: Wenn Sie an mehrere Empfänger senden, auch wenn 1 Empfänger die E-Mail liest, wird sie als "Geöffnet" betrachtet.",
Track milestones for any document,Verfolgen Sie Meilensteine für jedes Dokument,
Transaction Hash,Transaktions-Hash,
@@ -2584,6 +2586,7 @@ Transition Rules,Übergangsbestimmungen,
Transitions,Übergänge,
Translatable,Übersetzbar,
Translate {0},Übersetzen {0},
+Translate Link Fields,Verknünpfungsfelder übersetzen,
Translated Text,Übersetzter Text,
Translation,Übersetzung,
Translations,Übersetzungen,
@@ -3254,9 +3257,11 @@ Cron,Cron,
Cron Format,Cron Format,
Daily Events should finish on the Same Day.,Tägliche Ereignisse sollten am selben Tag enden.,
Daily Long,Täglich lang,
+Default {0},Standard {0},
+Default Email Template, Standard E-Mailvorlage,
Default Role on Creation,Standardrolle bei der Erstellung,
Default Theme,Standardthema,
-Default {0},Standard {0},
+Default View,Standardansicht,
Delete All,Alles löschen,
Do you want to cancel all linked documents?,Möchten Sie alle verknüpften Dokumente stornieren?,
DocType Action,DocType-Aktion,
From 2c16ad2bfb3115797fcf19582515e6eb62ea9ae1 Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Wed, 15 Mar 2023 13:41:46 +0530
Subject: [PATCH 023/120] fix: sidebar becomes unhidden while removing skeleton
---
frappe/public/js/frappe/views/workspace/workspace.js | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/frappe/public/js/frappe/views/workspace/workspace.js b/frappe/public/js/frappe/views/workspace/workspace.js
index beb799124c..65d0b583ea 100644
--- a/frappe/public/js/frappe/views/workspace/workspace.js
+++ b/frappe/public/js/frappe/views/workspace/workspace.js
@@ -1445,15 +1445,15 @@ frappe.views.Workspace = class Workspace {
}
create_sidebar_skeleton() {
- if (this.sidebar.find(".workspace-sidebar-skeleton").length) return;
+ if ($(".workspace-sidebar-skeleton").length) return;
- this.sidebar.prepend(frappe.render_template("workspace_sidebar_loading_skeleton"));
- this.sidebar.find(".standard-sidebar-section").addClass("hidden");
+ $(frappe.render_template("workspace_sidebar_loading_skeleton")).insertBefore(this.sidebar);
+ this.sidebar.addClass("hidden");
}
remove_sidebar_skeleton() {
- this.sidebar.find(".standard-sidebar-section").removeClass("hidden");
- this.sidebar.find(".workspace-sidebar-skeleton").remove();
+ this.sidebar.removeClass("hidden");
+ $(".workspace-sidebar-skeleton").remove();
}
register_awesomebar_shortcut() {
From 20eebe7340d733a815df03994ae1c30b4c0d4982 Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Wed, 15 Mar 2023 13:43:16 +0530
Subject: [PATCH 024/120] fix: hide My Workspace sidebar section if empty in
edit mode
---
frappe/public/js/frappe/views/workspace/workspace.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/views/workspace/workspace.js b/frappe/public/js/frappe/views/workspace/workspace.js
index 65d0b583ea..af02629038 100644
--- a/frappe/public/js/frappe/views/workspace/workspace.js
+++ b/frappe/public/js/frappe/views/workspace/workspace.js
@@ -157,7 +157,10 @@ frappe.views.Workspace = class Workspace {
sidebar_section.addClass("hidden");
}
- if (sidebar_section.find("> [item-is-hidden='0']").length == 0) {
+ if (
+ sidebar_section.find("sidebar-item-container").length &&
+ sidebar_section.find("> [item-is-hidden='0']").length == 0
+ ) {
sidebar_section.addClass("hidden show-in-edit-mode");
}
}
From 09ea38e96fb0d9fe98e89933785017219d28656e Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Wed, 15 Mar 2023 14:14:41 +0530
Subject: [PATCH 025/120] fix: Set link title in PDF
Co-authored-by: Saqib Ansari
---
frappe/www/printview.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/frappe/www/printview.py b/frappe/www/printview.py
index f67aa9eee1..9fdc77a2ba 100644
--- a/frappe/www/printview.py
+++ b/frappe/www/printview.py
@@ -40,6 +40,8 @@ def get_context(context):
else:
doc = frappe.get_doc(frappe.form_dict.doctype, frappe.form_dict.name)
+ set_link_titles(doc)
+
settings = frappe.parse_json(frappe.form_dict.settings)
letterhead = frappe.form_dict.letterhead or None
From 56fb0a4f93be977c3bdd70fd886e5f6efb45276c Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 15 Mar 2023 15:12:13 +0530
Subject: [PATCH 026/120] fix: treat Phone as Data on list view
closes https://github.com/frappe/frappe/issues/20290
---
frappe/public/js/frappe/list/base_list.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js
index 2bf738d28b..600db57dd1 100644
--- a/frappe/public/js/frappe/list/base_list.js
+++ b/frappe/public/js/frappe/list/base_list.js
@@ -773,6 +773,7 @@ class FilterArea {
"HTML Editor",
"Data",
"Code",
+ "Phone",
"Read Only",
].includes(fieldtype)
) {
From 8dde2cd43ccadbb1e1ad821f80c4f1e7d39b61cd Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Thu, 16 Mar 2023 09:45:43 +0530
Subject: [PATCH 027/120] fix: Clear dasboard comment to avoid duplicate
- on save
---
frappe/core/doctype/doctype/doctype.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js
index d92277152c..37d80571d2 100644
--- a/frappe/core/doctype/doctype/doctype.js
+++ b/frappe/core/doctype/doctype/doctype.js
@@ -48,6 +48,7 @@ frappe.ui.form.on("DocType", {
true
);
} else if (frappe.boot.developer_mode) {
+ frm.dashboard.clear_comment();
let msg = __(
"This site is running in developer mode. Any change made here will be updated in code."
);
From 10a5861f11cc2da8097bb6b3f966194fa80ab47f Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Thu, 16 Mar 2023 09:47:53 +0530
Subject: [PATCH 028/120] fix: Fill partially entered data to quick entry
- if the autoname is "prompt" the auto fill was not working
Co-authored-by: Rushabh Mehta
---
frappe/public/js/frappe/model/create_new.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js
index ebe565f6a4..523cb3b7a6 100644
--- a/frappe/public/js/frappe/model/create_new.js
+++ b/frappe/public/js/frappe/model/create_new.js
@@ -48,6 +48,8 @@ $.extend(frappe.model, {
// set title field / name as name
if (meta.autoname && meta.autoname.indexOf("field:") !== -1) {
doc[meta.autoname.substr(6)] = frappe.route_options.name_field;
+ } else if (meta.autoname && meta.autoname === "prompt") {
+ doc.__newname = frappe.route_options.name_field;
} else if (meta.title_field) {
doc[meta.title_field] = frappe.route_options.name_field;
}
From 8aac4aae759356b02bda099288c749dc31ea69f4 Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Thu, 16 Mar 2023 10:10:52 +0530
Subject: [PATCH 029/120] fix(grid): Show filter row if some filters are
already applied
---
frappe/public/js/frappe/form/grid_row.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js
index f0795f1213..27ca3dba0c 100644
--- a/frappe/public/js/frappe/form/grid_row.js
+++ b/frappe/public/js/frappe/form/grid_row.js
@@ -40,7 +40,7 @@ export default class GridRow {
render_row = this.render_row();
}
- if (!this.render_row) return;
+ if (!render_row) return;
this.set_data();
this.wrapper.appendTo(this.parent);
@@ -762,7 +762,8 @@ export default class GridRow {
show_search_row() {
// show or remove search columns based on grid rows
- this.show_search = this.show_search && this.grid?.data?.length >= 20;
+ this.show_search =
+ this.show_search && (this.grid?.data?.length >= 20 || this.grid.filter_applied);
!this.show_search && this.wrapper.remove();
return this.show_search;
}
From 6b79a9756697e2c56a18a28c8540ba6c77f198b7 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 16 Mar 2023 11:03:45 +0530
Subject: [PATCH 030/120] fix(UX): clear comment to avoid duplicates
---
frappe/core/doctype/doctype/doctype.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js
index 37d80571d2..a8eed4270d 100644
--- a/frappe/core/doctype/doctype/doctype.js
+++ b/frappe/core/doctype/doctype/doctype.js
@@ -42,6 +42,7 @@ frappe.ui.form.on("DocType", {
if (!frappe.boot.developer_mode && !frm.doc.custom) {
// make the document read-only
frm.set_read_only();
+ frm.dashboard.clear_comment();
frm.dashboard.add_comment(
__("DocTypes can not be modified, please use {0} instead", [customize_form_link]),
"blue",
From c542882bf26d529457777e0aa1f3fc8a009733e0 Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Thu, 16 Mar 2023 11:40:47 +0530
Subject: [PATCH 031/120] fix: Remove lang selector in communication
to avoid confusion since the feature is not functional
---
.../public/js/frappe/views/communication.js | 31 -------------------
1 file changed, 31 deletions(-)
diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js
index 713afd0895..7230de9cf5 100755
--- a/frappe/public/js/frappe/views/communication.js
+++ b/frappe/public/js/frappe/views/communication.js
@@ -130,11 +130,6 @@ frappe.views.CommunicationComposer = class {
fieldtype: "Select",
fieldname: "select_print_format",
},
- {
- label: __("Select Languages"),
- fieldtype: "Select",
- fieldname: "language_sel",
- },
{ fieldtype: "Column Break" },
{
label: __("Select Attachments"),
@@ -183,7 +178,6 @@ frappe.views.CommunicationComposer = class {
prepare() {
this.setup_multiselect_queries();
this.setup_subject_and_recipients();
- this.setup_print_language();
this.setup_print();
this.setup_attach();
this.setup_email();
@@ -295,7 +289,6 @@ frappe.views.CommunicationComposer = class {
args: {
template_name: email_template,
doc: me.doc,
- _lang: me.dialog.get_value("language_sel"),
},
callback(r) {
prepend_reply(r.message);
@@ -403,29 +396,6 @@ frappe.views.CommunicationComposer = class {
}
}
- setup_print_language() {
- const fields = this.dialog.fields_dict;
-
- //Load default print language from doctype
- this.lang_code =
- this.doc.language ||
- this.get_print_format().default_print_language ||
- frappe.boot.lang;
-
- //On selection of language retrieve language code
- const me = this;
- $(fields.language_sel.input).change(function () {
- me.lang_code = this.value;
- });
-
- // Load all languages in the select field language_sel
- $(fields.language_sel.input).empty().add_options(frappe.get_languages());
-
- if (this.lang_code) {
- $(fields.language_sel.input).val(this.lang_code);
- }
- }
-
setup_print() {
// print formats
const fields = this.dialog.fields_dict;
@@ -676,7 +646,6 @@ frappe.views.CommunicationComposer = class {
sender_full_name: form_values.sender ? frappe.user.full_name() : undefined,
email_template: form_values.email_template,
attachments: selected_attachments,
- _lang: me.lang_code,
read_receipt: form_values.send_read_receipt,
print_letterhead: me.is_print_letterhead_checked(),
},
From fdcdb61a2dc5ae8306fc67e406da8d1c6b9cbd80 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 16 Mar 2023 14:01:25 +0530
Subject: [PATCH 032/120] fix: email linking and message_id indexing (#20356)
* fix: find communication regardless of system reply
* perf: convert and index message_id
---
.../doctype/communication/communication.json | 4 ++--
.../doctype/communication/communication.py | 1 +
.../doctype/email_queue/email_queue.json | 7 +++---
.../email/doctype/email_queue/email_queue.py | 2 ++
frappe/email/receive.py | 22 +++++++++----------
5 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json
index 293a6b2c87..e5f090e2f7 100644
--- a/frappe/core/doctype/communication/communication.json
+++ b/frappe/core/doctype/communication/communication.json
@@ -318,7 +318,7 @@
},
{
"fieldname": "message_id",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"ignore_xss_filter": 1,
"label": "Message ID",
"length": 995,
@@ -395,7 +395,7 @@
"icon": "fa fa-comment",
"idx": 1,
"links": [],
- "modified": "2022-05-09 00:13:45.310564",
+ "modified": "2023-03-16 12:04:18.113817",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",
diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py
index adeac35204..067cba59b2 100644
--- a/frappe/core/doctype/communication/communication.py
+++ b/frappe/core/doctype/communication/communication.py
@@ -397,6 +397,7 @@ def on_doctype_update():
"""Add indexes in `tabCommunication`"""
frappe.db.add_index("Communication", ["reference_doctype", "reference_name"])
frappe.db.add_index("Communication", ["status", "communication_type"])
+ frappe.db.add_index("Communication", ["message_id(140)"])
def has_permission(doc, ptype, user):
diff --git a/frappe/email/doctype/email_queue/email_queue.json b/frappe/email/doctype/email_queue/email_queue.json
index c9ec374687..ac8d656678 100644
--- a/frappe/email/doctype/email_queue/email_queue.json
+++ b/frappe/email/doctype/email_queue/email_queue.json
@@ -67,10 +67,9 @@
},
{
"fieldname": "message_id",
- "fieldtype": "Data",
+ "fieldtype": "Small Text",
"label": "Message ID",
- "read_only": 1,
- "search_index": 1
+ "read_only": 1
},
{
"fieldname": "reference_doctype",
@@ -153,7 +152,7 @@
"idx": 1,
"in_create": 1,
"links": [],
- "modified": "2022-07-12 15:17:37.934316",
+ "modified": "2023-03-16 12:15:17.850292",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Queue",
diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py
index 17fdddfaf3..b474f37e8e 100644
--- a/frappe/email/doctype/email_queue/email_queue.py
+++ b/frappe/email/doctype/email_queue/email_queue.py
@@ -407,6 +407,8 @@ def on_doctype_update():
"Email Queue", ("status", "send_after", "priority", "creation"), "index_bulk_flush"
)
+ frappe.db.add_index("Email Queue", ["message_id(140)"])
+
def get_email_retry_limit():
return cint(frappe.db.get_system_setting("email_retry_limit")) or 3
diff --git a/frappe/email/receive.py b/frappe/email/receive.py
index 538ab7738d..59d41b543f 100644
--- a/frappe/email/receive.py
+++ b/frappe/email/receive.py
@@ -782,7 +782,7 @@ class InboundMail(Email):
Here are the cases to handle:
1. If mail is a reply to already sent mail, then we can get parent communicaion from
- Email Queue record.
+ Email Queue record or message_id on communication.
2. Sometimes we send communication name in message-ID directly, use that to get parent communication.
3. Sender sent a reply but reply is on top of what (s)he sent before,
then parent record exists directly in communication.
@@ -795,17 +795,15 @@ class InboundMail(Email):
if not self.is_reply():
return ""
- if not self.is_reply_to_system_sent_mail():
- communication = Communication.find_one_by_filters(
- message_id=self.in_reply_to, creation=[">=", self.get_relative_dt(-30)]
- )
- elif self.parent_email_queue() and self.parent_email_queue().communication:
- communication = Communication.find(self.parent_email_queue().communication, ignore_error=True)
- else:
- reference = self.in_reply_to
- if "@" in self.in_reply_to:
- reference, _ = self.in_reply_to.split("@", 1)
- communication = Communication.find(reference, ignore_error=True)
+ communication = Communication.find_one_by_filters(message_id=self.in_reply_to)
+ if not communication:
+ if self.parent_email_queue() and self.parent_email_queue().communication:
+ communication = Communication.find(self.parent_email_queue().communication, ignore_error=True)
+ else:
+ reference = self.in_reply_to
+ if "@" in self.in_reply_to:
+ reference, _ = self.in_reply_to.split("@", 1)
+ communication = Communication.find(reference, ignore_error=True)
self._parent_communication = communication or ""
return self._parent_communication
From 035f7f93df2c083c152f0d77ffd205d11da8548e Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 16 Mar 2023 15:56:19 +0530
Subject: [PATCH 033/120] fix: skip InReadOnlyMode in error snapshots (#20358)
[skip ci]
---
frappe/utils/error.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/frappe/utils/error.py b/frappe/utils/error.py
index 235a9b3e67..2c450750e1 100644
--- a/frappe/utils/error.py
+++ b/frappe/utils/error.py
@@ -22,6 +22,7 @@ EXCLUDE_EXCEPTIONS = (
frappe.CSRFTokenError, # CSRF covers OAuth too
frappe.SecurityException,
LDAPException,
+ frappe.InReadOnlyMode,
)
From ba99f746609d279fbb749343181e1688f2996ca9 Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Thu, 16 Mar 2023 16:00:06 +0530
Subject: [PATCH 034/120] fix: handle image extraction while editing comment
---
frappe/desk/form/utils.py | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py
index 9e10ced912..28377572c3 100644
--- a/frappe/desk/form/utils.py
+++ b/frappe/desk/form/utils.py
@@ -57,7 +57,14 @@ def update_comment(name, content):
if frappe.session.user not in ["Administrator", doc.owner]:
frappe.throw(_("Comment can only be edited by the owner"), frappe.PermissionError)
- doc.content = content
+ if doc.reference_doctype and doc.reference_name:
+ reference_doc = frappe.get_doc(doc.reference_doctype, doc.reference_name)
+ reference_doc.check_permission()
+
+ doc.content = extract_images_from_html(reference_doc, content, is_private=True)
+ else:
+ doc.content = content
+
doc.save(ignore_permissions=True)
From 0743225cd2817941b2e3defb43fd2559406db92a Mon Sep 17 00:00:00 2001
From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com>
Date: Thu, 16 Mar 2023 16:38:52 +0530
Subject: [PATCH 035/120] fix(patch): move desk prop patch to post model sync
(#20361)
* fix(patch): Reload user_email to avoid failure
* fix: move patch to post sync
---------
Co-authored-by: Ankush Menat
[Skip ci]
---
.../doctype/role/patches/v13_set_default_desk_properties.py | 2 --
frappe/patches.txt | 2 +-
2 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/frappe/core/doctype/role/patches/v13_set_default_desk_properties.py b/frappe/core/doctype/role/patches/v13_set_default_desk_properties.py
index 87de6ac79a..22854fa5f8 100644
--- a/frappe/core/doctype/role/patches/v13_set_default_desk_properties.py
+++ b/frappe/core/doctype/role/patches/v13_set_default_desk_properties.py
@@ -4,8 +4,6 @@ from ..role import desk_properties
def execute():
- frappe.reload_doctype("user")
- frappe.reload_doctype("role")
for role in frappe.get_all("Role", ["name", "desk_access"]):
role_doc = frappe.get_doc("Role", role.name)
for key in desk_properties:
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 9ebb32fea0..ab05ca673d 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -162,7 +162,6 @@ execute:frappe.delete_doc("DocType", "Footer Item")
execute:frappe.reload_doctype('user')
execute:frappe.reload_doctype('docperm')
frappe.patches.v13_0.replace_field_target_with_open_in_new_tab
-frappe.core.doctype.role.patches.v13_set_default_desk_properties
frappe.patches.v13_0.add_switch_theme_to_navbar_settings
frappe.patches.v13_0.update_icons_in_customized_desk_pages
execute:frappe.db.set_default('desktop:home_page', 'space')
@@ -199,6 +198,7 @@ frappe.patches.v15_0.remove_event_streaming
frappe.patches.v15_0.copy_disable_prepared_report_to_prepared_report
[post_model_sync]
+frappe.core.doctype.role.patches.v13_set_default_desk_properties
frappe.patches.v14_0.drop_data_import_legacy
frappe.patches.v14_0.copy_mail_data #08.03.21
frappe.patches.v14_0.update_github_endpoints #08-11-2021
From d7ab46f7f815ec70cb4359e487d4102bc3c41cec Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Thu, 16 Mar 2023 17:16:32 +0530
Subject: [PATCH 036/120] fix: allow 5 column layout in doctype form
---
frappe/public/js/frappe/form/column.js | 7 ++++++-
frappe/public/scss/desk/form.scss | 15 +++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/form/column.js b/frappe/public/js/frappe/form/column.js
index 3ea4296a94..317679f829 100644
--- a/frappe/public/js/frappe/form/column.js
+++ b/frappe/public/js/frappe/form/column.js
@@ -38,7 +38,12 @@ export default class Column {
resize_all_columns() {
// distribute all columns equally
- let colspan = cint(12 / this.section.wrapper.find(".form-column").length);
+ let columns = this.section.wrapper.find(".form-column").length;
+ let colspan = cint(12 / columns);
+
+ if (columns == 5) {
+ colspan = 20;
+ }
this.section.wrapper
.find(".form-column")
diff --git a/frappe/public/scss/desk/form.scss b/frappe/public/scss/desk/form.scss
index 516bc699c6..837f32f184 100644
--- a/frappe/public/scss/desk/form.scss
+++ b/frappe/public/scss/desk/form.scss
@@ -409,6 +409,21 @@
}
}
+// handle 5 columns in form
+.form-column.col-sm-20 {
+ position: relative;
+ width: 100%;
+ padding-right: 15px;
+ padding-left: 15px;
+}
+
+@media (min-width: map-get($grid-breakpoints, "sm")) {
+ .form-column.col-sm-20 {
+ flex: 0 0 20%;
+ max-width: 20%;
+ }
+}
+
// above mobile
@media (min-width: map-get($grid-breakpoints, "md")) {
.layout-main .form-column.col-sm-12 > form > .input-max-width {
From f2f7d43811d3c90176594790b425cefdf8a429fd Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 16 Mar 2023 20:07:43 +0530
Subject: [PATCH 037/120] fix: tolerant newsletter view logging (#20369)
* fix: tolerant newsletter view logging
* Revert "fix: tolerant newsletter view logging"
This reverts commit 5a8567a9405f336c3dc8a481c27af70860334b2b.
* fix: use raw query for updating count
---
frappe/email/doctype/newsletter/newsletter.py | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py
index b5f07aa59c..43f767133c 100644
--- a/frappe/email/doctype/newsletter/newsletter.py
+++ b/frappe/email/doctype/newsletter/newsletter.py
@@ -377,14 +377,17 @@ def send_scheduled_email():
def newsletter_email_read(recipient_email, reference_doctype, reference_name):
verify_request()
try:
- doc = frappe.get_doc(reference_doctype, reference_name)
+ doc = frappe.get_cached_doc("Newsletter", reference_name)
if doc.add_viewed(recipient_email, force=True, unique_views=True):
- doc.db_set("total_views", frappe.utils.cint(doc.total_views) + 1, update_modified=False)
+ newsletter = frappe.qb.DocType("Newsletter")
+ (
+ frappe.qb.update(newsletter)
+ .set(newsletter.total_views, newsletter.total_views + 1)
+ .where(newsletter.name == doc.name)
+ ).run()
except Exception:
- frappe.log_error(
- f"Unable to mark as viewed for {recipient_email}", None, reference_doctype, reference_name
- )
+ doc.log_error(f"Unable to mark as viewed for {recipient_email}")
finally:
frappe.response.update(frappe.utils.get_imaginary_pixel_response())
From cad9228b6b8c5fb6f447147237ad9200dcf9bed8 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 17 Mar 2023 14:34:17 +0530
Subject: [PATCH 038/120] feat: clear integration log request logs (#20373)
* fix: integration request should be cleared
THese are basically logs of requests and shouldn't stay forever.
* fix(UX): show retention policy message on sidebar
These doctypes were added to log settings later.
---
frappe/core/doctype/access_log/access_log_list.js | 7 +++++++
frappe/core/doctype/log_settings/log_settings.py | 1 +
.../core/doctype/prepared_report/prepared_report_list.js | 7 +++++++
.../doctype/integration_request/integration_request.py | 7 +++++++
.../integration_request/integration_request_list.js | 7 +++++++
.../webhook_request_log/webhook_request_log_list.js | 7 +++++++
6 files changed, 36 insertions(+)
create mode 100644 frappe/core/doctype/access_log/access_log_list.js
create mode 100644 frappe/core/doctype/prepared_report/prepared_report_list.js
create mode 100644 frappe/integrations/doctype/integration_request/integration_request_list.js
create mode 100644 frappe/integrations/doctype/webhook_request_log/webhook_request_log_list.js
diff --git a/frappe/core/doctype/access_log/access_log_list.js b/frappe/core/doctype/access_log/access_log_list.js
new file mode 100644
index 0000000000..dab5f083cb
--- /dev/null
+++ b/frappe/core/doctype/access_log/access_log_list.js
@@ -0,0 +1,7 @@
+frappe.listview_settings["Access Log"] = {
+ onload: function (list_view) {
+ frappe.require("logtypes.bundle.js", () => {
+ frappe.utils.logtypes.show_log_retention_message(list_view.doctype);
+ });
+ },
+};
diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py
index 4a3b457bcc..832be49f3c 100644
--- a/frappe/core/doctype/log_settings/log_settings.py
+++ b/frappe/core/doctype/log_settings/log_settings.py
@@ -20,6 +20,7 @@ DEFAULT_LOGTYPES_RETENTION = {
"Submission Queue": 30,
"Prepared Report": 30,
"Webhook Request Log": 30,
+ "Integration Request": 90,
"Reminder": 30,
}
diff --git a/frappe/core/doctype/prepared_report/prepared_report_list.js b/frappe/core/doctype/prepared_report/prepared_report_list.js
new file mode 100644
index 0000000000..d0565fe826
--- /dev/null
+++ b/frappe/core/doctype/prepared_report/prepared_report_list.js
@@ -0,0 +1,7 @@
+frappe.listview_settings["Prepared Report"] = {
+ onload: function (list_view) {
+ frappe.require("logtypes.bundle.js", () => {
+ frappe.utils.logtypes.show_log_retention_message(list_view.doctype);
+ });
+ },
+};
diff --git a/frappe/integrations/doctype/integration_request/integration_request.py b/frappe/integrations/doctype/integration_request/integration_request.py
index 334736bc9b..7ca185bd70 100644
--- a/frappe/integrations/doctype/integration_request/integration_request.py
+++ b/frappe/integrations/doctype/integration_request/integration_request.py
@@ -13,6 +13,13 @@ class IntegrationRequest(Document):
if self.flags._name:
self.name = self.flags._name
+ def clear_old_logs(days=30):
+ from frappe.query_builder import Interval
+ from frappe.query_builder.functions import Now
+
+ table = frappe.qb.DocType("Integration Request")
+ frappe.db.delete(table, filters=(table.modified < (Now() - Interval(days=days))))
+
def update_status(self, params, status):
data = json.loads(self.data)
data.update(params)
diff --git a/frappe/integrations/doctype/integration_request/integration_request_list.js b/frappe/integrations/doctype/integration_request/integration_request_list.js
new file mode 100644
index 0000000000..9aede34f29
--- /dev/null
+++ b/frappe/integrations/doctype/integration_request/integration_request_list.js
@@ -0,0 +1,7 @@
+frappe.listview_settings["Integration Request"] = {
+ onload: function (list_view) {
+ frappe.require("logtypes.bundle.js", () => {
+ frappe.utils.logtypes.show_log_retention_message(list_view.doctype);
+ });
+ },
+};
diff --git a/frappe/integrations/doctype/webhook_request_log/webhook_request_log_list.js b/frappe/integrations/doctype/webhook_request_log/webhook_request_log_list.js
new file mode 100644
index 0000000000..dd4e215157
--- /dev/null
+++ b/frappe/integrations/doctype/webhook_request_log/webhook_request_log_list.js
@@ -0,0 +1,7 @@
+frappe.listview_settings["Webhook Request Log"] = {
+ onload: function (list_view) {
+ frappe.require("logtypes.bundle.js", () => {
+ frappe.utils.logtypes.show_log_retention_message(list_view.doctype);
+ });
+ },
+};
From 7ed43e2a68082ffa36933a0abf97fc7002696944 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 17 Mar 2023 15:57:51 +0530
Subject: [PATCH 039/120] fix: Drop message_id index before migrating email
queue (#20376)
[skip c]
---
frappe/email/doctype/email_queue/patches/__init__.py | 0
.../patches/drop_search_index_on_message_id.py | 11 +++++++++++
frappe/patches.txt | 1 +
3 files changed, 12 insertions(+)
create mode 100644 frappe/email/doctype/email_queue/patches/__init__.py
create mode 100644 frappe/email/doctype/email_queue/patches/drop_search_index_on_message_id.py
diff --git a/frappe/email/doctype/email_queue/patches/__init__.py b/frappe/email/doctype/email_queue/patches/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/email/doctype/email_queue/patches/drop_search_index_on_message_id.py b/frappe/email/doctype/email_queue/patches/drop_search_index_on_message_id.py
new file mode 100644
index 0000000000..7c4baf5a2a
--- /dev/null
+++ b/frappe/email/doctype/email_queue/patches/drop_search_index_on_message_id.py
@@ -0,0 +1,11 @@
+import frappe
+
+
+def execute():
+ """Drop search index on message_id"""
+
+ if frappe.db.get_column_type("Email Queue", "message_id") == "text":
+ return
+
+ if index := frappe.db.get_column_index("tabEmail Queue", "message_id", unique=False):
+ frappe.db.sql(f"ALTER TABLE `tabEmail Queue` DROP INDEX `{index.Key_name}`")
diff --git a/frappe/patches.txt b/frappe/patches.txt
index ab05ca673d..da83094961 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -183,6 +183,7 @@ frappe.patches.v13_0.encrypt_2fa_secrets
frappe.patches.v13_0.reset_corrupt_defaults
frappe.patches.v13_0.remove_share_for_std_users
execute:frappe.reload_doc('custom', 'doctype', 'custom_field')
+frappe.email.doctype.email_queue.patches.drop_search_index_on_message_id
frappe.patches.v14_0.update_workspace2 # 20.09.2021
frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021
frappe.patches.v14_0.transform_todo_schema
From 98006eb5fb3c74b42e8252052807c06667556ab1 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Mon, 20 Mar 2023 06:27:40 +0000
Subject: [PATCH 040/120] chore: fix typo in error dump (#20393)
[skip ci]
---
frappe/public/js/frappe/request.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js
index dae436a84a..47ca1f7548 100644
--- a/frappe/public/js/frappe/request.js
+++ b/frappe/public/js/frappe/request.js
@@ -529,7 +529,7 @@ frappe.request.report_error = function (xhr, request_opts) {
code_block(JSON.stringify(frappe.boot.versions, null, "\t")),
"### Route",
code_block(frappe.get_route_str()),
- "### Trackeback",
+ "### Traceback",
code_block(exc),
"### Request Data",
code_block(JSON.stringify(request_opts, null, "\t")),
From 14105e08164aa097aaf1b49e11706a29dcaada40 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Wed, 25 Jan 2023 15:23:47 +0530
Subject: [PATCH 041/120] fix: refresh access token for dropbox
- removed dropbox_access_token field
- added dropbox_refresh_token
- removed oauth2 access token generation from oauth1 logic
- removed code for dropbox_erpnext_broker
Auth logic has been changed a bit to generate access token(s) on the fly when taking backup from refresh token. This is due to the fact that backups are generally taken between long intervals which is generally greater than the access token expiry time.
---
.../dropbox_settings/dropbox_settings.js | 55 ++++----
.../dropbox_settings/dropbox_settings.json | 14 +-
.../dropbox_settings/dropbox_settings.py | 125 +++++-------------
3 files changed, 65 insertions(+), 129 deletions(-)
diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.js b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.js
index 9a5e9a4dc7..2b8b5c2db1 100644
--- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.js
+++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.js
@@ -5,50 +5,43 @@ frappe.ui.form.on("Dropbox Settings", {
refresh: function (frm) {
frm.toggle_display(
["app_access_key", "app_secret_key"],
- !(frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config)
+ !frm.doc.__onload?.dropbox_setup_via_site_config
);
- frm.clear_custom_buttons();
frm.events.take_backup(frm);
},
+ are_keys_present: function (frm) {
+ return (
+ (frm.doc.app_access_key && frm.doc.app_secret_key) ||
+ frm.doc.__onload?.dropbox_setup_via_site_config
+ );
+ },
+
allow_dropbox_access: function (frm) {
- if (frm.doc.app_access_key && frm.doc.app_secret_key) {
- frappe.call({
- method: "frappe.integrations.doctype.dropbox_settings.dropbox_settings.get_dropbox_authorize_url",
- freeze: true,
- callback: function (r) {
- if (!r.exc) {
- window.open(r.message.auth_url);
- }
- },
- });
- } else if (frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config) {
- frappe.call({
- method: "frappe.integrations.doctype.dropbox_settings.dropbox_settings.get_redirect_url",
- freeze: true,
- callback: function (r) {
- if (!r.exc) {
- window.open(r.message.auth_url);
- }
- },
- });
- } else {
- frappe.msgprint(__("Please enter values for App Access Key and App Secret Key"));
+ if (!frm.events.are_keys_present(frm)) {
+ frappe.msgprint(__("No App Access Key and Secret Key are present."));
+ return;
}
+
+ frappe.call({
+ method: "frappe.integrations.doctype.dropbox_settings.dropbox_settings.get_dropbox_authorize_url",
+ freeze: true,
+ callback: function (r) {
+ if (!r.exc) {
+ window.open(r.message.auth_url);
+ }
+ },
+ });
},
take_backup: function (frm) {
- if (
- frm.doc.enabled &&
- ((frm.doc.app_access_key && frm.doc.app_secret_key) ||
- (frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config))
- ) {
- frm.add_custom_button(__("Take Backup Now"), function (frm) {
+ if (frm.doc.enabled && frm.doc.dropbox_refresh_token) {
+ frm.add_custom_button(__("Take Backup Now"), function () {
frappe.call({
method: "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backup",
freeze: true,
});
- }).addClass("btn-primary");
+ });
}
},
});
diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json
index 858469647a..664e51df90 100644
--- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json
+++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json
@@ -1,8 +1,10 @@
{
+ "actions": [],
"creation": "2016-09-21 10:12:57.399174",
"doctype": "DocType",
"document_type": "System",
"editable_grid": 1,
+ "engine": "InnoDB",
"field_order": [
"enabled",
"send_notifications_to",
@@ -16,7 +18,7 @@
"allow_dropbox_access",
"dropbox_access_key",
"dropbox_access_secret",
- "dropbox_access_token"
+ "dropbox_refresh_token"
],
"fields": [
{
@@ -96,15 +98,18 @@
"read_only": 1
},
{
- "fieldname": "dropbox_access_token",
+ "fieldname": "dropbox_refresh_token",
"fieldtype": "Password",
"hidden": 1,
- "label": "Dropbox Access Token"
+ "label": "Dropbox Refresh Token",
+ "no_copy": 1,
+ "read_only": 1
}
],
"in_create": 1,
"issingle": 1,
- "modified": "2019-08-22 16:26:44.468391",
+ "links": [],
+ "modified": "2023-01-26 20:43:14.458823",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Dropbox Settings",
@@ -125,5 +130,6 @@
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
index e6998a9d6d..edf5f5e923 100644
--- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
+++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
@@ -1,7 +1,6 @@
# Copyright (c) 2015, Frappe Technologies and contributors
# License: MIT. See LICENSE
-import json
import os
from urllib.parse import parse_qs, urlparse
@@ -16,16 +15,8 @@ from frappe.integrations.offsite_backup_utils import (
send_email,
validate_file_size,
)
-from frappe.integrations.utils import make_post_request
from frappe.model.document import Document
-from frappe.utils import (
- cint,
- encode,
- get_backups_path,
- get_files_path,
- get_request_site_address,
- get_url,
-)
+from frappe.utils import cint, encode, get_backups_path, get_files_path, get_request_site_address
from frappe.utils.background_jobs import enqueue
from frappe.utils.backups import new_backup
@@ -106,22 +97,13 @@ def backup_to_dropbox(upload_db_backup=True):
# upload database
dropbox_settings = get_dropbox_settings()
-
- if not dropbox_settings["access_token"]:
- access_token = generate_oauth2_access_token_from_oauth1_token(dropbox_settings)
-
- if not access_token.get("oauth2_token"):
- return (
- "Failed backup upload",
- "No Access Token exists! Please generate the access token for Dropbox.",
- )
-
- dropbox_settings["access_token"] = access_token["oauth2_token"]
- set_dropbox_access_token(access_token["oauth2_token"])
-
dropbox_client = dropbox.Dropbox(
- oauth2_access_token=dropbox_settings["access_token"], timeout=None
+ oauth2_refresh_token=dropbox_settings["refresh_token"],
+ app_key=dropbox_settings["app_key"],
+ app_secret=dropbox_settings["app_secret"],
+ timeout=None,
)
+ dropbox_client.refresh_access_token()
if upload_db_backup:
if frappe.flags.create_new_backup:
@@ -267,24 +249,17 @@ def get_uploaded_files_meta(dropbox_folder, dropbox_client):
# folder not found
if isinstance(e.error, dropbox.files.ListFolderError):
return frappe._dict({"entries": []})
- else:
- raise
+ raise
def get_dropbox_settings(redirect_uri=False):
- if not frappe.conf.dropbox_broker_site:
- frappe.conf.dropbox_broker_site = "https://dropbox.erpnext.com"
settings = frappe.get_doc("Dropbox Settings")
app_details = {
"app_key": settings.app_access_key or frappe.conf.dropbox_access_key,
"app_secret": settings.get_password(fieldname="app_secret_key", raise_exception=False)
if settings.app_secret_key
else frappe.conf.dropbox_secret_key,
- "access_token": settings.get_password("dropbox_access_token", raise_exception=False)
- if settings.dropbox_access_token
- else "",
- "access_key": settings.get_password("dropbox_access_key", raise_exception=False),
- "access_secret": settings.get_password("dropbox_access_secret", raise_exception=False),
+ "refresh_token": settings.get_password("dropbox_refresh_token", raise_exception=False),
"file_backup": settings.file_backup,
"no_of_backups": settings.no_of_backups if settings.limit_no_of_backups else None,
}
@@ -294,14 +269,11 @@ def get_dropbox_settings(redirect_uri=False):
{
"redirect_uri": get_request_site_address(True)
+ "/api/method/frappe.integrations.doctype.dropbox_settings.dropbox_settings.dropbox_auth_finish"
- if settings.app_secret_key
- else frappe.conf.dropbox_broker_site
- + "/api/method/dropbox_erpnext_broker.www.setup_dropbox.generate_dropbox_access_token",
}
)
- if not app_details["app_key"] or not app_details["app_secret"]:
- raise Exception(_("Please set Dropbox access keys in your site config"))
+ if not (app_details["app_key"] and app_details["app_secret"]):
+ raise Exception(_("Please set Dropbox access keys in site config or doctype"))
return app_details
@@ -321,28 +293,6 @@ def delete_older_backups(dropbox_client, folder_path, to_keep):
dropbox_client.files_delete(os.path.join(folder_path, f.name))
-@frappe.whitelist()
-def get_redirect_url():
- if not frappe.conf.dropbox_broker_site:
- frappe.conf.dropbox_broker_site = "https://dropbox.erpnext.com"
- url = "{}/api/method/dropbox_erpnext_broker.www.setup_dropbox.get_authotize_url".format(
- frappe.conf.dropbox_broker_site
- )
-
- try:
- response = make_post_request(url, data={"site": get_url()})
- if response.get("message"):
- return response["message"]
-
- except Exception:
- frappe.log_error()
- frappe.throw(
- _(
- "Something went wrong while generating dropbox access token. Please check error log for more details."
- )
- )
-
-
@frappe.whitelist()
def get_dropbox_authorize_url():
app_details = get_dropbox_settings(redirect_uri=True)
@@ -352,6 +302,7 @@ def get_dropbox_authorize_url():
session={},
csrf_token_session_key="dropbox-auth-csrf-token",
consumer_secret=app_details["app_secret"],
+ token_access_type="offline",
)
auth_url = dropbox_oauth_flow.start()
@@ -360,11 +311,20 @@ def get_dropbox_authorize_url():
@frappe.whitelist()
-def dropbox_auth_finish(return_access_token=False):
+def dropbox_auth_finish():
app_details = get_dropbox_settings(redirect_uri=True)
callback = frappe.form_dict
close = '
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index d76d97f7e0..460ca26d85 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -2256,11 +2256,20 @@ def is_site_link(link: str) -> bool:
return urlparse(link).netloc == urlparse(frappe.utils.get_url()).netloc
-def add_source_to_url(url: str, reference_doctype: str, reference_docname: str) -> str:
+def add_trackers_to_url(url: str, source: str, campaign: str, medium: str = "email") -> str:
url_parts = list(urlparse(url))
- query = dict(parse_qsl(url_parts[4])) | {
- "source": f"{reference_doctype} > {reference_docname}",
+ if url_parts[0] == "mailto":
+ return url
+
+ trackers = {
+ "source": source,
+ "medium": medium,
}
+ if campaign:
+ trackers["campaign"] = campaign
+
+ query = dict(parse_qsl(url_parts[4])) | trackers
+
url_parts[4] = urlencode(query)
return urlunparse(url_parts)
diff --git a/frappe/website/doctype/marketing_campaign/__init__.py b/frappe/website/doctype/marketing_campaign/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/website/doctype/marketing_campaign/marketing_campaign.json b/frappe/website/doctype/marketing_campaign/marketing_campaign.json
new file mode 100644
index 0000000000..0a5fc45b29
--- /dev/null
+++ b/frappe/website/doctype/marketing_campaign/marketing_campaign.json
@@ -0,0 +1,64 @@
+{
+ "actions": [],
+ "autoname": "prompt",
+ "creation": "2023-03-20 22:36:45.058045",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "campaign_description"
+ ],
+ "fields": [
+ {
+ "allow_in_quick_entry": 1,
+ "fieldname": "campaign_description",
+ "fieldtype": "Small Text",
+ "in_filter": 1,
+ "in_list_view": 1,
+ "label": "Campaign Description (Optional)"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2023-03-20 22:47:25.768582",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "Marketing Campaign",
+ "naming_rule": "Set by user",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Newsletter Manager",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "role": "All",
+ "select": 1
+ }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/frappe/website/doctype/marketing_campaign/marketing_campaign.py b/frappe/website/doctype/marketing_campaign/marketing_campaign.py
new file mode 100644
index 0000000000..ef23a182e7
--- /dev/null
+++ b/frappe/website/doctype/marketing_campaign/marketing_campaign.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class MarketingCampaign(Document):
+ pass
From 1abcd5a11a6336ae5408b61ea8d608e2f3ad6b82 Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Mon, 20 Mar 2023 23:53:42 +0530
Subject: [PATCH 050/120] feat: Add a simple tool to generate tracking URL
---
frappe/public/js/frappe/ui/toolbar/toolbar.js | 8 +--
frappe/public/js/frappe/utils/utils.js | 52 +++++++++++++++++++
2 files changed, 56 insertions(+), 4 deletions(-)
diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js
index 8bf8f36f7a..419a22d764 100644
--- a/frappe/public/js/frappe/ui/toolbar/toolbar.js
+++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js
@@ -129,10 +129,10 @@ frappe.ui.toolbar.Toolbar = class {
let awesome_bar = new frappe.search.AwesomeBar();
awesome_bar.setup("#navbar-search");
- // TODO: Remove this in v14
- frappe.search.utils.make_function_searchable(function () {
- frappe.set_route("List", "Client Script");
- }, __("Custom Script List"));
+ frappe.search.utils.make_function_searchable(
+ frappe.utils.generate_tracking_url,
+ __("Generate Tracking URL")
+ );
}
}
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index 594da353e6..f849373229 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -1610,4 +1610,56 @@ Object.assign(frappe.utils, {
});
},
},
+ generate_tracking_url() {
+ frappe.prompt(
+ [
+ {
+ fieldname: "url",
+ label: __("Web Page URL"),
+ fieldtype: "Data",
+ options: "URL",
+ reqd: 1,
+ },
+ {
+ fieldname: "source",
+ label: __("Source"),
+ fieldtype: "Data",
+ },
+ {
+ fieldname: "campaign",
+ label: __("Campaign"),
+ fieldtype: "Link",
+ ignore_link_validation: 1,
+ options: "Marketing Campaign",
+ },
+ {
+ fieldname: "medium",
+ label: __("Medium"),
+ fieldtype: "Data",
+ },
+ ],
+ function (data) {
+ let url = data.url;
+ if (data.source) {
+ url += "?source=" + data.source;
+ }
+ if (data.campaign) {
+ url += "&campaign=" + data.campaign;
+ }
+ if (data.medium) {
+ url += "&medium=" + data.medium.toLowerCase();
+ }
+
+ frappe.utils.copy_to_clipboard(url);
+
+ frappe.msgprint(
+ __("Tracking URL generated and copied to clipboard") +
+ ": " +
+ `${url.bold()}`,
+ __("Here's your tracking URL")
+ );
+ },
+ __("Generate Tracking URL")
+ );
+ },
});
From 02b661bbb9b85a7e73e93844d6b70a4ea6dd57fd Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Tue, 21 Mar 2023 11:31:05 +0530
Subject: [PATCH 051/120] fix: Remove mandatory and save URL params to
localstorage
- Saving URL params to localstorage to avoid re-entering the data.
Usually only 1 or 2 param(s) change is required to generate new link
---
frappe/email/doctype/newsletter/newsletter.json | 2 +-
frappe/public/js/frappe/utils/utils.js | 9 +++++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json
index cd292bebbd..7ac6203ada 100644
--- a/frappe/email/doctype/newsletter/newsletter.json
+++ b/frappe/email/doctype/newsletter/newsletter.json
@@ -244,7 +244,7 @@
"fieldtype": "Link",
"label": "Campaign",
"options": "Marketing Campaign",
- "reqd": 1
+ "reqd": 0
}
],
"has_web_view": 1,
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index f849373229..ac9a18785b 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -1619,11 +1619,13 @@ Object.assign(frappe.utils, {
fieldtype: "Data",
options: "URL",
reqd: 1,
+ default: localStorage.getItem("tracker_url:url"),
},
{
fieldname: "source",
label: __("Source"),
fieldtype: "Data",
+ default: localStorage.getItem("tracker_url:source"),
},
{
fieldname: "campaign",
@@ -1631,23 +1633,30 @@ Object.assign(frappe.utils, {
fieldtype: "Link",
ignore_link_validation: 1,
options: "Marketing Campaign",
+ default: localStorage.getItem("tracker_url:campaign"),
},
{
fieldname: "medium",
label: __("Medium"),
fieldtype: "Data",
+ default: localStorage.getItem("tracker_url:medium"),
},
],
function (data) {
let url = data.url;
+ localStorage.setItem("tracker_url:url", data.url);
+
if (data.source) {
url += "?source=" + data.source;
+ localStorage.setItem("tracker_url:source", data.source);
}
if (data.campaign) {
url += "&campaign=" + data.campaign;
+ localStorage.setItem("tracker_url:campaign", data.campaign);
}
if (data.medium) {
url += "&medium=" + data.medium.toLowerCase();
+ localStorage.setItem("tracker_url:medium", data.medium);
}
frappe.utils.copy_to_clipboard(url);
From c5cfe8f5aad1df9f0fc34423ceba2d552ccf66cb Mon Sep 17 00:00:00 2001
From: Bernhard Sirlinger
Date: Tue, 21 Mar 2023 08:25:03 +0100
Subject: [PATCH 052/120] feat(minor): log datetime in worker log (#20414)
---
frappe/utils/background_jobs.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py
index 29098dce3b..47a821d8bb 100755
--- a/frappe/utils/background_jobs.py
+++ b/frappe/utils/background_jobs.py
@@ -243,7 +243,12 @@ def start_worker(
if quiet:
logging_level = "WARNING"
worker = WorkerKlass(queues, name=get_worker_name(queue_name))
- worker.work(logging_level=logging_level, burst=burst)
+ worker.work(
+ logging_level=logging_level,
+ burst=burst,
+ date_format="%Y-%m-%d %H:%M:%S",
+ log_format="%(asctime)s,%(msecs)03d %(message)s",
+ )
def get_worker_name(queue):
From 7fc6ae65aba13f277df98772f7ec65cea8c7e0cf Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 21 Mar 2023 14:11:26 +0530
Subject: [PATCH 053/120] perf: Dont update list view data if list view not
active (#20396)
Steps to reproduce:
1. visit a list view that's quite active
2. move to some other page
3. list view data keeps getting refreshed in background
Fix: Only refresh when user is back on list view
---
frappe/public/js/frappe/list/list_view.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 4b6b1e5bc1..6a6e0ae0c8 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -1354,6 +1354,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
process_document_refreshes() {
if (!this.pending_document_refreshes.length) return;
+ const route = frappe.get_route() || [];
+ if (!cur_list || route[0] != "List" || cur_list.doctype != route[1]) {
+ // wait till user is back on list view before refreshing
+ this.pending_document_refreshes = [];
+ return;
+ }
+
const names = this.pending_document_refreshes.map((d) => d.name);
this.pending_document_refreshes = this.pending_document_refreshes.filter(
(d) => names.indexOf(d.name) === -1
From ef11d67bb3a1feaf5e0759f5de3adfc5917ec00d Mon Sep 17 00:00:00 2001
From: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Date: Tue, 21 Mar 2023 12:34:28 +0100
Subject: [PATCH 054/120] chore: translations of "Login" and "Signup" (german)
(#20409)
---
frappe/translations/de.csv | 49 ++++++++++++++++++--------------------
1 file changed, 23 insertions(+), 26 deletions(-)
diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv
index a6aef52b1c..c60fcbb41e 100644
--- a/frappe/translations/de.csv
+++ b/frappe/translations/de.csv
@@ -38,8 +38,8 @@ Category Name,Kategoriename,
City,Ort,
City/Town,Ort/ Wohnort,
Client,Kunde,
-Client ID,Kunden-ID,
-Client Secret,Kundengeheimnis,
+Client ID,Client ID,
+Client Secret,Client Secret,
Closed,Geschlossen,
Code,Code,
Collapse All,Alles schließen,
@@ -924,7 +924,6 @@ Domains HTML,Domänen HTML,
"Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field","Nicht HTML Encode HTML-Tags wie <script> oder einfach nur Zeichen wie <oder>, da sie absichtlich auf diesem Gebiet verwendet werden könnten,",
Don't Override Status,Status nicht überschreiben,
Don't create new records,Keine neuen Datensätze erstellen,
-Don't have an account? Sign up,Sie haben noch kein Konto? Erstell' ein Konto,
"Don't know, ask 'help'","Sie wissen nicht, fragen Sie "Hilfe"",
Download Data,Daten herunterladen,
Download Files Backup,Dateiensicherung herunterladen,
@@ -1004,7 +1003,7 @@ Enable Print Server,Aktivieren Sie den Druckserver,
Enable Raw Printing,Aktivieren Sie den RAW-Druck,
Enable Report,Bericht aktivieren,
Enable Scheduled Jobs,Geplante Arbeiten aktivieren,
-Enable Social Login,Aktivieren Sie die soziale Anmeldung,
+Enable Social Login,Aktivieren Sie Social Logins,
Enable Two Factor Auth,Aktivieren Sie zwei Faktor Auth,
Enabled email inbox for user {0},Aktivierter E-Mail-Posteingang für Benutzer {0},
"Encryption key is invalid, Please check site_config.json","Verschlüsselungsschlüssel ist ungültig, bitte check site_config.json",
@@ -1217,7 +1216,7 @@ Has Attachments,Hat Anhänge,
Has Domain,Hat Domain,
Has Role,hat Rolle,
Has Web View,Hat Webansicht,
-Have an account? Login,Sie haben bereits ein Konto? Anmeldung,
+Have an account? Login,Sie haben bereits ein Konto? Anmelden,
Header,Kopfzeile,
Header HTML,HTML-Header,
Header HTML set from attachment {0},Header-HTML-Satz aus Anhang {0},
@@ -1321,7 +1320,7 @@ In seconds,In Sekunden,
Include Search in Top Bar,Suchen In Top Bar,
"Include symbols, numbers and capital letters in the password","Geben Sie Symbole, Zahlen und Großbuchstaben in das Passwort ein",
Incoming email account not correct,Falsches Konto für eingehende E-Mails,
-Incomplete login details,Unvollständige Login-Daten,
+Incomplete login details,Unvollständige Anmeldedaten,
Incorrect User or Password,Falscher Benutzer oder Passwort,
Incorrect Verification code,Falscher Bestätigungscode,
Incorrect value in row {0}: {1} must be {2} {3},Falscher Wert in Zeile {0}: {1} muss {2} {3} sein,
@@ -1358,8 +1357,8 @@ Invalid Access Key ID or Secret Access Key.,Ungültige Zugriffsschlüssel-ID ode
Invalid CSV Format,Ungültige CSV-Format,
Invalid Home Page,Ungültige Startseite,
Invalid Link,Ungültige Verknüpfung,
-Invalid Login Token,Invalid Login Token,
-Invalid Login. Try again.,Ungültiger Login. Versuch es noch einmal.,
+Invalid Login Token,Ungültiges Login-Token,
+Invalid Login. Try again.,Ungültige Anmeldedaten. Bitte versuchen Sie es noch einmal.,
Invalid Mail Server. Please rectify and try again.,Ungültiger E-Mail-Server. Bitte Angaben korrigieren und erneut versuchen.,
Invalid Outgoing Mail Server or Port,Ungültiger Postausgangsserver oder Schnittstelle,
Invalid Output Format,Ungültige Ausgabeformat,
@@ -1374,7 +1373,7 @@ Invalid column,Ungültige Spalte,
Invalid field name {0},Ungültiger Feldname {0},
Invalid fieldname '{0}' in autoname,Ungültige Feldname '{0}' in auton,
Invalid file path: {0},Ungültiger Dateipfad: {0},
-Invalid login or password,Ungültige Benutzerkennung oder ungültiges Passwort,
+Invalid login or password,Ungültiger Benutzer oder falsches Passwort,
Invalid module path,Ungültiger Modulpfad,
Invalid naming series (. missing),Ungültige Bezeichnungsserie (. fehlt),
Invalid payment gateway credentials,Ungültige Payment-Gateway-Anmeldeinformationen,
@@ -1514,11 +1513,11 @@ Login Id is required,Benutzer-ID wird benötigt,
Login Required,Anmeldung erforderlich,
Login Verification Code from {},Login-Bestätigungscode von {},
Login and view in Browser,Anmelden und im Browser anzeigen,
-Login not allowed at this time,Anmelden zur Zeit nicht erlaubt,
-"Login session expired, refresh page to retry","Anmeldesitzung abgelaufen, Seite aktualisieren, um es erneut zu versuchen",
-Login to comment,Anmelden um Kommentieren zu können,
+Login not allowed at this time,Anmelden zurzeit nicht erlaubt,
+"Login session expired, refresh page to retry","Sitzung abgelaufen. Aktualisieren Sie die Seite, um es erneut zu versuchen",
+Login to comment,"Anmelden, um Kommentieren zu können",
Login token required,Login-Token erforderlich,
-Login with LDAP,Einloggen mit LDAP,
+Login with LDAP,Mit LDAP anmelden,
Logout,Abmelden,
Long Text,Langer Text,
Looks like something is wrong with this site's Paypal configuration.,Sieht aus wie etwas ist falsch mit dieser Website Paypal-Konfiguration.,
@@ -1575,7 +1574,7 @@ Minimum Password Score,Mindest-Passwort-Score,
Miss,Fräulein,
Missing Fields,Nicht ausgefüllte Felder,
Missing parameter Kanban Board Name,Fehlender Parameter Kanban Board Name,
-Missing parameters for login,Fehlende Parameter für Login,
+Missing parameters for login,Fehlende Parameter für Anmeldung,
Models (building blocks) of the Application,Modelle (Bausteine) der Anwendung,
Modified By,Geändert von,
Module,Modul,
@@ -1726,7 +1725,7 @@ Notification Recipient,Benachrichtigungsempfänger,
Notification Tones,Benachrichtigungstöne,
Notifications,Benachrichtigungen,
Notifications and bulk mails will be sent from this outgoing server.,Hinweise und Massen-E-Mails werden von diesem Postausgangsserver versendet.,
-Notify Users On Every Login,Benachrichtige Benutzer bei jeder Anmeldung,
+Notify Users On Every Login,Benutzer bei jeder Anmeldung benachrichtigen,
Notify if unreplied,"Benachrichtigen, wenn unbeantwortet",
Notify if unreplied for (in mins),"Benachrichtigen, wenn unbeantwortet für (in Minuten)",
Notify users with a popup when they log in,"Benachrichtigen Sie die Benutzer mit einem Pop-up, wenn sie sich in",
@@ -1785,7 +1784,6 @@ Options 'Dynamic Link' type of field must point to another Link Field with optio
Options Help,Hilfe zu Optionen,
Options for select. Each option on a new line.,Optionen zum Auswählen. Jede Option in einer neuen Zeile.,
Options not set for link field {0},Optionen nicht für das Verknüpfungs-Feld {0} gesetzt,
-Or login with,Oder melden Sie sich an mit,
Order,Auftrag,
Org History,Unternehmensgeschichte,
Org History Heading,Überschrift zur Unternehmensgeschichte,
@@ -1880,8 +1878,8 @@ Please ensure that your profile has an email address,"Bitte stellen Sie sicher,
Please enter Access Token URL,Bitte geben Sie die Access Token URL ein,
Please enter Authorize URL,Bitte geben Sie die URL Autorisieren ein,
Please enter Base URL,Bitte geben Sie die Basis-URL ein,
-Please enter Client ID before social login is enabled,"Bitte geben Sie die Kunden-ID ein, bevor die Anmeldung mit sozialen Netzwerken aktiviert ist",
-Please enter Client Secret before social login is enabled,"Bitte geben Sie das Kundengeheimnis ein, bevor die Anmeldung an soziale Netzwerke aktiviert ist",
+Please enter Client ID before social login is enabled,"Bitte geben Sie die Client ID ein, bevor der Social Login aktiviert wird",
+Please enter Client Secret before social login is enabled,"Bitte geben Sie das Client Secret ein, bevor Social Login aktiviert wird",
Please enter Redirect URL,Bitte geben Sie die Weiterleitungs-URL ein,
Please enter the password,Bitte Passwort eingeben,
Please enter valid mobile nos,Bitte gültige Mobilnummern eingeben,
@@ -1906,7 +1904,7 @@ Please select another payment method. Razorpay does not support transactions in
Please select atleast 1 column from {0} to sort/group,Bitte wählen Sie atleast 1 Spalte von {0} sortieren / Gruppe,
Please select document type first.,Bitte wählen Sie zuerst den Dokumententyp.,
Please select the Document Type.,Bitte wählen Sie den Dokumententyp.,
-Please set Base URL in Social Login Key for Frappe,Bitte legen Sie die Basis-URL unter Social Login-Schlüssel für Frappe fest,
+Please set Base URL in Social Login Key for Frappe,Bitte legen Sie die Basis-URL im Social Login Key für Frappe fest,
Please set Dropbox access keys in your site config,Bitte Dropbox-Zugriffsdaten in den Einstellungen der Seite setzen,
Please set a printer mapping for this print format in the Printer Settings,Bitte legen Sie in den Druckereinstellungen eine Druckerzuordnung für dieses Druckformat fest,
Please set filters,Bitte Filter einstellen,
@@ -1992,7 +1990,7 @@ Push Update,Push-Aktualisierung,
Python Module,Python-Modul,
Pyver,Pyver,
QR Code,QR-Code,
-QR Code for Login Verification,QR Code für Login-Bestätigung,
+QR Code for Login Verification,QR-Code für Zwei-Faktor-Authentifizierung,
QZ Tray Connection Active!,QZ-Tray-Verbindung aktiv!,
QZ Tray Failed: ,QZ-Fach fehlgeschlagen:,
Quarter Day,Viertel-Tag,
@@ -2328,8 +2326,7 @@ Showing only Numeric fields from Report,Nur numerische Felder aus Bericht anzeig
Sidebar Items,Elemente der Seitenleiste,
Sidebar Settings,Sidebar-Einstellungen,
Sidebar and Comments,Sidebar und Kommentare,
-Sign Up,Anmelden,
-Sign Up is disabled,Registrieren ist deaktiviert,
+Sign Up is disabled,Die Registrierung ist deaktiviert,
Signature,Signatur,
"Simple Python Expression, Example: Status in (""Closed"", ""Cancelled"")","Einfacher Python-Ausdruck, Beispiel: Status in ("Geschlossen", "Abgebrochen")",
"Simple Python Expression, Example: status == 'Open' and type == 'Bug'","Einfacher Python-Ausdruck, Beispiel: status == 'Open' und type == 'Bug'",
@@ -2353,7 +2350,7 @@ Smallest Currency Fraction Value,Kleinste Währungsanteilwert,
Smallest circulating fraction unit (coin). For e.g. 1 cent for USD and it should be entered as 0.01,"Kleinste zirkulierenden Brucheinheit (Münze). Für zB 1 Cent für USD und es sollte als 0,01 eingegeben werden",
Snapshot View,Schnappschuss-Ansicht,
Social,Sozial,
-Social Login Key,Social Login-Schlüssel,
+Social Login Key,Social Login Key,
Social Login Provider,Social-Login-Anbieter,
Social Logins,Soziale Logins,
Socketio is not connected. Cannot upload,Socketio ist nicht verbunden. Kann nicht hochladen,
@@ -3295,7 +3292,7 @@ Enable Email Notifications,E-Mail-Benachrichtigungen aktivieren,
Enable Google API in Google Settings.,Aktivieren Sie die Google-API in den Google-Einstellungen.,
Enable Security,Sicherheit aktivieren,
Energy Point,Energiepunkt,
-Enter Client Id and Client Secret in Google Settings.,Geben Sie in den Google-Einstellungen die Kunden-ID und das Kundengeheimnis ein.,
+Enter Client Id and Client Secret in Google Settings.,Geben Sie in den Google-Einstellungen die Client ID und das Client Secret ein.,
Enter Code displayed in OTP App.,Geben Sie den in der OTP-App angezeigten Code ein.,
Event Configurations,Ereigniskonfigurationen,
Event Consumer,Ereignis Verbraucher,
@@ -3921,7 +3918,7 @@ Row #,Reihe #,
Scheduled to send,Geplant zum Senden,
Select Doctype,Wählen Sie Doctype,
Send Email for Successful backup,Senden Sie eine E-Mail für eine erfolgreiche Sicherung,
-Sign up,Anmeldung,
+Sign up,Registrieren,
Time format,Zeitformat,
Upload failed,Upload fehlgeschlagen,
User Id,Benutzeridentifikation,
@@ -4502,7 +4499,7 @@ Footer Template Values,Werte für Fußzeilenvorlagen,
Enable Tracking Page Views,Tracking-Seitenaufrufe aktivieren,
"Checking this will enable tracking page views for blogs, web pages, etc.","Wenn Sie dies aktivieren, können Sie Seitenaufrufe für Blogs, Webseiten usw. verfolgen.",
Disable Signup for your site,Deaktivieren Sie die Anmeldung für Ihre Site,
-Check this if you don't want users to sign up for an account on your site. Users won't get desk access unless you explicitly provide it.,"Aktivieren Sie diese Option, wenn Benutzer sich nicht für ein Konto auf Ihrer Website anmelden sollen. Benutzer erhalten keinen Zugriff auf den Schreibtisch, es sei denn, Sie geben dies ausdrücklich an.",
+Check this if you don't want users to sign up for an account on your site. Users won't get desk access unless you explicitly provide it.,"Aktivieren Sie diese Option, wenn Benutzer sich nicht für ein Konto auf Ihrer Website registrieren sollen. Benutzer erhalten keinen Zugriff auf die Anwendung, es sei denn, Sie geben dies ausdrücklich frei.",
URL to go to on clicking the slideshow image,URL zum Aufrufen des Diashow-Bildes,
Custom Overrides,Benutzerdefinierte Überschreibungen,
Ignored Apps,Ignorierte Apps,
From 516540ede9bee9b0c5e61c3984376e9daeb190ce Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 22 Mar 2023 10:02:37 +0530
Subject: [PATCH 055/120] perf: unsubscribe from list_update events (#20423)
---
frappe/public/js/frappe/list/list_view.js | 10 +++++++++-
frappe/public/js/frappe/socketio_client.js | 3 +++
socketio.js | 4 ++++
3 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 6a6e0ae0c8..59784b853e 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -308,6 +308,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.render_header(refresh_header);
this.update_checkbox();
this.update_url_with_filters();
+ this.setup_realtime_updates();
});
}
@@ -1329,7 +1330,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
setup_realtime_updates() {
this.pending_document_refreshes = [];
- if (this.list_view_settings && this.list_view_settings.disable_auto_refresh) {
+ if (this.list_view_settings?.disable_auto_refresh || this.realtime_events_setup) {
return;
}
frappe.socketio.doctype_subscribe(this.doctype);
@@ -1349,6 +1350,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.pending_document_refreshes.push(data);
frappe.utils.debounce(this.process_document_refreshes.bind(this), 1000)();
});
+ this.realtime_events_setup = true;
+ }
+
+ disable_realtime_updates() {
+ frappe.socketio.doctype_unsubscribe(this.doctype);
+ this.realtime_events_setup = false;
}
process_document_refreshes() {
@@ -1358,6 +1365,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
if (!cur_list || route[0] != "List" || cur_list.doctype != route[1]) {
// wait till user is back on list view before refreshing
this.pending_document_refreshes = [];
+ this.disable_realtime_updates();
return;
}
diff --git a/frappe/public/js/frappe/socketio_client.js b/frappe/public/js/frappe/socketio_client.js
index d4a26f3188..f67da84ef4 100644
--- a/frappe/public/js/frappe/socketio_client.js
+++ b/frappe/public/js/frappe/socketio_client.js
@@ -132,6 +132,9 @@ frappe.socketio = {
doctype_subscribe: function (doctype) {
frappe.socketio.socket.emit("doctype_subscribe", doctype);
},
+ doctype_unsubscribe: function (doctype) {
+ frappe.socketio.socket.emit("doctype_unsubscribe", doctype);
+ },
doc_subscribe: function (doctype, docname) {
if (frappe.flags.doc_subscribe) {
console.log("throttled");
diff --git a/socketio.js b/socketio.js
index 67746ee84e..8e87a0cce1 100644
--- a/socketio.js
+++ b/socketio.js
@@ -68,6 +68,10 @@ io.on("connection", function (socket) {
});
});
+ socket.on("doctype_unsubscribe", function (doctype) {
+ socket.leave(get_doctype_room(socket, doctype));
+ });
+
socket.on("task_subscribe", function (task_id) {
var room = get_task_room(socket, task_id);
socket.join(room);
From 064ef5a15a5a7c3d85cfe7a794d38d5308474b7f Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Wed, 22 Mar 2023 19:41:41 +0530
Subject: [PATCH 056/120] fix: Avoid list update if user is doing some bulk
operation
---
frappe/public/js/frappe/list/list_view.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 59784b853e..6b20282099 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -1339,6 +1339,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
return;
}
+ // if some bulk operation is happening by selecting list items, don't refresh
+ if (this.$checks && this.$checks.length) {
+ return;
+ }
+
if (!frappe.get_doc(data?.doctype, data?.name)?.__unsaved) {
frappe.model.remove_from_locals(data.doctype, data.name);
}
From 7ac619921375f5962edab73eb7452c7edb3ff85f Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 23 Mar 2023 11:54:24 +0530
Subject: [PATCH 057/120] feat: re-enable scheduler from desk (#20434)
[skip ci]
---
frappe/core/doctype/rq_job/rq_job_list.js | 33 +++++++++++++++++++----
frappe/utils/scheduler.py | 5 ++++
2 files changed, 33 insertions(+), 5 deletions(-)
diff --git a/frappe/core/doctype/rq_job/rq_job_list.js b/frappe/core/doctype/rq_job/rq_job_list.js
index 5f6646cd65..fed56a16fe 100644
--- a/frappe/core/doctype/rq_job/rq_job_list.js
+++ b/frappe/core/doctype/rq_job/rq_job_list.js
@@ -4,11 +4,15 @@ frappe.listview_settings["RQ Job"] = {
onload(listview) {
if (!has_common(frappe.user_roles, ["Administrator", "System Manager"])) return;
- listview.page.add_inner_button(__("Remove Failed Jobs"), () => {
- frappe.confirm(__("Are you sure you want to remove all failed jobs?"), () => {
- frappe.xcall("frappe.core.doctype.rq_job.rq_job.remove_failed_jobs");
- });
- });
+ listview.page.add_inner_button(
+ __("Remove Failed Jobs"),
+ () => {
+ frappe.confirm(__("Are you sure you want to remove all failed jobs?"), () => {
+ frappe.xcall("frappe.core.doctype.rq_job.rq_job.remove_failed_jobs");
+ });
+ },
+ __("Actions")
+ );
if (listview.list_view_settings) {
listview.list_view_settings.disable_count = 1;
@@ -20,6 +24,25 @@ frappe.listview_settings["RQ Job"] = {
listview.page.set_indicator(__("Scheduler: Active"), "green");
} else {
listview.page.set_indicator(__("Scheduler: Inactive"), "red");
+ listview.page.add_inner_button(
+ __("Enable Scheduler"),
+ () => {
+ frappe.confirm(__("Are you sure you want to re-enable scheduler?"), () => {
+ frappe
+ .xcall("frappe.utils.scheduler.activate_scheduler")
+ .then(() => {
+ frappe.show_alert(__("Enabled Scheduler"));
+ })
+ .catch((e) => {
+ frappe.show_alert({
+ message: __("Failed to enable scheduler: {0}", e),
+ indicator: "error",
+ });
+ });
+ });
+ },
+ __("Actions")
+ );
}
});
diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py
index 8cda71ee9a..529a3c7bf7 100755
--- a/frappe/utils/scheduler.py
+++ b/frappe/utils/scheduler.py
@@ -176,6 +176,11 @@ def _get_last_modified_timestamp(doctype):
@frappe.whitelist()
def activate_scheduler():
+ frappe.only_for("Administrator")
+
+ if frappe.local.conf.maintenance_mode:
+ frappe.throw(frappe._("Scheduler can not be re-enabled when maintenance mode is active."))
+
if is_scheduler_disabled():
enable_scheduler()
if frappe.conf.pause_scheduler:
From 2036dd19b23dad7859235877e5f0ebbf5b81e95f Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Thu, 23 Mar 2023 12:30:50 +0530
Subject: [PATCH 058/120] fix: Get translated value for child tables in print
format
---
frappe/templates/print_formats/standard_macros.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/templates/print_formats/standard_macros.html b/frappe/templates/print_formats/standard_macros.html
index 5dcbaff7c5..2bfcf074ab 100644
--- a/frappe/templates/print_formats/standard_macros.html
+++ b/frappe/templates/print_formats/standard_macros.html
@@ -56,9 +56,9 @@
{% if doc.child_print_templates %}
{%- set child_templates = doc.child_print_templates.get(df.fieldname) -%}
-