diff --git a/cypress/integration/folder_navigation.js b/cypress/integration/folder_navigation.js index 1f2f4dfe67..ba65454ef6 100644 --- a/cypress/integration/folder_navigation.js +++ b/cypress/integration/folder_navigation.js @@ -8,6 +8,7 @@ context("Folder Navigation", () => { it("Adding Folders", () => { //Adding filter to go into the home folder cy.get(".filter-x-button").click(); + cy.click_filter_button(); cy.get(".filter-action-buttons > .text-muted").findByText("+ Add a Filter").click(); cy.get(".fieldname-select-area > .awesomplete > .form-control:last").type("Fol{enter}"); cy.get( @@ -46,9 +47,13 @@ context("Folder Navigation", () => { //Adding a file inside the Test Folder cy.findByRole("button", { name: "Add File" }).eq(0).click({ force: true }); cy.get(".file-uploader").findByText("Link").click(); - cy.get(".input-group > .form-control").type( - "https://wallpaperplay.com/walls/full/8/2/b/72402.jpg" - ); + cy.get(".input-group > input.form-control:visible").as("upload_input"); + cy.get("@upload_input").type("https://wallpaperplay.com/walls/full/8/2/b/72402.jpg", { + waitForAnimations: false, + parseSpecialCharSequences: false, + force: true, + delay: 100, + }); cy.click_modal_primary_button("Upload"); //To check if the added file is present in the Test Folder diff --git a/cypress/integration/list_paging.js b/cypress/integration/list_paging.js index 3071950260..5195d0b3ae 100644 --- a/cypress/integration/list_paging.js +++ b/cypress/integration/list_paging.js @@ -12,7 +12,7 @@ context("List Paging", () => { it("test load more with count selection buttons", () => { cy.visit("/app/todo/view/report"); - cy.clear_filters(); + cy.get(".filter-x-button").click(); cy.get(".list-paging-area .list-count").should("contain.text", "20 of"); cy.get(".list-paging-area .btn-more").click(); diff --git a/cypress/integration/list_view_drag_select.js b/cypress/integration/list_view_drag_select.js index d481390d89..2dcb31372c 100644 --- a/cypress/integration/list_view_drag_select.js +++ b/cypress/integration/list_view_drag_select.js @@ -5,6 +5,7 @@ context("List View", () => { }); it("List view check rows on drag", () => { + cy.get(".filter-x-button").click(); cy.get(".list-row-checkbox").then(($checkbox) => { cy.wrap($checkbox).first().trigger("mousedown"); cy.get(".level.list-row").each(($ele) => { diff --git a/frappe/__init__.py b/frappe/__init__.py index 9d7befe2d1..4d67afe492 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -182,9 +182,9 @@ if TYPE_CHECKING: # end: static analysis hack -def init(site: str, sites_path: str = ".", new_site: bool = False) -> None: +def init(site: str, sites_path: str = ".", new_site: bool = False, force=False) -> None: """Initialize frappe for the current site. Reset thread locals `frappe.local`""" - if getattr(local, "initialised", None): + if getattr(local, "initialised", None) and not force: return local.error_log = [] diff --git a/frappe/app.py b/frappe/app.py index a647b251c8..fab8facd3f 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -74,12 +74,18 @@ def application(request: Request): rollback = sync_database(rollback) finally: + # Important note: + # this function *must* always return a response, hence any exception thrown outside of + # try..catch block like this finally block needs to be handled appropriately. + if request.method in UNSAFE_HTTP_METHODS and frappe.db and rollback: frappe.db.rollback() - if getattr(frappe.local, "initialised", False): - for after_request_task in frappe.get_hooks("after_request"): - frappe.call(after_request_task, response=response, request=request) + try: + run_after_request_hooks(request, response) + except Exception as e: + # We can not handle exceptions safely here. + frappe.logger().error("Failed to run after request hook", exc_info=True) log_request(request, response) process_response(response) @@ -89,12 +95,20 @@ def application(request: Request): return response +def run_after_request_hooks(request, response): + if not getattr(frappe.local, "initialised", False): + return + + for after_request_task in frappe.get_hooks("after_request"): + frappe.call(after_request_task, response=response, request=request) + + def init_request(request): frappe.local.request = request frappe.local.is_ajax = frappe.get_request_header("X-Requested-With") == "XMLHttpRequest" site = _site or request.headers.get("X-Frappe-Site-Name") or get_site_name(request.host) - frappe.init(site=site, sites_path=_sites_path) + frappe.init(site=site, sites_path=_sites_path, force=True) if not (frappe.local.conf and frappe.local.conf.db_name): # site does not exist diff --git a/frappe/core/doctype/data_import/exporter.py b/frappe/core/doctype/data_import/exporter.py index 1066b8bed9..2c9bfd95fd 100644 --- a/frappe/core/doctype/data_import/exporter.py +++ b/frappe/core/doctype/data_import/exporter.py @@ -205,9 +205,11 @@ class Exporter: for df in self.fields: is_parent = not df.is_child_table_field if is_parent: - label = _(df.label) + label = _(df.label or df.fieldname) else: - label = f"{_(df.label)} ({_(df.child_table_df.label)})" + label = ( + f"{_(df.label or df.fieldname)} ({_(df.child_table_df.label or df.child_table_df.fieldname)})" + ) if label in header: # this label is already in the header, diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 6cc0adcc87..9a0613e6ca 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -345,6 +345,7 @@ class DocType(Document): "name", "parent", "creation", + "owner", "modified", "modified_by", "parentfield", diff --git a/frappe/core/doctype/language/language.json b/frappe/core/doctype/language/language.json index 7e9bbb1038..c9110bb998 100644 --- a/frappe/core/doctype/language/language.json +++ b/frappe/core/doctype/language/language.json @@ -51,7 +51,7 @@ "icon": "fa fa-globe", "in_create": 1, "links": [], - "modified": "2022-08-14 18:54:03.490836", + "modified": "2023-04-13 13:48:38.127995", "modified_by": "Administrator", "module": "Core", "name": "Language", @@ -66,13 +66,8 @@ "write": 1 }, { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Guest", - "share": 1 + "role": "All", + "read": 1 } ], "search_fields": "language_name", diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index 37dce73dda..9b6b04afcc 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -148,11 +148,13 @@ { "collapsible": 1, "collapsible_depends_on": "filters", + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "filters_section", "fieldtype": "Section Break", "label": "Filters" }, { + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "filters", "fieldtype": "Table", "label": "Filters", @@ -161,11 +163,13 @@ { "collapsible": 1, "collapsible_depends_on": "columns", + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "columns_section", "fieldtype": "Section Break", "label": "Columns" }, { + "depends_on": "eval:doc.report_type != \"Custom Report\"", "fieldname": "columns", "fieldtype": "Table", "label": "Columns", @@ -182,7 +186,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2022-11-20 14:56:36.578412", + "modified": "2023-04-07 18:18:11.782178", "modified_by": "Administrator", "module": "Core", "name": "Report", diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index ef38387e57..ca1e7724c1 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -169,7 +169,7 @@ class Report(Document): return columns, result - def run_query_report(self, filters, user, ignore_prepared_report=False): + def run_query_report(self, filters=None, user=None, ignore_prepared_report=False): columns, result = [], [] data = frappe.desk.query_report.run( self.name, filters=filters, user=user, ignore_prepared_report=ignore_prepared_report diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index 0e1ed80eda..670b6b7410 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -118,11 +118,10 @@ class TestReport(FrappeTestCase): } ] ), + json.dumps({"user": "Administrator", "doctype": "User"}), ) custom_report = frappe.get_doc("Report", custom_report_name) - columns, result = custom_report.run_query_report( - filters={"user": "Administrator", "doctype": "User"}, user=frappe.session.user - ) + columns, result = custom_report.run_query_report(user=frappe.session.user) self.assertListEqual(["email"], [column.get("fieldname") for column in columns]) admin_dict = frappe.core.utils.find(result, lambda d: d["name"] == "Administrator") diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index bb845cae95..3f906d8f12 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -15,12 +15,13 @@ from frappe.model.utils import render_include from frappe.modules import get_module_path, scrub from frappe.monitor import add_data_to_monitor from frappe.permissions import get_role_permissions -from frappe.utils import cint, cstr, flt, format_duration, get_html_format +from frappe.utils import cint, cstr, flt, format_duration, get_html_format, sbool def get_report_doc(report_name): doc = frappe.get_doc("Report", report_name) doc.custom_columns = [] + doc.custom_filters = [] if doc.report_type == "Custom Report": custom_report_doc = doc @@ -30,7 +31,8 @@ def get_report_doc(report_name): if custom_report_doc.json: data = json.loads(custom_report_doc.json) if data: - doc.custom_columns = data["columns"] + doc.custom_columns = data.get("columns") + doc.custom_filters = data.get("filters") doc.is_custom_report = True if not doc.is_permitted(): @@ -182,6 +184,7 @@ def run( custom_columns=None, is_tree=False, parent_field=None, + are_default_filters=True, ): report = get_report_doc(report_name) if not user: @@ -194,6 +197,9 @@ def run( result = None + if sbool(are_default_filters) and report.custom_filters: + filters = report.custom_filters + if report.prepared_report and not ignore_prepared_report and not custom_columns: if filters: if isinstance(filters, str): @@ -209,6 +215,9 @@ def run( result["add_total_row"] = report.add_total_row and not result.get("skip_total_row", False) + if sbool(are_default_filters) and report.custom_filters: + result["custom_filters"] = report.custom_filters + return result @@ -463,7 +472,7 @@ def get_data_for_custom_report(columns): @frappe.whitelist() -def save_report(reference_report, report_name, columns): +def save_report(reference_report, report_name, columns, filters): report_doc = get_report_doc(reference_report) docname = frappe.db.exists( @@ -479,6 +488,7 @@ def save_report(reference_report, report_name, columns): report = frappe.get_doc("Report", docname) existing_jd = json.loads(report.json) existing_jd["columns"] = json.loads(columns) + existing_jd["filters"] = json.loads(filters) report.update({"json": json.dumps(existing_jd, separators=(",", ":"))}) report.save() frappe.msgprint(_("Report updated successfully")) @@ -489,7 +499,7 @@ def save_report(reference_report, report_name, columns): { "doctype": "Report", "report_name": report_name, - "json": f'{{"columns":{columns}}}', + "json": f'{{"columns":{columns},"filters":{filters}}}', "ref_doctype": report_doc.ref_doctype, "is_standard": "No", "report_type": "Custom Report", diff --git a/frappe/patches.txt b/frappe/patches.txt index da83094961..fa9d884386 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -168,7 +168,6 @@ execute:frappe.db.set_default('desktop:home_page', 'space') execute:frappe.delete_doc_if_exists('Page', 'workspace') execute:frappe.delete_doc_if_exists('Page', 'dashboard', force=1) frappe.core.doctype.page.patches.drop_unused_pages -execute:frappe.get_doc('Role', 'Guest').save() # remove desk access frappe.patches.v13_0.remove_chat frappe.patches.v13_0.rename_desk_page_to_workspace # 02.02.2021 frappe.patches.v13_0.delete_package_publish_tool @@ -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] +execute:frappe.get_doc('Role', 'Guest').save() # remove desk access 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 @@ -223,3 +223,4 @@ frappe.patches.v14_0.disable_email_accounts_with_oauth execute:frappe.delete_doc("Page", "translation-tool", force=1) frappe.patches.v15_0.remove_prepared_report_settings_from_system_settings frappe.patches.v14_0.remove_manage_subscriptions_from_navbar +frappe.patches.v15_0.remove_background_jobs_from_dropdown \ No newline at end of file diff --git a/frappe/patches/v15_0/remove_background_jobs_from_dropdown.py b/frappe/patches/v15_0/remove_background_jobs_from_dropdown.py new file mode 100644 index 0000000000..b070e0805c --- /dev/null +++ b/frappe/patches/v15_0/remove_background_jobs_from_dropdown.py @@ -0,0 +1,9 @@ +import frappe + + +def execute(): + item = frappe.db.exists("Navbar Item", {"item_label": "Background Jobs"}) + if not item: + return + + frappe.delete_doc("Navbar Item", item) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 27ca3dba0c..d4fdd47a11 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -1296,7 +1296,7 @@ export default class GridRow { .find(".grid-delete-row") .toggle(!(this.grid.df && this.grid.df.cannot_delete_rows)); - frappe.dom.freeze("", "dark"); + frappe.dom.freeze("", "dark grid-form"); if (cur_frm) cur_frm.cur_grid = this; this.wrapper.addClass("grid-row-open"); if ( diff --git a/frappe/public/js/frappe/model/perm.js b/frappe/public/js/frappe/model/perm.js index 8ed7d6b028..948a31b1fc 100644 --- a/frappe/public/js/frappe/model/perm.js +++ b/frappe/public/js/frappe/model/perm.js @@ -34,9 +34,7 @@ $.extend(frappe.perm, { doctype_perm: {}, - has_perm: (doctype, permlevel, ptype, doc) => { - if (!permlevel) permlevel = 0; - + has_perm: (doctype, permlevel = 0, ptype = "read", doc) => { const perms = frappe.perm.get_perm(doctype, doc); return !!perms?.[permlevel]?.[ptype]; }, diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js index 419a22d764..bef0c19b4e 100644 --- a/frappe/public/js/frappe/ui/toolbar/toolbar.js +++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js @@ -13,6 +13,9 @@ frappe.ui.toolbar.Toolbar = class { }) ); $(".dropdown-toggle").dropdown(); + $("#toolbar-user a[href]").click(function () { + $(this).closest(".dropdown-menu").prev().dropdown("toggle"); + }); this.setup_awesomebar(); this.setup_notifications(); @@ -133,6 +136,12 @@ frappe.ui.toolbar.Toolbar = class { frappe.utils.generate_tracking_url, __("Generate Tracking URL") ); + + if (frappe.perm.has_perm("RQ Job")) { + frappe.search.utils.make_function_searchable(function () { + frappe.set_route("List", "RQ Job"); + }, __("Background Jobs")); + } } } diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index d877f47f21..ee255032bb 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -542,7 +542,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { if (this.prepared_report) { this.reset_report_view(); } else if (!this._no_refresh) { - this.refresh(); + this.refresh(true); } } }; @@ -598,10 +598,25 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.page.clear_fields(); } - refresh() { + refresh(have_filters_changed) { this.toggle_message(true); this.toggle_report(false); let filters = this.get_filter_values(true); + + // for custom reports, + // are_default_filters is true if the filters haven't been modified and for all filters, + // the filter value is the default value or there's no default value for the filter and the current value is empty. + // are_default_filters is false otherwise. + + let are_default_filters = this.filters + .map((filter) => { + return ( + !have_filters_changed && + (filter.default === filter.value || (!filter.default && !filter.value)) + ); + }) + .every((res) => res === true); + this.show_loading_screen(); // only one refresh at a time @@ -624,6 +639,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { filters: filters, is_tree: this.report_settings.tree, parent_field: this.report_settings.parent_field, + are_default_filters: are_default_filters, }, callback: resolve, always: () => this.page.btn_secondary.prop("disabled", false), @@ -636,6 +652,11 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { this.execution_time = data.execution_time || 0.1; + if (data.custom_filters) { + this.set_filters(data.custom_filters); + this.previous_filters = data.custom_filters; + } + if (data.prepared_report) { this.prepared_report = true; this.prepared_report_document = data.doc; @@ -1715,6 +1736,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { reference_report: this.report_name, report_name: values.report_name, columns: this.get_visible_columns(), + filters: this.get_filter_values(), }, callback: function (r) { this.show_save = false; diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index f25c7ef66c..99ecfff932 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -321,7 +321,7 @@ overflow: hidden; height: 0; opacity: 0; - z-index: 1051; + z-index: 1021; border-radius: var(--border-radius-md); @include base-grid(); @@ -362,6 +362,10 @@ } } +#freeze.grid-form { + z-index: 1020; +} + .recorder-form-in-grid { z-index: 0; @include base-grid(); diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index 1bf5e37b3e..765e51cab9 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -433,7 +433,7 @@ kbd { // freeze backdrop text #freeze { - z-index: 1050; + z-index: 1055; bottom: 0; opacity: 0; background-color: var(--bg-color); diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index 7af2bfda8e..01f6e4f7cc 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -236,6 +236,7 @@ class TestWebsite(FrappeTestCase): def test_printview_page(self): frappe.db.value_cache[("DocType", "Language", "name")] = (("Language",),) + frappe.set_user("Administrator") content = get_response_content("/Language/ru") self.assertIn('