Merge remote-tracking branch 'upstream/develop' into list-padding
This commit is contained in:
commit
2c8dbb730a
73 changed files with 49917 additions and 8001 deletions
|
|
@ -1,44 +0,0 @@
|
|||
context("API Resources", () => {
|
||||
before(() => {
|
||||
cy.visit("/login");
|
||||
cy.login();
|
||||
cy.visit("/app/website");
|
||||
});
|
||||
|
||||
it("Creates two Comments", () => {
|
||||
cy.insert_doc("Comment", { comment_type: "Comment", content: "hello" });
|
||||
cy.insert_doc("Comment", { comment_type: "Comment", content: "world" });
|
||||
});
|
||||
|
||||
it("Lists the Comments", () => {
|
||||
cy.get_list("Comment")
|
||||
.its("data")
|
||||
.then((data) => expect(data.length).to.be.at.least(2));
|
||||
|
||||
cy.get_list("Comment", ["name", "content"], [["content", "=", "hello"]]).then((body) => {
|
||||
expect(body).to.have.property("data");
|
||||
expect(body.data).to.have.lengthOf(1);
|
||||
expect(body.data[0]).to.have.property("content");
|
||||
expect(body.data[0]).to.have.property("name");
|
||||
});
|
||||
});
|
||||
|
||||
it("Gets each Comment", () => {
|
||||
cy.get_list("Comment").then((body) =>
|
||||
body.data.forEach((comment) => {
|
||||
cy.get_doc("Comment", comment.name);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("Removes the Comments", () => {
|
||||
cy.get_list("Comment").then((body) => {
|
||||
let comment_names = [];
|
||||
body.data.map((comment) => comment_names.push(comment.name));
|
||||
comment_names = [...new Set(comment_names)]; // remove duplicates
|
||||
comment_names.forEach((comment_name) => {
|
||||
cy.remove_doc("Comment", comment_name);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -22,21 +22,19 @@ context("List View", () => {
|
|||
const actions = [
|
||||
"Approve",
|
||||
"Reject",
|
||||
"Edit",
|
||||
"Export",
|
||||
"Assign To",
|
||||
"Clear Assignment",
|
||||
"Apply Assignment Rule",
|
||||
"Add Tags",
|
||||
"Print",
|
||||
"Delete",
|
||||
];
|
||||
cy.go_to_list("ToDo");
|
||||
cy.clear_filters();
|
||||
cy.get(".list-header-subject > .list-subject > .list-check-all").click();
|
||||
cy.findByRole("button", { name: "Actions" }).click();
|
||||
cy.get(".dropdown-menu li:visible .dropdown-item")
|
||||
.should("have.length", 10)
|
||||
.should("have.length", 8)
|
||||
.each((el, index) => {
|
||||
cy.wrap(el).contains(actions[index]);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -340,6 +340,8 @@ class LoginManager:
|
|||
if user == frappe.session.user:
|
||||
delete_session(frappe.session.sid, user=user, reason="User Manually Logged Out")
|
||||
self.clear_cookies()
|
||||
if frappe.request:
|
||||
self.login_as_guest()
|
||||
else:
|
||||
clear_sessions(user)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import frappe
|
|||
import frappe.defaults
|
||||
import frappe.desk.desk_page
|
||||
from frappe.core.doctype.navbar_settings.navbar_settings import get_app_logo, get_navbar_settings
|
||||
from frappe.desk.doctype.changelog_feed.changelog_feed import get_changelog_feed_items
|
||||
from frappe.desk.doctype.form_tour.form_tour import get_onboarding_ui_tours
|
||||
from frappe.desk.doctype.route_history.route_history import frequently_visited_links
|
||||
from frappe.desk.form.load import get_meta_bundle
|
||||
|
|
@ -107,6 +108,7 @@ def get_bootinfo():
|
|||
bootinfo.translated_doctypes = get_translated_doctypes()
|
||||
bootinfo.subscription_conf = add_subscription_conf()
|
||||
bootinfo.marketplace_apps = get_marketplace_apps()
|
||||
bootinfo.changelog_feed = get_changelog_feed_items()
|
||||
|
||||
return bootinfo
|
||||
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ def get_address_display_list(doctype: str, name: str) -> list[dict]:
|
|||
["Dynamic Link", "parenttype", "=", "Address"],
|
||||
],
|
||||
fields=["*"],
|
||||
order_by="is_primary_address DESC, creation ASC",
|
||||
order_by="is_primary_address DESC, `tabAddress`.creation ASC",
|
||||
)
|
||||
for a in address_list:
|
||||
a["display"] = get_address_display(a)
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ def get_contact_display_list(doctype: str, name: str) -> list[dict]:
|
|||
["Dynamic Link", "parenttype", "=", "Contact"],
|
||||
],
|
||||
fields=["*"],
|
||||
order_by="is_primary_contact DESC, creation ASC",
|
||||
order_by="is_primary_contact DESC, `tabContact`.creation ASC",
|
||||
)
|
||||
|
||||
for contact in contact_list:
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@ from frappe.tests.utils import FrappeTestCase
|
|||
|
||||
|
||||
class TestActivityLog(FrappeTestCase):
|
||||
def setUp(self) -> None:
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_activity_log(self):
|
||||
# test user login log
|
||||
frappe.local.form_dict = frappe._dict(
|
||||
{
|
||||
"cmd": "login",
|
||||
"sid": "Guest",
|
||||
"pwd": frappe.conf.admin_password or "admin",
|
||||
"pwd": self.ADMIN_PASSWORD or "admin",
|
||||
"usr": "Administrator",
|
||||
}
|
||||
)
|
||||
|
|
@ -57,7 +60,7 @@ class TestActivityLog(FrappeTestCase):
|
|||
update_system_settings({"allow_consecutive_login_attempts": 3, "allow_login_after_fail": 5})
|
||||
|
||||
frappe.local.form_dict = frappe._dict(
|
||||
{"cmd": "login", "sid": "Guest", "pwd": "admin", "usr": "Administrator"}
|
||||
{"cmd": "login", "sid": "Guest", "pwd": self.ADMIN_PASSWORD, "usr": "Administrator"}
|
||||
)
|
||||
|
||||
frappe.local.request_ip = "127.0.0.1"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import frappe
|
|||
from frappe.cache_manager import clear_doctype_cache
|
||||
from frappe.core.doctype.doctype.doctype import (
|
||||
CannotIndexedError,
|
||||
DocType,
|
||||
DoctypeLinkError,
|
||||
HiddenAndMandatoryWithoutDefaultError,
|
||||
IllegalMandatoryError,
|
||||
|
|
@ -782,7 +783,7 @@ def new_doctype(
|
|||
custom: bool = True,
|
||||
default: str | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
) -> "DocType":
|
||||
if not name:
|
||||
# Test prefix is required to avoid coverage
|
||||
name = "Test " + "".join(random.sample(string.ascii_lowercase, 10))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Navbar Settings", {
|
||||
// refresh: function(frm) {
|
||||
// }
|
||||
});
|
||||
frappe.ui.form.on("Navbar Settings", {});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
"logo_width",
|
||||
"section_break_2",
|
||||
"settings_dropdown",
|
||||
"help_dropdown"
|
||||
"help_dropdown",
|
||||
"announcements_section",
|
||||
"announcement_widget"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -49,11 +51,23 @@
|
|||
"fieldname": "logo_width",
|
||||
"fieldtype": "Int",
|
||||
"label": "Logo Width"
|
||||
},
|
||||
{
|
||||
"fieldname": "announcements_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Announcements"
|
||||
},
|
||||
{
|
||||
"description": "These announcements will appear inside a dismissible alert below the Navbar.",
|
||||
"fieldname": "announcement_widget",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Announcement Widget",
|
||||
"max_height": "10em"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-23 16:03:30.561647",
|
||||
"modified": "2024-03-23 17:03:30.561647",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Navbar Settings",
|
||||
|
|
@ -75,4 +89,4 @@
|
|||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class NavbarSettings(Document):
|
|||
from frappe.core.doctype.navbar_item.navbar_item import NavbarItem
|
||||
from frappe.types import DF
|
||||
|
||||
announcement_widget: DF.TextEditor | None
|
||||
app_logo: DF.AttachImage | None
|
||||
help_dropdown: DF.Table[NavbarItem]
|
||||
logo_width: DF.Int
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class ScheduledJobType(Document):
|
|||
next_execution = croniter(self.cron_format, last_execution).get_next(datetime)
|
||||
|
||||
jitter = 0
|
||||
if self.frequency in ("Hourly Long", "Daily Long"):
|
||||
if "Long" in self.frequency:
|
||||
jitter = randint(1, 600)
|
||||
return next_execution + timedelta(seconds=jitter)
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class TestScheduledJobType(FrappeTestCase):
|
|||
dict(method="frappe.social.doctype.energy_point_log.energy_point_log.send_weekly_summary"),
|
||||
)
|
||||
job.db_set("last_execution", "2019-01-01 00:00:00")
|
||||
self.assertTrue(job.is_event_due(get_datetime("2019-01-06 00:00:01")))
|
||||
self.assertTrue(job.is_event_due(get_datetime("2019-01-06 00:10:01"))) # +10 min because of jitter
|
||||
self.assertFalse(job.is_event_due(get_datetime("2019-01-02 00:00:06")))
|
||||
self.assertFalse(job.is_event_due(get_datetime("2019-01-05 23:59:59")))
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
"fieldname": "doctype_event",
|
||||
"fieldtype": "Select",
|
||||
"label": "DocType Event",
|
||||
"options": "Before Insert\nBefore Validate\nBefore Save\nAfter Insert\nAfter Save\nBefore Rename\nAfter Rename\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)\nOn Payment Authorization\nOn Payment Paid\nOn Payment Failed"
|
||||
"options": "Before Insert\nBefore Validate\nBefore Save\nAfter Insert\nAfter Save\nBefore Rename\nAfter Rename\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)\nBefore Print\nOn Payment Authorization\nOn Payment Paid\nOn Payment Failed"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.script_type==='API'",
|
||||
|
|
@ -151,7 +151,7 @@
|
|||
"link_fieldname": "server_script"
|
||||
}
|
||||
],
|
||||
"modified": "2024-03-23 16:03:38.075313",
|
||||
"modified": "2024-04-08 16:18:52.901097",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Server Script",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class ServerScript(Document):
|
|||
"After Delete",
|
||||
"Before Save (Submitted Document)",
|
||||
"After Save (Submitted Document)",
|
||||
"Before Print",
|
||||
"On Payment Authorization",
|
||||
"On Payment Paid",
|
||||
"On Payment Failed",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ EVENT_MAP = {
|
|||
"after_delete": "After Delete",
|
||||
"before_update_after_submit": "Before Save (Submitted Document)",
|
||||
"on_update_after_submit": "After Save (Submitted Document)",
|
||||
"before_print": "Before Print",
|
||||
"on_payment_paid": "On Payment Paid",
|
||||
"on_payment_failed": "On Payment Failed",
|
||||
"on_payment_authorized": "On Payment Authorization",
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ class TestUser(FrappeTestCase):
|
|||
res1 = c.session.post(url, data=data, verify=c.verify, headers=c.headers)
|
||||
res2 = c.session.post(url, data=data, verify=c.verify, headers=c.headers)
|
||||
self.assertEqual(res1.status_code, 404)
|
||||
self.assertEqual(res2.status_code, 417)
|
||||
self.assertEqual(res2.status_code, 429)
|
||||
|
||||
def test_user_rename(self):
|
||||
old_name = "test_user_rename@example.com"
|
||||
|
|
|
|||
|
|
@ -814,7 +814,7 @@
|
|||
"write": 1
|
||||
},
|
||||
{
|
||||
"role": "All",
|
||||
"role": "Desk User",
|
||||
"select": 1
|
||||
}
|
||||
],
|
||||
|
|
@ -827,4 +827,4 @@
|
|||
"states": [],
|
||||
"title_field": "full_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,11 +161,11 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
|||
self.db_type = "mariadb"
|
||||
self.type_map = {
|
||||
"Currency": ("decimal", "21,9"),
|
||||
"Int": ("int", "11"),
|
||||
"Int": ("int", None),
|
||||
"Long Int": ("bigint", "20"),
|
||||
"Float": ("decimal", "21,9"),
|
||||
"Percent": ("decimal", "21,9"),
|
||||
"Check": ("int", "1"),
|
||||
"Check": ("tinyint", None),
|
||||
"Small Text": ("text", ""),
|
||||
"Long Text": ("longtext", ""),
|
||||
"Code": ("longtext", ""),
|
||||
|
|
@ -288,7 +288,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
|||
`name` VARCHAR(255) NOT NULL,
|
||||
`fieldname` VARCHAR(140) NOT NULL,
|
||||
`password` TEXT NOT NULL,
|
||||
`encrypted` INT(1) NOT NULL DEFAULT 0,
|
||||
`encrypted` TINYINT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`doctype`, `name`, `fieldname`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci"""
|
||||
)
|
||||
|
|
@ -303,7 +303,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
|
|||
content text,
|
||||
fulltext(content),
|
||||
route varchar({self.VARCHAR_LEN}),
|
||||
published int(1) not null default 0,
|
||||
published TINYINT not null default 0,
|
||||
unique `doctype_name` (doctype, name))
|
||||
COLLATE=utf8mb4_unicode_ci
|
||||
ENGINE=MyISAM
|
||||
|
|
|
|||
|
|
@ -13,61 +13,61 @@ CREATE TABLE `tabDocField` (
|
|||
`modified` datetime(6) DEFAULT NULL,
|
||||
`modified_by` varchar(255) DEFAULT NULL,
|
||||
`owner` varchar(255) DEFAULT NULL,
|
||||
`docstatus` int(1) NOT NULL DEFAULT 0,
|
||||
`docstatus` tinyint NOT NULL DEFAULT 0,
|
||||
`parent` varchar(255) DEFAULT NULL,
|
||||
`parentfield` varchar(255) DEFAULT NULL,
|
||||
`parenttype` varchar(255) DEFAULT NULL,
|
||||
`idx` int(8) NOT NULL DEFAULT 0,
|
||||
`idx` int NOT NULL DEFAULT 0,
|
||||
`fieldname` varchar(255) DEFAULT NULL,
|
||||
`label` varchar(255) DEFAULT NULL,
|
||||
`oldfieldname` varchar(255) DEFAULT NULL,
|
||||
`fieldtype` varchar(255) DEFAULT NULL,
|
||||
`oldfieldtype` varchar(255) DEFAULT NULL,
|
||||
`options` text,
|
||||
`search_index` int(1) NOT NULL DEFAULT 0,
|
||||
`show_dashboard` int(1) NOT NULL DEFAULT 0,
|
||||
`hidden` int(1) NOT NULL DEFAULT 0,
|
||||
`set_only_once` int(1) NOT NULL DEFAULT 0,
|
||||
`allow_in_quick_entry` int(1) NOT NULL DEFAULT 0,
|
||||
`print_hide` int(1) NOT NULL DEFAULT 0,
|
||||
`report_hide` int(1) NOT NULL DEFAULT 0,
|
||||
`reqd` int(1) NOT NULL DEFAULT 0,
|
||||
`bold` int(1) NOT NULL DEFAULT 0,
|
||||
`in_global_search` int(1) NOT NULL DEFAULT 0,
|
||||
`collapsible` int(1) NOT NULL DEFAULT 0,
|
||||
`unique` int(1) NOT NULL DEFAULT 0,
|
||||
`no_copy` int(1) NOT NULL DEFAULT 0,
|
||||
`allow_on_submit` int(1) NOT NULL DEFAULT 0,
|
||||
`show_preview_popup` int(1) NOT NULL DEFAULT 0,
|
||||
`search_index` tinyint NOT NULL DEFAULT 0,
|
||||
`show_dashboard` tinyint NOT NULL DEFAULT 0,
|
||||
`hidden` tinyint NOT NULL DEFAULT 0,
|
||||
`set_only_once` tinyint NOT NULL DEFAULT 0,
|
||||
`allow_in_quick_entry` tinyint NOT NULL DEFAULT 0,
|
||||
`print_hide` tinyint NOT NULL DEFAULT 0,
|
||||
`report_hide` tinyint NOT NULL DEFAULT 0,
|
||||
`reqd` tinyint NOT NULL DEFAULT 0,
|
||||
`bold` tinyint NOT NULL DEFAULT 0,
|
||||
`in_global_search` tinyint NOT NULL DEFAULT 0,
|
||||
`collapsible` tinyint NOT NULL DEFAULT 0,
|
||||
`unique` tinyint NOT NULL DEFAULT 0,
|
||||
`no_copy` tinyint NOT NULL DEFAULT 0,
|
||||
`allow_on_submit` tinyint NOT NULL DEFAULT 0,
|
||||
`show_preview_popup` tinyint NOT NULL DEFAULT 0,
|
||||
`trigger` varchar(255) DEFAULT NULL,
|
||||
`collapsible_depends_on` text,
|
||||
`mandatory_depends_on` text,
|
||||
`read_only_depends_on` text,
|
||||
`depends_on` text,
|
||||
`permlevel` int(11) NOT NULL DEFAULT 0,
|
||||
`ignore_user_permissions` int(1) NOT NULL DEFAULT 0,
|
||||
`permlevel` int NOT NULL DEFAULT 0,
|
||||
`ignore_user_permissions` tinyint NOT NULL DEFAULT 0,
|
||||
`width` varchar(255) DEFAULT NULL,
|
||||
`print_width` varchar(255) DEFAULT NULL,
|
||||
`columns` int(11) NOT NULL DEFAULT 0,
|
||||
`columns` int NOT NULL DEFAULT 0,
|
||||
`default` text,
|
||||
`description` text,
|
||||
`in_list_view` int(1) NOT NULL DEFAULT 0,
|
||||
`fetch_if_empty` int(1) NOT NULL DEFAULT 0,
|
||||
`in_filter` int(1) NOT NULL DEFAULT 0,
|
||||
`remember_last_selected_value` int(1) NOT NULL DEFAULT 0,
|
||||
`ignore_xss_filter` int(1) NOT NULL DEFAULT 0,
|
||||
`print_hide_if_no_value` int(1) NOT NULL DEFAULT 0,
|
||||
`allow_bulk_edit` int(1) NOT NULL DEFAULT 0,
|
||||
`in_standard_filter` int(1) NOT NULL DEFAULT 0,
|
||||
`in_preview` int(1) NOT NULL DEFAULT 0,
|
||||
`read_only` int(1) NOT NULL DEFAULT 0,
|
||||
`in_list_view` tinyint NOT NULL DEFAULT 0,
|
||||
`fetch_if_empty` tinyint NOT NULL DEFAULT 0,
|
||||
`in_filter` tinyint NOT NULL DEFAULT 0,
|
||||
`remember_last_selected_value` tinyint NOT NULL DEFAULT 0,
|
||||
`ignore_xss_filter` tinyint NOT NULL DEFAULT 0,
|
||||
`print_hide_if_no_value` tinyint NOT NULL DEFAULT 0,
|
||||
`allow_bulk_edit` tinyint NOT NULL DEFAULT 0,
|
||||
`in_standard_filter` tinyint NOT NULL DEFAULT 0,
|
||||
`in_preview` tinyint NOT NULL DEFAULT 0,
|
||||
`read_only` tinyint NOT NULL DEFAULT 0,
|
||||
`precision` varchar(255) DEFAULT NULL,
|
||||
`max_height` varchar(10) DEFAULT NULL,
|
||||
`length` int(11) NOT NULL DEFAULT 0,
|
||||
`translatable` int(1) NOT NULL DEFAULT 0,
|
||||
`hide_border` int(1) NOT NULL DEFAULT 0,
|
||||
`hide_days` int(1) NOT NULL DEFAULT 0,
|
||||
`hide_seconds` int(1) NOT NULL DEFAULT 0,
|
||||
`length` int NOT NULL DEFAULT 0,
|
||||
`translatable` tinyint NOT NULL DEFAULT 0,
|
||||
`hide_border` tinyint NOT NULL DEFAULT 0,
|
||||
`hide_days` tinyint NOT NULL DEFAULT 0,
|
||||
`hide_seconds` tinyint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`name`),
|
||||
KEY `parent` (`parent`),
|
||||
KEY `label` (`label`),
|
||||
|
|
@ -87,27 +87,27 @@ CREATE TABLE `tabDocPerm` (
|
|||
`modified` datetime(6) DEFAULT NULL,
|
||||
`modified_by` varchar(255) DEFAULT NULL,
|
||||
`owner` varchar(255) DEFAULT NULL,
|
||||
`docstatus` int(1) NOT NULL DEFAULT 0,
|
||||
`docstatus` tinyint NOT NULL DEFAULT 0,
|
||||
`parent` varchar(255) DEFAULT NULL,
|
||||
`parentfield` varchar(255) DEFAULT NULL,
|
||||
`parenttype` varchar(255) DEFAULT NULL,
|
||||
`idx` int(8) NOT NULL DEFAULT 0,
|
||||
`permlevel` int(11) DEFAULT '0',
|
||||
`idx` int NOT NULL DEFAULT 0,
|
||||
`permlevel` int DEFAULT '0',
|
||||
`role` varchar(255) DEFAULT NULL,
|
||||
`match` varchar(255) DEFAULT NULL,
|
||||
`read` int(1) NOT NULL DEFAULT 1,
|
||||
`write` int(1) NOT NULL DEFAULT 1,
|
||||
`create` int(1) NOT NULL DEFAULT 1,
|
||||
`submit` int(1) NOT NULL DEFAULT 0,
|
||||
`cancel` int(1) NOT NULL DEFAULT 0,
|
||||
`delete` int(1) NOT NULL DEFAULT 1,
|
||||
`amend` int(1) NOT NULL DEFAULT 0,
|
||||
`report` int(1) NOT NULL DEFAULT 1,
|
||||
`export` int(1) NOT NULL DEFAULT 1,
|
||||
`import` int(1) NOT NULL DEFAULT 0,
|
||||
`share` int(1) NOT NULL DEFAULT 1,
|
||||
`print` int(1) NOT NULL DEFAULT 1,
|
||||
`email` int(1) NOT NULL DEFAULT 1,
|
||||
`read` tinyint NOT NULL DEFAULT 1,
|
||||
`write` tinyint NOT NULL DEFAULT 1,
|
||||
`create` tinyint NOT NULL DEFAULT 1,
|
||||
`submit` tinyint NOT NULL DEFAULT 0,
|
||||
`cancel` tinyint NOT NULL DEFAULT 0,
|
||||
`delete` tinyint NOT NULL DEFAULT 1,
|
||||
`amend` tinyint NOT NULL DEFAULT 0,
|
||||
`report` tinyint NOT NULL DEFAULT 1,
|
||||
`export` tinyint NOT NULL DEFAULT 1,
|
||||
`import` tinyint NOT NULL DEFAULT 0,
|
||||
`share` tinyint NOT NULL DEFAULT 1,
|
||||
`print` tinyint NOT NULL DEFAULT 1,
|
||||
`email` tinyint NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (`name`),
|
||||
KEY `parent` (`parent`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
|
@ -123,11 +123,11 @@ CREATE TABLE `tabDocType Action` (
|
|||
`modified` datetime(6) DEFAULT NULL,
|
||||
`modified_by` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`owner` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`docstatus` int(1) NOT NULL DEFAULT 0,
|
||||
`docstatus` tinyint NOT NULL DEFAULT 0,
|
||||
`parent` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`parentfield` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`parenttype` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`idx` int(8) NOT NULL DEFAULT 0,
|
||||
`idx` int NOT NULL DEFAULT 0,
|
||||
`label` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`group` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`action_type` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
|
|
@ -147,11 +147,11 @@ CREATE TABLE `tabDocType Link` (
|
|||
`modified` datetime(6) DEFAULT NULL,
|
||||
`modified_by` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`owner` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`docstatus` int(1) NOT NULL DEFAULT 0,
|
||||
`docstatus` tinyint NOT NULL DEFAULT 0,
|
||||
`parent` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`parentfield` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`parenttype` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`idx` int(8) NOT NULL DEFAULT 0,
|
||||
`idx` int NOT NULL DEFAULT 0,
|
||||
`group` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`link_doctype` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`link_fieldname` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
|
|
@ -170,15 +170,15 @@ CREATE TABLE `tabDocType` (
|
|||
`modified` datetime(6) DEFAULT NULL,
|
||||
`modified_by` varchar(255) DEFAULT NULL,
|
||||
`owner` varchar(255) DEFAULT NULL,
|
||||
`docstatus` int(1) NOT NULL DEFAULT 0,
|
||||
`idx` int(8) NOT NULL DEFAULT 0,
|
||||
`docstatus` tinyint NOT NULL DEFAULT 0,
|
||||
`idx` int NOT NULL DEFAULT 0,
|
||||
`search_fields` varchar(255) DEFAULT NULL,
|
||||
`issingle` int(1) NOT NULL DEFAULT 0,
|
||||
`is_virtual` int(1) NOT NULL DEFAULT 0,
|
||||
`is_tree` int(1) NOT NULL DEFAULT 0,
|
||||
`istable` int(1) NOT NULL DEFAULT 0,
|
||||
`editable_grid` int(1) NOT NULL DEFAULT 1,
|
||||
`track_changes` int(1) NOT NULL DEFAULT 0,
|
||||
`issingle` tinyint NOT NULL DEFAULT 0,
|
||||
`is_virtual` tinyint NOT NULL DEFAULT 0,
|
||||
`is_tree` tinyint NOT NULL DEFAULT 0,
|
||||
`istable` tinyint NOT NULL DEFAULT 0,
|
||||
`editable_grid` tinyint NOT NULL DEFAULT 1,
|
||||
`track_changes` tinyint NOT NULL DEFAULT 0,
|
||||
`module` varchar(255) DEFAULT NULL,
|
||||
`restrict_to_domain` varchar(255) DEFAULT NULL,
|
||||
`app` varchar(255) DEFAULT NULL,
|
||||
|
|
@ -191,17 +191,17 @@ CREATE TABLE `tabDocType` (
|
|||
`sort_order` varchar(255) DEFAULT NULL,
|
||||
`description` text,
|
||||
`colour` varchar(255) DEFAULT NULL,
|
||||
`read_only` int(1) NOT NULL DEFAULT 0,
|
||||
`in_create` int(1) NOT NULL DEFAULT 0,
|
||||
`menu_index` int(11) DEFAULT NULL,
|
||||
`read_only` tinyint NOT NULL DEFAULT 0,
|
||||
`in_create` tinyint NOT NULL DEFAULT 0,
|
||||
`menu_index` int DEFAULT NULL,
|
||||
`parent_node` varchar(255) DEFAULT NULL,
|
||||
`smallicon` varchar(255) DEFAULT NULL,
|
||||
`allow_copy` int(1) NOT NULL DEFAULT 0,
|
||||
`allow_rename` int(1) NOT NULL DEFAULT 0,
|
||||
`allow_import` int(1) NOT NULL DEFAULT 0,
|
||||
`hide_toolbar` int(1) NOT NULL DEFAULT 0,
|
||||
`track_seen` int(1) NOT NULL DEFAULT 0,
|
||||
`max_attachments` int(11) NOT NULL DEFAULT 0,
|
||||
`allow_copy` tinyint NOT NULL DEFAULT 0,
|
||||
`allow_rename` tinyint NOT NULL DEFAULT 0,
|
||||
`allow_import` tinyint NOT NULL DEFAULT 0,
|
||||
`hide_toolbar` tinyint NOT NULL DEFAULT 0,
|
||||
`track_seen` tinyint NOT NULL DEFAULT 0,
|
||||
`max_attachments` int NOT NULL DEFAULT 0,
|
||||
`print_outline` varchar(255) DEFAULT NULL,
|
||||
`document_type` varchar(255) DEFAULT NULL,
|
||||
`icon` varchar(255) DEFAULT NULL,
|
||||
|
|
@ -211,22 +211,22 @@ CREATE TABLE `tabDocType` (
|
|||
`_last_update` varchar(32) DEFAULT NULL,
|
||||
`engine` varchar(20) DEFAULT 'InnoDB',
|
||||
`default_print_format` varchar(255) DEFAULT NULL,
|
||||
`is_submittable` int(1) NOT NULL DEFAULT 0,
|
||||
`show_name_in_global_search` int(1) NOT NULL DEFAULT 0,
|
||||
`is_submittable` tinyint NOT NULL DEFAULT 0,
|
||||
`show_name_in_global_search` tinyint NOT NULL DEFAULT 0,
|
||||
`_user_tags` varchar(255) DEFAULT NULL,
|
||||
`custom` int(1) NOT NULL DEFAULT 0,
|
||||
`beta` int(1) NOT NULL DEFAULT 0,
|
||||
`has_web_view` int(1) NOT NULL DEFAULT 0,
|
||||
`allow_guest_to_view` int(1) NOT NULL DEFAULT 0,
|
||||
`custom` tinyint NOT NULL DEFAULT 0,
|
||||
`beta` tinyint NOT NULL DEFAULT 0,
|
||||
`has_web_view` tinyint NOT NULL DEFAULT 0,
|
||||
`allow_guest_to_view` tinyint NOT NULL DEFAULT 0,
|
||||
`route` varchar(255) DEFAULT NULL,
|
||||
`is_published_field` varchar(255) DEFAULT NULL,
|
||||
`website_search_field` varchar(255) DEFAULT NULL,
|
||||
`email_append_to` int(1) NOT NULL DEFAULT 0,
|
||||
`email_append_to` tinyint NOT NULL DEFAULT 0,
|
||||
`subject_field` varchar(255) DEFAULT NULL,
|
||||
`sender_field` varchar(255) DEFAULT NULL,
|
||||
`show_title_field_in_link` int(1) NOT NULL DEFAULT 0,
|
||||
`show_title_field_in_link` tinyint NOT NULL DEFAULT 0,
|
||||
`migration_hash` varchar(255) DEFAULT NULL,
|
||||
`translated_doctype` int(1) NOT NULL DEFAULT 0,
|
||||
`translated_doctype` tinyint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`name`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ CREATE TABLE `tabDocType` (
|
|||
DROP TABLE IF EXISTS `tabSeries`;
|
||||
CREATE TABLE `tabSeries` (
|
||||
`name` varchar(100),
|
||||
`current` int(10) NOT NULL DEFAULT 0,
|
||||
`current` int NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(`name`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
|
@ -280,7 +280,7 @@ CREATE TABLE `__Auth` (
|
|||
`name` VARCHAR(255) NOT NULL,
|
||||
`fieldname` VARCHAR(140) NOT NULL,
|
||||
`password` TEXT NOT NULL,
|
||||
`encrypted` INT(1) NOT NULL DEFAULT 0,
|
||||
`encrypted` tinyint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`doctype`, `name`, `fieldname`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
|
@ -295,19 +295,20 @@ CREATE TABLE `tabFile` (
|
|||
`modified` datetime(6) DEFAULT NULL,
|
||||
`modified_by` varchar(255) DEFAULT NULL,
|
||||
`owner` varchar(255) DEFAULT NULL,
|
||||
`docstatus` int(1) NOT NULL DEFAULT 0,
|
||||
`docstatus` tinyint NOT NULL DEFAULT 0,
|
||||
`parent` varchar(255) DEFAULT NULL,
|
||||
`parentfield` varchar(255) DEFAULT NULL,
|
||||
`parenttype` varchar(255) DEFAULT NULL,
|
||||
`idx` int(8) NOT NULL DEFAULT 0,
|
||||
`idx` int NOT NULL DEFAULT 0,
|
||||
`file_name` varchar(255) DEFAULT NULL,
|
||||
`file_url` varchar(255) DEFAULT NULL,
|
||||
`module` varchar(255) DEFAULT NULL,
|
||||
`attached_to_name` varchar(255) DEFAULT NULL,
|
||||
`file_size` int(11) NOT NULL DEFAULT 0,
|
||||
`file_size` int NOT NULL DEFAULT 0,
|
||||
`attached_to_doctype` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`name`),
|
||||
KEY `parent` (`parent`),
|
||||
KEY `creation` (`creation`),
|
||||
KEY `attached_to_name` (`attached_to_name`),
|
||||
KEY `attached_to_doctype` (`attached_to_doctype`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
|
@ -323,11 +324,11 @@ CREATE TABLE `tabDefaultValue` (
|
|||
`modified` datetime(6) DEFAULT NULL,
|
||||
`modified_by` varchar(255) DEFAULT NULL,
|
||||
`owner` varchar(255) DEFAULT NULL,
|
||||
`docstatus` int(1) NOT NULL DEFAULT 0,
|
||||
`docstatus` tinyint NOT NULL DEFAULT 0,
|
||||
`parent` varchar(255) DEFAULT NULL,
|
||||
`parentfield` varchar(255) DEFAULT NULL,
|
||||
`parenttype` varchar(255) DEFAULT NULL,
|
||||
`idx` int(8) NOT NULL DEFAULT 0,
|
||||
`idx` int NOT NULL DEFAULT 0,
|
||||
`defvalue` text,
|
||||
`defkey` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`name`),
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ class MariaDBTable(DBTable):
|
|||
modified datetime(6),
|
||||
modified_by varchar({varchar_len}),
|
||||
owner varchar({varchar_len}),
|
||||
docstatus int(1) not null default '0',
|
||||
idx int(8) not null default '0',
|
||||
docstatus tinyint not null default '0',
|
||||
idx int not null default '0',
|
||||
{additional_definitions})
|
||||
ENGINE={engine}
|
||||
ROW_FORMAT=DYNAMIC
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
|
|||
self.db_type = "postgres"
|
||||
self.type_map = {
|
||||
"Currency": ("decimal", "21,9"),
|
||||
"Int": ("bigint", None),
|
||||
"Int": ("int", None),
|
||||
"Long Int": ("bigint", None),
|
||||
"Float": ("decimal", "21,9"),
|
||||
"Percent": ("decimal", "21,9"),
|
||||
|
|
|
|||
0
frappe/desk/doctype/changelog_feed/__init__.py
Normal file
0
frappe/desk/doctype/changelog_feed/__init__.py
Normal file
8
frappe/desk/doctype/changelog_feed/changelog_feed.js
Normal file
8
frappe/desk/doctype/changelog_feed/changelog_feed.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2023, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Changelog Feed", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
70
frappe/desk/doctype/changelog_feed/changelog_feed.json
Normal file
70
frappe/desk/doctype/changelog_feed/changelog_feed.json
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"beta": 1,
|
||||
"creation": "2023-05-16 19:37:51.047664",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"app_name",
|
||||
"link",
|
||||
"posting_timestamp"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "app_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "App Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "link",
|
||||
"fieldtype": "Long Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Link",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "posting_timestamp",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Posting Timestamp",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-04-08 18:36:42.203032",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Changelog Feed",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
93
frappe/desk/doctype/changelog_feed/changelog_feed.py
Normal file
93
frappe/desk/doctype/changelog_feed/changelog_feed.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Copyright (c) 2023, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import requests
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.caching import redis_cache
|
||||
from frappe.utils.data import add_to_date
|
||||
|
||||
|
||||
class ChangelogFeed(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
app_name: DF.Data | None
|
||||
link: DF.LongText
|
||||
posting_timestamp: DF.Datetime
|
||||
title: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def fetch_changelog_feed():
|
||||
"""Fetches changelog feed items from source using `get_changelog_feed` hook and stores in the db"""
|
||||
since = frappe.db.get_value(
|
||||
"Changelog Feed",
|
||||
filters={},
|
||||
fieldname="posting_timestamp",
|
||||
order_by="posting_timestamp desc",
|
||||
) or add_to_date(None, months=-1, as_datetime=True, as_string=False)
|
||||
|
||||
for fn in frappe.get_hooks("get_changelog_feed"):
|
||||
try:
|
||||
cache_key = f"changelog_feed::{fn}"
|
||||
changelog_feed = frappe.cache.get_value(cache_key, shared=True)
|
||||
if changelog_feed is None:
|
||||
changelog_feed = frappe.call(fn, since=since)[:20] or []
|
||||
frappe.cache.set_value(
|
||||
cache_key, changelog_feed, expires_in_sec=7 * 24 * 60 * 60, shared=True
|
||||
)
|
||||
|
||||
for feed_item in changelog_feed:
|
||||
feed = {
|
||||
"title": feed_item["title"],
|
||||
"app_name": feed_item["app_name"],
|
||||
"link": feed_item["link"],
|
||||
"posting_timestamp": feed_item["creation"],
|
||||
}
|
||||
if not frappe.db.exists("Changelog Feed", feed):
|
||||
frappe.new_doc("Changelog Feed").update(feed).insert()
|
||||
except Exception:
|
||||
frappe.log_error(f"Failed to fetch changelog from {fn}")
|
||||
# don't retry if it's broken for 1 week
|
||||
frappe.cache.set_value(cache_key, [], expires_in_sec=7 * 24 * 60 * 60, shared=True)
|
||||
|
||||
|
||||
@redis_cache
|
||||
def get_changelog_feed_items():
|
||||
"""Returns a list of latest 10 changelog feed items"""
|
||||
feed = frappe.get_all(
|
||||
"Changelog Feed",
|
||||
fields=["title", "app_name", "link", "posting_timestamp"],
|
||||
# allow pubishing feed for many apps with single hook
|
||||
filters={"app_name": ("in", frappe.get_installed_apps())},
|
||||
order_by="posting_timestamp desc",
|
||||
limit=20,
|
||||
)
|
||||
for f in feed:
|
||||
f["app_title"] = _app_title(f["app_name"])
|
||||
|
||||
return feed
|
||||
|
||||
|
||||
def _app_title(app_name):
|
||||
try:
|
||||
return frappe.get_hooks("app_title", app_name=app_name)[0]
|
||||
except Exception:
|
||||
return app_name
|
||||
|
||||
|
||||
def get_feed(since):
|
||||
"""'What's New' feed implementation for Frappe"""
|
||||
r = requests.get(f"https://frappe.io/api/method/changelog_feed?since={since}")
|
||||
r.raise_for_status()
|
||||
return r.json()["message"]
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright (c) 2023, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestChangelogFeed(FrappeTestCase):
|
||||
pass
|
||||
|
|
@ -260,7 +260,7 @@ def get_heatmap_chart_config(chart, filters, heatmap_year):
|
|||
}
|
||||
|
||||
|
||||
def get_group_by_chart_config(chart, filters):
|
||||
def get_group_by_chart_config(chart, filters) -> dict | None:
|
||||
aggregate_function = get_aggregate_function(chart.group_by_type)
|
||||
value_field = chart.aggregate_function_based_on or "1"
|
||||
group_by_field = chart.group_by_based_on
|
||||
|
|
@ -281,11 +281,10 @@ def get_group_by_chart_config(chart, filters):
|
|||
|
||||
if data:
|
||||
return {
|
||||
"labels": [item["name"] if item["name"] else "Not Specified" for item in data],
|
||||
"labels": [item.get("name", "Not Specified") for item in data],
|
||||
"datasets": [{"name": chart.name, "values": [item["count"] for item in data]}],
|
||||
}
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def get_aggregate_function(chart_type):
|
||||
|
|
@ -304,8 +303,8 @@ def get_result(data, timegrain, from_date, to_date, chart_type):
|
|||
for d in result:
|
||||
count = 0
|
||||
while data_index < len(data) and getdate(data[data_index][0]) <= d[0]:
|
||||
d[1] += data[data_index][1]
|
||||
count += data[data_index][2]
|
||||
d[1] += cint(data[data_index][1])
|
||||
count += cint(data[data_index][2])
|
||||
data_index += 1
|
||||
if chart_type == "Average" and count != 0:
|
||||
d[1] = d[1] / count
|
||||
|
|
|
|||
|
|
@ -206,18 +206,22 @@ def run(
|
|||
if sbool(are_default_filters) and report.custom_filters:
|
||||
filters = report.custom_filters
|
||||
|
||||
if report.prepared_report and not sbool(ignore_prepared_report) and not custom_columns:
|
||||
if filters:
|
||||
if isinstance(filters, str):
|
||||
filters = json.loads(filters)
|
||||
try:
|
||||
if report.prepared_report and not sbool(ignore_prepared_report) and not custom_columns:
|
||||
if filters:
|
||||
if isinstance(filters, str):
|
||||
filters = json.loads(filters)
|
||||
|
||||
dn = filters.pop("prepared_report_name", None)
|
||||
dn = filters.pop("prepared_report_name", None)
|
||||
else:
|
||||
dn = ""
|
||||
result = get_prepared_report_result(report, filters, dn, user)
|
||||
else:
|
||||
dn = ""
|
||||
result = get_prepared_report_result(report, filters, dn, user)
|
||||
else:
|
||||
result = generate_report_result(report, filters, user, custom_columns, is_tree, parent_field)
|
||||
add_data_to_monitor(report=report.reference_report or report.name)
|
||||
result = generate_report_result(report, filters, user, custom_columns, is_tree, parent_field)
|
||||
add_data_to_monitor(report=report.reference_report or report.name)
|
||||
except Exception:
|
||||
frappe.log_error("Report Error")
|
||||
raise
|
||||
|
||||
result["add_total_row"] = report.add_total_row and not result.get("skip_total_row", False)
|
||||
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ class InvalidSignatureError(ValidationError):
|
|||
|
||||
|
||||
class RateLimitExceededError(ValidationError):
|
||||
pass
|
||||
http_status_code = 429
|
||||
|
||||
|
||||
class CannotChangeConstantError(ValidationError):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import json
|
||||
|
||||
EXCLUDE_SELECT_OPTIONS = [
|
||||
"naming_series",
|
||||
"number_format",
|
||||
"float_precision",
|
||||
"currency_precision",
|
||||
"minimum_password_score",
|
||||
]
|
||||
|
||||
|
||||
def extract(fileobj, *args, **kwargs):
|
||||
"""
|
||||
|
|
@ -26,13 +34,14 @@ def extract(fileobj, *args, **kwargs):
|
|||
|
||||
for field in fields:
|
||||
fieldtype = field.get("fieldtype")
|
||||
fieldname = field.get("fieldname")
|
||||
label = field.get("label")
|
||||
|
||||
if label:
|
||||
messages.append((label, f"Label of a {fieldtype} field in DocType '{doctype}'"))
|
||||
_label = label
|
||||
else:
|
||||
_label = field.get("fieldname")
|
||||
_label = fieldname
|
||||
|
||||
if description := field.get("description"):
|
||||
messages.append(
|
||||
|
|
@ -41,6 +50,9 @@ def extract(fileobj, *args, **kwargs):
|
|||
|
||||
if message := field.get("options"):
|
||||
if fieldtype == "Select":
|
||||
if fieldname in EXCLUDE_SELECT_OPTIONS:
|
||||
continue
|
||||
|
||||
select_options = [option for option in message.split("\n") if option and not option.isdigit()]
|
||||
|
||||
if select_options and "icon" in select_options[0]:
|
||||
|
|
|
|||
|
|
@ -259,6 +259,7 @@ scheduler_events = {
|
|||
"frappe.desk.form.document_follow.send_weekly_updates",
|
||||
"frappe.social.doctype.energy_point_log.energy_point_log.send_weekly_summary",
|
||||
"frappe.integrations.doctype.google_drive.google_drive.weekly_backup",
|
||||
"frappe.desk.doctype.changelog_feed.changelog_feed.fetch_changelog_feed",
|
||||
],
|
||||
"monthly": [
|
||||
"frappe.email.doctype.auto_email_report.auto_email_report.send_monthly",
|
||||
|
|
@ -450,6 +451,8 @@ extend_bootinfo = [
|
|||
"frappe.utils.sentry.add_bootinfo",
|
||||
]
|
||||
|
||||
get_changelog_feed = "frappe.desk.doctype.changelog_feed.changelog_feed.get_feed"
|
||||
|
||||
export_python_type_annotations = True
|
||||
|
||||
standard_navbar_items = [
|
||||
|
|
|
|||
39798
frappe/locale/bs.po
Normal file
39798
frappe/locale/bs.po
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: developers@frappe.io\n"
|
||||
"POT-Creation-Date: 2024-03-24 09:33+0000\n"
|
||||
"PO-Revision-Date: 2024-03-26 12:06\n"
|
||||
"PO-Revision-Date: 2024-04-04 14:56\n"
|
||||
"Last-Translator: developers@frappe.io\n"
|
||||
"Language-Team: German\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -2510,11 +2510,11 @@ msgstr "Änderung"
|
|||
#: core/doctype/document_naming_settings/document_naming_settings.json
|
||||
msgctxt "Document Naming Settings"
|
||||
msgid "Amendment Naming Override"
|
||||
msgstr ""
|
||||
msgstr "Überschreibung der Berichtigungsbenennung"
|
||||
|
||||
#: core/doctype/document_naming_settings/document_naming_settings.py:207
|
||||
msgid "Amendment naming rules updated."
|
||||
msgstr ""
|
||||
msgstr "Benennungsregeln für Berichtigungen aktualisiert."
|
||||
|
||||
#: public/js/frappe/ui/toolbar/toolbar.js:297
|
||||
msgid "An error occurred while setting Session Defaults"
|
||||
|
|
@ -2827,7 +2827,7 @@ msgstr "Genehmigung erforderlich"
|
|||
#: public/js/frappe/utils/number_systems.js:41
|
||||
msgctxt "Number system"
|
||||
msgid "Ar"
|
||||
msgstr ""
|
||||
msgstr "Ar"
|
||||
|
||||
#: public/js/frappe/views/kanban/kanban_column.html:14
|
||||
msgid "Archive"
|
||||
|
|
@ -3314,7 +3314,7 @@ msgstr "Zielgruppe"
|
|||
#. Name of a report
|
||||
#: custom/report/audit_system_hooks/audit_system_hooks.json
|
||||
msgid "Audit System Hooks"
|
||||
msgstr ""
|
||||
msgstr "System Hooks überprüfen"
|
||||
|
||||
#. Name of a DocType
|
||||
#: core/doctype/audit_trail/audit_trail.json
|
||||
|
|
@ -3497,11 +3497,11 @@ msgstr "Automatische Wiederholung"
|
|||
#. Name of a DocType
|
||||
#: automation/doctype/auto_repeat_day/auto_repeat_day.json
|
||||
msgid "Auto Repeat Day"
|
||||
msgstr ""
|
||||
msgstr "Tag mit automatischer Wiederholung"
|
||||
|
||||
#: automation/doctype/auto_repeat/auto_repeat.py:159
|
||||
msgid "Auto Repeat Day{0} {1} has been repeated."
|
||||
msgstr ""
|
||||
msgstr "Auto-Wiederholung Tag{0} {1} wurde wiederholt."
|
||||
|
||||
#: automation/doctype/auto_repeat/auto_repeat.py:437
|
||||
msgid "Auto Repeat Document Creation Failed"
|
||||
|
|
@ -8710,7 +8710,7 @@ msgstr "Lösche {0} Einträge..."
|
|||
|
||||
#: public/js/frappe/model/model.js:711
|
||||
msgid "Deleting {0}..."
|
||||
msgstr ""
|
||||
msgstr "Lösche {0}..."
|
||||
|
||||
#. Label of a Table field in DocType 'Personal Data Deletion Request'
|
||||
#: website/doctype/personal_data_deletion_request/personal_data_deletion_request.json
|
||||
|
|
@ -9197,7 +9197,7 @@ msgstr "Verwerfen"
|
|||
|
||||
#: public/js/frappe/web_form/web_form.js:184
|
||||
msgid "Discard?"
|
||||
msgstr ""
|
||||
msgstr "Verwerfen?"
|
||||
|
||||
#. Name of a DocType
|
||||
#: website/doctype/discussion_reply/discussion_reply.json
|
||||
|
|
@ -10601,7 +10601,7 @@ msgstr "z. B. smsgateway.com/api/send_sms.cgi"
|
|||
|
||||
#: rate_limiter.py:139
|
||||
msgid "Either key or IP flag is required."
|
||||
msgstr ""
|
||||
msgstr "Entweder der key (Schlüssel) oder das IP-Flag ist erforderlich."
|
||||
|
||||
#. Label of a Data field in DocType 'Form Tour Step'
|
||||
#: desk/doctype/form_tour_step/form_tour_step.json
|
||||
|
|
@ -13573,7 +13573,7 @@ msgstr "Formular-Generator"
|
|||
#: core/doctype/recorder/recorder.json
|
||||
msgctxt "Recorder"
|
||||
msgid "Form Dict"
|
||||
msgstr ""
|
||||
msgstr "Formular Dict"
|
||||
|
||||
#. Label of a Section Break field in DocType 'Customize Form'
|
||||
#: custom/doctype/customize_form/customize_form.json
|
||||
|
|
@ -13617,19 +13617,19 @@ msgstr "Formular URL-verschlüsselt"
|
|||
|
||||
#: public/js/frappe/widgets/widget_dialog.js:567
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
msgstr "Formatierung"
|
||||
|
||||
#. Label of a Select field in DocType 'Auto Email Report'
|
||||
#: email/doctype/auto_email_report/auto_email_report.json
|
||||
msgctxt "Auto Email Report"
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
msgstr "Formatierung"
|
||||
|
||||
#. Label of a Data field in DocType 'Workspace Shortcut'
|
||||
#: desk/doctype/workspace_shortcut/workspace_shortcut.json
|
||||
msgctxt "Workspace Shortcut"
|
||||
msgid "Format"
|
||||
msgstr ""
|
||||
msgstr "Formatierung"
|
||||
|
||||
#. Label of a Code field in DocType 'Print Format'
|
||||
#: printing/doctype/print_format/print_format.json
|
||||
|
|
@ -17004,7 +17004,7 @@ msgstr "Ist Abfragebericht"
|
|||
#: integrations/doctype/integration_request/integration_request.json
|
||||
msgctxt "Integration Request"
|
||||
msgid "Is Remote Request?"
|
||||
msgstr ""
|
||||
msgstr "Ist dies eine Remote-Anfrage?"
|
||||
|
||||
#: core/doctype/doctype/doctype_list.js:64
|
||||
msgid "Is Single"
|
||||
|
|
@ -18269,7 +18269,7 @@ msgstr "Like Limit pro Stunde"
|
|||
|
||||
#: templates/includes/likes/likes.py:30
|
||||
msgid "Like on {0}: {1}"
|
||||
msgstr ""
|
||||
msgstr "„Gefällt mir“ für {0}: {1}"
|
||||
|
||||
#: desk/like.py:91
|
||||
msgid "Liked"
|
||||
|
|
@ -21219,7 +21219,7 @@ msgstr "Noch keine Kommentare"
|
|||
|
||||
#: templates/includes/comments/comments.html:4
|
||||
msgid "No comments yet. "
|
||||
msgstr ""
|
||||
msgstr "Noch keine Kommentare. "
|
||||
|
||||
#: public/js/frappe/form/templates/contact_list.html:85
|
||||
msgid "No contacts added yet."
|
||||
|
|
@ -23161,7 +23161,7 @@ msgstr "Passwort überschreitet die maximal zulässige Länge."
|
|||
|
||||
#: www/update-password.html:78
|
||||
msgid "Passwords do not match"
|
||||
msgstr ""
|
||||
msgstr "Passwörter stimmen nicht überein"
|
||||
|
||||
#: core/doctype/user/user.js:191
|
||||
msgid "Passwords do not match!"
|
||||
|
|
@ -25367,13 +25367,13 @@ msgstr "Erneut in der Konsole ausführen"
|
|||
|
||||
#: email/doctype/email_account/email_account.py:660
|
||||
msgid "Re:"
|
||||
msgstr ""
|
||||
msgstr "AW:"
|
||||
|
||||
#: core/doctype/communication/communication.js:268
|
||||
#: public/js/frappe/form/footer/form_timeline.js:587
|
||||
#: public/js/frappe/views/communication.js:347
|
||||
msgid "Re: {0}"
|
||||
msgstr ""
|
||||
msgstr "AW: {0}"
|
||||
|
||||
#: client.py:459
|
||||
msgid "Read"
|
||||
|
|
@ -25622,12 +25622,12 @@ msgstr "Empfänger"
|
|||
#. Name of a DocType
|
||||
#: core/doctype/recorder/recorder.json
|
||||
msgid "Recorder"
|
||||
msgstr ""
|
||||
msgstr "Rekorder"
|
||||
|
||||
#. Name of a DocType
|
||||
#: core/doctype/recorder_query/recorder_query.json
|
||||
msgid "Recorder Query"
|
||||
msgstr ""
|
||||
msgstr "Rekorder-Abfrage"
|
||||
|
||||
#: core/doctype/user_permission/user_permission_help.html:2
|
||||
msgid "Records for following doctypes will be filtered"
|
||||
|
|
@ -26844,7 +26844,7 @@ msgstr "Benötigt einen gültigen fdn-Pfad. z.B. ou=users,dc=example,dc=com"
|
|||
|
||||
#: core/doctype/communication/communication.js:279
|
||||
msgid "Res: {0}"
|
||||
msgstr ""
|
||||
msgstr "AW: {0}"
|
||||
|
||||
#: desk/doctype/form_tour/form_tour.js:101
|
||||
#: desk/doctype/global_search_settings/global_search_settings.js:19
|
||||
|
|
@ -26955,7 +26955,7 @@ msgstr "Antwort"
|
|||
#: email/doctype/email_template/email_template.json
|
||||
msgctxt "Email Template"
|
||||
msgid "Response "
|
||||
msgstr ""
|
||||
msgstr "Antwort "
|
||||
|
||||
#. Label of a Select field in DocType 'OAuth Client'
|
||||
#: integrations/doctype/oauth_client/oauth_client.json
|
||||
|
|
@ -30631,13 +30631,13 @@ msgstr "Pos"
|
|||
|
||||
#: core/doctype/recorder/recorder.js:33
|
||||
msgid "Stack Trace"
|
||||
msgstr ""
|
||||
msgstr "Stack Trace"
|
||||
|
||||
#. Label of a HTML field in DocType 'Recorder Query'
|
||||
#: core/doctype/recorder_query/recorder_query.json
|
||||
msgctxt "Recorder Query"
|
||||
msgid "Stack Trace"
|
||||
msgstr ""
|
||||
msgstr "Stack Trace"
|
||||
|
||||
#: core/doctype/user_type/user_type_list.js:5
|
||||
msgid "Standard"
|
||||
|
|
@ -31264,7 +31264,7 @@ msgstr "Betreff Der Feldtyp sollte Daten, Text, Langtext, Kleiner Text, Textedit
|
|||
#. Name of a DocType
|
||||
#: core/doctype/submission_queue/submission_queue.json
|
||||
msgid "Submission Queue"
|
||||
msgstr ""
|
||||
msgstr "Buchungs-Warteschlange"
|
||||
|
||||
#: core/doctype/user_permission/user_permission_list.js:138
|
||||
#: public/js/frappe/form/quick_entry.js:198
|
||||
|
|
@ -33281,19 +33281,19 @@ msgstr "Titel der Seite"
|
|||
#: public/js/frappe/views/communication.js:53
|
||||
#: public/js/frappe/views/inbox/inbox_view.js:70
|
||||
msgid "To"
|
||||
msgstr "Zu"
|
||||
msgstr "An"
|
||||
|
||||
#. Label of a Code field in DocType 'Communication'
|
||||
#: core/doctype/communication/communication.json
|
||||
msgctxt "Communication"
|
||||
msgid "To"
|
||||
msgstr "Zu"
|
||||
msgstr "An"
|
||||
|
||||
#. Label of a Section Break field in DocType 'Newsletter'
|
||||
#: email/doctype/newsletter/newsletter.json
|
||||
msgctxt "Newsletter"
|
||||
msgid "To"
|
||||
msgstr "Zu"
|
||||
msgstr "An"
|
||||
|
||||
#: website/report/website_analytics/website_analytics.js:14
|
||||
msgid "To Date"
|
||||
|
|
@ -33706,7 +33706,7 @@ msgstr "E-Mail-Status verfolgen"
|
|||
#: automation/doctype/milestone/milestone.json
|
||||
msgctxt "Milestone"
|
||||
msgid "Track Field"
|
||||
msgstr ""
|
||||
msgstr "Verfolge Feld"
|
||||
|
||||
#. Label of a Check field in DocType 'DocType'
|
||||
#: core/doctype/doctype/doctype.json
|
||||
|
|
@ -35694,7 +35694,7 @@ msgstr "Tutorial ansehen"
|
|||
#: desk/doctype/onboarding_step/onboarding_step.json
|
||||
msgctxt "Onboarding Step"
|
||||
msgid "Watch Video"
|
||||
msgstr "Schau Video"
|
||||
msgstr "Video ansehen"
|
||||
|
||||
#: desk/doctype/workspace/workspace.js:38
|
||||
msgid "We do not allow editing of this document. Simply click the Edit button on the workspace page to make your workspace editable and customize it as you wish"
|
||||
|
|
@ -35963,13 +35963,13 @@ msgstr "Website-Meta-Tag"
|
|||
#. Name of a DocType
|
||||
#: website/doctype/website_route_meta/website_route_meta.json
|
||||
msgid "Website Route Meta"
|
||||
msgstr ""
|
||||
msgstr "Meta-Tags für Website-Pfad"
|
||||
|
||||
#. Label of a Link in the Website Workspace
|
||||
#: website/workspace/website/website.json
|
||||
msgctxt "Website Route Meta"
|
||||
msgid "Website Route Meta"
|
||||
msgstr ""
|
||||
msgstr "Meta-Tags für Website-Pfad"
|
||||
|
||||
#. Name of a DocType
|
||||
#: website/doctype/website_route_redirect/website_route_redirect.json
|
||||
|
|
@ -37372,7 +37372,7 @@ msgstr "Der Parameter `job_id` ist für die Deduplizierung erforderlich."
|
|||
|
||||
#: public/js/frappe/form/footer/version_timeline_content_builder.js:219
|
||||
msgid "added rows for {0}"
|
||||
msgstr "Zeilen für {0} hinzugefügt"
|
||||
msgstr "hat Zeilen für {0} hinzugefügt"
|
||||
|
||||
#. Option for the 'Icon' (Select) field in DocType 'Workflow State'
|
||||
#: workflow/doctype/workflow_state/workflow_state.json
|
||||
|
|
@ -37415,7 +37415,7 @@ msgstr "rechtsbündig"
|
|||
#: core/doctype/permission_inspector/permission_inspector.json
|
||||
msgctxt "Permission Inspector"
|
||||
msgid "amend"
|
||||
msgstr ""
|
||||
msgstr "berichtigen"
|
||||
|
||||
#: public/js/frappe/utils/utils.js:396 utils/data.py:1504
|
||||
msgid "and"
|
||||
|
|
@ -37551,13 +37551,13 @@ msgstr "Kamera"
|
|||
#: core/doctype/permission_inspector/permission_inspector.json
|
||||
msgctxt "Permission Inspector"
|
||||
msgid "cancel"
|
||||
msgstr ""
|
||||
msgstr "stornieren"
|
||||
|
||||
#. Option for the 'Status' (Select) field in DocType 'RQ Job'
|
||||
#: core/doctype/rq_job/rq_job.json
|
||||
msgctxt "RQ Job"
|
||||
msgid "canceled"
|
||||
msgstr ""
|
||||
msgstr "storniert"
|
||||
|
||||
#. Option for the 'Icon' (Select) field in DocType 'Workflow State'
|
||||
#: workflow/doctype/workflow_state/workflow_state.json
|
||||
|
|
@ -37644,7 +37644,7 @@ msgstr "kommentierte(n)"
|
|||
#: core/doctype/permission_inspector/permission_inspector.json
|
||||
msgctxt "Permission Inspector"
|
||||
msgid "create"
|
||||
msgstr ""
|
||||
msgstr "erstellen"
|
||||
|
||||
#. Option for the 'Indicator Color' (Select) field in DocType 'Workspace'
|
||||
#: desk/doctype/workspace/workspace.json
|
||||
|
|
@ -37708,7 +37708,7 @@ msgstr "aufgeschoben"
|
|||
#: core/doctype/permission_inspector/permission_inspector.json
|
||||
msgctxt "Permission Inspector"
|
||||
msgid "delete"
|
||||
msgstr ""
|
||||
msgstr "löschen"
|
||||
|
||||
#: public/js/frappe/ui/sort_selector.html:5
|
||||
#: public/js/frappe/ui/sort_selector.js:48
|
||||
|
|
@ -37860,7 +37860,7 @@ msgstr "Apple FaceTime-Video"
|
|||
#: core/doctype/rq_job/rq_job.json
|
||||
msgctxt "RQ Job"
|
||||
msgid "failed"
|
||||
msgstr ""
|
||||
msgstr "fehlgeschlagen"
|
||||
|
||||
#. Option for the 'Social Login Provider' (Select) field in DocType 'Social
|
||||
#. Login Key'
|
||||
|
|
@ -37903,7 +37903,7 @@ msgstr "Filter"
|
|||
#: core/doctype/rq_job/rq_job.json
|
||||
msgctxt "RQ Job"
|
||||
msgid "finished"
|
||||
msgstr ""
|
||||
msgstr "fertig"
|
||||
|
||||
#. Option for the 'Icon' (Select) field in DocType 'Workflow State'
|
||||
#: workflow/doctype/workflow_state/workflow_state.json
|
||||
|
|
@ -38495,7 +38495,7 @@ msgstr "zufällig"
|
|||
#: core/doctype/permission_inspector/permission_inspector.json
|
||||
msgctxt "Permission Inspector"
|
||||
msgid "read"
|
||||
msgstr ""
|
||||
msgstr "lesen"
|
||||
|
||||
#. Option for the 'Indicator Color' (Select) field in DocType 'Workspace'
|
||||
#: desk/doctype/workspace/workspace.json
|
||||
|
|
@ -38529,7 +38529,7 @@ msgstr "Entfernen-Zeichen"
|
|||
|
||||
#: public/js/frappe/form/footer/version_timeline_content_builder.js:221
|
||||
msgid "removed rows for {0}"
|
||||
msgstr "entfernte Zeilen für {0}"
|
||||
msgstr "hat Zeilen von {0} entfernt"
|
||||
|
||||
#: model/rename_doc.py:214
|
||||
msgid "renamed from {0} to {1}"
|
||||
|
|
@ -38610,7 +38610,7 @@ msgstr "s256"
|
|||
#: core/doctype/rq_job/rq_job.json
|
||||
msgctxt "RQ Job"
|
||||
msgid "scheduled"
|
||||
msgstr ""
|
||||
msgstr "geplant"
|
||||
|
||||
#. Option for the 'Icon' (Select) field in DocType 'Workflow State'
|
||||
#: workflow/doctype/workflow_state/workflow_state.json
|
||||
|
|
@ -38629,7 +38629,7 @@ msgstr "Suche"
|
|||
#: core/doctype/permission_inspector/permission_inspector.json
|
||||
msgctxt "Permission Inspector"
|
||||
msgid "select"
|
||||
msgstr ""
|
||||
msgstr "auswählen"
|
||||
|
||||
#. Option for the 'Permission Type' (Select) field in DocType 'Permission
|
||||
#. Inspector'
|
||||
|
|
@ -38706,7 +38706,7 @@ msgstr "sternenleer"
|
|||
#: core/doctype/rq_job/rq_job.json
|
||||
msgctxt "RQ Job"
|
||||
msgid "started"
|
||||
msgstr ""
|
||||
msgstr "gestartet"
|
||||
|
||||
#: desk/page/setup_wizard/setup_wizard.js:194
|
||||
msgid "starting the setup..."
|
||||
|
|
@ -38756,7 +38756,7 @@ msgstr "String-Wert, z.B {0} oder uid={0},ou=users,dc=example,dc=com"
|
|||
#: core/doctype/permission_inspector/permission_inspector.json
|
||||
msgctxt "Permission Inspector"
|
||||
msgid "submit"
|
||||
msgstr ""
|
||||
msgstr "buchen"
|
||||
|
||||
#. Option for the 'Icon' (Select) field in DocType 'Workflow State'
|
||||
#: workflow/doctype/workflow_state/workflow_state.json
|
||||
|
|
@ -38962,7 +38962,7 @@ msgstr "Schraubenschlüssel"
|
|||
#: core/doctype/permission_inspector/permission_inspector.json
|
||||
msgctxt "Permission Inspector"
|
||||
msgid "write"
|
||||
msgstr ""
|
||||
msgstr "schreiben"
|
||||
|
||||
#. Option for the 'Indicator Color' (Select) field in DocType 'Workspace'
|
||||
#: desk/doctype/workspace/workspace.json
|
||||
|
|
|
|||
1312
frappe/locale/es.po
1312
frappe/locale/es.po
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
13930
frappe/locale/tr.po
13930
frappe/locale/tr.po
File diff suppressed because it is too large
Load diff
|
|
@ -983,6 +983,9 @@ class BaseDocument:
|
|||
self.throw_length_exceeded_error(df, max_length, value)
|
||||
|
||||
elif column_type in ("int", "bigint", "smallint"):
|
||||
if cint(df.get("length")) > 11: # We implicitl switch to bigint for >11
|
||||
column_type = "bigint"
|
||||
|
||||
max_length = max_positive_value[column_type]
|
||||
|
||||
if abs(cint(value)) > max_length:
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ from frappe.utils.data import get_absolute_url, get_datetime, get_timedelta, get
|
|||
from frappe.utils.global_search import update_global_search
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from frappe.core.doctype.docfield.docfield import DocField
|
||||
|
||||
|
||||
|
|
@ -144,7 +146,7 @@ class Document(BaseDocument):
|
|||
def is_locked(self):
|
||||
return file_lock.lock_exists(self.get_signature())
|
||||
|
||||
def load_from_db(self):
|
||||
def load_from_db(self) -> "Self":
|
||||
"""Load document and children from database and create properties
|
||||
from fields"""
|
||||
self.flags.ignore_children = True
|
||||
|
|
@ -204,7 +206,7 @@ class Document(BaseDocument):
|
|||
|
||||
return self
|
||||
|
||||
def reload(self):
|
||||
def reload(self) -> "Self":
|
||||
"""Reload document from database"""
|
||||
return self.load_from_db()
|
||||
|
||||
|
|
@ -248,7 +250,7 @@ class Document(BaseDocument):
|
|||
ignore_mandatory=None,
|
||||
set_name=None,
|
||||
set_child_names=True,
|
||||
) -> "Document":
|
||||
) -> "Self":
|
||||
"""Insert the document in the database (as a new document).
|
||||
This will check for user permissions and execute `before_insert`,
|
||||
`validate`, `on_update`, `after_insert` methods if they are written.
|
||||
|
|
@ -333,11 +335,11 @@ class Document(BaseDocument):
|
|||
if self.creation and self.is_locked:
|
||||
raise frappe.DocumentLockedError
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
def save(self, *args, **kwargs) -> "Self":
|
||||
"""Wrapper for _save"""
|
||||
return self._save(*args, **kwargs)
|
||||
|
||||
def _save(self, ignore_permissions=None, ignore_version=None) -> "Document":
|
||||
def _save(self, ignore_permissions=None, ignore_version=None) -> "Self":
|
||||
"""Save the current document in the database in the **DocType**'s table or
|
||||
`tabSingles` (for single types).
|
||||
|
||||
|
|
|
|||
|
|
@ -332,11 +332,6 @@ def has_user_permission(doc, user=None, debug=False):
|
|||
debug and _debug_log("User is not affected by any user permissions")
|
||||
return True
|
||||
|
||||
# user can create own role permissions, so nothing applies
|
||||
if get_role_permissions("User Permission", user=user).get("write"):
|
||||
debug and _debug_log("User permission bypassed because user can modify user permissions.")
|
||||
return True
|
||||
|
||||
# don't apply strict user permissions for single doctypes since they contain empty link fields
|
||||
apply_strict_user_permissions = (
|
||||
False if doc.meta.issingle else frappe.get_system_settings("apply_strict_user_permissions")
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ frappe.ui.form.ControlTable = class ControlTable extends frappe.ui.form.Control
|
|||
const doctype = grid.doctype;
|
||||
const row_docname = $(e.target).closest(".grid-row").data("name");
|
||||
const in_grid_form = $(e.target).closest(".form-in-grid").length;
|
||||
const value_formatter_map = {
|
||||
Date: (val) => (val ? frappe.datetime.user_to_str(val) : val),
|
||||
Int: (val) => cint(val),
|
||||
Check: (val) => cint(val),
|
||||
Float: (val) => flt(val),
|
||||
Currency: (val) => flt(val),
|
||||
};
|
||||
|
||||
let pasted_data = frappe.utils.get_clipboard_data(e);
|
||||
|
||||
|
|
@ -34,10 +41,13 @@ frappe.ui.form.ControlTable = class ControlTable extends frappe.ui.form.Control
|
|||
if (data.length === 1 && data[0].length === 1) return;
|
||||
|
||||
let fieldnames = [];
|
||||
let fieldtypes = [];
|
||||
// for raw data with column header
|
||||
if (this.get_field(data[0][0])) {
|
||||
data[0].forEach((column) => {
|
||||
fieldnames.push(this.get_field(column));
|
||||
const df = frappe.meta.get_docfield(doctype, this.get_field(column));
|
||||
fieldtypes.push(df ? df.fieldtype : "");
|
||||
});
|
||||
data.shift();
|
||||
} else {
|
||||
|
|
@ -51,6 +61,8 @@ frappe.ui.form.ControlTable = class ControlTable extends frappe.ui.form.Control
|
|||
column.fieldname === $(e.target).data("fieldname")
|
||||
) {
|
||||
fieldnames.push(column.fieldname);
|
||||
const df = frappe.meta.get_docfield(doctype, column.fieldname);
|
||||
fieldtypes.push(df ? df.fieldtype : "");
|
||||
target_column_matched = true;
|
||||
}
|
||||
});
|
||||
|
|
@ -73,6 +85,10 @@ frappe.ui.form.ControlTable = class ControlTable extends frappe.ui.form.Control
|
|||
const row_name = grid_rows[row_idx - 1].doc.name;
|
||||
row.forEach((value, data_index) => {
|
||||
if (fieldnames[data_index]) {
|
||||
// format value before setting
|
||||
value = value_formatter_map[fieldtypes[data_index]]
|
||||
? value_formatter_map[fieldtypes[data_index]](value)
|
||||
: value;
|
||||
frappe.model.set_value(
|
||||
doctype,
|
||||
row_name,
|
||||
|
|
|
|||
|
|
@ -1730,17 +1730,19 @@ frappe.ui.form.Form = class FrappeForm {
|
|||
if (opts.child) {
|
||||
// update child doc
|
||||
opts.child = locals[opts.child.doctype][opts.child.name];
|
||||
|
||||
var std_field_list = ["doctype"]
|
||||
.concat(frappe.model.std_fields_list)
|
||||
.concat(frappe.model.child_table_field_list);
|
||||
for (var key in r.message) {
|
||||
if (std_field_list.indexOf(key) === -1) {
|
||||
opts.child[key] = r.message[key];
|
||||
// if child row is deleted, don't update
|
||||
if (opts.child) {
|
||||
var std_field_list = ["doctype"]
|
||||
.concat(frappe.model.std_fields_list)
|
||||
.concat(frappe.model.child_table_field_list);
|
||||
for (var key in r.message) {
|
||||
if (std_field_list.indexOf(key) === -1) {
|
||||
opts.child[key] = r.message[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
me.fields_dict[opts.child.parentfield].refresh();
|
||||
me.fields_dict[opts.child.parentfield].refresh();
|
||||
}
|
||||
} else {
|
||||
// update parent doc
|
||||
me.set_value(r.message);
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ export default class Grid {
|
|||
this.df.data = this.get_data();
|
||||
this.df.data = this.df.data.filter((row) => row.idx != doc.idx);
|
||||
}
|
||||
this.grid_rows_by_docname[doc.name].remove();
|
||||
this.grid_rows_by_docname[doc.name]?.remove();
|
||||
dirty = true;
|
||||
});
|
||||
tasks.push(() => frappe.timeout(0.1));
|
||||
|
|
@ -795,7 +795,7 @@ export default class Grid {
|
|||
}
|
||||
|
||||
set_value(fieldname, value, doc) {
|
||||
if (this.display_status !== "None" && this.grid_rows_by_docname[doc.name]) {
|
||||
if (this.display_status !== "None" && doc.name && this.grid_rows_by_docname[doc.name]) {
|
||||
this.grid_rows_by_docname[doc.name].refresh_field(fieldname, value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -212,6 +212,10 @@ frappe.ui.form.Layout = class Layout {
|
|||
|
||||
const parent = this.column.form.get(0);
|
||||
const fieldobj = this.init_field(df, parent, render);
|
||||
|
||||
// An invalid control name will return in a null fieldobj
|
||||
if (!fieldobj) return;
|
||||
|
||||
this.fields_list.push(fieldobj);
|
||||
this.fields_dict[df.fieldname] = fieldobj;
|
||||
|
||||
|
|
@ -234,7 +238,11 @@ frappe.ui.form.Layout = class Layout {
|
|||
layout: this,
|
||||
});
|
||||
|
||||
fieldobj.layout = this;
|
||||
// make_control can return null for invalid control names
|
||||
if (fieldobj) {
|
||||
fieldobj.layout = this;
|
||||
}
|
||||
|
||||
return fieldobj;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ frappe.views.ListSidebar = class ListSidebar {
|
|||
|
||||
const message = __("Get more insights with");
|
||||
const link = "https://frappe.io/s/insights";
|
||||
const cta = __("Frappe Insights");
|
||||
const cta = "Frappe Insights";
|
||||
|
||||
this.insights_banner = $(`
|
||||
<div style="position: relative;">
|
||||
|
|
|
|||
|
|
@ -2042,7 +2042,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
};
|
||||
|
||||
// bulk edit
|
||||
if (has_editable_fields(doctype)) {
|
||||
if (has_editable_fields(doctype) && !frappe.model.has_workflow(doctype)) {
|
||||
actions_menu_items.push(bulk_edit());
|
||||
}
|
||||
|
||||
|
|
@ -2077,7 +2077,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
|
|||
}
|
||||
|
||||
// bulk delete
|
||||
if (frappe.model.can_delete(doctype)) {
|
||||
if (frappe.model.can_delete(doctype) && !frappe.model.has_workflow(doctype)) {
|
||||
actions_menu_items.push(bulk_delete());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,10 +59,9 @@ frappe.workflow = {
|
|||
|
||||
var state =
|
||||
doc[state_fieldname] || frappe.workflow.get_default_state(doctype, doc.docstatus);
|
||||
if (!state) return false;
|
||||
|
||||
let allow_edit_roles = state
|
||||
? frappe.workflow.get_document_state_roles(doctype, state)
|
||||
: null;
|
||||
let allow_edit_roles = frappe.workflow.get_document_state_roles(doctype, state);
|
||||
let has_common_role = frappe.user_roles.some((role) =>
|
||||
allow_edit_roles.includes(role)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -152,6 +152,11 @@ frappe.ui.keys.AltShortcutGroup = class AltShortcutGroup {
|
|||
}
|
||||
|
||||
underline_text(shortcut) {
|
||||
if (frappe.boot.lang === "eo") {
|
||||
// The language code "eo" is used to trigger the In-Context Translation feature.
|
||||
// In this case we don't want shortcuts to rip apart the ID of the translatable text.
|
||||
return;
|
||||
}
|
||||
shortcut.$text_el.attr("data-label", encodeURIComponent(shortcut.text));
|
||||
let underline_el_found = false;
|
||||
let text_html = shortcut.text
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ frappe.ui.Filter = class {
|
|||
let args = {};
|
||||
if (this.filters_config[condition].depends_on) {
|
||||
const field_name = this.filters_config[condition].depends_on;
|
||||
const filter_value = this.filter_list.get_filter_value(fieldname);
|
||||
const filter_value = this.filter_list.get_filter_value(field_name);
|
||||
args[field_name] = filter_value;
|
||||
}
|
||||
let setup_field = (field) => {
|
||||
|
|
@ -424,6 +424,12 @@ frappe.ui.filter_utils = {
|
|||
|
||||
let val = field.get_value() ?? field.value;
|
||||
|
||||
if (!val && ["Link", "Dynamic Link"].includes(field.df.fieldtype)) {
|
||||
// HACK: link field with show title are async so their input value is "" but they have
|
||||
// some actual value set.
|
||||
val = field.value;
|
||||
}
|
||||
|
||||
if (typeof val === "string") {
|
||||
val = strip(val);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ frappe.ui.Notifications = class Notifications {
|
|||
this.body = this.dropdown_list.find(".notification-list-body");
|
||||
this.panel_events = this.dropdown_list.find(".panel-events");
|
||||
this.panel_notifications = this.dropdown_list.find(".panel-notifications");
|
||||
this.panel_changelog_feed = this.dropdown_list.find(".panel-changelog-feed");
|
||||
|
||||
this.user = frappe.session.user;
|
||||
|
||||
|
|
@ -52,11 +53,17 @@ frappe.ui.Notifications = class Notifications {
|
|||
el: this.panel_notifications,
|
||||
},
|
||||
{
|
||||
label: __("Today's Events"),
|
||||
label: __("Events"),
|
||||
id: "todays_events",
|
||||
view: EventsView,
|
||||
el: this.panel_events,
|
||||
},
|
||||
{
|
||||
label: __("What's New"),
|
||||
id: "changelog_feed",
|
||||
view: ChangelogFeedView,
|
||||
el: this.panel_changelog_feed,
|
||||
},
|
||||
];
|
||||
|
||||
let get_headers_html = (item) => {
|
||||
|
|
@ -439,3 +446,53 @@ class EventsView extends BaseNotificationsView {
|
|||
this.container.html(html);
|
||||
}
|
||||
}
|
||||
|
||||
class ChangelogFeedView extends BaseNotificationsView {
|
||||
make() {
|
||||
this.render_changelog_feed_html(frappe.boot.changelog_feed || []);
|
||||
}
|
||||
|
||||
render_changelog_feed_html(changelog_feed) {
|
||||
let html = "";
|
||||
if (changelog_feed.length) {
|
||||
this.container.empty();
|
||||
const get_changelog_feed_html = (changelog_feed_item) => {
|
||||
const timestamp = frappe.datetime.prettyDate(
|
||||
changelog_feed_item.posting_timestamp
|
||||
);
|
||||
const message_html = `<div class="message">
|
||||
<div>${changelog_feed_item.title}</div>
|
||||
<div class="notification-timestamp text-muted">
|
||||
${changelog_feed_item.app_title} | ${timestamp}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
const item_html = `<a class="recent-item notification-item"
|
||||
href="${changelog_feed_item.link}"
|
||||
data-name="${changelog_feed_item.title}"
|
||||
target="_blank" rel="noopener noreferrer"
|
||||
>
|
||||
<div class="notification-body">
|
||||
${message_html}
|
||||
</div>
|
||||
</div>
|
||||
</a>`;
|
||||
|
||||
return item_html;
|
||||
};
|
||||
html = changelog_feed.map(get_changelog_feed_html).join("");
|
||||
} else {
|
||||
html = `<div class="notification-null-state">
|
||||
<div class="text-center">
|
||||
<img src="/assets/frappe/images/ui-states/notification-empty-state.svg" alt="Generic Empty State" class="null-state">
|
||||
<div class="title">${__("Nothing New")}</div>
|
||||
<div class="subtitle">
|
||||
${__("There is nothing new to show you right now.")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
this.container.html(html);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
</button>
|
||||
<div class="flex fill-width title-area">
|
||||
<div>
|
||||
<div class="flex">
|
||||
<div class="flex col-md-8 col-sm-8">
|
||||
<h3 class="ellipsis title-text"></h3>
|
||||
<span class="indicator-pill whitespace-nowrap"></span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ frappe.ui.misc.about = function () {
|
|||
<hr>
|
||||
<h4>${__("Installed Apps")}</h4>
|
||||
<div id='about-app-versions'>${__("Loading versions...")}</div>
|
||||
<p>
|
||||
<b>
|
||||
<a href="/attribution" target="_blank" class="text-muted">
|
||||
${__("Dependencies & Licenses")}
|
||||
</a>
|
||||
</b>
|
||||
</p>
|
||||
<hr>
|
||||
<p class='text-muted'>${__("© Frappe Technologies Pvt. Ltd. and contributors")} </p>
|
||||
</div>`,
|
||||
|
|
|
|||
|
|
@ -1,139 +1,154 @@
|
|||
<header class="navbar navbar-expand sticky-top" role="navigation">
|
||||
<div class="container">
|
||||
<a class="navbar-brand navbar-home" href="/app">
|
||||
<img
|
||||
class="app-logo"
|
||||
style="width: {{ navbar_settings.logo_width || 28 }}px"
|
||||
src="{{ frappe.boot.app_logo_url }}"
|
||||
alt="{{ __("App Logo") }}"
|
||||
>
|
||||
</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">
|
||||
<input
|
||||
id="navbar-search"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="{%= __('Search or type a command ({0})', [frappe.utils.is_mac() ? '⌘ + G' : 'Ctrl + G']) %}"
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<span class="search-icon">
|
||||
<svg class="icon icon-sm"><use href="#icon-search"></use></svg>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown dropdown-notifications dropdown-mobile hidden">
|
||||
<button
|
||||
class="btn-reset nav-link notifications-icon text-muted"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="notifications-seen">
|
||||
<span class="sr-only">{{ __("No new notifications") }}</span>
|
||||
<svg class="es-icon icon-sm" style="stroke:none;"><use href="#es-line-notifications"></use></svg>
|
||||
<div class="sticky-top">
|
||||
<header class="navbar navbar-expand" role="navigation">
|
||||
<div class="container">
|
||||
<a class="navbar-brand navbar-home" href="/app">
|
||||
<img
|
||||
class="app-logo"
|
||||
style="width: {{ navbar_settings.logo_width || 28 }}px"
|
||||
src="{{ frappe.boot.app_logo_url }}"
|
||||
alt="{{ __("App Logo") }}"
|
||||
>
|
||||
</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>
|
||||
<span class="notifications-unseen">
|
||||
<span class="sr-only">{{ __("You have unseen notifications") }}</span>
|
||||
<svg class="es-icon icon-sm"><use href="#es-line-notifications-unseen"></use></svg>
|
||||
{% } %}
|
||||
{% 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>
|
||||
</button>
|
||||
<div class="dropdown-menu notifications-list dropdown-menu-right" role="menu">
|
||||
<div class="notification-list-header">
|
||||
<div class="header-items"></div>
|
||||
<div class="header-actions"></div>
|
||||
{% } %}
|
||||
<div class="input-group search-bar text-muted hidden">
|
||||
<input
|
||||
id="navbar-search"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="{%= __('Search or type a command ({0})', [frappe.utils.is_mac() ? '⌘ + G' : 'Ctrl + G']) %}"
|
||||
aria-haspopup="true"
|
||||
>
|
||||
<span class="search-icon">
|
||||
<svg class="icon icon-sm"><use href="#icon-search"></use></svg>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown dropdown-notifications dropdown-mobile hidden">
|
||||
<button
|
||||
class="btn-reset nav-link notifications-icon text-muted"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="notifications-seen">
|
||||
<span class="sr-only">{{ __("No new notifications") }}</span>
|
||||
<svg class="es-icon icon-sm" style="stroke:none;"><use href="#es-line-notifications"></use></svg>
|
||||
</span>
|
||||
<span class="notifications-unseen">
|
||||
<span class="sr-only">{{ __("You have unseen notifications") }}</span>
|
||||
<svg class="es-icon icon-sm"><use href="#es-line-notifications-unseen"></use></svg>
|
||||
</span>
|
||||
</button>
|
||||
<div class="dropdown-menu notifications-list dropdown-menu-right" role="menu">
|
||||
<div class="notification-list-header">
|
||||
<div class="header-items"></div>
|
||||
<div class="header-actions"></div>
|
||||
</div>
|
||||
<div class="notification-list-body">
|
||||
<div class="panel-notifications"></div>
|
||||
<div class="panel-events"></div>
|
||||
<div class="panel-changelog-feed"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification-list-body">
|
||||
<div class="panel-notifications"></div>
|
||||
<div class="panel-events"></div>
|
||||
</li>
|
||||
<li class="nav-item dropdown dropdown-message dropdown-mobile hidden">
|
||||
<button
|
||||
class="btn-reset nav-link notifications-icon text-muted"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
>
|
||||
<span>
|
||||
<svg class="es-icon icon-sm"><use href="#es-line-chat-alt"></use></svg>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="vertical-bar d-none d-sm-block"></li>
|
||||
<li class="nav-item dropdown dropdown-help dropdown-mobile d-none d-lg-block">
|
||||
<button
|
||||
class="btn-reset nav-link"
|
||||
data-toggle="dropdown"
|
||||
aria-controls="toolbar-help"
|
||||
aria-label="{{ __("Help Dropdown") }}"
|
||||
>
|
||||
<span>
|
||||
{{ __("Help") }}
|
||||
<svg class="es-icon icon-xs"><use href="#es-line-down"></use></svg>
|
||||
</span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" id="toolbar-help" role="menu">
|
||||
<div id="help-links"></div>
|
||||
<div class="dropdown-divider documentation-links"></div>
|
||||
{% for item in navbar_settings.help_dropdown %}
|
||||
{% if (!item.hidden) { %}
|
||||
{% if (item.route) { %}
|
||||
<a class="dropdown-item" href="{{ item.route }}">
|
||||
{%= __(item.item_label) %}
|
||||
</a>
|
||||
{% } else if (item.action) { %}
|
||||
<button class="btn-reset dropdown-item" onclick="return {{ item.action }}">
|
||||
{%= __(item.item_label) %}
|
||||
</button>
|
||||
{% } else { %}
|
||||
<div class="dropdown-divider"></div>
|
||||
{% } %}
|
||||
{% } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown dropdown-message dropdown-mobile hidden">
|
||||
<button
|
||||
class="btn-reset nav-link notifications-icon text-muted"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
>
|
||||
<span>
|
||||
<svg class="es-icon icon-sm"><use href="#es-line-chat-alt"></use></svg>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="vertical-bar d-none d-sm-block"></li>
|
||||
<li class="nav-item dropdown dropdown-help dropdown-mobile d-none d-lg-block">
|
||||
<button
|
||||
class="btn-reset nav-link"
|
||||
data-toggle="dropdown"
|
||||
aria-controls="toolbar-help"
|
||||
aria-label="{{ __("Help Dropdown") }}"
|
||||
>
|
||||
<span>
|
||||
{{ __("Help") }}
|
||||
<svg class="es-icon icon-xs"><use href="#es-line-down"></use></svg>
|
||||
</span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" id="toolbar-help" role="menu">
|
||||
<div id="help-links"></div>
|
||||
<div class="dropdown-divider documentation-links"></div>
|
||||
{% for item in navbar_settings.help_dropdown %}
|
||||
{% if (!item.hidden) { %}
|
||||
{% if (item.route) { %}
|
||||
<a class="dropdown-item" href="{{ item.route }}">
|
||||
{%= __(item.item_label) %}
|
||||
</a>
|
||||
{% } else if (item.action) { %}
|
||||
<button class="btn-reset dropdown-item" onclick="return {{ item.action }}">
|
||||
{%= __(item.item_label) %}
|
||||
</button>
|
||||
{% } else { %}
|
||||
<div class="dropdown-divider"></div>
|
||||
</li>
|
||||
<li class="nav-item dropdown dropdown-navbar-user dropdown-mobile">
|
||||
<button
|
||||
class="btn-reset nav-link"
|
||||
data-toggle="dropdown"
|
||||
aria-label="{{ __("User Menu") }}"
|
||||
>
|
||||
{{ avatar }}
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" id="toolbar-user" role="menu">
|
||||
{% for item in navbar_settings.settings_dropdown %}
|
||||
{% if (!item.hidden) { %}
|
||||
{% if (item.route) { %}
|
||||
<a class="dropdown-item" href="{{ item.route }}">
|
||||
{%= __(item.item_label) %}
|
||||
</a>
|
||||
{% } else if (item.action) { %}
|
||||
<button class="btn-reset dropdown-item" onclick="return {{ item.action }}">
|
||||
{%= __(item.item_label) %}
|
||||
</button>
|
||||
{% } else { %}
|
||||
<div class="dropdown-divider"></div>
|
||||
{% } %}
|
||||
{% } %}
|
||||
{% } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown dropdown-navbar-user dropdown-mobile">
|
||||
<button
|
||||
class="btn-reset nav-link"
|
||||
data-toggle="dropdown"
|
||||
aria-label="{{ __("User Menu") }}"
|
||||
>
|
||||
{{ avatar }}
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" id="toolbar-user" role="menu">
|
||||
{% for item in navbar_settings.settings_dropdown %}
|
||||
{% if (!item.hidden) { %}
|
||||
{% if (item.route) { %}
|
||||
<a class="dropdown-item" href="{{ item.route }}">
|
||||
{%= __(item.item_label) %}
|
||||
</a>
|
||||
{% } else if (item.action) { %}
|
||||
<button class="btn-reset dropdown-item" onclick="return {{ item.action }}">
|
||||
{%= __(item.item_label) %}
|
||||
</button>
|
||||
{% } else { %}
|
||||
<div class="dropdown-divider"></div>
|
||||
{% } %}
|
||||
{% } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{% if !localStorage.getItem("dismissed_announcement_widget") && strip_html(navbar_settings.announcement_widget) != '' %}
|
||||
<div class="announcement-widget form-message p-2 m-0" style="position: relative; z-index: -1; border-radius: 0; background-color: var(--bg-blue);">
|
||||
<div class="container flex justify-between align-center mx-auto">
|
||||
{{ navbar_settings.announcement_widget }}
|
||||
<div class="close-message p-0 mr-2" style="position: relative;">
|
||||
{{ frappe.utils.icon("close") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ frappe.ui.toolbar.Toolbar = class {
|
|||
this.setup_notifications();
|
||||
this.setup_help();
|
||||
this.setup_read_only_mode();
|
||||
this.setup_announcement_widget();
|
||||
this.make();
|
||||
}
|
||||
|
||||
|
|
@ -56,6 +57,30 @@ frappe.ui.toolbar.Toolbar = class {
|
|||
});
|
||||
}
|
||||
|
||||
setup_announcement_widget() {
|
||||
let current_announcement = frappe.boot.navbar_settings.announcement_widget;
|
||||
|
||||
if (!current_announcement) return;
|
||||
|
||||
// If an unseen announcement is added, overlook dismiss flag
|
||||
if (current_announcement != localStorage.getItem("announcement_widget")) {
|
||||
localStorage.removeItem("dismissed_announcement_widget");
|
||||
localStorage.setItem("announcement_widget", current_announcement);
|
||||
}
|
||||
|
||||
// When an announcement is closed, add dismiss flag
|
||||
if (!localStorage.getItem("dismissed_announcement_widget")) {
|
||||
let announcement_widget = $(".announcement-widget");
|
||||
let close_message = announcement_widget.find(".close-message");
|
||||
close_message.on(
|
||||
"click",
|
||||
() =>
|
||||
localStorage.setItem("dismissed_announcement_widget", true) ||
|
||||
announcement_widget.addClass("hidden")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setup_help() {
|
||||
if (!frappe.boot.desk_settings.notifications) {
|
||||
// hide the help section
|
||||
|
|
|
|||
|
|
@ -98,7 +98,9 @@ export default class Widget {
|
|||
let base = this.title || this.label || this.name;
|
||||
let title = max_chars ? frappe.ellipsis(base, max_chars) : base;
|
||||
|
||||
this.title_field[0].innerHTML = `<span class="ellipsis" title="${title}">${title}</span>`;
|
||||
this.title_field[0].innerHTML = `<span class="ellipsis" title="${__(title)}">${__(
|
||||
title
|
||||
)}</span>`;
|
||||
if (max_chars) {
|
||||
this.title_field[0].setAttribute("title", this.title || this.label);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,6 +292,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
p:not(:first-child) {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ql-editor td {
|
||||
border: 1px solid var(--dark-border-color);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,7 +147,8 @@ def rate_limit(
|
|||
value = frappe.cache.incrby(cache_key, 1)
|
||||
if value > _limit:
|
||||
frappe.throw(
|
||||
_("You hit the rate limit because of too many requests. Please try after sometime.")
|
||||
_("You hit the rate limit because of too many requests. Please try after sometime."),
|
||||
frappe.RateLimitExceededError,
|
||||
)
|
||||
|
||||
return fn(*args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -114,21 +114,9 @@ def emit_via_redis(event, message, room):
|
|||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def can_subscribe_doc(doctype: str, docname: str) -> bool:
|
||||
from frappe.exceptions import PermissionError
|
||||
|
||||
if not frappe.has_permission(doctype=doctype, doc=docname, ptype="read"):
|
||||
raise PermissionError()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def can_subscribe_doctype(doctype: str) -> bool:
|
||||
from frappe.exceptions import PermissionError
|
||||
|
||||
if not frappe.has_permission(doctype=doctype, ptype="read"):
|
||||
raise PermissionError()
|
||||
def has_permission(doctype: str, name: str) -> bool:
|
||||
if not frappe.has_permission(doctype=doctype, doc=name, ptype="read"):
|
||||
raise frappe.PermissionError
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -138,6 +126,7 @@ def get_user_info():
|
|||
return {
|
||||
"user": frappe.session.user,
|
||||
"user_type": frappe.session.data.user_type,
|
||||
"installed_apps": frappe.get_installed_apps(),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -82,8 +82,6 @@ def delete_session(sid=None, user=None, reason="Session Expired"):
|
|||
# we should just ignore it till database is back up again.
|
||||
return
|
||||
|
||||
frappe.cache.hdel("session", sid)
|
||||
frappe.cache.hdel("last_db_session_update", sid)
|
||||
if sid and not user:
|
||||
table = frappe.qb.DocType("Sessions")
|
||||
user_details = frappe.qb.from_(table).where(table.sid == sid).select(table.user).run(as_dict=True)
|
||||
|
|
@ -94,6 +92,9 @@ def delete_session(sid=None, user=None, reason="Session Expired"):
|
|||
frappe.db.delete("Sessions", {"sid": sid})
|
||||
frappe.db.commit()
|
||||
|
||||
frappe.cache.hdel("session", sid)
|
||||
frappe.cache.hdel("last_db_session_update", sid)
|
||||
|
||||
|
||||
def clear_all_sessions(reason=None):
|
||||
"""This effectively logs out all users"""
|
||||
|
|
@ -167,6 +168,7 @@ def get():
|
|||
|
||||
bootinfo["desk_theme"] = frappe.db.get_value("User", frappe.session.user, "desk_theme") or "Light"
|
||||
bootinfo["user"]["impersonated_by"] = frappe.session.data.get("impersonated_by")
|
||||
bootinfo["navbar_settings"] = frappe.get_cached_doc("Navbar Settings")
|
||||
|
||||
return bootinfo
|
||||
|
||||
|
|
@ -358,7 +360,7 @@ class Session:
|
|||
|
||||
def update(self, force=False):
|
||||
"""extend session expiry"""
|
||||
if frappe.session["user"] == "Guest" or frappe.form_dict.cmd == "logout":
|
||||
if frappe.session.user == "Guest":
|
||||
return
|
||||
|
||||
now = frappe.utils.now()
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ class TestAuth(FrappeTestCase):
|
|||
# Rate limiting
|
||||
for _ in range(6):
|
||||
res = requests.get(_generate_temporary_login_link(user, 10))
|
||||
if res.status_code == 417:
|
||||
if res.status_code == 429:
|
||||
break
|
||||
else:
|
||||
self.fail("Rate limting not working")
|
||||
|
|
|
|||
|
|
@ -144,7 +144,6 @@ class TestClient(FrappeTestCase):
|
|||
first_item = data["message"][0]
|
||||
self.assertTrue("name" in first_item)
|
||||
self.assertTrue("modified" in first_item)
|
||||
frappe.local.login_manager.logout()
|
||||
|
||||
def test_client_get(self):
|
||||
from frappe.client import get
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class TestDBUpdate(FrappeTestCase):
|
|||
)
|
||||
default = field_def.default if field_def.default is not None else fallback_default
|
||||
|
||||
self.assertEqual(fieldtype, table_column.type)
|
||||
self.assertIn(fieldtype, table_column.type, msg=f"Types not matching for {fieldname}")
|
||||
self.assertIn(cstr(table_column.default) or "NULL", [cstr(default), f"'{default}'"])
|
||||
|
||||
def test_index_and_unique_constraints(self):
|
||||
|
|
@ -99,6 +99,16 @@ class TestDBUpdate(FrappeTestCase):
|
|||
len(indexes), 1, msg=f"There should be 1 index on {doctype}.{field}, found {indexes}"
|
||||
)
|
||||
|
||||
def test_bigint_conversion(self):
|
||||
doctype = new_doctype(fields=[{"fieldname": "int_field", "fieldtype": "Int"}]).insert()
|
||||
|
||||
with self.assertRaises(frappe.CharacterLengthExceededError):
|
||||
frappe.get_doc(doctype=doctype.name, int_field=2**62 - 1).insert()
|
||||
|
||||
doctype.fields[0].length = 14
|
||||
doctype.save()
|
||||
frappe.get_doc(doctype=doctype.name, int_field=2**62 - 1).insert()
|
||||
|
||||
@run_only_if(db_type_is.MARIADB)
|
||||
def test_unique_index_on_install(self):
|
||||
"""Only one unique index should be added"""
|
||||
|
|
@ -157,7 +167,7 @@ class TestDBUpdate(FrappeTestCase):
|
|||
def get_fieldtype_from_def(field_def):
|
||||
fieldtuple = frappe.db.type_map.get(field_def.fieldtype, ("", 0))
|
||||
fieldtype = fieldtuple[0]
|
||||
if fieldtype in ("varchar", "datetime", "int"):
|
||||
if fieldtype in ("varchar", "datetime"):
|
||||
fieldtype += f"({field_def.length or fieldtuple[1]})"
|
||||
return fieldtype
|
||||
|
||||
|
|
|
|||
|
|
@ -421,13 +421,6 @@ class TestPermissions(FrappeTestCase):
|
|||
clear_user_permissions_for_doctype("Salutation")
|
||||
clear_user_permissions_for_doctype("Contact")
|
||||
|
||||
def test_user_permissions_not_applied_if_user_can_edit_user_permissions(self):
|
||||
add_user_permission("Blogger", "_Test Blogger 1", "test1@example.com")
|
||||
|
||||
# test1@example.com has rights to create user permissions
|
||||
# so it should not matter if explicit user permissions are not set
|
||||
self.assertTrue(frappe.get_doc("Blogger", "_Test Blogger").has_permission("read"))
|
||||
|
||||
def test_user_permission_is_not_applied_if_user_roles_does_not_have_permission(self):
|
||||
add_user_permission("Blog Post", "-test-blog-post-1", "test3@example.com")
|
||||
frappe.set_user("test3@example.com")
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ def capture_exception(message: str | None = None) -> None:
|
|||
exc_info = sys.exc_info()
|
||||
if any(exc_info):
|
||||
# Don't report validation errors
|
||||
if isinstance(exc_info[0], frappe.ValidationError):
|
||||
if isinstance(exc_info[1], frappe.ValidationError):
|
||||
return
|
||||
|
||||
event, hint = event_from_exception(
|
||||
|
|
|
|||
118
frappe/www/attribution.html
Normal file
118
frappe/www/attribution.html
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
{% extends "templates/web.html" %}
|
||||
|
||||
{% block head_include %}
|
||||
<link rel="stylesheet" href="/assets/frappe/css/fonts/fontawesome/font-awesome.min.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
|
||||
<h1>{{ _("Attribution") }}</h1>
|
||||
<p>
|
||||
{{ _("This software is built on top of many open source packages. We would like to thank the authors of these
|
||||
packages for their contribution.") }}
|
||||
</p>
|
||||
|
||||
{% for app_info in apps %}
|
||||
<section id="{{ app_info.name }}">
|
||||
<h2><a href="#{{ app_info.name }}">{{ app_info.name }}</a></h2>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{{ _("Authors") }}</th>
|
||||
<td>{{ app_info.authors }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ _("Description") }}</th>
|
||||
<td>{{ app_info.description }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ _("Dependencies") }}</th>
|
||||
<td>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _("Package") }}</th>
|
||||
<th>{{ _("Type") }}</th>
|
||||
<th>{{ _("License") }}</th>
|
||||
<th>{{ _("Authors / Maintainers") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for package in app_info["dependencies"] %}
|
||||
<tr>
|
||||
<td class="package">
|
||||
{{ package.name | e }}
|
||||
</td>
|
||||
<td class="type">{{ package.type }}</td>
|
||||
<td class="license"><i class="fa fa-spinner fa-spin"></i></td>
|
||||
<td class="author"><i class="fa fa-spinner fa-spin"></i></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</table>
|
||||
</section>
|
||||
{% endfor %}
|
||||
|
||||
<script>
|
||||
const tables = document.querySelectorAll('table.table-striped');
|
||||
tables.forEach((table) => {
|
||||
var package_cells = table.querySelectorAll('td.package');
|
||||
package_cells.forEach((package_cell) => {
|
||||
var name = package_cell.innerText;
|
||||
var type_cell = package_cell.nextElementSibling;
|
||||
var license_cell = type_cell.nextElementSibling;
|
||||
var author_cell = license_cell.nextElementSibling;
|
||||
if (type_cell.innerText === "JavaScript") {
|
||||
get_info_from_npm(name).then((info) => {
|
||||
license_cell.innerText = info.license;
|
||||
author_cell.innerText = info.author;
|
||||
});
|
||||
}
|
||||
else if (type_cell.innerText === "Python") {
|
||||
get_info_from_pypi(name).then((info) => {
|
||||
license_cell.innerText = info.license;
|
||||
author_cell.innerText = info.author;
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
function get_info_from_npm(package) {
|
||||
const registry_url = `https://registry.npmjs.org/${package}`;
|
||||
return fetch(registry_url)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
return {
|
||||
license: (
|
||||
typeof data.license === "object" ? data.license.type : data.license
|
||||
) || "Unknown",
|
||||
author: (
|
||||
data.author?.name ||
|
||||
data.maintainers?.map((c) => c.name).join(", ") ||
|
||||
"Unknown"
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function get_info_from_pypi(package) {
|
||||
const registry_url = `https://pypi.org/pypi/${package}/json`;
|
||||
return fetch(registry_url)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
return {
|
||||
license: data.info.license || "Unknown",
|
||||
author: (
|
||||
data.info.author ||
|
||||
data.info.maintainer ||
|
||||
data.info.author_email ||
|
||||
data.info.maintainer_email ||
|
||||
"Unknown"
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
70
frappe/www/attribution.py
Normal file
70
frappe/www/attribution.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import tomllib
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.permissions import is_system_user
|
||||
|
||||
|
||||
def get_context(context):
|
||||
if not is_system_user():
|
||||
frappe.throw(_("You need to be a system user to access this page."), frappe.PermissionError)
|
||||
|
||||
apps = []
|
||||
for app in frappe.get_installed_apps():
|
||||
app_info = get_app_info(app)
|
||||
if any([app_info.get("authors"), app_info.get("dependencies"), app_info.get("description")]):
|
||||
apps.append(app_info)
|
||||
|
||||
context.apps = apps
|
||||
|
||||
|
||||
def get_app_info(app: str):
|
||||
app_info = get_pyproject_info(app)
|
||||
result = {
|
||||
"name": app,
|
||||
"description": app_info.get("description", ""),
|
||||
"authors": ", ".join([a.get("name", "") for a in app_info.get("authors", [])]),
|
||||
"dependencies": [],
|
||||
}
|
||||
|
||||
for requirement in app_info.get("dependencies", []):
|
||||
name = parse_pip_requirement(requirement)
|
||||
result["dependencies"].append({"name": name, "type": "Python"})
|
||||
|
||||
result["dependencies"].extend(get_js_deps(app))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_js_deps(app: str) -> list[dict]:
|
||||
package_json = Path(frappe.get_app_path(app, "..", "package.json"))
|
||||
if not package_json.exists():
|
||||
return {}
|
||||
|
||||
with open(package_json) as f:
|
||||
package = json.load(f)
|
||||
|
||||
packages = package.get("dependencies", {}).keys()
|
||||
return [{"name": name, "type": "JavaScript"} for name in packages]
|
||||
|
||||
|
||||
def get_pyproject_info(app: str) -> dict:
|
||||
pyproject_toml = Path(frappe.get_app_path(app, "..", "pyproject.toml"))
|
||||
if not pyproject_toml.exists():
|
||||
return {}
|
||||
|
||||
with open(pyproject_toml, "rb") as f:
|
||||
pyproject = tomllib.load(f)
|
||||
|
||||
return pyproject.get("project", {})
|
||||
|
||||
|
||||
def parse_pip_requirement(requirement: str) -> str:
|
||||
"""Parse pip requirement string to package name and version"""
|
||||
match = re.match(r"^([A-Za-z0-9_\-\[\]]+)(.*)$", requirement)
|
||||
|
||||
return match[1] if match else requirement
|
||||
|
|
@ -73,7 +73,6 @@
|
|||
"socket.io": "^4.7.1",
|
||||
"socket.io-client": "^4.7.1",
|
||||
"sortablejs": "^1.15.0",
|
||||
"superagent": "^8.0.0",
|
||||
"touch": "^3.1.0",
|
||||
"vue": "^3.3.0",
|
||||
"vue-router": "^4.1.5",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ dependencies = [
|
|||
"filetype~=1.2.0",
|
||||
"GitPython~=3.1.34",
|
||||
"Jinja2~=3.1.2",
|
||||
"Pillow~=10.2.0",
|
||||
"Pillow~=10.3.0",
|
||||
"PyJWT~=2.8.0",
|
||||
# We depend on internal attributes,
|
||||
# do NOT add loose requirements on PyMySQL versions.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
const { frappe_request } = require("../utils");
|
||||
const log = console.log;
|
||||
|
||||
const WEBSITE_ROOM = "website";
|
||||
const SITE_ROOM = "all";
|
||||
|
||||
function frappe_handlers(realtime, socket) {
|
||||
function frappe_handlers(socket) {
|
||||
socket.join(user_room(socket.user));
|
||||
socket.join(WEBSITE_ROOM);
|
||||
|
||||
|
|
@ -12,13 +9,23 @@ function frappe_handlers(realtime, socket) {
|
|||
socket.join(SITE_ROOM);
|
||||
}
|
||||
|
||||
socket.has_permission = (doctype, name) => {
|
||||
return new Promise((resolve) => {
|
||||
socket
|
||||
.frappe_request("/api/method/frappe.realtime.has_permission", { doctype, name })
|
||||
.then((res) => res.json())
|
||||
.then(({ message }) => {
|
||||
if (message) {
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch((err) => console.log("Can't check permissions", err));
|
||||
});
|
||||
};
|
||||
|
||||
socket.on("doctype_subscribe", function (doctype) {
|
||||
can_subscribe_doctype({
|
||||
socket,
|
||||
doctype,
|
||||
callback: () => {
|
||||
socket.join(doctype_room(doctype));
|
||||
},
|
||||
socket.has_permission(doctype).then(() => {
|
||||
socket.join(doctype_room(doctype));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -42,14 +49,8 @@ function frappe_handlers(realtime, socket) {
|
|||
});
|
||||
|
||||
socket.on("doc_subscribe", function (doctype, docname) {
|
||||
can_subscribe_doc({
|
||||
socket,
|
||||
doctype,
|
||||
docname,
|
||||
callback: () => {
|
||||
let room = doc_room(doctype, docname);
|
||||
socket.join(room);
|
||||
},
|
||||
socket.has_permission(doctype, docname).then(() => {
|
||||
socket.join(doc_room(doctype, docname));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -59,23 +60,18 @@ function frappe_handlers(realtime, socket) {
|
|||
});
|
||||
|
||||
socket.on("doc_open", function (doctype, docname) {
|
||||
can_subscribe_doc({
|
||||
socket,
|
||||
doctype,
|
||||
docname,
|
||||
callback: () => {
|
||||
let room = open_doc_room(doctype, docname);
|
||||
socket.join(room);
|
||||
if (!socket.subscribed_documents) socket.subscribed_documents = [];
|
||||
socket.subscribed_documents.push([doctype, docname]);
|
||||
socket.has_permission(doctype, docname).then(() => {
|
||||
let room = open_doc_room(doctype, docname);
|
||||
socket.join(room);
|
||||
if (!socket.subscribed_documents) socket.subscribed_documents = [];
|
||||
socket.subscribed_documents.push([doctype, docname]);
|
||||
|
||||
// show who is currently viewing the form
|
||||
notify_subscribed_doc_users({
|
||||
socket: socket,
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
});
|
||||
},
|
||||
// show who is currently viewing the form
|
||||
notify_subscribed_doc_users({
|
||||
socket: socket,
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -110,28 +106,6 @@ function notify_disconnected_documents(socket) {
|
|||
}
|
||||
}
|
||||
|
||||
function can_subscribe_doctype(args) {
|
||||
if (!args) return;
|
||||
if (!args.doctype) return;
|
||||
frappe_request("/api/method/frappe.realtime.can_subscribe_doctype", args.socket)
|
||||
.type("form")
|
||||
.query({
|
||||
doctype: args.doctype,
|
||||
})
|
||||
.end(function (err, res) {
|
||||
if (!res || res.status == 403 || err) {
|
||||
if (err) {
|
||||
log(err);
|
||||
}
|
||||
return false;
|
||||
} else if (res.status == 200) {
|
||||
args.callback && args.callback(err, res);
|
||||
return true;
|
||||
}
|
||||
log("ERROR (can_subscribe_doctype): ", err, res);
|
||||
});
|
||||
}
|
||||
|
||||
function notify_subscribed_doc_users(args) {
|
||||
if (!(args && args.doctype && args.docname)) {
|
||||
return;
|
||||
|
|
@ -160,30 +134,6 @@ function notify_subscribed_doc_users(args) {
|
|||
});
|
||||
}
|
||||
|
||||
function can_subscribe_doc(args) {
|
||||
if (!args) return;
|
||||
if (!args.doctype || !args.docname) return;
|
||||
frappe_request("/api/method/frappe.realtime.can_subscribe_doc", args.socket)
|
||||
.type("form")
|
||||
.query({
|
||||
doctype: args.doctype,
|
||||
docname: args.docname,
|
||||
})
|
||||
.end(function (err, res) {
|
||||
if (!res) {
|
||||
log("No response for doc_subscribe");
|
||||
} else if (res.status == 403) {
|
||||
return;
|
||||
} else if (err) {
|
||||
log(err);
|
||||
} else if (res.status == 200) {
|
||||
args.callback(err, res);
|
||||
} else {
|
||||
log("Something went wrong", err, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const doc_room = (doctype, docname) => "doc:" + doctype + "/" + docname;
|
||||
const open_doc_room = (doctype, docname) => "open_doc:" + doctype + "/" + docname;
|
||||
const user_room = (user) => "user:" + user;
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
const { Server } = require("socket.io");
|
||||
const http = require("node:http");
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { get_conf, get_redis_subscriber } = require("../node_utils");
|
||||
const conf = get_conf();
|
||||
|
||||
|
|
@ -25,10 +27,16 @@ const authenticate = require("./middlewares/authenticate");
|
|||
realtime.use(authenticate);
|
||||
// =======================
|
||||
|
||||
// load and register handlers
|
||||
const frappe_handlers = require("./handlers/frappe_handlers");
|
||||
function on_connection(socket) {
|
||||
frappe_handlers(realtime, socket);
|
||||
socket.installed_apps.forEach((app) => {
|
||||
let app_handler = get_app_handlers(app);
|
||||
try {
|
||||
app_handler && app_handler(socket);
|
||||
} catch (err) {
|
||||
console.warn(`failed to setup event handlers from ${app}`);
|
||||
console.warn(err);
|
||||
}
|
||||
});
|
||||
|
||||
// ESBUild "open in editor" on error
|
||||
socket.on("open_in_editor", async (data) => {
|
||||
|
|
@ -37,6 +45,27 @@ function on_connection(socket) {
|
|||
});
|
||||
}
|
||||
|
||||
const _app_handlers = {};
|
||||
function get_app_handlers(app) {
|
||||
if (app in _app_handlers) {
|
||||
return _app_handlers[app];
|
||||
}
|
||||
|
||||
let file = `../../${app}/realtime/handlers.js`;
|
||||
let abs_path = path.resolve(__dirname, file);
|
||||
let handler = null;
|
||||
if (fs.existsSync(abs_path)) {
|
||||
try {
|
||||
handler = require(file);
|
||||
} catch (err) {
|
||||
console.warn(`failed to load event handlers from ${abs_path}`);
|
||||
console.warn(err);
|
||||
}
|
||||
}
|
||||
_app_handlers[app] = handler;
|
||||
return handler;
|
||||
}
|
||||
|
||||
realtime.on("connection", on_connection);
|
||||
// =======================
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
const cookie = require("cookie");
|
||||
const request = require("superagent");
|
||||
const { get_url } = require("../utils");
|
||||
|
||||
const { get_conf } = require("../../node_utils");
|
||||
const { get_url } = require("../utils");
|
||||
const conf = get_conf();
|
||||
|
||||
function authenticate_with_frappe(socket, next) {
|
||||
|
|
@ -30,21 +28,35 @@ function authenticate_with_frappe(socket, next) {
|
|||
next(new Error("No authentication method used. Use cookie or authorization header."));
|
||||
return;
|
||||
}
|
||||
socket.sid = cookies.sid;
|
||||
socket.authorization_header = authorization_header;
|
||||
|
||||
let auth_req = request.get(get_url(socket, "/api/method/frappe.realtime.get_user_info"));
|
||||
if (cookies.sid) {
|
||||
auth_req = auth_req.query({ sid: cookies.sid });
|
||||
} else {
|
||||
auth_req = auth_req.set("Authorization", authorization_header);
|
||||
}
|
||||
socket.frappe_request = (path, args = {}, opts = {}) => {
|
||||
let query_args = new URLSearchParams(args);
|
||||
if (query_args.toString()) {
|
||||
path = path + "?" + query_args.toString();
|
||||
}
|
||||
|
||||
auth_req
|
||||
.type("form")
|
||||
.then((res) => {
|
||||
socket.user = res.body.message.user;
|
||||
socket.user_type = res.body.message.user_type;
|
||||
socket.sid = cookies.sid;
|
||||
socket.authorization_header = authorization_header;
|
||||
let headers = {};
|
||||
if (socket.sid) {
|
||||
headers["Cookie"] = `sid=${socket.sid}`;
|
||||
} else if (socket.authorization_header) {
|
||||
headers["Authorization"] = socket.authorization_header;
|
||||
}
|
||||
|
||||
return fetch(get_url(socket, path), {
|
||||
...opts,
|
||||
headers,
|
||||
});
|
||||
};
|
||||
|
||||
socket
|
||||
.frappe_request("/api/method/frappe.realtime.get_user_info")
|
||||
.then((res) => res.json())
|
||||
.then(({ message }) => {
|
||||
socket.user = message.user;
|
||||
socket.user_type = message.user_type;
|
||||
socket.installed_apps = message.installed_apps;
|
||||
next();
|
||||
})
|
||||
.catch((e) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const { get_conf } = require("../node_utils");
|
||||
const conf = get_conf();
|
||||
const request = require("superagent");
|
||||
|
||||
function get_url(socket, path) {
|
||||
if (!path) {
|
||||
|
|
@ -17,17 +16,6 @@ function get_url(socket, path) {
|
|||
return url + path;
|
||||
}
|
||||
|
||||
// Authenticates a partial request created using superagent
|
||||
function frappe_request(path, socket) {
|
||||
const partial_req = request.get(get_url(socket, path));
|
||||
if (socket.sid) {
|
||||
return partial_req.query({ sid: socket.sid });
|
||||
} else if (socket.authorization_header) {
|
||||
return partial_req.set("Authorization", socket.authorization_header);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
get_url,
|
||||
frappe_request,
|
||||
};
|
||||
|
|
|
|||
121
yarn.lock
121
yarn.lock
|
|
@ -449,7 +449,7 @@ array-buffer-byte-length@^1.0.0:
|
|||
call-bind "^1.0.2"
|
||||
is-array-buffer "^3.0.1"
|
||||
|
||||
asap@^2.0.0, asap@~2.0.3:
|
||||
asap@~2.0.3:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
|
||||
integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==
|
||||
|
|
@ -459,11 +459,6 @@ assert-never@^1.2.1:
|
|||
resolved "https://registry.yarnpkg.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe"
|
||||
integrity sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||
|
||||
at-least-node@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||
|
|
@ -705,13 +700,6 @@ colord@^2.9.1:
|
|||
resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
|
||||
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
|
||||
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
||||
|
|
@ -722,11 +710,6 @@ commander@^9.0.0:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30"
|
||||
integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
|
||||
|
||||
component-emitter@^1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17"
|
||||
integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
|
|
@ -752,11 +735,6 @@ cookie@^0.4.0, cookie@~0.4.1:
|
|||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
|
||||
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
|
||||
|
||||
cookiejar@^2.1.4:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b"
|
||||
integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==
|
||||
|
||||
copy-anything@^2.0.1:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480"
|
||||
|
|
@ -989,7 +967,7 @@ debug@^3.2.6:
|
|||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
|
||||
debug@^4.3.2, debug@~4.3.1, debug@~4.3.2:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
|
|
@ -1062,19 +1040,6 @@ define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0:
|
|||
has-property-descriptors "^1.0.0"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
|
||||
|
||||
dezalgo@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81"
|
||||
integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==
|
||||
dependencies:
|
||||
asap "^2.0.0"
|
||||
wrappy "1"
|
||||
|
||||
doctypes@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
|
||||
|
|
@ -1408,11 +1373,6 @@ fast-glob@^3.2.5:
|
|||
merge2 "^1.3.0"
|
||||
micromatch "^4.0.4"
|
||||
|
||||
fast-safe-stringify@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
|
||||
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
|
||||
|
||||
fastparse@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9"
|
||||
|
|
@ -1454,25 +1414,6 @@ for-each@^0.3.3:
|
|||
dependencies:
|
||||
is-callable "^1.1.3"
|
||||
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
formidable@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.1.2.tgz#fa973a2bec150e4ce7cac15589d7a25fc30ebd89"
|
||||
integrity sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==
|
||||
dependencies:
|
||||
dezalgo "^1.0.4"
|
||||
hexoid "^1.0.0"
|
||||
once "^1.4.0"
|
||||
qs "^6.11.0"
|
||||
|
||||
fraction.js@^4.3.6:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
|
||||
|
|
@ -1673,11 +1614,6 @@ hasown@^2.0.0:
|
|||
dependencies:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
hexoid@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
|
||||
integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==
|
||||
|
||||
highlight.js@^10.4.1:
|
||||
version "10.7.3"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
|
||||
|
|
@ -2122,13 +2058,6 @@ lru-cache@^4.1.2:
|
|||
pseudomap "^1.0.2"
|
||||
yallist "^2.1.2"
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
magic-string@^0.30.5:
|
||||
version "0.30.5"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9"
|
||||
|
|
@ -2170,11 +2099,6 @@ merge2@^1.3.0:
|
|||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
methods@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
|
||||
|
||||
micromatch@^4.0.4:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
|
||||
|
|
@ -2188,18 +2112,13 @@ mime-db@1.52.0:
|
|||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.34:
|
||||
mime-types@~2.1.34:
|
||||
version "2.1.35"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
||||
dependencies:
|
||||
mime-db "1.52.0"
|
||||
|
||||
mime@2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367"
|
||||
integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==
|
||||
|
||||
mime@^1.4.1:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
|
|
@ -2345,7 +2264,7 @@ object.assign@^4.1.4:
|
|||
has-symbols "^1.0.3"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
once@^1.3.0, once@^1.4.0:
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
|
||||
|
|
@ -2872,13 +2791,6 @@ pug@^3.0.1:
|
|||
pug-runtime "^3.0.1"
|
||||
pug-strip-comments "^2.0.0"
|
||||
|
||||
qs@^6.11.0:
|
||||
version "6.11.2"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9"
|
||||
integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
|
|
@ -3077,13 +2989,6 @@ semver@^6.3.0:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.3.8:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
set-function-length@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed"
|
||||
|
|
@ -3293,22 +3198,6 @@ stylus@^0.x:
|
|||
sax "~1.3.0"
|
||||
source-map "^0.7.3"
|
||||
|
||||
superagent@^8.0.0:
|
||||
version "8.1.2"
|
||||
resolved "https://registry.yarnpkg.com/superagent/-/superagent-8.1.2.tgz#03cb7da3ec8b32472c9d20f6c2a57c7f3765f30b"
|
||||
integrity sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==
|
||||
dependencies:
|
||||
component-emitter "^1.3.0"
|
||||
cookiejar "^2.1.4"
|
||||
debug "^4.3.4"
|
||||
fast-safe-stringify "^2.1.1"
|
||||
form-data "^4.0.0"
|
||||
formidable "^2.1.2"
|
||||
methods "^1.1.2"
|
||||
mime "2.6.0"
|
||||
qs "^6.11.0"
|
||||
semver "^7.3.8"
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||
|
|
@ -3569,7 +3458,7 @@ y18n@^5.0.5:
|
|||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||
|
||||
yallist@4.0.0, yallist@^4.0.0:
|
||||
yallist@4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue