Merge branch 'develop' into get-all-mod
This commit is contained in:
commit
80e64c7143
49 changed files with 1012 additions and 774 deletions
|
|
@ -2,17 +2,16 @@ codecov:
|
|||
require_ci_to_pass: yes
|
||||
|
||||
coverage:
|
||||
range: 60..90
|
||||
status:
|
||||
project:
|
||||
default: false
|
||||
server-mariadb:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 0.5%
|
||||
flags:
|
||||
- server-mariadb
|
||||
patch:
|
||||
default: false
|
||||
server-mariadb:
|
||||
default:
|
||||
target: 85%
|
||||
threshold: 0%
|
||||
only_pulls: true
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ context("Web Form", () => {
|
|||
cy.login();
|
||||
cy.visit("/app/web-form/note");
|
||||
|
||||
cy.findByRole("tab", { name: "Form Settings" }).click();
|
||||
cy.findByRole("tab", { name: "Settings" }).click();
|
||||
cy.get('input[data-fieldname="login_required"]').check({ force: true });
|
||||
|
||||
cy.save();
|
||||
|
|
@ -65,7 +65,8 @@ context("Web Form", () => {
|
|||
cy.login();
|
||||
cy.visit("/app/web-form/note");
|
||||
|
||||
cy.findByRole("tab", { name: "List Settings" }).click();
|
||||
cy.findByRole("tab", { name: "Settings" }).click();
|
||||
cy.get(".section-head").contains("List Settings").click();
|
||||
cy.get('input[data-fieldname="show_list"]').check();
|
||||
|
||||
cy.save();
|
||||
|
|
@ -78,7 +79,7 @@ context("Web Form", () => {
|
|||
it("Show Custom List Title", () => {
|
||||
cy.visit("/app/web-form/note");
|
||||
|
||||
cy.findByRole("tab", { name: "List Settings" }).click();
|
||||
cy.findByRole("tab", { name: "Settings" }).click();
|
||||
cy.fill_field("list_title", "Note List");
|
||||
|
||||
cy.save();
|
||||
|
|
@ -97,7 +98,7 @@ context("Web Form", () => {
|
|||
|
||||
cy.visit("/app/web-form/note");
|
||||
|
||||
cy.findByRole("tab", { name: "List Settings" }).click();
|
||||
cy.findByRole("tab", { name: "Settings" }).click();
|
||||
|
||||
cy.get('[data-fieldname="list_columns"] .grid-footer button')
|
||||
.contains("Add Row")
|
||||
|
|
@ -108,19 +109,19 @@ context("Web Form", () => {
|
|||
cy.get("@grid-rows").find('.grid-row:first [data-fieldname="fieldname"]').click();
|
||||
cy.get("@grid-rows")
|
||||
.find('.grid-row:first select[data-fieldname="fieldname"]')
|
||||
.select("Title (Data)");
|
||||
.select("Title");
|
||||
|
||||
cy.get("@add-row").click();
|
||||
cy.get("@grid-rows").find('.grid-row[data-idx="2"] [data-fieldname="fieldname"]').click();
|
||||
cy.get("@grid-rows")
|
||||
.find('.grid-row[data-idx="2"] select[data-fieldname="fieldname"]')
|
||||
.select("Public (Check)");
|
||||
.select("Public");
|
||||
|
||||
cy.get("@add-row").click();
|
||||
cy.get("@grid-rows").find('.grid-row:last [data-fieldname="fieldname"]').click();
|
||||
cy.get("@grid-rows")
|
||||
.find('.grid-row:last select[data-fieldname="fieldname"]')
|
||||
.select("Content (Text Editor)");
|
||||
.select("Content");
|
||||
|
||||
cy.save();
|
||||
|
||||
|
|
@ -171,7 +172,7 @@ context("Web Form", () => {
|
|||
it("Edit Mode", () => {
|
||||
cy.visit("/app/web-form/note");
|
||||
|
||||
cy.findByRole("tab", { name: "Form Settings" }).click();
|
||||
cy.findByRole("tab", { name: "Settings" }).click();
|
||||
cy.get('input[data-fieldname="allow_edit"]').check();
|
||||
|
||||
cy.save();
|
||||
|
|
@ -179,7 +180,7 @@ context("Web Form", () => {
|
|||
cy.visit("/note/Note 1");
|
||||
cy.url().should("include", "/note/Note%201");
|
||||
|
||||
cy.get(".web-form-actions a").contains("Edit").click();
|
||||
cy.get(".web-form-actions a").contains("Edit Response").click();
|
||||
cy.url().should("include", "/note/Note%201/edit");
|
||||
|
||||
// Editable Field
|
||||
|
|
@ -194,7 +195,7 @@ context("Web Form", () => {
|
|||
it("Allow Multiple Response", () => {
|
||||
cy.visit("/app/web-form/note");
|
||||
|
||||
cy.findByRole("tab", { name: "Form Settings" }).click();
|
||||
cy.findByRole("tab", { name: "Settings" }).click();
|
||||
cy.get('input[data-fieldname="allow_multiple"]').check();
|
||||
|
||||
cy.save();
|
||||
|
|
@ -212,7 +213,7 @@ context("Web Form", () => {
|
|||
it("Allow Delete", () => {
|
||||
cy.visit("/app/web-form/note");
|
||||
|
||||
cy.findByRole("tab", { name: "Form Settings" }).click();
|
||||
cy.findByRole("tab", { name: "Settings" }).click();
|
||||
cy.get('input[data-fieldname="allow_delete"]').check();
|
||||
|
||||
cy.save();
|
||||
|
|
@ -235,7 +236,7 @@ context("Web Form", () => {
|
|||
it("Navigate and Submit a WebForm", () => {
|
||||
cy.visit("/update-profile");
|
||||
|
||||
cy.get(".web-form-actions a").contains("Edit").click();
|
||||
cy.get(".web-form-actions a").contains("Edit Response").click();
|
||||
|
||||
cy.fill_field("middle_name", "_Test User");
|
||||
|
||||
|
|
@ -247,7 +248,7 @@ context("Web Form", () => {
|
|||
cy.call("frappe.tests.ui_test_helpers.update_webform_to_multistep").then(() => {
|
||||
cy.visit("/update-profile-duplicate");
|
||||
|
||||
cy.get(".web-form-actions a").contains("Edit").click();
|
||||
cy.get(".web-form-actions a").contains("Edit Response").click();
|
||||
|
||||
cy.fill_field("middle_name", "_Test User");
|
||||
|
||||
|
|
|
|||
|
|
@ -496,6 +496,32 @@ def add_system_manager(context, email, first_name, last_name, send_welcome_email
|
|||
raise SiteNotSpecifiedError
|
||||
|
||||
|
||||
@click.command("add-user")
|
||||
@click.argument("email")
|
||||
@click.option("--first-name")
|
||||
@click.option("--last-name")
|
||||
@click.option("--password")
|
||||
@click.option("--user-type")
|
||||
@click.option("--add-role", multiple=True)
|
||||
@click.option("--send-welcome-email", default=False, is_flag=True)
|
||||
@pass_context
|
||||
def add_user_for_sites(
|
||||
context, email, first_name, last_name, user_type, send_welcome_email, password, add_role
|
||||
):
|
||||
"Add user to a site"
|
||||
import frappe.utils.user
|
||||
|
||||
for site in context.sites:
|
||||
frappe.connect(site=site)
|
||||
try:
|
||||
add_new_user(email, first_name, last_name, user_type, send_welcome_email, password, add_role)
|
||||
frappe.db.commit()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
if not context.sites:
|
||||
raise SiteNotSpecifiedError
|
||||
|
||||
|
||||
@click.command("disable-user")
|
||||
@click.argument("email")
|
||||
@pass_context
|
||||
|
|
@ -1275,8 +1301,38 @@ def handle_data(data: dict, format="json"):
|
|||
render_table(data)
|
||||
|
||||
|
||||
def add_new_user(
|
||||
email,
|
||||
first_name=None,
|
||||
last_name=None,
|
||||
user_type="System User",
|
||||
send_welcome_email=False,
|
||||
password=None,
|
||||
role=None,
|
||||
):
|
||||
user = frappe.new_doc("User")
|
||||
user.update(
|
||||
{
|
||||
"name": email,
|
||||
"email": email,
|
||||
"enabled": 1,
|
||||
"first_name": first_name or email,
|
||||
"last_name": last_name,
|
||||
"user_type": user_type,
|
||||
"send_welcome_email": 1 if send_welcome_email else 0,
|
||||
}
|
||||
)
|
||||
user.insert()
|
||||
user.add_roles(*role)
|
||||
if password:
|
||||
from frappe.utils.password import update_password
|
||||
|
||||
update_password(user=user.name, pwd=password)
|
||||
|
||||
|
||||
commands = [
|
||||
add_system_manager,
|
||||
add_user_for_sites,
|
||||
backup,
|
||||
drop_site,
|
||||
install_app,
|
||||
|
|
|
|||
|
|
@ -990,10 +990,11 @@ class Column:
|
|||
not_exists = list(set(values) - set(exists))
|
||||
if not_exists:
|
||||
missing_values = ", ".join(not_exists)
|
||||
message = _("The following values do not exist for {0}: {1}")
|
||||
self.warnings.append(
|
||||
{
|
||||
"col": self.column_number,
|
||||
"message": (f"The following values do not exist for {self.df.options}: {missing_values}"),
|
||||
"message": message.format(self.df.options, missing_values),
|
||||
"type": "warning",
|
||||
}
|
||||
)
|
||||
|
|
@ -1003,17 +1004,18 @@ class Column:
|
|||
if not self.date_format:
|
||||
if self.df.fieldtype == "Time":
|
||||
self.date_format = "%H:%M:%S"
|
||||
format = "HH:mm:ss"
|
||||
date_format = "HH:mm:ss"
|
||||
else:
|
||||
self.date_format = "%Y-%m-%d"
|
||||
format = "yyyy-mm-dd"
|
||||
date_format = "yyyy-mm-dd"
|
||||
|
||||
message = _(
|
||||
"{0} format could not be determined from the values in this column. Defaulting to {1}."
|
||||
)
|
||||
self.warnings.append(
|
||||
{
|
||||
"col": self.column_number,
|
||||
"message": _(
|
||||
"{0} format could not be determined from the values in this column. Defaulting to {1}."
|
||||
).format(self.df.fieldtype, format),
|
||||
"message": message.format(self.df.fieldtype, date_format),
|
||||
"type": "info",
|
||||
}
|
||||
)
|
||||
|
|
@ -1025,13 +1027,11 @@ class Column:
|
|||
if invalid:
|
||||
valid_values = ", ".join(frappe.bold(o) for o in options)
|
||||
invalid_values = ", ".join(frappe.bold(i) for i in invalid)
|
||||
message = _("The following values are invalid: {0}. Values must be one of {1}")
|
||||
self.warnings.append(
|
||||
{
|
||||
"col": self.column_number,
|
||||
"message": (
|
||||
"The following values are invalid: {}. Values must be"
|
||||
" one of {}".format(invalid_values, valid_values)
|
||||
),
|
||||
"message": message.format(invalid_values, valid_values),
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,18 +11,19 @@ class TestTranslation(FrappeTestCase):
|
|||
|
||||
def tearDown(self):
|
||||
frappe.local.lang = "en"
|
||||
frappe.local.lang_full_dict = None
|
||||
clear_translation_cache()
|
||||
|
||||
def test_doctype(self):
|
||||
translation_data = get_translation_data()
|
||||
for key, val in translation_data.items():
|
||||
frappe.local.lang = key
|
||||
frappe.local.lang_full_dict = None
|
||||
|
||||
clear_translation_cache()
|
||||
translation = create_translation(key, val)
|
||||
self.assertEqual(_(val[0]), val[1])
|
||||
|
||||
frappe.delete_doc("Translation", translation.name)
|
||||
frappe.local.lang_full_dict = None
|
||||
clear_translation_cache()
|
||||
|
||||
self.assertEqual(_(val[0]), val[0])
|
||||
|
||||
|
|
@ -38,20 +39,20 @@ class TestTranslation(FrappeTestCase):
|
|||
|
||||
frappe.local.lang = "es"
|
||||
|
||||
frappe.local.lang_full_dict = None
|
||||
clear_translation_cache()
|
||||
self.assertTrue(_(data[0][0]), data[0][1])
|
||||
|
||||
frappe.local.lang_full_dict = None
|
||||
clear_translation_cache()
|
||||
self.assertTrue(_(data[1][0]), data[1][1])
|
||||
|
||||
frappe.local.lang = "es-MX"
|
||||
|
||||
# different translation for es-MX
|
||||
frappe.local.lang_full_dict = None
|
||||
clear_translation_cache()
|
||||
self.assertTrue(_(data[2][0]), data[2][1])
|
||||
|
||||
# from spanish (general)
|
||||
frappe.local.lang_full_dict = None
|
||||
clear_translation_cache()
|
||||
self.assertTrue(_(data[1][0]), data[1][1])
|
||||
|
||||
def test_html_content_data_translation(self):
|
||||
|
|
@ -109,3 +110,8 @@ def create_translation(key, val):
|
|||
translation.translated_text = val[1]
|
||||
translation.save()
|
||||
return translation
|
||||
|
||||
|
||||
def clear_translation_cache():
|
||||
frappe.local.lang_full_dict = None
|
||||
frappe.cache().delete_key("lang_full_dict", shared=True)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ from frappe.cache_manager import clear_defaults_cache, common_default_keys
|
|||
from frappe.desk.notifications import clear_notifications
|
||||
from frappe.query_builder import DocType
|
||||
|
||||
# Note: DefaultValue records are identified by parenttype
|
||||
# __default, __global or 'User Permission'
|
||||
# Note: DefaultValue records are identified by parent (e.g. __default, __global)
|
||||
|
||||
|
||||
def set_user_default(key, value, user=None, parenttype=None):
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@
|
|||
{
|
||||
"fieldname": "icon",
|
||||
"fieldtype": "Icon",
|
||||
"label": "Icon"
|
||||
"label": "Icon",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "links",
|
||||
|
|
@ -122,18 +123,21 @@
|
|||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Public",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "parent_page",
|
||||
"fieldtype": "Data",
|
||||
"label": "Parent Page"
|
||||
"label": "Parent Page",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "[]",
|
||||
|
|
@ -145,7 +149,8 @@
|
|||
{
|
||||
"fieldname": "sequence_id",
|
||||
"fieldtype": "Float",
|
||||
"label": "Sequence Id"
|
||||
"label": "Sequence Id",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "roles",
|
||||
|
|
@ -172,7 +177,7 @@
|
|||
],
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-12 13:00:03.925605",
|
||||
"modified": "2022-08-16 18:01:42.632238",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Workspace",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from frappe.desk.desktop import save_new_widget
|
|||
from frappe.desk.utils import validate_route_conflict
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.rename_doc import rename_doc
|
||||
from frappe.modules.export_file import export_to_files
|
||||
from frappe.modules.export_file import delete_folder, export_to_files
|
||||
|
||||
|
||||
class Workspace(Document):
|
||||
|
|
@ -28,8 +28,22 @@ class Workspace(Document):
|
|||
if disable_saving_as_public():
|
||||
return
|
||||
|
||||
if frappe.conf.developer_mode and self.module and self.public:
|
||||
export_to_files(record_list=[["Workspace", self.name]], record_module=self.module)
|
||||
if frappe.conf.developer_mode and self.public:
|
||||
if self.module:
|
||||
export_to_files(record_list=[["Workspace", self.name]], record_module=self.module)
|
||||
|
||||
if self.has_value_changed("title") or self.has_value_changed("module"):
|
||||
previous = self.get_doc_before_save()
|
||||
if previous and previous.get("module") and previous.get("title"):
|
||||
delete_folder(previous.get("module"), "Workspace", previous.get("title"))
|
||||
|
||||
def before_export(self, doc):
|
||||
if doc.title != doc.label and doc.label == doc.name:
|
||||
self.name = doc.name = doc.label = doc.title
|
||||
|
||||
def after_delete(self):
|
||||
if self.module:
|
||||
delete_folder(self.module, "Workspace", self.title)
|
||||
|
||||
@staticmethod
|
||||
def get_module_page_map():
|
||||
|
|
|
|||
|
|
@ -158,7 +158,6 @@ frappe.ui.form.on("Email Account", {
|
|||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
frm.events.set_domain_fields(frm);
|
||||
frm.events.enable_incoming(frm);
|
||||
frm.events.notify_if_unreplied(frm);
|
||||
frm.events.show_gmail_message_for_less_secure_apps(frm);
|
||||
|
|
@ -211,42 +210,24 @@ frappe.ui.form.on("Email Account", {
|
|||
oauth_access(frm);
|
||||
},
|
||||
|
||||
email_id: function (frm) {
|
||||
//pull domain and if no matching domain go create one
|
||||
frm.events.update_domain(frm);
|
||||
},
|
||||
|
||||
update_domain: function (frm) {
|
||||
if (!frm.doc.email_id && !frm.doc.service) {
|
||||
return;
|
||||
domain: frappe.utils.debounce((frm) => {
|
||||
if (frm.doc.domain) {
|
||||
frappe.call({
|
||||
method: "get_domain_values",
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
domain: frm.doc.domain,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
for (let field in r.message) {
|
||||
frm.set_value(field, r.message[field]);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: "get_domain",
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
email_id: frm.doc.email_id,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
frm.events.set_domain_fields(frm, r.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
set_domain_fields: function (frm, args) {
|
||||
if (!args) {
|
||||
args = frappe.route_flags.set_domain_values ? frappe.route_options : {};
|
||||
}
|
||||
|
||||
for (var field in args) {
|
||||
frm.set_value(field, args[field]);
|
||||
}
|
||||
|
||||
delete frappe.route_flags.set_domain_values;
|
||||
frappe.route_options = {};
|
||||
},
|
||||
}),
|
||||
|
||||
email_sync_option: function (frm) {
|
||||
// confirm if the ALL sync option is selected
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@
|
|||
"hide_seconds": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Domain (optional)",
|
||||
"label": "Domain",
|
||||
"options": "Email Domain"
|
||||
},
|
||||
{
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
"fieldtype": "Select",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Service (optional)",
|
||||
"label": "Service",
|
||||
"options": "\nGMail\nSendgrid\nSparkPost\nYahoo Mail\nOutlook.com\nYandex.Mail"
|
||||
},
|
||||
{
|
||||
|
|
@ -615,7 +615,7 @@
|
|||
"icon": "fa fa-inbox",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-08-16 13:05:45.445572",
|
||||
"modified": "2022-08-23 00:31:05.305462",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Email Account",
|
||||
|
|
@ -639,4 +639,4 @@
|
|||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ from poplib import error_proto
|
|||
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.receive import EmailServer, InboundMail, SentEmailInInboxError
|
||||
from frappe.email.smtp import SMTPServer
|
||||
from frappe.email.utils import get_port
|
||||
|
|
@ -179,26 +180,8 @@ class EmailAccount(Document):
|
|||
email_account.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_domain(self, email_id):
|
||||
"""look-up the domain and then full"""
|
||||
try:
|
||||
domain = email_id.split("@")
|
||||
fields = [
|
||||
"name as domain",
|
||||
"use_imap",
|
||||
"email_server",
|
||||
"use_ssl",
|
||||
"use_starttls",
|
||||
"smtp_server",
|
||||
"use_tls",
|
||||
"smtp_port",
|
||||
"incoming_port",
|
||||
"append_emails_to_sent_folder",
|
||||
"use_ssl_for_outgoing",
|
||||
]
|
||||
return frappe.db.get_value("Email Domain", domain[1], fields, as_dict=True)
|
||||
except Exception:
|
||||
pass
|
||||
def get_domain_values(self, domain: str):
|
||||
return frappe.db.get_value("Email Domain", domain, EMAIL_DOMAIN_FIELDS, as_dict=True)
|
||||
|
||||
def get_incoming_server(self, in_receive=False, email_sync_rule="UNSEEN"):
|
||||
"""Returns logged in POP3/IMAP connection object."""
|
||||
|
|
|
|||
|
|
@ -11,6 +11,20 @@ from frappe.email.utils import get_port
|
|||
from frappe.model.document import Document
|
||||
from frappe.utils import cint
|
||||
|
||||
EMAIL_DOMAIN_FIELDS = [
|
||||
"email_server",
|
||||
"use_imap",
|
||||
"use_ssl",
|
||||
"use_starttls",
|
||||
"use_tls",
|
||||
"attachment_limit",
|
||||
"smtp_server",
|
||||
"smtp_port",
|
||||
"use_ssl_for_outgoing",
|
||||
"append_emails_to_sent_folder",
|
||||
"incoming_port",
|
||||
]
|
||||
|
||||
|
||||
def get_error_message(event):
|
||||
return {
|
||||
|
|
@ -52,19 +66,7 @@ class EmailDomain(Document):
|
|||
for email_account in frappe.get_all("Email Account", filters={"domain": self.name}):
|
||||
try:
|
||||
email_account = frappe.get_doc("Email Account", email_account.name)
|
||||
for attr in [
|
||||
"email_server",
|
||||
"use_imap",
|
||||
"use_ssl",
|
||||
"use_starttls",
|
||||
"use_tls",
|
||||
"attachment_limit",
|
||||
"smtp_server",
|
||||
"smtp_port",
|
||||
"use_ssl_for_outgoing",
|
||||
"append_emails_to_sent_folder",
|
||||
"incoming_port",
|
||||
]:
|
||||
for attr in EMAIL_DOMAIN_FIELDS:
|
||||
email_account.set(attr, self.get(attr, default=0))
|
||||
email_account.save()
|
||||
|
||||
|
|
|
|||
|
|
@ -387,6 +387,9 @@ def get_context(context):
|
|||
if not is_html(self.message):
|
||||
self.message = frappe.utils.md_to_html(self.message)
|
||||
|
||||
def on_trash(self):
|
||||
frappe.cache().hdel("notifications", self.document_type)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_documents_for_today(notification):
|
||||
|
|
|
|||
|
|
@ -612,6 +612,9 @@ class DatabaseQuery:
|
|||
)
|
||||
|
||||
elif f.operator.lower() in ("in", "not in"):
|
||||
# if values contain '' or falsy values then only coalesce column
|
||||
can_be_null = not f.value or any(v is None or v == "" for v in f.value)
|
||||
|
||||
values = f.value or ""
|
||||
if isinstance(values, str):
|
||||
values = values.split(",")
|
||||
|
|
|
|||
|
|
@ -167,9 +167,6 @@ def delete_doc(
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
# delete user_permissions
|
||||
frappe.defaults.clear_default(parenttype="User Permission", key=doctype, value=name)
|
||||
|
||||
|
||||
def add_to_deleted_document(doc):
|
||||
"""Add this document to Deleted Document table. Called after delete"""
|
||||
|
|
|
|||
|
|
@ -197,14 +197,6 @@ def rename_doc(
|
|||
if not merge:
|
||||
rename_password(doctype, old, new)
|
||||
|
||||
# update user_permissions
|
||||
DefaultValue = frappe.qb.DocType("DefaultValue")
|
||||
frappe.qb.update(DefaultValue).set(DefaultValue.defvalue, new).where(
|
||||
(DefaultValue.parenttype == "User Permission")
|
||||
& (DefaultValue.defkey == doctype)
|
||||
& (DefaultValue.defvalue == old)
|
||||
).run()
|
||||
|
||||
if merge:
|
||||
new_doc.add_comment("Edit", _("merged {0} into {1}").format(frappe.bold(old), frappe.bold(new)))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
# License: MIT. See LICENSE
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import frappe
|
||||
import frappe.model
|
||||
|
|
@ -92,6 +93,21 @@ def get_module_name(doc):
|
|||
return module
|
||||
|
||||
|
||||
def delete_folder(module, dt, dn):
|
||||
if frappe.db.get_value("Module Def", module, "custom"):
|
||||
module_path = get_custom_module_path(module)
|
||||
else:
|
||||
module_path = get_module_path(module)
|
||||
|
||||
dt, dn = scrub_dt_dn(dt, dn)
|
||||
|
||||
# delete folder
|
||||
folder = os.path.join(module_path, dt, dn)
|
||||
|
||||
if os.path.exists(folder):
|
||||
shutil.rmtree(folder)
|
||||
|
||||
|
||||
def create_folder(module, dt, dn, create_init):
|
||||
if frappe.db.get_value("Module Def", module, "custom"):
|
||||
module_path = get_custom_module_path(module)
|
||||
|
|
|
|||
13
frappe/public/js/bootstrap-4-web.bundle.js
vendored
13
frappe/public/js/bootstrap-4-web.bundle.js
vendored
|
|
@ -32,10 +32,7 @@ frappe.get_modal = function (title, content) {
|
|||
${content}
|
||||
</div>
|
||||
<div class="modal-footer hidden">
|
||||
<button type="button" class="btn btn-default btn-sm btn-modal-close" data-dismiss="modal">
|
||||
<i class="octicon octicon-x visible-xs" style="padding: 1px 0px;"></i>
|
||||
<span class="hidden-xs">${__("Close")}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary hidden"></button>
|
||||
<button type="button" class="btn btn-sm btn-primary hidden"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -49,11 +46,19 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.Dialog {
|
|||
return this.$wrapper.find(".modal-footer .btn-primary");
|
||||
}
|
||||
|
||||
get_secondary_btn() {
|
||||
return this.$wrapper.find(".modal-footer .btn-secondary");
|
||||
}
|
||||
|
||||
set_primary_action(label, click) {
|
||||
this.$wrapper.find(".modal-footer").removeClass("hidden");
|
||||
return super.set_primary_action(label, click).removeClass("hidden");
|
||||
}
|
||||
|
||||
set_secondary_action(click) {
|
||||
return super.set_secondary_action(click).removeClass("hidden");
|
||||
}
|
||||
|
||||
make() {
|
||||
super.make();
|
||||
if (this.fields) {
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ frappe.ui.form.ControlAttach = class ControlAttach extends frappe.ui.form.Contro
|
|||
}
|
||||
|
||||
set_input(value, dataurl) {
|
||||
this.last_value = this.value;
|
||||
this.value = value;
|
||||
if (this.value) {
|
||||
this.$input.toggle(false);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,14 @@ frappe.ui.form.Control = class BaseControl {
|
|||
this.refresh();
|
||||
}
|
||||
|
||||
get perm() {
|
||||
return this.frm?.perm;
|
||||
}
|
||||
|
||||
set perm(_perm) {
|
||||
console.error("Setting perm on controls isn't supported, update form's perm instead");
|
||||
}
|
||||
|
||||
// returns "Read", "Write" or "None"
|
||||
// as strings based on permissions
|
||||
get_status(explain) {
|
||||
|
|
@ -82,7 +90,7 @@ frappe.ui.form.Control = class BaseControl {
|
|||
is_null(value) &&
|
||||
!in_list(["HTML", "Image", "Button"], this.df.fieldtype)
|
||||
)
|
||||
status = "None";
|
||||
status = "Read";
|
||||
|
||||
return status;
|
||||
}
|
||||
|
|
@ -270,9 +278,6 @@ frappe.ui.form.Control = class BaseControl {
|
|||
} else {
|
||||
if (this.doc) {
|
||||
this.doc[this.df.fieldname] = value;
|
||||
} else {
|
||||
// case where input is rendered on dialog where doc is not maintained
|
||||
this.value = value;
|
||||
}
|
||||
this.set_input(value);
|
||||
return Promise.resolve();
|
||||
|
|
|
|||
|
|
@ -31,11 +31,12 @@ frappe.ui.form.ControlCheck = class ControlCheck extends frappe.ui.form.ControlD
|
|||
return cint(value);
|
||||
}
|
||||
set_input(value) {
|
||||
this.last_value = this.value;
|
||||
value = cint(value);
|
||||
this.value = value;
|
||||
if (this.input) {
|
||||
this.input.checked = value ? 1 : 0;
|
||||
}
|
||||
this.last_value = value;
|
||||
this.set_mandatory(value);
|
||||
this.set_disp_area(value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,11 +93,11 @@ frappe.ui.form.ControlColor = class ControlColor extends frappe.ui.form.ControlD
|
|||
|
||||
set_formatted_input(value) {
|
||||
super.set_formatted_input(value);
|
||||
this.$input.val(value);
|
||||
this.selected_color.css({
|
||||
this.$input?.val(value);
|
||||
this.selected_color?.css({
|
||||
"background-color": value || "transparent",
|
||||
});
|
||||
this.selected_color.toggleClass("no-value", !value);
|
||||
this.selected_color?.toggleClass("no-value", !value);
|
||||
}
|
||||
|
||||
get_color() {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ frappe.ui.form.ControlTable = class ControlTable extends frappe.ui.form.Control
|
|||
this.grid = new Grid({
|
||||
frm: this.frm,
|
||||
df: this.df,
|
||||
perm: this.perm || (this.frm && this.frm.perm) || this.df.perm,
|
||||
parent: this.wrapper,
|
||||
control: this,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -403,11 +403,17 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
this.doc = frappe.get_doc(this.doctype, this.docname);
|
||||
|
||||
// check permissions
|
||||
this.fetch_permissions();
|
||||
if (!this.has_read_permission()) {
|
||||
frappe.show_not_permitted(__(this.doctype) + " " + __(cstr(this.docname)));
|
||||
return;
|
||||
}
|
||||
|
||||
// update grids with new permissions
|
||||
this.grids.forEach((table) => {
|
||||
table.grid.refresh();
|
||||
});
|
||||
|
||||
// read only (workflow)
|
||||
this.read_only = frappe.workflow.is_read_only(this.doctype, this.docname);
|
||||
if (this.read_only) this.set_read_only(true);
|
||||
|
|
@ -1157,11 +1163,12 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
.attr("target", "_blank");
|
||||
}
|
||||
|
||||
has_read_permission() {
|
||||
// get perm
|
||||
var dt = this.parent_doctype ? this.parent_doctype : this.doctype;
|
||||
fetch_permissions() {
|
||||
let dt = this.parent_doctype ? this.parent_doctype : this.doctype;
|
||||
this.perm = frappe.perm.get_perm(dt, this.doc);
|
||||
}
|
||||
|
||||
has_read_permission() {
|
||||
if (!this.perm[0].read) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -294,7 +294,10 @@ frappe.form.formatters = {
|
|||
let formatted_value = frappe.form.formatters.Text(value);
|
||||
// to use ql-editor styles
|
||||
try {
|
||||
if (!$(formatted_value).find(".ql-editor").length) {
|
||||
if (
|
||||
!$(formatted_value).find(".ql-editor").length &&
|
||||
!$(formatted_value).hasClass("ql-editor")
|
||||
) {
|
||||
formatted_value = `<div class="ql-editor read-mode">${formatted_value}</div>`;
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,14 @@ export default class Grid {
|
|||
this.debounced_refresh = frappe.utils.debounce(this.debounced_refresh, 100);
|
||||
}
|
||||
|
||||
get perm() {
|
||||
return this.control?.perm || this.frm?.perm || this.df.perm;
|
||||
}
|
||||
|
||||
set perm(_perm) {
|
||||
console.error("Setting perm on grid isn't supported, update form's perm instead");
|
||||
}
|
||||
|
||||
allow_on_grid_editing() {
|
||||
if (frappe.utils.is_xs()) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -194,9 +194,6 @@ frappe.ui.form.Layout = class Layout {
|
|||
this.fields_dict[fieldname].$wrapper.remove();
|
||||
this.fields_list.splice(this.fields_dict[fieldname], 1, fieldobj);
|
||||
this.fields_dict[fieldname] = fieldobj;
|
||||
if (this.frm) {
|
||||
fieldobj.perm = this.frm.perm;
|
||||
}
|
||||
this.section.fields_list.splice(this.section.fields_dict[fieldname], 1, fieldobj);
|
||||
this.section.fields_dict[fieldname] = fieldobj;
|
||||
this.refresh_fields([df]);
|
||||
|
|
@ -210,9 +207,6 @@ frappe.ui.form.Layout = class Layout {
|
|||
const fieldobj = this.init_field(df, render);
|
||||
this.fields_list.push(fieldobj);
|
||||
this.fields_dict[df.fieldname] = fieldobj;
|
||||
if (this.frm) {
|
||||
fieldobj.perm = this.frm.perm;
|
||||
}
|
||||
|
||||
this.section.add_field(fieldobj);
|
||||
this.column.add_field(fieldobj);
|
||||
|
|
@ -465,11 +459,6 @@ frappe.ui.form.Layout = class Layout {
|
|||
fieldobj.df =
|
||||
frappe.meta.get_docfield(me.doc.doctype, fieldobj.df.fieldname, me.doc.name) ||
|
||||
fieldobj.df;
|
||||
|
||||
// on form change, permissions can change
|
||||
if (me.frm) {
|
||||
fieldobj.perm = me.frm.perm;
|
||||
}
|
||||
}
|
||||
refresh && fieldobj.df && fieldobj.refresh && fieldobj.refresh();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {
|
|||
|
||||
set_secondary_action(click) {
|
||||
this.footer.removeClass("hide");
|
||||
this.get_secondary_btn().removeClass("hide").off("click").on("click", click);
|
||||
return this.get_secondary_btn().removeClass("hide").off("click").on("click", click);
|
||||
}
|
||||
|
||||
set_secondary_action_label(label) {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ frappe.warn = function (title, message_html, proceed_action, primary_label, is_m
|
|||
|
||||
d.$body.append(`<div class="frappe-confirm-message">${message_html}</div>`);
|
||||
d.standard_actions.find(".btn-primary").removeClass("btn-primary").addClass("btn-danger");
|
||||
d.standard_actions.find(".btn-primary").removeClass("btn-primary").addClass("btn-danger");
|
||||
|
||||
d.show();
|
||||
return d;
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
super.make();
|
||||
this.set_page_breaks();
|
||||
this.set_field_values();
|
||||
this.setup_listeners();
|
||||
|
||||
if (this.is_new || this.is_form_editable) {
|
||||
if (this.is_new || this.in_edit_mode) {
|
||||
this.setup_primary_action();
|
||||
this.setup_discard_action();
|
||||
}
|
||||
|
||||
this.setup_previous_next_button();
|
||||
|
|
@ -35,6 +35,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
|
||||
// webform client script
|
||||
frappe.init_client_script && frappe.init_client_script();
|
||||
this.setup_listeners();
|
||||
frappe.web_form.events.trigger("after_load");
|
||||
this.after_load && this.after_load();
|
||||
}
|
||||
|
|
@ -43,34 +44,39 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
let field = this.fields_dict[fieldname];
|
||||
field.df.change = () => {
|
||||
handler(field, field.value);
|
||||
this.make_form_dirty();
|
||||
};
|
||||
}
|
||||
|
||||
setup_listeners() {
|
||||
// Event listener for triggering Save/Next button for Multi Step Forms
|
||||
// Do not use `on` event here since that can be used by user which will render this function useless
|
||||
// setTimeout has 200ms delay so that all the base_control triggers for the fields have been run
|
||||
let me = this;
|
||||
// setup change event for all fields if not already set through client script
|
||||
this.fields.forEach((field) => {
|
||||
if (!field.change) {
|
||||
field.change = () => {
|
||||
this.make_form_dirty();
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!me.is_multi_step_form) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let field of $(".input-with-feedback")) {
|
||||
$(field).change((e) => {
|
||||
setTimeout(() => {
|
||||
e.stopPropagation();
|
||||
me.toggle_buttons();
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
make_form_dirty() {
|
||||
frappe.form_dirty = true;
|
||||
$(".indicator-pill.orange").removeClass("hide");
|
||||
}
|
||||
|
||||
set_page_breaks() {
|
||||
if (this.page_breaks.length) return;
|
||||
this.page_breaks = $(".page-break");
|
||||
|
||||
this.page_breaks = $(`.page-break`);
|
||||
this.is_multi_step_form = true;
|
||||
if (this.page_breaks.length) {
|
||||
this.page_breaks.each((i, page_break) => {
|
||||
if (!$(page_break).find("form").length) {
|
||||
$(page_break).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.page_breaks = $(".page-break");
|
||||
this.is_multi_step_form = !!this.page_breaks.length;
|
||||
}
|
||||
|
||||
setup_previous_next_button() {
|
||||
|
|
@ -80,15 +86,19 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
return;
|
||||
}
|
||||
|
||||
$(".web-form-footer .web-form-actions .left-area").prepend(`
|
||||
<button class="btn btn-default btn-previous btn-md mr-2">${__("Previous")}</button>
|
||||
`);
|
||||
this.$next_button = $(`<button class="btn btn-default btn-next btn-sm ml-2">
|
||||
${__("Next")}
|
||||
</button>`);
|
||||
|
||||
$(".web-form-footer .web-form-actions .right-area").prepend(`
|
||||
<button class="btn btn-default btn-next btn-md">${__("Next")}</button>
|
||||
`);
|
||||
this.$previous_button = $(`<button class="btn btn-default btn-previous btn-sm">
|
||||
${__("Previous")}
|
||||
</button>`);
|
||||
|
||||
$(".btn-previous").on("click", function () {
|
||||
this.$next_button.insertAfter(".web-form-footer .right-area .discard-btn");
|
||||
this.in_view_mode && $(".web-form-footer .right-area").append(this.$next_button);
|
||||
$(".web-form-footer .left-area").prepend(this.$previous_button);
|
||||
|
||||
this.$previous_button.on("click", () => {
|
||||
let is_validated = me.validate_section();
|
||||
|
||||
if (!is_validated) return false;
|
||||
|
|
@ -115,7 +125,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
return false;
|
||||
});
|
||||
|
||||
$(".btn-next").on("click", function () {
|
||||
this.$next_button.on("click", () => {
|
||||
let is_validated = me.validate_section();
|
||||
|
||||
if (!is_validated) return false;
|
||||
|
|
@ -155,7 +165,29 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
}
|
||||
|
||||
setup_primary_action() {
|
||||
$(".web-form-container").on("submit", () => this.save());
|
||||
$(".web-form").on("submit", () => this.save());
|
||||
}
|
||||
|
||||
setup_discard_action() {
|
||||
$(".web-form-footer .discard-btn").on("click", () => this.discard_form());
|
||||
}
|
||||
|
||||
discard_form() {
|
||||
let path = window.location.href;
|
||||
// remove new or edit after last / from url
|
||||
path = path.substring(0, path.lastIndexOf("/"));
|
||||
|
||||
if (frappe.form_dirty) {
|
||||
frappe.warn(
|
||||
__("Discard?"),
|
||||
__("Are you sure you want to discard the changes?"),
|
||||
() => (window.location.href = path),
|
||||
__("Discard")
|
||||
);
|
||||
} else {
|
||||
window.location.href = path;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
validate_section() {
|
||||
|
|
@ -223,8 +255,18 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
}
|
||||
|
||||
render_progress_dots() {
|
||||
if (!this.is_multi_step_form) return;
|
||||
$(".center-area.paging").empty();
|
||||
|
||||
if (this.in_view_mode) {
|
||||
let paging_text = __("Page {0} of {1}", [
|
||||
this.current_section + 1,
|
||||
this.page_breaks.length + 1,
|
||||
]);
|
||||
$(".center-area.paging").append(`<div>${paging_text}</div>`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$slide_progress = $(`<div class="slides-progress"></div>`).appendTo(
|
||||
$(".center-area.paging")
|
||||
);
|
||||
|
|
@ -246,12 +288,6 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
}
|
||||
this.$slide_progress.append($dot);
|
||||
}
|
||||
|
||||
let paging_text = __("Page {0} of {1}", [
|
||||
this.current_section + 1,
|
||||
this.page_breaks.length + 1,
|
||||
]);
|
||||
$(".center-area.paging").append(`<div>${paging_text}</div>`);
|
||||
}
|
||||
|
||||
toggle_buttons() {
|
||||
|
|
@ -290,7 +326,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
|
||||
show_next_and_hide_save_button() {
|
||||
$(".btn-next").show();
|
||||
$(".submit-btn").hide();
|
||||
!this.allow_incomplete && $(".submit-btn").hide();
|
||||
}
|
||||
|
||||
toggle_previous_button() {
|
||||
|
|
@ -398,16 +434,16 @@ export default class WebForm extends frappe.ui.FieldGroup {
|
|||
|
||||
render_success_page(data) {
|
||||
if (this.allow_edit && data.name) {
|
||||
$(".success-page").append(`
|
||||
<a href="/${this.route}/${data.name}/edit" class="edit-button btn btn-light btn-md ml-2">
|
||||
$(".success-footer").append(`
|
||||
<a href="/${this.route}/${data.name}/edit" class="edit-button btn btn-default btn-md">
|
||||
${__("Edit your response", null, "Button in web form")}
|
||||
</a>
|
||||
`);
|
||||
}
|
||||
|
||||
if (this.login_required && !this.allow_multiple && !this.show_list && data.name) {
|
||||
$(".success-page").append(`
|
||||
<a href="/${this.route}/${data.name}" class="view-button btn btn-light btn-md ml-2">
|
||||
$(".success-footer").append(`
|
||||
<a href="/${this.route}/${data.name}" class="view-button btn btn-default btn-md">
|
||||
${__("View your response", null, "Button in web form")}
|
||||
</a>
|
||||
`);
|
||||
|
|
|
|||
|
|
@ -381,7 +381,11 @@ frappe.ui.WebFormListRow = class WebFormListRow {
|
|||
formatter(this.doc[field.fieldname], field, { only_value: 1 }, this.doc)
|
||||
)) ||
|
||||
"";
|
||||
let cell = $(`<td>${value}</td>`);
|
||||
let cell = $(`<td><p class="ellipsis">${value}</p></td>`);
|
||||
if (field.fieldtype === "Text Editor") {
|
||||
value = $(value).addClass("ellipsis");
|
||||
cell = $("<td></td>").append(value);
|
||||
}
|
||||
cell.appendTo(this.row);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -36,9 +36,6 @@ frappe.ready(function () {
|
|||
function show_form() {
|
||||
let web_form = new WebForm({
|
||||
parent: $(".web-form-wrapper"),
|
||||
is_new: web_form_doc.is_new,
|
||||
is_form_editable: web_form_doc.is_form_editable,
|
||||
web_form_name: web_form_doc.name,
|
||||
});
|
||||
let doc = reference_doc || {};
|
||||
setup_fields(web_form_doc, doc);
|
||||
|
|
@ -58,7 +55,7 @@ frappe.ready(function () {
|
|||
function setup_fields(web_form_doc, doc_data) {
|
||||
web_form_doc.web_form_fields.forEach((df) => {
|
||||
df.is_web_form = true;
|
||||
df.read_only = !web_form_doc.is_new && !web_form_doc.is_form_editable;
|
||||
df.read_only = !web_form_doc.is_new && !web_form_doc.in_edit_mode;
|
||||
if (df.fieldtype === "Table") {
|
||||
df.get_data = () => {
|
||||
let data = [];
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import "./controls.bundle.js";
|
||||
import "./dialog.bundle.js";
|
||||
import "./lib/moment.js";
|
||||
import "./frappe/utils/datetime.js";
|
||||
import "./frappe/web_form/webform_script.js";
|
||||
import "./bootstrap-4-web.bundle.js";
|
||||
|
|
|
|||
|
|
@ -1,361 +1,494 @@
|
|||
@import "../common/form";
|
||||
|
||||
[data-doctype="Web Form"] {
|
||||
.page_content {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
|
||||
h1 {
|
||||
font-size: 2.25rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.web-form-banner-image {
|
||||
margin: -4rem -14rem 5rem;
|
||||
padding-top: 3rem;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
object-fit: cover;
|
||||
.page-content-wrapper {
|
||||
.container {
|
||||
.page-header {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.web-form-header {
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-bottom: none;
|
||||
border-top-left-radius: var(--border-radius-md);
|
||||
border-top-right-radius: var(--border-radius-md);
|
||||
background-color: var(--fg-color);
|
||||
padding: 2rem 2rem 0;
|
||||
|
||||
.breadcrumb-container {
|
||||
padding: 0px;
|
||||
margin: 0 0 2rem;
|
||||
|
||||
ol.breadcrumb {
|
||||
padding: 0px;
|
||||
img {
|
||||
margin: -1rem 0rem -10.5rem;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.web-form-head {
|
||||
border-bottom: 1px solid var(--dark-border-color);
|
||||
padding-bottom: 1.25rem;
|
||||
.page_content {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
h1 {
|
||||
font-size: 2.25rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.web-form-introduction {
|
||||
color: var(--text-muted);
|
||||
margin-top: 1.25rem;
|
||||
.web-form-header {
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-bottom: none;
|
||||
border-top-left-radius: var(--border-radius-md);
|
||||
border-top-right-radius: var(--border-radius-md);
|
||||
background-color: var(--fg-color);
|
||||
padding: 2rem 2rem 0;
|
||||
|
||||
p {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.breadcrumb-container {
|
||||
padding: 0px;
|
||||
margin: 0 0 2rem;
|
||||
|
||||
.web-form {
|
||||
background-color: var(--fg-color);
|
||||
padding: 1.25rem 2rem 2rem;
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-top: none;
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border-bottom-right-radius: var(--border-radius);
|
||||
|
||||
.web-form-wrapper {
|
||||
.form-control {
|
||||
color: var(--text-color);
|
||||
background-color: var(--control-bg);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
.section-head {
|
||||
font-weight: bold;
|
||||
font-size: var(--text-xl);
|
||||
padding: var(--padding-md) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-column {
|
||||
padding: 0 var(--padding-sm);
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
ol.breadcrumb {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
.web-form-head {
|
||||
border-bottom: 1px solid var(--dark-border-color);
|
||||
padding-bottom: 1.25rem;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
|
||||
.web-form-skeleton {
|
||||
.box-group {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.box-container {
|
||||
width: 100%;
|
||||
|
||||
.box {
|
||||
background-color: var(--control-bg);
|
||||
border-radius: var(--border-radius);
|
||||
.web-form-title p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.box-label {
|
||||
height: 20px;
|
||||
width: 100px;
|
||||
margin-bottom: 0.5rem;
|
||||
.indicator-pill {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.box-area {
|
||||
height: 34px;
|
||||
width: 100%;
|
||||
.web-form-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
|
||||
.btn {
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.web-form-introduction {
|
||||
color: var(--text-muted);
|
||||
margin-top: 1.25rem;
|
||||
|
||||
p {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.web-form-footer {
|
||||
margin-top: 1rem;
|
||||
.web-form {
|
||||
background-color: var(--fg-color);
|
||||
padding: 1.25rem 2rem 2rem;
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-top: none;
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border-bottom-right-radius: var(--border-radius);
|
||||
|
||||
.web-form-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.web-form-wrapper {
|
||||
.form-control {
|
||||
color: var(--text-color);
|
||||
background-color: var(--control-bg);
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-size: var(--font-size-base);
|
||||
.form-section {
|
||||
.section-head {
|
||||
font-weight: bold;
|
||||
font-size: var(--text-xl);
|
||||
padding: var(--padding-md) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-column {
|
||||
padding: 0 var(--padding-sm);
|
||||
|
||||
.frappe-control[data-fieldtype="Rating"] {
|
||||
.like-disabled-input {
|
||||
background-color: unset;
|
||||
padding-left: 0px;
|
||||
|
||||
.rating {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.web-form-skeleton {
|
||||
.box-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.box-container {
|
||||
width: 100%;
|
||||
padding: 0 var(--padding-sm);
|
||||
margin-bottom: 15px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.box {
|
||||
background-color: var(--control-bg);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.box-label {
|
||||
height: 20px;
|
||||
width: 100px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.box-area {
|
||||
height: 34px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center-area {
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.web-form-footer {
|
||||
margin-top: 1rem;
|
||||
|
||||
.slides-progress {
|
||||
.web-form-actions {
|
||||
display: flex;
|
||||
margin-right: .5rem;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.slide-step {
|
||||
@include flex(flex, center, center, null);
|
||||
.btn {
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: var(--border-radius-full);
|
||||
border: 1px solid var(--gray-300);
|
||||
margin: 0 var(--margin-xs);
|
||||
background-color: var(--card-bg);
|
||||
.btn-link {
|
||||
padding-left: 0px;
|
||||
color: var(--text-color);
|
||||
|
||||
.slide-step-indicator {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
background-color: var(--gray-300);
|
||||
border-radius: var(--border-radius-full);
|
||||
&:hover {
|
||||
color: var(--text-on-light-blue);
|
||||
}
|
||||
}
|
||||
|
||||
.left-area {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
order: 1
|
||||
}
|
||||
}
|
||||
|
||||
.center-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: var(--text-base);
|
||||
|
||||
.slides-progress {
|
||||
display: flex;
|
||||
|
||||
.slide-step {
|
||||
@include flex(flex, center, center, null);
|
||||
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: var(--border-radius-full);
|
||||
border: 1px solid var(--gray-300);
|
||||
margin: 0 var(--margin-xs);
|
||||
background-color: var(--card-bg);
|
||||
|
||||
.slide-step-indicator {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
background-color: var(--gray-300);
|
||||
border-radius: var(--border-radius-full);
|
||||
}
|
||||
|
||||
.slide-step-complete {
|
||||
display: none;
|
||||
|
||||
.icon-xs {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border: 1px solid var(--primary);
|
||||
|
||||
.slide-step-indicator {
|
||||
display: block;
|
||||
background-color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
&.step-success:not(.active) {
|
||||
background-color: var(--primary);
|
||||
border: 1px solid var(--primary);
|
||||
|
||||
.slide-step-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.slide-step-complete {
|
||||
display: flex;
|
||||
|
||||
.icon use {
|
||||
stroke-width: 2;
|
||||
stroke: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slide-step-complete {
|
||||
@include media-breakpoint-down(sm) {
|
||||
order: 0;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.right-area {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
order: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attachments {
|
||||
margin-top: 2rem;
|
||||
padding: 2rem;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--dark-border-color);
|
||||
|
||||
.attachment {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--text-md);
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
.file-name span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.success-page {
|
||||
background-color: var(--fg-color);
|
||||
padding: 5rem 2rem;
|
||||
margin-top: 3rem;
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
text-align: center;
|
||||
|
||||
.success-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
|
||||
.success-icon {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin: 0;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.success-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.success-body .success-message {
|
||||
margin: 1rem 0rem 1.5rem;
|
||||
}
|
||||
|
||||
.success-footer a {
|
||||
margin: 0rem 0.3rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.web-list-container {
|
||||
min-height: 470px;
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: 2rem;
|
||||
|
||||
.web-list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
border-bottom: 1px solid var(--dark-border-color);
|
||||
padding-bottom: 1.25rem;
|
||||
|
||||
.web-list-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.web-list-filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 1.25rem 0;
|
||||
gap: 10px;
|
||||
|
||||
.form-group.frappe-control {
|
||||
min-width: 145px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
align-self: center;
|
||||
|
||||
.checkbox {
|
||||
.input-xs {
|
||||
height: var(--checkbox-size);
|
||||
}
|
||||
|
||||
.help-box {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-xs {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
.input-xs {
|
||||
height: 28px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.web-list-table {
|
||||
overflow: auto;
|
||||
|
||||
.table {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-top: 1px solid var(--border-color);
|
||||
|
||||
thead tr {
|
||||
th {
|
||||
border: 0;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
color: var(--text-muted);
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border: 1px solid var(--primary);
|
||||
tbody tr {
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
|
||||
.slide-step-indicator {
|
||||
display: block;
|
||||
background-color: var(--primary);
|
||||
}
|
||||
}
|
||||
td {
|
||||
font-size: 13px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
max-width: 160px;
|
||||
|
||||
&.step-success:not(.active) {
|
||||
background-color: var(--primary);
|
||||
border: 1px solid var(--primary);
|
||||
.ql-editor, p {
|
||||
width: max-content;
|
||||
max-width: 150px;
|
||||
margin-bottom: 0;
|
||||
|
||||
.slide-step-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.slide-step-complete {
|
||||
display: flex;
|
||||
|
||||
.icon use {
|
||||
stroke-width: 2;
|
||||
stroke: var(--white);
|
||||
&.read-mode {
|
||||
display: inline-flex;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attachments {
|
||||
margin-top: 2rem;
|
||||
padding: 2rem;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--dark-border-color);
|
||||
|
||||
.attachment {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--text-md);
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
.file-name span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.success-page {
|
||||
background-color: var(--fg-color);
|
||||
padding: 2rem;
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
text-align: center;
|
||||
|
||||
svg.icon {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.web-list-container {
|
||||
min-height: 470px;
|
||||
border: 1px solid var(--dark-border-color);
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: 2rem;
|
||||
|
||||
.web-list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--dark-border-color);
|
||||
padding-bottom: 1.25rem;
|
||||
|
||||
.web-list-actions {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.web-list-filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 1.25rem 0;
|
||||
gap: 10px;
|
||||
|
||||
.form-group.frappe-control {
|
||||
min-width: 145px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
align-self: center;
|
||||
|
||||
.checkbox {
|
||||
.input-xs {
|
||||
height: var(--checkbox-size);
|
||||
}
|
||||
|
||||
.help-box {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.input-xs {
|
||||
height: 28px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.web-list-table {
|
||||
overflow: auto;
|
||||
|
||||
.table {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-top: 1px solid var(--border-color);
|
||||
|
||||
thead tr {
|
||||
th {
|
||||
border: 0;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
color: var(--text-muted);
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-bottom: -2px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.list-col-checkbox {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.list-col-serial {
|
||||
width: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
color: var(--text-color);
|
||||
cursor: pointer;
|
||||
|
||||
td {
|
||||
font-size: 13px;
|
||||
.no-result {
|
||||
min-height: 330px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.list-col-checkbox {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.list-col-serial {
|
||||
width: 1.5rem;
|
||||
.web-list-footer {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.no-result {
|
||||
min-height: 330px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
.breadcrumb-container.container {
|
||||
@include media-breakpoint-up(sm) {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.web-list-footer {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-container.container {
|
||||
@include media-breakpoint-up(sm) {
|
||||
@include media-breakpoint-down(lg) {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -691,6 +691,17 @@ class TestSiteMigration(BaseTestCommands):
|
|||
self.assertEqual(result.exception, None)
|
||||
|
||||
|
||||
class TestAddNewUser(BaseTestCommands):
|
||||
def test_create_user(self):
|
||||
self.execute(
|
||||
"bench --site {site} add-user test@gmail.com --first-name test --last-name test --password 123 --user-type 'System User' --add-role 'Accounts User' --add-role 'Sales User'"
|
||||
)
|
||||
self.assertEqual(self.returncode, 0)
|
||||
user = frappe.get_doc("User", "test@gmail.com")
|
||||
roles = {r.role for r in user.roles}
|
||||
self.assertEqual({"Accounts User", "Sales User"}, roles)
|
||||
|
||||
|
||||
class TestBenchBuild(BaseTestCommands):
|
||||
def test_build_assets_size_check(self):
|
||||
with cli(frappe.commands.utils.build, "--force --production") as result:
|
||||
|
|
|
|||
|
|
@ -831,6 +831,11 @@ class TestReportview(FrappeTestCase):
|
|||
|
||||
self.assertTrue(dashboard_settings)
|
||||
|
||||
def test_coalesce_with_in_ops(self):
|
||||
self.assertNotIn("ifnull", frappe.get_all("User", {"name": ("in", ["a", "b"])}, run=0))
|
||||
self.assertIn("ifnull", frappe.get_all("User", {"name": ("in", ["a", None])}, run=0))
|
||||
self.assertIn("ifnull", frappe.get_all("User", {"name": ("in", ["a", ""])}, run=0))
|
||||
|
||||
|
||||
def add_child_table_to_blog_post():
|
||||
child_table = frappe.get_doc(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from unittest.mock import patch
|
|||
import frappe
|
||||
import frappe.translate
|
||||
from frappe import _
|
||||
from frappe.core.doctype.translation.test_translation import clear_translation_cache
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.translate import (
|
||||
extract_javascript,
|
||||
|
|
@ -37,13 +38,15 @@ class TestTranslate(FrappeTestCase):
|
|||
def setUp(self):
|
||||
if self._testMethodName in self.guest_sessions_required:
|
||||
frappe.set_user("Guest")
|
||||
frappe.local.lang_full_dict = None # reset cached translations
|
||||
|
||||
clear_translation_cache()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.form_dict.pop("_lang", None)
|
||||
if self._testMethodName in self.guest_sessions_required:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.local.lang_full_dict = None # reset cached translations
|
||||
|
||||
clear_translation_cache()
|
||||
|
||||
def test_extract_message_from_file(self):
|
||||
data = frappe.translate.get_messages_from_file(translation_string_file)
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ def _restore_thread_locals(flags):
|
|||
frappe.local.cache = {}
|
||||
frappe.local.lang = "en"
|
||||
frappe.local.lang_full_dict = None
|
||||
frappe.local.preload_assets = {"style": [], "script": []}
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
|
|||
|
|
@ -2,49 +2,44 @@
|
|||
|
||||
{% block breadcrumbs %}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
{% if banner_image %}
|
||||
<!-- banner image -->
|
||||
<img class="web-form-banner-image" src="{{ banner_image }}" alt="Banner Image">
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% macro header_buttons() %}
|
||||
{% if allow_print and not is_new %}
|
||||
{% set print_format_url = "/printview?doctype=" + doc_type + "&name=" + doc_name + "&format=" + print_format %}
|
||||
<!-- print button -->
|
||||
<a href="{{ print_format_url }}" target="_blank" class="print-btn btn btn-light btn-sm ml-2">
|
||||
<svg class="icon icon-sm"><use href="#icon-printer"></use></svg>
|
||||
</a>
|
||||
{% if allow_edit and in_view_mode %}
|
||||
<!-- edit button -->
|
||||
<a href="/{{ route }}/{{ doc_name }}/edit" class="edit-button btn btn-default btn-sm">{{ _("Edit Response", null, "Button in web form") }}</a>
|
||||
{% endif %}
|
||||
|
||||
{% if allow_edit and doc_name and not is_form_editable %}
|
||||
<!-- edit button -->
|
||||
<a href="/{{ route }}/{{ doc_name }}/edit" class="edit-button btn btn-primary btn-sm ml-2">{{ _("Edit", null, "Button in web form") }}</a>
|
||||
{% if allow_print and in_view_mode %}
|
||||
{% set print_format_url = "/printview?doctype=" + doc_type + "&name=" + doc_name + "&format=" + print_format %}
|
||||
<!-- print button -->
|
||||
<a href="{{ print_format_url }}" target="_blank" class="print-btn btn btn-default btn-sm ml-2">
|
||||
<svg class="icon icon-sm"><use href="#icon-printer"></use></svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro action_buttons() %}
|
||||
{% if is_new or is_form_editable %}
|
||||
<div class="left-area">
|
||||
<!-- clear button -->
|
||||
<a href="/{{ path }}" class="clear-btn btn btn-light btn-md">
|
||||
{% if is_form_editable %}
|
||||
{{ _("Reset Form", null, "Button in web form") }}
|
||||
{% else %}
|
||||
{{ _("Clear Form", null, "Button in web form") }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="center-area paging"></div>
|
||||
<div class="right-area">
|
||||
<div class="left-area"></div>
|
||||
<div class="center-area paging"></div>
|
||||
<div class="right-area">
|
||||
{% if not in_view_mode %}
|
||||
<!-- discard button -->
|
||||
<button class="discard-btn btn btn-default btn-sm">
|
||||
{{ _("Discard", null, "Button in web form") }}
|
||||
</button>
|
||||
<!-- submit button -->
|
||||
<button type="submit" class="submit-btn btn btn-primary btn-md ml-2">{{ button_label or _("Submit", null, "Button in web form") }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit" class="submit-btn btn btn-primary btn-sm ml-2">{{ button_label or _("Submit", null, "Button in web form") }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% block page_content %}
|
||||
<!-- banner image -->
|
||||
{% if banner_image %}
|
||||
<div class="web-form-banner-image">
|
||||
<img src="{{ banner_image }}" alt="Banner Image">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- web form container -->
|
||||
<div class="web-form-container">
|
||||
<!-- breadcrumb -->
|
||||
|
|
@ -61,12 +56,20 @@
|
|||
{% endif %}
|
||||
<div class="web-form-head">
|
||||
<div class="title">
|
||||
<h1>{{ _(title) }}</h1>
|
||||
<div class="web-form-title ellipsis">
|
||||
{% if show_list and not is_new %}
|
||||
<h1 class="ellipsis">{{ _(web_form_title) }}</h1>
|
||||
<p class="ellipsis">{{ _(title) }}</p>
|
||||
{% else %}
|
||||
<h1 class="ellipsis">{{ _(title) }}</h1>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="indicator-pill orange hide">Not Saved</span>
|
||||
<div class="web-form-actions">
|
||||
{{ header_buttons() }}
|
||||
</div>
|
||||
</div>
|
||||
{% if is_new and introduction_text %}
|
||||
{% if introduction_text and (is_new or in_edit_mode) %}
|
||||
<div class="web-form-introduction">{{ introduction_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -115,30 +118,37 @@
|
|||
|
||||
<!-- success page -->
|
||||
<div class="success-page hide">
|
||||
<svg class="icon">
|
||||
<use href="#icon-solid-success"></use>
|
||||
</svg>
|
||||
<h2 class="success-title">{{ _(success_title) or _("Submitted") }}</h2>
|
||||
<p class="success-message">{{ _(success_message) or _("Thank you for spending your valuable time to fill this form") }}</p>
|
||||
<div class="success-header">
|
||||
<svg class="success-icon icon">
|
||||
<use href="#icon-solid-success"></use>
|
||||
</svg>
|
||||
<h2 class="success-title">{{ _(success_title) or _("Submitted") }}</h2>
|
||||
</div>
|
||||
|
||||
{% if success_url %}
|
||||
<div class="success_url_message">
|
||||
<p>
|
||||
<span>Click on this </span>
|
||||
<a href="{{ success_url }}">{{_("URL")}}</a>
|
||||
<span> if you are not redirected within </span>
|
||||
<span class="time">5</span>
|
||||
<span> seconds.</span>
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if show_list %}
|
||||
<a href="/{{ route }}/list" class="show-list-button btn btn-light btn-md mr-2">{{ _("See previous responses", null, "Button in web form") }}</a>
|
||||
<div class="success-body">
|
||||
<p class="success-message">{{ _(success_message) or _("Thank you for spending your valuable time to fill this form") }}</p>
|
||||
</div>
|
||||
|
||||
<div class="success-footer">
|
||||
{% if success_url %}
|
||||
<div class="success_url_message">
|
||||
<p>
|
||||
<span>Click on this </span>
|
||||
<a href="{{ success_url }}">{{_("URL")}}</a>
|
||||
<span> if you are not redirected within </span>
|
||||
<span class="time">5</span>
|
||||
<span> seconds.</span>
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if show_list %}
|
||||
<a href="/{{ route }}/list" class="show-list-button btn btn-default btn-md">{{ _("See previous responses", null, "Button in web form") }}</a>
|
||||
{% endif %}
|
||||
{% if not login_required or allow_multiple %}
|
||||
<a href="/{{ route }}/new" class="new-btn btn btn-default btn-md">{{ _("Submit another response", null, "Button in web form") }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if not login_required or allow_multiple %}
|
||||
<a href="/{{ route }}/new" class="new-btn btn btn-light btn-md">{{ _("Submit another response", null, "Button in web form") }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock page_content %}
|
||||
|
|
@ -157,10 +167,7 @@
|
|||
Vue.prototype.frappe = window.frappe;
|
||||
</script>
|
||||
|
||||
{{ include_script("controls.bundle.js") }}
|
||||
{{ include_script("dialog.bundle.js") }}
|
||||
{{ include_script("web_form.bundle.js") }}
|
||||
{{ include_script("bootstrap-4-web.bundle.js") }}
|
||||
|
||||
<script>
|
||||
{% if client_script %}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<div class="web-form-skeleton">
|
||||
<div class="box-group">
|
||||
<div class="box-container">
|
||||
<div class="box-container col-sm-6">
|
||||
<div class="box box-label"></div>
|
||||
<div class="box box-area"></div>
|
||||
</div>
|
||||
<div class="box-container">
|
||||
<div class="box-container col-sm-6">
|
||||
<div class="box box-label"></div>
|
||||
<div class="box box-area"></div>
|
||||
</div>
|
||||
|
|
@ -16,21 +16,21 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="box-group">
|
||||
<div class="box-container">
|
||||
<div class="box-container col-sm-6">
|
||||
<div class="box box-label"></div>
|
||||
<div class="box box-area"></div>
|
||||
</div>
|
||||
<div class="box-container">
|
||||
<div class="box-container col-sm-6">
|
||||
<div class="box box-label"></div>
|
||||
<div class="box box-area"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-group">
|
||||
<div class="box-container">
|
||||
<div class="box-container col-sm-6">
|
||||
<div class="box box-label"></div>
|
||||
<div class="box box-area"></div>
|
||||
</div>
|
||||
<div class="box-container">
|
||||
<div class="box-container col-sm-6">
|
||||
<div class="box box-label"></div>
|
||||
<div class="box box-area"></div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
<div class="web-list-container">
|
||||
<!-- list -->
|
||||
<div class="web-list-header">
|
||||
<h1>{{ _(list_title or title) }}</h1>
|
||||
<div class="web-list-title ellipsis">
|
||||
<h1 class="ellipsis">{{ _(list_title or title) }}</h1>
|
||||
</div>
|
||||
<div class="web-list-actions">
|
||||
{%- if allow_multiple -%}
|
||||
<a class="btn btn-primary btn-sm button-new" href="/{{ route }}/new">New</a>
|
||||
|
|
@ -27,10 +29,7 @@
|
|||
frappe.web_form_doc = {{ web_form_doc | json }};
|
||||
</script>
|
||||
|
||||
{{ include_script("controls.bundle.js") }}
|
||||
{{ include_script("dialog.bundle.js") }}
|
||||
{{ include_script("web_form.bundle.js") }}
|
||||
{{ include_script("bootstrap-4-web.bundle.js") }}
|
||||
{% endblock script %}
|
||||
|
||||
{% block style %}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class TestWebForm(FrappeTestCase):
|
|||
def test_webform_render(self):
|
||||
set_request(method="GET", path="manage-events/new")
|
||||
content = get_response_content("manage-events/new")
|
||||
self.assertIn("<h1>New Manage Events</h1>", content)
|
||||
self.assertIn('<h1 class="ellipsis">New Manage Events</h1>', content)
|
||||
self.assertIn('data-doctype="Web Form"', content)
|
||||
self.assertIn('data-path="manage-events/new"', content)
|
||||
self.assertIn('source-type="Generator"', content)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,28 @@
|
|||
frappe.ui.form.on("Web Form", {
|
||||
setup: function () {
|
||||
frappe.meta.docfield_map["Web Form Field"].fieldtype.formatter = (value) => {
|
||||
const prefix = {
|
||||
"Page Break": "--red-600",
|
||||
"Section Break": "--blue-600",
|
||||
"Column Break": "--yellow-600",
|
||||
};
|
||||
if (prefix[value]) {
|
||||
value = `<span class="bold" style="color: var(${prefix[value]})">${value}</span>`;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
frappe.meta.docfield_map["Web Form Field"].fieldname.formatter = (value) => {
|
||||
if (!value) return;
|
||||
return frappe.unscrub(value);
|
||||
};
|
||||
|
||||
frappe.meta.docfield_map["Web Form List Column"].fieldname.formatter = (value) => {
|
||||
if (!value) return;
|
||||
return frappe.unscrub(value);
|
||||
};
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
// show is-standard only if developer mode
|
||||
frm.get_field("is_standard").toggle(frappe.boot.developer_mode);
|
||||
|
|
@ -32,6 +56,14 @@ frappe.ui.form.on("Web Form", {
|
|||
frm.scroll_to_field("web_form_fields");
|
||||
frappe.throw(__("Atleast one field is required in Web Form Fields Table"));
|
||||
}
|
||||
|
||||
let page_break_count = frm.doc.web_form_fields.filter(
|
||||
(f) => f.fieldtype == "Page Break"
|
||||
).length;
|
||||
|
||||
if (page_break_count >= 10) {
|
||||
frappe.throw(__("There can be only 9 Page Break fields in a Web Form"));
|
||||
}
|
||||
},
|
||||
|
||||
add_publish_button(frm) {
|
||||
|
|
@ -97,7 +129,7 @@ frappe.ui.form.on("Web Form", {
|
|||
|
||||
get_fields_for_doctype(doc.doc_type).then((fields) => {
|
||||
let as_select_option = (df) => ({
|
||||
label: df.label + " (" + df.fieldtype + ")",
|
||||
label: df.label,
|
||||
value: df.fieldname,
|
||||
});
|
||||
update_options(fields.map(as_select_option));
|
||||
|
|
@ -147,9 +179,19 @@ frappe.ui.form.on("Web Form List Column", {
|
|||
|
||||
frappe.ui.form.on("Web Form Field", {
|
||||
fieldtype: function (frm, doctype, name) {
|
||||
var doc = frappe.get_doc(doctype, name);
|
||||
let doc = frappe.get_doc(doctype, name);
|
||||
|
||||
if (doc.fieldtype == "Page Break") {
|
||||
let page_break_count = frm.doc.web_form_fields.filter(
|
||||
(f) => f.fieldtype == "Page Break"
|
||||
).length;
|
||||
page_break_count >= 10 &&
|
||||
frappe.throw(__("There can be only 9 Page Break fields in a Web Form"));
|
||||
}
|
||||
|
||||
if (["Section Break", "Column Break", "Page Break"].includes(doc.fieldtype)) {
|
||||
doc.fieldname = "";
|
||||
doc.label = "";
|
||||
doc.options = "";
|
||||
frm.refresh_field("web_form_fields");
|
||||
}
|
||||
|
|
@ -188,23 +230,18 @@ function get_fields_for_doctype(doctype) {
|
|||
function render_list_settings_message(frm) {
|
||||
// render list setting message
|
||||
if (frm.fields_dict["list_setting_message"] && !frm.doc.login_required) {
|
||||
const switch_to_form_settings_tab = `
|
||||
<span class="bold pointer" title="${__("Switch to Form Settings Tab")}">
|
||||
${__("Form Settings Tab")}
|
||||
</span>
|
||||
const go_to_login_required_field = `
|
||||
<code class="pointer" title="${__("Go to Login Required field")}">
|
||||
${__("login_required")}
|
||||
</code>
|
||||
`;
|
||||
let message = __(
|
||||
"Login is required to see web form list view. Enable {0} to see list settings",
|
||||
[go_to_login_required_field]
|
||||
);
|
||||
$(frm.fields_dict["list_setting_message"].wrapper)
|
||||
.html(
|
||||
$(
|
||||
`<div class="form-message blue">
|
||||
${__(
|
||||
"Login is required to see web form list view. Enable <code>login_required</code> from {0} to see list settings",
|
||||
[switch_to_form_settings_tab]
|
||||
)}
|
||||
</div>`
|
||||
)
|
||||
)
|
||||
.find("span")
|
||||
.html($(`<div class="form-message blue">${message}</div>`))
|
||||
.find("code")
|
||||
.click(() => frm.scroll_to_field("login_required"));
|
||||
} else {
|
||||
$(frm.fields_dict["list_setting_message"].wrapper).empty();
|
||||
|
|
|
|||
|
|
@ -5,50 +5,50 @@
|
|||
"document_type": "Document",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title_and_route_tab",
|
||||
"form_tab",
|
||||
"title",
|
||||
"route",
|
||||
"published",
|
||||
"column_break_4",
|
||||
"column_break_1",
|
||||
"doc_type",
|
||||
"module",
|
||||
"is_standard",
|
||||
"introduction",
|
||||
"section_break_1",
|
||||
"introduction_text",
|
||||
"form_settings_tab",
|
||||
"web_form_fields",
|
||||
"settings_tab",
|
||||
"login_required",
|
||||
"allow_multiple",
|
||||
"allow_edit",
|
||||
"allow_delete",
|
||||
"column_break_18",
|
||||
"column_break_2",
|
||||
"apply_document_permissions",
|
||||
"allow_print",
|
||||
"print_format",
|
||||
"allow_comments",
|
||||
"show_attachments",
|
||||
"allow_incomplete",
|
||||
"form_fields",
|
||||
"web_form_fields",
|
||||
"section_break_2",
|
||||
"max_attachment_size",
|
||||
"list_settings_tab",
|
||||
"section_break_3",
|
||||
"list_setting_message",
|
||||
"show_list",
|
||||
"list_title",
|
||||
"list_columns",
|
||||
"sidebar_settings_tab",
|
||||
"section_break_4",
|
||||
"show_sidebar",
|
||||
"website_sidebar",
|
||||
"customization_tab",
|
||||
"button_label",
|
||||
"banner_image",
|
||||
"column_break_37",
|
||||
"column_break_3",
|
||||
"breadcrumbs",
|
||||
"section_break_43",
|
||||
"section_break_5",
|
||||
"success_title",
|
||||
"success_url",
|
||||
"column_break_41",
|
||||
"column_break_4",
|
||||
"success_message",
|
||||
"scripting_style_tab",
|
||||
"section_break_6",
|
||||
"client_script",
|
||||
"custom_css"
|
||||
],
|
||||
|
|
@ -81,10 +81,6 @@
|
|||
"label": "Module",
|
||||
"options": "Module Def"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
|
|
@ -158,12 +154,6 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Allow Incomplete Forms"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "introduction",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Introduction"
|
||||
},
|
||||
{
|
||||
"fieldname": "introduction_text",
|
||||
"fieldtype": "Text Editor",
|
||||
|
|
@ -250,21 +240,6 @@
|
|||
"label": "List Columns",
|
||||
"options": "Web Form List Column"
|
||||
},
|
||||
{
|
||||
"fieldname": "title_and_route_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Title & Route"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "form_fields",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Form Fields"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "website_sidebar",
|
||||
"fieldtype": "Link",
|
||||
|
|
@ -276,29 +251,6 @@
|
|||
"fieldtype": "HTML",
|
||||
"label": "List Setting Message"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Form Settings"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "show_list",
|
||||
"fieldname": "list_settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "List Settings"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "sidebar_settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Sidebar Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "scripting_style_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Scripting / Style"
|
||||
},
|
||||
{
|
||||
"fieldname": "customization_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
|
|
@ -315,24 +267,74 @@
|
|||
"label": "Banner Image"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_41",
|
||||
"fieldname": "form_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Form"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_43",
|
||||
"fieldname": "section_break_1",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "settings_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "show_list",
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "List Settings"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "show_sidebar",
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Sidebar Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.success_title || doc.success_message || doc.success_url",
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "After Submission"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_37",
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.client_script || doc.custom_css",
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Scripting / Style"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
"icon": "icon-edit",
|
||||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"modified": "2022-08-11 16:27:25.914627",
|
||||
"modified": "2022-08-17 18:58:49.451658",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Form",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import frappe
|
|||
from frappe import _, scrub
|
||||
from frappe.core.api.file import get_max_file_size
|
||||
from frappe.core.doctype.file import remove_file_by_url
|
||||
from frappe.custom.doctype.customize_form.customize_form import docfield_properties
|
||||
from frappe.desk.form.meta import get_code_files_via_hooks
|
||||
from frappe.modules.utils import export_module_json, get_doc_module
|
||||
from frappe.rate_limiter import rate_limit
|
||||
|
|
@ -22,8 +21,6 @@ class WebForm(WebsiteGenerator):
|
|||
|
||||
def onload(self):
|
||||
super().onload()
|
||||
if self.is_standard and not frappe.conf.developer_mode:
|
||||
self.use_meta_fields()
|
||||
|
||||
def validate(self):
|
||||
super().validate()
|
||||
|
|
@ -67,31 +64,6 @@ class WebForm(WebsiteGenerator):
|
|||
for df in self.web_form_fields:
|
||||
df.parent = self.doc_type
|
||||
|
||||
def use_meta_fields(self):
|
||||
"""Override default properties for standard web forms"""
|
||||
meta = frappe.get_meta(self.doc_type)
|
||||
|
||||
for df in self.web_form_fields:
|
||||
meta_df = meta.get_field(df.fieldname)
|
||||
|
||||
if not meta_df:
|
||||
continue
|
||||
|
||||
for prop in docfield_properties:
|
||||
if df.fieldtype == meta_df.fieldtype and prop not in (
|
||||
"idx",
|
||||
"reqd",
|
||||
"default",
|
||||
"description",
|
||||
"options",
|
||||
"hidden",
|
||||
"read_only",
|
||||
"label",
|
||||
):
|
||||
df.set(prop, meta_df.get(prop))
|
||||
|
||||
# TODO translate options of Select fields like Country
|
||||
|
||||
# export
|
||||
def on_update(self):
|
||||
"""
|
||||
|
|
@ -124,7 +96,8 @@ def get_context(context):
|
|||
|
||||
def get_context(self, context):
|
||||
"""Build context to render the `web_form.html` template"""
|
||||
context.is_form_editable = False
|
||||
context.in_edit_mode = False
|
||||
context.in_view_mode = False
|
||||
self.set_web_form_module()
|
||||
|
||||
if frappe.form_dict.is_list:
|
||||
|
|
@ -156,10 +129,14 @@ def get_context(context):
|
|||
frappe.redirect(f"/{self.route}/new")
|
||||
|
||||
if frappe.form_dict.is_edit and not self.allow_edit:
|
||||
context.in_view_mode = True
|
||||
frappe.redirect(f"/{self.route}/{frappe.form_dict.name}")
|
||||
|
||||
if frappe.form_dict.is_edit:
|
||||
context.is_form_editable = True
|
||||
context.in_edit_mode = True
|
||||
|
||||
if frappe.form_dict.is_read:
|
||||
context.in_view_mode = True
|
||||
|
||||
if (
|
||||
not frappe.form_dict.is_edit
|
||||
|
|
@ -167,7 +144,7 @@ def get_context(context):
|
|||
and self.allow_edit
|
||||
and frappe.form_dict.name
|
||||
):
|
||||
context.is_form_editable = True
|
||||
context.in_edit_mode = True
|
||||
frappe.redirect(f"/{frappe.local.path}/edit")
|
||||
|
||||
if (
|
||||
|
|
@ -179,6 +156,7 @@ def get_context(context):
|
|||
):
|
||||
name = frappe.db.get_value(self.doc_type, {"owner": frappe.session.user}, "name")
|
||||
if name:
|
||||
context.in_view_mode = True
|
||||
frappe.redirect(f"/{self.route}/{name}")
|
||||
|
||||
# Show new form when
|
||||
|
|
@ -190,9 +168,6 @@ def get_context(context):
|
|||
|
||||
self.reset_field_parent()
|
||||
|
||||
if self.is_standard:
|
||||
self.use_meta_fields()
|
||||
|
||||
# add keys from form_dict to context
|
||||
context.update(dict_with_keys(frappe.form_dict, ["is_list", "is_new", "is_edit", "is_read"]))
|
||||
|
||||
|
|
@ -203,7 +178,9 @@ def get_context(context):
|
|||
|
||||
# load web form doc
|
||||
context.web_form_doc = self.as_dict(no_nulls=True)
|
||||
context.web_form_doc.update(dict_with_keys(context, ["is_list", "is_new", "is_form_editable"]))
|
||||
context.web_form_doc.update(
|
||||
dict_with_keys(context, ["is_list", "is_new", "in_edit_mode", "in_view_mode"])
|
||||
)
|
||||
|
||||
if self.show_sidebar and self.website_sidebar:
|
||||
context.sidebar_items = get_sidebar_items(self.website_sidebar)
|
||||
|
|
@ -278,17 +255,11 @@ def get_context(context):
|
|||
if frappe.form_dict.name:
|
||||
context.doc_name = frappe.form_dict.name
|
||||
context.reference_doc = frappe.get_doc(self.doc_type, context.doc_name)
|
||||
context.title = strip_html(
|
||||
context.reference_doc.get(context.reference_doc.meta.get_title_field())
|
||||
context.web_form_title = context.title
|
||||
context.title = (
|
||||
strip_html(context.reference_doc.get(context.reference_doc.meta.get_title_field()))
|
||||
or context.doc_name
|
||||
)
|
||||
if context.is_form_editable and context.parents:
|
||||
context.parents.append(
|
||||
{
|
||||
"label": _(context.title),
|
||||
"route": f"{self.route}/{context.doc_name}",
|
||||
}
|
||||
)
|
||||
context.title = _("Editing {0}").format(context.title)
|
||||
context.reference_doc.add_seen()
|
||||
context.reference_doctype = context.reference_doc.doctype
|
||||
context.reference_name = context.reference_doc.name
|
||||
|
|
@ -309,7 +280,7 @@ def get_context(context):
|
|||
context.reference_doc.doctype, context.reference_doc.name
|
||||
)
|
||||
|
||||
context.reference_doc = json.loads(context.reference_doc.as_json())
|
||||
context.reference_doc = context.reference_doc.as_dict(no_nulls=True)
|
||||
|
||||
def add_custom_context_and_script(self, context):
|
||||
"""Update context from module if standard and append script"""
|
||||
|
|
@ -341,62 +312,6 @@ def get_context(context):
|
|||
|
||||
context.style = style
|
||||
|
||||
def get_layout(self):
|
||||
layout = []
|
||||
|
||||
def add_page(df=None):
|
||||
new_page = {"sections": []}
|
||||
layout.append(new_page)
|
||||
if df and df.fieldtype == "Page Break":
|
||||
new_page.update(df.as_dict())
|
||||
|
||||
return new_page
|
||||
|
||||
def add_section(df=None):
|
||||
new_section = {"columns": []}
|
||||
if layout:
|
||||
layout[-1]["sections"].append(new_section)
|
||||
if df and df.fieldtype == "Section Break":
|
||||
new_section.update(df.as_dict())
|
||||
|
||||
return new_section
|
||||
|
||||
def add_column(df=None):
|
||||
new_col = []
|
||||
if layout:
|
||||
layout[-1]["sections"][-1]["columns"].append(new_col)
|
||||
|
||||
return new_col
|
||||
|
||||
page, section, column = None, None, None
|
||||
for df in self.web_form_fields:
|
||||
|
||||
# breaks
|
||||
if df.fieldtype == "Page Break":
|
||||
page = add_page(df)
|
||||
section, column = None, None
|
||||
|
||||
if df.fieldtype == "Section Break":
|
||||
section = add_section(df)
|
||||
column = None
|
||||
|
||||
if df.fieldtype == "Column Break":
|
||||
column = add_column(df)
|
||||
|
||||
# input
|
||||
if df.fieldtype not in ("Section Break", "Column Break", "Page Break"):
|
||||
if not page:
|
||||
page = add_page()
|
||||
section, column = None, None
|
||||
if not section:
|
||||
section = add_section()
|
||||
column = None
|
||||
if column is None:
|
||||
column = add_column()
|
||||
column.append(df)
|
||||
|
||||
return layout
|
||||
|
||||
def get_parents(self, context):
|
||||
parents = None
|
||||
|
||||
|
|
@ -481,7 +396,7 @@ def accept(web_form, data, docname=None):
|
|||
for field in web_form.web_form_fields:
|
||||
fieldname = field.fieldname
|
||||
df = meta.get_field(fieldname)
|
||||
value = data.get(fieldname, None)
|
||||
value = data.get(fieldname, "")
|
||||
|
||||
if df and df.fieldtype in ("Attach", "Attach Image"):
|
||||
if value and "data:" and "base64" in value:
|
||||
|
|
@ -597,17 +512,6 @@ def get_web_form_filters(web_form_name):
|
|||
return [field for field in web_form.web_form_fields if field.show_in_filter]
|
||||
|
||||
|
||||
def make_route_string(parameters):
|
||||
route_string = ""
|
||||
delimeter = "?"
|
||||
if isinstance(parameters, dict):
|
||||
for key in parameters:
|
||||
if key != "web_form_name":
|
||||
route_string += route_string + delimeter + key + "=" + cstr(parameters[key])
|
||||
delimeter = "&"
|
||||
return (route_string, delimeter)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_form_data(doctype, docname=None, web_form_name=None):
|
||||
web_form = frappe.get_doc("Web Form", web_form_name)
|
||||
|
|
|
|||
|
|
@ -32,20 +32,20 @@
|
|||
"fieldname": "fieldname",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldname"
|
||||
"label": "Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldtype",
|
||||
"options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nDuration\nFloat\nHTML\nInt\nLink\nPassword\nRating\nSelect\nSignature\nSmall Text\nText\nText Editor\nTable\nTime\nSection Break\nColumn Break\nPage Break"
|
||||
"options": "Attach\nAttach Image\nCheck\nCurrency\nColor\nData\nDate\nDatetime\nDuration\nFloat\nHTML\nInt\nLink\nPassword\nRating\nSelect\nSignature\nSmall Text\nText\nText Editor\nTable\nTime\nSection Break\nColumn Break\nPage Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label"
|
||||
"label": "Custom Label"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -58,6 +58,7 @@
|
|||
"default": "0",
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Mandatory"
|
||||
},
|
||||
{
|
||||
|
|
@ -146,7 +147,7 @@
|
|||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-08-10 12:59:51.170546",
|
||||
"modified": "2022-08-22 17:22:39.026893",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Form Field",
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@
|
|||
"fieldname": "fieldname",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldname",
|
||||
"label": "Field",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label"
|
||||
"label": "Custom Label"
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldtype",
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-21 17:22:14.978947",
|
||||
"modified": "2022-08-17 19:09:01.417841",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Website",
|
||||
"name": "Web Form List Column",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,29 @@
|
|||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
# import frappe
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.website.doctype.website_settings.website_settings import get_website_settings
|
||||
|
||||
|
||||
class TestWebsiteSettings(FrappeTestCase):
|
||||
pass
|
||||
def test_child_items_in_top_bar(self):
|
||||
ws = frappe.get_doc("Website Settings")
|
||||
ws.append(
|
||||
"top_bar_items",
|
||||
{"label": "Parent Item"},
|
||||
)
|
||||
ws.append(
|
||||
"top_bar_items",
|
||||
{"parent_label": "Parent Item", "label": "Child Item"},
|
||||
)
|
||||
ws.save()
|
||||
|
||||
context = get_website_settings()
|
||||
|
||||
for item in context.top_bar_items:
|
||||
if item.label == "Parent Item":
|
||||
self.assertEqual(item.child_items[0].label, "Child Item")
|
||||
break
|
||||
else:
|
||||
self.fail("Child items not found")
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ def modify_header_footer_items(items: list):
|
|||
continue
|
||||
|
||||
if not top_bar_item.get("child_items"):
|
||||
top_bar_item["child_items"] = []
|
||||
top_bar_item.child_items = []
|
||||
|
||||
top_bar_item.child_items.append(item)
|
||||
break
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue