Merge branch 'develop' into freeze-modal-z-index
This commit is contained in:
commit
602120eb8c
21 changed files with 124 additions and 60 deletions
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from frappe.core.api.file import (
|
|||
move_file,
|
||||
unzip_file,
|
||||
)
|
||||
from frappe.core.doctype.file.utils import get_extension
|
||||
from frappe.exceptions import ValidationError
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import get_files_path
|
||||
|
|
@ -461,7 +462,7 @@ class TestFile(FrappeTestCase):
|
|||
).insert(ignore_permissions=True)
|
||||
|
||||
test_file.make_thumbnail()
|
||||
self.assertTrue(test_file.thumbnail_url.endswith("_small.jpeg"))
|
||||
self.assertTrue(test_file.thumbnail_url.endswith("_small.jpg"))
|
||||
|
||||
# test local image
|
||||
test_file.db_set("thumbnail_url", None)
|
||||
|
|
@ -739,3 +740,10 @@ class TestFileOptimization(FrappeTestCase):
|
|||
size_after_rollback = os.stat(image_path).st_size
|
||||
|
||||
self.assertEqual(size_before_optimization, size_after_rollback)
|
||||
|
||||
def test_image_header_guessing(self):
|
||||
file_path = frappe.get_app_path("frappe", "tests/data/sample_image_for_optimization.jpg")
|
||||
with open(file_path, "rb") as f:
|
||||
file_content = f.read()
|
||||
|
||||
self.assertEqual(get_extension("", None, file_content), "jpg")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import hashlib
|
||||
import imghdr
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
|
|
@ -7,6 +6,7 @@ from io import BytesIO
|
|||
from typing import TYPE_CHECKING, Optional
|
||||
from urllib.parse import unquote
|
||||
|
||||
import filetype
|
||||
import requests
|
||||
import requests.exceptions
|
||||
from PIL import Image
|
||||
|
|
@ -76,9 +76,11 @@ def get_extension(
|
|||
|
||||
mimetype = mimetypes.guess_type(filename + "." + extn)[0]
|
||||
|
||||
if mimetype is None or not mimetype.startswith("image/") and content:
|
||||
# detect file extension by reading image header properties
|
||||
extn = imghdr.what(filename + "." + (extn or ""), h=content)
|
||||
if mimetype is None and extn is None and content:
|
||||
# detect file extension by using filetype matchers
|
||||
_type_info = filetype.match(content)
|
||||
if _type_info:
|
||||
extn = _type_info.extension
|
||||
|
||||
return extn
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -678,7 +678,7 @@ def get_filters_cond(
|
|||
for f in filters:
|
||||
if isinstance(f[1], str) and f[1][0] == "!":
|
||||
flt.append([doctype, f[0], "!=", f[1][1:]])
|
||||
elif isinstance(f[1], (list, tuple)) and f[1][0] in (
|
||||
elif isinstance(f[1], (list, tuple)) and f[1][0].lower() in (
|
||||
">",
|
||||
"<",
|
||||
">=",
|
||||
|
|
|
|||
|
|
@ -186,11 +186,13 @@ scheduler_events = {
|
|||
"frappe.oauth.delete_oauth2_data",
|
||||
"frappe.website.doctype.web_page.web_page.check_publish_status",
|
||||
"frappe.twofactor.delete_all_barcodes_for_users",
|
||||
]
|
||||
],
|
||||
"0/10 * * * *": [
|
||||
"frappe.email.doctype.email_account.email_account.pull",
|
||||
],
|
||||
},
|
||||
"all": [
|
||||
"frappe.email.queue.flush",
|
||||
"frappe.email.doctype.email_account.email_account.pull",
|
||||
"frappe.email.doctype.email_account.email_account.notify_unreplied",
|
||||
"frappe.utils.global_search.sync_global_search",
|
||||
"frappe.monitor.flush",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import datetime
|
|||
import hashlib
|
||||
import re
|
||||
from http import cookies
|
||||
from urllib.parse import unquote, urlparse, urljoin
|
||||
from urllib.parse import unquote, urljoin, urlparse
|
||||
|
||||
import jwt
|
||||
import pytz
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
// MIT License. See license.txt
|
||||
import DataTable from "frappe-datatable";
|
||||
|
||||
// Expose DataTable globally to allow customizations.
|
||||
window.DataTable = DataTable;
|
||||
|
||||
frappe.provide("frappe.widget.utils");
|
||||
frappe.provide("frappe.views");
|
||||
frappe.provide("frappe.query_reports");
|
||||
|
|
@ -539,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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -595,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
|
||||
|
|
@ -621,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),
|
||||
|
|
@ -633,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;
|
||||
|
|
@ -933,7 +957,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
if (this.report_settings.get_datatable_options) {
|
||||
datatable_options = this.report_settings.get_datatable_options(datatable_options);
|
||||
}
|
||||
this.datatable = new DataTable(this.$report[0], datatable_options);
|
||||
this.datatable = new window.DataTable(this.$report[0], datatable_options);
|
||||
}
|
||||
|
||||
if (typeof this.report_settings.initial_depth == "number") {
|
||||
|
|
@ -1712,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;
|
||||
|
|
|
|||
|
|
@ -243,6 +243,8 @@ $input-height: 28px !default;
|
|||
--highlight-color: var(--gray-50);
|
||||
--yellow-highlight-color: var(--yellow-50);
|
||||
|
||||
--btn-group-border-color: var(--gray-300);
|
||||
|
||||
--field-placeholder-color: var(--gray-50);
|
||||
|
||||
--highlight-shadow: 1px 1px 10px var(--blue-50), 0px 0px 4px var(--blue-600);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@
|
|||
--highlight-color: var(--gray-700);
|
||||
--yellow-highlight-color: var(--yellow-700);
|
||||
|
||||
--btn-group-border-color: var(--gray-800);
|
||||
|
||||
--field-placeholder-color: var(--gray-700);
|
||||
|
||||
--highlight-shadow: 1px 1px 10px var(--blue-900), 0px 0px 4px var(--blue-500);
|
||||
|
|
|
|||
|
|
@ -234,6 +234,21 @@ h2 {
|
|||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
.btn {
|
||||
box-shadow: none;
|
||||
outline: 1px solid var(--btn-group-border-color);
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid var(--dark-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-xs {
|
||||
@extend .btn-sm;
|
||||
line-height: 1.2;
|
||||
|
|
|
|||
|
|
@ -189,29 +189,12 @@ $level-margin-right: 8px;
|
|||
.list-paging-area, .footnote-area {
|
||||
border-top: 1px solid var(--border-color);
|
||||
|
||||
.btn-group {
|
||||
box-shadow: var(--drop-shadow);
|
||||
border-radius: var(--border-radius-md);
|
||||
|
||||
&> .btn:nth-child(2) {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.btn-paging {
|
||||
box-shadow: none;
|
||||
margin-left: 0px !important;
|
||||
border: 1px solid var(--dark-border-color);
|
||||
|
||||
&.btn-info {
|
||||
background-color: var(--gray-600);
|
||||
border-color: var(--gray-600);
|
||||
color: var(--white);
|
||||
font-weight: var(--text-bold);
|
||||
}
|
||||
}
|
||||
.btn-group .btn-paging.btn-info {
|
||||
background-color: var(--gray-600);
|
||||
border-color: var(--gray-600);
|
||||
color: var(--white);
|
||||
font-weight: var(--text-bold);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.frappe-card {
|
||||
|
|
|
|||
|
|
@ -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('<div class="print-format">', content)
|
||||
self.assertIn("<div>Language</div>", content)
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ class Workflow(Document):
|
|||
|
||||
@frappe.whitelist()
|
||||
def get_workflow_state_count(doctype, workflow_state_field, states):
|
||||
frappe.has_permission(doctype=doctype, ptype='read', throw=True)
|
||||
frappe.has_permission(doctype=doctype, ptype="read", throw=True)
|
||||
states = frappe.parse_json(states)
|
||||
result = frappe.get_all(
|
||||
doctype,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "field:workflow_action_name",
|
||||
"allow_rename": 1,
|
||||
"creation": "2012-12-28 10:49:56",
|
||||
"description": "Workflow Action Master",
|
||||
"doctype": "DocType",
|
||||
|
|
@ -21,7 +22,7 @@
|
|||
"icon": "fa fa-flag",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2022-08-03 12:20:52.449982",
|
||||
"modified": "2023-04-14 12:20:52.449982",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Workflow",
|
||||
"name": "Workflow Action Master",
|
||||
|
|
@ -43,4 +44,4 @@
|
|||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ dependencies = [
|
|||
"Babel~=2.12.1",
|
||||
"Click~=8.1.3",
|
||||
"filelock~=3.8.0",
|
||||
"filetype~=1.2.0",
|
||||
"GitPython~=3.1.30",
|
||||
"Jinja2~=3.1.2",
|
||||
"Pillow~=9.3.0",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue