Merge branch 'develop' into bugfix/postgres_schema_support

This commit is contained in:
gruener 2024-07-16 22:47:01 +02:00 committed by GitHub
commit efc9bdd4f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 1538 additions and 797 deletions

View file

@ -442,6 +442,12 @@ def get_site_config(sites_path: str | None = None, site_path: str | None = None)
# Set the user as database name if not set in config
config["db_user"] = os.environ.get("FRAPPE_DB_USER") or config.get("db_user") or config.get("db_name")
# vice versa for dbname if not defined
config["db_name"] = os.environ.get("FRAPPE_DB_NAME") or config.get("db_name") or config["db_user"]
# read password
config["db_password"] = os.environ.get("FRAPPE_DB_PASSWORD") or config.get("db_password")
# Allow externally extending the config with hooks
if extra_config := config.get("extra_config"):
if isinstance(extra_config, str):

View file

@ -138,7 +138,7 @@ def get_preview_from_template(data_import, import_file=None, google_sheets_url=N
@frappe.whitelist()
def form_start_import(data_import):
def form_start_import(data_import: str):
return frappe.get_doc("Data Import", data_import).start_import()

View file

@ -62,7 +62,7 @@ frappe.ui.form.on("DocType", {
}
}
const customize_form_link = "<a href='/app/customize-form'>Customize Form</a>";
const customize_form_link = `<a href="/app/customize-form">${__("Customize Form")}</a>`;
if (!frappe.boot.developer_mode && !frm.doc.custom) {
// make the document read-only
frm.set_read_only();

View file

@ -17,7 +17,7 @@ from frappe.core.api.file import (
unzip_file,
)
from frappe.core.doctype.file.exceptions import FileTypeNotAllowed
from frappe.core.doctype.file.utils import get_extension
from frappe.core.doctype.file.utils import get_corrupted_image_msg, get_extension
from frappe.desk.form.utils import add_comment
from frappe.exceptions import ValidationError
from frappe.tests.utils import FrappeTestCase, change_settings
@ -768,6 +768,25 @@ class TestFileUtils(FrappeTestCase):
)
self.assertRegex(communication.content, r"<img src=\"(.*)/files/pix\.png(.*)\">")
def test_broken_image(self):
"""Ensure that broken inline images don't cause errors."""
is_private = not frappe.get_meta("Communication").make_attachments_public
communication = frappe.get_doc(
doctype="Communication",
communication_type="Communication",
communication_medium="Email",
content='<div class="ql-editor read-mode"><img src="data:image/png;filename=pix.png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CY="></div>',
recipients="to <to@test.com>",
cc=None,
bcc=None,
sender="sender@test.com",
).insert(ignore_permissions=True)
self.assertFalse(
frappe.db.exists("File", {"attached_to_name": communication.name, "is_private": is_private})
)
self.assertIn(f'<img src="#broken-image" alt="{get_corrupted_image_msg()}">', communication.content)
def test_create_new_folder(self):
folder = create_new_folder("test_folder", "Home")
self.assertTrue(folder.is_folder)

View file

@ -2,6 +2,7 @@ import hashlib
import mimetypes
import os
import re
from binascii import Error as BinasciiError
from io import BytesIO
from typing import TYPE_CHECKING, Optional
from urllib.parse import unquote
@ -234,7 +235,12 @@ def extract_images_from_html(doc: "Document", content: str, is_private: bool = F
content = content.encode("utf-8")
if b"," in content:
content = content.split(b",")[1]
content = safe_b64decode(content)
try:
content = safe_b64decode(content)
except BinasciiError:
frappe.flags.has_dataurl = True
return f'<img src="#broken-image" alt="{get_corrupted_image_msg()}"'
if "filename=" in headers:
filename = headers.split("filename=")[-1]
@ -273,6 +279,10 @@ def extract_images_from_html(doc: "Document", content: str, is_private: bool = F
return content
def get_corrupted_image_msg():
return _("Image: Corrupted Data Stream")
def get_random_filename(content_type: str | None = None) -> str:
extn = None
if content_type:

View file

@ -72,20 +72,23 @@ if doc.allocated_to:
# retreive payment session state
ps = doc.flags.payment_session
if ps.changed: # could be an idempotent run
if ps.flags.status_changed_to in ps.flowstates.success:
if ps.is_success:
if ps.changed: # could be an idempotent run
doc.set_as_paid()
# custom process return values
doc.flags.payment_result = {
"message": "Thank you for your payment",
"action": {"href": "https://shop.example.com", "label": "Return to shop"},
}
if ps.flags.status_changed_to in ps.flowstates.pre_authorized:
# do something else
if ps.flags.status_changed_to in ps.flowstates.processing:
# do something else
if ps.flags.status_changed_to in ps.flowstates.declined:
# do something else
# custom process return values
doc.flags.payment_session.result = {
"message": "Thank you for your payment",
"action": {"href": "https://shop.example.com", "label": "Return to shop"},
}
if ps.is_pre_authorized:
if ps.changed: # could be an idempotent run
...
if ps.is_processing:
if ps.changed: # could be an idempotent run
...
if ps.is_declined:
if ps.changed: # could be an idempotent run
...
</code>
</pre>
<p>The <i>On Payment Failed</i> (<code>on_payment_failed</code>) event only transports the error message which the controller implementation had extracted from the transaction.</p>

View file

@ -17,7 +17,6 @@ from frappe.desk.doctype.notification_settings.notification_settings import (
toggle_notifications,
)
from frappe.desk.notifications import clear_notifications
from frappe.model.delete_doc import check_if_doc_is_linked
from frappe.model.document import Document
from frappe.query_builder import DocType
from frappe.rate_limiter import rate_limit
@ -536,11 +535,20 @@ class User(Document):
# Delete EPS data
frappe.db.delete("Energy Point Log", {"user": self.name})
# Ask user to disable instead if document is still linked
try:
check_if_doc_is_linked(self)
except frappe.LinkExistsError:
frappe.throw(_("You can disable the user instead of deleting it."), frappe.LinkExistsError)
# Remove user link from Workflow Action
frappe.db.set_value("Workflow Action", {"user": self.name}, "user", None)
# Delete user's List Filters
frappe.db.delete("List Filter", {"for_user": self.name})
# Remove user from Note's Seen By table
seen_notes = frappe.get_all("Note", filters=[["Note Seen By", "user", "=", self.name]], pluck="name")
for note_id in seen_notes:
note = frappe.get_doc("Note", note_id)
for row in note.seen_by:
if row.user == self.name:
note.remove(row)
note.save(ignore_permissions=True)
def before_rename(self, old_name, new_name, merge=False):
# if merging, delete the old user notification settings

View file

@ -106,7 +106,7 @@ frappe.PermissionEngine = class PermissionEngine {
reset_std_permissions(data) {
let doctype = this.get_doctype();
let d = frappe.confirm(__("Reset Permissions for {0}?", [doctype]), () => {
let d = frappe.confirm(__("Reset Permissions for {0}?", [__(doctype)]), () => {
return frappe
.call({
module: "frappe.core",
@ -122,7 +122,7 @@ frappe.PermissionEngine = class PermissionEngine {
// show standard permissions
let $d = $(d.wrapper)
.find(".frappe-confirm-message")
.append("<hr><h5>Standard Permissions:</h5><br>");
.append(`<hr><h5>${__("Standard Permissions")}:</h5><br>`);
let $wrapper = $("<p></p>").appendTo($d);
data.message.forEach((d) => {
let rights = this.rights
@ -134,7 +134,7 @@ frappe.PermissionEngine = class PermissionEngine {
d.rights = rights.join(", ");
$wrapper.append(`<div class="row">\
<div class="col-xs-5"><b>${d.role}</b>, Level ${d.permlevel || 0}</div>\
<div class="col-xs-5"><b>${__(d.role)}</b>, ${__("Level")} ${d.permlevel || 0}</div>\
<div class="col-xs-7">${d.rights}</div>\
</div><br>`);
});

View file

@ -397,7 +397,7 @@ frappe.ui.form.on("Dashboard Chart", {
}
}
},
primary_action_label: "Set",
primary_action_label: __("Set"),
});
frappe.dashboards.filters_dialog = dialog;
@ -484,7 +484,7 @@ frappe.ui.form.on("Dashboard Chart", {
}
frm.trigger("set_dynamic_filters_in_table");
},
primary_action_label: "Set",
primary_action_label: __("Set"),
});
dialog.show();

View file

@ -93,6 +93,7 @@ def enqueue_create_notification(users: list[str] | str, doc: dict):
doc=doc,
users=users,
now=frappe.flags.in_test,
enqueue_after_commit=not frappe.flags.in_test,
)

View file

@ -33,7 +33,7 @@ frappe.ui.form.on("Number Card", {
},
create_add_to_dashboard_button: function (frm) {
frm.add_custom_button("Add Card to Dashboard", () => {
frm.add_custom_button(__("Add Card to Dashboard"), () => {
const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog(
frm.doc.name,
"Number Card",
@ -292,7 +292,7 @@ frappe.ui.form.on("Number Card", {
frm.trigger("render_filters_table");
}
},
primary_action_label: "Set",
primary_action_label: __("Set"),
});
if (is_document_type) {
@ -384,7 +384,7 @@ frappe.ui.form.on("Number Card", {
}
frm.trigger("set_dynamic_filters_in_table");
},
primary_action_label: "Set",
primary_action_label: __("Set"),
});
dialog.show();

View file

@ -186,19 +186,40 @@ class SystemHealthReport(Document):
# Exclude "maybe" curently executing job
upper_threshold = add_to_date(None, minutes=-30, as_datetime=True)
self.scheduler_status = get_scheduler_status().get("status")
failing_jobs = frappe.db.sql(
"""
select scheduled_job_type,
avg(CASE WHEN status != 'Complete' THEN 1 ELSE 0 END) * 100 as failure_rate
from `tabScheduled Job Log`
where
creation > %(lower_threshold)s
and modified > %(lower_threshold)s
and creation < %(upper_threshold)s
group by scheduled_job_type
having failure_rate > 0
order by failure_rate desc
limit 5""",
mariadb_query = """
SELECT scheduled_job_type,
AVG(CASE WHEN status != 'Complete' THEN 1 ELSE 0 END) * 100 AS failure_rate
FROM `tabScheduled Job Log`
WHERE
creation > %(lower_threshold)s
AND modified > %(lower_threshold)s
AND creation < %(upper_threshold)s
GROUP BY scheduled_job_type
HAVING failure_rate > 0
ORDER BY failure_rate DESC
LIMIT 5
"""
postgres_query = """
SELECT scheduled_job_type,
AVG(CASE WHEN status != 'Complete' THEN 1 ELSE 0 END) * 100 AS "failure_rate"
FROM "tabScheduled Job Log"
WHERE
creation > %(lower_threshold)s
AND modified > %(lower_threshold)s
AND creation < %(upper_threshold)s
GROUP BY scheduled_job_type
HAVING AVG(CASE WHEN status != 'Complete' THEN 1 ELSE 0 END) * 100 > 0
ORDER BY "failure_rate" DESC
LIMIT 5
"""
failing_jobs = frappe.db.multisql(
{
"mariadb": mariadb_query,
"postgres": postgres_query,
},
{"lower_threshold": lower_threshold, "upper_threshold": upper_threshold},
as_dict=True,
)

View file

@ -421,99 +421,85 @@ def get_linked_docs(doctype: str, name: str, linkinfo: dict | None = None) -> di
if not linkinfo:
return results
for dt, link in linkinfo.items():
filters = []
link["doctype"] = dt
try:
link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt)
except Exception as e:
if isinstance(e, frappe.DoesNotExistError):
frappe.clear_last_message()
is_target_doctype_table = frappe.get_meta(doctype).istable
for linked_doctype, link_context in linkinfo.items():
linked_doctype_meta = frappe.get_meta(linked_doctype)
if linked_doctype_meta.issingle:
continue
linkmeta = link_meta_bundle[0]
if not linkmeta.get("issingle"):
fields = [
d.fieldname
for d in linkmeta.get(
"fields",
{
"in_list_view": 1,
"fieldtype": ["not in", ("Image", "HTML", "Button", *frappe.model.table_fields)],
},
)
] + ["name", "modified", "docstatus"]
filters = []
ret = None
parent_info = None
if link.get("add_fields"):
fields += link["add_fields"]
fields = [
d.fieldname
for d in linked_doctype_meta.get(
"fields",
{
"in_list_view": 1,
"fieldtype": ["not in", ("Image", "HTML", "Button", *frappe.model.table_fields)],
},
)
] + ["name", "modified", "docstatus"]
fields = [f"`tab{dt}`.`{sf.strip()}`" for sf in fields if sf and "`tab" not in sf]
if add_fields := link_context.get("add_fields"):
fields += add_fields
try:
if link.get("filters"):
ret = frappe.get_all(
doctype=dt, fields=fields, filters=link.get("filters"), order_by=None
)
fields = [f"`tab{linked_doctype}`.`{sf.strip()}`" for sf in fields if sf and "`tab" not in sf]
elif link.get("get_parent"):
ret = None
# check for child table
if not frappe.get_meta(doctype).istable:
continue
me = frappe.db.get_value(
doctype, name, ["parenttype", "parent"], as_dict=True, order_by=None
)
if me and me.parenttype == dt:
ret = frappe.get_all(
doctype=dt, fields=fields, filters=[[dt, "name", "=", me.parent]], order_by=None
)
elif link.get("child_doctype"):
or_filters = [
[link.get("child_doctype"), link_fieldnames, "=", name]
for link_fieldnames in link.get("fieldname")
]
# dynamic link
if link.get("doctype_fieldname"):
filters.append(
[link.get("child_doctype"), link.get("doctype_fieldname"), "=", doctype]
)
ret = frappe.get_all(
doctype=dt,
fields=fields,
filters=filters,
or_filters=or_filters,
distinct=True,
order_by=None,
)
else:
link_fieldnames = link.get("fieldname")
if link_fieldnames:
if isinstance(link_fieldnames, str):
link_fieldnames = [link_fieldnames]
or_filters = [[dt, fieldname, "=", name] for fieldname in link_fieldnames]
# dynamic link
if link.get("doctype_fieldname"):
filters.append([dt, link.get("doctype_fieldname"), "=", doctype])
ret = frappe.get_all(
doctype=dt, fields=fields, filters=filters, or_filters=or_filters, order_by=None
)
else:
ret = None
except frappe.PermissionError:
frappe.clear_last_message()
if filters_ctx := link_context.get("filters"):
ret = frappe.get_list(doctype=linked_doctype, fields=fields, filters=filters_ctx, order_by=None)
elif link_context.get("get_parent"):
# check for child table
if not is_target_doctype_table:
continue
if ret:
results[dt] = ret
parent_info = parent_info or frappe.db.get_value(
doctype, name, ["parenttype", "parent"], as_dict=True, order_by=None
)
if parent_info and parent_info.parenttype == linked_doctype:
ret = frappe.get_list(
doctype=linked_doctype,
fields=fields,
filters=[[linked_doctype, "name", "=", parent_info.parent]],
order_by=None,
)
elif child_doctype := link_context.get("child_doctype"):
or_filters = [
[child_doctype, link_fieldnames, "=", name] for link_fieldnames in link_context["fieldname"]
]
# dynamic link_context
if doctype_fieldname := link_context.get("doctype_fieldname"):
filters.append([child_doctype, doctype_fieldname, "=", doctype])
ret = frappe.get_list(
doctype=linked_doctype,
fields=fields,
filters=filters,
or_filters=or_filters,
distinct=True,
order_by=None,
)
elif link_fieldnames := link_context.get("fieldname"):
if isinstance(link_fieldnames, str):
link_fieldnames = [link_fieldnames]
or_filters = [[linked_doctype, fieldname, "=", name] for fieldname in link_fieldnames]
# dynamic link_context
if doctype_fieldname := link_context.get("doctype_fieldname"):
filters.append([linked_doctype, doctype_fieldname, "=", doctype])
ret = frappe.get_list(
doctype=linked_doctype, fields=fields, filters=filters, or_filters=or_filters, order_by=None
)
if ret:
results[linked_doctype] = ret
return results

View file

@ -38,7 +38,7 @@ def getdoc(doctype, name, user=None):
if not doc.has_permission("read"):
frappe.flags.error_message = _("Insufficient Permission for {0}").format(
frappe.bold(doctype + " " + name)
frappe.bold(_(doctype) + " " + name)
)
raise frappe.PermissionError(("read", doctype, name))

View file

@ -77,7 +77,7 @@ class UserProfile {
render_heatmap() {
this.heatmap = new frappe.Chart(".performance-heatmap", {
type: "heatmap",
countLabel: "Energy Points",
countLabel: __("Energy Points"),
data: {},
discreteDomains: 1,
radius: 3,
@ -111,7 +111,7 @@ class UserProfile {
value_based_on: "points",
chart_type: "Sum",
document_type: "Energy Point Log",
name: "Energy Points",
name: __("Energy Points"),
width: "half",
based_on: "creation",
};

View file

@ -1,4 +1,24 @@
frappe.email_defaults = {
"Frappe Mail": {
domain: null,
password: null,
awaiting_password: 0,
ascii_encode_password: 0,
login_id_is_different: 0,
login_id: null,
use_imap: 0,
use_ssl: 0,
validate_ssl_certificate: 0,
use_starttls: 0,
email_server: null,
incoming_port: 0,
always_use_account_email_id_as_sender: 1,
use_tls: 0,
use_ssl_for_outgoing: 0,
smtp_server: null,
smtp_port: null,
no_smtp_authentication: 0,
},
GMail: {
email_server: "imap.gmail.com",
incoming_port: 993,
@ -144,11 +164,11 @@ frappe.ui.form.on("Email Account", {
frm.refresh_field("imap_folder");
}
set_default_max_attachment_size(frm);
frm.events.show_oauth_authorization_message(frm);
},
refresh: function (frm) {
frm.events.enable_incoming(frm);
frm.events.show_oauth_authorization_message(frm);
if (frappe.route_flags.delete_user_from_locals && frappe.route_flags.linked_user) {
delete frappe.route_flags.delete_user_from_locals;
@ -169,6 +189,15 @@ frappe.ui.form.on("Email Account", {
oauth_access(frm);
},
validate_frappe_mail_settings: function (frm) {
if (frm.doc.service == "Frappe Mail") {
frappe.call({
doc: frm.doc,
method: "validate_frappe_mail_settings",
});
}
},
show_oauth_authorization_message(frm) {
if (frm.doc.auth_method === "OAuth" && frm.doc.connected_app) {
frappe.call({

View file

@ -2,7 +2,7 @@
"actions": [],
"allow_rename": 1,
"autoname": "field:email_account_name",
"creation": "2014-09-11 12:04:34.163728",
"creation": "2024-06-11 16:39:01.323289",
"doctype": "DocType",
"document_type": "Setup",
"engine": "InnoDB",
@ -13,22 +13,30 @@
"enable_incoming",
"enable_outgoing",
"column_break_3",
"domain",
"service",
"domain",
"frappe_mail_site",
"authentication_column",
"auth_method",
"authorize_api_access",
"validate_frappe_mail_settings",
"password",
"awaiting_password",
"ascii_encode_password",
"column_break_10",
"api_key",
"api_secret",
"connected_app",
"connected_user",
"login_id_is_different",
"login_id",
"incoming_popimap_tab",
"mailbox_settings",
"section_break_uc6h",
"default_incoming",
"column_break_uynb",
"attachment_limit",
"last_synced_at",
"mailbox_settings",
"use_imap",
"use_ssl",
"validate_ssl_certificate",
@ -36,7 +44,6 @@
"email_server",
"incoming_port",
"column_break_18",
"attachment_limit",
"email_sync_option",
"initial_sync_count",
"section_break_25",
@ -50,19 +57,19 @@
"notify_if_unreplied",
"unreplied_for_mins",
"send_notification_to",
"outgoing_smtp_tab",
"outgoing_tab",
"default_outgoing",
"column_break_h5pd",
"always_use_account_email_id_as_sender",
"always_use_account_name_as_sender_name",
"send_unsubscribe_message",
"track_email_status",
"outgoing_mail_settings",
"column_break_bidn",
"use_tls",
"use_ssl_for_outgoing",
"smtp_server",
"smtp_port",
"column_break_38",
"default_outgoing",
"always_use_account_email_id_as_sender",
"always_use_account_name_as_sender_name",
"send_unsubscribe_message",
"track_email_status",
"no_smtp_authentication",
"signature_section",
"add_signature",
@ -92,6 +99,7 @@
},
{
"default": "0",
"depends_on": "eval: doc.service != \"Frappe Mail\"",
"fieldname": "login_id_is_different",
"fieldtype": "Check",
"hide_days": 1,
@ -107,7 +115,7 @@
"label": "Alternative Email ID"
},
{
"depends_on": "eval: doc.auth_method === \"Basic\"",
"depends_on": "eval: doc.auth_method === \"Basic\" && doc.service != \"Frappe Mail\"",
"fieldname": "password",
"fieldtype": "Password",
"hide_days": 1,
@ -116,7 +124,7 @@
},
{
"default": "0",
"depends_on": "eval: doc.auth_method === \"Basic\"",
"depends_on": "eval: doc.auth_method === \"Basic\" && doc.service != \"Frappe Mail\"",
"fieldname": "awaiting_password",
"fieldtype": "Check",
"hide_days": 1,
@ -125,7 +133,7 @@
},
{
"default": "0",
"depends_on": "eval: doc.auth_method === \"Basic\"",
"depends_on": "eval: doc.auth_method === \"Basic\" && doc.service != \"Frappe Mail\"",
"fieldname": "ascii_encode_password",
"fieldtype": "Check",
"hide_days": 1,
@ -159,9 +167,10 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Service",
"options": "\nGMail\nSendgrid\nSparkPost\nYahoo Mail\nOutlook.com\nYandex.Mail"
"options": "\nFrappe Mail\nGMail\nSendgrid\nSparkPost\nYahoo Mail\nOutlook.com\nYandex.Mail"
},
{
"depends_on": "eval: doc.service != \"Frappe Mail\"",
"fieldname": "mailbox_settings",
"fieldtype": "Section Break",
"hide_days": 1,
@ -289,6 +298,7 @@
"mandatory_depends_on": "notify_if_unreplied"
},
{
"depends_on": "eval: doc.service != \"Frappe Mail\"",
"fieldname": "outgoing_mail_settings",
"fieldtype": "Section Break",
"hide_days": 1,
@ -621,23 +631,65 @@
"depends_on": "enable_incoming",
"fieldname": "incoming_popimap_tab",
"fieldtype": "Tab Break",
"label": "Incoming (POP/IMAP)"
"label": "Incoming"
},
{
"default": "https://frappemail.com",
"depends_on": "eval: doc.service == \"Frappe Mail\"",
"fieldname": "frappe_mail_site",
"fieldtype": "Data",
"label": "Frappe Mail Site",
"mandatory_depends_on": "eval: doc.service == \"Frappe Mail\""
},
{
"depends_on": "enable_outgoing",
"fieldname": "outgoing_smtp_tab",
"fieldname": "outgoing_tab",
"fieldtype": "Tab Break",
"label": "Outgoing (SMTP)"
"label": "Outgoing"
},
{
"fieldname": "column_break_bidn",
"fieldname": "column_break_h5pd",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_uc6h",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_uynb",
"fieldtype": "Column Break"
},
{
"depends_on": "eval: doc.service == \"Frappe Mail\"",
"fieldname": "last_synced_at",
"fieldtype": "Datetime",
"label": "Last Synced At"
},
{
"depends_on": "eval: (doc.service == \"Frappe Mail\" && doc.auth_method != \"Basic\" && !doc.__islocal && !doc.__unsaved)",
"fieldname": "validate_frappe_mail_settings",
"fieldtype": "Button",
"label": "Validate Frappe Mail Settings"
},
{
"depends_on": "eval: doc.service == \"Frappe Mail\" && doc.auth_method == \"Basic\"",
"fieldname": "api_key",
"fieldtype": "Data",
"label": "API Key",
"mandatory_depends_on": "eval: doc.service == \"Frappe Mail\" && doc.auth_method == \"Basic\""
},
{
"depends_on": "eval: doc.service == \"Frappe Mail\" && doc.auth_method == \"Basic\"",
"fieldname": "api_secret",
"fieldtype": "Password",
"label": "API Secret",
"mandatory_depends_on": "eval: doc.service == \"Frappe Mail\" && doc.auth_method == \"Basic\""
}
],
"icon": "fa fa-inbox",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-04-17 14:46:38.836631",
"modified": "2024-06-28 08:45:43.565934",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Account",

View file

@ -12,6 +12,7 @@ import frappe
from frappe import _, are_emails_muted, safe_encode
from frappe.desk.form import assign_to
from frappe.email.doctype.email_domain.email_domain import EMAIL_DOMAIN_FIELDS
from frappe.email.frappemail import FrappeMail
from frappe.email.receive import EmailServer, InboundMail, SentEmailInInboxError
from frappe.email.smtp import SMTPServer
from frappe.email.utils import get_port
@ -61,6 +62,8 @@ class EmailAccount(Document):
add_signature: DF.Check
always_use_account_email_id_as_sender: DF.Check
always_use_account_name_as_sender_name: DF.Check
api_key: DF.Data | None
api_secret: DF.Password | None
append_emails_to_sent_folder: DF.Check
append_to: DF.Link | None
ascii_encode_password: DF.Check
@ -84,9 +87,11 @@ class EmailAccount(Document):
enable_incoming: DF.Check
enable_outgoing: DF.Check
footer: DF.TextEditor | None
frappe_mail_site: DF.Data | None
imap_folder: DF.Table[IMAPFolder]
incoming_port: DF.Data | None
initial_sync_count: DF.Literal["100", "250", "500"]
last_synced_at: DF.Datetime | None
login_id: DF.Data | None
login_id_is_different: DF.Check
no_failed: DF.Int
@ -96,13 +101,7 @@ class EmailAccount(Document):
send_notification_to: DF.SmallText | None
send_unsubscribe_message: DF.Check
service: DF.Literal[
"",
"GMail",
"Sendgrid",
"SparkPost",
"Yahoo Mail",
"Outlook.com",
"Yandex.Mail",
"", "Frappe Mail", "GMail", "Sendgrid", "SparkPost", "Yahoo Mail", "Outlook.com", "Yandex.Mail"
]
signature: DF.TextEditor | None
smtp_port: DF.Data | None
@ -142,6 +141,13 @@ class EmailAccount(Document):
else:
self.login_id = None
if self.service == "Frappe Mail":
self.use_imap = 0
self.always_use_account_email_id_as_sender = 1
if self.auth_method == "Basic" or self.get_oauth_token():
self.validate_frappe_mail_settings()
# validate the imap settings
if self.enable_incoming and self.use_imap and len(self.imap_folder) <= 0:
frappe.throw(_("You need to set one IMAP folder for {0}").format(frappe.bold(self.email_id)))
@ -158,7 +164,11 @@ class EmailAccount(Document):
self.awaiting_password = 0
self.password = None
if not frappe.local.flags.in_install and not self.awaiting_password:
if (
not frappe.local.flags.in_install
and not self.awaiting_password
and not self.service == "Frappe Mail"
):
if validate_oauth or self.password or self.smtp_server in ("127.0.0.1", "localhost"):
if self.enable_incoming:
self.get_incoming_server()
@ -184,6 +194,12 @@ class EmailAccount(Document):
if folder.append_to not in valid_doctypes:
frappe.throw(_("Append To can be one of {0}").format(comma_or(valid_doctypes)))
@frappe.whitelist()
def validate_frappe_mail_settings(self):
if self.service == "Frappe Mail":
frappe_mail_client = self.get_frappe_mail_client()
frappe_mail_client.validate(for_inbound=self.enable_incoming, for_outbound=self.enable_outgoing)
def validate_smtp_conn(self):
if not self.smtp_server:
frappe.throw(_("SMTP Server is required"))
@ -476,9 +492,11 @@ class EmailAccount(Document):
return account_details
def sendmail_config(self):
def get_access_token(self) -> str | None:
oauth_token = self.get_oauth_token()
return oauth_token.get_password("access_token") if oauth_token else None
def sendmail_config(self):
return {
"email_account": self.name,
"server": self.smtp_server,
@ -488,7 +506,7 @@ class EmailAccount(Document):
"use_ssl": cint(self.use_ssl_for_outgoing),
"use_tls": cint(self.use_tls),
"use_oauth": self.auth_method == "OAuth",
"access_token": oauth_token.get_password("access_token") if oauth_token else None,
"access_token": self.get_access_token(),
}
def get_smtp_server(self):
@ -504,6 +522,26 @@ class EmailAccount(Document):
config = self.sendmail_config()
return SMTPServer(**config)
def get_frappe_mail_client(self):
return self._frappe_mail_client
@functools.cached_property
def _frappe_mail_client(self):
if self.auth_method == "OAuth":
if access_token := self.get_access_token():
return FrappeMail(self.frappe_mail_site, self.email_id, access_token=access_token)
frappe.throw(
_("Please Authorize OAuth for Email Account {0}").format(
frappe.bold(self.email_account_name)
),
title=_("Frappe Mail OAuth Error"),
)
else:
return FrappeMail(
self.frappe_mail_site, self.email_id, self.api_key, self.get_password("api_secret")
)
def remove_unpicklable_values(self, state):
super().remove_unpicklable_values(state)
state.pop("_smtp_server_instance", None)
@ -594,25 +632,33 @@ class EmailAccount(Document):
if not self.enable_incoming:
return []
email_sync_rule = self.build_email_sync_rule()
try:
email_server = self.get_incoming_server(in_receive=True, email_sync_rule=email_sync_rule)
if self.use_imap:
# process all given imap folder
for folder in self.imap_folder:
if email_server.select_imap_folder(folder.folder_name):
email_server.settings["uid_validity"] = folder.uidvalidity
messages = email_server.get_messages(folder=f'"{folder.folder_name}"') or {}
process_mail(messages, folder.append_to)
else:
# process the pop3 account
messages = email_server.get_messages() or {}
if self.service == "Frappe Mail":
frappe_mail_client = self.get_frappe_mail_client()
messages = frappe_mail_client.pull_raw(last_synced_at=self.last_synced_at)
process_mail(messages)
# close connection to mailserver
email_server.logout()
self.db_set("last_synced_at", messages["last_synced_at"], update_modified=False)
else:
email_sync_rule = self.build_email_sync_rule()
email_server = self.get_incoming_server(in_receive=True, email_sync_rule=email_sync_rule)
if self.use_imap:
# process all given imap folder
for folder in self.imap_folder:
if email_server.select_imap_folder(folder.folder_name):
email_server.settings["uid_validity"] = folder.uidvalidity
messages = email_server.get_messages(folder=f'"{folder.folder_name}"') or {}
process_mail(messages, folder.append_to)
else:
# process the pop3 account
messages = email_server.get_messages() or {}
process_mail(messages)
# close connection to mailserver
email_server.logout()
except Exception:
self.log_error(title=_("Error while connecting to email account {0}").format(self.name))
return []
return mails
def handle_bad_emails(self, uid, raw, reason):

View file

@ -14,6 +14,7 @@ from frappe.core.utils import html2text
from frappe.database.database import savepoint
from frappe.email.doctype.email_account.email_account import EmailAccount
from frappe.email.email_body import add_attachment, get_email, get_formatted_html
from frappe.email.frappemail import FrappeMail
from frappe.email.queue import get_unsubcribed_url, get_unsubscribe_message
from frappe.email.smtp import SMTPServer
from frappe.model.document import Document
@ -153,13 +154,13 @@ class EmailQueue(Document):
return True
def send(self, smtp_server_instance: SMTPServer = None):
def send(self, smtp_server_instance: SMTPServer = None, frappe_mail_client: FrappeMail = None):
"""Send emails to recipients."""
if not self.can_send_now():
return
with SendMailContext(self, smtp_server_instance) as ctx:
ctx.fetch_smtp_server()
with SendMailContext(self, smtp_server_instance, frappe_mail_client) as ctx:
ctx.fetch_outgoing_server()
message = None
for recipient in self.recipients:
if recipient.is_mail_sent():
@ -168,8 +169,14 @@ class EmailQueue(Document):
message = ctx.build_message(recipient.recipient)
if method := get_hook_method("override_email_send"):
method(self, self.sender, recipient.recipient, message)
else:
if not frappe.flags.in_test or frappe.flags.testing_email:
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"),
)
else:
ctx.smtp_server.session.sendmail(
from_addr=self.sender,
to_addrs=recipient.recipient,
@ -231,17 +238,23 @@ class SendMailContext:
self,
queue_doc: Document,
smtp_server_instance: SMTPServer = None,
frappe_mail_client: FrappeMail = None,
):
self.queue_doc: EmailQueue = queue_doc
self.smtp_server: SMTPServer = smtp_server_instance
self.frappe_mail_client: FrappeMail = frappe_mail_client
self.sent_to_atleast_one_recipient = any(
rec.recipient for rec in self.queue_doc.recipients if rec.is_mail_sent()
)
self.email_account_doc = None
def fetch_smtp_server(self):
def fetch_outgoing_server(self):
self.email_account_doc = self.queue_doc.get_email_account(raise_error=True)
if not self.smtp_server:
if self.email_account_doc.service == "Frappe Mail":
if not self.frappe_mail_client:
self.frappe_mail_client = self.email_account_doc.get_frappe_mail_client()
elif not self.smtp_server:
self.smtp_server = self.email_account_doc.get_smtp_server()
def __enter__(self):
@ -751,16 +764,21 @@ class QueueBuilder:
def send_emails(self, queue_data, final_recipients):
# This is used to bulk send emails from same sender to multiple recipients separately
# This re-uses smtp server instance to minimize the cost of new session creation
frappe_mail_client = None
smtp_server_instance = None
for r in final_recipients:
recipients = list(set([r, *self.final_cc(), *self.bcc]))
q = EmailQueue.new({**queue_data, **{"recipients": recipients}}, ignore_permissions=True)
if not smtp_server_instance:
if not frappe_mail_client and not smtp_server_instance:
email_account = q.get_email_account(raise_error=True)
smtp_server_instance = email_account.get_smtp_server()
if email_account.service == "Frappe Mail":
frappe_mail_client = email_account.get_frappe_mail_client()
else:
smtp_server_instance = email_account.get_smtp_server()
with suppress(Exception):
q.send(smtp_server_instance=smtp_server_instance)
q.send(smtp_server_instance=smtp_server_instance, frappe_mail_client=frappe_mail_client)
smtp_server_instance.quit()

View file

@ -1,6 +1,8 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
const DATE_BASED_EVENTS = ["Days Before", "Days After"];
frappe.notification = {
setup_fieldname_select: function (frm) {
// get the doctype to update fields
@ -129,6 +131,8 @@ Last comment: {{ comments[-1].comment }} by {{ comments[-1].by }}
frappe.ui.form.on("Notification", {
onload: function (frm) {
frm.set_query("document_type", function () {
if (DATE_BASED_EVENTS.includes(frm.doc.event)) return;
return {
filters: {
istable: 0,
@ -166,23 +170,23 @@ frappe.ui.form.on("Notification", {
frappe.set_route("Form", "Customize Form");
},
event: function (frm) {
if (["Days Before", "Days After"].includes(frm.doc.event)) {
frm.add_custom_button(__("Get Alerts for Today"), function () {
frappe.call({
method: "frappe.email.doctype.notification.notification.get_documents_for_today",
args: {
notification: frm.doc.name,
},
callback: function (r) {
if (r.message && r.message.length > 0) {
frappe.msgprint(r.message.toString());
} else {
frappe.msgprint(__("No alerts for today"));
}
},
});
if (!DATE_BASED_EVENTS.includes(frm.doc.event) || frm.is_new()) return;
frm.add_custom_button(__("Get Alerts for Today"), function () {
frappe.call({
method: "frappe.email.doctype.notification.notification.get_documents_for_today",
args: {
notification: frm.doc.name,
},
callback: function (r) {
if (r.message && r.message.length > 0) {
frappe.msgprint(r.message.toString());
} else {
frappe.msgprint(__("No alerts for today"));
}
},
});
}
});
},
channel: function (frm) {
frm.toggle_reqd("recipients", frm.doc.channel == "Email");

View file

@ -8,16 +8,16 @@
"engine": "InnoDB",
"field_order": [
"enabled",
"is_standard",
"module",
"column_break_2",
"channel",
"slack_webhook_url",
"filters",
"subject",
"document_type",
"is_standard",
"module",
"col_break_1",
"event",
"document_type",
"col_break_1",
"method",
"date_changed",
"days_in_advance",
@ -119,7 +119,6 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval: doc.document_type",
"fieldname": "event",
"fieldtype": "Select",
"in_list_view": 1,
@ -292,7 +291,7 @@
"icon": "fa fa-envelope",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-06-17 04:03:22.591781",
"modified": "2024-07-04 05:53:40.595130",
"modified_by": "Administrator",
"module": "Email",
"name": "Notification",
@ -315,4 +314,4 @@
"states": [],
"title_field": "subject",
"track_changes": 1
}
}

View file

@ -18,6 +18,8 @@ from frappe.utils.jinja import validate_template
from frappe.utils.safe_exec import get_safe_globals
FORMATS = {"HTML": ".html", "Markdown": ".md", "Plain Text": ".txt"}
FORBIDDEN_DOCUMENT_TYPES = frozenset(("Email Queue",))
DATE_BASED_EVENTS = frozenset(("Days Before", "Days After"))
class Notification(Document):
@ -27,9 +29,7 @@ class Notification(Document):
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.email.doctype.notification_recipient.notification_recipient import (
NotificationRecipient,
)
from frappe.email.doctype.notification_recipient.notification_recipient import NotificationRecipient
from frappe.types import DF
attach_print: DF.Check
@ -90,7 +90,7 @@ class Notification(Document):
if self.event == "Value Change" and not self.value_changed:
frappe.throw(_("Please specify which value field must be checked"))
self.validate_forbidden_types()
self.validate_forbidden_document_types()
self.validate_condition()
self.validate_standard()
frappe.cache.hdel("notifications", self.document_type)
@ -130,12 +130,16 @@ def get_context(context):
except Exception:
frappe.throw(_("The Condition '{0}' is invalid").format(self.condition))
def validate_forbidden_types(self):
forbidden_document_types = ("Email Queue",)
if self.document_type in forbidden_document_types or frappe.get_meta(self.document_type).istable:
# currently notifications don't work on child tables as events are not fired for each record of child table
frappe.throw(_("Cannot set Notification on Document Type {0}").format(self.document_type))
def validate_forbidden_document_types(self):
if self.document_type in FORBIDDEN_DOCUMENT_TYPES or (
frappe.get_meta(self.document_type).istable and self.event not in DATE_BASED_EVENTS
):
# only date based events are allowed for child tables
frappe.throw(
_("Cannot set Notification with event {0} on Document Type {1}").format(
_(self.event), _(self.document_type)
)
)
def get_documents_for_today(self):
"""get list of documents that will be triggered today"""
@ -237,8 +241,8 @@ def get_context(context):
notification_doc = {
"type": "Alert",
"document_type": doc.doctype,
"document_name": doc.name,
"document_type": get_reference_doctype(doc),
"document_name": get_reference_name(doc),
"subject": subject,
"from_user": doc.modified_by or doc.owner,
"email_content": frappe.render_template(self.message, context),
@ -270,8 +274,8 @@ def get_context(context):
# No need to add if it is already a communication.
if doc.doctype != "Communication":
communication = make_communication(
doctype=doc.doctype,
name=doc.name,
doctype=get_reference_doctype(doc),
name=get_reference_name(doc),
content=message,
subject=subject,
sender=sender,
@ -294,8 +298,8 @@ def get_context(context):
cc=cc,
bcc=bcc,
message=message,
reference_doctype=doc.doctype,
reference_name=doc.name,
reference_doctype=get_reference_doctype(doc),
reference_name=get_reference_name(doc),
attachments=attachments,
expose_recipients="header",
print_letterhead=((attachments and attachments[0].get("print_letterhead")) or False),
@ -306,8 +310,8 @@ def get_context(context):
send_slack_message(
webhook_url=self.slack_webhook_url,
message=frappe.render_template(self.message, context),
reference_doctype=doc.doctype,
reference_name=doc.name,
reference_doctype=get_reference_doctype(doc),
reference_name=get_reference_name(doc),
)
def send_sms(self, doc, context):
@ -543,3 +547,11 @@ def get_emails_from_template(template, context):
emails = frappe.render_template(template, context) if "{" in template else template
return filter(None, emails.replace(",", "\n").split("\n"))
def get_reference_doctype(doc):
return doc.parenttype if doc.meta.istable else doc.doctype
def get_reference_name(doc):
return doc.parent if doc.meta.istable else doc.name

140
frappe/email/frappemail.py Normal file
View file

@ -0,0 +1,140 @@
from datetime import datetime
from typing import TYPE_CHECKING
from urllib.parse import urljoin
import pytz
import frappe
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."""
def __init__(
self,
site: str,
mailbox: str,
api_key: str | None = None,
api_secret: str | None = None,
access_token: str | None = None,
) -> None:
self.site = site
self.mailbox = mailbox
self.api_key = api_key
self.api_secret = api_secret
self.access_token = access_token
@staticmethod
def get_client(
site: str,
mailbox: str,
api_key: str | None = None,
api_secret: str | None = None,
access_token: str | None = None,
) -> FrappeClient | FrappeOAuth2Client:
"""Returns a FrappeClient or FrappeOAuth2Client instance."""
if hasattr(frappe.local, "frappe_mail_clients"):
if client := frappe.local.frappe_mail_clients.get(mailbox):
return client
else:
frappe.local.frappe_mail_clients = {}
client = (
FrappeOAuth2Client(url=site, access_token=access_token)
if access_token
else FrappeClient(url=site, api_key=api_key, api_secret=api_secret)
)
frappe.local.frappe_mail_clients[mailbox] = client
return client
def request(
self,
method: str,
endpoint: str,
params: dict | None = None,
data: dict | None = None,
json: dict | None = None,
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."""
url = urljoin(self.site, endpoint)
client = self.get_client(self.site, self.mailbox, self.api_key, self.api_secret, self.access_token)
headers = headers or {}
headers.update(client.headers)
response = 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
def validate(self, for_outbound: bool = False, for_inbound: bool = False) -> None:
"""Validates the mailbox for inbound and outbound emails."""
endpoint = "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)
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:
"""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)
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"
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"]
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}
def convert_to_utc(date_time: datetime | str, from_timezone: str | None = None) -> str:
"""Converts datetime to UTC timezone."""
dt = (
pytz.timezone(from_timezone or get_system_timezone())
.localize(get_datetime(date_time))
.astimezone(pytz.utc)
)
return get_datetime_str(dt)

View file

@ -2731,18 +2731,6 @@
],
"isd": "+216"
},
"Turkey": {
"code": "tr",
"currency": "TRY",
"currency_fraction": "Kuru\u015f",
"currency_fraction_units": 100,
"currency_symbol": "\u20ba",
"number_format": "#.###,##",
"timezones": [
"Europe/Istanbul"
],
"isd": "+90"
},
"Turkmenistan": {
"code": "tm",
"currency": "TMM",
@ -2774,6 +2762,18 @@
"Pacific/Funafuti"
],
"isd": "+688"
},
"T\u00fcrkiye": {
"code": "tr",
"currency": "TRY",
"currency_fraction": "Kuru\u015f",
"currency_fraction_units": 100,
"currency_symbol": "\u20ba",
"number_format": "#.###,##",
"timezones": [
"Europe/Istanbul"
],
"isd": "+90"
},
"Uganda": {
"code": "ug",

View file

@ -139,7 +139,7 @@
"link_fieldname": "connected_app"
}
],
"modified": "2024-03-23 16:01:30.633764",
"modified": "2024-07-05 08:24:50.182706",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Connected App",
@ -162,6 +162,7 @@
"role": "All"
}
],
"show_title_field_in_link": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [],

View file

@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: developers@frappe.io\n"
"POT-Creation-Date: 2024-06-30 09:33+0000\n"
"PO-Revision-Date: 2024-07-02 15:49\n"
"POT-Creation-Date: 2024-07-07 09:33+0000\n"
"PO-Revision-Date: 2024-07-10 16:24\n"
"Last-Translator: developers@frappe.io\n"
"Language-Team: Esperanto\n"
"MIME-Version: 1.0\n"
@ -1003,8 +1003,8 @@ msgid "Add Child"
msgstr "crwdns90826:0crwdne90826:0"
#: public/js/frappe/views/kanban/kanban_board.html:4
#: public/js/frappe/views/reports/query_report.js:1696
#: public/js/frappe/views/reports/query_report.js:1699
#: public/js/frappe/views/reports/query_report.js:1681
#: public/js/frappe/views/reports/query_report.js:1684
#: public/js/frappe/views/reports/report_view.js:324
#: public/js/frappe/views/reports/report_view.js:349
msgid "Add Column"
@ -3719,6 +3719,7 @@ msgstr "crwdns128570:0crwdne128570:0"
#. Name of a DocType
#: desk/doctype/calendar_view/calendar_view.json
#: public/js/frappe/list/base_list.js:208
msgid "Calendar View"
msgstr "crwdns91974:0crwdne91974:0"
@ -3895,7 +3896,7 @@ msgstr "crwdns92056:0crwdne92056:0"
msgid "Cannot Remove"
msgstr "crwdns92058:0crwdne92058:0"
#: model/base_document.py:1072
#: model/base_document.py:1073
msgid "Cannot Update After Submit"
msgstr "crwdns92060:0crwdne92060:0"
@ -3983,7 +3984,7 @@ msgstr "crwdns92100:0{0}crwdne92100:0"
msgid "Cannot delete {0}"
msgstr "crwdns92102:0{0}crwdne92102:0"
#: utils/nestedset.py:296
#: utils/nestedset.py:299
msgid "Cannot delete {0} as it has child nodes"
msgstr "crwdns92104:0{0}crwdne92104:0"
@ -3991,7 +3992,7 @@ msgstr "crwdns92104:0{0}crwdne92104:0"
msgid "Cannot edit Standard Dashboards"
msgstr "crwdns92106:0crwdne92106:0"
#: email/doctype/notification/notification.py:121
#: email/doctype/notification/notification.py:122
msgid "Cannot edit Standard Notification. To edit, please disable this and duplicate it"
msgstr "crwdns92108:0crwdne92108:0"
@ -4060,7 +4061,7 @@ msgstr "crwdns92136:0crwdne92136:0"
msgid "Cannot set 'Report' permission if 'Only If Creator' permission is set"
msgstr "crwdns110830:0crwdne110830:0"
#: email/doctype/notification/notification.py:137
#: email/doctype/notification/notification.py:138
msgid "Cannot set Notification on Document Type {0}"
msgstr "crwdns92138:0{0}crwdne92138:0"
@ -4234,6 +4235,8 @@ msgstr "crwdns128610:0crwdne128610:0"
#. Label of the chart_name (Link) field in DocType 'Workspace Chart'
#: desk/doctype/dashboard_chart/dashboard_chart.json
#: desk/doctype/workspace_chart/workspace_chart.json
#: public/js/frappe/views/reports/query_report.js:289
#: public/js/frappe/widgets/widget_dialog.js:137
msgid "Chart Name"
msgstr "crwdns128612:0crwdne128612:0"
@ -4650,7 +4653,7 @@ msgctxt "Shrink code field."
msgid "Collapse"
msgstr "crwdns92402:0crwdne92402:0"
#: public/js/frappe/views/reports/query_report.js:1979
#: public/js/frappe/views/reports/query_report.js:1964
#: public/js/frappe/views/treeview.js:121
msgid "Collapse All"
msgstr "crwdns92404:0crwdne92404:0"
@ -6179,6 +6182,10 @@ msgstr "crwdns128842:0crwdne128842:0"
msgid "Dashboard Settings"
msgstr "crwdns93118:0crwdne93118:0"
#: public/js/frappe/list/base_list.js:205
msgid "Dashboard View"
msgstr "crwdns142844:0crwdne142844:0"
#. Label of the tab_break_2 (Tab Break) field in DocType 'Workspace'
#: desk/doctype/workspace/workspace.json
msgid "Dashboards"
@ -6760,7 +6767,8 @@ msgid "Department"
msgstr "crwdns128922:0crwdne128922:0"
#. Label of the dependencies (Data) field in DocType 'Workspace Link'
#: desk/doctype/workspace_link/workspace_link.json www/attribution.html:29
#: desk/doctype/workspace_link/workspace_link.json
#: public/js/frappe/widgets/widget_dialog.js:318 www/attribution.html:29
msgid "Dependencies"
msgstr "crwdns112688:0crwdne112688:0"
@ -7219,6 +7227,7 @@ msgstr "crwdns93582:0{0}crwdne93582:0"
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: integrations/doctype/webhook/webhook.json
#: printing/doctype/print_format/print_format.json
#: public/js/frappe/widgets/widget_dialog.js:164
#: website/doctype/website_slideshow/website_slideshow.js:18
msgid "DocType"
msgstr "crwdns93584:0crwdne93584:0"
@ -7277,6 +7286,7 @@ msgstr "crwdns93632:0crwdne93632:0"
#. Label of the doc_view (Select) field in DocType 'Workspace Shortcut'
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: public/js/frappe/widgets/widget_dialog.js:477
msgid "DocType View"
msgstr "crwdns128992:0crwdne128992:0"
@ -7800,7 +7810,7 @@ msgstr "crwdns129042:0crwdne129042:0"
msgid "Download Your Data"
msgstr "crwdns93896:0crwdne93896:0"
#: contacts/doctype/contact/contact.js:93
#: contacts/doctype/contact/contact.js:98
msgid "Download vCard"
msgstr "crwdns140790:0crwdne140790:0"
@ -7988,7 +7998,7 @@ msgstr "crwdns93972:0crwdne93972:0"
#: public/js/frappe/form/templates/contact_list.html:7
#: public/js/frappe/form/toolbar.js:681
#: public/js/frappe/views/reports/query_report.js:815
#: public/js/frappe/views/reports/query_report.js:1649
#: public/js/frappe/views/reports/query_report.js:1634
#: public/js/frappe/views/workspace/workspace.js:460
#: public/js/frappe/views/workspace/workspace.js:816
#: public/js/frappe/widgets/base_widget.js:64
@ -8965,9 +8975,9 @@ msgstr "crwdns94422:0crwdne94422:0"
msgid "Error in Header/Footer Script"
msgstr "crwdns110924:0crwdne110924:0"
#: email/doctype/notification/notification.py:394
#: email/doctype/notification/notification.py:510
#: email/doctype/notification/notification.py:516
#: email/doctype/notification/notification.py:395
#: email/doctype/notification/notification.py:511
#: email/doctype/notification/notification.py:517
msgid "Error in Notification"
msgstr "crwdns94424:0crwdne94424:0"
@ -8979,7 +8989,7 @@ msgstr "crwdns94426:0{0}crwdnd94426:0{1}crwdne94426:0"
msgid "Error while connecting to email account {0}"
msgstr "crwdns94428:0{0}crwdne94428:0"
#: email/doctype/notification/notification.py:507
#: email/doctype/notification/notification.py:508
msgid "Error while evaluating Notification {0}. Please fix your template."
msgstr "crwdns94430:0{0}crwdne94430:0"
@ -9136,7 +9146,7 @@ msgstr "crwdns94494:0crwdne94494:0"
msgid "Executing..."
msgstr "crwdns94496:0crwdne94496:0"
#: public/js/frappe/views/reports/query_report.js:1993
#: public/js/frappe/views/reports/query_report.js:1978
msgid "Execution Time: {0} sec"
msgstr "crwdns94498:0{0}crwdne94498:0"
@ -9154,7 +9164,7 @@ msgctxt "Enlarge code field."
msgid "Expand"
msgstr "crwdns94504:0crwdne94504:0"
#: public/js/frappe/views/reports/query_report.js:1979
#: public/js/frappe/views/reports/query_report.js:1964
#: public/js/frappe/views/treeview.js:125
msgid "Expand All"
msgstr "crwdns94506:0crwdne94506:0"
@ -9210,7 +9220,7 @@ msgstr "crwdns129232:0crwdne129232:0"
#: core/doctype/docperm/docperm.json core/doctype/recorder/recorder_list.js:37
#: public/js/frappe/data_import/data_exporter.js:91
#: public/js/frappe/data_import/data_exporter.js:242
#: public/js/frappe/views/reports/query_report.js:1684
#: public/js/frappe/views/reports/query_report.js:1669
#: public/js/frappe/views/reports/report_view.js:1550
msgid "Export"
msgstr "crwdns94526:0crwdne94526:0"
@ -9551,7 +9561,7 @@ msgstr "crwdns94660:0crwdne94660:0"
#: public/js/frappe/list/bulk_operations.js:297
#: public/js/frappe/list/list_view_permission_restrictions.html:3
#: public/js/frappe/views/reports/query_report.js:236
#: public/js/frappe/views/reports/query_report.js:1738
#: public/js/frappe/views/reports/query_report.js:1723
#: website/doctype/web_form_field/web_form_field.json
#: website/doctype/web_form_list_column/web_form_list_column.json
msgid "Field"
@ -9859,7 +9869,7 @@ msgstr "crwdns94824:0crwdne94824:0"
#: desk/doctype/number_card/number_card.js:205
#: desk/doctype/number_card/number_card.js:336
#: email/doctype/auto_email_report/auto_email_report.js:90
#: public/js/frappe/list/base_list.js:882
#: public/js/frappe/list/base_list.js:890
#: public/js/frappe/ui/filters/filter_list.js:134
#: website/doctype/web_form/web_form.js:197
msgid "Filter"
@ -10295,7 +10305,7 @@ msgstr "crwdns95024:0crwdne95024:0"
msgid "For Value"
msgstr "crwdns129392:0crwdne129392:0"
#: public/js/frappe/views/reports/query_report.js:1990
#: public/js/frappe/views/reports/query_report.js:1975
#: public/js/frappe/views/reports/report_view.js:96
msgid "For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10)."
msgstr "crwdns95034:0crwdne95034:0"
@ -10542,7 +10552,7 @@ msgstr "crwdns95156:0crwdne95156:0"
msgid "From Date Field"
msgstr "crwdns129430:0crwdne129430:0"
#: public/js/frappe/views/reports/query_report.js:1704
#: public/js/frappe/views/reports/query_report.js:1689
msgid "From Document Type"
msgstr "crwdns95162:0crwdne95162:0"
@ -10638,6 +10648,10 @@ msgstr "crwdns129446:0crwdne129446:0"
msgid "Gantt"
msgstr "crwdns95208:0crwdne95208:0"
#: public/js/frappe/list/base_list.js:206
msgid "Gantt View"
msgstr "crwdns142846:0crwdne142846:0"
#. Label of the gender (Link) field in DocType 'Contact'
#. Name of a DocType
#. Label of the gender (Data) field in DocType 'Gender'
@ -12009,6 +12023,10 @@ msgstr "crwdns129686:0crwdne129686:0"
msgid "Image Link"
msgstr "crwdns129688:0crwdne129688:0"
#: public/js/frappe/list/base_list.js:209
msgid "Image View"
msgstr "crwdns142848:0crwdne142848:0"
#. Label of the image_width (Float) field in DocType 'Letter Head'
#. Label of the footer_image_width (Float) field in DocType 'Letter Head'
#: printing/doctype/letter_head/letter_head.json
@ -12280,6 +12298,10 @@ msgstr "crwdns95966:0crwdne95966:0"
msgid "Inbox User"
msgstr "crwdns95970:0crwdne95970:0"
#: public/js/frappe/list/base_list.js:210
msgid "Inbox View"
msgstr "crwdns142850:0crwdne142850:0"
#. Label of the include_name_field (Check) field in DocType 'Form Tour'
#: desk/doctype/form_tour/form_tour.json
msgid "Include Name Field"
@ -12299,11 +12321,11 @@ msgstr "crwdns95976:0crwdne95976:0"
msgid "Include Web View Link in Email"
msgstr "crwdns129732:0crwdne129732:0"
#: public/js/frappe/views/reports/query_report.js:1521
#: public/js/frappe/views/reports/query_report.js:1506
msgid "Include filters"
msgstr "crwdns95980:0crwdne95980:0"
#: public/js/frappe/views/reports/query_report.js:1513
#: public/js/frappe/views/reports/query_report.js:1498
msgid "Include indentation"
msgstr "crwdns95982:0crwdne95982:0"
@ -12459,7 +12481,7 @@ msgstr "crwdns110970:0crwdne110970:0"
#. Label of the insert_after (Select) field in DocType 'Custom Field'
#: custom/doctype/custom_field/custom_field.json
#: public/js/frappe/views/reports/query_report.js:1744
#: public/js/frappe/views/reports/query_report.js:1729
msgid "Insert After"
msgstr "crwdns96046:0crwdne96046:0"
@ -12666,7 +12688,7 @@ msgstr "crwdns96132:0{0}crwdne96132:0"
msgid "Invalid \"mandatory_depends_on\" expression"
msgstr "crwdns96134:0crwdne96134:0"
#: utils/nestedset.py:177
#: utils/nestedset.py:178
msgid "Invalid Action"
msgstr "crwdns96136:0crwdne96136:0"
@ -13037,6 +13059,7 @@ msgstr "crwdns96308:0crwdne96308:0"
#. Label of the is_query_report (Check) field in DocType 'Workspace Link'
#: desk/doctype/workspace_link/workspace_link.json
#: public/js/frappe/widgets/widget_dialog.js:336
msgid "Is Query Report"
msgstr "crwdns129828:0crwdne129828:0"
@ -13140,7 +13163,7 @@ msgstr "crwdns129850:0crwdne129850:0"
msgid "Item Type"
msgstr "crwdns129852:0crwdne129852:0"
#: utils/nestedset.py:228
#: utils/nestedset.py:229
msgid "Item cannot be added to its own descendants"
msgstr "crwdns96372:0crwdne96372:0"
@ -13265,6 +13288,7 @@ msgstr "crwdns129880:0crwdne129880:0"
#. Label of the kanban_board (Link) field in DocType 'Workspace Shortcut'
#: desk/doctype/kanban_board/kanban_board.json
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: public/js/frappe/widgets/widget_dialog.js:509
msgid "Kanban Board"
msgstr "crwdns96432:0crwdne96432:0"
@ -13284,6 +13308,10 @@ msgctxt "Button in kanban view menu"
msgid "Kanban Settings"
msgstr "crwdns96444:0crwdne96444:0"
#: public/js/frappe/list/base_list.js:207
msgid "Kanban View"
msgstr "crwdns142852:0crwdne142852:0"
#. Description of a DocType
#: core/doctype/activity_log/activity_log.json
msgid "Keep track of all update feeds"
@ -13529,7 +13557,10 @@ msgstr "crwdns96524:0{0}crwdne96524:0"
#: desk/doctype/workspace_quick_list/workspace_quick_list.json
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: printing/page/print_format_builder/print_format_builder.js:474
#: public/js/frappe/widgets/widget_dialog.js:187
#: public/js/frappe/widgets/widget_dialog.js:255
#: public/js/frappe/widgets/widget_dialog.js:304
#: public/js/frappe/widgets/widget_dialog.js:421
#: public/js/frappe/widgets/widget_dialog.js:645
#: public/js/frappe/widgets/widget_dialog.js:678
#: templates/form_grid/fields.html:37
@ -14096,6 +14127,8 @@ msgstr "crwdns130038:0crwdne130038:0"
#. Label of the link_to (Dynamic Link) field in DocType 'Workspace Shortcut'
#: desk/doctype/workspace_link/workspace_link.json
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: public/js/frappe/widgets/widget_dialog.js:285
#: public/js/frappe/widgets/widget_dialog.js:430
msgid "Link To"
msgstr "crwdns130040:0crwdne130040:0"
@ -14105,6 +14138,7 @@ msgstr "crwdns110990:0crwdne110990:0"
#. Label of the link_type (Select) field in DocType 'Workspace Link'
#: desk/doctype/workspace_link/workspace_link.json
#: public/js/frappe/widgets/widget_dialog.js:277
msgid "Link Type"
msgstr "crwdns130042:0crwdne130042:0"
@ -14148,7 +14182,7 @@ msgstr "crwdns96840:0crwdne96840:0"
#. Label of the links (Table) field in DocType 'Customize Form'
#. Label of the links (Table) field in DocType 'Workspace'
#: contacts/doctype/address/address.js:39 contacts/doctype/address/address.json
#: contacts/doctype/contact/contact.js:87 contacts/doctype/contact/contact.json
#: contacts/doctype/contact/contact.js:92 contacts/doctype/contact/contact.json
#: core/doctype/doctype/doctype.json
#: custom/doctype/customize_form/customize_form.json
#: desk/doctype/workspace/workspace.json public/js/frappe/form/toolbar.js:377
@ -14201,6 +14235,10 @@ msgctxt "Button in list view menu"
msgid "List Settings"
msgstr "crwdns96868:0crwdne96868:0"
#: public/js/frappe/list/base_list.js:203
msgid "List View"
msgstr "crwdns142854:0crwdne142854:0"
#. Name of a DocType
#: desk/doctype/list_view_settings/list_view_settings.json
msgid "List View Settings"
@ -14237,7 +14275,7 @@ msgstr "crwdns96884:0crwdne96884:0"
msgid "Load Balancing"
msgstr "crwdns130066:0crwdne130066:0"
#: public/js/frappe/list/base_list.js:378
#: public/js/frappe/list/base_list.js:386
#: website/doctype/blog_post/templates/blog_post_list.html:50
#: website/doctype/help_article/templates/help_article_list.html:30
msgid "Load More"
@ -14251,7 +14289,7 @@ msgstr "crwdns96890:0crwdne96890:0"
#: core/page/permission_manager/permission_manager.js:165
#: public/js/frappe/form/controls/multicheck.js:13
#: public/js/frappe/form/linked_with.js:13
#: public/js/frappe/list/base_list.js:490
#: public/js/frappe/list/base_list.js:498
#: public/js/frappe/list/list_view.js:335 public/js/frappe/ui/listing.html:16
#: public/js/frappe/views/reports/query_report.js:1017
msgid "Loading"
@ -14279,7 +14317,7 @@ msgstr "crwdns110996:0crwdne110996:0"
#: public/js/frappe/views/kanban/kanban_board.html:11
#: public/js/frappe/widgets/chart_widget.js:50
#: public/js/frappe/widgets/number_card_widget.js:174
#: public/js/frappe/widgets/quick_list_widget.js:126
#: public/js/frappe/widgets/quick_list_widget.js:128
msgid "Loading..."
msgstr "crwdns96898:0crwdne96898:0"
@ -14664,6 +14702,10 @@ msgstr "crwdns130124:0crwdne130124:0"
msgid "Map Columns"
msgstr "crwdns97072:0crwdne97072:0"
#: public/js/frappe/list/base_list.js:212
msgid "Map View"
msgstr "crwdns142856:0crwdne142856:0"
#: public/js/frappe/data_import/import_preview.js:290
msgid "Map columns from {0} to fields in {1}"
msgstr "crwdns111002:0{0}crwdnd111002:0{1}crwdne111002:0"
@ -14893,7 +14935,7 @@ msgstr "crwdns97168:0crwdne97168:0"
msgid "Merge with existing"
msgstr "crwdns97170:0crwdne97170:0"
#: utils/nestedset.py:304
#: utils/nestedset.py:307
msgid "Merging is only possible between Group-to-Group or Leaf Node-to-Leaf Node"
msgstr "crwdns97172:0crwdne97172:0"
@ -15049,6 +15091,10 @@ msgstr "crwdns97252:0crwdne97252:0"
msgid "Method"
msgstr "crwdns130200:0crwdne130200:0"
#: __init__.py:936
msgid "Method Not Allowed"
msgstr "crwdns142858:0crwdne142858:0"
#: desk/doctype/number_card/number_card.py:70
msgid "Method is required to create a number card"
msgstr "crwdns97266:0crwdne97266:0"
@ -15440,7 +15486,7 @@ msgstr "crwdns130234:0crwdne130234:0"
msgid "Mozilla doesn't support :has() so you can pass parent selector here as workaround"
msgstr "crwdns130236:0crwdne130236:0"
#: utils/nestedset.py:328
#: utils/nestedset.py:331
msgid "Multiple root nodes not allowed."
msgstr "crwdns97478:0crwdne97478:0"
@ -15674,7 +15720,7 @@ msgstr "crwdns97574:0crwdne97574:0"
msgid "Negative Value"
msgstr "crwdns97576:0crwdne97576:0"
#: utils/nestedset.py:93
#: utils/nestedset.py:94
msgid "Nested set error. Please contact the Administrator."
msgstr "crwdns97578:0crwdne97578:0"
@ -16021,7 +16067,7 @@ msgstr "crwdns130294:0crwdne130294:0"
#: public/js/form_builder/utils.js:341
#: public/js/frappe/form/controls/link.js:475
#: public/js/frappe/list/list_sidebar_group_by.js:223
#: public/js/frappe/views/reports/query_report.js:1541
#: public/js/frappe/views/reports/query_report.js:1526
#: website/doctype/help_article/templates/help_article.html:26
msgid "No"
msgstr "crwdns97696:0crwdne97696:0"
@ -16065,7 +16111,7 @@ msgstr "crwdns97718:0crwdne97718:0"
msgid "No Data to Show"
msgstr "crwdns111038:0crwdne111038:0"
#: public/js/frappe/widgets/quick_list_widget.js:131
#: public/js/frappe/widgets/quick_list_widget.js:133
msgid "No Data..."
msgstr "crwdns111040:0crwdne111040:0"
@ -16532,7 +16578,7 @@ msgstr "crwdns97892:0crwdne97892:0"
msgid "Not allowed for {0}: {1}"
msgstr "crwdns97894:0{0}crwdnd97894:0{1}crwdne97894:0"
#: email/doctype/notification/notification.py:391
#: email/doctype/notification/notification.py:392
msgid "Not allowed to attach {0} document, please enable Allow Print For {0} in Print Settings"
msgstr "crwdns97896:0{0}crwdnd97896:0{0}crwdne97896:0"
@ -16651,7 +16697,7 @@ msgstr "crwdns97940:0crwdne97940:0"
msgid "Nothing left to undo"
msgstr "crwdns97942:0crwdne97942:0"
#: public/js/frappe/list/base_list.js:362
#: public/js/frappe/list/base_list.js:370
#: public/js/frappe/views/reports/query_report.js:105
#: templates/includes/list/list.html:7
#: website/doctype/blog_post/templates/blog_post_list.html:41
@ -17057,9 +17103,14 @@ msgstr "crwdns111096:0{0}crwdnd111096:0{1}crwdne111096:0"
#. Label of the onboard (Check) field in DocType 'Workspace Link'
#: desk/doctype/workspace_link/workspace_link.json
#: public/js/frappe/widgets/widget_dialog.js:330
msgid "Onboard"
msgstr "crwdns130402:0crwdne130402:0"
#: public/js/frappe/widgets/widget_dialog.js:236
msgid "Onboarding Name"
msgstr "crwdns142860:0crwdne142860:0"
#. Name of a DocType
#: desk/doctype/onboarding_permission/onboarding_permission.json
msgid "Onboarding Permission"
@ -17167,6 +17218,7 @@ msgstr "crwdns127690:0crwdne127690:0"
#. Label of the only_for (Link) field in DocType 'Workspace Link'
#: desk/doctype/workspace_link/workspace_link.json
#: public/js/frappe/widgets/widget_dialog.js:323
msgid "Only for"
msgstr "crwdns130412:0crwdne130412:0"
@ -17516,7 +17568,7 @@ msgstr "crwdns130460:0crwdne130460:0"
#: printing/page/print/print.js:71
#: public/js/frappe/form/templates/print_layout.html:44
#: public/js/frappe/views/reports/query_report.js:1669
#: public/js/frappe/views/reports/query_report.js:1654
msgid "PDF"
msgstr "crwdns98288:0crwdne98288:0"
@ -17908,8 +17960,8 @@ msgid "Password not found for {0} {1} {2}"
msgstr "crwdns98464:0{0}crwdnd98464:0{1}crwdnd98464:0{2}crwdne98464:0"
#: core/doctype/user/user.py:1021
msgid "Password reset instructions have been sent to your email"
msgstr "crwdns98466:0crwdne98466:0"
msgid "Password reset instructions have been sent to {}'s email"
msgstr "crwdns142862:0crwdne142862:0"
#: www/update-password.html:164
msgid "Password set"
@ -18308,7 +18360,7 @@ msgstr "crwdns98646:0crwdne98646:0"
msgid "Please check the filter values set for Dashboard Chart: {}"
msgstr "crwdns98648:0crwdne98648:0"
#: model/base_document.py:872
#: model/base_document.py:873
msgid "Please check the value of \"Fetch From\" set for field {0}"
msgstr "crwdns98650:0{0}crwdne98650:0"
@ -18610,11 +18662,11 @@ msgstr "crwdns98790:0crwdne98790:0"
msgid "Please specify a valid parent DocType for {0}"
msgstr "crwdns98792:0{0}crwdne98792:0"
#: email/doctype/notification/notification.py:87
#: email/doctype/notification/notification.py:88
msgid "Please specify which date field must be checked"
msgstr "crwdns98794:0crwdne98794:0"
#: email/doctype/notification/notification.py:90
#: email/doctype/notification/notification.py:91
msgid "Please specify which value field must be checked"
msgstr "crwdns98796:0crwdne98796:0"
@ -18947,7 +18999,7 @@ msgstr "crwdns112704:0{0}crwdne112704:0"
#: public/js/frappe/form/templates/print_layout.html:46
#: public/js/frappe/form/toolbar.js:332 public/js/frappe/form/toolbar.js:344
#: public/js/frappe/list/bulk_operations.js:87
#: public/js/frappe/views/reports/query_report.js:1655
#: public/js/frappe/views/reports/query_report.js:1640
#: public/js/frappe/views/reports/report_view.js:1460
#: public/js/frappe/views/treeview.js:469 www/printview.html:18
msgid "Print"
@ -19809,7 +19861,7 @@ msgstr "crwdns99328:0crwdne99328:0"
msgid "Rebuild Tree"
msgstr "crwdns99330:0crwdne99330:0"
#: utils/nestedset.py:176
#: utils/nestedset.py:177
msgid "Rebuilding of tree is not supported for {}"
msgstr "crwdns99332:0crwdne99332:0"
@ -20167,7 +20219,7 @@ msgstr "crwdns99526:0crwdne99526:0"
#: public/js/frappe/desk.js:533 public/js/frappe/form/form.js:1196
#: public/js/frappe/form/templates/print_layout.html:6
#: public/js/frappe/list/base_list.js:66
#: public/js/frappe/views/reports/query_report.js:1644
#: public/js/frappe/views/reports/query_report.js:1629
#: public/js/frappe/views/treeview.js:475
#: public/js/frappe/widgets/chart_widget.js:290
#: public/js/frappe/widgets/number_card_widget.js:324
@ -20265,7 +20317,7 @@ msgstr "crwdns99566:0crwdne99566:0"
msgid "Reload File"
msgstr "crwdns111162:0crwdne111162:0"
#: public/js/frappe/list/base_list.js:242
#: public/js/frappe/list/base_list.js:250
msgid "Reload List"
msgstr "crwdns99568:0crwdne99568:0"
@ -20534,7 +20586,7 @@ msgstr "crwdns99694:0crwdne99694:0"
#: core/doctype/report/report.json
#: desk/doctype/dashboard_chart/dashboard_chart.json
#: desk/doctype/number_card/number_card.json
#: public/js/frappe/views/reports/query_report.js:1825
#: public/js/frappe/views/reports/query_report.js:1810
msgid "Report Name"
msgstr "crwdns99696:0crwdne99696:0"
@ -20557,6 +20609,10 @@ msgstr "crwdns130836:0crwdne130836:0"
msgid "Report Type"
msgstr "crwdns130838:0crwdne130838:0"
#: public/js/frappe/list/base_list.js:204
msgid "Report View"
msgstr "crwdns142864:0crwdne142864:0"
#: core/doctype/doctype/doctype.py:1780
msgid "Report cannot be set for Single types"
msgstr "crwdns99718:0crwdne99718:0"
@ -20591,7 +20647,7 @@ msgstr "crwdns99730:0crwdne99730:0"
msgid "Report was not saved (there were errors)"
msgstr "crwdns99732:0crwdne99732:0"
#: public/js/frappe/views/reports/query_report.js:1863
#: public/js/frappe/views/reports/query_report.js:1848
msgid "Report with more than 10 columns looks better in Landscape mode."
msgstr "crwdns99734:0crwdne99734:0"
@ -21177,7 +21233,7 @@ msgstr "crwdns130922:0crwdne130922:0"
msgid "Roles can be set for users from their User page."
msgstr "crwdns111176:0crwdne111176:0"
#: utils/nestedset.py:277
#: utils/nestedset.py:280
msgid "Root {0} cannot be deleted"
msgstr "crwdns100012:0{0}crwdne100012:0"
@ -21250,7 +21306,7 @@ msgstr "crwdns111178:0crwdne111178:0"
msgid "Row # {0}: Non administrator user can not set the role {1} to the custom doctype"
msgstr "crwdns100056:0{0}crwdnd100056:0{1}crwdne100056:0"
#: model/base_document.py:903
#: model/base_document.py:904
msgid "Row #{0}:"
msgstr "crwdns100058:0#{0}crwdne100058:0"
@ -21534,11 +21590,11 @@ msgstr "crwdns130978:0crwdne130978:0"
#: public/js/frappe/views/kanban/kanban_settings.js:45
#: public/js/frappe/views/kanban/kanban_settings.js:189
#: public/js/frappe/views/kanban/kanban_view.js:343
#: public/js/frappe/views/reports/query_report.js:1817
#: public/js/frappe/views/reports/query_report.js:1802
#: public/js/frappe/views/reports/report_view.js:1640
#: public/js/frappe/views/workspace/workspace.js:501
#: public/js/frappe/widgets/base_widget.js:142
#: public/js/frappe/widgets/quick_list_widget.js:117
#: public/js/frappe/widgets/quick_list_widget.js:119
#: public/js/print_format_builder/print_format_builder.bundle.js:15
#: public/js/workflow_builder/workflow_builder.bundle.js:33
msgid "Save"
@ -21565,7 +21621,7 @@ msgstr "crwdns100180:0crwdne100180:0"
msgid "Save Filter"
msgstr "crwdns111194:0crwdne111194:0"
#: public/js/frappe/views/reports/query_report.js:1820
#: public/js/frappe/views/reports/query_report.js:1805
msgid "Save Report"
msgstr "crwdns100182:0crwdne100182:0"
@ -22689,7 +22745,7 @@ msgid "Set Filters"
msgstr "crwdns100696:0crwdne100696:0"
#: public/js/frappe/widgets/chart_widget.js:395
#: public/js/frappe/widgets/quick_list_widget.js:102
#: public/js/frappe/widgets/quick_list_widget.js:104
msgid "Set Filters for {0}"
msgstr "crwdns100698:0{0}crwdne100698:0"
@ -22902,7 +22958,7 @@ msgstr "crwdns111218:0crwdne111218:0"
msgid "Setup Approval Workflows"
msgstr "crwdns100772:0crwdne100772:0"
#: public/js/frappe/views/reports/query_report.js:1690
#: public/js/frappe/views/reports/query_report.js:1675
#: public/js/frappe/views/reports/report_view.js:1618
msgid "Setup Auto Email"
msgstr "crwdns100774:0crwdne100774:0"
@ -24936,7 +24992,7 @@ msgid "The Client ID obtained from the Google Cloud Console under <a href=\"http
"</a>"
msgstr "crwdns131442:0crwdne131442:0"
#: email/doctype/notification/notification.py:130
#: email/doctype/notification/notification.py:131
msgid "The Condition '{0}' is invalid"
msgstr "crwdns101636:0{0}crwdne101636:0"
@ -25390,7 +25446,7 @@ msgstr "crwdns131482:0crwdne131482:0"
msgid "This goes above the slideshow."
msgstr "crwdns131484:0crwdne131484:0"
#: public/js/frappe/views/reports/query_report.js:2027
#: public/js/frappe/views/reports/query_report.js:2012
msgid "This is a background report. Please set the appropriate filters and then generate a new one."
msgstr "crwdns101812:0crwdne101812:0"
@ -26249,6 +26305,10 @@ msgstr "crwdns131624:0crwdne131624:0"
msgid "Tree"
msgstr "crwdns131626:0crwdne131626:0"
#: public/js/frappe/list/base_list.js:211
msgid "Tree View"
msgstr "crwdns142866:0crwdne142866:0"
#. Description of the 'Is Tree' (Check) field in DocType 'DocType'
#: core/doctype/doctype/doctype.json
msgid "Tree structures are implemented using Nested Set"
@ -26354,6 +26414,7 @@ msgstr "crwdns131640:0crwdne131640:0"
#: desk/doctype/workspace_link/workspace_link.json
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: public/js/frappe/views/file/file_view.js:337
#: public/js/frappe/widgets/widget_dialog.js:399
#: social/doctype/energy_point_log/energy_point_log.json
#: website/doctype/web_template/web_template.json www/attribution.html:35
msgid "Type"
@ -26435,6 +26496,7 @@ msgstr "crwdns131652:0crwdne131652:0"
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: integrations/doctype/integration_request/integration_request.json
#: integrations/doctype/webhook_request_log/webhook_request_log.json
#: public/js/frappe/widgets/widget_dialog.js:469
#: website/doctype/top_bar_item/top_bar_item.json
#: website/doctype/website_slideshow_item/website_slideshow_item.json
msgid "URL"
@ -27111,7 +27173,7 @@ msgid "User Permission"
msgstr "crwdns102624:0crwdne102624:0"
#: core/page/permission_manager/permission_manager_help.html:30
#: public/js/frappe/views/reports/query_report.js:1804
#: public/js/frappe/views/reports/query_report.js:1789
#: public/js/frappe/views/reports/report_view.js:1666
msgid "User Permissions"
msgstr "crwdns102628:0crwdne102628:0"
@ -27429,7 +27491,7 @@ msgstr "crwdns131784:0crwdne131784:0"
msgid "Value To Be Set"
msgstr "crwdns131786:0crwdne131786:0"
#: model/base_document.py:965 model/document.py:682
#: model/base_document.py:966 model/document.py:682
msgid "Value cannot be changed for {0}"
msgstr "crwdns102750:0{0}crwdne102750:0"
@ -27472,7 +27534,7 @@ msgstr "crwdns102766:0{0}crwdne102766:0"
msgid "Value to Validate"
msgstr "crwdns131790:0crwdne131790:0"
#: model/base_document.py:1035
#: model/base_document.py:1036
msgid "Value too big"
msgstr "crwdns102770:0crwdne102770:0"
@ -27571,7 +27633,7 @@ msgid "View Full Log"
msgstr "crwdns111324:0crwdne111324:0"
#: public/js/frappe/views/treeview.js:463
#: public/js/frappe/widgets/quick_list_widget.js:245
#: public/js/frappe/widgets/quick_list_widget.js:247
msgid "View List"
msgstr "crwdns102808:0crwdne102808:0"
@ -28453,7 +28515,7 @@ msgstr "crwdns127790:0crwdne127790:0"
msgid "Write"
msgstr "crwdns131894:0crwdne131894:0"
#: model/base_document.py:875
#: model/base_document.py:876
msgid "Wrong Fetch From value"
msgstr "crwdns103194:0crwdne103194:0"
@ -28540,7 +28602,7 @@ msgstr "crwdns131908:0crwdne131908:0"
#: public/js/form_builder/utils.js:336
#: public/js/frappe/form/controls/link.js:475
#: public/js/frappe/list/list_sidebar_group_by.js:223
#: public/js/frappe/views/reports/query_report.js:1541
#: public/js/frappe/views/reports/query_report.js:1526
#: website/doctype/help_article/templates/help_article.html:25
msgid "Yes"
msgstr "crwdns103238:0crwdne103238:0"
@ -29753,7 +29815,7 @@ msgstr "crwdns104008:0crwdne104008:0"
msgid "via Google Meet"
msgstr "crwdns132068:0crwdne132068:0"
#: email/doctype/notification/notification.py:215
#: email/doctype/notification/notification.py:216
msgid "via Notification"
msgstr "crwdns104012:0crwdne104012:0"
@ -29896,7 +29958,7 @@ msgstr "crwdns104080:0{0}crwdne104080:0"
msgid "{0} Name"
msgstr "crwdns104082:0{0}crwdne104082:0"
#: model/base_document.py:1065
#: model/base_document.py:1066
msgid "{0} Not allowed to change {1} after submission from {2} to {3}"
msgstr "crwdns104084:0{0}crwdnd104084:0{1}crwdnd104084:0{2}crwdnd104084:0{3}crwdne104084:0"
@ -29922,10 +29984,6 @@ msgstr "crwdns104088:0{0}crwdne104088:0"
msgid "{0} Tree"
msgstr "crwdns104090:0{0}crwdne104090:0"
#: public/js/frappe/list/base_list.js:209
msgid "{0} View"
msgstr "crwdns104092:0{0}crwdne104092:0"
#: public/js/frappe/form/footer/form_timeline.js:126
#: public/js/frappe/form/sidebar/form_sidebar.js:86
msgid "{0} Web page views"
@ -30539,11 +30597,11 @@ msgstr "crwdns104370:0{0}crwdnd104370:0{1}crwdnd104370:0{2}crwdne104370:0"
msgid "{0} {1} already exists"
msgstr "crwdns104372:0{0}crwdnd104372:0{1}crwdne104372:0"
#: model/base_document.py:908
#: model/base_document.py:909
msgid "{0} {1} cannot be \"{2}\". It should be one of \"{3}\""
msgstr "crwdns104374:0{0}crwdnd104374:0{1}crwdnd104374:0{2}crwdnd104374:0{3}crwdne104374:0"
#: utils/nestedset.py:337
#: utils/nestedset.py:340
msgid "{0} {1} cannot be a leaf node as it has children"
msgstr "crwdns104376:0{0}crwdnd104376:0{1}crwdne104376:0"
@ -30563,11 +30621,11 @@ msgstr "crwdns104382:0{0}crwdnd104382:0{1}crwdne104382:0"
msgid "{0} {1}: Submitted Record cannot be deleted. You must {2} Cancel {3} it first."
msgstr "crwdns104384:0{0}crwdnd104384:0{1}crwdnd104384:0{2}crwdnd104384:0{3}crwdne104384:0"
#: model/base_document.py:1026
#: model/base_document.py:1027
msgid "{0}, Row {1}"
msgstr "crwdns104386:0{0}crwdnd104386:0{1}crwdne104386:0"
#: model/base_document.py:1031
#: model/base_document.py:1032
msgid "{0}: '{1}' ({3}) will get truncated, as max characters allowed is {2}"
msgstr "crwdns104388:0{0}crwdnd104388:0{1}crwdnd104388:0{3}crwdnd104388:0{2}crwdne104388:0"
@ -30660,7 +30718,7 @@ msgid "{0}: fieldname cannot be set to reserved keyword {1}"
msgstr "crwdns104430:0{0}crwdnd104430:0{1}crwdne104430:0"
#: contacts/doctype/address/address.js:35
#: contacts/doctype/contact/contact.js:83
#: contacts/doctype/contact/contact.js:88
#: public/js/frappe/views/workspace/workspace.js:170
msgid "{0}: {1}"
msgstr "crwdns104432:0{0}crwdnd104432:0{1}crwdne104432:0"

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: developers@frappe.io\n"
"POT-Creation-Date: 2024-06-30 09:33+0000\n"
"PO-Revision-Date: 2024-07-02 15:49\n"
"POT-Creation-Date: 2024-07-07 09:33+0000\n"
"PO-Revision-Date: 2024-07-14 17:15\n"
"Last-Translator: developers@frappe.io\n"
"Language-Team: Swedish\n"
"MIME-Version: 1.0\n"
@ -540,7 +540,7 @@ msgstr "<h4>E-post Svar Exempel</h4>\n\n"
"- Kund: {{ customer }}\n"
"- Belopp: {{ grand_total }}\n"
"</pre>\n\n"
"<h4>Hur man får fältnamn</h4>\n\n"
"<h4>Hur hämtas fältnamn</h4>\n\n"
"<p>Fältnamn som kan använda i e-post mall är fält i dokument som du skickar e-post meddelande från. Ta reda på fält namn för alla dokument via Inställningar &gt; Anpassa formulär vy och välja dokument typ (t.ex. Försäljning Faktura)</p>\n\n"
"<h4>Mallar</h4>\n\n"
"<p>Mallar kompileras med Jinja Templating Language. För att lära dig mer om Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">läs denna dokumentation.</a></p>\n"
@ -1182,8 +1182,8 @@ msgid "Add Child"
msgstr "Lägg till Underval"
#: public/js/frappe/views/kanban/kanban_board.html:4
#: public/js/frappe/views/reports/query_report.js:1696
#: public/js/frappe/views/reports/query_report.js:1699
#: public/js/frappe/views/reports/query_report.js:1681
#: public/js/frappe/views/reports/query_report.js:1684
#: public/js/frappe/views/reports/report_view.js:324
#: public/js/frappe/views/reports/report_view.js:349
msgid "Add Column"
@ -1292,7 +1292,7 @@ msgstr "Lägg till Mall"
#. Label of the add_total_row (Check) field in DocType 'Report'
#: core/doctype/report/report.json
msgid "Add Total Row"
msgstr "Lägg till Totalt Rad"
msgstr "Lägg till Totalt Antal Rader"
#. Label of the add_unsubscribe_link (Check) field in DocType 'Email Queue'
#: email/doctype/email_queue/email_queue.json
@ -3154,7 +3154,7 @@ msgstr "Bakgrund Jobb"
#. Report'
#: desk/doctype/system_health_report/system_health_report.json
msgid "Background Jobs Check"
msgstr "Bakgrund Jobb Koll"
msgstr "Bakgrund Jobb Status"
#. Label of the background_jobs_queue (Autocomplete) field in DocType 'Webhook'
#: integrations/doctype/webhook/webhook.json
@ -3407,7 +3407,7 @@ msgstr "Faktura Kontakt"
#. Label of the binary_logging (Data) field in DocType 'System Health Report'
#: desk/doctype/system_health_report/system_health_report.json
msgid "Binary Logging"
msgstr "Fakturering Logg"
msgstr "Binär Logg"
#. Label of the bio (Small Text) field in DocType 'User'
#. Label of the bio (Small Text) field in DocType 'About Us Team Member'
@ -3899,6 +3899,7 @@ msgstr "Kalender Namn"
#. Name of a DocType
#: desk/doctype/calendar_view/calendar_view.json
#: public/js/frappe/list/base_list.js:208
msgid "Calendar View"
msgstr "Kalender Vy"
@ -4075,7 +4076,7 @@ msgstr "Kan inte Hämta Värden"
msgid "Cannot Remove"
msgstr "Kan inte Ta Bort"
#: model/base_document.py:1072
#: model/base_document.py:1073
msgid "Cannot Update After Submit"
msgstr "Kan inte Uppdatera efter Godkännande"
@ -4163,7 +4164,7 @@ msgstr "Kan inte ta bort system skapad fält <strong>{0}</strong>. Dölj det ist
msgid "Cannot delete {0}"
msgstr "Kan inte radera {0}"
#: utils/nestedset.py:296
#: utils/nestedset.py:299
msgid "Cannot delete {0} as it has child nodes"
msgstr "Kan inte radera {0} eftersom det har underordnade noder"
@ -4171,7 +4172,7 @@ msgstr "Kan inte radera {0} eftersom det har underordnade noder"
msgid "Cannot edit Standard Dashboards"
msgstr "Kan inte redigera standard översikt panel"
#: email/doctype/notification/notification.py:121
#: email/doctype/notification/notification.py:122
msgid "Cannot edit Standard Notification. To edit, please disable this and duplicate it"
msgstr "Kan inte redigera standard avisering. Kopiera och skapa ny"
@ -4240,7 +4241,7 @@ msgstr "Kan inte ta bort ID fält"
msgid "Cannot set 'Report' permission if 'Only If Creator' permission is set"
msgstr "Kan inte ange \"Rapport\" behörighet om behörighet \"Endast om Ägare\" är angiven"
#: email/doctype/notification/notification.py:137
#: email/doctype/notification/notification.py:138
msgid "Cannot set Notification on Document Type {0}"
msgstr "Kan inte ange Avisering för DocType {0}"
@ -4354,7 +4355,7 @@ msgstr "Ändra"
#: tests/test_translate.py:99
msgctxt "Coins"
msgid "Change"
msgstr "Ändra"
msgstr "Växel"
#. Label of the label (Data) field in DocType 'Customize Form'
#: custom/doctype/customize_form/customize_form.json
@ -4415,6 +4416,8 @@ msgstr "Diagram Inställningar"
#. Label of the chart_name (Link) field in DocType 'Workspace Chart'
#: desk/doctype/dashboard_chart/dashboard_chart.json
#: desk/doctype/workspace_chart/workspace_chart.json
#: public/js/frappe/views/reports/query_report.js:289
#: public/js/frappe/widgets/widget_dialog.js:137
msgid "Chart Name"
msgstr "Diagram Namn"
@ -4831,7 +4834,7 @@ msgctxt "Shrink code field."
msgid "Collapse"
msgstr "Fäll In"
#: public/js/frappe/views/reports/query_report.js:1979
#: public/js/frappe/views/reports/query_report.js:1964
#: public/js/frappe/views/treeview.js:121
msgid "Collapse All"
msgstr "Fäll In Alla"
@ -6362,6 +6365,10 @@ msgstr "Översikt Panel Namn"
msgid "Dashboard Settings"
msgstr "Översikt Panel Inställningar"
#: public/js/frappe/list/base_list.js:205
msgid "Dashboard View"
msgstr "Översikt Panel Vy"
#. Label of the tab_break_2 (Tab Break) field in DocType 'Workspace'
#: desk/doctype/workspace/workspace.json
msgid "Dashboards"
@ -6943,7 +6950,8 @@ msgid "Department"
msgstr "Avdelning"
#. Label of the dependencies (Data) field in DocType 'Workspace Link'
#: desk/doctype/workspace_link/workspace_link.json www/attribution.html:29
#: desk/doctype/workspace_link/workspace_link.json
#: public/js/frappe/widgets/widget_dialog.js:318 www/attribution.html:29
msgid "Dependencies"
msgstr "Beroenden "
@ -7405,6 +7413,7 @@ msgstr "Status för följande tillstånd är ändrad:<br><strong>{0}</strong><br
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: integrations/doctype/webhook/webhook.json
#: printing/doctype/print_format/print_format.json
#: public/js/frappe/widgets/widget_dialog.js:164
#: website/doctype/website_slideshow/website_slideshow.js:18
msgid "DocType"
msgstr "DocType"
@ -7463,6 +7472,7 @@ msgstr "DocType Tillstånd"
#. Label of the doc_view (Select) field in DocType 'Workspace Shortcut'
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: public/js/frappe/widgets/widget_dialog.js:477
msgid "DocType View"
msgstr "DocType Vy"
@ -7986,7 +7996,7 @@ msgstr "Ladda ner Mall"
msgid "Download Your Data"
msgstr "Ladda ner Data"
#: contacts/doctype/contact/contact.js:93
#: contacts/doctype/contact/contact.js:98
msgid "Download vCard"
msgstr "Ladda ner vCard"
@ -8174,7 +8184,7 @@ msgstr "Varje dokument skapad i System kan ha unikt ID genererad för det, med d
#: public/js/frappe/form/templates/contact_list.html:7
#: public/js/frappe/form/toolbar.js:681
#: public/js/frappe/views/reports/query_report.js:815
#: public/js/frappe/views/reports/query_report.js:1649
#: public/js/frappe/views/reports/query_report.js:1634
#: public/js/frappe/views/workspace/workspace.js:460
#: public/js/frappe/views/workspace/workspace.js:816
#: public/js/frappe/widgets/base_widget.js:64
@ -9152,9 +9162,9 @@ msgstr "Fel i Klient Skript."
msgid "Error in Header/Footer Script"
msgstr "Fel i Sidhuvud/Sidfot Skript"
#: email/doctype/notification/notification.py:394
#: email/doctype/notification/notification.py:510
#: email/doctype/notification/notification.py:516
#: email/doctype/notification/notification.py:395
#: email/doctype/notification/notification.py:511
#: email/doctype/notification/notification.py:517
msgid "Error in Notification"
msgstr "Fel i Avisering"
@ -9166,7 +9176,7 @@ msgstr "Fel i Utskrift Format på rad {0}: {1}"
msgid "Error while connecting to email account {0}"
msgstr "Fel vid anslutning till E-post Konto {0}"
#: email/doctype/notification/notification.py:507
#: email/doctype/notification/notification.py:508
msgid "Error while evaluating Notification {0}. Please fix your template."
msgstr "Fel vid test av Avisering {0}. Fixa Mall."
@ -9324,7 +9334,7 @@ msgstr "Kör Konsol Skript"
msgid "Executing..."
msgstr "Kör..."
#: public/js/frappe/views/reports/query_report.js:1993
#: public/js/frappe/views/reports/query_report.js:1978
msgid "Execution Time: {0} sec"
msgstr "Exekvering Tid: {0} sek"
@ -9342,7 +9352,7 @@ msgctxt "Enlarge code field."
msgid "Expand"
msgstr "Expandera"
#: public/js/frappe/views/reports/query_report.js:1979
#: public/js/frappe/views/reports/query_report.js:1964
#: public/js/frappe/views/treeview.js:125
msgid "Expand All"
msgstr "Expandera Alla"
@ -9398,7 +9408,7 @@ msgstr "Förfallo Tid för QR Kod Bild Sida"
#: core/doctype/docperm/docperm.json core/doctype/recorder/recorder_list.js:37
#: public/js/frappe/data_import/data_exporter.js:91
#: public/js/frappe/data_import/data_exporter.js:242
#: public/js/frappe/views/reports/query_report.js:1684
#: public/js/frappe/views/reports/query_report.js:1669
#: public/js/frappe/views/reports/report_view.js:1550
msgid "Export"
msgstr "Export"
@ -9739,7 +9749,7 @@ msgstr "Hämtar standard Global Sökning dokument."
#: public/js/frappe/list/bulk_operations.js:297
#: public/js/frappe/list/list_view_permission_restrictions.html:3
#: public/js/frappe/views/reports/query_report.js:236
#: public/js/frappe/views/reports/query_report.js:1738
#: public/js/frappe/views/reports/query_report.js:1723
#: website/doctype/web_form_field/web_form_field.json
#: website/doctype/web_form_list_column/web_form_list_column.json
msgid "Field"
@ -10047,7 +10057,7 @@ msgstr "Filer"
#: desk/doctype/number_card/number_card.js:205
#: desk/doctype/number_card/number_card.js:336
#: email/doctype/auto_email_report/auto_email_report.js:90
#: public/js/frappe/list/base_list.js:882
#: public/js/frappe/list/base_list.js:890
#: public/js/frappe/ui/filters/filter_list.js:134
#: website/doctype/web_form/web_form.js:197
msgid "Filter"
@ -10483,7 +10493,7 @@ msgstr "För Användare"
msgid "For Value"
msgstr "För Värde"
#: public/js/frappe/views/reports/query_report.js:1990
#: public/js/frappe/views/reports/query_report.js:1975
#: public/js/frappe/views/reports/report_view.js:96
msgid "For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10)."
msgstr "För jämförelse, använd >5, <10 eller = 324. För intervall, använd 5:10 (för värden mellan 5 och 10)."
@ -10730,7 +10740,7 @@ msgstr "Från Datum"
msgid "From Date Field"
msgstr "Från Datum"
#: public/js/frappe/views/reports/query_report.js:1704
#: public/js/frappe/views/reports/query_report.js:1689
msgid "From Document Type"
msgstr "Från DocType"
@ -10826,6 +10836,10 @@ msgstr "GNU General Public License"
msgid "Gantt"
msgstr "Gantt"
#: public/js/frappe/list/base_list.js:206
msgid "Gantt View"
msgstr "Gantt Vy"
#. Label of the gender (Link) field in DocType 'Contact'
#. Name of a DocType
#. Label of the gender (Data) field in DocType 'Gender'
@ -12197,6 +12211,10 @@ msgstr "Bild Höjd"
msgid "Image Link"
msgstr "Bild Länk"
#: public/js/frappe/list/base_list.js:209
msgid "Image View"
msgstr "Visa Bild"
#. Label of the image_width (Float) field in DocType 'Letter Head'
#. Label of the footer_image_width (Float) field in DocType 'Letter Head'
#: printing/doctype/letter_head/letter_head.json
@ -12468,6 +12486,10 @@ msgstr "Inkorg"
msgid "Inbox User"
msgstr "Inkorg Användare"
#: public/js/frappe/list/base_list.js:210
msgid "Inbox View"
msgstr "Inkorg Vy"
#. Label of the include_name_field (Check) field in DocType 'Form Tour'
#: desk/doctype/form_tour/form_tour.json
msgid "Include Name Field"
@ -12487,11 +12509,11 @@ msgstr "Inkludera Tema från Appar"
msgid "Include Web View Link in Email"
msgstr "Inkludera Länk till Webbvy i E-post"
#: public/js/frappe/views/reports/query_report.js:1521
#: public/js/frappe/views/reports/query_report.js:1506
msgid "Include filters"
msgstr "Inkludera Filter"
#: public/js/frappe/views/reports/query_report.js:1513
#: public/js/frappe/views/reports/query_report.js:1498
msgid "Include indentation"
msgstr "Inkludera Fördjupning"
@ -12647,7 +12669,7 @@ msgstr "Infoga \tOvan"
#. Label of the insert_after (Select) field in DocType 'Custom Field'
#: custom/doctype/custom_field/custom_field.json
#: public/js/frappe/views/reports/query_report.js:1744
#: public/js/frappe/views/reports/query_report.js:1729
msgid "Insert After"
msgstr "Infoga Efter"
@ -12854,7 +12876,7 @@ msgstr "Ogiltig uttryck 'beroende på' i filter {0}"
msgid "Invalid \"mandatory_depends_on\" expression"
msgstr "Ogiltigt uttryck för \"obligatoriskt_beror_på\""
#: utils/nestedset.py:177
#: utils/nestedset.py:178
msgid "Invalid Action"
msgstr "Ogiltig åtgärd"
@ -13048,7 +13070,7 @@ msgstr "Ogiltiga begäran argument"
#: integrations/doctype/connected_app/connected_app.py:173
msgid "Invalid state."
msgstr "Ogiltig status."
msgstr "Ogiltigt tillstånd."
#: core/doctype/data_import/importer.py:423
msgid "Invalid template file for import"
@ -13225,6 +13247,7 @@ msgstr "Är Publicerad Fält måste vara giltig Fält Namn"
#. Label of the is_query_report (Check) field in DocType 'Workspace Link'
#: desk/doctype/workspace_link/workspace_link.json
#: public/js/frappe/widgets/widget_dialog.js:336
msgid "Is Query Report"
msgstr "Är Dataförfråga Rapport"
@ -13328,7 +13351,7 @@ msgstr "Artikel Titel"
msgid "Item Type"
msgstr "Artikel Typ"
#: utils/nestedset.py:228
#: utils/nestedset.py:229
msgid "Item cannot be added to its own descendants"
msgstr "Artikel kan inte läggas till egna undertyper"
@ -13453,6 +13476,7 @@ msgstr "Anslag Tavla"
#. Label of the kanban_board (Link) field in DocType 'Workspace Shortcut'
#: desk/doctype/kanban_board/kanban_board.json
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: public/js/frappe/widgets/widget_dialog.js:509
msgid "Kanban Board"
msgstr "Anslag Tavla"
@ -13472,6 +13496,10 @@ msgctxt "Button in kanban view menu"
msgid "Kanban Settings"
msgstr "Anslag Tavla Inställningar"
#: public/js/frappe/list/base_list.js:207
msgid "Kanban View"
msgstr "Anslag Tavla Vy"
#. Description of a DocType
#: core/doctype/activity_log/activity_log.json
msgid "Keep track of all update feeds"
@ -13717,7 +13745,10 @@ msgstr "LDAP Inställningar felaktiga. validering svar var: {0}"
#: desk/doctype/workspace_quick_list/workspace_quick_list.json
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: printing/page/print_format_builder/print_format_builder.js:474
#: public/js/frappe/widgets/widget_dialog.js:187
#: public/js/frappe/widgets/widget_dialog.js:255
#: public/js/frappe/widgets/widget_dialog.js:304
#: public/js/frappe/widgets/widget_dialog.js:421
#: public/js/frappe/widgets/widget_dialog.js:645
#: public/js/frappe/widgets/widget_dialog.js:678
#: templates/form_grid/fields.html:37
@ -13848,7 +13879,7 @@ msgstr "Senaste Tilldelning Datum"
#. Option for the 'Timespan' (Select) field in DocType 'Dashboard Chart'
#: desk/doctype/dashboard_chart/dashboard_chart.json
msgid "Last Quarter"
msgstr "Sista Kvartal"
msgstr "Förra Kvartal"
#. Label of the last_reset_password_key_generated_on (Datetime) field in
#. DocType 'User'
@ -14284,6 +14315,8 @@ msgstr "Länk Titel"
#. Label of the link_to (Dynamic Link) field in DocType 'Workspace Shortcut'
#: desk/doctype/workspace_link/workspace_link.json
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: public/js/frappe/widgets/widget_dialog.js:285
#: public/js/frappe/widgets/widget_dialog.js:430
msgid "Link To"
msgstr "Länk Till"
@ -14293,6 +14326,7 @@ msgstr "Länk Till i Rad"
#. Label of the link_type (Select) field in DocType 'Workspace Link'
#: desk/doctype/workspace_link/workspace_link.json
#: public/js/frappe/widgets/widget_dialog.js:277
msgid "Link Type"
msgstr "Länk Typ"
@ -14336,7 +14370,7 @@ msgstr "Länkad Med"
#. Label of the links (Table) field in DocType 'Customize Form'
#. Label of the links (Table) field in DocType 'Workspace'
#: contacts/doctype/address/address.js:39 contacts/doctype/address/address.json
#: contacts/doctype/contact/contact.js:87 contacts/doctype/contact/contact.json
#: contacts/doctype/contact/contact.js:92 contacts/doctype/contact/contact.json
#: core/doctype/doctype/doctype.json
#: custom/doctype/customize_form/customize_form.json
#: desk/doctype/workspace/workspace.json public/js/frappe/form/toolbar.js:377
@ -14389,6 +14423,10 @@ msgctxt "Button in list view menu"
msgid "List Settings"
msgstr "Lista Inställningar"
#: public/js/frappe/list/base_list.js:203
msgid "List View"
msgstr "List Vy"
#. Name of a DocType
#: desk/doctype/list_view_settings/list_view_settings.json
msgid "List View Settings"
@ -14425,7 +14463,7 @@ msgstr "Listor"
msgid "Load Balancing"
msgstr "Last Balansering"
#: public/js/frappe/list/base_list.js:378
#: public/js/frappe/list/base_list.js:386
#: website/doctype/blog_post/templates/blog_post_list.html:50
#: website/doctype/help_article/templates/help_article_list.html:30
msgid "Load More"
@ -14439,7 +14477,7 @@ msgstr "Ladda Mer Korenspondens"
#: core/page/permission_manager/permission_manager.js:165
#: public/js/frappe/form/controls/multicheck.js:13
#: public/js/frappe/form/linked_with.js:13
#: public/js/frappe/list/base_list.js:490
#: public/js/frappe/list/base_list.js:498
#: public/js/frappe/list/list_view.js:335 public/js/frappe/ui/listing.html:16
#: public/js/frappe/views/reports/query_report.js:1017
msgid "Loading"
@ -14467,7 +14505,7 @@ msgstr "Laddar versioner..."
#: public/js/frappe/views/kanban/kanban_board.html:11
#: public/js/frappe/widgets/chart_widget.js:50
#: public/js/frappe/widgets/number_card_widget.js:174
#: public/js/frappe/widgets/quick_list_widget.js:126
#: public/js/frappe/widgets/quick_list_widget.js:128
msgid "Loading..."
msgstr "Laddar..."
@ -14852,6 +14890,10 @@ msgstr "Mapp"
msgid "Map Columns"
msgstr "Mappa Kolumner"
#: public/js/frappe/list/base_list.js:212
msgid "Map View"
msgstr "Mapp Vy"
#: public/js/frappe/data_import/import_preview.js:290
msgid "Map columns from {0} to fields in {1}"
msgstr "Mappa Kolumner från {0} till fält i {1}"
@ -15081,7 +15123,7 @@ msgstr "Meny"
msgid "Merge with existing"
msgstr "Slå samman med befintlig"
#: utils/nestedset.py:304
#: utils/nestedset.py:307
msgid "Merging is only possible between Group-to-Group or Leaf Node-to-Leaf Node"
msgstr "Sammanslafning är endast möjlig mellan grupp till grupp eller underordnad till underordnad"
@ -15237,6 +15279,10 @@ msgstr "Meta Titel för SEO"
msgid "Method"
msgstr "Sätt"
#: __init__.py:936
msgid "Method Not Allowed"
msgstr "Metod ej Tillåten"
#: desk/doctype/number_card/number_card.py:70
msgid "Method is required to create a number card"
msgstr "Sätt erfodras för att skapa nummerkort"
@ -15628,7 +15674,7 @@ msgstr "Flytta till nästa steg när du klickar i det markerade området."
msgid "Mozilla doesn't support :has() so you can pass parent selector here as workaround"
msgstr "Mozilla stöder inte :has() så du kan skicka överordnad väljare här som lösning"
#: utils/nestedset.py:328
#: utils/nestedset.py:331
msgid "Multiple root nodes not allowed."
msgstr "Flera rot noder är inte tillåtna."
@ -15866,7 +15912,7 @@ msgstr "Arbetsyta Ansvarig roll erfodras för att dölja/visa publika arbetsyto
msgid "Negative Value"
msgstr "Negativ Värde"
#: utils/nestedset.py:93
#: utils/nestedset.py:94
msgid "Nested set error. Please contact the Administrator."
msgstr "Nested set fel. Kontakta Administratör."
@ -16074,7 +16120,7 @@ msgstr "Ny {0}: {1}"
#: utils/change_log.py:373
msgid "New {} releases for the following apps are available"
msgstr "Nya {} utgåvor för följande appar finns tillgängliga"
msgstr "Nya {} versioner för följande appar finns tillgängliga"
#: core/doctype/user/user.py:753
msgid "Newly created user {0} has no roles enabled."
@ -16213,7 +16259,7 @@ msgstr "Nästa på Klick"
#: public/js/form_builder/utils.js:341
#: public/js/frappe/form/controls/link.js:475
#: public/js/frappe/list/list_sidebar_group_by.js:223
#: public/js/frappe/views/reports/query_report.js:1541
#: public/js/frappe/views/reports/query_report.js:1526
#: website/doctype/help_article/templates/help_article.html:26
msgid "No"
msgstr "Nej"
@ -16257,7 +16303,7 @@ msgstr "Ingen Data"
msgid "No Data to Show"
msgstr "Ingen Data att visa"
#: public/js/frappe/widgets/quick_list_widget.js:131
#: public/js/frappe/widgets/quick_list_widget.js:133
msgid "No Data..."
msgstr "Ingen Data..."
@ -16724,7 +16770,7 @@ msgstr "Inte Aktiv"
msgid "Not allowed for {0}: {1}"
msgstr "Ej tillåtet för {0}: {1}"
#: email/doctype/notification/notification.py:391
#: email/doctype/notification/notification.py:392
msgid "Not allowed to attach {0} document, please enable Allow Print For {0} in Print Settings"
msgstr "Ej Tillåtet att bifoga {0} dokument, aktivera \"Tillåt Utskrift\" för {0} i Utskrift Inställningar"
@ -16843,7 +16889,7 @@ msgstr "Inget mer att göra om"
msgid "Nothing left to undo"
msgstr "Inget mer att ångra"
#: public/js/frappe/list/base_list.js:362
#: public/js/frappe/list/base_list.js:370
#: public/js/frappe/views/reports/query_report.js:105
#: templates/includes/list/list.html:7
#: website/doctype/blog_post/templates/blog_post_list.html:41
@ -17047,7 +17093,7 @@ msgstr "Antal dagar efter vilka dokument Webb Vy länk delad i e-post kommer att
#. Label of the cache_keys (Int) field in DocType 'System Health Report'
#: desk/doctype/system_health_report/system_health_report.json
msgid "Number of keys"
msgstr "Antal nycklar"
msgstr "Antal Nycklar"
#. Label of the onsite_backups (Int) field in DocType 'System Health Report'
#: desk/doctype/system_health_report/system_health_report.json
@ -17249,9 +17295,14 @@ msgstr "{0}, {1} skrev"
#. Label of the onboard (Check) field in DocType 'Workspace Link'
#: desk/doctype/workspace_link/workspace_link.json
#: public/js/frappe/widgets/widget_dialog.js:330
msgid "Onboard"
msgstr "Introduktion"
#: public/js/frappe/widgets/widget_dialog.js:236
msgid "Onboarding Name"
msgstr "Introduktion Namn"
#. Name of a DocType
#: desk/doctype/onboarding_permission/onboarding_permission.json
msgid "Onboarding Permission"
@ -17359,6 +17410,7 @@ msgstr "Endast utkast dokument kan förkastas"
#. Label of the only_for (Link) field in DocType 'Workspace Link'
#: desk/doctype/workspace_link/workspace_link.json
#: public/js/frappe/widgets/widget_dialog.js:323
msgid "Only for"
msgstr "Endast för"
@ -17658,7 +17710,7 @@ msgstr "Utgående (SMTP) Inställningar"
#. Health Report'
#: desk/doctype/system_health_report/system_health_report.json
msgid "Outgoing Emails (Last 7 days)"
msgstr "Utgående E-post (sista 7 dagar)"
msgstr "Utgående E-post (Senaste 7 dagar)"
#. Label of the smtp_server (Data) field in DocType 'Email Account'
#. Label of the smtp_server (Data) field in DocType 'Email Domain'
@ -17708,7 +17760,7 @@ msgstr "PATCH"
#: printing/page/print/print.js:71
#: public/js/frappe/form/templates/print_layout.html:44
#: public/js/frappe/views/reports/query_report.js:1669
#: public/js/frappe/views/reports/query_report.js:1654
msgid "PDF"
msgstr "PDF"
@ -18100,8 +18152,8 @@ msgid "Password not found for {0} {1} {2}"
msgstr "Lösenord hittades inte för {0} {1} {2}"
#: core/doctype/user/user.py:1021
msgid "Password reset instructions have been sent to your email"
msgstr "Lösenord Återställning instruktioner har skickats till din E-post"
msgid "Password reset instructions have been sent to {}'s email"
msgstr "Instruktioner för återställning av lösenord är skickade till {}'s e-post"
#: www/update-password.html:164
msgid "Password set"
@ -18198,24 +18250,24 @@ msgstr "Pågående"
#. Request'
#: website/doctype/personal_data_deletion_request/personal_data_deletion_request.json
msgid "Pending Approval"
msgstr "Pågående Godkännande"
msgstr "Godkännande"
#. Label of the pending_emails (Int) field in DocType 'System Health Report'
#: desk/doctype/system_health_report/system_health_report.json
msgid "Pending Emails"
msgstr "Pågående E-post"
msgstr "E-post"
#. Label of the pending_jobs (Int) field in DocType 'System Health Report
#. Queue'
#: desk/doctype/system_health_report_queue/system_health_report_queue.json
msgid "Pending Jobs"
msgstr "Pågående Jobb"
msgstr "Jobb"
#. Option for the 'Status' (Select) field in DocType 'Personal Data Deletion
#. Request'
#: website/doctype/personal_data_deletion_request/personal_data_deletion_request.json
msgid "Pending Verification"
msgstr "Pågående Verifiering"
msgstr "Verifiering"
#. Option for the 'Type' (Select) field in DocType 'DocField'
#. Option for the 'Field Type' (Select) field in DocType 'Custom Field'
@ -18500,7 +18552,7 @@ msgstr "Kontrollera OpenID Configuration URL"
msgid "Please check the filter values set for Dashboard Chart: {}"
msgstr "Kontrollera filter värden angivna för Översikt Panel Diagram: {}"
#: model/base_document.py:872
#: model/base_document.py:873
msgid "Please check the value of \"Fetch From\" set for field {0}"
msgstr "Kontrollera värde för uppsättning 'Hämta från' för fält {0}"
@ -18802,11 +18854,11 @@ msgstr "Specificera"
msgid "Please specify a valid parent DocType for {0}"
msgstr "Ange giltig överordnad DocType för {0}"
#: email/doctype/notification/notification.py:87
#: email/doctype/notification/notification.py:88
msgid "Please specify which date field must be checked"
msgstr "Ange Datum Fält som måste kontrolleras"
#: email/doctype/notification/notification.py:90
#: email/doctype/notification/notification.py:91
msgid "Please specify which value field must be checked"
msgstr "Ange Värde Fält som måste kontrolleras"
@ -19139,7 +19191,7 @@ msgstr "Primär nyckel för doctype {0} kan inte ändras eftersom det finns befi
#: public/js/frappe/form/templates/print_layout.html:46
#: public/js/frappe/form/toolbar.js:332 public/js/frappe/form/toolbar.js:344
#: public/js/frappe/list/bulk_operations.js:87
#: public/js/frappe/views/reports/query_report.js:1655
#: public/js/frappe/views/reports/query_report.js:1640
#: public/js/frappe/views/reports/report_view.js:1460
#: public/js/frappe/views/treeview.js:469 www/printview.html:18
msgid "Print"
@ -20001,7 +20053,7 @@ msgstr "Uppdatera"
msgid "Rebuild Tree"
msgstr "Uppdatera Träd Vy"
#: utils/nestedset.py:176
#: utils/nestedset.py:177
msgid "Rebuilding of tree is not supported for {}"
msgstr "Uppdatering av Träd Vy stöds inte för {}"
@ -20359,7 +20411,7 @@ msgstr "Referens"
#: public/js/frappe/desk.js:533 public/js/frappe/form/form.js:1196
#: public/js/frappe/form/templates/print_layout.html:6
#: public/js/frappe/list/base_list.js:66
#: public/js/frappe/views/reports/query_report.js:1644
#: public/js/frappe/views/reports/query_report.js:1629
#: public/js/frappe/views/treeview.js:475
#: public/js/frappe/widgets/chart_widget.js:290
#: public/js/frappe/widgets/number_card_widget.js:324
@ -20457,7 +20509,7 @@ msgstr "Ladda om"
msgid "Reload File"
msgstr "Ladda om Fil"
#: public/js/frappe/list/base_list.js:242
#: public/js/frappe/list/base_list.js:250
msgid "Reload List"
msgstr "Ladda om Lista"
@ -20726,7 +20778,7 @@ msgstr "Rapport Ansvarig"
#: core/doctype/report/report.json
#: desk/doctype/dashboard_chart/dashboard_chart.json
#: desk/doctype/number_card/number_card.json
#: public/js/frappe/views/reports/query_report.js:1825
#: public/js/frappe/views/reports/query_report.js:1810
msgid "Report Name"
msgstr "Rapport Namn"
@ -20749,6 +20801,10 @@ msgstr "Rapportera Referens DocType"
msgid "Report Type"
msgstr "Rapport Typ"
#: public/js/frappe/list/base_list.js:204
msgid "Report View"
msgstr "Rapport Vy"
#: core/doctype/doctype/doctype.py:1780
msgid "Report cannot be set for Single types"
msgstr "Rapport kan inte anges för Enskilda Typer"
@ -20783,7 +20839,7 @@ msgstr "Rapport är uppdaterad"
msgid "Report was not saved (there were errors)"
msgstr "Rapport är inte sparad (det fanns fel)"
#: public/js/frappe/views/reports/query_report.js:1863
#: public/js/frappe/views/reports/query_report.js:1848
msgid "Report with more than 10 columns looks better in Landscape mode."
msgstr "Rapport med mer än 10 kolumner ser bättre ut i Liggande Läge."
@ -21369,7 +21425,7 @@ msgstr "Roller HTML"
msgid "Roles can be set for users from their User page."
msgstr "Roller kan anges för användare från deras Användarsida."
#: utils/nestedset.py:277
#: utils/nestedset.py:280
msgid "Root {0} cannot be deleted"
msgstr "Root {0} kan inte raderas"
@ -21442,7 +21498,7 @@ msgstr "Rad #"
msgid "Row # {0}: Non administrator user can not set the role {1} to the custom doctype"
msgstr "Rad # {0}: Användare som inte är administratör kan inte ange roll {1} till anpassad Dokument Typ"
#: model/base_document.py:903
#: model/base_document.py:904
msgid "Row #{0}:"
msgstr "Rad # {0}:"
@ -21726,11 +21782,11 @@ msgstr "Lördag"
#: public/js/frappe/views/kanban/kanban_settings.js:45
#: public/js/frappe/views/kanban/kanban_settings.js:189
#: public/js/frappe/views/kanban/kanban_view.js:343
#: public/js/frappe/views/reports/query_report.js:1817
#: public/js/frappe/views/reports/query_report.js:1802
#: public/js/frappe/views/reports/report_view.js:1640
#: public/js/frappe/views/workspace/workspace.js:501
#: public/js/frappe/widgets/base_widget.js:142
#: public/js/frappe/widgets/quick_list_widget.js:117
#: public/js/frappe/widgets/quick_list_widget.js:119
#: public/js/print_format_builder/print_format_builder.bundle.js:15
#: public/js/workflow_builder/workflow_builder.bundle.js:33
msgid "Save"
@ -21757,7 +21813,7 @@ msgstr "Spara Anpassningar"
msgid "Save Filter"
msgstr "Spara Filter "
#: public/js/frappe/views/reports/query_report.js:1820
#: public/js/frappe/views/reports/query_report.js:1805
msgid "Save Report"
msgstr "Spara Rapport"
@ -22881,7 +22937,7 @@ msgid "Set Filters"
msgstr "Ange Filter"
#: public/js/frappe/widgets/chart_widget.js:395
#: public/js/frappe/widgets/quick_list_widget.js:102
#: public/js/frappe/widgets/quick_list_widget.js:104
msgid "Set Filters for {0}"
msgstr "Ange Filter för {0}"
@ -23118,7 +23174,7 @@ msgstr "Inställningar > Användar Behörigheter"
msgid "Setup Approval Workflows"
msgstr "Ange Arbetsflöde för Godkännande "
#: public/js/frappe/views/reports/query_report.js:1690
#: public/js/frappe/views/reports/query_report.js:1675
#: public/js/frappe/views/reports/report_view.js:1618
msgid "Setup Auto Email"
msgstr "Automatisk E-post Rapport"
@ -25154,7 +25210,7 @@ msgid "The Client ID obtained from the Google Cloud Console under <a href=\"http
"</a>"
msgstr "Klient ID som erhållits från Google Cloud Console under <a href=\"https://console.cloud.google.com/apis/credentials\">\"API och tjänster\" &gt; \"Inloggningsuppgifter\"</a>"
#: email/doctype/notification/notification.py:130
#: email/doctype/notification/notification.py:131
msgid "The Condition '{0}' is invalid"
msgstr "Villkor '{0}' är ogiltig"
@ -25613,7 +25669,7 @@ msgstr "Detta format används om land specifika format inte hittas"
msgid "This goes above the slideshow."
msgstr "Text ovanför Bildspel."
#: public/js/frappe/views/reports/query_report.js:2027
#: public/js/frappe/views/reports/query_report.js:2012
msgid "This is a background report. Please set the appropriate filters and then generate a new one."
msgstr "Detta är bakgrund rapport. Ange lämplig filter och skapa ny rapport."
@ -26284,16 +26340,16 @@ msgstr "Totalt"
#. Report'
#: desk/doctype/system_health_report/system_health_report.json
msgid "Total Background Workers"
msgstr "Totalt Bakgrund Tjänster"
msgstr "Totalt Antal Bakgrund Tjänster"
#. Label of the total_errors (Int) field in DocType 'System Health Report'
#: desk/doctype/system_health_report/system_health_report.json
msgid "Total Errors (last 1 day)"
msgstr "Totalt Fel (sista dagen)"
msgstr "Totalt Antal Fel (Senaste dag)"
#: public/js/frappe/ui/capture.js:259
msgid "Total Images"
msgstr "Totalt Bilder"
msgstr "Totalt Antal Bilder"
#. Label of the total_outgoing_emails (Int) field in DocType 'System Health
#. Report'
@ -26322,18 +26378,18 @@ msgstr "Totalt Användare"
#. Label of the total_views (Int) field in DocType 'Newsletter'
#: email/doctype/newsletter/newsletter.json
msgid "Total Views"
msgstr "Totalt Visningar"
msgstr "Totalt Antal Visningar"
#. Label of the total_working_time (Duration) field in DocType 'RQ Worker'
#: core/doctype/rq_worker/rq_worker.json
msgid "Total Working Time"
msgstr "Totalt Antal Arbetstimmar"
msgstr "Totalt Antal Arbetade Timmar"
#. Description of the 'Initial Sync Count' (Select) field in DocType 'Email
#. Account'
#: email/doctype/email_account/email_account.json
msgid "Total number of emails to sync in initial sync process "
msgstr "Totalt antal E-post meddelande som ska synkroniseras i första synkronisering behandling"
msgstr "Totalt antal E-post meddelande som ska synkroniseras i första synkronisering "
#: public/js/frappe/views/reports/report_view.js:1178
msgid "Totals"
@ -26341,7 +26397,7 @@ msgstr "Totals"
#: public/js/frappe/views/reports/report_view.js:1153
msgid "Totals Row"
msgstr "Totalt Rad"
msgstr "Totalt Antal Rader"
#. Label of the trace_id (Data) field in DocType 'Error Log'
#: core/doctype/error_log/error_log.json
@ -26478,6 +26534,10 @@ msgstr "Skräp"
msgid "Tree"
msgstr "Träd Vy"
#: public/js/frappe/list/base_list.js:211
msgid "Tree View"
msgstr "Träd Vy"
#. Description of the 'Is Tree' (Check) field in DocType 'DocType'
#: core/doctype/doctype/doctype.json
msgid "Tree structures are implemented using Nested Set"
@ -26583,6 +26643,7 @@ msgstr "Två Faktor Autentisering Sätt"
#: desk/doctype/workspace_link/workspace_link.json
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: public/js/frappe/views/file/file_view.js:337
#: public/js/frappe/widgets/widget_dialog.js:399
#: social/doctype/energy_point_log/energy_point_log.json
#: website/doctype/web_template/web_template.json www/attribution.html:35
msgid "Type"
@ -26665,6 +26726,7 @@ msgstr "URI för att ta emot behörighet kod när Användare tillåter tillgång
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
#: integrations/doctype/integration_request/integration_request.json
#: integrations/doctype/webhook_request_log/webhook_request_log.json
#: public/js/frappe/widgets/widget_dialog.js:469
#: website/doctype/top_bar_item/top_bar_item.json
#: website/doctype/website_slideshow_item/website_slideshow_item.json
msgid "URL"
@ -27341,7 +27403,7 @@ msgid "User Permission"
msgstr "Användare Behörighet"
#: core/page/permission_manager/permission_manager_help.html:30
#: public/js/frappe/views/reports/query_report.js:1804
#: public/js/frappe/views/reports/query_report.js:1789
#: public/js/frappe/views/reports/report_view.js:1666
msgid "User Permissions"
msgstr "Användare Behörigheter"
@ -27659,7 +27721,7 @@ msgstr "Värde Ändrad"
msgid "Value To Be Set"
msgstr "Värde som ska Anges"
#: model/base_document.py:965 model/document.py:682
#: model/base_document.py:966 model/document.py:682
msgid "Value cannot be changed for {0}"
msgstr "Värde kan inte ändras för {0}"
@ -27702,7 +27764,7 @@ msgstr "Värde måste vara ett av {0}"
msgid "Value to Validate"
msgstr "Värde att Validera"
#: model/base_document.py:1035
#: model/base_document.py:1036
msgid "Value too big"
msgstr "Värde för hög"
@ -27801,7 +27863,7 @@ msgid "View Full Log"
msgstr "Visa Full Logg"
#: public/js/frappe/views/treeview.js:463
#: public/js/frappe/widgets/quick_list_widget.js:245
#: public/js/frappe/widgets/quick_list_widget.js:247
msgid "View List"
msgstr "Visa Lista"
@ -28569,7 +28631,7 @@ msgstr "Arbetsflöde Tillstånd inte angiven"
#: model/workflow.py:197 model/workflow.py:205
msgid "Workflow State transition not allowed from {0} to {1}"
msgstr "Arbetsflöde Övergång inte tillåten från {0} till {1}"
msgstr "Arbetsflöde Tillstånd Övergång inte tillåten från {0} till {1}"
#: model/workflow.py:320
msgid "Workflow Status"
@ -28683,7 +28745,7 @@ msgstr "Slutför"
msgid "Write"
msgstr "Skriva"
#: model/base_document.py:875
#: model/base_document.py:876
msgid "Wrong Fetch From value"
msgstr "Fel Hämtning Från Värde"
@ -28770,7 +28832,7 @@ msgstr "Gul"
#: public/js/form_builder/utils.js:336
#: public/js/frappe/form/controls/link.js:475
#: public/js/frappe/list/list_sidebar_group_by.js:223
#: public/js/frappe/views/reports/query_report.js:1541
#: public/js/frappe/views/reports/query_report.js:1526
#: website/doctype/help_article/templates/help_article.html:25
msgid "Yes"
msgstr "Ja"
@ -29983,7 +30045,7 @@ msgstr "via Data Import"
msgid "via Google Meet"
msgstr "via Google Meet"
#: email/doctype/notification/notification.py:215
#: email/doctype/notification/notification.py:216
msgid "via Notification"
msgstr "via Avisering"
@ -30126,7 +30188,7 @@ msgstr "{0} Moduler"
msgid "{0} Name"
msgstr "{0} Namn"
#: model/base_document.py:1065
#: model/base_document.py:1066
msgid "{0} Not allowed to change {1} after submission from {2} to {3}"
msgstr "{0} Ej Tillåtet att ändra {1} efter godkännande från {2} till {3}"
@ -30152,10 +30214,6 @@ msgstr "{0} Inställningar"
msgid "{0} Tree"
msgstr "{0} Träd Vy"
#: public/js/frappe/list/base_list.js:209
msgid "{0} View"
msgstr "{0} Vy"
#: public/js/frappe/form/footer/form_timeline.js:126
#: public/js/frappe/form/sidebar/form_sidebar.js:86
msgid "{0} Web page views"
@ -30595,7 +30653,7 @@ msgstr "{0} måste börja och sluta med bokstav och får bara innehålla bokstä
#: workflow/doctype/workflow/workflow.py:91
msgid "{0} not a valid State"
msgstr "{0} inte en giltig Tillstånd"
msgstr "{0} är inte giltig Tillstånd"
#: model/rename_doc.py:380
msgid "{0} not allowed to be renamed"
@ -30770,11 +30828,11 @@ msgstr "{0} {1} är lagd till i Översikt Panel {2}"
msgid "{0} {1} already exists"
msgstr "{0} {1} finns redan"
#: model/base_document.py:908
#: model/base_document.py:909
msgid "{0} {1} cannot be \"{2}\". It should be one of \"{3}\""
msgstr "{0} {1} kan inte vara \"{2}\". Det kan vara en av följande: \"{3}\""
#: utils/nestedset.py:337
#: utils/nestedset.py:340
msgid "{0} {1} cannot be a leaf node as it has children"
msgstr "{0} {1} kan inte vara undernod då den har undernoder"
@ -30794,11 +30852,11 @@ msgstr "{0} {1} hittades inte"
msgid "{0} {1}: Submitted Record cannot be deleted. You must {2} Cancel {3} it first."
msgstr "{0} {1}: Godkänd Post kan inte raderas. Du måste {2} Annullera {3} det först."
#: model/base_document.py:1026
#: model/base_document.py:1027
msgid "{0}, Row {1}"
msgstr "{0}, Rad {1}"
#: model/base_document.py:1031
#: model/base_document.py:1032
msgid "{0}: '{1}' ({3}) will get truncated, as max characters allowed is {2}"
msgstr "{0}: {1} ({3}) kommer att förminskas då maximum tillåtna antal tecken är {2}"
@ -30891,7 +30949,7 @@ msgid "{0}: fieldname cannot be set to reserved keyword {1}"
msgstr "{0}: fältnamn kan inte anges för reserverad sökord {1}"
#: contacts/doctype/address/address.js:35
#: contacts/doctype/contact/contact.js:83
#: contacts/doctype/contact/contact.js:88
#: public/js/frappe/views/workspace/workspace.js:170
msgid "{0}: {1}"
msgstr "{0}: {1}"

View file

@ -127,8 +127,17 @@ def delete_doc(
# check if links exist
if not force:
check_if_doc_is_linked(doc)
check_if_doc_is_dynamically_linked(doc)
try:
check_if_doc_is_linked(doc)
check_if_doc_is_dynamically_linked(doc)
except frappe.LinkExistsError as e:
if doc.meta.has_field("enabled") or doc.meta.has_field("disabled"):
frappe.throw(
_("You can disable this {0} instead of deleting it.").format(_(doctype)),
frappe.LinkExistsError,
)
else:
raise e
update_naming_series(doc)
delete_from_table(doctype, name, ignore_doctypes, doc)

View file

@ -238,7 +238,7 @@ class Document(BaseDocument):
def raise_no_permission_to(self, perm_type):
"""Raise `frappe.PermissionError`."""
frappe.flags.error_message = (
_("Insufficient Permission for {0}").format(self.doctype) + f" ({frappe.bold(_(perm_type))})"
_("Insufficient Permission for {0}").format(_(self.doctype)) + f" ({frappe.bold(_(perm_type))})"
)
raise frappe.PermissionError
@ -458,7 +458,7 @@ class Document(BaseDocument):
d: Document
d.db_update()
def get_doc_before_save(self) -> "Document":
def get_doc_before_save(self) -> "Self":
return getattr(self, "_doc_before_save", None)
def has_value_changed(self, fieldname):
@ -1031,7 +1031,7 @@ class Document(BaseDocument):
"on_cancel": "Cancel",
}
if not self.flags.in_insert:
if not self.flags.in_insert and not self.flags.in_delete:
# value change is not applicable in insert
event_map["on_change"] = "Value Change"

View file

@ -419,7 +419,7 @@
</symbol>
<symbol fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" id="icon-map">
<g stroke="#111" stroke-miterlimit="10">
<g stroke="var(--icon-stroke)" stroke-miterlimit="10">
<path d="M11.467 3.458c1.958 1.957 1.958 5.088.027 7.02L7.97 14l-3.523-3.523a4.945 4.945 0 010-6.993l.026-.026a4.922 4.922 0 016.993 0zm0 0c-.026-.026-.026-.026 0 0z"></path>
<path d="M7.971 8.259a1.305 1.305 0 100-2.61 1.305 1.305 0 000 2.61z"></path>
</g>

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View file

@ -63,7 +63,7 @@ let field_df = computedAsync(async () => {
watch(
() => props.value,
(value) => {
[doctype.value, fieldname.value] = value?.split(".") || ["", ""];
if (value) [doctype.value, fieldname.value] = value.split(".") || ["", ""];
},
{ immediate: true }
);

View file

@ -67,6 +67,7 @@ frappe.data_import.DataExporter = class DataExporter {
columns: 2,
on_change: () => this.update_primary_action(),
options: this.get_multicheck_options(this.doctype),
sort_options: false,
},
...frappe.meta.get_table_fields(this.doctype).map((df) => {
let doctype = df.options;

View file

@ -7,10 +7,11 @@ frappe.ui.form.ControlData = class ControlData extends frappe.ui.form.ControlInp
make_input() {
if (this.$input) return;
let { html_element, input_type } = this.constructor;
let { html_element, input_type, input_mode } = this.constructor;
this.$input = $("<" + html_element + ">")
.attr("type", input_type)
.attr("inputmode", input_mode)
.attr("autocomplete", "off")
.addClass("input-with-feedback form-control")
.prependTo(this.input_area);

View file

@ -4,6 +4,13 @@ frappe.ui.form.ControlDuration = class ControlDuration extends frappe.ui.form.Co
this.make_picker();
}
validate(value) {
if (!value) {
return null;
}
return super.validate(value);
}
make_picker() {
this.inputs = [];
this.set_duration_options();

View file

@ -1,5 +1,6 @@
frappe.ui.form.ControlInt = class ControlInt extends frappe.ui.form.ControlData {
static trigger_change_on_input_event = false;
static input_mode = "numeric";
make() {
super.make();
}

View file

@ -255,7 +255,7 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
doctype: doctype,
ignore_user_permissions: me.df.ignore_user_permissions,
reference_doctype: me.get_reference_doctype() || "",
page_length: cint(frappe.boot.sysdefaults.link_field_results_limit) || 10,
page_length: cint(frappe.boot.sysdefaults?.link_field_results_limit) || 10,
};
me.set_custom_query(args);

View file

@ -139,7 +139,7 @@ frappe.ui.form.ControlMultiSelectList = class ControlMultiSelectList extends (
//Unselect old values
this.values.forEach((value) => {
this.$list_wrapper
.find(`.selectable-item[data-value=${value}]`)
.find(`.selectable-item[data-value=${CSS.escape(value)}]`)
.toggleClass("selected");
});
this.values = value;
@ -147,7 +147,7 @@ frappe.ui.form.ControlMultiSelectList = class ControlMultiSelectList extends (
this.update_selected_values(value);
//Select new values
this.$list_wrapper
.find(`.selectable-item[data-value=${value}]`)
.find(`.selectable-item[data-value=${CSS.escape(value)}]`)
.toggleClass("selected");
});
this.parse_validate_and_set_in_model("");

View file

@ -633,8 +633,8 @@ frappe.ui.form.Dashboard = class FormDashboard {
}
// TODO: Review! code related to headline should be the part of layout/form
set_headline(html, color) {
this.frm.layout.show_message(html, color);
set_headline(html, color, permanent = false) {
this.frm.layout.show_message(html, color, permanent);
}
clear_headline() {
@ -642,7 +642,7 @@ frappe.ui.form.Dashboard = class FormDashboard {
}
add_comment(text, alert_class, permanent) {
this.set_headline_alert(text, alert_class);
this.set_headline_alert(text, alert_class, permanent);
if (!permanent) {
setTimeout(() => {
this.clear_headline();
@ -654,9 +654,9 @@ frappe.ui.form.Dashboard = class FormDashboard {
this.clear_headline();
}
set_headline_alert(text, color) {
set_headline_alert(text, color, permanent = false) {
if (text) {
this.set_headline(`<div>${text}</div>`, color);
this.set_headline(`<div>${text}</div>`, color, permanent);
} else {
this.clear_headline();
}

View file

@ -550,7 +550,6 @@ frappe.ui.form.Form = class FrappeForm {
}
trigger_onload(switched) {
this.cscript.is_onload = false;
if (!this.opendocs[this.docname]) {
var me = this;
this.cscript.is_onload = true;
@ -628,6 +627,7 @@ frappe.ui.form.Form = class FrappeForm {
() => this.cscript.is_onload && this.is_new() && this.focus_on_first_input(),
() => this.run_after_load_hook(),
() => this.dashboard.after_refresh(),
() => (this.cscript.is_onload = false),
]);
} else {
this.refresh_header(switched);

View file

@ -11,8 +11,10 @@ frappe.ui.form.FormTour = class FormTour {
padding: 10,
overlayClickNext: true,
keyboardControl: true,
nextBtnText: "Next",
prevBtnText: "Previous",
nextBtnText: __("Next"),
prevBtnText: __("Previous"),
doneBtnText: __("Done"),
closeBtnText: __("Close"),
opacity: 0.25,
onHighlighted: (step) => {
// if last step is to save, then attach a listener to save button
@ -135,7 +137,11 @@ frappe.ui.form.FormTour = class FormTour {
return {
element,
name,
popover: { title, description, position: frappe.router.slug(position || "Bottom") },
popover: {
title: __(title),
description: __(description),
position: frappe.router.slug(position || "Bottom"),
},
onNext: on_next,
onPrevious: on_prev,
};

View file

@ -97,7 +97,7 @@ frappe.ui.form.Layout = class Layout {
return fields;
}
show_message(html, color) {
show_message(html, color, permanent = false) {
if (this.message_color) {
// remove previous color
this.message.removeClass(this.message_color);
@ -112,8 +112,10 @@ frappe.ui.form.Layout = class Layout {
}
this.message.removeClass("hidden").addClass(this.message_color);
$(html).appendTo(this.message);
close_message.appendTo(this.message);
close_message.on("click", () => this.message.empty().addClass("hidden"));
if (!permanent) {
close_message.appendTo(this.message);
close_message.on("click", () => this.message.empty().addClass("hidden"));
}
} else {
this.message.empty().addClass("hidden");
}

View file

@ -260,12 +260,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
set_primary_action() {
if (this.can_create && !frappe.boot.read_only) {
const doctype_name = __(frappe.router.doctype_layout) || __(this.doctype);
// Better style would be __("Add {0}", [doctype_name], "Primary action in list view")
// Keeping it like this to not disrupt existing translations
const label = `${__("Add", null, "Primary action in list view")} ${doctype_name}`;
this.page.set_primary_action(
label,
__("Add {0}", [doctype_name], "Primary action in list view"),
() => {
if (this.settings.primary_action) {
this.settings.primary_action();
@ -315,6 +311,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.update_checkbox();
this.update_url_with_filters();
this.setup_realtime_updates();
this.apply_styles_basedon_dropdown();
});
}
@ -547,7 +544,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
toggle_result_area() {
super.toggle_result_area();
this.toggle_actions_menu_button(this.$result.find(".list-row-check:checked").length > 0);
this.toggle_actions_menu_button(
this.$result.find(".list-row-checkbox:checked").length > 0
);
}
toggle_actions_menu_button(toggle) {
@ -787,9 +786,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
const format = () => {
if (df.fieldtype === "Code") {
return value;
} else if (df.fieldtype === "Percent") {
if (df.fieldtype === "Percent") {
return `<div class="progress" style="margin: 0px;">
<div class="progress-bar progress-bar-success" role="progressbar"
aria-valuenow="${value}"
@ -841,11 +838,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
data-filter="${fieldname},=,${value}">
${_value}
</a>`;
} else if (
["Text Editor", "Text", "Small Text", "HTML Editor", "Markdown Editor"].includes(
df.fieldtype
)
) {
} else if (frappe.model.html_fieldtypes.includes(df.fieldtype)) {
html = `<span class="ellipsis">
${_value}
</span>`;
@ -915,8 +908,10 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
get_meta_html(doc) {
let html = "";
let settings_button = "";
let button_section = "";
const dropdown_button = this.generate_dropdown_html(doc);
let settings_button = null;
if (this.settings.button && this.settings.button.show(doc)) {
settings_button = `
<span class="list-actions">
@ -929,6 +924,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
`;
}
button_section = settings_button + dropdown_button;
const modified = comment_when(doc.modified, true);
let assigned_to = ``;
@ -950,13 +946,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
html += `
<div class="level-item list-row-activity hidden-xs">
<div class="hidden-md hidden-xs">
${settings_button || assigned_to}
<div class="hidden-md hidden-xs d-flex">
${button_section || assigned_to}
</div>
<span class="modified">${modified}</span>
${comment_count || ""}
${comment_count ? '<span class="mx-2">·</span>' : ""}
<span class="list-row-like hidden-xs style="margin-bottom: 1px;">
<span class="list-row-like hidden-xs" style="margin-bottom: 1px;">
${this.get_like_html(doc)}
</span>
</div>
@ -968,6 +964,47 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
return html;
}
generate_dropdown_html(doc) {
let dropdown_button = "";
if (this.settings.dropdown_button) {
let button_actions = "";
this.settings.dropdown_button.buttons.forEach((button, index) => {
if (!button.show || button.show(doc)) {
let description = button.get_description ? button.get_description(doc) : "";
button_actions += `
<a class="dropdown-item" href="#" onclick="return false;" data-idx="${doc._idx}" button-idx="${index}" title="${description}">
${button.get_label}
</a>
`;
}
});
if (button_actions) {
dropdown_button = `
<div class="inner-group-button mr-2" data-name="${doc.name}" data-label="${
this.settings.dropdown_button.get_label
}">
<button type="button" class="btn btn-xs btn-default ellipsis" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
${this.settings.dropdown_button.get_label}
${frappe.utils.icon("select", "xs")}
</button>
<div role="menu" class="dropdown-menu">${button_actions}</div>
</div>
`;
}
}
return dropdown_button;
}
apply_styles_basedon_dropdown() {
if ($(".list-actions").length > 0 && $(".inner-group-button").length > 0) {
$(".list-row .level-left, .list-row-head .level-left").css({
flex: "2",
"min-width": "72%",
});
}
}
get_count_str() {
let current_count = this.data.length;
let count_without_children = this.data.uniqBy((d) => d.name).length;
@ -1277,6 +1314,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.on_row_checked();
return;
}
if ($target.is("[data-toggle='dropdown']")) return true;
// don't open form when checkbox, like, filterable are clicked
if (
$target.hasClass("filterable") ||
@ -1342,6 +1382,20 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
e.stopPropagation();
return false;
});
this.$result.on("click", ".inner-group-button .dropdown-item", (e) => {
const $button = $(e.currentTarget);
const doc = this.data[$button.attr("data-idx")];
const btn_idx = parseInt($button.attr("button-idx"), 10);
const button = this.settings.dropdown_button.buttons[btn_idx];
if (button && button.action) {
button.action(doc);
}
e.stopPropagation();
return false;
});
}
setup_check_events() {

View file

@ -97,7 +97,7 @@ frappe.get_indicator = function (doc, doctype, show_workflow_state) {
// based on status
if (doc.status) {
return [__(doc.status), frappe.utils.guess_colour(doc.status)];
return [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
}
// based on enabled

View file

@ -33,7 +33,7 @@ frappe.ui.RealtimeChart = class RealtimeChart extends frappe.Chart {
} else {
this.currentSize++;
}
this.addDataPoint(label, data);
this.addDataPoint(__(label), data);
};
}
};

View file

@ -228,7 +228,7 @@ frappe.msgprint = function (msg, title, is_minimizable) {
}
frappe.msg_dialog.set_primary_action(
__(data.primary_action.label || data.primary_action_label || "Done"),
__(data.primary_action.label) || __(data.primary_action_label) || __("Done"),
data.primary_action.action
);
} else {
@ -240,7 +240,9 @@ frappe.msgprint = function (msg, title, is_minimizable) {
if (data.secondary_action) {
frappe.msg_dialog.set_secondary_action(data.secondary_action.action);
frappe.msg_dialog.set_secondary_action_label(__(data.secondary_action.label || "Close"));
frappe.msg_dialog.set_secondary_action_label(
__(data.secondary_action.label) || __("Close")
);
}
if (data.message == null) {

View file

@ -160,9 +160,9 @@ frappe.dashboard_utils = {
fieldtype: "HTML",
fieldname: "description",
options: `<div>
<p>Set dynamic filter values in JavaScript for the required fields here.
<p>${__("Set dynamic filter values in JavaScript for the required fields here.")}
</p>
<p>Ex:
<p>${__("For example:")}
<code>frappe.defaults.get_user_default("Company")</code>
</p>
</div>`,

View file

@ -1330,14 +1330,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
raise && this.toggle_message(false);
return this.filters
.filter((f) => {
const filter_value = f.get_value();
if (typeof filter_value === "object") {
return filter_value.length > 0;
} else {
return filter_value;
}
})
.filter((f) => f.get_value())
.map((f) => {
var v = f.get_value();
// hidden fields dont have $input
@ -1480,7 +1473,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
get_filters_html_for_print() {
const applied_filters = this.get_filter_values();
const filter_html = Object.keys(applied_filters)
return Object.keys(applied_filters)
.map((fieldname) => {
const docfield = frappe.query_report.get_filter(fieldname).df;
const value = applied_filters[fieldname];
@ -1489,14 +1482,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
</div>`;
})
.join("");
return `<div>${filter_html}</div>
<style>
.filter-row div {
/* prevent newline + right alignment of number fields */
display: inline-block;
}
</style>`;
}
export_report() {

View file

@ -125,7 +125,7 @@ export default class ChartWidget extends Widget {
if (this.chart_doc.type == "Heatmap") {
filters = [
{
label: this.chart_settings.heatmap_year || this.chart_doc.heatmap_year,
label: __(this.chart_settings.heatmap_year) || __(this.chart_doc.heatmap_year),
options: frappe.dashboard_utils.get_years_since_creation(
frappe.boot.user.creation
),
@ -141,7 +141,8 @@ export default class ChartWidget extends Widget {
} else {
filters = [
{
label: this.chart_settings.time_interval || this.chart_doc.time_interval,
label:
__(this.chart_settings.time_interval) || __(this.chart_doc.time_interval),
options: ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily"],
icon: "calendar",
class: "time-interval-filter",
@ -233,7 +234,7 @@ export default class ChartWidget extends Widget {
df: {
fieldtype: "DateRange",
fieldname: "from_date",
placeholder: "Date Range",
placeholder: __("Date Range"),
input_class: "input-xs",
default: [this.chart_settings.from_date, this.chart_settings.to_date],
value: [this.chart_settings.from_date, this.chart_settings.to_date],
@ -314,7 +315,7 @@ export default class ChartWidget extends Widget {
if (this.chart_doc.document_type) {
actions.push({
label: __("{0} List", [this.chart_doc.document_type]),
label: __("{0} List", [__(this.chart_doc.document_type)]),
action: "action-list",
handler: () => {
frappe.set_route("List", this.chart_doc.document_type);
@ -322,7 +323,7 @@ export default class ChartWidget extends Widget {
});
} else if (this.chart_doc.chart_type === "Report") {
actions.push({
label: __("{0} Report", [this.chart_doc.report_name]),
label: __("{0} Report", [__(this.chart_doc.report_name)]),
action: "action-list",
handler: () => {
frappe.set_route("query-report", this.chart_doc.report_name, this.filters);
@ -392,7 +393,7 @@ export default class ChartWidget extends Widget {
setup_filter_dialog(fields) {
let me = this;
let dialog = new frappe.ui.Dialog({
title: __("Set Filters for {0}", [this.chart_doc.chart_name]),
title: __("Set Filters for {0}", [__(this.chart_doc.chart_name)]),
fields: fields,
primary_action: function () {
let values = this.get_values();
@ -403,7 +404,7 @@ export default class ChartWidget extends Widget {
me.fetch_and_update_chart();
}
},
primary_action_label: "Set",
primary_action_label: __("Set"),
});
dialog.show();
@ -473,7 +474,9 @@ export default class ChartWidget extends Widget {
${actions
.map(
(action) =>
`<li><a class="dropdown-item" data-action="${action.action}">${action.label}</a></li>`
`<li><a class="dropdown-item" data-action="${action.action}">${__(
action.label
)}</a></li>`
)
.join("")}
</ul>

View file

@ -129,9 +129,9 @@ export default class OnboardingWidget extends Widget {
.on("click", toggle_video);
} else {
$(
`<button class="btn btn-default btn-sm">${__(
step.action_label || step.action
)}</button>`
`<button class="btn btn-default btn-sm">${
__(step.action_label) || __(step.action)
}</button>`
)
.appendTo(this.step_footer)
.on("click", () => actions[step.action](step));
@ -139,8 +139,8 @@ export default class OnboardingWidget extends Widget {
};
const set_description = () => {
let content = step.description
? frappe.markdown(step.description)
let content = __(step.description)
? frappe.markdown(__(step.description))
: `<h1>${__(step.title)}</h1>`;
if (step.action === "Create Entry") {
@ -168,9 +168,9 @@ export default class OnboardingWidget extends Widget {
});
$(
`<button class="btn btn-primary btn-sm">${__(
step.action_label || step.action
)}</button>`
`<button class="btn btn-primary btn-sm">${
__(step.action_label) || __(step.action)
}</button>`
)
.appendTo(this.step_footer)
.on("click", () => {

View file

@ -46,3 +46,8 @@
overflow-wrap: anywhere;
}
}
/* prevent newline and right alignment of number fields in printed report filters */
.filter-row div {
display: inline-block;
}

View file

@ -5,4 +5,7 @@
<a class="btn {% if action.is_primary %} btn-primary {% endif %}" style="margin-right: 10px" href="{{ action.action_link }}">{{_(action.action_name)}}</a>
{% endfor %}
</p>
<div class="text-muted text-small" style="padding-top: 20px;">
{{ _("The contents of this email are strictly confidential. Please do not forward this email to anyone.") }}
</div>
</div>

View file

@ -14,7 +14,7 @@
{% endfor %}
<li itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem" class="breadcrumb-item active" aria-current="page">
<span itemprop="item">
<span itemprop="name">{{ title }}</span>
<span itemprop="name">{{ _(title) }}</span>
<meta itemprop="position" content="{{ count }}"/>
</span>
</li>

View file

@ -329,7 +329,7 @@ var request_otp = function (r) {
$('.login-content:visible').append(
`<div id="twofactor_div">
<form class="form-verify">
<div class="page-card-head">
<div class="page-card-head p-0">
<span class="indicator blue" data-text="Verification">{{ _("Verification") | e }}</span>
</div>
<div id="otp_div"></div>

View file

@ -153,7 +153,7 @@ data-fieldname="{{ df.fieldname }}" data-fieldtype="{{ df.fieldtype }}"
{% elif df.fieldtype=="Signature" %}
<img src="{{ doc[df.fieldname] }}" class="signature-img img-responsive"
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}>
{% elif df.fieldtype in ("Attach", "Attach Image") %}
{% elif df.fieldtype in ("Attach", "Attach Image") and frappe.utils.is_image(doc[df.fieldname]) %}
<img src="{{ doc[df.fieldname] }}" class="img-responsive"
{%- if df.print_width %} style="width: {{ get_width(df) }};"{% endif %}>
{% elif df.fieldtype=="HTML" %}

View file

@ -1080,7 +1080,6 @@ class TestSqlIterator(FrappeTestCase):
with frappe.db.unbuffered_cursor():
self.test_db_sql_iterator()
class ExtFrappeTestCase(FrappeTestCase):
def assertSqlException(self):
class SqlExceptionContextManager:
@ -1259,3 +1258,88 @@ class TestPostgresSchemaQueryIndependence(ExtFrappeTestCase):
self.assertEqual(rows, []) # there are no records in the alt_schema table
del frappe.conf["db_schema"]
class TestDbConnectWithEnvCredentials(FrappeTestCase):
current_site = frappe.local.site
def tearDown(self):
frappe.init(self.current_site, force=True)
frappe.connect()
def test_connect_fails_with_wrong_credentials_by_env(self) -> None:
import contextlib
import os
import re
@contextlib.contextmanager
def set_env_variable(key, value):
if orig_value_set := key in os.environ:
orig_value = os.environ.get(key)
os.environ[key] = value
try:
yield
finally:
if orig_value_set:
os.environ[key] = orig_value
else:
del os.environ[key]
# with wrong db name
with set_env_variable("FRAPPE_DB_NAME", "dbiq"):
frappe.init(self.current_site, force=True)
frappe.connect()
with self.assertRaises(Exception) as cm:
frappe.db.connect()
self.assertTrue(re.search(r"database [\"']dbiq[\"']", str(cm.exception)))
# with wrong host
with set_env_variable("FRAPPE_DB_HOST", "iqx.local"):
frappe.init(self.current_site, force=True)
frappe.connect()
with self.assertRaises(Exception) as cm:
frappe.db.connect()
self.assertTrue(re.search(r"(host name|server on) [\"']iqx.local[\"']", str(cm.exception)))
# with wrong user name
with set_env_variable("FRAPPE_DB_USER", "uname"):
frappe.init(self.current_site, force=True)
frappe.connect()
with self.assertRaises(Exception) as cm:
frappe.db.connect()
self.assertTrue(re.search(r"user [\"']uname[\"']", str(cm.exception)))
# with wrong password
with set_env_variable("FRAPPE_DB_PASSWORD", "pass"):
frappe.init(self.current_site, force=True)
frappe.connect()
with self.assertRaises(Exception) as cm:
frappe.db.connect()
self.assertTrue(
re.search(r"(password authentication failed|Access denied for)", str(cm.exception))
)
# with wrong password
with set_env_variable("FRAPPE_DB_PORT", "1111"):
frappe.init(self.current_site, force=True)
frappe.connect()
with self.assertRaises(Exception) as cm:
frappe.db.connect()
self.assertTrue(re.search("(port 1111 failed|Errno 111)", str(cm.exception)))
# now with configured settings without any influences from env
# finally connect should work without any error (when no wrong credentials are given via ENV)
frappe.init(self.current_site, force=True)
frappe.connect()
frappe.db.connect()

View file

@ -344,30 +344,14 @@ def send_token_via_email(user, token, otp_secret, otp_issuer, subject=None, mess
hotp = pyotp.HOTP(otp_secret)
otp = hotp.at(int(token))
template_args = {"otp": otp, "otp_issuer": otp_issuer}
if not subject:
subject = get_email_subject_for_2fa(template_args)
if not message:
message = get_email_body_for_2fa(template_args)
email_args = {
"recipients": user_email,
"sender": None,
"subject": subject,
"message": message,
"header": [_("Verfication Code"), "blue"],
"delayed": False,
"retry": 3,
}
enqueue(
method=frappe.sendmail,
queue="short",
timeout=300,
event=None,
is_async=True,
job_name=None,
now=False,
**email_args,
frappe.sendmail(
recipients=user_email,
subject=subject or get_email_subject_for_2fa(template_args),
message=message or get_email_body_for_2fa(template_args),
header=[_("Verfication Code"), "blue"],
delayed=False,
retry=3,
)
return True

View file

@ -163,7 +163,7 @@ def enqueue(
def enqueue_call():
return q.enqueue_call(
execute_job,
"frappe.utils.background_jobs.execute_job",
on_success=Callback(func=on_success) if on_success else None,
on_failure=Callback(func=on_failure) if on_failure else None,
timeout=timeout,

View file

@ -2,6 +2,7 @@
# License: MIT. See LICENSE
import pickle
import re
from contextlib import suppress
import redis
from redis.commands.search import Search
@ -62,14 +63,8 @@ class RedisWrapper(redis.Redis):
if not expires_in_sec:
frappe.local.cache[key] = val
try:
if expires_in_sec:
self.setex(name=key, time=expires_in_sec, value=pickle.dumps(val))
else:
self.set(key, pickle.dumps(val))
except redis.exceptions.ConnectionError:
return None
with suppress(redis.exceptions.ConnectionError):
self.set(name=key, value=pickle.dumps(val), ex=expires_in_sec)
def get_value(self, key, generator=None, user=None, expires=False, shared=False):
"""Return cache value. If not found and generator function is

View file

@ -80,7 +80,7 @@ dependencies = [
"markdownify~=0.11.6",
# integration dependencies
"boto3~=1.28.10",
"boto3~=1.34.143",
"dropbox~=11.36.2",
"google-api-python-client~=2.2.0",
"google-auth-oauthlib~=0.4.4",