Merge branch 'frappe:develop' into fix-report-excel-translation
This commit is contained in:
commit
9a5aac232b
24 changed files with 1057 additions and 802 deletions
|
|
@ -7,5 +7,6 @@ hooks.py,frappe.gettext.extractors.navbar.extract
|
|||
**.py,frappe.gettext.extractors.python.extract
|
||||
**.js,frappe.gettext.extractors.javascript.extract
|
||||
**.html,frappe.gettext.extractors.html_template.extract
|
||||
**.vue,frappe.gettext.extractors.html_template.extract
|
||||
**/custom/*.json,frappe.gettext.extractors.customization.extract
|
||||
**/fixtures/custom_field.json,frappe.gettext.extractors.custom_field.extract
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"fetch_if_empty",
|
||||
"visibility_section",
|
||||
"hidden",
|
||||
"show_on_timeline",
|
||||
"bold",
|
||||
"allow_in_quick_entry",
|
||||
"translatable",
|
||||
|
|
@ -578,13 +579,20 @@
|
|||
"fieldname": "not_nullable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Not Nullable"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.hidden",
|
||||
"fieldname": "show_on_timeline",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show on Timeline"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-04-12 16:27:34.546314",
|
||||
"modified": "2024-07-30 13:15:32.037892",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
@ -594,4 +602,4 @@
|
|||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
|
@ -112,6 +112,7 @@ class DocField(Document):
|
|||
search_index: DF.Check
|
||||
set_only_once: DF.Check
|
||||
show_dashboard: DF.Check
|
||||
show_on_timeline: DF.Check
|
||||
sort_options: DF.Check
|
||||
translatable: DF.Check
|
||||
unique: DF.Check
|
||||
|
|
|
|||
|
|
@ -237,7 +237,8 @@
|
|||
"options": "Has Role",
|
||||
"permlevel": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"show_on_timeline": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
|
|
@ -428,7 +429,8 @@
|
|||
"hidden": 1,
|
||||
"label": "Block Modules",
|
||||
"options": "Block Module",
|
||||
"permlevel": 1
|
||||
"permlevel": 1,
|
||||
"show_on_timeline": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "home_settings",
|
||||
|
|
@ -796,7 +798,7 @@
|
|||
"link_fieldname": "user"
|
||||
}
|
||||
],
|
||||
"modified": "2024-04-12 23:25:04.628007",
|
||||
"modified": "2024-07-15 18:40:18.842915",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User",
|
||||
|
|
|
|||
|
|
@ -362,7 +362,11 @@ class User(Document):
|
|||
user=self.name, pwd=new_password, logout_all_sessions=self.logout_all_sessions
|
||||
)
|
||||
|
||||
if not self.flags.no_welcome_mail and cint(self.send_welcome_email):
|
||||
if (
|
||||
not self.flags.no_welcome_mail
|
||||
and cint(self.send_welcome_email)
|
||||
and not self.flags.email_sent
|
||||
):
|
||||
self.send_welcome_mail_to_user()
|
||||
self.flags.email_sent = 1
|
||||
if frappe.session.user != "Guest":
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Type",
|
||||
"options": "Mention\nEnergy Point\nAssignment\nShare\nAlert"
|
||||
"options": "\nMention\nEnergy Point\nAssignment\nShare\nAlert"
|
||||
},
|
||||
{
|
||||
"fieldname": "email_content",
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
"hide_toolbar": 1,
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-23 16:03:31.715461",
|
||||
"modified": "2024-08-03 09:38:10.497711",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Notification Log",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class NotificationLog(Document):
|
|||
link: DF.Data | None
|
||||
read: DF.Check
|
||||
subject: DF.Text | None
|
||||
type: DF.Literal["Mention", "Energy Point", "Assignment", "Share", "Alert"]
|
||||
type: DF.Literal["", "Mention", "Energy Point", "Assignment", "Share", "Alert"]
|
||||
# end: auto-generated types
|
||||
|
||||
def after_insert(self):
|
||||
|
|
|
|||
|
|
@ -59,9 +59,10 @@ def is_email_notifications_enabled_for_type(user, notification_type):
|
|||
return False
|
||||
|
||||
fieldname = "enable_email_" + frappe.scrub(notification_type)
|
||||
enabled = frappe.db.get_value("Notification Settings", user, fieldname)
|
||||
enabled = frappe.db.get_value("Notification Settings", user, fieldname, ignore=True)
|
||||
if enabled is None:
|
||||
return True
|
||||
|
||||
return enabled
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -171,11 +171,18 @@ class EmailQueue(Document):
|
|||
method(self, self.sender, recipient.recipient, message)
|
||||
elif not frappe.flags.in_test or frappe.flags.testing_email:
|
||||
if ctx.email_account_doc.service == "Frappe Mail":
|
||||
ctx.frappe_mail_client.send_raw(
|
||||
sender=self.sender,
|
||||
recipients=recipient.recipient,
|
||||
message=message.decode("utf-8"),
|
||||
)
|
||||
if self.reference_doctype == "Newsletter":
|
||||
ctx.frappe_mail_client.send_newsletter(
|
||||
sender=self.sender,
|
||||
recipients=recipient.recipient,
|
||||
message=message.decode("utf-8"),
|
||||
)
|
||||
else:
|
||||
ctx.frappe_mail_client.send_raw(
|
||||
sender=self.sender,
|
||||
recipients=recipient.recipient,
|
||||
message=message.decode("utf-8"),
|
||||
)
|
||||
else:
|
||||
ctx.smtp_server.session.sendmail(
|
||||
from_addr=self.sender,
|
||||
|
|
@ -780,7 +787,8 @@ class QueueBuilder:
|
|||
with suppress(Exception):
|
||||
q.send(smtp_server_instance=smtp_server_instance, frappe_mail_client=frappe_mail_client)
|
||||
|
||||
smtp_server_instance.quit()
|
||||
if smtp_server_instance:
|
||||
smtp_server_instance.quit()
|
||||
|
||||
def as_dict(self, include_recipients=True):
|
||||
email_account = self.get_outgoing_email_account()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import pytz
|
||||
|
|
@ -9,9 +9,6 @@ from frappe import _
|
|||
from frappe.frappeclient import FrappeClient, FrappeOAuth2Client
|
||||
from frappe.utils import convert_utc_to_system_timezone, get_datetime, get_datetime_str, get_system_timezone
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from requests import Response
|
||||
|
||||
|
||||
class FrappeMail:
|
||||
"""Class to interact with the Frappe Mail API."""
|
||||
|
|
@ -29,6 +26,9 @@ class FrappeMail:
|
|||
self.api_key = api_key
|
||||
self.api_secret = api_secret
|
||||
self.access_token = access_token
|
||||
self.client = self.get_client(
|
||||
self.site, self.mailbox, self.api_key, self.api_secret, self.access_token
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_client(
|
||||
|
|
@ -65,64 +65,51 @@ class FrappeMail:
|
|||
headers: dict[str, str] | None = None,
|
||||
timeout: int | tuple[int, int] = (60, 120),
|
||||
raise_exception: bool = True,
|
||||
) -> "Response":
|
||||
"""Makes a HTTP request to the Frappe Mail API."""
|
||||
) -> Any | None:
|
||||
"""Makes a request to the Frappe Mail API."""
|
||||
|
||||
url = urljoin(self.site, endpoint)
|
||||
client = self.get_client(self.site, self.mailbox, self.api_key, self.api_secret, self.access_token)
|
||||
url = urljoin(self.client.url, endpoint)
|
||||
|
||||
headers = headers or {}
|
||||
headers.update(client.headers)
|
||||
headers.update(self.client.headers)
|
||||
|
||||
response = client.session.request(
|
||||
response = self.client.session.request(
|
||||
method=method, url=url, params=params, data=data, json=json, headers=headers, timeout=timeout
|
||||
)
|
||||
|
||||
if not response.ok and raise_exception:
|
||||
error_msg = response.text
|
||||
if response.status_code == 401:
|
||||
if self.access_token:
|
||||
error_msg = _("Authentication Error: Reauthorize OAuth for Email Account {0}.").format(
|
||||
frappe.bold(self.mailbox)
|
||||
)
|
||||
else:
|
||||
error_msg = _("Authentication Error: Invalid API Key or Secret")
|
||||
|
||||
frappe.throw(title=_("Frappe Mail"), msg=error_msg)
|
||||
|
||||
return response
|
||||
return self.client.post_process(response)
|
||||
|
||||
def validate(self, for_outbound: bool = False, for_inbound: bool = False) -> None:
|
||||
"""Validates the mailbox for inbound and outbound emails."""
|
||||
|
||||
endpoint = "auth/validate"
|
||||
endpoint = "/api/method/mail.api.auth.validate"
|
||||
data = {"mailbox": self.mailbox, "for_outbound": for_outbound, "for_inbound": for_inbound}
|
||||
response = self.request("POST", endpoint=endpoint, data=data, raise_exception=False)
|
||||
self.request("POST", endpoint=endpoint, data=data)
|
||||
|
||||
if not response.ok:
|
||||
if error_msg := response.json().get("exception"):
|
||||
if error_msg == "frappe.exceptions.AuthenticationError":
|
||||
error_msg += ": Invalid API Key or Secret"
|
||||
|
||||
frappe.throw(title="Frappe Mail", msg=error_msg)
|
||||
|
||||
def send_raw(self, sender: str, recipients: str, message: str) -> None:
|
||||
def send_raw(self, sender: str, recipients: str | list, message: str) -> None:
|
||||
"""Sends an email using the Frappe Mail API."""
|
||||
|
||||
endpoint = "outbound/send-raw"
|
||||
json_data = {"from": sender, "to": recipients, "raw_message": message}
|
||||
self.request("POST", endpoint=endpoint, json=json_data)
|
||||
endpoint = "/api/method/mail.api.outbound.send_raw"
|
||||
data = {"from_": sender, "to": recipients, "raw_message": message}
|
||||
self.request("POST", endpoint=endpoint, data=data)
|
||||
|
||||
def send_newsletter(self, sender: str, recipients: str | list, message: str) -> None:
|
||||
"""Sends an newsletter using the Frappe Mail API."""
|
||||
|
||||
endpoint = "/api/method/mail.api.outbound.send_newsletter"
|
||||
data = {"from_": sender, "to": recipients, "raw_message": message}
|
||||
self.request("POST", endpoint=endpoint, json=data)
|
||||
|
||||
def pull_raw(self, limit: int = 50, last_synced_at: str | None = None) -> dict[str, list[str] | str]:
|
||||
"""Pulls emails from the mailbox using the Frappe Mail API."""
|
||||
|
||||
endpoint = "inbound/pull-raw"
|
||||
endpoint = "/api/method/mail.api.inbound.pull_raw"
|
||||
if last_synced_at:
|
||||
last_synced_at = convert_to_utc(last_synced_at)
|
||||
|
||||
data = {"mailbox": self.mailbox, "limit": limit, "last_synced_at": last_synced_at}
|
||||
headers = {"X-Site": frappe.utils.get_url()}
|
||||
response = self.request("GET", endpoint=endpoint, data=data, headers=headers).json()["message"]
|
||||
response = self.request("GET", endpoint=endpoint, data=data, headers=headers)
|
||||
last_synced_at = convert_utc_to_system_timezone(get_datetime(response["last_synced_at"]))
|
||||
|
||||
return {"latest_messages": response["mails"], "last_synced_at": last_synced_at}
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ def extract(fileobj, *args, **kwargs):
|
|||
yield from (
|
||||
(
|
||||
None,
|
||||
"pgettext",
|
||||
(link.get("link_to") if link.get("link_type") == "DocType" else None, link.get("label")),
|
||||
"_",
|
||||
link.get("label"),
|
||||
[f"Label of a {link.get('type')} in the {workspace_name} Workspace"],
|
||||
)
|
||||
for link in data.get("links", [])
|
||||
|
|
@ -38,8 +38,8 @@ def extract(fileobj, *args, **kwargs):
|
|||
yield from (
|
||||
(
|
||||
None,
|
||||
"pgettext",
|
||||
(link.get("link_to") if link.get("link_type") == "DocType" else None, link.get("description")),
|
||||
"_",
|
||||
link.get("description"),
|
||||
[f"Description of a {link.get('type')} in the {workspace_name} Workspace"],
|
||||
)
|
||||
for link in data.get("links", [])
|
||||
|
|
@ -47,8 +47,8 @@ def extract(fileobj, *args, **kwargs):
|
|||
yield from (
|
||||
(
|
||||
None,
|
||||
"pgettext",
|
||||
(shortcut.get("link_to") if shortcut.get("type") == "DocType" else None, shortcut.get("label")),
|
||||
"_",
|
||||
shortcut.get("label"),
|
||||
[f"Label of a shortcut in the {workspace_name} Workspace"],
|
||||
)
|
||||
for shortcut in data.get("shortcuts", [])
|
||||
|
|
@ -56,8 +56,8 @@ def extract(fileobj, *args, **kwargs):
|
|||
yield from (
|
||||
(
|
||||
None,
|
||||
"pgettext",
|
||||
(shortcut.get("link_to") if shortcut.get("type") == "DocType" else None, shortcut.get("format")),
|
||||
"_",
|
||||
shortcut.get("format"),
|
||||
[f"Count format of shortcut in the {workspace_name} Workspace"],
|
||||
)
|
||||
for shortcut in data.get("shortcuts", [])
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ def update_po(target_app: str | None = None, locale: str | None = None):
|
|||
pot_catalog = get_catalog(app)
|
||||
for locale in locales:
|
||||
po_catalog = get_catalog(app, locale)
|
||||
po_catalog.update(pot_catalog)
|
||||
po_catalog.update(pot_catalog, no_fuzzy_matching=True)
|
||||
po_path = write_catalog(app, po_catalog, locale)
|
||||
print(f"PO file modified at {po_path}")
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -638,13 +638,18 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
|
|||
if (value) {
|
||||
field_value = response[source_field];
|
||||
}
|
||||
frappe.model.set_value(
|
||||
this.df.parent,
|
||||
this.docname,
|
||||
target_field,
|
||||
field_value,
|
||||
this.df.fieldtype
|
||||
);
|
||||
|
||||
if (this.layout?.set_value) {
|
||||
this.layout.set_value(target_field, field_value);
|
||||
} else if (this.frm) {
|
||||
frappe.model.set_value(
|
||||
this.df.parent,
|
||||
this.docname,
|
||||
target_field,
|
||||
field_value,
|
||||
this.df.fieldtype
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -669,8 +674,73 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
|
|||
}
|
||||
}
|
||||
|
||||
fetch_map_for_quick_entry() {
|
||||
let me = this;
|
||||
let fetch_map = {};
|
||||
function add_fetch(link_field, source_field, target_field, target_doctype) {
|
||||
if (!target_doctype) target_doctype = "*";
|
||||
|
||||
if (!me.layout.fetch_dict) {
|
||||
me.layout.fetch_dict = {};
|
||||
}
|
||||
|
||||
// Target field kept as key because source field could be non-unique
|
||||
me.layout.fetch_dict.setDefault(target_doctype, {}).setDefault(link_field, {})[
|
||||
target_field
|
||||
] = source_field;
|
||||
}
|
||||
|
||||
function setup_add_fetch(df) {
|
||||
let is_read_only_field =
|
||||
[
|
||||
"Data",
|
||||
"Read Only",
|
||||
"Text",
|
||||
"Small Text",
|
||||
"Currency",
|
||||
"Check",
|
||||
"Text Editor",
|
||||
"Attach Image",
|
||||
"Code",
|
||||
"Link",
|
||||
"Float",
|
||||
"Int",
|
||||
"Date",
|
||||
"Select",
|
||||
"Duration",
|
||||
"Time",
|
||||
].includes(df.fieldtype) ||
|
||||
df.read_only == 1 ||
|
||||
df.is_virtual == 1;
|
||||
|
||||
if (is_read_only_field && df.fetch_from && df.fetch_from.indexOf(".") != -1) {
|
||||
var parts = df.fetch_from.split(".");
|
||||
add_fetch(parts[0], parts[1], df.fieldname, df.parent);
|
||||
}
|
||||
}
|
||||
|
||||
$.each(this.layout.fields, (i, field) => setup_add_fetch(field));
|
||||
|
||||
for (const key of ["*", this.df.parent]) {
|
||||
if (!this.layout.fetch_dict) {
|
||||
this.layout.fetch_dict = {};
|
||||
}
|
||||
if (this.layout.fetch_dict[key] && this.layout.fetch_dict[key][this.df.fieldname]) {
|
||||
Object.assign(fetch_map, this.layout.fetch_dict[key][this.df.fieldname]);
|
||||
}
|
||||
}
|
||||
|
||||
return fetch_map;
|
||||
}
|
||||
|
||||
get fetch_map() {
|
||||
const fetch_map = {};
|
||||
|
||||
// Create fetch_map from quick entry fields
|
||||
if (!this.frm && this.layout && this.layout.fields) {
|
||||
return this.fetch_map_for_quick_entry();
|
||||
}
|
||||
|
||||
if (!this.frm) return fetch_map;
|
||||
|
||||
for (const key of ["*", this.df.parent]) {
|
||||
|
|
|
|||
|
|
@ -83,13 +83,17 @@ function get_version_timeline_content(version_doc, frm) {
|
|||
}
|
||||
} else {
|
||||
const df = frappe.meta.get_docfield(frm.doctype, p[0], frm.docname);
|
||||
if (df && !df.hidden) {
|
||||
if (df && (!df.hidden || df.show_on_timeline)) {
|
||||
const field_display_status = frappe.perm.get_field_display_status(
|
||||
df,
|
||||
null,
|
||||
frm.perm
|
||||
);
|
||||
if (field_display_status === "Read" || field_display_status === "Write") {
|
||||
if (
|
||||
field_display_status === "Read" ||
|
||||
field_display_status === "Write" ||
|
||||
(df.hidden && df.show_on_timeline)
|
||||
) {
|
||||
parts.push(
|
||||
__("{0} from {1} to {2}", [
|
||||
__(df.label, null, df.parent),
|
||||
|
|
@ -142,14 +146,18 @@ function get_version_timeline_content(version_doc, frm) {
|
|||
frm.docname
|
||||
);
|
||||
|
||||
if (df && !df.hidden) {
|
||||
if (df && (!df.hidden || df.show_on_timeline)) {
|
||||
var field_display_status = frappe.perm.get_field_display_status(
|
||||
df,
|
||||
null,
|
||||
frm.perm
|
||||
);
|
||||
|
||||
if (field_display_status === "Read" || field_display_status === "Write") {
|
||||
if (
|
||||
field_display_status === "Read" ||
|
||||
field_display_status === "Write" ||
|
||||
(df.hidden && df.show_on_timeline)
|
||||
) {
|
||||
parts.push(
|
||||
__("{0} from {1} to {2} in row #{3}", [
|
||||
frappe.meta.get_label(frm.fields_dict[row[0]].grid.doctype, p[0]),
|
||||
|
|
@ -197,14 +205,19 @@ function get_version_timeline_content(version_doc, frm) {
|
|||
if (data[key] && data[key].length) {
|
||||
let parts = (data[key] || []).map(function (p) {
|
||||
var df = frappe.meta.get_docfield(frm.doctype, p[0], frm.docname);
|
||||
if (df && !df.hidden) {
|
||||
|
||||
if (df && (!df.hidden || df.show_on_timeline)) {
|
||||
var field_display_status = frappe.perm.get_field_display_status(
|
||||
df,
|
||||
null,
|
||||
frm.perm
|
||||
);
|
||||
|
||||
if (field_display_status === "Read" || field_display_status === "Write") {
|
||||
if (
|
||||
field_display_status === "Read" ||
|
||||
field_display_status === "Write" ||
|
||||
(df.hidden && df.show_on_timeline)
|
||||
) {
|
||||
return __(frappe.meta.get_label(frm.doctype, p[0]));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -515,7 +515,7 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
|
||||
// feedback
|
||||
frappe.msgprint({
|
||||
message: __("{} Complete", [action.label]),
|
||||
message: __("{} Complete", [__(action.label)]),
|
||||
alert: true,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -840,10 +840,12 @@ export default class GridRow {
|
|||
delete this.grid.filter[df.fieldname];
|
||||
}
|
||||
|
||||
this.grid.grid_sortable.option(
|
||||
"disabled",
|
||||
Object.keys(this.grid.filter).length !== 0
|
||||
);
|
||||
if (this.grid.grid_sortable) {
|
||||
this.grid.grid_sortable.option(
|
||||
"disabled",
|
||||
Object.keys(this.grid.filter).length !== 0
|
||||
);
|
||||
}
|
||||
|
||||
this.grid.prevent_build = true;
|
||||
this.grid.grid_pagination.go_to_page(1);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ window.refresh_field = function (n, docname, table_field) {
|
|||
};
|
||||
|
||||
window.set_field_options = function (n, txt) {
|
||||
cur_frm.set_df_property(n, "options", txt);
|
||||
cur_frm?.set_df_property(n, "options", txt);
|
||||
};
|
||||
|
||||
window.toggle_field = function (n, hidden) {
|
||||
|
|
|
|||
|
|
@ -918,18 +918,18 @@ Object.assign(frappe.utils, {
|
|||
let route = route_str.split("/");
|
||||
|
||||
if (route[2] === "Report" || route[0] === "query-report") {
|
||||
return __("{0} Report", [route[3] || route[1]]);
|
||||
return __("{0} Report", [__(route[3]) || __(route[1])]);
|
||||
}
|
||||
if (route[0] === "List") {
|
||||
return __("{0} List", [route[1]]);
|
||||
return __("{0} List", [__(route[1])]);
|
||||
}
|
||||
if (route[0] === "modules") {
|
||||
return __("{0} Modules", [route[1]]);
|
||||
return __("{0} Modules", [__(route[1])]);
|
||||
}
|
||||
if (route[0] === "dashboard") {
|
||||
return __("{0} Dashboard", [route[1]]);
|
||||
return __("{0} Dashboard", [__(route[1])]);
|
||||
}
|
||||
return __(frappe.utils.to_title_case(route[0], true));
|
||||
return __(frappe.utils.to_title_case(__(route[0]), true));
|
||||
},
|
||||
report_column_total: function (values, column, type) {
|
||||
if (column.column.disable_total) {
|
||||
|
|
@ -1213,9 +1213,7 @@ Object.assign(frappe.utils, {
|
|||
},
|
||||
|
||||
flag(country_code) {
|
||||
return `<img
|
||||
src="https://flagcdn.com/${country_code}.svg"
|
||||
width="20" height="15">`;
|
||||
return `<img loading="lazy" src="https://flagcdn.com/${country_code}.svg" width="20" height="15">`;
|
||||
},
|
||||
|
||||
make_chart(wrapper, custom_options = {}) {
|
||||
|
|
|
|||
|
|
@ -462,7 +462,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
|
||||
setup_progress_bar() {
|
||||
let seconds_elapsed = 0;
|
||||
const execution_time = this.report_settings.execution_time || 0;
|
||||
const execution_time = this.report_settings?.execution_time || 0;
|
||||
|
||||
if (execution_time < 5) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -104,8 +104,16 @@ export default class Header extends Block {
|
|||
this._data = this.normalizeData(data);
|
||||
|
||||
if (data.text !== undefined) {
|
||||
let text = this._data.text || "";
|
||||
let text = __(this._data.text) || "";
|
||||
const contains_html_tag = /<[a-z][\s\S]*>/i.test(text);
|
||||
|
||||
// apply translation to header text
|
||||
let div = document.createElement("div");
|
||||
div.innerHTML = text;
|
||||
let only_text = div.innerText;
|
||||
only_text = frappe.utils.escape_html(only_text);
|
||||
text = text.replace(only_text, __(only_text));
|
||||
|
||||
this._element.innerHTML = contains_html_tag
|
||||
? text
|
||||
: `<span class="h${this._settings.default_size}">${text}</span>`;
|
||||
|
|
|
|||
|
|
@ -99,15 +99,13 @@ frappe.views.Workspace = class Workspace {
|
|||
<svg class="es-icon es-line icon-xs" style="" aria-hidden="true">
|
||||
<use class="" href="#es-line-add"></use>
|
||||
</svg>
|
||||
<span class="hidden-xs" data-label="Edit">New</span>
|
||||
<span class="hidden-xs" data-label="New">${__("New")}</span>
|
||||
</button>
|
||||
<button class="btn btn-default btn-sm mr-2 btn-edit-workspace" data-label="Edit">
|
||||
<svg class="es-icon es-line icon-xs" style="" aria-hidden="true">
|
||||
<use class="" href="#es-line-edit"></use>
|
||||
</svg>
|
||||
<span class="hidden-xs" data-label="Edit">
|
||||
<span><span class="alt-underline">E</span>dit</span>
|
||||
</span>
|
||||
<span class="hidden-xs" data-label="Edit">${__("Edit")}</span>
|
||||
</button>
|
||||
</div>
|
||||
`).appendTo(this.body);
|
||||
|
|
|
|||
|
|
@ -45,8 +45,16 @@
|
|||
.layout-side-section.print-preview-sidebar {
|
||||
padding-right: var(--padding-md);
|
||||
|
||||
.checkbox label {
|
||||
align-items: unset;
|
||||
}
|
||||
|
||||
.input-area {
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.label-area {
|
||||
white-space: nowrap;
|
||||
white-space: unset;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import xlrd
|
|||
from openpyxl import load_workbook
|
||||
from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
from openpyxl.workbook.child import INVALID_TITLE_REGEX
|
||||
|
||||
import frappe
|
||||
from frappe.utils.html_utils import unescape_html
|
||||
|
|
@ -21,7 +22,8 @@ def make_xlsx(data, sheet_name, wb=None, column_widths=None):
|
|||
if wb is None:
|
||||
wb = openpyxl.Workbook(write_only=True)
|
||||
|
||||
ws = wb.create_sheet(sheet_name, 0)
|
||||
sheet_name_sanitized = INVALID_TITLE_REGEX.sub(" ", sheet_name)
|
||||
ws = wb.create_sheet(sheet_name_sanitized, 0)
|
||||
|
||||
for i, column_width in enumerate(column_widths):
|
||||
if column_width:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue