diff --git a/cypress/integration/form_builder.js b/cypress/integration/form_builder.js index 85d7ef91e0..968f6aaaf0 100644 --- a/cypress/integration/form_builder.js +++ b/cypress/integration/form_builder.js @@ -32,7 +32,7 @@ context("Form Builder", () => { cy.click_modal_primary_button("Change"); - cy.get(".page-title .title-text").should("have.text", "Form Builder: Web Form Field"); + cy.get(".page-title .title-text").should("have.text", "Web Form Field"); }); it("Save without change, check form dirty and reset changes", () => { diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index 73ffcc560b..4953c8c157 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -1,6 +1,8 @@ # Copyright (c) 2015, Frappe Technologies and contributors # License: MIT. See LICENSE +from typing import Optional + from jinja2 import TemplateSyntaxError import frappe @@ -100,32 +102,26 @@ def get_preferred_address(doctype, name, preferred_key="is_primary_address"): @frappe.whitelist() -def get_default_address(doctype, name, sort_key="is_primary_address"): +def get_default_address( + doctype: str, name: str, sort_key: str = "is_primary_address" +) -> str | None: """Returns default Address name for the given doctype, name""" if sort_key not in ["is_shipping_address", "is_primary_address"]: return None - out = frappe.db.sql( - """ SELECT - addr.name, addr.%s - FROM - `tabAddress` addr, `tabDynamic Link` dl - WHERE - dl.parent = addr.name and dl.link_doctype = %s and - dl.link_name = %s and ifnull(addr.disabled, 0) = 0 - """ - % (sort_key, "%s", "%s"), - (doctype, name), - as_dict=True, + addresses = frappe.get_all( + "Address", + filters=[ + ["Dynamic Link", "link_doctype", "=", doctype], + ["Dynamic Link", "link_name", "=", name], + ["disabled", "=", 0], + ], + pluck="name", + order_by=f"{sort_key} DESC", + limit=1, ) - if out: - for contact in out: - if contact.get(sort_key): - return contact.name - return out[0].name - else: - return None + return addresses[0] if addresses else None @frappe.whitelist() diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index a8eed4270d..bb2af5cec0 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -21,10 +21,7 @@ frappe.ui.form.on("DocType", { frm.toggle_enable("beta", 0); } - !frm.is_new() && - frm.add_custom_button(__("Try new form builder", [__(frm.doc.name)]), () => { - frappe.set_route("form-builder", frm.doc.name); - }); + render_form_builder_message(frm); if (!frm.is_new() && !frm.doc.istable) { if (frm.doc.issingle) { @@ -118,4 +115,31 @@ frappe.ui.form.on("DocField", { }, }); +function render_form_builder_message(frm) { + $(frm.fields_dict["try_form_builder_html"].wrapper).empty(); + if (!frm.is_new() && frm.fields_dict["try_form_builder_html"]) { + let title = __("Use Form Builder to visually edit your form layout"); + let msg = __( + "You can drag and drop fields to create your form layout, add tabs, sections and columns to organize your form and update field properties all from one screen." + ); + + let message = ` +
+
+
+

${title}

+

${msg}

+
+ + ${__("Form Builder")} ${frappe.utils.icon("right", "xs")} + +
+
+
+ `; + + $(frm.fields_dict["try_form_builder_html"].wrapper).html(message); + } +} + extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({ frm: cur_frm })); diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index b3196158f5..842898d064 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -26,6 +26,7 @@ "is_virtual", "queue_in_background", "fields_section_break", + "try_form_builder_html", "fields", "sb1", "naming_rule", @@ -630,6 +631,11 @@ "fieldname": "is_calendar_and_gantt", "fieldtype": "Check", "label": "Is Calendar and Gantt" + }, + { + "fieldname": "try_form_builder_html", + "fieldtype": "HTML", + "label": "Try Form Builder HTML" } ], "icon": "fa fa-bolt", @@ -712,7 +718,7 @@ "link_fieldname": "reference_doctype" } ], - "modified": "2023-03-23 16:15:51.067267", + "modified": "2023-05-15 14:07:51.526257", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 1323359030..3728bd0af0 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -638,7 +638,9 @@ class File(Document): def create_attachment_record(self): icon = ' ' if self.is_private else "" - file_url = quote(frappe.safe_encode(self.file_url)) if self.file_url else self.file_name + file_url = ( + quote(frappe.safe_encode(self.file_url), safe="/:") if self.file_url else self.file_name + ) file_name = self.file_name or self.file_url self.add_comment_in_reference_doc( diff --git a/frappe/core/doctype/server_script/server_script.json b/frappe/core/doctype/server_script/server_script.json index 9eb42aaf36..3aedd4f542 100644 --- a/frappe/core/doctype/server_script/server_script.json +++ b/frappe/core/doctype/server_script/server_script.json @@ -124,14 +124,14 @@ "depends_on": "enable_rate_limit", "fieldname": "rate_limit_count", "fieldtype": "Int", - "label": "Limit" + "label": "Request Limit" }, { "default": "86400", "depends_on": "enable_rate_limit", "fieldname": "rate_limit_seconds", "fieldtype": "Int", - "label": "Seconds" + "label": "Time Window (Seconds)" } ], "index_web_pages_for_search": 1, @@ -141,7 +141,7 @@ "link_fieldname": "server_script" } ], - "modified": "2023-05-12 20:54:54.365266", + "modified": "2023-05-16 11:03:58.282680", "modified_by": "Administrator", "module": "Core", "name": "Server Script", diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index 0c5badb21f..d39d2062eb 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -436,8 +436,8 @@ class TestUser(FrappeTestCase): getdoc("User", "Administrator") doc = frappe.response.docs[0] self.assertListEqual( - doc.get("__onload").get("all_modules", []), - [m.get("module_name") for m in get_modules_from_all_apps()], + sorted(doc.get("__onload").get("all_modules", [])), + sorted(m.get("module_name") for m in get_modules_from_all_apps()), ) def test_reset_password_link_expiry(self): diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index c420dca3e4..8549c239e5 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -115,14 +115,7 @@ frappe.ui.form.on("Customize Form", { frm.page.set_title(__("Customize Form - {0}", [frm.doc.doc_type])); frappe.customize_form.set_primary_action(frm); - if (!frm.is_new()) { - frm.add_custom_button( - __("Try new form builder", [__(frm.doc.doc_type)]), - () => { - frappe.set_route("form-builder", frm.doc.doc_type, "customize"); - } - ); - } + render_form_builder_message(frm); frm.add_custom_button( __("Go to {0} List", [__(frm.doc.doc_type)]), @@ -426,4 +419,31 @@ frappe.customize_form.clear_locals_and_refresh = function (frm) { frm.refresh(); }; +function render_form_builder_message(frm) { + $(frm.fields_dict["try_form_builder_html"].wrapper).empty(); + if (!frm.is_new() && frm.fields_dict["try_form_builder_html"]) { + let title = __("Use Form Builder to visually customize your form layout"); + let msg = __( + "You can drag and drop fields to create your form layout, add tabs, sections and columns to organize your form and update field properties all from one screen." + ); + + let message = ` +
+
+
+

${title}

+

${msg}

+
+ + ${__("Form Builder")} ${frappe.utils.icon("right", "xs")} + +
+
+
+ `; + + $(frm.fields_dict["try_form_builder_html"].wrapper).html(message); + } +} + extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({ frm: cur_frm })); diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index b9fb52d1dc..e0d822eb61 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -22,6 +22,7 @@ "allow_import", "queue_in_background", "fields_section_break", + "try_form_builder_html", "fields", "naming_section", "naming_rule", @@ -366,6 +367,11 @@ "fieldname": "is_calendar_and_gantt", "fieldtype": "Check", "label": "Is Calendar and Gantt" + }, + { + "fieldname": "try_form_builder_html", + "fieldtype": "HTML", + "label": "Try Form Builder HTML" } ], "hide_toolbar": 1, @@ -374,7 +380,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-10-30 23:39:49.628093", + "modified": "2023-05-15 16:03:19.872532", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/desk/page/form_builder/form_builder.js b/frappe/desk/page/form_builder/form_builder.js index c968ceb2e9..454bdcae78 100644 --- a/frappe/desk/page/form_builder/form_builder.js +++ b/frappe/desk/page/form_builder/form_builder.js @@ -18,6 +18,7 @@ frappe.pages["form-builder"].on_page_show = function (wrapper) { function load_form_builder(wrapper) { let route = frappe.get_route(); + route = route.filter((a) => a); if (route.length > 1) { let doctype = route[1]; let is_customize_form = route[2] === "customize"; @@ -42,5 +43,140 @@ function load_form_builder(wrapper) { customize: is_customize_form, }); }); + } else { + let d = new frappe.ui.Dialog({ + title: __("Select DocType"), + fields: [ + { + label: __("Select DocType"), + fieldname: "doctype", + fieldtype: "Link", + options: "DocType", + only_select: 1, + }, + { + label: __("Customize"), + fieldname: "customize", + fieldtype: "Check", + }, + ], + primary_action_label: __("Edit"), + primary_action({ doctype, customize }) { + if (customize) { + frappe.model.with_doctype(doctype).then(() => { + let meta = frappe.get_meta(doctype); + if (in_list(frappe.model.core_doctypes_list, this.doctype)) + frappe.throw(__("Core DocTypes cannot be customized.")); + + if (meta.issingle) + frappe.throw(__("Single DocTypes cannot be customized.")); + + if (meta.custom) + frappe.throw( + __( + "Only standard DocTypes are allowed to be customized from Customize Form." + ) + ); + frappe.set_route("form-builder", doctype, "customize"); + }); + } else { + frappe.set_route("form-builder", doctype); + } + }, + secondary_action_label: __("Create New DocType"), + secondary_action() { + d.hide(); + let new_d = new frappe.ui.Dialog({ + title: __("Create New DocType"), + fields: [ + { + label: __("DocType Name"), + fieldname: "doctype_name", + fieldtype: "Data", + reqd: 1, + }, + { fieldtype: "Column Break" }, + { + label: __("Module"), + fieldname: "module", + fieldtype: "Link", + options: "Module Def", + reqd: 1, + }, + { fieldtype: "Section Break" }, + { + label: __("Is Submittable"), + fieldname: "is_submittable", + fieldtype: "Check", + description: __( + "Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended." + ), + depends_on: "eval:!doc.istable && !doc.issingle", + }, + { + label: __("Is Child Table"), + fieldname: "istable", + fieldtype: "Check", + description: __("Child Tables are shown as a Grid in other DocTypes"), + depends_on: "eval:!doc.is_submittable && !doc.issingle", + }, + { + label: __("Editable Grid"), + fieldname: "editable_grid", + fieldtype: "Check", + depends_on: "istable", + default: 1, + }, + { + label: __("Is Single"), + fieldname: "issingle", + fieldtype: "Check", + description: __( + "Single Types have only one record no tables associated. Values are stored in tabSingles" + ), + depends_on: "eval:!doc.istable && !doc.is_submittable", + }, + { + label: __("Custom?"), + fieldname: "custom", + fieldtype: "Check", + }, + ], + primary_action_label: __("Create & Continue"), + primary_action(values) { + if (!values.istable) values.editable_grid = 0; + frappe.db + .insert({ + doctype: "DocType", + name: values.doctype_name, + module: values.module, + istable: values.istable, + editable_grid: values.editable_grid, + issingle: values.issingle, + custom: values.custom, + is_submittable: values.is_submittable, + fields: [ + { + label: "Title", + fieldname: "title", + fieldtype: "Data", + }, + ], + }) + .then((doc) => { + frappe.set_route("form-builder", doc.name); + }); + }, + secondary_action_label: __("Back"), + secondary_action() { + new_d.hide(); + d.show(); + }, + }); + new_d.show(); + }, + }); + + d.show(); } } diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 3f906d8f12..69cdecb6dd 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -298,7 +298,9 @@ def export_query(): if isinstance(visible_idx, str): visible_idx = json.loads(visible_idx) - data = run(report_name, form_params.filters, custom_columns=custom_columns) + data = run( + report_name, form_params.filters, custom_columns=custom_columns, are_default_filters=False + ) data = frappe._dict(data) if not data.columns: frappe.respond_as_web_page( diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.js b/frappe/email/doctype/auto_email_report/auto_email_report.js index 4ce4a63b03..62b562b97d 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.js +++ b/frappe/email/doctype/auto_email_report/auto_email_report.js @@ -93,7 +93,7 @@ frappe.ui.form.on("Auto Email Report", { wrapper ); - var filters = JSON.parse(frm.doc.filters || "{}"); + var filters = {}; let report_filters; @@ -102,8 +102,19 @@ frappe.ui.form.on("Auto Email Report", { frappe.query_reports[frm.doc.reference_report] && frappe.query_reports[frm.doc.reference_report].filters ) { + if (frm.doc.filters) { + filters = JSON.parse(frm.doc.filters); + } else { + frappe.db.get_value("Report", frm.doc.report, "json", (r) => { + if (r && r.json) { + filters = JSON.parse(r.json).filters || {}; + } + }); + } + report_filters = frappe.query_reports[frm.doc.reference_report].filters; } else { + filters = JSON.parse(frm.doc.filters || "{}"); report_filters = frappe.query_reports[frm.doc.report].filters; } diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index bfad833d38..150be95476 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -328,6 +328,13 @@ def check_if_doc_is_dynamically_linked(doc, method="Delete"): ): reference_doctype = refdoc.parenttype if meta.istable else df.parent reference_docname = refdoc.parent if meta.istable else refdoc.name + + if reference_doctype in frappe.get_hooks("ignore_links_on_delete") or ( + reference_doctype in ignore_linked_doctypes and method == "Cancel" + ): + # don't check for communication and todo! + continue + at_position = f"at Row: {refdoc.idx}" if meta.istable else "" raise_link_exists_exception(doc, reference_doctype, reference_docname, at_position) diff --git a/frappe/public/images/form-builder.gif b/frappe/public/images/form-builder.gif new file mode 100644 index 0000000000..6c8e1aaa1c Binary files /dev/null and b/frappe/public/images/form-builder.gif differ diff --git a/frappe/public/js/form_builder/components/FormBuilder.vue b/frappe/public/js/form_builder/FormBuilder.vue similarity index 95% rename from frappe/public/js/form_builder/components/FormBuilder.vue rename to frappe/public/js/form_builder/FormBuilder.vue index 354346fcc2..fe7eaaa682 100644 --- a/frappe/public/js/form_builder/components/FormBuilder.vue +++ b/frappe/public/js/form_builder/FormBuilder.vue @@ -1,8 +1,8 @@