From 5b9f85f1c469d169b287914421b588de93481d83 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 18 Oct 2023 01:35:40 +0200 Subject: [PATCH 001/362] fix(geolocation): modal and state flow closes: #22796 --- .../js/frappe/form/controls/geolocation.js | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js index 56a8d2a073..827a9be315 100644 --- a/frappe/public/js/frappe/form/controls/geolocation.js +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -13,30 +13,41 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f if (!this.disp_area) { return; } + if (!this.map_id) { + this.map_id = frappe.dom.get_unique_id(); + this.map_area = $( + `
+
+
` + ); - this.map_id = frappe.dom.get_unique_id(); - this.map_area = $( - `
-
-
` - ); + $(this.disp_area).html(this.map_area); + } - $(this.disp_area).html(this.map_area); + // show again on idempotent invocations $(this.disp_area).removeClass("like-disabled-input"); $(this.disp_area).css("display", "block"); if (this.frm) { - this.make_map(value); + this.make_map(); + if (value) { + this.bind_leaflet_data(value); + } } else { $(document).on("frappe.ui.Dialog:shown", () => { this.make_map(); + if (value) { + this.bind_leaflet_data(value); + } }); } } make_map(value) { - this.customize_draw_controls(); - this.bind_leaflet_map(); + if (!this.map) { + this.customize_draw_controls(); + this.bind_leaflet_map(); + } if (this.disabled) { this.map.dragging.disable(); this.map.touchZoom.disable(); @@ -47,17 +58,17 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f this.map.zoomControl.remove(); } else { this.bind_leaflet_draw_control(); - this.bind_leaflet_event_listeners(); - this.bind_leaflet_locate_control(); - this.bind_leaflet_data(value); + if (!this.bound_event_listeners) { + this.bind_leaflet_event_listeners(); + } + if (!this.locate_control) { + this.bind_leaflet_locate_control(); + } } } bind_leaflet_data(value) { /* render raw value from db into map */ - if (!this.map || !value) { - return; - } this.clear_editable_layers(); const data_layers = new L.FeatureGroup().addLayer( @@ -159,14 +170,14 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f } bind_leaflet_draw_control() { - if ( - !frappe.perm.has_perm(this.doctype, this.df.permlevel, "write", this.doc) || - this.df.read_only - ) { - return; + if (!this.draw_control) { + this.draw_control = this.get_leaflet_controls(); + } + if (this.disp_status == "Write") { + this.draw_control.addTo(this.map); + } else { + this.draw_control.remove(); } - - this.map.addControl(this.get_leaflet_controls()); } get_leaflet_controls() { @@ -204,6 +215,7 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f } bind_leaflet_event_listeners() { + this.bound_event_listeners = true; this.map.on("draw:created", (e) => { var type = e.layerType, layer = e.layer; From 5226b32f2f42c9320caa8748b267ebe8b4c09d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Oliver=20S=C3=BCnderhauf?= <46800703+bosue@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:42:15 +0100 Subject: [PATCH 002/362] test: Add reliably failing UI test: Awesomebar filtering current doclist --- cypress/integration/awesome_bar.js | 49 ++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js index 03ef96783a..dff04a5693 100644 --- a/cypress/integration/awesome_bar.js +++ b/cypress/integration/awesome_bar.js @@ -2,7 +2,11 @@ context("Awesome Bar", () => { before(() => { cy.visit("/login"); cy.login(); - cy.visit("/app/website"); + cy.visit("/app/todo"); // Make sure ToDo filters are cleared. + cy.clear_filters(); + cy.visit("/app/blog-post"); // Make sure Blog Post filters are cleared. + cy.clear_filters(); + cy.visit("/app/website"); // Go to some other page. }); beforeEach(() => { @@ -11,36 +15,61 @@ context("Awesome Bar", () => { cy.get("@awesome_bar").type("{selectall}"); }); + after(() => { + cy.visit("/app/todo"); // Make sure we're not bleeding any filters to the next spec. + cy.clear_filters(); + }); + it("navigates to doctype list", () => { cy.get("@awesome_bar").type("todo"); - cy.wait(100); + cy.wait(100); // Wait a bit before hitting enter. cy.get(".awesomplete").findByRole("listbox").should("be.visible"); cy.get("@awesome_bar").type("{enter}"); cy.get(".title-text").should("contain", "To Do"); cy.location("pathname").should("eq", "/app/todo"); }); - it("find text in doctype list", () => { + it("finds text in doctype list", () => { cy.get("@awesome_bar").type("test in todo"); - cy.wait(100); + cy.wait(150); // Wait a bit before hitting enter. cy.get("@awesome_bar").type("{enter}"); cy.get(".title-text").should("contain", "To Do"); - cy.wait(200); - const name_filter = cy.get('[data-original-title="ID"] > input'); - name_filter.should("have.value", "%test%"); - cy.clear_filters(); + cy.wait(200); // Wait a bit longer before checking the filter. + cy.get('[data-original-title="ID"] > input').should("have.value", "%test%"); + }); + + it("filter preserved, now finds something else", () => { + cy.visit("/app/todo"); + cy.get(".title-text").should("contain", "To Do"); + cy.wait(200); // Wait a bit longer before checking the filter. + cy.get('[data-original-title="ID"] > input').as("filter"); + cy.get("@filter").should("have.value", "%test%"); + cy.get("@awesome_bar").type("anothertest in todo"); + cy.wait(200); // Wait a bit longer before hitting enter. + cy.get("@awesome_bar").type("{enter}"); + cy.wait(200); // Wait a bit longer before checking the filter. + cy.get("@filter").should("have.value", "%anothertest%"); + }); + + it("navigates to another doctype, filter not bleeding", () => { + cy.get("@awesome_bar").type("blog post"); + cy.wait(150); // Wait a bit before hitting enter. + cy.get("@awesome_bar").type("{enter}"); + cy.get(".title-text").should("contain", "Blog Post"); + cy.wait(200); // Wait a bit longer before checking the filter. + cy.location("search").should("be.empty"); }); it("navigates to new form", () => { cy.get("@awesome_bar").type("new blog post"); - cy.wait(100); + cy.wait(150); // Wait a bit before hitting enter cy.get("@awesome_bar").type("{enter}"); cy.get(".title-text:visible").should("have.text", "New Blog Post"); }); it("calculates math expressions", () => { cy.get("@awesome_bar").type("55 + 32"); - cy.wait(100); + cy.wait(150); // Wait a bit before hitting enter cy.get("@awesome_bar").type("{downarrow}{enter}"); cy.get(".modal-title").should("contain", "Result"); cy.get(".msgprint").should("contain", "55 + 32 = 87"); From c3adc13206ef44a0d50f2a00d7514690b2a71df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Oliver=20S=C3=BCnderhauf?= <46800703+bosue@users.noreply.github.com> Date: Tue, 31 Oct 2023 23:01:09 +0100 Subject: [PATCH 003/362] fix!: Make router always take query parameters into account. --- frappe/public/js/frappe/router.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 2631c6374e..3b50791aa7 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -368,7 +368,11 @@ frappe.router = { window.open(sub_path, "_blank"); frappe.open_in_new_tab = false; } else { - this.push_state(sub_path); + const route_options = frappe.route_options || {}; + const query_params = Object.entries(route_options) + .map(([key, value]) => `${key}=` + encodeURIComponent(JSON.stringify(value))) + .join("&"); + this.push_state(sub_path, query_params ? `?${query_params}` : ""); } setTimeout(() => { frappe.after_ajax && @@ -469,12 +473,19 @@ frappe.router = { return "/app/" + (path_string || default_page); }, - push_state(url) { - // change the URL and call the router - if (window.location.pathname !== url) { + /** + * Changes the URL and calls the router. + * + * @param {string} path - The desired URI path to replace or push, + * without query string. Example: "/app/todo" + * @param {string} query_params - The desired query parameter string. + * @returns {void} + */ + push_state(path, query_params = "") { + if (window.location.pathname !== path || window.location.search !== query_params) { // push/replace state so the browser looks fine const method = frappe.route_flags.replace_route ? "replaceState" : "pushState"; - history[method](null, null, url); + history[method](null, null, path); // now process the route this.route(); From f991b0be0f56a5577844200a259c475fe5960ab4 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:15:09 +0100 Subject: [PATCH 004/362] fix: max-width of email attachment filename --- .../public/js/frappe/views/communication.js | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 9b69ebee3c..c415da95b8 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -522,18 +522,27 @@ frappe.views.CommunicationComposer = class { get_attachment_row(attachment, checked) { return $(`

-

