Merge branch 'frappe:develop' into tabs_on_grid_row_form
This commit is contained in:
commit
5df1c3c211
49 changed files with 10680 additions and 9511 deletions
|
|
@ -11,10 +11,7 @@ context("Awesome Bar", () => {
|
|||
|
||||
beforeEach(() => {
|
||||
cy.get("body").click(0, 0); // Click on some blank space to avoid any modals.
|
||||
let txt = `Search or type a command (${
|
||||
window.navigator.platform === "MacIntel" ? "⌘" : "Ctrl"
|
||||
} + K)`;
|
||||
cy.contains(txt).as("awesome_bar_search");
|
||||
cy.get("#navbar-modal-search").as("awesome_bar_search");
|
||||
cy.get("@awesome_bar_search").click();
|
||||
cy.get("#navbar-search").as("awesome_bar");
|
||||
cy.get("#navbar-search").type("{selectall}");
|
||||
|
|
|
|||
|
|
@ -416,8 +416,8 @@ def validate_link(doctype: str, docname: str, fields=None):
|
|||
if not isinstance(docname, str):
|
||||
frappe.throw(_("Document Name must be a string"))
|
||||
|
||||
parent_doctype = None
|
||||
if doctype != "DocType":
|
||||
parent_doctype = None
|
||||
if frappe.get_meta(doctype).istable: # needed for links to child rows
|
||||
parent_doctype = frappe.db.get_value(doctype, docname, "parenttype")
|
||||
if not (
|
||||
|
|
@ -453,7 +453,7 @@ def validate_link(doctype: str, docname: str, fields=None):
|
|||
return values
|
||||
|
||||
try:
|
||||
values.update(get_value(doctype, fields, docname))
|
||||
values.update(get_value(doctype, fields, docname, parent=parent_doctype))
|
||||
except frappe.PermissionError:
|
||||
frappe.clear_last_message()
|
||||
frappe.msgprint(
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ class CommunicationEmailMixin:
|
|||
)
|
||||
bcc = self.get_mail_bcc_with_displayname(is_inbound_mail_communcation=is_inbound_mail_communcation)
|
||||
|
||||
if not (recipients or cc):
|
||||
if not (recipients or cc or bcc):
|
||||
return {}
|
||||
|
||||
final_attachments = self.mail_attachments(
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ class DataImport(Document):
|
|||
if not self.google_sheets_url:
|
||||
return
|
||||
validate_google_sheets_url(self.google_sheets_url)
|
||||
self.get_importer()
|
||||
|
||||
def set_payload_count(self, importer: Importer | None = None):
|
||||
if self.import_file:
|
||||
|
|
|
|||
|
|
@ -483,6 +483,35 @@ class ImportFile:
|
|||
title=_("Template Error"),
|
||||
)
|
||||
|
||||
def validate_columns_of_import_file(self, data):
|
||||
mandatory_fields = self.get_mandatory_fields()
|
||||
headers = data[0] if data else []
|
||||
|
||||
if len(headers) == 1 and ";" in headers[0]:
|
||||
return
|
||||
|
||||
if not len(headers):
|
||||
frappe.throw(_("Import template should contain a Header row."), title=_("Template Error"))
|
||||
|
||||
for field in mandatory_fields:
|
||||
if field not in headers:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Mandatory field {0} is missing in the import template for {1}. Please correct the template and try again."
|
||||
).format(frappe.bold(field), frappe.bold(self.doctype)),
|
||||
title=_("Template Error"),
|
||||
)
|
||||
|
||||
def get_mandatory_fields(self):
|
||||
meta = frappe.get_meta(self.doctype)
|
||||
mandatory_fields = []
|
||||
|
||||
for df in meta.fields:
|
||||
if df.reqd and df.fieldtype not in no_value_fields:
|
||||
mandatory_fields.append(df.label)
|
||||
|
||||
return mandatory_fields
|
||||
|
||||
def get_data_for_import_preview(self):
|
||||
"""Adds a serial number column as the first column"""
|
||||
|
||||
|
|
@ -618,6 +647,7 @@ class ImportFile:
|
|||
elif extension == "xls":
|
||||
data = read_xls_file_from_attached_file(content)
|
||||
|
||||
self.validate_columns_of_import_file(data)
|
||||
return data
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@
|
|||
"system_updates_section",
|
||||
"disable_system_update_notification",
|
||||
"disable_change_log_notification",
|
||||
"column_break_ewhs",
|
||||
"hide_empty_read_only_fields",
|
||||
"disable_product_suggestion",
|
||||
"backups_tab",
|
||||
|
|
@ -777,12 +778,16 @@
|
|||
"fieldname": "disable_product_suggestion",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Product Suggestion"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ewhs",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-23 13:17:57.577690",
|
||||
"modified": "2025-12-01 00:12:17.823242",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
|
|||
|
|
@ -32,24 +32,22 @@
|
|||
|
||||
.desktop-search-wrapper{
|
||||
flex: 1;
|
||||
max-width: 396px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#navbar-modal-search{
|
||||
padding-left: 32px;
|
||||
.desktop-search-wrapper span {
|
||||
color: var(--text-light);
|
||||
}
|
||||
.desktop-search-icon{
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 4px;
|
||||
|
||||
#navbar-modal-search{
|
||||
background-color: var(--control-bg);
|
||||
}
|
||||
#brand-logo{
|
||||
width: auto;
|
||||
}
|
||||
.desktop-search-icon > .icon {
|
||||
stroke: var(--ink-gray-4);
|
||||
stroke-width: 1px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.desktop-container{
|
||||
|
|
|
|||
|
|
@ -8,17 +8,24 @@
|
|||
alt="{{ _("App Logo") |e }}"
|
||||
>
|
||||
</div>
|
||||
<div class="desktop-search-wrapper input-group search-bar text-muted ">
|
||||
<div id="navbar-modal-search">
|
||||
Search or type a command
|
||||
</div>
|
||||
<div class="desktop-search-wrapper input-group search-bar">
|
||||
<button
|
||||
id="navbar-modal-search"
|
||||
class="btn-reset flex justify-between"
|
||||
title="Search"
|
||||
>
|
||||
<span class="desktop-search-icon">
|
||||
<svg class="icon icon-sm"><use href="#icon-search"></use></svg>
|
||||
Search
|
||||
</span>
|
||||
<span>
|
||||
{{ "⌘ K" if is_mac else "Ctrl K" }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<span class="desktop-avatar" style="margin-left: -10px;">
|
||||
<div class="desktop-avatar" style="margin-left: -10px;">
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</header>
|
||||
<div class="desktop-container">
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import sys
|
||||
|
||||
import frappe
|
||||
from frappe.desk.doctype.desktop_icon.desktop_icon import get_desktop_icons
|
||||
|
||||
|
|
@ -13,4 +15,6 @@ def get_context(context):
|
|||
context.brand_logo = brand_logo
|
||||
context.desktop_icons = get_desktop_icons()
|
||||
context.current_user = frappe.session.user
|
||||
# check if system is mac or not
|
||||
context.is_mac = sys.platform == "darwin"
|
||||
return context
|
||||
|
|
|
|||
|
|
@ -664,7 +664,7 @@ class QueueBuilder:
|
|||
if self._unsubscribed_user_emails is not None:
|
||||
return self._unsubscribed_user_emails
|
||||
|
||||
all_ids = list(set(self.recipients + self.cc))
|
||||
all_ids = list(set(self.recipients + self.cc + self.bcc))
|
||||
|
||||
EmailUnsubscribe = DocType("Email Unsubscribe")
|
||||
|
||||
|
|
@ -698,6 +698,10 @@ class QueueBuilder:
|
|||
unsubscribed_emails = self.get_unsubscribed_user_emails()
|
||||
return [mail_id for mail_id in self.cc if mail_id not in unsubscribed_emails]
|
||||
|
||||
def final_bcc(self):
|
||||
unsubscribed_emails = self.get_unsubscribed_user_emails()
|
||||
return [mail_id for mail_id in self.bcc if mail_id not in unsubscribed_emails]
|
||||
|
||||
def get_attachments(self):
|
||||
attachments = []
|
||||
if self._attachments:
|
||||
|
|
@ -725,7 +729,7 @@ class QueueBuilder:
|
|||
attachments=self._attachments,
|
||||
reply_to=self.reply_to,
|
||||
cc=self.final_cc(),
|
||||
bcc=self.bcc,
|
||||
bcc=self.final_bcc(),
|
||||
email_account=email_account,
|
||||
expose_recipients=self.expose_recipients,
|
||||
inline_images=self.inline_images,
|
||||
|
|
@ -752,7 +756,7 @@ class QueueBuilder:
|
|||
"""
|
||||
final_recipients = self.final_recipients()
|
||||
queue_separately = (final_recipients and self.queue_separately) or len(final_recipients) > 100
|
||||
if not (final_recipients + self.final_cc()):
|
||||
if not (final_recipients + self.final_cc() + self.final_bcc()):
|
||||
return []
|
||||
|
||||
queue_data = self.as_dict(include_recipients=False)
|
||||
|
|
@ -760,7 +764,7 @@ class QueueBuilder:
|
|||
return []
|
||||
|
||||
if not queue_separately:
|
||||
recipients = list(set(final_recipients + self.final_cc() + self.bcc))
|
||||
recipients = list(set(final_recipients + self.final_cc() + self.final_bcc()))
|
||||
q = EmailQueue.new({**queue_data, **{"recipients": recipients}}, ignore_permissions=True)
|
||||
send_now and q.send()
|
||||
return q
|
||||
|
|
@ -786,7 +790,7 @@ class QueueBuilder:
|
|||
frappe_mail_client = None
|
||||
smtp_server_instance = None
|
||||
for r in final_recipients:
|
||||
recipients = list(set([r, *self.final_cc(), *self.bcc]))
|
||||
recipients = list(set([r, *self.final_cc(), *self.final_bcc()]))
|
||||
q = EmailQueue.new({**queue_data, **{"recipients": recipients}}, ignore_permissions=True)
|
||||
if not frappe_mail_client and not smtp_server_instance:
|
||||
email_account = q.get_email_account(raise_error=True)
|
||||
|
|
@ -836,7 +840,7 @@ class QueueBuilder:
|
|||
"communication": self.communication,
|
||||
"send_after": self.send_after,
|
||||
"show_as_cc": ",".join(self.final_cc()),
|
||||
"show_as_bcc": ",".join(self.bcc),
|
||||
"show_as_bcc": ",".join(self.final_bcc()),
|
||||
"email_account": email_account_name or None,
|
||||
"email_read_tracker_url": self.email_read_tracker_url,
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -160,7 +160,12 @@ function get_version_timeline_content(version_doc, frm) {
|
|||
) {
|
||||
parts.push(
|
||||
__("{0} from {1} to {2} in row #{3}", [
|
||||
frappe.meta.get_label(frm.fields_dict[row[0]].grid.doctype, p[0]),
|
||||
__(
|
||||
frappe.meta.get_label(
|
||||
frm.fields_dict[row[0]].grid.doctype,
|
||||
p[0]
|
||||
)
|
||||
),
|
||||
format_content_for_timeline(p[1]),
|
||||
format_content_for_timeline(p[2]),
|
||||
row[1] + 1,
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@ frappe.ui.menu = class ContextMenu {
|
|||
this.add_menu_item(f);
|
||||
});
|
||||
|
||||
if (!$.contains(document.body, this.template[0])) {
|
||||
$(document.body).append(this.template);
|
||||
}
|
||||
// if (!$.contains(document.body, this.template[0])) {
|
||||
// $(document.body).append(this.template);
|
||||
// }
|
||||
$(document.body).append(this.template);
|
||||
}
|
||||
add_menu_item(item) {
|
||||
const me = this;
|
||||
|
|
@ -36,21 +37,40 @@ frappe.ui.menu = class ContextMenu {
|
|||
}
|
||||
</div>
|
||||
<span class="menu-item-title">${item.label}</span>
|
||||
<div class="menu-item-icon" style="margin-left:auto">
|
||||
${item.items && item.items.length ? frappe.utils.icon("chevron-right") : ""}
|
||||
</div>
|
||||
|
||||
</a>
|
||||
</div>`);
|
||||
if (!item.url) {
|
||||
item_wrapper.on("click", function () {
|
||||
item.onClick();
|
||||
me.opts.onItemClick && me.opts.onItemClick(me.opts.parent);
|
||||
me.hide();
|
||||
item.onClick && item.onClick();
|
||||
if (!(item.items && item.items.length)) {
|
||||
me.opts.onItemClick && me.opts.onItemClick(me.opts.parent);
|
||||
me.hide();
|
||||
}
|
||||
});
|
||||
} else if (item.items) {
|
||||
$();
|
||||
} else {
|
||||
$(item_wrapper).find("a").attr("href", item.url);
|
||||
}
|
||||
item_wrapper.appendTo(this.template);
|
||||
if (item.items) {
|
||||
this.handle_nested_menu(item_wrapper, item);
|
||||
}
|
||||
}
|
||||
handle_nested_menu(item_wrapper, item) {
|
||||
frappe.ui.create_menu({
|
||||
parent: item_wrapper,
|
||||
menu_items: item.items,
|
||||
nested: true,
|
||||
parent_menu: this.name,
|
||||
});
|
||||
}
|
||||
show(parent) {
|
||||
this.close_all_other_menu();
|
||||
// this.close_all_other_menu();
|
||||
|
||||
this.make();
|
||||
|
||||
|
|
@ -58,12 +78,25 @@ frappe.ui.menu = class ContextMenu {
|
|||
const height = $(parent).outerHeight();
|
||||
this.left_offset = 0;
|
||||
this.gap = 4;
|
||||
this.template.css({
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
top: offset.top + height + this.gap + "px",
|
||||
left: offset.left,
|
||||
});
|
||||
if (this.opts.nested && this.opts.parent_menu) {
|
||||
let dropdown = frappe.menu_map[this.opts.parent_menu].template;
|
||||
let width = dropdown.outerWidth();
|
||||
let offset = $(dropdown).offset();
|
||||
this.template.css({
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
top: offset.top + "px",
|
||||
left: offset.left + width + this.gap + "px",
|
||||
});
|
||||
} else {
|
||||
this.template.css({
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
top: offset.top + height + this.gap + "px",
|
||||
left: offset.left,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.open_on_left) {
|
||||
this.left_offset = parent.getBoundingClientRect().width;
|
||||
this.template.css({
|
||||
|
|
@ -151,12 +184,14 @@ frappe.ui.create_menu = function (opts) {
|
|||
$(document).on("click", function () {
|
||||
if (frappe.menu_map[context_menu.name].visible) {
|
||||
frappe.menu_map[context_menu.name].hide();
|
||||
opts.onHide && opts.onHide(opts.parent);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("keydown", function (e) {
|
||||
if (e.key === "Escape" && frappe.menu_map[context_menu.name].visible) {
|
||||
frappe.menu_map[context_menu.name].hide();
|
||||
opts.onHide && opts.onHide(opts.parent);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ frappe.ui.Sidebar = class Sidebar {
|
|||
for (const app of frappe.boot.app_data) {
|
||||
if (app.workspaces.includes(this.workspace_title)) {
|
||||
this.header_subtitle = app.app_title;
|
||||
frappe.current_app = app;
|
||||
this.app_logo_url = app.app_logo_url;
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,14 @@ frappe.ui.SidebarHeader = class SidebarHeader {
|
|||
this.drop_down_expanded = false;
|
||||
this.workspace_title = this.sidebar.workspace_title;
|
||||
const me = this;
|
||||
this.fetch;
|
||||
this.dropdown_items = [
|
||||
{
|
||||
name: "workspaces",
|
||||
label: "Workspaces",
|
||||
icon: "wallpaper",
|
||||
items: this.fetch_sibling_workspaces(),
|
||||
},
|
||||
{
|
||||
name: "desktop",
|
||||
label: __("Desktop"),
|
||||
|
|
@ -37,7 +44,21 @@ frappe.ui.SidebarHeader = class SidebarHeader {
|
|||
this.populate_dropdown_menu();
|
||||
this.setup_select_options();
|
||||
}
|
||||
|
||||
fetch_sibling_workspaces() {
|
||||
let sibling_workspaces = [];
|
||||
let workspaces = frappe.current_app.workspaces;
|
||||
workspaces.splice(workspaces.indexOf(this.workspace_title), 1);
|
||||
workspaces.forEach((w) => {
|
||||
let item = {
|
||||
name: w.toLowerCase(),
|
||||
label: w,
|
||||
icon: "wallpaper",
|
||||
url: frappe.utils.generate_route({ type: "Workspace", route: w.toLowerCase() }),
|
||||
};
|
||||
sibling_workspaces.push(item);
|
||||
});
|
||||
return sibling_workspaces;
|
||||
}
|
||||
make() {
|
||||
$(".sidebar-header").remove();
|
||||
$(".sidebar-header-menu").remove();
|
||||
|
|
|
|||
|
|
@ -246,6 +246,10 @@ frappe.ui.sidebar_item.TypeSectionBreak = class SectionBreakSidebarItem extends
|
|||
if (e.originalEvent.isTrusted) {
|
||||
me.save_section_break_state();
|
||||
}
|
||||
if (!frappe.app.sidebar.sidebar_expanded) {
|
||||
frappe.app.sidebar.open();
|
||||
this.open();
|
||||
}
|
||||
});
|
||||
}
|
||||
save_section_break_state() {
|
||||
|
|
|
|||
|
|
@ -238,6 +238,11 @@ frappe.search.AwesomeBar = class AwesomeBar {
|
|||
__("module name...") +
|
||||
"</td></tr>\
|
||||
<tr><td>" +
|
||||
__("Open in new tab") +
|
||||
"</td><td>" +
|
||||
(frappe.utils.is_mac() ? "⌘ + Enter" : "Ctrl + Enter") +
|
||||
"</td></tr>\
|
||||
<tr><td>" +
|
||||
__("Calculate") +
|
||||
"</td><td>" +
|
||||
__("e.g. (55 + 434) / 4 or =Math.sin(Math.PI/2)...") +
|
||||
|
|
|
|||
|
|
@ -10,31 +10,17 @@
|
|||
</a>
|
||||
<ul class="nav navbar-nav d-none d-sm-flex" id="navbar-breadcrumbs"></ul>
|
||||
<div class="collapse navbar-collapse justify-content-end">
|
||||
<form class="form-inline fill-width justify-content-end" role="search" onsubmit="return false;">
|
||||
{% if (frappe.boot.read_only) { %}
|
||||
<span class="indicator-pill yellow no-indicator-dot read-only-banner" title="{%= __("Your site is undergoing maintenance or being updated.") %}">
|
||||
{%= __("Read Only Mode") %}
|
||||
</span>
|
||||
{% } %}
|
||||
{% if (frappe.boot.user.impersonated_by) { %}
|
||||
<span class="indicator-pill red no-indicator-dot" title="{%= __("You are impersonating as another user.") %}">
|
||||
{%= __("Impersonating {0}", [frappe.boot.user.name]) %}
|
||||
</span>
|
||||
{% } %}
|
||||
<div class="input-group search-bar text-muted hidden">
|
||||
<div
|
||||
id="navbar-modal-search"
|
||||
class=""
|
||||
placeholder="Search for type a command"
|
||||
>
|
||||
{%= __('Search or type a command ({0})', [frappe.utils.is_mac() ? '⌘ + K' : 'Ctrl + K']) %}
|
||||
</div>
|
||||
<span class="search-icon">
|
||||
<svg class="icon icon-sm"><use href="#icon-search"></use></svg>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="navbar-nav">
|
||||
<li>
|
||||
<button
|
||||
id="navbar-modal-search"
|
||||
class="btn-reset text-muted"
|
||||
>
|
||||
<span class="search-icon">
|
||||
<svg class="icon icon-sm"><use href="#icon-search"></use></svg>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item dropdown dropdown-notifications dropdown-mobile hidden">
|
||||
<button
|
||||
class="btn-reset nav-link notifications-icon text-muted"
|
||||
|
|
|
|||
|
|
@ -519,9 +519,14 @@
|
|||
max-width: var(--page-max-width);
|
||||
}
|
||||
|
||||
// Adjust position when sidebar is expanded to avoid collision
|
||||
.body-sidebar-container.expanded ~ .main-section & {
|
||||
left: calc(50% + var(--sidebar-width, 220px) / 2);
|
||||
}
|
||||
|
||||
.grid-form-body {
|
||||
max-height: 80vh;
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,5 @@
|
|||
#navbar-modal-search {
|
||||
border-radius: var(--border-radius-sm);
|
||||
padding: var(--input-padding);
|
||||
background-color: var(--control-bg);
|
||||
width: 100%;
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,24 @@ class TestClient(IntegrationTestCase):
|
|||
validate_link("User", "Guest", fields=["enabled"]), {"name": "Guest", "enabled": 1}
|
||||
)
|
||||
|
||||
def test_validate_link_child_table(self):
|
||||
"""
|
||||
Test validate_link works for child table doctypes with field fetch.
|
||||
"""
|
||||
from frappe.client import validate_link
|
||||
|
||||
self.addCleanup(frappe.db.rollback)
|
||||
|
||||
user = frappe.get_doc("User", "Administrator")
|
||||
user.append("block_modules", {"module": "Setup"})
|
||||
user.save()
|
||||
|
||||
child_row = user.block_modules[-1]
|
||||
|
||||
result = validate_link("Block Module", child_row.name, fields=["module"])
|
||||
self.assertEqual(result.get("name"), child_row.name)
|
||||
self.assertEqual(result.get("module"), "Setup")
|
||||
|
||||
def test_client_insert(self):
|
||||
from frappe.client import insert
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue