Merge remote-tracking branch 'upstream/develop' into attribution-page

This commit is contained in:
barredterra 2024-04-06 20:00:16 +02:00
commit 595bb20a97
30 changed files with 48619 additions and 7160 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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))

View file

@ -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

View file

@ -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,16 +295,16 @@ 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`),
@ -323,11 +323,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`),

View file

@ -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

View file

@ -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"),

View file

@ -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

View file

@ -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]:

39798
frappe/locale/bs.po Normal file

File diff suppressed because it is too large Load diff

View file

@ -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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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:

View file

@ -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).

View file

@ -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")

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -292,6 +292,12 @@
}
}
.ql-editor {
p:not(:first-child) {
margin-top: 0.75rem;
}
}
.ql-editor td {
border: 1px solid var(--dark-border-color);
}

View file

@ -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(),
}

View file

@ -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

View file

@ -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")

View file

@ -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",

View file

@ -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.

View file

@ -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;

View file

@ -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);
// =======================

View file

@ -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) => {

View file

@ -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
View file

@ -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==