`); } From e059aa385fb2bfa005add29aa5d9fe10f55f55dd Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 9 Nov 2023 12:08:05 +0530 Subject: [PATCH 005/362] feat: add check to include filters in popup --- .../js/frappe/views/reports/query_report.js | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index b92c3166f5..e03f227055 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1475,21 +1475,34 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { return; } - let extra_fields = null; + let extra_fields = []; + if (this.tree_report) { - extra_fields = [ - { - label: __("Include indentation"), - fieldname: "include_indentation", - fieldtype: "Check", - }, - ]; + extra_fields.push({ + label: __("Include indentation"), + fieldname: "include_indentation", + fieldtype: "Check", + }); + } + + if (this.filters.length > 0) { + extra_fields.push({ + label: __("Include filters"), + fieldname: "include_filters", + fieldtype: "Check", + }); } this.export_dialog = frappe.report_utils.get_export_dialog( __(this.report_name), extra_fields, - ({ file_format, include_indentation, csv_delimiter, csv_quoting }) => { + ({ + file_format, + include_indentation, + include_filters, + csv_delimiter, + csv_quoting, + }) => { this.make_access_log("Export", file_format); let filters = this.get_filter_values(true); @@ -1515,6 +1528,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { csv_delimiter, csv_quoting, include_indentation, + include_filters, }; open_url_post(frappe.request.url, args); From a52d1870dc8e803f28c1410860ec5b54a9560c69 Mon Sep 17 00:00:00 2001 From: Gursheen Anand Date: Thu, 9 Nov 2023 12:09:42 +0530 Subject: [PATCH 006/362] feat: add filter values while building report xlsx data --- frappe/desk/query_report.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 7ca483d806..0a25dd3b8f 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -318,6 +318,7 @@ def export_query(): file_format_type = form_params.file_format_type custom_columns = frappe.parse_json(form_params.custom_columns or "[]") include_indentation = form_params.include_indentation + include_filters = form_params.include_filters visible_idx = form_params.visible_idx if isinstance(visible_idx, str): @@ -327,6 +328,7 @@ def export_query(): report_name, form_params.filters, custom_columns=custom_columns, are_default_filters=False ) data = frappe._dict(data) + data.filters = form_params.filters if not data.columns: frappe.respond_as_web_page( _("No data to export"), @@ -335,7 +337,9 @@ def export_query(): return format_duration_fields(data) - xlsx_data, column_widths = build_xlsx_data(data, visible_idx, include_indentation) + xlsx_data, column_widths = build_xlsx_data( + data, visible_idx, include_indentation, include_filters=include_filters + ) if file_format_type == "CSV": content = get_csv_bytes(xlsx_data, csv_params) @@ -360,7 +364,9 @@ def format_duration_fields(data: frappe._dict) -> None: row[index] = format_duration(row[index]) -def build_xlsx_data(data, visible_idx, include_indentation, ignore_visible_idx=False): +def build_xlsx_data( + data, visible_idx, include_indentation, include_filters=False, ignore_visible_idx=False +): EXCEL_TYPES = ( str, bool, @@ -380,17 +386,30 @@ def build_xlsx_data(data, visible_idx, include_indentation, ignore_visible_idx=F # Note: converted for faster lookups visible_idx = set(visible_idx) - result = [[]] + result = [] column_widths = [] + if cint(include_filters): + filter_data = [] + filters = data.filters + for filter_name, filter_value in filters.items(): + if filter_value in ["", None, []]: + continue + filter_value = ", ".join(filter_value) if isinstance(filter_value, list) else cstr(filter_value) + filter_data.append([cstr(filter_name) + ": ", filter_value]) + filter_data.append([]) + result += filter_data + + column_data = [] for column in data.columns: if column.get("hidden"): continue - result[0].append(_(column.get("label"))) + column_data.append(_(column.get("label"))) column_width = cint(column.get("width", 0)) # to convert into scale accepted by openpyxl column_width /= 10 column_widths.append(column_width) + result.append(column_data) # build table from result for row_idx, row in enumerate(data.result): From 3f148a0f7edb6242e5dda962317e577575f8d9c2 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Sat, 18 Nov 2023 16:10:33 +0100 Subject: [PATCH 007/362] fix(list_settings): don't count tags to total fields --- frappe/public/js/frappe/list/list_view.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 893c212f23..e865636e03 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -358,11 +358,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { }); } - this.columns.push({ - type: "Tag", - }); - - // 2nd column: Status indicator + // 3nd column: Status indicator if (frappe.has_indicator(this.doctype)) { // indicator this.columns.push({ @@ -407,11 +403,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.columns = this.columns.slice(0, this.list_view_settings.total_fields || total_fields); - if ( - !this.settings.hide_name_column && - this.meta.title_field && - this.meta.title_field !== "name" - ) { + // 2st column: tag - normally hidden doesn't count towards total_fields + this.columns.splice(1, 0, { + type: "Tag", + }); + + if (!this.settings.hide_name_column) { this.columns.push({ type: "Field", df: { @@ -426,10 +423,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { let fields_order = []; let fields = JSON.parse(this.list_view_settings.fields); - //title and tags field is fixed + // title field is fixed fields_order.push(this.columns[0]); - fields_order.push(this.columns[1]); - this.columns.splice(0, 2); + this.columns.splice(0, 1); for (let fld in fields) { for (let col in this.columns) { From 4203c3b13da0821419f0f513121ba9926da7cd00 Mon Sep 17 00:00:00 2001 From: HarryPaulo Date: Mon, 20 Nov 2023 22:31:12 -0300 Subject: [PATCH 008/362] fix: load languages that have capital letters --- frappe/translate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/translate.py b/frappe/translate.py index cc0771583e..2f3341fd35 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -200,7 +200,7 @@ def get_translations_from_apps(lang, apps=None): translations = {} for app in apps or frappe.get_installed_apps(_ensure_on_bench=True): - path = frappe.get_app_path(app, "translations", lang + ".csv") + path = os.path.join(frappe.get_app_path(app, "translations"), lang + ".csv") translations.update(get_translation_dict_from_file(path, lang, app) or {}) if "-" in lang: parent = lang.split("-", 1)[0] From aeec01c7f933615c126fd7b6a1a832c54f8d230a Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 21 Nov 2023 12:13:35 +0530 Subject: [PATCH 009/362] perf(Scheduling): add jitter to job scheduling Addresses #19007 --- .../doctype/scheduled_job_type/scheduled_job_type.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index 6f56180c89..cfb741e5fe 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -2,7 +2,8 @@ # License: MIT. See LICENSE import json -from datetime import datetime +from datetime import datetime, timedelta +from random import randint import click from croniter import croniter @@ -110,7 +111,12 @@ class ScheduledJobType(Document): # immediately, even when it's meant to be daily. # A dynamic fallback like current time might miss the scheduler interval and job will never start. last_execution = get_datetime(self.last_execution or self.creation) - return croniter(self.cron_format, last_execution).get_next(datetime) + next_execution = croniter(self.cron_format, last_execution).get_next(datetime) + + jitter = 0 + if self.frequency in ("Hourly Long", "Daily Long"): + jitter = randint(1, 600) + return next_execution + timedelta(seconds=jitter) def execute(self): self.scheduler_log = None From 5af9b294f56e819a4ec2471a5e59bd556bd180e5 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 21 Nov 2023 16:34:26 +0530 Subject: [PATCH 010/362] fix: honour max file size in upload file In case path is /api/method/upload_file, we should honour the max file size set in system settings and set request max_content_length to that value. --- frappe/app.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/app.py b/frappe/app.py index c036b65e9c..9ed084742e 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -179,9 +179,12 @@ def init_request(request): raise frappe.SessionStopped("Session Stopped") else: frappe.connect(set_admin_as_user=False) + if request.path.startswith("/api/method/upload_file"): + from frappe.core.api.file import get_max_file_size - request.max_content_length = cint(frappe.local.conf.get("max_file_size")) or 10 * 1024 * 1024 - + request.max_content_length = get_max_file_size() + else: + request.max_content_length = cint(frappe.local.conf.get("max_file_size")) or 10 * 1024 * 1024 make_form_dict(request) if request.method != "OPTIONS": From f9141d04bf4450f32c06d0e2e08fe85b61da8da4 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 21 Nov 2023 17:54:17 +0100 Subject: [PATCH 011/362] fix(naming_rule): polish list view --- .../doctype/document_naming_rule/document_naming_rule.json | 7 +++++-- .../document_naming_rule/document_naming_rule_list.js | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 frappe/core/doctype/document_naming_rule/document_naming_rule_list.js diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.json b/frappe/core/doctype/document_naming_rule/document_naming_rule.json index 1e2247c250..f6b3245086 100644 --- a/frappe/core/doctype/document_naming_rule/document_naming_rule.json +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.json @@ -35,6 +35,7 @@ { "fieldname": "prefix", "fieldtype": "Data", + "in_list_view": 1, "label": "Prefix", "mandatory_depends_on": "eval:doc.naming_by===\"Numbered\"", "reqd": 1 @@ -44,6 +45,7 @@ "description": "Warning: Updating counter may lead to document name conflicts if not done properly", "fieldname": "counter", "fieldtype": "Int", + "in_list_view": 1, "label": "Counter", "no_copy": 1 }, @@ -78,6 +80,7 @@ "description": "Rules with higher priority number will be applied first.", "fieldname": "priority", "fieldtype": "Int", + "in_standard_filter": 1, "label": "Priority" }, { @@ -87,7 +90,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-04-24 15:14:32.054272", + "modified": "2023-11-21 11:58:25.712375", "modified_by": "Administrator", "module": "Core", "name": "Document Naming Rule", @@ -107,7 +110,7 @@ } ], "quick_entry": 1, - "sort_field": "modified", + "sort_field": "priority", "sort_order": "DESC", "states": [], "title_field": "document_type", diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule_list.js b/frappe/core/doctype/document_naming_rule/document_naming_rule_list.js new file mode 100644 index 0000000000..0dba534891 --- /dev/null +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule_list.js @@ -0,0 +1,3 @@ +frappe.listview_settings["Document Naming Rule"] = { + hide_name_column: true, +}; From b43b8839c005b4b5a4e1c30286d3abeeb2f0f509 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 22 Nov 2023 10:26:23 +0530 Subject: [PATCH 012/362] chore: remove dead link --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index aefa0db1d2..898d1dbc9b 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,6 @@ Full-stack web application framework that uses Python and MariaDB on the server 1. [Code of Conduct](CODE_OF_CONDUCT.md) 1. [Contribution Guidelines](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines) 1. [Security Policy](SECURITY.md) -1. [Translations](https://translate.erpnext.com) ## Resources From 28d05c41c3be7b0a524865036348d5508a5f84ba Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 22 Nov 2023 10:52:35 +0530 Subject: [PATCH 013/362] fix: correct max file size in boot --- frappe/app.py | 2 +- frappe/boot.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/app.py b/frappe/app.py index 9ed084742e..0284968113 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -184,7 +184,7 @@ def init_request(request): request.max_content_length = get_max_file_size() else: - request.max_content_length = cint(frappe.local.conf.get("max_file_size")) or 10 * 1024 * 1024 + request.max_content_length = cint(frappe.local.conf.get("max_file_size")) or 25 * 1024 * 1024 make_form_dict(request) if request.method != "OPTIONS": diff --git a/frappe/boot.py b/frappe/boot.py index c36927637a..2ce950a55a 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -122,12 +122,12 @@ def get_letter_heads(): def load_conf_settings(bootinfo): - from frappe import conf + from frappe.core.api.file import get_max_file_size - bootinfo.max_file_size = conf.get("max_file_size") or 10485760 + bootinfo.max_file_size = get_max_file_size() for key in ("developer_mode", "socketio_port", "file_watcher_port"): - if key in conf: - bootinfo[key] = conf.get(key) + if key in frappe.conf: + bootinfo[key] = frappe.conf.get(key) def load_desktop_data(bootinfo): From 67b74cd4bb95ed52c51bf01d3e52a331fa3ef944 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Wed, 22 Nov 2023 11:54:36 +0530 Subject: [PATCH 014/362] fix(web_form): check properties for title field as well Signed-off-by: Akhil Narang --- frappe/website/doctype/web_form/web_form.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index 95684dee99..b848c11385 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -11,7 +11,7 @@ from frappe.core.doctype.file.utils import remove_file_by_url from frappe.desk.form.meta import get_code_files_via_hooks from frappe.modules.utils import export_module_json, get_doc_module from frappe.rate_limiter import rate_limit -from frappe.utils import cstr, dict_with_keys, strip_html +from frappe.utils import dict_with_keys, strip_html from frappe.utils.caching import redis_cache from frappe.website.utils import get_boot_data, get_comment_list, get_sidebar_items from frappe.website.website_generator import WebsiteGenerator @@ -692,6 +692,13 @@ def get_link_options(web_form_name, doctype, allow_read_on_all_link_options=Fals if value and int(value) == 1: show_title_field_in_link = True + if not title_field: + title_field = frappe.db.get_value( + "Property Setter", + fieldname="value", + filters={"property": "title_field", "doc_type": doctype}, + ) + if title_field and show_title_field_in_link: fields.append(f"{title_field} as label") From 83f2227cdbe64d5bfd373c0e94ef488801a4eac2 Mon Sep 17 00:00:00 2001 From: Maharshi Patel <39730881+maharshivpatel@users.noreply.github.com> Date: Wed, 22 Nov 2023 12:31:35 +0530 Subject: [PATCH 015/362] fix: review points when added more then once. (#23239) * fix: review points when added more then once. previously when user added review more then once it didn't work properly because of apply_only_once check which is only meant for rule based points changed that behaviour to only check when called from energy_point_rule by checking if doc.rule is set. --- .../doctype/energy_point_log/energy_point_log.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.py b/frappe/social/doctype/energy_point_log/energy_point_log.py index 07c91396d7..a9b6d01e4e 100644 --- a/frappe/social/doctype/energy_point_log/energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/energy_point_log.py @@ -189,13 +189,12 @@ def get_alert_dict(doc): def create_energy_points_log(ref_doctype, ref_name, doc, apply_only_once=False): doc = frappe._dict(doc) - - log_exists = check_if_log_exists( - ref_doctype, ref_name, doc.rule, None if apply_only_once else doc.user - ) - - if log_exists: - return frappe.get_doc("Energy Point Log", log_exists) + if doc.rule: + log_exists = check_if_log_exists( + ref_doctype, ref_name, doc.rule, None if apply_only_once else doc.user + ) + if log_exists: + return frappe.get_doc("Energy Point Log", log_exists) new_log = frappe.new_doc("Energy Point Log") new_log.reference_doctype = ref_doctype From 250dbf6a1caa5b0ab9bf89fe0413fc3a2b81ba06 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 22 Nov 2023 13:09:34 +0530 Subject: [PATCH 016/362] fix(email): Allow users to pull all (read & unread) emails during initial sync --- frappe/email/receive.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 381d43a124..2d81edd297 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -236,11 +236,6 @@ class EmailServer: EmailAccount.uidnext, uidnext ).where(EmailAccount.name == self.settings.email_account_name).run() - # uid validity not found pulling emails for first time - if not uid_validity: - self.settings.email_sync_rule = "UNSEEN" - return - sync_count = 100 if uid_validity else int(self.settings.initial_sync_count) from_uid = ( 1 if uidnext < (sync_count + 1) or (uidnext - sync_count) < 1 else uidnext - sync_count From 39bd7353d1184eb7fd0702b83563f42a70b668bc Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 22 Nov 2023 09:30:29 +0000 Subject: [PATCH 017/362] fix: zeros converted to empty string in csv (cherry picked from commit fa4b816ea0431e38538e7095a9db4140926d7207) --- frappe/public/js/frappe/views/reports/query_report.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index b92c3166f5..a31d1c21a1 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1538,9 +1538,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { cell.content = frappe.utils.get_formatted_duration(cell.content); } if (include_indentation && i === 0) { - cell.content = " ".repeat(row.meta.indent) + (cell.content || ""); + cell.content = " ".repeat(row.meta.indent) + (cell.content ?? ""); } - return cell.content || ""; + return cell.content ?? ""; }); }); } From a5b102dcd26dcec987835ea96c5708fc80324142 Mon Sep 17 00:00:00 2001 From: "Indrajith.vs" <91895505+Gubbu77@users.noreply.github.com> Date: Wed, 22 Nov 2023 16:00:09 +0530 Subject: [PATCH 018/362] fix: button "Add Chart to Dashboard" visible before saving (#23256) * fix: dashboard chart - add chart to dashboard btn fix * style: format [skip ci] --------- Co-authored-by: Ankush Menat --- .../dashboard_chart/dashboard_chart.js | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index 5d16a6d6d1..3577e9c5ec 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -30,19 +30,21 @@ frappe.ui.form.on("Dashboard Chart", { frm.disable_form(); } - frm.add_custom_button("Add Chart to Dashboard", () => { - const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog( - frm.doc.name, - "Dashboard Chart", - "frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard" - ); + if (!frm.is_new()) { + frm.add_custom_button("Add Chart to Dashboard", () => { + const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog( + frm.doc.name, + "Dashboard Chart", + "frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard" + ); - if (!frm.doc.chart_name) { - frappe.msgprint(__("Please create chart first")); - } else { - dialog.show(); - } - }); + if (!frm.doc.chart_name) { + frappe.msgprint(__("Please create chart first")); + } else { + dialog.show(); + } + }); + } frm.set_df_property("filters_section", "hidden", 1); frm.set_df_property("dynamic_filters_section", "hidden", 1); From 1a3f39daa0cf303bd132b150983b420406966915 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 22 Nov 2023 16:20:20 +0530 Subject: [PATCH 019/362] chore: readme logo on dark theme (#23358) closes https://github.com/frappe/frappe/issues/23352 --- .github/frappe-framework-logo-dark.svg | 5 +++++ README.md | 10 ++++------ 2 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 .github/frappe-framework-logo-dark.svg diff --git a/.github/frappe-framework-logo-dark.svg b/.github/frappe-framework-logo-dark.svg new file mode 100644 index 0000000000..7c43cea2db --- /dev/null +++ b/.github/frappe-framework-logo-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/README.md b/README.md index 898d1dbc9b..50540ad1c7 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@
-

-
- - - -

+ + + +

a web framework with "batteries included"

From 78c78ece342d9eeecae48c4bdf8abb0604e7b823 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 22 Nov 2023 16:51:07 +0530 Subject: [PATCH 020/362] fix: Show list similar to how it looks in text editor --- frappe/public/scss/email.bundle.scss | 82 ++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/frappe/public/scss/email.bundle.scss b/frappe/public/scss/email.bundle.scss index ef274f023f..860c0df8e4 100644 --- a/frappe/public/scss/email.bundle.scss +++ b/frappe/public/scss/email.bundle.scss @@ -346,3 +346,85 @@ blockquote { margin: 5px 0; } } + +.ql-editor li { + list-style-type: none; + padding-left: 1.5em; + position: relative; +} +.ql-editor li > .ql-ui:before { + display: inline-block; + margin-left: -1.5em; + margin-right: 0.3em; + text-align: right; + white-space: nowrap; + width: 1.2em; +} +.ql-editor li[data-list="checked"] > .ql-ui, +.ql-editor li[data-list="unchecked"] > .ql-ui { + color: #777; +} +.ql-editor li[data-list="bullet"] > .ql-ui:before { + content: "\2022"; +} +.ql-editor li[data-list="checked"] > .ql-ui:before { + content: "\2611"; +} +.ql-editor li[data-list="unchecked"] > .ql-ui:before { + content: "\2610"; +} +.ql-editor li[data-list="ordered"] { + counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; + counter-increment: list-0; +} +.ql-editor li[data-list="ordered"] > .ql-ui:before { + content: counter(list-0, decimal) ". "; +} +.ql-editor li[data-list="ordered"].ql-indent-1 { + counter-increment: list-1; +} +.ql-editor li[data-list="ordered"].ql-indent-1 > .ql-ui:before { + content: counter(list-1, lower-alpha) ". "; +} +.ql-editor li[data-list="ordered"].ql-indent-1 { + counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; +} +.ql-editor li[data-list="ordered"].ql-indent-2 { + counter-increment: list-2; +} +.ql-editor li[data-list="ordered"].ql-indent-2 > .ql-ui:before { + content: counter(list-2, lower-roman) ". "; +} +.ql-editor li[data-list="ordered"].ql-indent-2 { + counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; +} +.ql-editor li[data-list="ordered"].ql-indent-3 { + counter-increment: list-3; +} +.ql-editor li[data-list="ordered"].ql-indent-3 > .ql-ui:before { + content: counter(list-3, decimal) ". "; +} +.ql-editor li[data-list="ordered"].ql-indent-3 { + counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; +} +.ql-editor .ql-indent-1:not(.ql-direction-rtl) { + padding-left: 3em; +} + +.ql-editor li.ql-indent-1:not(.ql-direction-rtl) { + padding-left: 4.5em; +} + +.ql-editor .ql-indent-2:not(.ql-direction-rtl) { + padding-left: 6em; +} +.ql-editor li.ql-indent-2:not(.ql-direction-rtl) { + padding-left: 7.5em; +} + +.ql-editor .ql-indent-3:not(.ql-direction-rtl) { + padding-left: 9em; +} +.ql-editor li.ql-indent-3:not(.ql-direction-rtl) { + padding-left: 10.5em; +} From 3896750e2e6f0d07675e9f67a026626acdb62fa0 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Wed, 22 Nov 2023 17:01:01 +0530 Subject: [PATCH 021/362] refactor(web_form): cleanup code `frappe.get_meta()` gives us all the data we require Signed-off-by: Akhil Narang --- frappe/website/doctype/web_form/web_form.py | 26 ++++----------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index b848c11385..ded5eb7f02 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -679,32 +679,14 @@ def get_link_options(web_form_name, doctype, allow_read_on_all_link_options=Fals fields = ["name as value"] - title_field = frappe.get_cached_value("DocType", doctype, "title_field") - show_title_field_in_link = ( - frappe.get_cached_value("DocType", doctype, "show_title_field_in_link") == 1 - ) - if not show_title_field_in_link: - value = frappe.db.get_value( - "Property Setter", - fieldname="value", - filters={"property": "show_title_field_in_link", "doc_type": doctype}, - ) - if value and int(value) == 1: - show_title_field_in_link = True + meta = frappe.get_meta(doctype) - if not title_field: - title_field = frappe.db.get_value( - "Property Setter", - fieldname="value", - filters={"property": "title_field", "doc_type": doctype}, - ) - - if title_field and show_title_field_in_link: - fields.append(f"{title_field} as label") + if meta.title_field and meta.show_title_field_in_link: + fields.append(f"{meta.title_field} as label") link_options = frappe.get_all(doctype, filters, fields) - if title_field and show_title_field_in_link: + if meta.title_field and meta.show_title_field_in_link: return json.dumps(link_options, default=str) else: return "\n".join([str(doc.value) for doc in link_options]) From 31e3b3f98118cef238581d0c27abd751b87894bb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 22 Nov 2023 17:02:45 +0530 Subject: [PATCH 022/362] fix: social key signup without signup conf (#23359) --- .../integrations/doctype/social_login_key/social_login_key.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/social_login_key/social_login_key.py b/frappe/integrations/doctype/social_login_key/social_login_key.py index 4c1fc8708f..3445bb92e3 100644 --- a/frappe/integrations/doctype/social_login_key/social_login_key.py +++ b/frappe/integrations/doctype/social_login_key/social_login_key.py @@ -222,6 +222,6 @@ def provider_allows_signup(provider: str) -> bool: sign_up_config = frappe.db.get_value("Social Login Key", provider, "sign_ups") - if not (sign_up_config and provider): # fallback to global settings + if not sign_up_config: # fallback to global settings return is_signup_disabled() return sign_up_config == "Allow" From 6e3b1cc0adc7f6279b4cc43d970ff1158ce22a67 Mon Sep 17 00:00:00 2001 From: Md Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com> Date: Wed, 22 Nov 2023 18:27:54 +0530 Subject: [PATCH 023/362] docs: add BWH link to resources (#23365) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 50540ad1c7..9a1cee5534 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ Full-stack web application framework that uses Python and MariaDB on the server 1. [frappeframework.com](https://frappeframework.com) - Official documentation of the Frappe Framework. 1. [frappe.school](https://frappe.school) - Pick from the various courses by the maintainers or from the community. +1. [buildwithhussain.dev](https://buildwithhussain.dev) - Watch Frappe Framework being used in the wild to build world-class web apps. ## License This repository has been released under the [MIT License](LICENSE). From 21e8abf89904c1358e1f764129284303604ca3fb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 23 Nov 2023 11:42:51 +0530 Subject: [PATCH 024/362] build: python 3.12 support (#22706) * ci: use python 3.12 We'll use minimum versions on stable branch CI configs. * build(deps): bump dependencies - Some for v12 support - some just for new stuff --- .github/workflows/server-tests.yml | 2 +- .github/workflows/ui-tests.yml | 2 +- pyproject.toml | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 97000bff15..e20ae8fc6e 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -85,7 +85,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Check for valid Python & Merge Conflicts run: | diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 00e370e4ed..4742d97a37 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -67,7 +67,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.12' - name: Check for valid Python & Merge Conflicts run: | diff --git a/pyproject.toml b/pyproject.toml index 28eb0858b9..c371e87ac1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,14 +4,14 @@ authors = [ { name = "Frappe Technologies Pvt Ltd", email = "developers@frappe.io"} ] description = "Metadata driven, full-stack low code web framework" -requires-python = ">=3.10,<3.12" +requires-python = ">=3.10,<3.13" readme = "README.md" dynamic = ["version"] dependencies = [ # core dependencies - "Babel~=2.12.1", + "Babel~=2.13.1", "Click~=8.1.7", - "filelock~=3.8.0", + "filelock~=3.13.1", "filetype~=1.2.0", "GitPython~=3.1.34", "Jinja2~=3.1.2", @@ -22,7 +22,7 @@ dependencies = [ "PyPika~=0.48.9", "PyQRCode~=1.2.1", "PyYAML~=6.0.1", - "RestrictedPython~=6.2", + "RestrictedPython~=7.0", "WeasyPrint==59.0", "Werkzeug~=3.0.1", "Whoosh~=2.7.4", @@ -31,7 +31,7 @@ dependencies = [ "bleach[css]~=6.0.0", "cairocffi==1.5.1", "chardet~=5.1.0", - "croniter~=1.4.1", + "croniter~=2.0.1", "cryptography~=41.0.3", "email-reply-parser~=0.5.12", "git-url-parse~=1.2.2", @@ -57,7 +57,7 @@ dependencies = [ "python-dateutil~=2.8.2", "pytz==2023.3", "rauth~=0.7.3", - "redis~=4.5.5", + "redis~=5.0.1", "hiredis~=2.2.3", "requests-oauthlib~=1.3.1", "requests~=2.31.0", From c2309969d570243010c44ad8cc75ed9b1e3e4b06 Mon Sep 17 00:00:00 2001 From: Maharshi Patel <39730881+maharshivpatel@users.noreply.github.com> Date: Thu, 23 Nov 2023 11:46:54 +0530 Subject: [PATCH 025/362] fix: typo in strip folder name quotes (#23368) fixed incorrect negative index. --- frappe/email/receive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 2d81edd297..19798851fe 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -222,7 +222,7 @@ class EmailServer: if self.settings.use_imap: # Remove {"} quotes that are added to handle spaces in IMAP Folder names if folder[0] == folder[-1] == '"': - folder = folder[1:-2] + folder = folder[1:-1] # new update for the IMAP Folder DocType IMAPFolder = frappe.qb.DocType("IMAP Folder") frappe.qb.update(IMAPFolder).set(IMAPFolder.uidvalidity, current_uid_validity).set( From 89dbb42f6fb0369a40a7d3a236ca56366d00a344 Mon Sep 17 00:00:00 2001 From: RitvikSardana Date: Thu, 23 Nov 2023 13:12:16 +0530 Subject: [PATCH 026/362] fix: link field shown only for Link fieldtype --- frappe/public/js/form_builder/components/Field.vue | 9 +++++++-- .../js/form_builder/components/FieldProperties.vue | 6 +++++- frappe/public/js/form_builder/store.js | 8 ++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/form_builder/components/Field.vue b/frappe/public/js/form_builder/components/Field.vue index 08e65473ee..92825e2375 100644 --- a/frappe/public/js/form_builder/components/Field.vue +++ b/frappe/public/js/form_builder/components/Field.vue @@ -174,8 +174,13 @@ function edit_filters() { } function is_filter_applied() { - if (props.field.df.link_filters && JSON.parse(props.field.df.link_filters).length > 0) { - return "btn-filter-applied"; + if (props.field.df.link_filters) { + try { + JSON.parse(props.field.df.link_filters).length > 0; + return "btn-filter-applied"; + } catch (error) { + return ""; + } } } diff --git a/frappe/public/js/form_builder/components/FieldProperties.vue b/frappe/public/js/form_builder/components/FieldProperties.vue index 7132bfeba9..5f903ed36c 100644 --- a/frappe/public/js/form_builder/components/FieldProperties.vue +++ b/frappe/public/js/form_builder/components/FieldProperties.vue @@ -51,6 +51,11 @@ let docfield_df = computed(() => { } } + // show link_filters docfield only when link field is selected + if (df.fieldname === "link_filters" && store.form.selected_field.fieldtype !== "Link") { + return false; + } + if (search_text.value) { if ( df.label.toLowerCase().includes(search_text.value.toLowerCase()) || @@ -62,7 +67,6 @@ let docfield_df = computed(() => { } return true; }); - return [...fields]; }); diff --git a/frappe/public/js/form_builder/store.js b/frappe/public/js/form_builder/store.js index 069cd10540..97aa89b58c 100644 --- a/frappe/public/js/form_builder/store.js +++ b/frappe/public/js/form_builder/store.js @@ -202,14 +202,18 @@ export const useStore = defineStore("form-builder-store", () => { ); } - // check if link_filters format is correct or not + if (df.link_filters === "") { + delete df.link_filters; + } + // check if link_filters format is correct or not if (df.link_filters) { try { let link_filters = JSON.parse(df.link_filters); } catch (e) { error_message = __( - `Invalid Filter Format. Try using filter icon on the field to set it correctly` + "Invalid Filter Format for field {0} of type {1}. Try using filter icon on the field to set it correctly", + get_field_data(df) ); } } From 9e7a0b73edb7444581863e6a5f5744c2bfa1bf69 Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor Date: Thu, 23 Nov 2023 13:18:08 +0530 Subject: [PATCH 027/362] fix: show fieldname if field label is not set --- frappe/public/js/frappe/ui/group_by/group_by.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 2f218678ac..46b85a05cb 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -147,10 +147,13 @@ frappe.ui.GroupBy = class { doctype_fields.forEach((field) => { // pick numeric fields for sum / avg if (frappe.model.is_numeric_field(field.fieldtype)) { + let field_label = field.label + ? field.label + : frappe.model.unscrub(field.fieldname); let option_text = doctype == this.doctype - ? field.label - : `${field.label} (${__(doctype)})`; + ? field_label + : `${field_label} (${__(doctype)})`; this.aggregate_on_html += ``; } From f526054ae2e140a27dae27a3c7fb5ced68ee74b1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 23 Nov 2023 13:21:27 +0530 Subject: [PATCH 028/362] refactor: Remove usage of utcnow (#23369) --- frappe/core/doctype/rq_worker/rq_worker.py | 5 ++++- .../integrations/doctype/token_cache/token_cache.py | 6 +++--- frappe/monitor.py | 7 ++++--- frappe/oauth.py | 2 +- frappe/rate_limiter.py | 7 ++++--- frappe/utils/caching.py | 10 ++++++---- frappe/utils/data.py | 12 +++++++----- 7 files changed, 29 insertions(+), 20 deletions(-) diff --git a/frappe/core/doctype/rq_worker/rq_worker.py b/frappe/core/doctype/rq_worker/rq_worker.py index d3ea97203e..68dd83c41b 100644 --- a/frappe/core/doctype/rq_worker/rq_worker.py +++ b/frappe/core/doctype/rq_worker/rq_worker.py @@ -4,6 +4,7 @@ import datetime from contextlib import suppress +import pytz from rq import Worker import frappe @@ -105,5 +106,7 @@ def serialize_worker(worker: Worker) -> frappe._dict: def compute_utilization(worker: Worker) -> float: with suppress(Exception): - total_time = (datetime.datetime.utcnow() - worker.birth_date).total_seconds() + total_time = ( + datetime.datetime.now(pytz.UTC) - worker.birth_date.replace(tzinfo=pytz.UTC) + ).total_seconds() return worker.total_working_time / total_time * 100 diff --git a/frappe/integrations/doctype/token_cache/token_cache.py b/frappe/integrations/doctype/token_cache/token_cache.py index da72335413..5619030499 100644 --- a/frappe/integrations/doctype/token_cache/token_cache.py +++ b/frappe/integrations/doctype/token_cache/token_cache.py @@ -1,7 +1,7 @@ # Copyright (c) 2019, Frappe Technologies and contributors # License: MIT. See LICENSE -from datetime import datetime, timedelta +import datetime import pytz @@ -73,8 +73,8 @@ class TokenCache(Document): system_timezone = pytz.timezone(get_system_timezone()) modified = frappe.utils.get_datetime(self.modified) modified = system_timezone.localize(modified) - expiry_utc = modified.astimezone(pytz.utc) + timedelta(seconds=self.expires_in) - now_utc = datetime.utcnow().replace(tzinfo=pytz.utc) + expiry_utc = modified.astimezone(pytz.utc) + datetime.timedelta(seconds=self.expires_in) + now_utc = datetime.datetime.now(pytz.utc) return cint((expiry_utc - now_utc).total_seconds()) def is_expired(self): diff --git a/frappe/monitor.py b/frappe/monitor.py index 9b8f500358..aae54987c8 100644 --- a/frappe/monitor.py +++ b/frappe/monitor.py @@ -1,12 +1,13 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE +import datetime import json import os import traceback import uuid -from datetime import datetime +import pytz import rq import frappe @@ -50,7 +51,7 @@ class Monitor: self.data = frappe._dict( { "site": frappe.local.site, - "timestamp": datetime.utcnow(), + "timestamp": datetime.datetime.now(pytz.UTC), "transaction_type": transaction_type, "uuid": str(uuid.uuid4()), } @@ -92,7 +93,7 @@ class Monitor: def dump(self, response=None): try: - timediff = datetime.utcnow() - self.data.timestamp + timediff = datetime.datetime.now(pytz.UTC) - self.data.timestamp # Obtain duration in microseconds self.data.duration = int(timediff.total_seconds() * 1000000) diff --git a/frappe/oauth.py b/frappe/oauth.py index 1094194348..f0a13488da 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -245,7 +245,7 @@ class OAuthWebRequestValidator(RequestValidator): ) token_expiration_utc = token_expiration_local.astimezone(pytz.utc) is_token_valid = ( - frappe.utils.datetime.datetime.utcnow().replace(tzinfo=pytz.utc) < token_expiration_utc + datetime.datetime.now(pytz.UTC) < token_expiration_utc ) and otoken.status != "Revoked" client_scopes = frappe.db.get_value("OAuth Client", otoken.client, "scopes").split( get_url_delimiter() diff --git a/frappe/rate_limiter.py b/frappe/rate_limiter.py index 11f3be8cb3..e1c93338e7 100644 --- a/frappe/rate_limiter.py +++ b/frappe/rate_limiter.py @@ -1,10 +1,11 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE +import datetime from collections.abc import Callable -from datetime import datetime from functools import wraps +import pytz from werkzeug.wrappers import Response import frappe @@ -34,7 +35,7 @@ class RateLimiter: self.limit = int(limit * 1000000) self.window = window - self.start = datetime.utcnow() + self.start = datetime.datetime.now(pytz.UTC) timestamp = int(frappe.utils.now_datetime().timestamp()) self.window_number, self.spent = divmod(timestamp, self.window) @@ -79,7 +80,7 @@ class RateLimiter: def record_request_end(self): if self.end is not None: return - self.end = datetime.utcnow() + self.end = datetime.datetime.now(pytz.UTC) self.duration = int((self.end - self.start).total_seconds() * 1000000) def respond(self): diff --git a/frappe/utils/caching.py b/frappe/utils/caching.py index a0e40abc8a..9b359e9209 100644 --- a/frappe/utils/caching.py +++ b/frappe/utils/caching.py @@ -1,12 +1,14 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. Check LICENSE +import datetime import json from collections import defaultdict from collections.abc import Callable -from datetime import datetime, timedelta from functools import wraps +import pytz + import frappe _SITE_CACHE = defaultdict(lambda: defaultdict(dict)) @@ -96,7 +98,7 @@ def site_cache(ttl: int | None = None, maxsize: int | None = None) -> Callable: if ttl is not None and not callable(ttl): func.ttl = ttl - func.expiration = datetime.utcnow() + timedelta(seconds=func.ttl) + func.expiration = datetime.datetime.now(pytz.UTC) + datetime.timedelta(seconds=func.ttl) if maxsize is not None and not callable(maxsize): func.maxsize = maxsize @@ -106,9 +108,9 @@ def site_cache(ttl: int | None = None, maxsize: int | None = None) -> Callable: if getattr(frappe.local, "initialised", None): func_call_key = json.dumps((args, kwargs)) - if hasattr(func, "ttl") and datetime.utcnow() >= func.expiration: + if hasattr(func, "ttl") and datetime.datetime.now(pytz.UTC) >= func.expiration: func.clear_cache() - func.expiration = datetime.utcnow() + timedelta(seconds=func.ttl) + func.expiration = datetime.datetime.now(pytz.UTC) + datetime.timedelta(seconds=func.ttl) if hasattr(func, "maxsize") and len(_SITE_CACHE[func_key][frappe.local.site]) >= func.maxsize: _SITE_CACHE[func_key][frappe.local.site].pop( diff --git a/frappe/utils/data.py b/frappe/utils/data.py index d3cb996c9d..f3cf219603 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -14,6 +14,7 @@ from enum import Enum from typing import Any, Literal, Optional, TypeVar, Union from urllib.parse import parse_qsl, quote, urlencode, urljoin, urlparse, urlunparse +import pytz from click import secho from dateutil import parser from dateutil.parser import ParserError @@ -295,7 +296,7 @@ def time_diff_in_hours(string_ed_date, string_st_date): def now_datetime(): - dt = convert_utc_to_system_timezone(datetime.datetime.utcnow()) + dt = convert_utc_to_system_timezone(datetime.datetime.now(pytz.UTC)) return dt.replace(tzinfo=None) @@ -322,15 +323,16 @@ def get_system_timezone(): def convert_utc_to_timezone(utc_timestamp, time_zone): from pytz import UnknownTimeZoneError, timezone - utcnow = timezone("UTC").localize(utc_timestamp) + if utc_timestamp.tzinfo is None: + utc_timestamp = timezone("UTC").localize(utc_timestamp) try: - return utcnow.astimezone(timezone(time_zone)) + return utc_timestamp.astimezone(timezone(time_zone)) except UnknownTimeZoneError: - return utcnow + return utc_timestamp def get_datetime_in_timezone(time_zone): - utc_timestamp = datetime.datetime.utcnow() + utc_timestamp = datetime.datetime.now(pytz.UTC) return convert_utc_to_timezone(utc_timestamp, time_zone) From 71201675ba5e5f9c2d65b4442dc6ffb3058fe05f Mon Sep 17 00:00:00 2001 From: RitvikSardana Date: Thu, 23 Nov 2023 13:33:15 +0530 Subject: [PATCH 029/362] fix: add back if condition --- frappe/public/js/form_builder/components/Field.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/form_builder/components/Field.vue b/frappe/public/js/form_builder/components/Field.vue index 92825e2375..26d488cdf8 100644 --- a/frappe/public/js/form_builder/components/Field.vue +++ b/frappe/public/js/form_builder/components/Field.vue @@ -176,8 +176,9 @@ function edit_filters() { function is_filter_applied() { if (props.field.df.link_filters) { try { - JSON.parse(props.field.df.link_filters).length > 0; - return "btn-filter-applied"; + if (JSON.parse(props.field.df.link_filters).length > 0) { + return "btn-filter-applied"; + } } catch (error) { return ""; } From fbc88a4d24b34d6e7c9339645c06465634581934 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Fri, 10 Nov 2023 18:31:30 +0530 Subject: [PATCH 030/362] refactor(treewide): code cleanup Drop redundant bool conversion Signed-off-by: Akhil Narang --- frappe/__init__.py | 10 ++++------ frappe/auth.py | 5 ++--- frappe/commands/site.py | 5 ++--- frappe/core/doctype/communication/mixins.py | 2 +- frappe/core/doctype/data_import/exporter.py | 3 +-- frappe/core/doctype/domain/domain.py | 3 ++- frappe/core/doctype/recorder/recorder.py | 5 +++-- frappe/core/doctype/report/report.py | 6 +++--- frappe/core/doctype/rq_job/rq_job.py | 3 ++- frappe/core/doctype/rq_worker/rq_worker.py | 3 ++- frappe/core/doctype/system_settings/system_settings.py | 7 ++++--- frappe/core/doctype/user/user.py | 4 ++-- frappe/core/doctype/user_type/user_type.py | 3 ++- frappe/database/mariadb/schema.py | 2 +- frappe/database/postgres/schema.py | 2 +- frappe/database/schema.py | 6 +++--- frappe/desk/doctype/desktop_icon/desktop_icon.py | 5 +++-- frappe/email/doctype/email_domain/email_domain.py | 3 ++- frappe/oauth.py | 2 +- frappe/rate_limiter.py | 2 +- .../doctype/energy_point_log/test_energy_point_log.py | 2 +- frappe/twofactor.py | 6 +++--- frappe/utils/dashboard.py | 2 +- frappe/utils/file_manager.py | 5 +---- frappe/utils/install.py | 3 ++- frappe/website/doctype/blog_post/blog_post.py | 7 ++++--- .../personal_data_deletion_request.py | 3 ++- .../doctype/website_settings/website_settings.py | 5 +++-- frappe/website/website_generator.py | 5 ++--- frappe/workflow/doctype/workflow/workflow.py | 7 ++++--- 30 files changed, 65 insertions(+), 61 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 90f142c14c..6cb6e4a3d4 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -17,7 +17,6 @@ import inspect import json import os import re -import unicodedata import warnings from collections.abc import Callable from typing import TYPE_CHECKING, Any, Literal, Optional, TypeAlias, overload @@ -43,7 +42,6 @@ from .utils.jinja import ( get_template, render_template, ) -from .utils.lazy_loader import lazy_import __version__ = "15.0.0-dev" __title__ = "Frappe Framework" @@ -418,7 +416,7 @@ def errprint(msg: str) -> None: :param msg: Message.""" msg = as_unicode(msg) - if not request or (not "cmd" in local.form_dict) or conf.developer_mode: + if not request or ("cmd" not in local.form_dict) or conf.developer_mode: print(msg) error_log.append({"exc": msg}) @@ -433,7 +431,7 @@ def log(msg: str) -> None: :param msg: Message.""" if not request: - if conf.get("logging") or False: + if conf.get("logging"): print(repr(msg)) debug_log.append(as_unicode(msg)) @@ -1959,7 +1957,7 @@ def get_all(doctype, *args, **kwargs): frappe.get_all("ToDo", fields=["*"], filters = [["modified", ">", "2014-01-01"]]) """ kwargs["ignore_permissions"] = True - if not "limit_page_length" in kwargs: + if "limit_page_length" not in kwargs: kwargs["limit_page_length"] = 0 return get_list(doctype, *args, **kwargs) @@ -2008,7 +2006,7 @@ def as_json(obj: dict | list, indent=1, separators=None, ensure_ascii=True) -> s def are_emails_muted(): - return flags.mute_emails or cint(conf.get("mute_emails") or 0) or False + return flags.mute_emails or cint(conf.get("mute_emails")) def get_test_records(doctype): diff --git a/frappe/auth.py b/frappe/auth.py index 941edb9277..084489b19a 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -96,7 +96,6 @@ class HTTPRequest: class LoginManager: - __slots__ = ("user", "info", "full_name", "user_type", "resume") def __init__(self): @@ -305,8 +304,8 @@ class LoginManager: def validate_hour(self): """check if user is logging in during restricted hours""" - login_before = int(frappe.db.get_value("User", self.user, "login_before", ignore=True) or 0) - login_after = int(frappe.db.get_value("User", self.user, "login_after", ignore=True) or 0) + login_before = cint(frappe.db.get_value("User", self.user, "login_before", ignore=True)) + login_after = cint(frappe.db.get_value("User", self.user, "login_after", ignore=True)) if not (login_before or login_after): return diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 28d6cac695..729e19594a 100644 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -413,7 +413,6 @@ def _reinstall( verbose=False, ): from frappe.installer import _new_site - from frappe.utils.synchronization import filelock if not yes: click.confirm("This will wipe your database. Are you sure you want to reinstall?", abort=True) @@ -951,9 +950,9 @@ def move(dest_dir, site): site_dump_exists = True count = 0 while site_dump_exists: - final_new_path = new_path + (count and str(count) or "") + final_new_path = new_path + str(count) site_dump_exists = os.path.exists(final_new_path) - count = int(count or 0) + 1 + count += 1 shutil.move(old_path, final_new_path) frappe.destroy() diff --git a/frappe/core/doctype/communication/mixins.py b/frappe/core/doctype/communication/mixins.py index 8db13b8993..bbe5881d7a 100644 --- a/frappe/core/doctype/communication/mixins.py +++ b/frappe/core/doctype/communication/mixins.py @@ -288,7 +288,7 @@ class CommunicationEmailMixin: "delayed": True, "communication": self.name, "read_receipt": self.read_receipt, - "is_notification": (self.sent_or_received == "Received" and True) or False, + "is_notification": (self.sent_or_received == "Received"), "print_letterhead": print_letterhead, } diff --git a/frappe/core/doctype/data_import/exporter.py b/frappe/core/doctype/data_import/exporter.py index 88d5b2b4b1..691474c3d3 100644 --- a/frappe/core/doctype/data_import/exporter.py +++ b/frappe/core/doctype/data_import/exporter.py @@ -144,8 +144,7 @@ class Exporter: value = doc.get(df.fieldname, None) if df.fieldtype == "Duration": - value = flt(value or 0) - value = format_duration(value, df.hide_days) + value = format_duration(flt(value), df.hide_days) row[i] = value return rows diff --git a/frappe/core/doctype/domain/domain.py b/frappe/core/doctype/domain/domain.py index 0c98856490..50e187968e 100644 --- a/frappe/core/doctype/domain/domain.py +++ b/frappe/core/doctype/domain/domain.py @@ -4,6 +4,7 @@ import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.model.document import Document +from frappe.utils import cint class Domain(Document): @@ -28,7 +29,7 @@ class Domain(Document): self.setup_properties() self.set_values() - if not int(frappe.defaults.get_defaults().setup_complete or 0): + if not cint(frappe.defaults.get_defaults().setup_complete): # if setup not complete, setup desktop etc. self.setup_sidebar_items() self.set_default_portal_role() diff --git a/frappe/core/doctype/recorder/recorder.py b/frappe/core/doctype/recorder/recorder.py index c8ca1cc798..f5ef909a2a 100644 --- a/frappe/core/doctype/recorder/recorder.py +++ b/frappe/core/doctype/recorder/recorder.py @@ -4,7 +4,7 @@ import frappe from frappe.model.document import Document from frappe.recorder import get as get_recorder_data -from frappe.utils import cint, evaluate_filters, make_filter_dict +from frappe.utils import cint, evaluate_filters class Recorder(Document): @@ -27,6 +27,7 @@ class Recorder(Document): sql_queries: DF.Table[RecorderQuery] time: DF.Datetime | None time_in_queries: DF.Float + # end: auto-generated types def load_from_db(self): @@ -38,7 +39,7 @@ class Recorder(Document): @staticmethod def get_list(args): - start = cint(args.get("start")) or 0 + start = cint(args.get("start")) page_length = cint(args.get("page_length")) or 20 requests = Recorder.get_filtered_requests(args)[start : start + page_length] diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 0443766de1..65fa096df0 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -45,6 +45,7 @@ class Report(Document): report_script: DF.Code | None report_type: DF.Literal["Report Builder", "Query Report", "Script Report", "Custom Report"] roles: DF.Table[HasRole] + # end: auto-generated types def validate(self): """only administrator can save standard report""" @@ -129,7 +130,7 @@ class Report(Document): if frappe.flags.in_import: return - if self.is_standard == "Yes" and (frappe.local.conf.get("developer_mode") or 0) == 1: + if self.is_standard == "Yes" and (frappe.local.conf.get("developer_mode", 0) == 1): export_to_files( record_list=[["Report", self.name]], record_module=self.module, create_init=True ) @@ -155,7 +156,6 @@ class Report(Document): def execute_script_report(self, filters): # save the timestamp to automatically set to prepared threshold = 15 - res = [] start_time = datetime.datetime.now() @@ -382,7 +382,7 @@ class Report(Document): def is_prepared_report_enabled(report): - return cint(frappe.db.get_value("Report", report, "prepared_report")) or 0 + return cint(frappe.db.get_value("Report", report, "prepared_report")) def get_report_module_dotted_path(module, report_name): diff --git a/frappe/core/doctype/rq_job/rq_job.py b/frappe/core/doctype/rq_job/rq_job.py index 453a375a5a..81fa3fdf3e 100644 --- a/frappe/core/doctype/rq_job/rq_job.py +++ b/frappe/core/doctype/rq_job/rq_job.py @@ -59,6 +59,7 @@ class RQJob(Document): ] time_taken: DF.Duration | None timeout: DF.Duration | None + # end: auto-generated types def load_from_db(self): try: @@ -79,7 +80,7 @@ class RQJob(Document): @staticmethod def get_list(args): - start = cint(args.get("start")) or 0 + start = cint(args.get("start")) page_length = cint(args.get("page_length")) or 20 order_desc = "desc" in args.get("order_by", "") diff --git a/frappe/core/doctype/rq_worker/rq_worker.py b/frappe/core/doctype/rq_worker/rq_worker.py index 68dd83c41b..025feb18a6 100644 --- a/frappe/core/doctype/rq_worker/rq_worker.py +++ b/frappe/core/doctype/rq_worker/rq_worker.py @@ -34,6 +34,7 @@ class RQWorker(Document): total_working_time: DF.Duration | None utilization_percent: DF.Percent worker_name: DF.Data | None + # end: auto-generated types def load_from_db(self): @@ -47,7 +48,7 @@ class RQWorker(Document): @staticmethod def get_list(args): - start = cint(args.get("start")) or 0 + start = cint(args.get("start")) page_length = cint(args.get("page_length")) or 20 workers = get_workers() diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index 9a34ccd5b6..1c64a22e54 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -93,12 +93,13 @@ class SystemSettings(Document): time_zone: DF.Literal two_factor_method: DF.Literal["OTP App", "SMS", "Email"] welcome_email_template: DF.Link | None + # end: auto-generated types def validate(self): from frappe.twofactor import toggle_two_factor_auth - enable_password_policy = cint(self.enable_password_policy) and True or False - minimum_password_score = cint(getattr(self, "minimum_password_score", 0)) or 0 + enable_password_policy = cint(self.enable_password_policy) + minimum_password_score = cint(getattr(self, "minimum_password_score", 0)) if enable_password_policy and minimum_password_score <= 0: frappe.throw(_("Please select Minimum Password Score")) elif not enable_password_policy: @@ -195,7 +196,7 @@ def update_last_reset_password_date(): def load(): from frappe.utils.momentjs import get_all_timezones - if not "System Manager" in frappe.get_roles(): + if "System Manager" not in frappe.get_roles(): frappe.throw(_("Not permitted"), frappe.PermissionError) all_defaults = frappe.db.get_defaults() diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index cdb3e394ee..78ba86de3f 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -872,7 +872,7 @@ def test_password_strength( "Arguments `key` and `old_password` are deprecated in function `test_password_strength`." ) - enable_password_policy = frappe.get_system_settings("enable_password_policy") or 0 + enable_password_policy = frappe.get_system_settings("enable_password_policy") if not enable_password_policy: return {} @@ -885,7 +885,7 @@ def test_password_strength( if new_password: result = _test_password_strength(new_password, user_inputs=user_data) password_policy_validation_passed = False - minimum_password_score = cint(frappe.get_system_settings("minimum_password_score")) or 0 + minimum_password_score = cint(frappe.get_system_settings("minimum_password_score")) # score should be greater than 0 and minimum_password_score if result.get("score") and result.get("score") >= minimum_password_score: diff --git a/frappe/core/doctype/user_type/user_type.py b/frappe/core/doctype/user_type/user_type.py index 355b390b3f..0d7c2f9c9f 100644 --- a/frappe/core/doctype/user_type/user_type.py +++ b/frappe/core/doctype/user_type/user_type.py @@ -31,6 +31,7 @@ class UserType(Document): user_doctypes: DF.Table[UserDocumentType] user_id_field: DF.Literal user_type_modules: DF.Table[UserTypeModule] + # end: auto-generated types def validate(self): self.set_modules() @@ -140,7 +141,7 @@ class UserType(Document): for row in self.user_doctypes: docperm = add_role_permissions(row.document_type, self.role) - values = {perm: row.get(perm) or 0 for perm in perms} + values = {perm: row.get(perm, default=0) for perm in perms} for perm in ["print", "email", "share"]: values[perm] = 1 diff --git a/frappe/database/mariadb/schema.py b/frappe/database/mariadb/schema.py index d57335f589..0486ab9463 100644 --- a/frappe/database/mariadb/schema.py +++ b/frappe/database/mariadb/schema.py @@ -24,7 +24,7 @@ class MariaDBTable(DBTable): additional_definitions += index_defs # child table columns - if self.meta.get("istable") or 0: + if self.meta.get("istable", default=0): additional_definitions += [ f"parent varchar({varchar_len})", f"parentfield varchar({varchar_len})", diff --git a/frappe/database/postgres/schema.py b/frappe/database/postgres/schema.py index 946747aa47..48fd66e31a 100644 --- a/frappe/database/postgres/schema.py +++ b/frappe/database/postgres/schema.py @@ -17,7 +17,7 @@ class PostgresTable(DBTable): additional_definitions += ",\n".join(column_defs) # child table columns - if self.meta.get("istable") or 0: + if self.meta.get("istable", default=0): if column_defs: additional_definitions += ",\n" diff --git a/frappe/database/schema.py b/frappe/database/schema.py index 90c3055452..1387cbc549 100644 --- a/frappe/database/schema.py +++ b/frappe/database/schema.py @@ -213,11 +213,11 @@ class DbColumn: unique = False if self.fieldtype in ("Check", "Int"): - default = cint(self.default) or 0 + default = cint(self.default) null = False elif self.fieldtype in ("Currency", "Float", "Percent"): - default = flt(self.default) or 0 + default = flt(self.default) null = False elif ( @@ -292,7 +292,7 @@ class DbColumn: if (current_def["index"] and not self.set_index) and column_type not in ("text", "longtext"): self.table.drop_index.append(self) - elif (not current_def["index"] and self.set_index) and not (column_type in ("text", "longtext")): + elif (not current_def["index"] and self.set_index) and column_type not in ("text", "longtext"): self.table.add_index.append(self) def default_changed(self, current_def): diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py index 7901ef9500..fda9eed7bb 100644 --- a/frappe/desk/doctype/desktop_icon/desktop_icon.py +++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py @@ -37,6 +37,7 @@ class DesktopIcon(Document): reverse: DF.Check standard: DF.Check type: DF.Literal["module", "list", "link", "page", "query-report"] + # end: auto-generated types def validate(self): if not self.label: @@ -225,7 +226,7 @@ def add_user_icon(_doctype, _report=None, label=None, link=None, type="link", st icon_name = new_icon.name - except frappe.UniqueValidationError as e: + except frappe.UniqueValidationError: frappe.throw(_("Desktop Icon already exists")) except Exception as e: raise e @@ -262,7 +263,7 @@ def set_desktop_icons(visible_list, ignore_duplicate=True): an icon for the doctype""" # clear all custom only if setup is not complete - if not int(frappe.defaults.get_defaults().setup_complete or 0): + if not frappe.defaults.get_defaults().get("setup_complete", 0): frappe.db.delete("Desktop Icon", {"standard": 0}) # set standard as blocked and hidden if setting first active domain diff --git a/frappe/email/doctype/email_domain/email_domain.py b/frappe/email/doctype/email_domain/email_domain.py index 5b9f38615a..fef6e1b303 100644 --- a/frappe/email/doctype/email_domain/email_domain.py +++ b/frappe/email/doctype/email_domain/email_domain.py @@ -73,6 +73,7 @@ class EmailDomain(Document): use_ssl_for_outgoing: DF.Check use_starttls: DF.Check use_tls: DF.Check + # end: auto-generated types def validate(self): """Validate POP3/IMAP and SMTP connections.""" @@ -120,4 +121,4 @@ class EmailDomain(Document): elif self.use_tls: self.smtp_port = self.smtp_port or 587 - conn_method((self.smtp_server or ""), cint(self.smtp_port) or 0).quit() + conn_method((self.smtp_server or ""), cint(self.smtp_port)).quit() diff --git a/frappe/oauth.py b/frappe/oauth.py index f0a13488da..78a70a2c1e 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -252,7 +252,7 @@ class OAuthWebRequestValidator(RequestValidator): ) are_scopes_valid = True for scp in scopes: - are_scopes_valid = are_scopes_valid and True if scp in client_scopes else False + are_scopes_valid = are_scopes_valid if scp in client_scopes else False return is_token_valid and are_scopes_valid diff --git a/frappe/rate_limiter.py b/frappe/rate_limiter.py index e1c93338e7..f1451ef91c 100644 --- a/frappe/rate_limiter.py +++ b/frappe/rate_limiter.py @@ -140,7 +140,7 @@ def rate_limit( cache_key = f"rl:{frappe.form_dict.cmd}:{identity}" - value = frappe.cache.get(cache_key) or 0 + value = frappe.cache.get(cache_key) if not value: frappe.cache.setex(cache_key, seconds, 0) diff --git a/frappe/social/doctype/energy_point_log/test_energy_point_log.py b/frappe/social/doctype/energy_point_log/test_energy_point_log.py index 4cc176013d..2b4a8335fc 100644 --- a/frappe/social/doctype/energy_point_log/test_energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/test_energy_point_log.py @@ -376,7 +376,7 @@ def create_a_todo(description=None): def get_points(user, point_type="energy_points"): - return _get_energy_points(user).get(point_type) or 0 + return _get_energy_points(user).get(point_type, 0) def assign_users_to_todo(todo_name, users): diff --git a/frappe/twofactor.py b/frappe/twofactor.py index 2236be3267..c166af56ce 100644 --- a/frappe/twofactor.py +++ b/frappe/twofactor.py @@ -43,10 +43,10 @@ def toggle_two_factor_auth(state, roles=None): def two_factor_is_enabled(user=None): """Returns True if 2FA is enabled.""" - enabled = int(frappe.db.get_single_value("System Settings", "enable_two_factor_auth") or 0) + enabled = cint(frappe.db.get_single_value("System Settings", "enable_two_factor_auth")) if enabled: - bypass_two_factor_auth = int( - frappe.db.get_single_value("System Settings", "bypass_2fa_for_retricted_ip_users") or 0 + bypass_two_factor_auth = cint( + frappe.db.get_single_value("System Settings", "bypass_2fa_for_retricted_ip_users") ) if bypass_two_factor_auth and user: user_doc = frappe.get_doc("User", user) diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py index 4ec3f090fc..d3fa6ac6fa 100644 --- a/frappe/utils/dashboard.py +++ b/frappe/utils/dashboard.py @@ -22,7 +22,7 @@ def cache_source(function): return function(chart=chart, no_cache=no_cache) chart_name = frappe.parse_json(chart).name cache_key = f"chart-data:{chart_name}" - if int(kwargs.get("refresh") or 0): + if cint(kwargs.get("refresh")): results = generate_and_cache_results(kwargs, function, cache_key, chart) else: cached_results = frappe.cache.get_value(cache_key) diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index 17901c56e8..02ddcc2940 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -3,7 +3,6 @@ import base64 import hashlib -import io import json import mimetypes import os @@ -283,9 +282,7 @@ def remove_file( ignore_permissions, comment = False, None if attached_to_doctype and attached_to_name and not from_delete: doc = frappe.get_doc(attached_to_doctype, attached_to_name) - ignore_permissions = doc.has_permission("write") or False - if frappe.flags.in_web_form: - ignore_permissions = True + ignore_permissions = frappe.flags.in_web_form or doc.has_permission("write") if not file_name: file_name = frappe.db.get_value("File", fid, "file_name") comment = doc.add_comment("Attachment Removed", file_name) diff --git a/frappe/utils/install.py b/frappe/utils/install.py index df918c27e0..2fb423bb0b 100644 --- a/frappe/utils/install.py +++ b/frappe/utils/install.py @@ -4,6 +4,7 @@ import getpass import frappe from frappe.geo.doctype.country.country import import_country_and_currency +from frappe.utils import cint from frappe.utils.password import update_password @@ -166,7 +167,7 @@ def before_tests(): frappe.clear_cache() # complete setup if missing - if not int(frappe.db.get_single_value("System Settings", "setup_complete") or 0): + if not cint(frappe.db.get_single_value("System Settings", "setup_complete")): complete_setup_wizard() frappe.db.set_single_value("Website Settings", "disable_signup", 0) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index f6a2c2f495..a59ae89764 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -53,6 +53,7 @@ class BlogPost(WebsiteGenerator): read_time: DF.Int route: DF.Data | None title: DF.Data + # end: auto-generated types @frappe.whitelist() def make_route(self): @@ -211,14 +212,14 @@ class BlogPost(WebsiteGenerator): "reference_name": self.name, } - context.like_count = frappe.db.count("Comment", filters) or 0 + context.like_count = frappe.db.count("Comment", filters) filters["comment_email"] = user if user == "Guest": filters["ip_address"] = frappe.local.request_ip - context.like = frappe.db.count("Comment", filters) or 0 + context.like = frappe.db.count("Comment", filters) def set_read_time(self): content = self.content or self.content_html or "" @@ -385,7 +386,7 @@ def get_blog_list( if ( post.avatar - and (not "http:" in post.avatar and not "https:" in post.avatar) + and ("http:" not in post.avatar and "https:" not in post.avatar) and not post.avatar.startswith("/") ): post.avatar = "/" + post.avatar diff --git a/frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.py b/frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.py index 63ed5c24ed..6b166d2a14 100644 --- a/frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.py +++ b/frappe/website/doctype/personal_data_deletion_request/personal_data_deletion_request.py @@ -29,6 +29,7 @@ class PersonalDataDeletionRequest(Document): deletion_steps: DF.Table[PersonalDataDeletionStep] email: DF.Data status: DF.Literal["Pending Verification", "Pending Approval", "On Hold", "Deleted"] + # end: auto-generated types def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -154,7 +155,7 @@ class PersonalDataDeletionRequest(Document): row_data = { "status": "Pending", "document_type": step.get("doctype"), - "partial": step.get("partial") or False, + "partial": step.get("partial", False), "fields": json.dumps(step.get("redact_fields", [])), "filtered_by": step.get("filtered_by") or "", } diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index 931acb87cf..d19ac375a5 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -6,7 +6,7 @@ from urllib.parse import quote import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import encode, get_request_site_address +from frappe.utils import cint, encode, get_request_site_address from frappe.website.utils import get_boot_data @@ -63,6 +63,7 @@ class WebsiteSettings(Document): top_bar_items: DF.Table[TopBarItem] website_theme: DF.Link | None website_theme_image_link: DF.Code | None + # end: auto-generated types def validate(self): self.validate_top_bar_items() @@ -216,7 +217,7 @@ def get_website_settings(context=None): "linked_in_share", "disable_signup", ]: - context[k] = int(context.get(k) or 0) + context[k] = cint(context.get(k)) if settings.address: context["footer_address"] = settings.address diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index 509a01033c..43db2a780e 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -98,8 +98,8 @@ class WebsiteGenerator(Document): def is_website_published(self): """Return true if published in website""" - if self.get_condition_field(): - return self.get(self.get_condition_field()) and True or False + if data := self.get_condition_field(): + return self.get(data) or False else: return True @@ -141,7 +141,6 @@ class WebsiteGenerator(Document): and self.is_website_published() and self.meta.allow_guest_to_view ): - url = frappe.utils.get_url(self.route) frappe.enqueue( "frappe.website.doctype.website_settings.google_indexing.publish_site", diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index 9591d3df69..a8c59748b1 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -3,8 +3,8 @@ import frappe from frappe import _ -from frappe.model import no_value_fields from frappe.model.document import Document +from frappe.utils import cint class Workflow(Document): @@ -29,6 +29,7 @@ class Workflow(Document): workflow_data: DF.JSON | None workflow_name: DF.Data workflow_state_field: DF.Data + # end: auto-generated types def validate(self): self.set_active() @@ -69,7 +70,7 @@ class Workflow(Document): docstatus_map = {} states = self.get("states") for d in states: - if not d.doc_status in docstatus_map: + if d.doc_status not in docstatus_map: frappe.db.sql( """ UPDATE `tab{doctype}` @@ -140,7 +141,7 @@ class Workflow(Document): frappe.throw(frappe._("Cannot cancel before submitting. See Transition {0}").format(t.idx)) def set_active(self): - if int(self.is_active or 0): + if cint(self.is_active): # clear all other frappe.db.sql( """UPDATE `tabWorkflow` SET `is_active`=0 From 43ffd175590f1d5c0072764ac292fec11e1d1096 Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor Date: Thu, 23 Nov 2023 14:26:29 +0530 Subject: [PATCH 031/362] chore: translation --- frappe/public/js/frappe/ui/group_by/group_by.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 46b85a05cb..2f62904813 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -153,7 +153,7 @@ frappe.ui.GroupBy = class { let option_text = doctype == this.doctype ? field_label - : `${field_label} (${__(doctype)})`; + : `${__(field_label)} (${__(doctype)})`; this.aggregate_on_html += ``; } From 1e0920409fc22455a118cec7c13003266af20841 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Nov 2023 14:49:28 +0530 Subject: [PATCH 032/362] fix: ignore dynamic fields in virtual doctypes --- frappe/model/dynamic_links.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/dynamic_links.py b/frappe/model/dynamic_links.py index 523d587389..61ed86de46 100644 --- a/frappe/model/dynamic_links.py +++ b/frappe/model/dynamic_links.py @@ -13,7 +13,7 @@ dynamic_link_queries = [ `tabDocField`.fieldname, `tabDocField`.options from `tabDocField`, `tabDocType` where `tabDocField`.fieldtype='Dynamic Link' and - `tabDocType`.`name`=`tabDocField`.parent + `tabDocType`.`name`=`tabDocField`.parent and `tabDocType`.is_virtual = 0 order by `tabDocType`.read_only, `tabDocType`.in_create""", """select `tabCustom Field`.dt as parent, `tabDocType`.read_only, `tabDocType`.in_create, From f007f16ce98a61beafc2507eba60065ed9484caa Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Thu, 23 Nov 2023 15:35:37 +0530 Subject: [PATCH 033/362] fix: handle invalid passwords better (#23377) * chore(login): show a message for response code 500 as well Signed-off-by: Akhil Narang * refactor: reject passwords > 512 characters Signed-off-by: Akhil Narang --------- Signed-off-by: Akhil Narang --- frappe/auth.py | 4 ++++ frappe/core/doctype/user/user.py | 4 ++++ frappe/templates/includes/login/login.js | 5 +++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frappe/auth.py b/frappe/auth.py index 941edb9277..efd1428545 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -25,6 +25,7 @@ from frappe.website.utils import get_home_page SAFE_HTTP_METHODS = frozenset(("GET", "HEAD", "OPTIONS")) UNSAFE_HTTP_METHODS = frozenset(("POST", "PUT", "DELETE", "PATCH")) +MAX_PASSWORD_SIZE = 512 class HTTPRequest: @@ -235,6 +236,9 @@ class LoginManager: if not (user and pwd): self.fail(_("Incomplete login details"), user=user) + if len(pwd) > MAX_PASSWORD_SIZE: + self.fail(_("Password size exceeded the maximum allowed size"), user=user) + _raw_user_name = user user = User.find_by_credentials(user, pwd) diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index cdb3e394ee..711c07ed95 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -9,6 +9,7 @@ import frappe.defaults import frappe.permissions import frappe.share from frappe import STANDARD_USERS, _, msgprint, throw +from frappe.auth import MAX_PASSWORD_SIZE from frappe.core.doctype.user_type.user_type import user_linked_with_permission_on_doctype from frappe.desk.doctype.notification_settings.notification_settings import ( create_notification_settings, @@ -823,6 +824,9 @@ def update_password( old_password (str, optional): Old password. Defaults to None. """ + if len(new_password) > MAX_PASSWORD_SIZE: + frappe.throw(_("Password size exceeded the maximum allowed size.")) + result = test_password_strength(new_password) feedback = result.get("feedback", None) diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index 6e18421837..90e12cf3d0 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -287,8 +287,9 @@ login.login_handlers = (function () { } }, 401: get_error_handler('{{ _("Invalid Login. Try again.") }}'), - 417: get_error_handler('{{ _("Oops! Something went wrong") }}'), - 404: get_error_handler('{{ _("User does not exist.")}}') + 417: get_error_handler('{{ _("Oops! Something went wrong.") }}'), + 404: get_error_handler('{{ _("User does not exist.")}}'), + 500: get_error_handler('{{ _("Something went wrong.") }}') }; return login_handlers; From f8c5a61bbd27629dc16d6e18903e1d2b3cf8bd52 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Thu, 23 Nov 2023 15:54:13 +0530 Subject: [PATCH 034/362] refactor(report): simplify check further NoneType will evaluate to False here so we don't need this complexity Co-authored-by: Ankush Menat --- frappe/core/doctype/report/report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 65fa096df0..2d78892f14 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -130,7 +130,7 @@ class Report(Document): if frappe.flags.in_import: return - if self.is_standard == "Yes" and (frappe.local.conf.get("developer_mode", 0) == 1): + if self.is_standard == "Yes" and frappe.conf.developer_mode: export_to_files( record_list=[["Report", self.name]], record_module=self.module, create_init=True ) From cc58fd20ca2dd52a8c5761358137ee645d1716d1 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Thu, 23 Nov 2023 16:04:56 +0530 Subject: [PATCH 035/362] refactor(oauth): simplify scopes check with the usage of `all()` Signed-off-by: Akhil Narang --- frappe/oauth.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frappe/oauth.py b/frappe/oauth.py index 78a70a2c1e..ebd6b91ae7 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -250,10 +250,7 @@ class OAuthWebRequestValidator(RequestValidator): client_scopes = frappe.db.get_value("OAuth Client", otoken.client, "scopes").split( get_url_delimiter() ) - are_scopes_valid = True - for scp in scopes: - are_scopes_valid = are_scopes_valid if scp in client_scopes else False - + are_scopes_valid = all(scope in client_scopes for scope in scopes) return is_token_valid and are_scopes_valid # Token refresh request From 23bcb6733ebfcc5a4eaef14d6c938ea6dda2a1c6 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Thu, 23 Nov 2023 16:06:07 +0530 Subject: [PATCH 036/362] refactor(website_generator): update variable name to make more sense as per usage Co-authored-by: Ankush Menat --- frappe/website/website_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index 43db2a780e..8f814f60cb 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -98,8 +98,8 @@ class WebsiteGenerator(Document): def is_website_published(self): """Return true if published in website""" - if data := self.get_condition_field(): - return self.get(data) or False + if condition_field := self.get_condition_field(): + return self.get(condition_field) or False else: return True From d21c7730dab6b8e8dc946af49a31f3b423e03aca Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Thu, 23 Nov 2023 16:34:10 +0530 Subject: [PATCH 037/362] fix(commands/site): the old logic did rely on 0 evaluting to false Signed-off-by: Akhil Narang --- frappe/commands/site.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 729e19594a..65f896eb24 100644 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -950,7 +950,7 @@ def move(dest_dir, site): site_dump_exists = True count = 0 while site_dump_exists: - final_new_path = new_path + str(count) + final_new_path = new_path + str(count or "") site_dump_exists = os.path.exists(final_new_path) count += 1 From d258222f284ce3be474c765e3654682afcbab0a4 Mon Sep 17 00:00:00 2001 From: Corentin Flr <10946971+cogk@users.noreply.github.com> Date: Tue, 21 Nov 2023 00:01:33 +0100 Subject: [PATCH 038/362] fix: Inherit text-align for .btn-reset --- frappe/public/scss/common/buttons.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/scss/common/buttons.scss b/frappe/public/scss/common/buttons.scss index 672498ada3..d0eb6da859 100644 --- a/frappe/public/scss/common/buttons.scss +++ b/frappe/public/scss/common/buttons.scss @@ -118,6 +118,7 @@ border: 0; font-size: inherit; background-color: inherit; + text-align: inherit; } [data-theme="dark"] { From f825acf92249cb585478bd8ef608abc173e6486c Mon Sep 17 00:00:00 2001 From: Corentin Flr <10946971+cogk@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:14:29 +0100 Subject: [PATCH 039/362] fix(a11y): Make navbar more accessible * Use