{{ __("Exact Copies") }}
diff --git a/frappe/public/js/frappe/roles_editor.js b/frappe/public/js/frappe/roles_editor.js
index efdba7d4d8..312a90a82c 100644
--- a/frappe/public/js/frappe/roles_editor.js
+++ b/frappe/public/js/frappe/roles_editor.js
@@ -59,7 +59,7 @@ frappe.RoleEditor = class {
const $body = $(this.perm_dialog.body);
if (!permissions.length) {
$body.append(`
- ${__("{0} role does not have permission on any doctype", [role])}
+ ${__("{0} role does not have permission on any doctype", [__(role)])}
`);
} else {
$body.append(`
@@ -68,7 +68,7 @@ frappe.RoleEditor = class {
| ${__("Document Type")} |
${__("Level")} |
- ${frappe.perm.rights.map((p) => ` ${frappe.unscrub(p)} | `).join("")}
+ ${frappe.perm.rights.map((p) => ` ${__(frappe.unscrub(p))} | `).join("")}
@@ -77,7 +77,7 @@ frappe.RoleEditor = class {
permissions.forEach((perm) => {
$body.find("tbody").append(`
- | ${perm.parent} |
+ ${__(perm.parent)} |
${perm.permlevel} |
${frappe.perm.rights
.map(
@@ -91,7 +91,7 @@ frappe.RoleEditor = class {
`);
});
}
- this.perm_dialog.set_title(role);
+ this.perm_dialog.set_title(__(role));
this.perm_dialog.show();
});
}
@@ -102,8 +102,10 @@ frappe.RoleEditor = class {
this.perm_dialog.$wrapper
.find(".modal-dialog")
- .css("width", "1200px")
- .css("max-width", "80vw");
+ .css("width", "auto")
+ .css("max-width", "1200px");
+
+ this.perm_dialog.$wrapper.find(".modal-body").css("overflow", "overlay");
}
show() {
this.reset();
diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js
index f6301085d7..c03bfc9b95 100644
--- a/frappe/public/js/frappe/router.js
+++ b/frappe/public/js/frappe/router.js
@@ -203,7 +203,7 @@ frappe.router = {
? meta.default_view
: null
);
- } else if (route[1] && route[1] !== "view" && !route[2]) {
+ } else if (route[1] && route[1] !== "view") {
let docname = route[1];
if (route.length > 2) {
docname = route.slice(1).join("/");
diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js
index 3119ee4b68..4691e05792 100644
--- a/frappe/public/js/frappe/ui/filters/filter_list.js
+++ b/frappe/public/js/frappe/ui/filters/filter_list.js
@@ -14,9 +14,31 @@ frappe.ui.FilterGroup = class {
make_popover() {
this.init_filter_popover();
+ this.set_clear_all_filters_event();
this.set_popover_events();
}
+ set_clear_all_filters_event() {
+ if (!this.filter_x_button) return;
+
+ this.filter_x_button.on("click", () => {
+ this.toggle_empty_filters(true);
+ if (typeof this.base_list !== "undefined") {
+ // It's a list view. Clear all the filters, also the ones in the
+ // FilterArea outside this FilterGroup
+ this.base_list.filter_area.clear();
+ } else {
+ // Not a list view, just clear the filters in this FilterGroup
+ this.clear_filters();
+ }
+ this.update_filter_button();
+ });
+ }
+
+ hide_popover() {
+ this.filter_button.popover("hide");
+ }
+
init_filter_popover() {
this.filter_button.popover({
content: this.get_filter_area_template(),
@@ -54,7 +76,7 @@ frappe.ui.FilterGroup = class {
!$(e.target).is(this.filter_button) &&
!in_datepicker
) {
- this.wrapper && this.filter_button.popover("hide");
+ this.wrapper && this.hide_popover();
}
}
});
@@ -85,7 +107,7 @@ frappe.ui.FilterGroup = class {
// REDESIGN-TODO: (Temporary) Review and find best solution for this
frappe.router.on("change", () => {
if (this.wrapper && this.wrapper.is(":visible")) {
- this.filter_button.popover("hide");
+ this.hide_popover();
}
});
}
@@ -130,11 +152,10 @@ frappe.ui.FilterGroup = class {
this.toggle_empty_filters(true);
this.clear_filters();
this.on_change();
+ this.hide_popover();
});
- this.wrapper.find(".apply-filters").on("click", () => {
- this.filter_button.popover("hide");
- });
+ this.wrapper.find(".apply-filters").on("click", () => this.hide_popover());
}
add_filters(filters) {
diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js
index bef0c19b4e..8e8db5b211 100644
--- a/frappe/public/js/frappe/ui/toolbar/toolbar.js
+++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js
@@ -137,11 +137,13 @@ frappe.ui.toolbar.Toolbar = class {
__("Generate Tracking URL")
);
- if (frappe.perm.has_perm("RQ Job")) {
- frappe.search.utils.make_function_searchable(function () {
- frappe.set_route("List", "RQ Job");
- }, __("Background Jobs"));
- }
+ frappe.model.with_doctype("RQ Job").then(() => {
+ if (frappe.perm.has_perm("RQ Job", 0, "read")) {
+ frappe.search.utils.make_function_searchable(function () {
+ frappe.set_route("List", "RQ Job");
+ }, __("Background Jobs"));
+ }
+ });
}
}
diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js
index 7230de9cf5..3505199d3f 100755
--- a/frappe/public/js/frappe/views/communication.js
+++ b/frappe/public/js/frappe/views/communication.js
@@ -339,7 +339,7 @@ frappe.views.CommunicationComposer = class {
await this.dialog.set_value(fieldname, this[fieldname] || "");
}
- const subject = frappe.utils.html2text(this.subject) || "";
+ const subject = this.subject ? frappe.utils.html2text(this.subject) : "";
await this.dialog.set_value("subject", subject);
await this.set_values_from_last_edited_communication();
diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js
index ee255032bb..9f81f9f6f1 100644
--- a/frappe/public/js/frappe/views/reports/query_report.js
+++ b/frappe/public/js/frappe/views/reports/query_report.js
@@ -1424,9 +1424,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
const applied_filters = this.get_filter_values();
return Object.keys(applied_filters)
.map((fieldname) => {
- const label = frappe.query_report.get_filter(fieldname).df.label;
+ const docfield = frappe.query_report.get_filter(fieldname).df;
const value = applied_filters[fieldname];
- return `${__(label)}: ${value}
`;
+ return `${__(docfield.label)}: ${frappe.format(value, docfield)}
`;
})
.join("");
}
diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js
index 83411f0ddf..5e76d61c09 100644
--- a/frappe/public/js/frappe/views/reports/report_view.js
+++ b/frappe/public/js/frappe/views/reports/report_view.js
@@ -1349,9 +1349,8 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
.map((f) => {
const [doctype, fieldname, condition, value] = f;
if (condition !== "=") return "";
-
- const label = frappe.meta.get_label(doctype, fieldname);
- return `${__(label)}: ${value}
`;
+ const docfield = frappe.meta.get_docfield(doctype, fieldname);
+ return `${__(docfield.label)}: ${frappe.format(value, docfield)}
`;
})
.join("");
}
diff --git a/frappe/public/js/frappe/views/workspace/workspace.js b/frappe/public/js/frappe/views/workspace/workspace.js
index af02629038..029197d6bd 100644
--- a/frappe/public/js/frappe/views/workspace/workspace.js
+++ b/frappe/public/js/frappe/views/workspace/workspace.js
@@ -158,7 +158,7 @@ frappe.views.Workspace = class Workspace {
}
if (
- sidebar_section.find("sidebar-item-container").length &&
+ sidebar_section.find(".sidebar-item-container").length &&
sidebar_section.find("> [item-is-hidden='0']").length == 0
) {
sidebar_section.addClass("hidden show-in-edit-mode");
diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js
index d82f63a035..7d340b04e8 100644
--- a/frappe/public/js/frappe/widgets/shortcut_widget.js
+++ b/frappe/public/js/frappe/widgets/shortcut_widget.js
@@ -20,6 +20,7 @@ export default class ShortcutWidget extends Widget {
restrict_to_domain: this.restrict_to_domain,
stats_filter: this.stats_filter,
type: this.type,
+ url: this.url,
};
}
@@ -45,6 +46,16 @@ export default class ShortcutWidget extends Widget {
frappe.open_in_new_tab = true;
}
+ if (this.type == "URL") {
+ if (frappe.open_in_new_tab) {
+ window.open(this.url, "_blank");
+ frappe.open_in_new_tab = false;
+ } else {
+ window.location.href = this.url;
+ }
+ return;
+ }
+
frappe.set_route(route);
});
}
diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js
index 45f7e5fc47..db402211df 100644
--- a/frappe/public/js/frappe/widgets/widget_dialog.js
+++ b/frappe/public/js/frappe/widgets/widget_dialog.js
@@ -350,7 +350,7 @@ class ShortcutDialog extends WidgetDialog {
fieldname: "type",
label: "Type",
reqd: 1,
- options: "DocType\nReport\nPage\nDashboard",
+ options: "DocType\nReport\nPage\nDashboard\nURL",
onchange: () => {
if (this.dialog.get_value("type") == "DocType") {
this.dialog.fields_dict.link_to.get_query = () => {
@@ -379,7 +379,6 @@ class ShortcutDialog extends WidgetDialog {
fieldtype: "Dynamic Link",
fieldname: "link_to",
label: "Link To",
- reqd: 1,
options: "type",
onchange: () => {
const doctype = this.dialog.get_value("link_to");
@@ -404,6 +403,17 @@ class ShortcutDialog extends WidgetDialog {
this.hide_filters();
}
},
+ depends_on: (s) => s.type != "URL",
+ mandatory_depends_on: (s) => s.type != "URL",
+ },
+ {
+ fieldtype: "Data",
+ fieldname: "url",
+ label: "URL",
+ options: "URL",
+ default: "",
+ depends_on: (s) => s.type == "URL",
+ mandatory_depends_on: (s) => s.type == "URL",
},
{
fieldtype: "Select",
@@ -500,6 +510,19 @@ class ShortcutDialog extends WidgetDialog {
data.label = data.label ? data.label : frappe.model.unscrub(data.link_to);
+ if (data.url) {
+ !validate_url(data.url) &&
+ frappe.throw({
+ message: __("{0} is not a valid URL", [data.url]),
+ title: __("Invalid URL"),
+ indicator: "red",
+ });
+
+ if (!data.label) {
+ data.label = "No Label (URL)";
+ }
+ }
+
return data;
}
}
diff --git a/frappe/public/js/print_format_builder/Preview.vue b/frappe/public/js/print_format_builder/Preview.vue
index 1603711846..377ec92b6d 100644
--- a/frappe/public/js/print_format_builder/Preview.vue
+++ b/frappe/public/js/print_format_builder/Preview.vue
@@ -130,7 +130,7 @@ onMounted(() => {
margin-top: auto;
margin-bottom: 1.2rem;
}
-.preview-control >>> .form-control {
+.preview-control :deep(.form-control) {
background: var(--control-bg-on-gray);
}
diff --git a/frappe/public/js/print_format_builder/PrintFormatControls.vue b/frappe/public/js/print_format_builder/PrintFormatControls.vue
index 7d38148d5e..eac5fbe9f6 100644
--- a/frappe/public/js/print_format_builder/PrintFormatControls.vue
+++ b/frappe/public/js/print_format_builder/PrintFormatControls.vue
@@ -332,7 +332,7 @@ watch(print_format, () => (store.dirty.value = true), { deep: true });
margin-bottom: 0;
}
-.control-font >>> .frappe-control[data-fieldname="font"] label {
+.control-font :deep(.frappe-control[data-fieldname="font"] label) {
display: none;
}
diff --git a/frappe/public/scss/desk/filters.scss b/frappe/public/scss/desk/filters.scss
index ffaea7a9bd..3f197e8278 100644
--- a/frappe/public/scss/desk/filters.scss
+++ b/frappe/public/scss/desk/filters.scss
@@ -1,7 +1,5 @@
.filter-icon.active {
- use {
- stroke: var(--text-on-blue);
- }
+ --icon-stroke: var(--text-on-blue);
}
.filter-popover {
diff --git a/frappe/public/scss/desk/list.scss b/frappe/public/scss/desk/list.scss
index 30bf1d6499..d464e6dec1 100644
--- a/frappe/public/scss/desk/list.scss
+++ b/frappe/public/scss/desk/list.scss
@@ -378,13 +378,14 @@ input.list-check-all {
padding: 0 var(--padding-xs);
}
- .filter-button {
- margin: 5px;
- // padding: 4px 8px;
+ .filter-selector .btn-group {
+ margin: var(--margin-xs);
}
.filter-button.btn-primary-light {
color: var(--text-on-blue);
+ outline: 1px solid var(--bg-dark-blue);
+ z-index: 1;
}
.sort-selector {
diff --git a/frappe/realtime.py b/frappe/realtime.py
index 4a7cce0f45..e6980ef917 100644
--- a/frappe/realtime.py
+++ b/frappe/realtime.py
@@ -126,7 +126,7 @@ def can_subscribe_doctype(doctype: str) -> bool:
def get_user_info():
return {
"user": frappe.session.user,
- "user_type": frappe.session.user_type,
+ "user_type": frappe.session.data.user_type,
}
diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv
index 834281bbab..dae676ee72 100644
--- a/frappe/translations/de.csv
+++ b/frappe/translations/de.csv
@@ -2290,8 +2290,8 @@ Setup Auto Email,Einstellungen Auto E-Mail,
Setup Complete,Einrichtung abgeschlossen,
Setup Notifications based on various criteria.,Setup Benachrichtigungen basierend auf verschiedenen Kriterien.,
Setup Reports to be emailed at regular intervals,Berichte regelmäßig per E-Mail senden,
-"Setup of top navigation bar, footer and logo.","Einrichten der oberen Navigationsleiste, der Fußzeile und des Logos",
-Share,Aktie,
+"Setup of top navigation bar, footer and logo.","Einrichten der oberen Navigationsleiste, der Fußzeile und des Logos",
+Share,Freigeben,
Share URL,URL teilen,
Share With,Freigeben für,
Share this document with,Dieses Dokument teilen mit,
@@ -4840,3 +4840,4 @@ Non-numeric,Nicht-numerische,
Minimal,Minimal,
This value is fetched from {0}'s {1} field,Dieser Wert ergibt sich aus dem Feld {1} von {0},
This form is not editable due to a Workflow.,Dieses Formular kann in diesem Workflow-Status nicht bearbeitet werden.,
+{0} role does not have permission on any doctype,Die Rolle {0} hat auf keinen DocType Zugriff,
diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py
index 678671bce2..0c273854f7 100644
--- a/frappe/utils/pdf.py
+++ b/frappe/utils/pdf.py
@@ -23,6 +23,31 @@ PDF_CONTENT_ERRORS = [
]
+def pdf_header_html(soup, head, content, styles, html_id, css):
+ return frappe.render_template(
+ "templates/print_formats/pdf_header_footer.html",
+ {
+ "head": head,
+ "content": content,
+ "styles": styles,
+ "html_id": html_id,
+ "css": css,
+ "lang": frappe.local.lang,
+ "layout_direction": "rtl" if is_rtl() else "ltr",
+ },
+ )
+
+
+def pdf_body_html(template, args, **kwargs):
+ return template.render(args, filters={"len": len})
+
+
+def pdf_footer_html(soup, head, content, styles, html_id, css):
+ return pdf_header_html(
+ soup=soup, head=head, content=content, styles=styles, html_id=html_id, css=css
+ )
+
+
def get_pdf(html, options=None, output: PdfWriter | None = None):
html = scrub_urls(html)
html, options = prepare_options(html, options)
@@ -196,17 +221,15 @@ def prepare_header_footer(soup):
tag.extract()
toggle_visible_pdf(content)
- html = frappe.render_template(
- "templates/print_formats/pdf_header_footer.html",
- {
- "head": head,
- "content": content,
- "styles": styles,
- "html_id": html_id,
- "css": css,
- "lang": frappe.local.lang,
- "layout_direction": "rtl" if is_rtl() else "ltr",
- },
+ id_map = {"header-html": "pdf_header_html", "footer-html": "pdf_footer_html"}
+ hook_func = frappe.get_hooks(id_map.get(html_id))
+ html = frappe.get_attr(hook_func[-1])(
+ soup=soup,
+ head=head,
+ content=content,
+ styles=styles,
+ html_id=html_id,
+ css=css,
)
# create temp file
diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py
index cb4467a56c..180edefaf0 100644
--- a/frappe/utils/print_format.py
+++ b/frappe/utils/print_format.py
@@ -17,7 +17,9 @@ from frappe.www.printview import validate_print_permission
@frappe.whitelist()
-def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options=None):
+def download_multi_pdf(
+ doctype, name, format=None, no_letterhead=False, letterhead=None, options=None
+):
"""
Concatenate multiple docs as PDF .
@@ -76,6 +78,7 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options=
as_pdf=True,
output=output,
no_letterhead=no_letterhead,
+ letterhead=letterhead,
pdf_options=options,
)
frappe.local.response.filename = "{doctype}.pdf".format(
@@ -92,6 +95,7 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options=
as_pdf=True,
output=output,
no_letterhead=no_letterhead,
+ letterhead=letterhead,
pdf_options=options,
)
except Exception:
diff --git a/frappe/website/doctype/web_form/templates/web_form.html b/frappe/website/doctype/web_form/templates/web_form.html
index 9caf1983b3..cbc4c8ac0a 100644
--- a/frappe/website/doctype/web_form/templates/web_form.html
+++ b/frappe/website/doctype/web_form/templates/web_form.html
@@ -68,7 +68,7 @@
{{ _(title) }}
{% endif %}
- Not Saved
+ {{ _("Not Saved") }}
{{ header_buttons() }}
diff --git a/frappe/website/doctype/web_form/web_form.js b/frappe/website/doctype/web_form/web_form.js
index 28d33a5266..277330e674 100644
--- a/frappe/website/doctype/web_form/web_form.js
+++ b/frappe/website/doctype/web_form/web_form.js
@@ -42,6 +42,12 @@ frappe.ui.form.on("Web Form", {
render_list_settings_message(frm);
},
+ anonymous: function (frm) {
+ if (frm.doc.anonymous) {
+ frm.set_value("login_required", 0);
+ }
+ },
+
validate: function (frm) {
if (!frm.doc.login_required) {
frm.set_value("allow_multiple", 0);
diff --git a/frappe/website/doctype/web_form/web_form.json b/frappe/website/doctype/web_form/web_form.json
index 21e501481b..96749e460d 100644
--- a/frappe/website/doctype/web_form/web_form.json
+++ b/frappe/website/doctype/web_form/web_form.json
@@ -9,7 +9,7 @@
"title",
"route",
"published",
- "column_break_1",
+ "column_break_vdhm",
"doc_type",
"module",
"is_standard",
@@ -21,6 +21,7 @@
"allow_multiple",
"allow_edit",
"allow_delete",
+ "anonymous",
"column_break_2",
"apply_document_permissions",
"allow_print",
@@ -96,10 +97,12 @@
"default": "0",
"fieldname": "published",
"fieldtype": "Check",
+ "hidden": 1,
"label": "Published"
},
{
"default": "0",
+ "depends_on": "eval:!doc.anonymous",
"fieldname": "login_required",
"fieldtype": "Check",
"label": "Login Required"
@@ -301,6 +304,7 @@
{
"collapsible": 1,
"collapsible_depends_on": "show_list",
+ "depends_on": "eval:!doc.anonymous",
"fieldname": "section_break_3",
"fieldtype": "Section Break",
"label": "List Settings"
@@ -308,6 +312,7 @@
{
"collapsible": 1,
"collapsible_depends_on": "show_sidebar",
+ "depends_on": "eval:!doc.anonymous",
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"label": "Sidebar Settings"
@@ -358,13 +363,24 @@
"fieldname": "meta_image",
"fieldtype": "Attach Image",
"label": "Meta Image"
+ },
+ {
+ "fieldname": "column_break_vdhm",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "description": "Receive anonymous response",
+ "fieldname": "anonymous",
+ "fieldtype": "Check",
+ "label": "Anonymous"
}
],
"has_web_view": 1,
"icon": "icon-edit",
"is_published_field": "published",
"links": [],
- "modified": "2023-01-02 10:19:15.680960",
+ "modified": "2023-04-20 17:24:42.657731",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Form",
diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py
index ac6276c00b..3e2705bdbe 100644
--- a/frappe/website/doctype/web_form/web_form.py
+++ b/frappe/website/doctype/web_form/web_form.py
@@ -387,6 +387,10 @@ def accept(web_form, data):
web_form = frappe.get_doc("Web Form", web_form)
doctype = web_form.doc_type
+ user = frappe.session.user
+
+ if web_form.anonymous and frappe.session.user != "Guest":
+ frappe.session.user = "Guest"
if data.name and not web_form.allow_edit:
frappe.throw(_("You are not allowed to update this Web Form Document"))
@@ -468,6 +472,9 @@ def accept(web_form, data):
if f:
remove_file_by_url(f, doctype=doctype, name=doc.name)
+ if web_form.anonymous and frappe.session.user == "Guest" and user:
+ frappe.session.user = user
+
frappe.flags.web_form_doc = doc
return doc
diff --git a/frappe/www/printview.py b/frappe/www/printview.py
index 38a0409e5f..538893d818 100644
--- a/frappe/www/printview.py
+++ b/frappe/www/printview.py
@@ -208,8 +208,10 @@ def get_rendered_template(
"print_settings": print_settings,
}
)
-
- html = template.render(args, filters={"len": len})
+ hook_func = frappe.get_hooks("pdf_body_html")
+ html = frappe.get_attr(hook_func[-1])(
+ jenv=jenv, template=template, print_format=print_format, args=args
+ )
if cint(trigger_print):
html += trigger_print_script