From a7c706672f4eb28bdaecb6109e042070db1da188 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Wed, 25 May 2022 01:12:48 +0200
Subject: [PATCH 001/205] fix: allow All to select a User
---
frappe/core/doctype/user/user.json | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json
index 42122ebfda..82e3fa71f3 100644
--- a/frappe/core/doctype/user/user.json
+++ b/frappe/core/doctype/user/user.json
@@ -722,7 +722,7 @@
"link_fieldname": "user"
}
],
- "modified": "2022-03-09 01:47:56.745069",
+ "modified": "2022-05-25 01:00:51.345319",
"modified_by": "Administrator",
"module": "Core",
"name": "User",
@@ -747,6 +747,10 @@
"read": 1,
"role": "System Manager",
"write": 1
+ },
+ {
+ "role": "All",
+ "select": 1
}
],
"quick_entry": 1,
From 4422754624297c1622efd321ca75519530e15eb7 Mon Sep 17 00:00:00 2001
From: Vladislav
Date: Tue, 31 May 2022 18:09:38 +0300
Subject: [PATCH 002/205] Update ru.csv
---
frappe/translations/ru.csv | 64 +++++++++++++++++++++-----------------
1 file changed, 36 insertions(+), 28 deletions(-)
diff --git a/frappe/translations/ru.csv b/frappe/translations/ru.csv
index 3fdeab5546..94a87bdcf8 100644
--- a/frappe/translations/ru.csv
+++ b/frappe/translations/ru.csv
@@ -840,7 +840,7 @@ Default Sending and Inbox,По умолчанию отправка и получ
Default Sort Field,Поле сортировки по умолчанию,
Default Sort Order,Порядок сортировки по умолчанию,
Default Value,Значение по умолчанию,
-"Default: ""Contact Us""","По умолчанию: ""Обратная связь""",
+"Default: ""Contact Us""","По умолчанию: ""Contact Us""",
DefaultValue,DefaultValue,
Define workflows for forms.,Определите рабочие процессы для форм.,
Defines actions on states and the next step and allowed roles.,"Определяет действия на статусах, следующий шаг и роли, обладающие правами перевода статусов.",
@@ -849,7 +849,7 @@ Delayed,Задерживается,
Delete Data,Удалить данные,
Delete comment?,Удалить комментарий?,
Delete this record to allow sending to this email address,"Удалить эту запись, чтобы разрешить отправку на этот адрес электронной почты",
-Delete {0} items permanently?,Удалить {0} продуктов навсегда?,
+Delete {0} items permanently?,Удалить {0} объектов навсегда?,
Deleted,Удаленный,
Deleted DocType,Удаленный DocType,
Deleted Document,Удаленный документ,
@@ -914,7 +914,7 @@ Document can't saved.,Документ не может быть сохранен
Document {0} has been set to state {1} by {2},Документ {0} установлен в состояние {1} на {2},
Documents,Документы,
Documents assigned to you and by you.,"Документы, назначенные вам и вами.",
-Domain Settings,Настройки домена,
+Domain Settings,Настройка сфер деятельности,
Domains HTML,Домены HTML,
"Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field","Не HTML Кодировать HTML-теги, такие как <скрипт> или просто символы, такие как <или>, так как они могут быть преднамеренно использованы в этой области",
Don't Override Status,Не переопределять статус,
@@ -990,7 +990,7 @@ Enable Auto Reply,Включить автоматический ответ,
Enable Automatic Backup,Включить автоматическое резервное копирование,
Enable Chat,Включить чат,
Enable Comments,Включить комментарии,
-Enable Incoming,Включение входящей,
+Enable Incoming,Включить входящие,
Enable Outgoing,Включить исходящие,
Enable Password Policy,Включить политику паролей,
Enable Print Server,Включить сервер печати,
@@ -1000,7 +1000,7 @@ Enable Scheduled Jobs,Включить запланированных задан
Enable Social Login,Включить социальный вход,
Enable Two Factor Auth,Включить двухфакторный аут,
Enabled email inbox for user {0},Включен почтовый ящик для пользователя {0},
-"Encryption key is invalid, Please check site_config.json","Ключ шифрования недействителен, проверьте сайт_config.json",
+"Encryption key is invalid, Please check site_config.json","Ключ шифрования недействителен, проверьте site_config.json",
End Date Field,Поле конечной даты,
End Date cannot be before Start Date!,Дата окончания не может быть до даты начала!,
Endpoint URL,URL конечной точки,
@@ -1029,8 +1029,8 @@ Error in Notification: {},Ошибка в уведомлении: {},
Error while connecting to email account {0},Ошибка при подключении к учетной записи электронной почты {0},
Error while evaluating Notification {0}. Please fix your template.,Ошибка при оценке уведомления {0}. Исправьте шаблон.,
Error: Document has been modified after you have opened it,"Ошибка: документ был изменен после того, как вы открыли его",
-Error: Value missing for {0}: {1},Ошибка: значение отсутствует для {0}: {1},
-Errors in Background Events,Ошибки в фоновых событий,
+Error: Value missing for {0}: {1},Ошибка: отсутствует значение для {0}: {1},
+Errors in Background Events,Ошибки в фоновых событиях,
Event Category,Категория события,
Event Participants,Участники мероприятия,
Event Type,Тип события,
@@ -1184,7 +1184,7 @@ Get Contacts,Получить контакты,
Get Fields,Получить поля,
Get your globally recognized avatar from Gravatar.com,Получить всемирно признанный аватара из Gravatar.com,
GitHub,GitHub,
-Give Review Points,Дайте очки обзора,
+Give Review Points,Дайте баллы обзора,
Global Unsubscribe,Глобальная отписка,
Go to the document,Перейти к документу,
Go to this URL after completing the form (only for Guest users),Перейдите по этому URL-адресу после заполнения формы (только для гостевых пользователей),
@@ -1461,8 +1461,8 @@ Letter Head Name,Название заголовка письма,
Letter Head in HTML,Заголовок письма в HTML,
Level Name,Название уровня,
Liked,Понравилось,
-Liked By,В избранное К,
-Liked by {0},В избранное {0},
+Liked By,Нравится,
+Liked by {0},Нравится {0},
Likes,Понравившееся,
Limit Number of DB Backups,Ограничение количества резервных копий БД,
Line,Линия,
@@ -1470,7 +1470,7 @@ Link DocType,Ссылка DocType,
Link Expired,Срок действия ссылки,
Link Name,Имя ссылки,
Link Title,Название ссылки,
-"Link that is the website home page. Standard Links (index, login, products, blog, about, contact)","Ссылка, которая является стартовой страницей сайта. Стандартные ссылки (индекс, логин, продукты, блог, о, контакт)",
+"Link that is the website home page. Standard Links (index, login, products, blog, about, contact)","Ссылка, которая является стартовой страницей сайта. Стандартные ссылки (index, login, products, blog, about, contact)",
Link to the page you want to open. Leave blank if you want to make it a group parent.,"Ссылка на страницу, которую вы хотите открыть. Оставьте пустым, если хотите сделать его родительским элементом группы.",
Linked,Связанный,
Linked With,Связанные с,
@@ -2096,7 +2096,7 @@ Revert Of,Вернуть из,
Reverted,Отменено,
Review Level,Уровень обзора,
Review Levels,Уровни обзора,
-Review Points,Очки обзора,
+Review Points,Баллы обзора,
Reviews,Отзывы,
Revoke,Аннулировать,
Revoked,Аннулировано,
@@ -2141,10 +2141,10 @@ SMS sent to following numbers: {0},SMS отправлено следующим
SMTP Server,SMTP-сервер,
SMTP Settings for outgoing emails,Настройки SMTP для исходящих писем,
"SQL Conditions. Example: status=""Open""",SQL условия. Пример: статус = "Открыть",
-SSL/TLS Mode,Режим SSL / TLS,
+SSL/TLS Mode,Режим SSL/TLS,
Salesforce,Salesforce,
Same Field is entered more than once,Одно и то же поле вводится не один раз,
-Save API Secret: ,Сохранить API-интерфейс:,
+Save API Secret: ,Сохранить API секрет: ,
Save As,Сохранить как,
Save Filter,Сохранить фильтр,
Save Report,Сохранить отчет,
@@ -2258,7 +2258,7 @@ Set Property After Alert,Задать свойство после оповеще
Set Quantity,Установите Количество,
Set Role For,Установить роль для,
Set User Permissions,Задание разрешений пользователя,
-Set Value,Задать значение,
+Set Value,Установить значение,
Set custom roles for page and report,Набор пользовательских ролей для страницы и отчета,
"Set default format, page size, print style etc.","Установить форму, размер страницы, стиль печати и т.д., используюмых по умолчанию",
Set non-standard precision for a Float or Currency field,Установите нестандартные точность для поплавка или валютной области,
@@ -2337,7 +2337,7 @@ Slideshow like display for the website,"Слайд-шоу, как дисплей
Small Text,Маленьикий текст,
Smallest Currency Fraction Value,Минимальное дробное значение,
Smallest circulating fraction unit (coin). For e.g. 1 cent for USD and it should be entered as 0.01,"Минимальная разменная денежная единица (монета). Например, для доллара — 1 цент, и его нужно ввести как 0,01",
-Snapshot View,Снимок Посмотреть,
+Snapshot View,Просмотр снимка,
Social,Сообщество,
Social Login Key,Ключ социального входа,
Social Login Provider,Социальный провайдер,
@@ -2418,7 +2418,7 @@ Suspend Sending,Приостановить Отправка,
Switch To Desk,Переключение на рабочий стол,
Symbol,Символ,
Sync,Синхронизация,
-Sync on Migrate,Синхронизация по Migrate,
+Sync on Migrate,Синхронизировать при переносе,
Syntax error in template,Синтаксическая ошибка в шаблоне,
System,Система,
System Page,Страница системы,
@@ -2450,7 +2450,7 @@ Thank you for your interest in subscribing to our updates,Спасибо за в
Thank you for your message,Спасибо за ваше сообщение,
The CSV format is case sensitive,Формат CSV чувствителен к регистру,
The Condition '{0}' is invalid,Условие '{0}' является недействительным,
-The First User: You,Первый пользователя: Вы,
+The First User: You,Первый пользователь: Вы,
"The application has been updated to a new version, please refresh this page","Приложение был обновлен до новой версии, пожалуйста, обновите эту страницу",
The attachments could not be correctly linked to the new document,Вложения не могут быть правильно связаны с новым документом,
The document could not be correctly assigned,Документ не может быть правильно назначен,
@@ -2653,7 +2653,7 @@ User Field,Поле пользователя,
User ID of a Blogger,ID пользователя-блоггера,
User Image,Изображение пользователя,
User Name,Имя пользователя,
-User Permission,Пользователь Введено,
+User Permission,Разрешения пользователя,
User Permissions,Разрешения пользователей,
User Permissions are used to limit users to specific records.,Пользовательские разрешения используются для ограничения пользователей конкретными записями.,
User Permissions created sucessfully,Пользовательские разрешения созданы успешно,
@@ -3068,8 +3068,8 @@ zoom-out,отдалить,
{0} or {1},{0} или {1},
{0} record deleted,{0} запись удалена,
{0} records deleted,{0} записей удалено,
-{0} reverted your point on {1},{0} вернул вашу точку на {1},
-{0} reverted your points on {1},{0} вернул ваши очки на {1},
+{0} reverted your point on {1},{0} вернул ваш балл на {1},
+{0} reverted your points on {1},{0} вернул ваши баллы на {1},
{0} reverted {1},{0} вернул {1},
{0} room must have atmost one user.,{0} номер должен иметь самого одного пользователя.,
{0} rows for {1},{0} строк для {1},
@@ -3148,7 +3148,7 @@ Access not allowed from this IP Address,Доступ с этого IP-адрес
Action Type,Тип действия,
Activity Log by ,Активность Журнал по,
Add Fields,Добавить поля,
-Administration,Администрация,
+Administration,Администрирование,
After Cancel,После отмены,
After Delete,После удаления,
After Save,После сохранения,
@@ -3157,7 +3157,7 @@ After Submit,После отправки,
Aggregate Function Based On,"Агрегатная функция, основанная на",
Aggregate Function field is required to create a dashboard chart,Поле Aggregate Function необходимо для создания диаграммы панели мониторинга.,
All Records,Все записи,
-Allot Points To Assigned Users,Выделить очки назначенным пользователям,
+Allot Points To Assigned Users,Выделить баллы назначенным пользователям,
Allow Auto Repeat,Разрешить автоматическое повторение,
Allow Google Calendar Access,Разрешить доступ к Календарю Google,
Allow Google Contacts Access,Разрешить доступ к контактам Google,
@@ -3380,7 +3380,7 @@ Invalid field name: {0},Неверное имя поля: {0},
Invalid file URL. Please contact System Administrator.,"Неверный URL файла. Пожалуйста, свяжитесь с системным администратором.",
Invalid include path,Неверный путь включения,
Invalid username or password,неправильное имя пользователя или пароль,
-Is Primary,Первичный,
+Is Primary,Основной,
Is Primary Mobile,Основной мобильный,
Is Primary Phone,Основной телефон,
Is Tree,Дерево,
@@ -3667,7 +3667,7 @@ via Data Import,через импорт данных,
{0} are mandatory fields,{0} обязательные поля,
{0} are required,{0} требуется,
{0} assigned a new task {1} {2} to you,{0} назначил вам новое задание {1} {2},
-{0} gained {1} point for {2} {3},{0} набрал {1} очко за {2} {3},
+{0} gained {1} point for {2} {3},{0} получил {1} балл за {2} {3},
{0} gained {1} points for {2} {3},{0} набрал {1} баллов за {2} {3},
{0} has no versions tracked.,{0} не отслеживает версии.,
{0} is not a valid report format. Report format should one of the following {1},{0} не является допустимым форматом отчета. Формат отчета должен быть одним из следующих {1},
@@ -4045,7 +4045,7 @@ No Permitted Charts on this Dashboard,На этой панели инструм
No Permitted Charts,Нет разрешенных графиков,
Reset Chart,Сбросить график,
via {0},через {0},
-{0} is not a valid Phone Number,{0} не является действительным номером телефона,
+{0} is not a valid Phone Number,{0} недействительный номер телефона,
Failed Transactions,Неудачные транзакции,
Value for field {0} is too long in {1}. Length should be lesser than {2} characters,Значение поля {0} слишком длинное в {1}. Длина должна быть меньше {2} симв.,
Data Too Long,Данные слишком длинные,
@@ -4121,8 +4121,8 @@ Using this console may allow attackers to impersonate you and steal your informa
{0} w,{0} н,
{0} M,{0} М,
{0} y,{0} г,
-yesterday,вчерашний день,
-{0} years ago,{0} лет назад,
+yesterday,вчера,
+{0} years ago,{0} год назад,
New Chart,Новый график,
New Shortcut,Новый ярлык,
Edit Chart,Изменить диаграмму,
@@ -4700,3 +4700,11 @@ Value cannot be negative for {0}: {1},Значение не может быть
Negative Value,Отрицательное значение,
Authentication failed while receiving emails from Email Account: {0}.,Ошибка аутентификации при получении писем из учетной записи электронной почты: {0}.,
Message from server: {0},Сообщение с сервера: {0},
+Documentation,Документация,
+User Forum,Форум пользователей,
+Report an issue,Сообщить об ошибке,
+My Profile,Мой профиль,
+My Settings,Мои настройки,
+Toggle Full Width,Переключить ширину,
+Toggle Theme,Переключить тему,
+Modules,Модули,
From e150d99ff02e1a000d2a5737b07b10b63b2d33b0 Mon Sep 17 00:00:00 2001
From: naveen <172697+naveensrinivasan@users.noreply.github.com>
Date: Thu, 9 Jun 2022 01:29:31 +0000
Subject: [PATCH 003/205] chore: Included githubactions in the dependabot
config
This should help with keeping the GitHub actions updated on new releases. This will also help with keeping it secure.
Dependabot helps in keeping the supply chain secure https://docs.github.com/en/code-security/dependabot
GitHub actions up to date https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
https://github.com/ossf/scorecard/blob/main/docs/checks.md#dependency-update-tool
Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com>
---
.github/dependabot.yml | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 .github/dependabot.yml
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..5ace4600a1
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
From ac59087f49510e22284a9cb872c8463305bd52be Mon Sep 17 00:00:00 2001
From: s-aga-r
Date: Thu, 9 Jun 2022 13:30:55 +0530
Subject: [PATCH 004/205] fix: can't create a new User Type
---
frappe/core/doctype/user_type/user_type.js | 9 ++------
frappe/core/doctype/user_type/user_type.json | 24 ++++++++++++--------
2 files changed, 16 insertions(+), 17 deletions(-)
diff --git a/frappe/core/doctype/user_type/user_type.js b/frappe/core/doctype/user_type/user_type.js
index c8bd499b58..6b53248fd4 100644
--- a/frappe/core/doctype/user_type/user_type.js
+++ b/frappe/core/doctype/user_type/user_type.js
@@ -3,13 +3,8 @@
frappe.ui.form.on('User Type', {
refresh: function(frm) {
- frm.toggle_display('is_standard', frappe.boot.developer_mode);
- frm.set_df_property('is_standard', 'read_only', !frappe.boot.developer_mode);
-
- const fields = ['role', 'apply_user_permission_on', 'user_id_field',
- 'user_doctypes', 'user_type_modules'];
-
- frm.toggle_display(fields, !frm.doc.is_standard);
+ if (frm.is_new() && !frappe.boot.developer_mode)
+ frm.set_value('is_standard', 1);
frm.set_query('document_type', 'user_doctypes', function() {
return {
diff --git a/frappe/core/doctype/user_type/user_type.json b/frappe/core/doctype/user_type/user_type.json
index 9ea5d5be71..3d6b470af5 100644
--- a/frappe/core/doctype/user_type/user_type.json
+++ b/frappe/core/doctype/user_type/user_type.json
@@ -22,9 +22,11 @@
"fields": [
{
"default": "0",
+ "depends_on": "eval: frappe.boot.developer_mode",
"fieldname": "is_standard",
"fieldtype": "Check",
- "label": "Is Standard"
+ "label": "Is Standard",
+ "read_only_depends_on": "eval: !frappe.boot.developer_mode"
},
{
"depends_on": "eval: !doc.is_standard",
@@ -33,21 +35,21 @@
"label": "Document Types and Permissions"
},
{
+ "depends_on": "eval: !doc.is_standard",
"fieldname": "user_doctypes",
"fieldtype": "Table",
"label": "Document Types",
"mandatory_depends_on": "eval: !doc.is_standard",
- "options": "User Document Type",
- "read_only": 1
+ "options": "User Document Type"
},
{
+ "depends_on": "eval: !doc.is_standard",
"fieldname": "role",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Role",
"mandatory_depends_on": "eval: !doc.is_standard",
- "options": "Role",
- "read_only": 1
+ "options": "Role"
},
{
"fieldname": "select_doctypes",
@@ -62,13 +64,13 @@
"fieldtype": "Column Break"
},
{
+ "depends_on": "eval: !doc.is_standard",
"description": "Can only list down the document types which has been linked to the User document type.",
"fieldname": "apply_user_permission_on",
"fieldtype": "Link",
"label": "Apply User Permission On",
"mandatory_depends_on": "eval: !doc.is_standard",
- "options": "DocType",
- "read_only": 1
+ "options": "DocType"
},
{
"depends_on": "eval: !doc.is_standard",
@@ -81,8 +83,7 @@
"fieldname": "user_id_field",
"fieldtype": "Select",
"label": "User Id Field",
- "mandatory_depends_on": "eval: !doc.is_standard",
- "read_only": 1
+ "mandatory_depends_on": "eval: !doc.is_standard"
},
{
"depends_on": "eval: !doc.is_standard",
@@ -93,6 +94,7 @@
{
"fieldname": "user_type_modules",
"fieldtype": "Table",
+ "label": "User Type Module",
"no_copy": 1,
"options": "User Type Module",
"print_hide": 1,
@@ -107,10 +109,11 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2021-03-12 16:25:18.639050",
+ "modified": "2022-06-09 14:00:36.820306",
"modified_by": "Administrator",
"module": "Core",
"name": "User Type",
+ "naming_rule": "Set by user",
"owner": "Administrator",
"permissions": [
{
@@ -137,5 +140,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
From c67ec6c87aeb3b829eecd96c30126bd0d420d326 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 9 Jun 2022 16:31:29 +0530
Subject: [PATCH 005/205] chore(deps): Bump cryptography from 3.4.8 to 37.0.2
changelog: https://cryptography.io/en/latest/changelog/
diff: https://github.com/pyca/cryptography/compare/3.4.8...37.0.x
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index bf798fe747..616f1a2c42 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,7 +7,7 @@ braintree~=4.8.0
chardet~=4.0.0
Click~=7.1.2
croniter~=1.0.11
-cryptography~=3.4.7
+cryptography~=37.0.2
dropbox~=11.7.0
email-reply-parser~=0.5.12
git-url-parse~=1.2.2
From 7e346933c73235e3a0f68e4c323083ab5c6f84d9 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 9 Jun 2022 16:43:58 +0530
Subject: [PATCH 006/205] fix: Cleaner error message on invalid encryption_key
---
frappe/utils/password.py | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/frappe/utils/password.py b/frappe/utils/password.py
index f2c4b9685a..c539891ac7 100644
--- a/frappe/utils/password.py
+++ b/frappe/utils/password.py
@@ -213,21 +213,16 @@ def decrypt(txt, encryption_key=None):
try:
cipher_suite = Fernet(encode(encryption_key or get_encryption_key()))
- plain_text = cstr(cipher_suite.decrypt(encode(txt)))
- return plain_text
+ return cstr(cipher_suite.decrypt(encode(txt)))
except InvalidToken:
# encryption_key in site_config is changed and not valid
- frappe.throw(
- _("Encryption key is invalid") + "!"
- if encryption_key
- else _(", please check site_config.json.")
- )
+ frappe.throw(_("Encryption key is invalid! Please check site_config.json"))
def get_encryption_key():
- from frappe.installer import update_site_config
-
if "encryption_key" not in frappe.local.conf:
+ from frappe.installer import update_site_config
+
encryption_key = Fernet.generate_key().decode()
update_site_config("encryption_key", encryption_key)
frappe.local.conf.encryption_key = encryption_key
From eb86a933e0ea22528a560c653d6f6418f6fa7ab5 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 9 Jun 2022 16:49:27 +0530
Subject: [PATCH 007/205] chore(deps): Bump IPython from 7.31.0 to 8.4.0
Major version includes multiple bug, security & performance fixes
changelog: https://ipython.readthedocs.io/en/stable/whatsnew/version8.html
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 616f1a2c42..6710dd4004 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -21,7 +21,7 @@ googlemaps~=4.4.5
gunicorn~=20.1.0
html2text==2020.1.16
html5lib~=1.1
-ipython~=7.31.1
+ipython~=8.4.0
Jinja2~=3.0.1
ldap3~=2.9
markdown2~=2.4.0
From e7f2b7c6287d1f80607803c7e227c617dd2868c5 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 10 Jun 2022 11:31:59 +0530
Subject: [PATCH 008/205] chore(deps): bump faker from 8.1.4 to 13.12.1
Faker is a dev dependency installed for tests - to access the
frappe.mock API to generate paragraphs, names & emails.
---
dev-requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dev-requirements.txt b/dev-requirements.txt
index f4045c6bed..b67e915a16 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,4 +1,4 @@
coverage==5.5
-Faker~=8.1.0
+Faker~=13.12.1
pyngrok~=5.0.5
unittest-xml-reporting~=3.0.4
From 88f27f4af4ce88c0e7de3e0a22115c21867fef45 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 10 Jun 2022 11:36:05 +0530
Subject: [PATCH 009/205] chore: Remove deprecated dependency_links from setup
* pdfkit dep is already being fulfilled by requirements.txt
* ref: https://stackoverflow.com/questions/12518499/pip-ignores-dependency-links-in-setup-py
---
setup.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/setup.py b/setup.py
index 92ff63baff..ba4034a766 100644
--- a/setup.py
+++ b/setup.py
@@ -53,7 +53,6 @@ setup(
zip_safe=False,
include_package_data=True,
install_requires=install_requires,
- dependency_links=["https://github.com/frappe/python-pdfkit.git#egg=pdfkit"],
cmdclass={"clean": CleanCommand},
python_requires=">=3.8",
)
From 3aef47200b3e0c1afbbe51e6af89e803118227a8 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 10 Jun 2022 11:53:50 +0530
Subject: [PATCH 010/205] chore(deps): Bumped pdfkit from 0.6.1 to 1.0.0
* Updated from_string API to match latest signature
* changelog: https://github.com/JazzCore/python-pdfkit/blob/master/HISTORY.rst
---
frappe/utils/pdf.py | 2 +-
requirements.txt | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py
index 952717434c..5e8b65a5bd 100644
--- a/frappe/utils/pdf.py
+++ b/frappe/utils/pdf.py
@@ -35,7 +35,7 @@ def get_pdf(html, options=None, output=None):
try:
# Set filename property to false, so no file is actually created
- filedata = pdfkit.from_string(html, False, options=options or {})
+ filedata = pdfkit.from_string(html, options=options or {}, verbose=True)
# https://pythonhosted.org/PyPDF2/PdfFileReader.html
# create in-memory binary streams from filedata and create a PdfFileReader object
diff --git a/requirements.txt b/requirements.txt
index 6710dd4004..b69716675e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -32,7 +32,7 @@ openpyxl~=3.0.7
parse~=1.19.0
passlib~=1.7.4
paytmchecksum~=1.7.0
-pdfkit~=0.6.1
+pdfkit~=1.0.0
Pillow~=9.0.0
premailer~=3.8.0
psutil~=5.8.0
From ef0a92c8499051c4ed4cb73740c1c9ed4ebed43d Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 10 Jun 2022 12:08:10 +0530
Subject: [PATCH 011/205] chore(deps): Bump PyPDF2 from 1.26.0 to 2.1.0
* Updated changes in API usages
* changelog: https://github.com/py-pdf/PyPDF2/blob/main/CHANGELOG
---
frappe/tests/test_pdf.py | 4 ++--
frappe/utils/pdf.py | 20 ++++++++++----------
frappe/utils/print_format.py | 6 +++---
requirements.txt | 2 +-
4 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/frappe/tests/test_pdf.py b/frappe/tests/test_pdf.py
index 497546ebd5..8f2a2c1cfa 100644
--- a/frappe/tests/test_pdf.py
+++ b/frappe/tests/test_pdf.py
@@ -3,7 +3,7 @@
import io
import unittest
-from PyPDF2 import PdfFileReader
+from PyPDF2 import PdfReader
import frappe
import frappe.utils.pdf as pdfgen
@@ -42,7 +42,7 @@ class TestPdf(unittest.TestCase):
def test_pdf_encryption(self):
password = "qwe"
pdf = pdfgen.get_pdf(self.html, options={"password": password})
- reader = PdfFileReader(io.BytesIO(pdf))
+ reader = PdfReader(io.BytesIO(pdf))
self.assertTrue(reader.isEncrypted)
self.assertTrue(reader.decrypt(password))
diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py
index 5e8b65a5bd..811a6511fd 100644
--- a/frappe/utils/pdf.py
+++ b/frappe/utils/pdf.py
@@ -5,10 +5,11 @@ import os
import re
import subprocess
from distutils.version import LooseVersion
+from typing import Optional
import pdfkit
from bs4 import BeautifulSoup
-from PyPDF2 import PdfFileReader, PdfFileWriter
+from PyPDF2 import PdfReader, PdfWriter
import frappe
from frappe import _
@@ -23,7 +24,7 @@ PDF_CONTENT_ERRORS = [
]
-def get_pdf(html, options=None, output=None):
+def get_pdf(html, options=None, output: Optional[PdfWriter] = None):
html = scrub_urls(html)
html, options = prepare_options(html, options)
@@ -37,9 +38,8 @@ def get_pdf(html, options=None, output=None):
# Set filename property to false, so no file is actually created
filedata = pdfkit.from_string(html, options=options or {}, verbose=True)
- # https://pythonhosted.org/PyPDF2/PdfFileReader.html
- # create in-memory binary streams from filedata and create a PdfFileReader object
- reader = PdfFileReader(io.BytesIO(filedata))
+ # create in-memory binary streams from filedata and create a PdfReader object
+ reader = PdfReader(io.BytesIO(filedata))
except OSError as e:
if any([error in str(e) for error in PDF_CONTENT_ERRORS]):
if not filedata:
@@ -47,8 +47,8 @@ def get_pdf(html, options=None, output=None):
frappe.throw(_("PDF generation failed because of broken image links"))
# allow pdfs with missing images if file got created
- if output: # output is a PdfFileWriter object
- output.appendPagesFromReader(reader)
+ if output:
+ output.append_pages_from_reader(reader)
else:
raise
finally:
@@ -58,11 +58,11 @@ def get_pdf(html, options=None, output=None):
password = options["password"]
if output:
- output.appendPagesFromReader(reader)
+ output.append_pages_from_reader(reader)
return output
- writer = PdfFileWriter()
- writer.appendPagesFromReader(reader)
+ writer = PdfWriter()
+ writer.append_pages_from_reader(reader)
if "password" in options:
writer.encrypt(password)
diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py
index 028501f306..a48d7ab84f 100644
--- a/frappe/utils/print_format.py
+++ b/frappe/utils/print_format.py
@@ -1,6 +1,6 @@
import os
-from PyPDF2 import PdfFileWriter
+from PyPDF2 import PdfWriter
import frappe
from frappe import _
@@ -58,7 +58,7 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options=
import json
- output = PdfFileWriter()
+ output = PdfWriter()
if isinstance(options, str):
options = json.loads(options)
@@ -152,7 +152,7 @@ def print_by_server(
cups.setServer(print_settings.server_ip)
cups.setPort(print_settings.port)
conn = cups.Connection()
- output = PdfFileWriter()
+ output = PdfWriter()
output = frappe.get_print(
doctype, name, print_format, doc=doc, no_letterhead=no_letterhead, as_pdf=True, output=output
)
diff --git a/requirements.txt b/requirements.txt
index b69716675e..62275b587e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -43,7 +43,7 @@ PyJWT~=2.0.1
PyMySQL~=1.0.2
pyOpenSSL~=20.0.1
pyotp~=2.6.0
-PyPDF2~=1.26.0
+PyPDF2~=2.1.0
PyPika~=0.48.9
pypng~=0.0.20
PyQRCode~=1.2.1
From 8fab5b96b79e134b428cbbda490eb99aa54fbaa0 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 10 Jun 2022 12:15:08 +0530
Subject: [PATCH 012/205] chore(deps): Bump pytz from 2021.1 to 2022.1
Multiple bug fixes & IANA 2022a
* changelog: https://github.com/stub42/pytz/blob/master/tz/NEWS
* diff: https://github.com/stub42/pytz/compare/release_2021.1...release_2022.1
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 62275b587e..085b596cea 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -48,7 +48,7 @@ PyPika~=0.48.9
pypng~=0.0.20
PyQRCode~=1.2.1
python-dateutil~=2.8.1
-pytz==2021.1
+pytz==2022.1
PyYAML~=5.4.1
rauth~=0.7.3
razorpay~=1.2.0
From 8f2bb4780a98081c841ace8f45e9bdad22d0ae7a Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 10 Jun 2022 12:30:07 +0530
Subject: [PATCH 013/205] chore(deps): Bump rq from 1.8.1 to 1.10.1
Exciting improvements to rq! Cleanup & bugs that made us sccratch heads
in prod.
changelog: https://github.com/rq/rq/blob/master/CHANGES.md
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 085b596cea..03731d77e5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -56,7 +56,7 @@ redis~=3.5.3
requests-oauthlib~=1.3.0
requests~=2.25.1
RestrictedPython~=5.1
-rq~=1.8.0
+rq~=1.10.1
rsa>=4.1 # not directly required, pinned by Snyk to avoid a vulnerability
schedule~=1.1.0
semantic-version~=2.8.5
From d60f228c9029528fd5f8d7e15e4e72f1dced2c00 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 10 Jun 2022 12:43:34 +0530
Subject: [PATCH 014/205] chore(deps): Bump Jinja2 from 3.0.3 to 3.1.2
changelog: https://jinja.palletsprojects.com/en/3.1.x/changes/
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 03731d77e5..3b99aa06d5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,7 +22,7 @@ gunicorn~=20.1.0
html2text==2020.1.16
html5lib~=1.1
ipython~=8.4.0
-Jinja2~=3.0.1
+Jinja2~=3.1.2
ldap3~=2.9
markdown2~=2.4.0
maxminddb-geolite2==2018.703
From 3d1df3525e78e8c0e8033e48aca2083ae901c12f Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 10 Jun 2022 12:46:59 +0530
Subject: [PATCH 015/205] chore(deps): Bump psutil from 5.8.0 to 5.9.1
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 3b99aa06d5..cc35b594c9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -35,7 +35,7 @@ paytmchecksum~=1.7.0
pdfkit~=1.0.0
Pillow~=9.0.0
premailer~=3.8.0
-psutil~=5.8.0
+psutil~=5.9.1
psycopg2-binary~=2.9.1
pyasn1~=0.4.8
pycryptodome~=3.10.1
From 5680f458c18f4b59bff645231d2bbb8e5c228656 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 10 Jun 2022 12:48:36 +0530
Subject: [PATCH 016/205] chore(deps): Bump Werkzeug from 2.0.3 to 2.1.2
changelog: https://werkzeug.palletsprojects.com/en/2.1.x/changes/
---
requirements.txt | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index cc35b594c9..2e3ec3434a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -65,7 +65,7 @@ stripe~=2.56.0
terminaltables~=3.1.0
traceback-with-variables~=2.0.4
urllib3~=1.26.4
-Werkzeug~=2.0.3
+Werkzeug~=2.1.2
Whoosh~=2.7.4
xlrd~=2.0.1
zxcvbn-python~=4.4.24
@@ -73,4 +73,3 @@ tenacity~=8.0.1
cairocffi==1.2.0
WeasyPrint==52.5
phonenumbers==8.12.40
-
From 65b011f14147f1e10670888f2424654096972192 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Sun, 5 Jun 2022 12:54:03 +0530
Subject: [PATCH 017/205] chore(deps): Bump semantic-version from 2.8.5 to
2.10.0
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 2e3ec3434a..6c16a5be85 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -59,7 +59,7 @@ RestrictedPython~=5.1
rq~=1.10.1
rsa>=4.1 # not directly required, pinned by Snyk to avoid a vulnerability
schedule~=1.1.0
-semantic-version~=2.8.5
+semantic-version~=2.10.0
sqlparse~=0.4.1
stripe~=2.56.0
terminaltables~=3.1.0
From 9bec09e14e20b8482baa867c1c7b793a42471202 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Sun, 5 Jun 2022 12:57:04 +0530
Subject: [PATCH 018/205] chore(deps): Bump croniter from 1.0.15 to 1.3.5
changelog: https://pypi.org/project/croniter/#changelog
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 6c16a5be85..53839d9e18 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@ boto3~=1.17.53
braintree~=4.8.0
chardet~=4.0.0
Click~=7.1.2
-croniter~=1.0.11
+croniter~=1.3.5
cryptography~=37.0.2
dropbox~=11.7.0
email-reply-parser~=0.5.12
From 111ad3cc82de1a7d6d89f5121e5e1dcdb98dd5fe Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 2 Jun 2022 12:58:40 +0530
Subject: [PATCH 019/205] chore(deps): Bump requests from 2.25.1 to 2.27.1
---
requirements.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/requirements.txt b/requirements.txt
index 53839d9e18..66a75c99ca 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -54,7 +54,7 @@ rauth~=0.7.3
razorpay~=1.2.0
redis~=3.5.3
requests-oauthlib~=1.3.0
-requests~=2.25.1
+requests~=2.27.1
RestrictedPython~=5.1
rq~=1.10.1
rsa>=4.1 # not directly required, pinned by Snyk to avoid a vulnerability
From fc422f4a97bd12ceadb05ffbad582e2a921c44f3 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 2 Jun 2022 13:03:57 +0530
Subject: [PATCH 020/205] chore(deps): Bump Pillow from 9.0.1 to 9.1.1
Includes security fixes, API & constants deprecations
Changelog: https://pillow.readthedocs.io/en/stable/releasenotes/index.html
---
frappe/core/doctype/file/file.py | 4 ++--
frappe/utils/data.py | 2 +-
frappe/utils/image.py | 6 ++----
requirements.txt | 2 +-
4 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py
index 1bcbaf161a..e8b8da76ab 100755
--- a/frappe/core/doctype/file/file.py
+++ b/frappe/core/doctype/file/file.py
@@ -341,9 +341,9 @@ class File(Document):
size = width, height
if crop:
- image = ImageOps.fit(image, size, Image.ANTIALIAS)
+ image = ImageOps.fit(image, size, Image.Resampling.LANCZOS)
else:
- image.thumbnail(size, Image.ANTIALIAS)
+ image.thumbnail(size, Image.Resampling.LANCZOS)
thumbnail_url = f"{filename}_{suffix}.{extn}"
path = os.path.abspath(frappe.get_site_path("public", thumbnail_url.lstrip("/")))
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index 60770ef6a9..49f9ead437 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -1349,7 +1349,7 @@ def get_thumbnail_base64_for_image(src):
original_size = image.size
size = 50, 50
- image.thumbnail(size, Image.ANTIALIAS)
+ image.thumbnail(size, Image.Resampling.LANCZOS)
base64_string = image_to_base64(image, extn)
return {
diff --git a/frappe/utils/image.py b/frappe/utils/image.py
index 0cbc02fb31..8823ea3dfe 100644
--- a/frappe/utils/image.py
+++ b/frappe/utils/image.py
@@ -7,8 +7,6 @@ from PIL import Image
def resize_images(path, maxdim=700):
- from PIL import Image
-
size = (maxdim, maxdim)
for basepath, folders, files in os.walk(path):
for fname in files:
@@ -16,7 +14,7 @@ def resize_images(path, maxdim=700):
if extn in ("jpg", "jpeg", "png", "gif"):
im = Image.open(os.path.join(basepath, fname))
if im.size[0] > size[0] or im.size[1] > size[1]:
- im.thumbnail(size, Image.ANTIALIAS)
+ im.thumbnail(size, Image.Resampling.LANCZOS)
im.save(os.path.join(basepath, fname))
print("resized {0}".format(os.path.join(basepath, fname)))
@@ -56,7 +54,7 @@ def optimize_image(
image = Image.open(io.BytesIO(content))
image_format = content_type.split("/")[1]
size = max_width, max_height
- image.thumbnail(size, Image.LANCZOS)
+ image.thumbnail(size, Image.Resampling.LANCZOS)
output = io.BytesIO()
image.save(
diff --git a/requirements.txt b/requirements.txt
index 66a75c99ca..1a6b6120a3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -33,7 +33,7 @@ parse~=1.19.0
passlib~=1.7.4
paytmchecksum~=1.7.0
pdfkit~=1.0.0
-Pillow~=9.0.0
+Pillow~=9.1.1
premailer~=3.8.0
psutil~=5.9.1
psycopg2-binary~=2.9.1
From 70188934bc143d84c3367a1f3a90628e77cc12b7 Mon Sep 17 00:00:00 2001
From: Jannat Patel <31363128+pateljannat@users.noreply.github.com>
Date: Fri, 10 Jun 2022 15:00:32 +0530
Subject: [PATCH 021/205] fix: pick the last signup template from hook (#17118)
---
frappe/www/login.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/frappe/www/login.py b/frappe/www/login.py
index fbb34e43e7..1b9a8c239a 100644
--- a/frappe/www/login.py
+++ b/frappe/www/login.py
@@ -55,10 +55,10 @@ def get_context(context):
)
signup_form_template = frappe.get_hooks("signup_form_template")
- if signup_form_template and len(signup_form_template) and signup_form_template[0]:
- path = signup_form_template[0]
+ if signup_form_template and len(signup_form_template):
+ path = signup_form_template[-1]
if not guess_is_path(path):
- path = frappe.get_attr(signup_form_template[0])()
+ path = frappe.get_attr(signup_form_template[-1])()
else:
path = "frappe/templates/signup.html"
if path:
From e740ad180d8cdd4804f4bc568412c857f9f2ced9 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Fri, 10 Jun 2022 15:46:50 +0530
Subject: [PATCH 022/205] test: fix for failing Data Control UI test (#17133)
---
cypress/support/commands.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index c64f0bf469..c168b0c201 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -271,10 +271,9 @@ Cypress.Commands.add('save', () => {
cy.get(`button[data-label="Save"]:visible`).click({scrollBehavior: false, force: true});
cy.wait('@api');
});
-
Cypress.Commands.add('hide_dialog', () => {
- cy.wait(400);
- cy.get('.btn-modal-close:visible').click({force: true});
+ cy.wait(300);
+ cy.get_open_dialog().focus().find('.btn-modal-close').click();
cy.get('.modal:visible').should('not.exist');
});
From 076816a7d7d7541b66cc7c3d6a977256b77642dd Mon Sep 17 00:00:00 2001
From: Faris Ansari
Date: Fri, 10 Jun 2022 15:55:34 +0530
Subject: [PATCH 023/205] fix: Allow to set empty values in Bulk Edit
- minor UX fixes
---
.../public/js/frappe/list/bulk_operations.js | 24 +++++++++++++++----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js
index 94ec9d4e67..7c8c515643 100644
--- a/frappe/public/js/frappe/list/bulk_operations.js
+++ b/frappe/public/js/frappe/list/bulk_operations.js
@@ -208,7 +208,7 @@ export default class BulkOperations {
const default_field = field_options.find(value => status_regex.test(value));
const dialog = new frappe.ui.Dialog({
- title: __('Edit'),
+ title: __('Bulk Edit'),
fields: [
{
'fieldtype': 'Select',
@@ -225,7 +225,9 @@ export default class BulkOperations {
'fieldtype': 'Data',
'label': __('Value'),
'fieldname': 'value',
- 'reqd': 1
+ onchange() {
+ show_help_text();
+ }
}
],
primary_action: ({ value }) => {
@@ -239,7 +241,7 @@ export default class BulkOperations {
docnames: docnames,
action: 'update',
data: {
- [fieldname]: value
+ [fieldname]: value || null
}
}
}).then(r => {
@@ -254,10 +256,11 @@ export default class BulkOperations {
frappe.show_alert(__('Updated successfully'));
});
},
- primary_action_label: __('Update')
+ primary_action_label: __('Update {0} records', [docnames.length]),
});
if (default_field) set_value_field(dialog); // to set `Value` df based on default `Field`
+ show_help_text();
function set_value_field (dialogObj) {
const new_df = Object.assign({},
@@ -275,9 +278,20 @@ export default class BulkOperations {
new_df.default = options[0] || options[1];
}
new_df.label = __('Value');
- new_df.reqd = 1;
+ new_df.onchange = show_help_text;
+
delete new_df.depends_on;
dialogObj.replace_field('value', new_df);
+ show_help_text();
+ }
+
+ function show_help_text() {
+ let value = dialog.get_value('value');
+ if (value == null || value === '') {
+ dialog.set_df_property('value', 'description', __('You have not entered a value. The field will be set to empty.'));
+ } else {
+ dialog.set_df_property('value', 'description', '');
+ }
}
dialog.refresh();
From 5f45897471be85bc96f64f0809a0c9d6bb447565 Mon Sep 17 00:00:00 2001
From: Vladislav
Date: Fri, 10 Jun 2022 13:31:53 +0300
Subject: [PATCH 024/205] Update ru.csv
---
frappe/translations/ru.csv | 42 +++++++++++++++++++-------------------
1 file changed, 21 insertions(+), 21 deletions(-)
diff --git a/frappe/translations/ru.csv b/frappe/translations/ru.csv
index 94a87bdcf8..5a23409ae8 100644
--- a/frappe/translations/ru.csv
+++ b/frappe/translations/ru.csv
@@ -68,7 +68,7 @@ Document Status,Статус документа,
Document Type,Тип документа,
Domain,Сфера деятельности,
Domains,Сферы деятельности,
-Draft,Проект,
+Draft,Черновик,
Edit,Редактировать,
Email Account,Электронная почта,
Email Address,Адрес электронной почты,
@@ -415,7 +415,7 @@ Allowed In Mentions,Разрешено в упоминаниях,
"Allowing DocType, DocType. Be careful!","Разрешение DocType, DocType. Будьте осторожны!",
Already Registered,Уже регистрировались,
Also adding the dependent currency field {0},Также добавление зависимого валютного поля {0},
-"Always add ""Draft"" Heading for printing draft documents",Всегда добавляйте "Черновик" Заголовок для печати проектов документов,
+"Always add ""Draft"" Heading for printing draft documents","Всегда добавляйте заголовок "Черновик" для печати черновых документов",
Always use Account's Email Address as Sender,Всегда использовать почту учетной записи в качестве отправителя,
Always use Account's Name as Sender's Name,Всегда использовать имя учетной записи в качестве имени отправителя,
Amend,Изменен,
@@ -456,7 +456,7 @@ Archive,Архив,
Archived,Архивные,
Archived Columns,Архивные столбцы,
Are you sure you want to delete the attachment?,"Вы уверены, что хотите удалить вложение?",
-Are you sure you want to relink this communication to {0}?,"Вы уверены, что хотите перелинковать эту куммуникацию на {0}?",
+Are you sure you want to relink this communication to {0}?,"Вы уверены, что хотите перелинковать эту коммуникацию на {0}?",
Are you sure?,Вы уверены?,
Arial,Arial,
"As a best practice, do not assign the same set of permission rule to different Roles. Instead, set multiple Roles to the same User.","В качестве лучшего решения, не присваивайте одинаковый набор правил доступа для различных ролей. Вместо этого, установите несколько ролей одному и тому же пользователю.",
@@ -795,7 +795,7 @@ Customize...,Пользовательские настройки...,
Customized HTML Templates for printing transactions.,Индивидуальные шаблоны HTML для печатных операций.,
Cut,Вырезать,
DESC,DESC,
-Daily Event Digest is sent for Calendar Events where reminders are set.,"Ежедневно событие Дайджест направляется на Календарь событий, где установлены напоминания.",
+Daily Event Digest is sent for Calendar Events where reminders are set.,"Ежедневный дайджест событий направляется в календарь событий, где установлены напоминания.",
Danger,Опасность,
Dark Color,Темный цвет,
Dashboard Chart,Диаграмма приборной панели,
@@ -3159,7 +3159,7 @@ Aggregate Function field is required to create a dashboard chart,Поле Aggreg
All Records,Все записи,
Allot Points To Assigned Users,Выделить баллы назначенным пользователям,
Allow Auto Repeat,Разрешить автоматическое повторение,
-Allow Google Calendar Access,Разрешить доступ к Календарю Google,
+Allow Google Calendar Access,Разрешить доступ к календарю Google,
Allow Google Contacts Access,Разрешить доступ к контактам Google,
Allow Google Drive Access,Разрешить доступ Google Drive,
Allow Guest,Разрешить гость,
@@ -3181,7 +3181,7 @@ Assignment Days,Дни назначения,
Assignment Rule Day,День Правил Назначения,
Assignments,Назначения,
Attach a web link,Прикрепите веб-ссылку,
-Authorize Google Calendar Access,Авторизовать доступ к Календарю Google,
+Authorize Google Calendar Access,Авторизовать доступ к календарю Google,
Authorize Google Contacts Access,Авторизовать доступ к контактам Google,
Authorize Google Drive Access,Авторизовать Google Drive Access,
Auto Repeat Document Creation Failed,Ошибка автоматического создания документа,
@@ -3325,12 +3325,12 @@ Google API Settings.,Настройки Google API.,
Google Calendar,Календарь Google,
"Google Calendar - Could not create Calendar for {0}, error code {1}.","Календарь Google. Не удалось создать календарь для {0}, код ошибки {1}.",
"Google Calendar - Could not delete Event {0} from Google Calendar, error code {1}.","Календарь Google - не удалось удалить событие {0} из календаря Google, код ошибки {1}.",
-"Google Calendar - Could not fetch event from Google Calendar, error code {0}.","Календарь Google - не удалось получить событие из Календаря Google, код ошибки {0}.",
+"Google Calendar - Could not fetch event from Google Calendar, error code {0}.","Календарь Google - не удалось получить событие из календаря Google, код ошибки {0}.",
"Google Calendar - Could not insert contact in Google Contacts {0}, error code {1}.","Календарь Google. Не удалось вставить контакт в контакты Google {0}, код ошибки {1}.",
-"Google Calendar - Could not insert event in Google Calendar {0}, error code {1}.","Календарь Google. Не удалось вставить событие в Календарь Google {0}, код ошибки {1}.",
-"Google Calendar - Could not update Event {0} in Google Calendar, error code {1}.","Календарь Google - не удалось обновить событие {0} в Календаре Google, код ошибки {1}.",
-Google Calendar Event ID,Идентификатор события Календаря Google,
-Google Calendar Integration.,Интеграция Календаря Google.,
+"Google Calendar - Could not insert event in Google Calendar {0}, error code {1}.","Календарь Google. Не удалось вставить событие в календарь Google {0}, код ошибки {1}.",
+"Google Calendar - Could not update Event {0} in Google Calendar, error code {1}.","Календарь Google - не удалось обновить событие {0} в календаре Google, код ошибки {1}.",
+Google Calendar Event ID,Идентификатор события календаря Google,
+Google Calendar Integration.,Интеграция календаря Google.,
Google Calendar has been configured.,Календарь Google был настроен.,
Google Contacts,Контакты Google,
"Google Contacts - Could not sync contacts from Google Contacts {0}, error code {1}.","Контакты Google - не удалось синхронизировать контакты из контактов Google {0}, код ошибки {1}.",
@@ -3434,7 +3434,7 @@ New Notification,Новое уведомление,
New {0}: {1},Новый {0}: {1},
Newsletter should have atleast one recipient,Бюллетень должен иметь как минимум одного получателя,
No Events Today,Сегодня нет событий,
-No Google Calendar Event to sync.,Нет события Календаря Google для синхронизации.,
+No Google Calendar Event to sync.,Нет события в календаря Google для синхронизации.,
No More Activity,Нет больше активности,
No Name Specified for {0},Имя не указано для {0},
No Upcoming Events,Нет предстоящих событий,
@@ -3484,11 +3484,11 @@ Print Settings...,Настройки печати...,
Producer Document Name,Название документа производителя,
Producer URL,URL производителя,
Property Depends On,Недвижимость зависит от,
-Pull from Google Calendar,Вытащить из календаря Google,
-Pull from Google Contacts,Вытащить из контактов Google,
-Pulled from Google Calendar,Вытащил из Календаря Google,
-Pulled from Google Contacts,Вытащено из контактов Google,
-Push to Google Calendar,Нажмите на Google Calendar,
+Pull from Google Calendar,Загрузить из календаря Google,
+Pull from Google Contacts,Загрузить из контактов Google,
+Pulled from Google Calendar,Загружено из календаря Google,
+Pulled from Google Contacts,Загружено из контактов Google,
+Push to Google Calendar,Нажмите на Google календарь,
Push to Google Contacts,Нажмите на контакты Google,
Queue / Worker,Очередь / Рабочий,
RAW Information Log,Необработанный информационный журнал,
@@ -3526,7 +3526,7 @@ Select Date Range,Выберите диапазон дат,
Select Field,Выберите поле,
Select Field...,Выберите поле...,
Select Filters,Выберите фильтры,
-Select Google Calendar to which event should be synced.,"Выберите Календарь Google, к которому нужно синхронизировать событие.",
+Select Google Calendar to which event should be synced.,"Выберите календарь Google, к которому нужно синхронизировать событие.",
Select Google Contacts to which contact should be synced.,"Выберите Google Контакты, с которыми контакт должен быть синхронизирован.",
Select Group By...,Выбрать группу по...,
Select Mandatory,Выберите Обязательный,
@@ -3593,7 +3593,7 @@ Time series based on is required to create a dashboard chart,Временной
Time {0} must be in format: {1},Время {0} должно быть в формате: {1},
"To configure Auto Repeat, enable ""Allow Auto Repeat"" from {0}.","Чтобы настроить автоматический повтор, включите «Разрешить автоматический повтор» из {0}.",
To enable it follow the instructions in the following link: {0},"Чтобы включить его, следуйте инструкциям по следующей ссылке: {0}",
-"To use Google Calendar, enable {0}.","Чтобы использовать Календарь Google, включите {0}.",
+"To use Google Calendar, enable {0}.","Чтобы использовать календарь Google, включите {0}.",
"To use Google Contacts, enable {0}.","Чтобы использовать контакты Google, включите {0}.",
"To use Google Drive, enable {0}.","Чтобы использовать Google Диск, включите {0}.",
Today's Events,Сегодняшние события,
@@ -3662,7 +3662,7 @@ submitted this document {0},отправил этот документ {0},
"tag name..., e.g. #tag","имя тега ..., например #tag",
uploaded file,загруженный файл,
via Data Import,через импорт данных,
-{0} Google Calendar Events synced.,{0} События Календаря Google синхронизированы.,
+{0} Google Calendar Events synced.,{0} События календаря Google синхронизированы.,
{0} Google Contacts synced.,{0} Google Контакты синхронизированы.,
{0} are mandatory fields,{0} обязательные поля,
{0} are required,{0} требуется,
@@ -3697,7 +3697,7 @@ Cancelled,отменен,
Chart,Диаграмма,
Close,Закрыть,
Communication,Коммуникация,
-Compact Item Print,Компактный товара печати,
+Compact Item Print,Компактное отоборажение товара при печати,
Company,Организация,
Complete,Завершен,
Completed,Завершено,
From 6734379848c507f368781df8aba1b1fb38bd7e7d Mon Sep 17 00:00:00 2001
From: Vladislav
Date: Fri, 10 Jun 2022 16:17:15 +0300
Subject: [PATCH 025/205] Update ru.csv
---
frappe/translations/ru.csv | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/frappe/translations/ru.csv b/frappe/translations/ru.csv
index 5a23409ae8..0295ab88e9 100644
--- a/frappe/translations/ru.csv
+++ b/frappe/translations/ru.csv
@@ -10,7 +10,7 @@ Actions,Действия,
Active,Активен,
Add,Добавить,
Add Comment,Добавить комментарий,
-Add Row,Добавить ряд,
+Add Row,Добавить строку,
Address,Адрес,
Address Line 2,Адрес (2-я строка),
Address Title,Название адреса,
@@ -1357,7 +1357,7 @@ Invalid Outgoing Mail Server or Port,Неверный сервер исходя
Invalid Output Format,Неверный формат выходного,
Invalid Password,Неверный пароль,
Invalid Password:,Неверный пароль:,
-Invalid Request,Неверная заявка,
+Invalid Request,Неверный запрос,
Invalid Search Field {0},Неверное поле поиска {0},
Invalid Subscription,Недействительная подписка,
Invalid Token,Недопустимый токен,
@@ -1972,7 +1972,7 @@ Pull Insert,Вставить вкладыш,
Pull Update,Pull Update,
Push,От себя,
Push Delete,Нажмите Удалить,
-Push Failed,Ошибка нажата,
+Push Failed,Ошибка отправки,
Push Insert,Push Insert,
Push Update,Push Update,
Python Module,Модуль Python,
@@ -2122,9 +2122,9 @@ Row No,Строка №,
Row Status,Статус строки,
Row Values Changed,Значения строк Измененные,
Row {0}: Not allowed to disable Mandatory for standard fields,Строка {0}: не разрешено отключать обязательные для стандартных полей,
-Row {0}: Not allowed to enable Allow on Submit for standard fields,Ряд {0}: Не разрешается включать Разрешить проведение для стандартных полей,
-Rows Added,Ряды Добавлено,
-Rows Removed,Ряды Удалены,
+Row {0}: Not allowed to enable Allow on Submit for standard fields,Строка {0}: Не разрешается включать Разрешить проведение для стандартных полей,
+Rows Added,Строки добавлены,
+Rows Removed,Строки удалены,
Rule,Правило,
Rule Name,Название правила,
Rules defining transition of state in the workflow.,"Правила, определяющие переход этапов в потоке.",
@@ -2194,8 +2194,8 @@ Select File Type,Выберите тип файла,
Select Language...,Выберите язык...,
Select Languages,Выберите языки,
Select Module,Выбор модуля,
-Select Print Format,Выберите Печатную форму,
-Select Print Format to Edit,Выберите Печатную форму для Редактирование,
+Select Print Format,Выберите бланк для печати,
+Select Print Format to Edit,Выберите печатный бланк для редактирования,
Select Role,Выберите роль,
Select Table Columns for {0},Выберите столбцы таблицы для {0},
Select Your Region,Выберите регион,
@@ -3814,7 +3814,7 @@ and,и,
{0} Name,{0} Имя,
{0} is required,{0} требуется,
ALL,ВСЕ,
-Attach File,Прикрепить файл,
+Attach File,Прикрепить файл,
Barcode,Штрих-код,
Beginning with,Начиная с,
Bold,Жирный,
@@ -4011,11 +4011,11 @@ Setup > User,Настройка> Пользователь,
Setup > Customize Form,Настройка> Настройка формы,
Setup > User Permissions,Настройка> Полномочия пользователя,
"Error connecting to QZ Tray Application...
You need to have QZ Tray application installed and running, to use the Raw Print feature.
\
", frappe.app));
frappe.ui.misc.about_dialog = d;
From e5bc96d4d80286a9eda34b97d649b29d1f3a5a66 Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Tue, 14 Jun 2022 18:19:11 +0530
Subject: [PATCH 075/205] fix: added validation in parse method for table
multiselect and multiselect pills
---
frappe/public/js/frappe/form/controls/multiselect_pills.js | 4 ++++
frappe/public/js/frappe/form/controls/table_multiselect.js | 4 ++++
2 files changed, 8 insertions(+)
diff --git a/frappe/public/js/frappe/form/controls/multiselect_pills.js b/frappe/public/js/frappe/form/controls/multiselect_pills.js
index bf93ac0dd8..3f3e204039 100644
--- a/frappe/public/js/frappe/form/controls/multiselect_pills.js
+++ b/frappe/public/js/frappe/form/controls/multiselect_pills.js
@@ -38,6 +38,10 @@ frappe.ui.form.ControlMultiSelectPills = class ControlMultiSelectPills extends f
}
parse(value) {
+ if (typeof value == "object" || !this.rows) {
+ return value;
+ }
+
if (value) {
this.rows.push(value);
}
diff --git a/frappe/public/js/frappe/form/controls/table_multiselect.js b/frappe/public/js/frappe/form/controls/table_multiselect.js
index e106d8eed6..14bc0e297c 100644
--- a/frappe/public/js/frappe/form/controls/table_multiselect.js
+++ b/frappe/public/js/frappe/form/controls/table_multiselect.js
@@ -50,6 +50,10 @@ frappe.ui.form.ControlTableMultiSelect = class ControlTableMultiSelect extends f
this.$input_area.find('.link-btn').remove();
}
parse(value, label) {
+ if (typeof value == "object" || !this.rows) {
+ return value;
+ }
+
const link_field = this.get_link_field();
if (value) {
From cb28af838e63dfb38761b9f593365ec57e291978 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 14 Jun 2022 18:15:12 +0530
Subject: [PATCH 076/205] fix: Add temporary backwards compatible setup.py
setup.py is maintained so that there is no hard dependency on bench
v5.11.0 and people get enough time to update their CLI. Hope the TODO
adds a subtle reminder for the future ;)
---
setup.py | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 setup.py
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000000..7a90eed81a
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,6 @@
+# TODO: Remove this file when bench >=v5.11.0 is adopted / v15.0.0 is released
+from setuptools import setup
+
+name = "frappe"
+
+setup()
From 4bc544bb0b32085abf5d08ee90e0a5f0b8d0a1c2 Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Tue, 14 Jun 2022 18:40:36 +0530
Subject: [PATCH 077/205] fix: validation in date range while parsing
---
frappe/public/js/frappe/form/controls/date_range.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/form/controls/date_range.js b/frappe/public/js/frappe/form/controls/date_range.js
index 170404f575..02cac1cf28 100644
--- a/frappe/public/js/frappe/form/controls/date_range.js
+++ b/frappe/public/js/frappe/form/controls/date_range.js
@@ -41,9 +41,10 @@ frappe.ui.form.ControlDateRange = class ControlDateRange extends frappe.ui.form.
this.set_mandatory && this.set_mandatory(value);
}
parse(value) {
+ if (!value || (value && !value.includes('to'))) return value;
// replace the separator (which can be in user language) with comma
const to = __('{0} to {1}').replace('{0}', '').replace('{1}', '');
- value = value.replace(to, ',');
+ value = value && value.replace(to, ',');
if(value && value.includes(',')) {
var vals = value.split(',');
From c77f36600a7d35ca0adad7f44dd070124761b225 Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Tue, 14 Jun 2022 18:58:28 +0530
Subject: [PATCH 078/205] chore: code cleanup
---
frappe/public/js/frappe/form/controls/datetime.js | 2 +-
frappe/public/js/frappe/form/controls/duration.js | 4 +---
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js
index 11dff265f2..43873b3b1e 100644
--- a/frappe/public/js/frappe/form/controls/datetime.js
+++ b/frappe/public/js/frappe/form/controls/datetime.js
@@ -43,8 +43,8 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co
if (value == "Invalid date") {
value = "";
}
- return value;
}
+ return value;
}
format_for_input(value) {
if (!value) return "";
diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js
index 35c691a0ca..940ad9d58a 100644
--- a/frappe/public/js/frappe/form/controls/duration.js
+++ b/frappe/public/js/frappe/form/controls/duration.js
@@ -110,9 +110,7 @@ frappe.ui.form.ControlDuration = class ControlDuration extends frappe.ui.form.Co
}
parse(value) {
- if (!value) {
- return "";
- }
+ return !value ? "" : value;
}
refresh_input() {
From 562499609c27c1b42a50b4197eef0f789ab3ecb2 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Tue, 14 Jun 2022 19:00:01 +0530
Subject: [PATCH 079/205] fix(minor): refresh tabs with sections (#17182)
---
frappe/public/js/frappe/form/layout.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js
index 6d000c99f9..080f8e0180 100644
--- a/frappe/public/js/frappe/form/layout.js
+++ b/frappe/public/js/frappe/form/layout.js
@@ -293,9 +293,6 @@ frappe.ui.form.Layout = class Layout {
// refresh sections
this.refresh_sections();
- // refresh tabs
- this.is_tabbed_layout() && this.refresh_tabs();
-
if (this.frm) {
// collapse sections
this.refresh_section_collapse();
@@ -325,6 +322,9 @@ frappe.ui.form.Layout = class Layout {
section.addClass("empty-section");
}
});
+
+ // refresh tabs
+ this.is_tabbed_layout() && this.refresh_tabs();
}
refresh_tabs() {
From bbc90e6578097ede9bff42849150a8fdc8a4bb03 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 15 Jun 2022 10:33:06 +0530
Subject: [PATCH 080/205] refactor!: frappe.db.get_singles_dict
* Don't cast values by default - only if cast kwarg is set
* Reverts breaking behaviour added via https://github.com/frappe/frappe/commit/f74dc5023d5ab1598e80a586b656b34e18a5ec0c
---
frappe/database/database.py | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 39ca846904..44256f58b0 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -619,23 +619,19 @@ class Database(object):
else:
return r and [[i[1] for i in r]] or []
- def get_singles_dict(self, doctype, debug=False, *, for_update=False):
+ def get_singles_dict(self, doctype, debug=False, *, for_update=False, cast=False):
"""Get Single DocType as dict.
:param doctype: DocType of the single object whose value is requested
+ :param debug: Execute query in debug mode - print to STDOUT
+ :param for_update: Take `FOR UPDATE` lock on the records
+ :param cast: Cast values to Python data types based on field type
Example:
# Get coulmn and value of the single doctype Accounts Settings
account_settings = frappe.db.get_singles_dict("Accounts Settings")
"""
- return_value = frappe._dict()
-
- try:
- meta = frappe.get_meta(doctype)
- except DoesNotExistError:
- return return_value
-
queried_result = self.query.get_sql(
"Singles",
filters={"doctype": doctype},
@@ -643,6 +639,16 @@ class Database(object):
for_update=for_update,
).run(debug=debug)
+ if not cast:
+ return frappe._dict(queried_result)
+
+ try:
+ meta = frappe.get_meta(doctype)
+ except DoesNotExistError:
+ return frappe._dict(queried_result)
+
+ return_value = frappe._dict()
+
for fieldname, value in queried_result:
if df := meta.get_field(fieldname):
casted_value = cast(df.fieldtype, value)
From 80b19a6031f94a83961dbdf15fb58a747fd85b23 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 15 Jun 2022 10:40:26 +0530
Subject: [PATCH 081/205] fix: Cast singles_dict' values
This is to adapt with the changes made in frappe.db.get_singles_dict in
the previous commit
---
frappe/__init__.py | 4 ++--
frappe/integrations/doctype/google_drive/google_drive.py | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index c4f4b2a690..054643903d 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -2221,14 +2221,14 @@ def safe_eval(code, eval_globals=None, eval_locals=None):
def get_website_settings(key):
if not hasattr(local, "website_settings"):
- local.website_settings = db.get_singles_dict("Website Settings")
+ local.website_settings = db.get_singles_dict("Website Settings", cast=True)
return local.website_settings[key]
def get_system_settings(key):
if not hasattr(local, "system_settings"):
- local.system_settings = db.get_singles_dict("System Settings")
+ local.system_settings = db.get_singles_dict("System Settings", cast=True)
return local.system_settings[key]
diff --git a/frappe/integrations/doctype/google_drive/google_drive.py b/frappe/integrations/doctype/google_drive/google_drive.py
index bbb1e8485e..347488ee44 100644
--- a/frappe/integrations/doctype/google_drive/google_drive.py
+++ b/frappe/integrations/doctype/google_drive/google_drive.py
@@ -259,13 +259,13 @@ def upload_system_backup_to_google_drive():
def daily_backup():
- drive_settings = frappe.db.get_singles_dict("Google Drive")
+ drive_settings = frappe.db.get_singles_dict("Google Drive", cast=True)
if drive_settings.enable and drive_settings.frequency == "Daily":
upload_system_backup_to_google_drive()
def weekly_backup():
- drive_settings = frappe.db.get_singles_dict("Google Drive")
+ drive_settings = frappe.db.get_singles_dict("Google Drive", cast=True)
if drive_settings.enable and drive_settings.frequency == "Weekly":
upload_system_backup_to_google_drive()
From 57bd1b6031980085c3bdca9a66586471b2a82715 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 15 Jun 2022 10:54:48 +0530
Subject: [PATCH 082/205] fix(db): Import cast as cast_fieldtype to manevour
ambiguity
---
frappe/database/database.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 44256f58b0..c68368ba38 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -22,7 +22,8 @@ from frappe.exceptions import DoesNotExistError
from frappe.model.utils.link_count import flush_local_link_count
from frappe.query_builder.functions import Count
from frappe.query_builder.utils import DocType
-from frappe.utils import cast, get_datetime, get_table_name, getdate, now, sbool
+from frappe.utils import cast as cast_fieldtype
+from frappe.utils import get_datetime, get_table_name, getdate, now, sbool
IFNULL_PATTERN = re.compile(r"ifnull\(", flags=re.IGNORECASE)
INDEX_PATTERN = re.compile(r"\s*\([^)]+\)\s*")
@@ -651,7 +652,7 @@ class Database(object):
for fieldname, value in queried_result:
if df := meta.get_field(fieldname):
- casted_value = cast(df.fieldtype, value)
+ casted_value = cast_fieldtype(df.fieldtype, value)
else:
casted_value = value
return_value[fieldname] = casted_value
@@ -719,7 +720,7 @@ class Database(object):
_("Invalid field name: {0}").format(frappe.bold(fieldname)), self.InvalidColumnName
)
- val = cast(df.fieldtype, val)
+ val = cast_fieldtype(df.fieldtype, val)
self.value_cache[doctype][fieldname] = val
From ef1200d94b235fd2dd6e58e6177f229354bcf88e Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Sat, 11 Jun 2022 11:42:16 +0530
Subject: [PATCH 083/205] fix!: dont auto set old logs as "seen"
Not sure why this is ever required, also not configurable.
---
frappe/core/doctype/error_log/error_log.py | 8 --------
frappe/hooks.py | 1 -
2 files changed, 9 deletions(-)
diff --git a/frappe/core/doctype/error_log/error_log.py b/frappe/core/doctype/error_log/error_log.py
index d93029179c..569e6d047e 100644
--- a/frappe/core/doctype/error_log/error_log.py
+++ b/frappe/core/doctype/error_log/error_log.py
@@ -13,14 +13,6 @@ class ErrorLog(Document):
frappe.db.commit()
-def set_old_logs_as_seen():
- # set logs as seen
- frappe.db.sql(
- """UPDATE `tabError Log` SET `seen`=1
- WHERE `seen`=0 AND `creation` < (NOW() - INTERVAL '7' DAY)"""
- )
-
-
@frappe.whitelist()
def clear_error_logs():
"""Flush all Error Logs"""
diff --git a/frappe/hooks.py b/frappe/hooks.py
index ca84b83663..ae2abcec68 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -219,7 +219,6 @@ scheduler_events = {
"daily": [
"frappe.email.queue.set_expiry_for_email_queue",
"frappe.desk.notifications.clear_notifications",
- "frappe.core.doctype.error_log.error_log.set_old_logs_as_seen",
"frappe.desk.doctype.event.event.send_event_digest",
"frappe.sessions.clear_expired_sessions",
"frappe.email.doctype.notification.notification.trigger_daily_alerts",
From ea416f9d6bd5312e48ca030ad915f01ad8434d40 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Sat, 11 Jun 2022 12:26:09 +0530
Subject: [PATCH 084/205] feat: log settings with "interface"
We have hardcoded "Log settings" to only apply on 3 doctypes, there are
few more logging doctypes in core which are not cleared right now, on
top of that it's not easy for user to configure all logging behaviour
from one place.
This change adds a table on log settings where logging doctypes that
support the interface required by log settings can auto-register and
show up in settings.
Currently only supported configuration is "number of days" to keep.
---
frappe/__init__.py | 12 +-
.../core/doctype/activity_log/activity_log.py | 16 +--
frappe/core/doctype/error_log/error_log.py | 7 +
.../core/doctype/log_settings/log_settings.js | 16 ++-
.../doctype/log_settings/log_settings.json | 56 ++------
.../core/doctype/log_settings/log_settings.py | 122 ++++++++++++------
.../doctype/log_settings/test_log_settings.py | 19 ++-
frappe/core/doctype/logs_to_clear/__init__.py | 0
.../doctype/logs_to_clear/logs_to_clear.json | 42 ++++++
.../doctype/logs_to_clear/logs_to_clear.py | 9 ++
.../email/doctype/email_queue/email_queue.py | 28 +++-
.../doctype/email_queue/test_email_queue.py | 5 +-
frappe/email/queue.py | 25 ----
frappe/patches.txt | 1 +
.../patches/v14_0/log_settings_migration.py | 26 ++++
15 files changed, 255 insertions(+), 129 deletions(-)
create mode 100644 frappe/core/doctype/logs_to_clear/__init__.py
create mode 100644 frappe/core/doctype/logs_to_clear/logs_to_clear.json
create mode 100644 frappe/core/doctype/logs_to_clear/logs_to_clear.py
create mode 100644 frappe/patches/v14_0/log_settings_migration.py
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 054643903d..4b64732245 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -17,7 +17,7 @@ import json
import os
import re
import warnings
-from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
+from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
import click
from werkzeug.local import Local, release_local
@@ -1551,7 +1551,15 @@ def call(fn, *args, **kwargs):
return fn(*args, **newargs)
-def get_newargs(fn, kwargs):
+def get_newargs(fn: Callable, kwargs: Dict[str, Any]) -> Dict[str, Any]:
+ """Remove any kwargs that are not supported by the function.
+
+ Example:
+ >>> def fn(a=1, b=2): pass
+
+ >>> get_newargs(fn, {"a": 2, "c": 1})
+ {"a": 2}
+ """
# if function has any **kwargs parameter that capture arbitrary keyword arguments
# Ref: https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind
diff --git a/frappe/core/doctype/activity_log/activity_log.py b/frappe/core/doctype/activity_log/activity_log.py
index 61dedd7bc0..468b7f4473 100644
--- a/frappe/core/doctype/activity_log/activity_log.py
+++ b/frappe/core/doctype/activity_log/activity_log.py
@@ -25,6 +25,13 @@ class ActivityLog(Document):
if self.reference_doctype and self.reference_name:
self.status = "Linked"
+ @staticmethod
+ def clear_old_logs(days=None):
+ if not days:
+ days = 90
+ doctype = DocType("Activity Log")
+ frappe.db.delete(doctype, filters=(doctype.modified < (Now() - Interval(days=days))))
+
def on_doctype_update():
"""Add indexes in `tabActivity Log`"""
@@ -43,12 +50,3 @@ def add_authentication_log(subject, user, operation="Login", status="Success"):
"operation": operation,
}
).insert(ignore_permissions=True, ignore_links=True)
-
-
-def clear_activity_logs(days=None):
- """clear 90 day old authentication logs or configured in log settings"""
-
- if not days:
- days = 90
- doctype = DocType("Activity Log")
- frappe.db.delete(doctype, filters=(doctype.creation < (Now() - Interval(days=days))))
diff --git a/frappe/core/doctype/error_log/error_log.py b/frappe/core/doctype/error_log/error_log.py
index 569e6d047e..57c519bd2e 100644
--- a/frappe/core/doctype/error_log/error_log.py
+++ b/frappe/core/doctype/error_log/error_log.py
@@ -4,6 +4,8 @@
import frappe
from frappe.model.document import Document
+from frappe.query_builder import Interval
+from frappe.query_builder.functions import Now
class ErrorLog(Document):
@@ -12,6 +14,11 @@ class ErrorLog(Document):
self.db_set("seen", 1, update_modified=0)
frappe.db.commit()
+ @staticmethod
+ def clear_old_logs(days=30):
+ table = frappe.qb.DocType("Error Log")
+ frappe.db.delete(table, filters=(table.creation < (Now() - Interval(days=days))))
+
@frappe.whitelist()
def clear_error_logs():
diff --git a/frappe/core/doctype/log_settings/log_settings.js b/frappe/core/doctype/log_settings/log_settings.js
index 09a2086a1d..dc7cc7eac2 100644
--- a/frappe/core/doctype/log_settings/log_settings.js
+++ b/frappe/core/doctype/log_settings/log_settings.js
@@ -1,8 +1,16 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Log Settings', {
- // refresh: function(frm) {
-
- // }
+frappe.ui.form.on("Log Settings", {
+ refresh: (frm) => {
+ frm.set_query("ref_doctype", "logs_to_clear", () => {
+ const added_doctypes = frm.doc.logs_to_clear.map((r) => r.ref_doctype);
+ return {
+ query: "frappe.core.doctype.log_settings.log_settings.get_log_doctypes",
+ filters: [
+ ["name", "not in", added_doctypes],
+ ],
+ };
+ });
+ },
});
diff --git a/frappe/core/doctype/log_settings/log_settings.json b/frappe/core/doctype/log_settings/log_settings.json
index f06d14f16b..5a9dd159cc 100644
--- a/frappe/core/doctype/log_settings/log_settings.json
+++ b/frappe/core/doctype/log_settings/log_settings.json
@@ -5,61 +5,20 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
- "error_log_notification_section",
- "users_to_notify",
- "log_cleanup_section",
- "clear_error_log_after",
- "clear_activity_log_after",
- "column_break_4",
- "clear_email_queue_after"
+ "logs_to_clear"
],
"fields": [
{
- "fieldname": "log_cleanup_section",
- "fieldtype": "Section Break",
- "label": "Log Cleanup"
- },
- {
- "fieldname": "column_break_4",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "error_log_notification_section",
- "fieldtype": "Section Break",
- "label": "Error Log Notification"
- },
- {
- "fieldname": "users_to_notify",
- "fieldtype": "Table MultiSelect",
- "label": "Users To Notify",
- "options": "Log Setting User"
- },
- {
- "default": "90",
- "description": "In Days",
- "fieldname": "clear_error_log_after",
- "fieldtype": "Int",
- "label": "Clear Error log After"
- },
- {
- "default": "90",
- "description": "In Days",
- "fieldname": "clear_activity_log_after",
- "fieldtype": "Int",
- "label": "Clear Activity Log After"
- },
- {
- "default": "30",
- "description": "In Days",
- "fieldname": "clear_email_queue_after",
- "fieldtype": "Int",
- "label": "Clear Email Queue After"
+ "fieldname": "logs_to_clear",
+ "fieldtype": "Table",
+ "label": "Logs to Clear",
+ "options": "Logs To Clear"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2020-10-13 12:18:48.649038",
+ "modified": "2022-06-11 02:17:30.803721",
"modified_by": "Administrator",
"module": "Core",
"name": "Log Settings",
@@ -79,5 +38,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py
index 5632f05a36..4f8ea549b9 100644
--- a/frappe/core/doctype/log_settings/log_settings.py
+++ b/frappe/core/doctype/log_settings/log_settings.py
@@ -2,49 +2,88 @@
# Copyright (c) 2020, Frappe Technologies and contributors
# License: MIT. See LICENSE
+from typing import Protocol, runtime_checkable
+
import frappe
from frappe import _
+from frappe.model.base_document import get_controller
from frappe.model.document import Document
-from frappe.query_builder import DocType, Interval
-from frappe.query_builder.functions import Now
+from frappe.utils import cint
+from frappe.utils.caching import site_cache
+
+
+@runtime_checkable
+class LogType(Protocol):
+ """Interface requirement for doctypes that can be cleared using log settings."""
+
+ @staticmethod
+ def clear_old_logs(days: int) -> None:
+ ...
+
+
+@site_cache
+def _supports_log_clearing(doctype: str) -> bool:
+ try:
+ controller = get_controller(doctype)
+ return issubclass(controller, LogType)
+ except Exception:
+ return False
class LogSettings(Document):
- def clear_logs(self, commit=False):
- self.clear_email_queue()
- if commit:
- # Since since deleting many logs can take significant amount of time, commit is required to relase locks.
- # Error log table doesn't require commit - myisam
- # activity logs are deleted last so background job finishes and commits.
+ def validate(self):
+ self.validate_supported_doctypes()
+ self.validate_duplicates()
+
+ def validate_supported_doctypes(self):
+ for entry in self.logs_to_clear:
+ if _supports_log_clearing(entry.ref_doctype):
+ continue
+
+ msg = _("{} does not support automated log clearing.").format(frappe.bold(entry.ref_doctype))
+ if frappe.conf.developer_mode:
+ msg += " " + _("Implement `clear_old_logs` method to enable auto error clearing.")
+ frappe.throw(msg, title=_("DocType not supported by Log Settings."))
+
+ def validate_duplicates(self):
+ seen = set()
+ for entry in self.logs_to_clear:
+ if entry.ref_doctype in seen:
+ frappe.throw(
+ _("{} appears more than once in configured log doctypes.").format(entry.ref_doctype)
+ )
+ seen.add(entry.ref_doctype)
+
+ def clear_logs(self):
+ """
+ Log settings can clear any log type that's registered to it and provides a method to delete old logs.
+
+ Check `LogDoctype` above for interface that doctypes need to implement.
+ """
+
+ for entry in self.logs_to_clear:
+ controller: LogType = get_controller(entry.ref_doctype)
+ func = controller.clear_old_logs
+
+ # Only pass what the method can handle, this is considering any
+ # future addition that might happen to the required interface.
+ kwargs = frappe.get_newargs(func, {"days": entry.days})
+ func(**kwargs)
frappe.db.commit()
- self.clear_error_logs()
- self.clear_activity_logs()
- def clear_error_logs(self):
- table = DocType("Error Log")
- frappe.db.delete(
- table, filters=(table.creation < (Now() - Interval(days=self.clear_error_log_after)))
- )
-
- def clear_activity_logs(self):
- from frappe.core.doctype.activity_log.activity_log import clear_activity_logs
-
- clear_activity_logs(days=self.clear_activity_log_after)
-
- def clear_email_queue(self):
- from frappe.email.queue import clear_outbox
-
- clear_outbox(days=self.clear_email_queue_after)
+ def register_doctype(self, doctype: str, days=30):
+ if doctype not in {d.ref_doctype for d in self.logs_to_clear}:
+ self.append("logs_to_clear", {"ref_doctype": doctype, "days": cint(days)})
def run_log_clean_up():
doc = frappe.get_doc("Log Settings")
- doc.clear_logs(commit=True)
+ doc.clear_logs()
@frappe.whitelist()
-def has_unseen_error_log(user):
- def _get_response(show_alert=True):
+def has_unseen_error_log():
+ if frappe.get_all("Error Log", filters={"seen": 0}, limit=1):
return {
"show_alert": True,
"message": _("You have unseen {0}").format(
@@ -52,13 +91,22 @@ def has_unseen_error_log(user):
),
}
- if frappe.get_all("Error Log", filters={"seen": 0}, limit=1):
- log_settings = frappe.get_cached_doc("Log Settings")
- if log_settings.users_to_notify:
- if user in [u.user for u in log_settings.users_to_notify]:
- return _get_response()
- else:
- return _get_response(show_alert=False)
- else:
- return _get_response()
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_log_doctypes(doctype, txt, searchfield, start, page_len, filters):
+
+ filters = filters or {}
+
+ filters.extend(
+ [
+ ["istable", "=", 0],
+ ["issingle", "=", 0],
+ ["name", "like", f"%%{txt}%%"],
+ ]
+ )
+ doctypes = frappe.get_list("DocType", filters=filters, pluck="name")
+
+ supported_doctypes = [(d,) for d in doctypes if _supports_log_clearing(d)]
+
+ return supported_doctypes[start:page_len]
diff --git a/frappe/core/doctype/log_settings/test_log_settings.py b/frappe/core/doctype/log_settings/test_log_settings.py
index 1b78745103..d7f43a181d 100644
--- a/frappe/core/doctype/log_settings/test_log_settings.py
+++ b/frappe/core/doctype/log_settings/test_log_settings.py
@@ -4,7 +4,7 @@
from datetime import datetime
import frappe
-from frappe.core.doctype.log_settings.log_settings import run_log_clean_up
+from frappe.core.doctype.log_settings.log_settings import _supports_log_clearing, run_log_clean_up
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_to_date, now_datetime
@@ -56,6 +56,23 @@ class TestLogSettings(FrappeTestCase):
self.assertEqual(error_log_count, 0)
self.assertEqual(email_queue_count, 0)
+ def test_logtype_identification(self):
+ supported_types = [
+ "Error Log",
+ "Activity Log",
+ "Email Queue",
+ "Route History",
+ "Error Snapshot",
+ "Scheduled Job Log",
+ ]
+
+ for lt in supported_types:
+ self.assertTrue(_supports_log_clearing(lt), f"{lt} should be recognized as log type")
+
+ unsupported_types = ["DocType", "User", "Non Existing dt"]
+ for dt in unsupported_types:
+ self.assertFalse(_supports_log_clearing(dt), f"{dt} shouldn't be recognized as log type")
+
def setup_test_logs(past: datetime) -> None:
activity_log = frappe.get_doc(
diff --git a/frappe/core/doctype/logs_to_clear/__init__.py b/frappe/core/doctype/logs_to_clear/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/core/doctype/logs_to_clear/logs_to_clear.json b/frappe/core/doctype/logs_to_clear/logs_to_clear.json
new file mode 100644
index 0000000000..212390adac
--- /dev/null
+++ b/frappe/core/doctype/logs_to_clear/logs_to_clear.json
@@ -0,0 +1,42 @@
+{
+ "actions": [],
+ "autoname": "autoincrement",
+ "creation": "2022-06-11 02:02:39.472511",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "ref_doctype",
+ "days"
+ ],
+ "fields": [
+ {
+ "fieldname": "ref_doctype",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Log DocType",
+ "options": "DocType",
+ "reqd": 1
+ },
+ {
+ "fieldname": "days",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Clear Logs After (days)",
+ "non_negative": 1,
+ "reqd": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-06-11 03:20:47.721188",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Logs To Clear",
+ "naming_rule": "Autoincrement",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/logs_to_clear/logs_to_clear.py b/frappe/core/doctype/logs_to_clear/logs_to_clear.py
new file mode 100644
index 0000000000..3fb4f8e72a
--- /dev/null
+++ b/frappe/core/doctype/logs_to_clear/logs_to_clear.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2022, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class LogsToClear(Document):
+ pass
diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py
index 662ba1b2ed..c3002607b4 100644
--- a/frappe/email/doctype/email_queue/email_queue.py
+++ b/frappe/email/doctype/email_queue/email_queue.py
@@ -18,7 +18,8 @@ from frappe.email.doctype.email_account.email_account import EmailAccount
from frappe.email.email_body import add_attachment, get_email, get_formatted_html
from frappe.email.queue import get_unsubcribed_url, get_unsubscribe_message
from frappe.model.document import Document
-from frappe.query_builder.utils import DocType
+from frappe.query_builder import DocType, Interval
+from frappe.query_builder.functions import Now
from frappe.utils import (
add_days,
cint,
@@ -144,6 +145,31 @@ class EmailQueue(Document):
if ctx.email_account_doc.append_emails_to_sent_folder and ctx.sent_to:
ctx.email_account_doc.append_email_to_sent_folder(message)
+ @staticmethod
+ def clear_old_logs(days=30):
+ """Remove low priority older than 31 days in Outbox or configured in Log Settings.
+ Note: Used separate query to avoid deadlock
+ """
+ days = days or 31
+ email_queue = frappe.qb.DocType("Email Queue")
+ email_recipient = frappe.qb.DocType("Email Queue Recipient")
+
+ # Delete queue table
+ (
+ frappe.qb.from_(email_queue)
+ .delete()
+ .where((email_queue.modified < (Now() - Interval(days=days))))
+ ).run()
+
+ # delete child tables, note that this has potential to leave some orphan
+ # child table behind if modified time was later than parent doc (rare).
+ # But it's safe since child table doesn't contain links.
+ (
+ frappe.qb.from_(email_recipient)
+ .delete()
+ .where((email_recipient.modified < (Now() - Interval(days=days))))
+ ).run()
+
@task(queue="short")
def send_mail(email_queue_name, is_background_task=False):
diff --git a/frappe/email/doctype/email_queue/test_email_queue.py b/frappe/email/doctype/email_queue/test_email_queue.py
index 96c566a041..435e4e691f 100644
--- a/frappe/email/doctype/email_queue/test_email_queue.py
+++ b/frappe/email/doctype/email_queue/test_email_queue.py
@@ -3,12 +3,13 @@
# License: MIT. See LICENSE
import frappe
-from frappe.email.queue import clear_outbox
from frappe.tests.utils import FrappeTestCase
class TestEmailQueue(FrappeTestCase):
def test_email_queue_deletion_based_on_modified_date(self):
+ from frappe.email.doctype.email_queue.email_queue import EmailQueue
+
old_record = frappe.get_doc(
{
"doctype": "Email Queue",
@@ -32,7 +33,7 @@ class TestEmailQueue(FrappeTestCase):
new_record = frappe.copy_doc(old_record)
new_record.insert()
- clear_outbox()
+ EmailQueue.clear_old_logs()
self.assertFalse(frappe.db.exists("Email Queue", old_record.name))
self.assertFalse(frappe.db.exists("Email Queue Recipient", {"parent": old_record.name}))
diff --git a/frappe/email/queue.py b/frappe/email/queue.py
index 07731417d8..1519c26841 100755
--- a/frappe/email/queue.py
+++ b/frappe/email/queue.py
@@ -190,31 +190,6 @@ def get_queue():
)
-def clear_outbox(days: int = None) -> None:
- """Remove low priority older than 31 days in Outbox or configured in Log Settings.
- Note: Used separate query to avoid deadlock
- """
- days = days or 31
- email_queue = frappe.qb.DocType("Email Queue")
- email_recipient = frappe.qb.DocType("Email Queue Recipient")
-
- # Delete queue table
- (
- frappe.qb.from_(email_queue)
- .delete()
- .where((email_queue.modified < (Now() - Interval(days=days))))
- ).run()
-
- # delete child tables, note that this has potential to leave some orphan
- # child table behind if modified time was later than parent doc (rare).
- # But it's safe since child table doesn't contain links.
- (
- frappe.qb.from_(email_recipient)
- .delete()
- .where((email_recipient.modified < (Now() - Interval(days=days))))
- ).run()
-
-
def set_expiry_for_email_queue():
"""Mark emails as expire that has not sent for 7 days.
Called daily via scheduler.
diff --git a/frappe/patches.txt b/frappe/patches.txt
index b771485c3c..853f02ff4c 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -191,6 +191,7 @@ frappe.patches.v14_0.remove_post_and_post_comment
frappe.patches.v14_0.reset_creation_datetime
frappe.patches.v14_0.remove_is_first_startup
frappe.patches.v14_0.reload_workspace_child_tables
+frappe.patches.v14_0.log_settings_migration
[post_model_sync]
frappe.patches.v14_0.drop_data_import_legacy
diff --git a/frappe/patches/v14_0/log_settings_migration.py b/frappe/patches/v14_0/log_settings_migration.py
new file mode 100644
index 0000000000..87f6f6e082
--- /dev/null
+++ b/frappe/patches/v14_0/log_settings_migration.py
@@ -0,0 +1,26 @@
+import frappe
+
+
+def execute():
+ logging_doctypes = {
+ "Error Log": get_current_setting("clear_error_log_after") or 30,
+ "Activity Log": get_current_setting("clear_activity_log_after") or 90,
+ "Email Queue": get_current_setting("clear_email_queue_after") or 30,
+ }
+
+ frappe.reload_doc("core", "doctype", "Logs To Clear")
+ frappe.reload_doc("core", "doctype", "Log Settings")
+
+ log_settings = frappe.get_doc("Log Settings")
+
+ for doctype, days in logging_doctypes.items():
+ log_settings.register_doctype(doctype, days)
+
+ log_settings.save()
+
+
+def get_current_setting(fieldname):
+ try:
+ return frappe.db.get_single_value("Log Settings", fieldname)
+ except Exception:
+ pass
From 95eb6cd0851bbad438f9cabf7957e167739c101e Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Sat, 11 Jun 2022 14:14:45 +0530
Subject: [PATCH 085/205] feat: support log clearing for more doctypes
- Route History
- Error Snapshot
- Scheduled Job Log (only completed ones)
---
frappe/core/doctype/error_log/error_log.py | 2 +-
frappe/core/doctype/error_snapshot/error_snapshot.py | 7 +++++++
frappe/core/doctype/log_settings/log_settings.py | 3 ++-
frappe/core/doctype/logs_to_clear/logs_to_clear.json | 3 ++-
.../core/doctype/scheduled_job_log/scheduled_job_log.py | 9 +++++++--
frappe/desk/doctype/route_history/route_history.py | 9 ++++++---
frappe/patches/v14_0/log_settings_migration.py | 3 +++
7 files changed, 28 insertions(+), 8 deletions(-)
diff --git a/frappe/core/doctype/error_log/error_log.py b/frappe/core/doctype/error_log/error_log.py
index 57c519bd2e..224a5673a7 100644
--- a/frappe/core/doctype/error_log/error_log.py
+++ b/frappe/core/doctype/error_log/error_log.py
@@ -17,7 +17,7 @@ class ErrorLog(Document):
@staticmethod
def clear_old_logs(days=30):
table = frappe.qb.DocType("Error Log")
- frappe.db.delete(table, filters=(table.creation < (Now() - Interval(days=days))))
+ frappe.db.delete(table, filters=(table.modified < (Now() - Interval(days=days))))
@frappe.whitelist()
diff --git a/frappe/core/doctype/error_snapshot/error_snapshot.py b/frappe/core/doctype/error_snapshot/error_snapshot.py
index 82f189217f..6e13b7a654 100644
--- a/frappe/core/doctype/error_snapshot/error_snapshot.py
+++ b/frappe/core/doctype/error_snapshot/error_snapshot.py
@@ -4,6 +4,8 @@
import frappe
from frappe.model.document import Document
+from frappe.query_builder import Interval
+from frappe.query_builder.functions import Now
class ErrorSnapshot(Document):
@@ -32,3 +34,8 @@ class ErrorSnapshot(Document):
frappe.db.set_value("Error Snapshot", parent["name"], "relapses", parent["relapses"] + 1)
if parent["seen"]:
frappe.db.set_value("Error Snapshot", parent["name"], "seen", False)
+
+ @staticmethod
+ def clear_old_logs(days=30):
+ table = frappe.qb.DocType("Error Snapshot")
+ frappe.db.delete(table, filters=(table.modified < (Now() - Interval(days=days))))
diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py
index 4f8ea549b9..82d6afb385 100644
--- a/frappe/core/doctype/log_settings/log_settings.py
+++ b/frappe/core/doctype/log_settings/log_settings.py
@@ -72,7 +72,8 @@ class LogSettings(Document):
frappe.db.commit()
def register_doctype(self, doctype: str, days=30):
- if doctype not in {d.ref_doctype for d in self.logs_to_clear}:
+ existing_logtypes = {d.ref_doctype for d in self.logs_to_clear}
+ if doctype not in existing_logtypes and _supports_log_clearing(doctype):
self.append("logs_to_clear", {"ref_doctype": doctype, "days": cint(days)})
diff --git a/frappe/core/doctype/logs_to_clear/logs_to_clear.json b/frappe/core/doctype/logs_to_clear/logs_to_clear.json
index 212390adac..5e242bc12f 100644
--- a/frappe/core/doctype/logs_to_clear/logs_to_clear.json
+++ b/frappe/core/doctype/logs_to_clear/logs_to_clear.json
@@ -18,6 +18,7 @@
"reqd": 1
},
{
+ "default": "30",
"fieldname": "days",
"fieldtype": "Int",
"in_list_view": 1,
@@ -29,7 +30,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-06-11 03:20:47.721188",
+ "modified": "2022-06-11 04:35:54.706775",
"modified_by": "Administrator",
"module": "Core",
"name": "Logs To Clear",
diff --git a/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py b/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py
index bead463ba5..68541a36a0 100644
--- a/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py
+++ b/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py
@@ -2,9 +2,14 @@
# Copyright (c) 2019, Frappe Technologies and contributors
# License: MIT. See LICENSE
-# import frappe
+import frappe
from frappe.model.document import Document
+from frappe.query_builder import Interval
+from frappe.query_builder.functions import Now
class ScheduledJobLog(Document):
- pass
+ @staticmethod
+ def clear_old_logs(days=90):
+ table = frappe.qb.DocType("Scheduled Job Log")
+ frappe.db.delete(table, filters=(table.modified < (Now() - Interval(days=days))))
diff --git a/frappe/desk/doctype/route_history/route_history.py b/frappe/desk/doctype/route_history/route_history.py
index e712a5bb11..c62311ae02 100644
--- a/frappe/desk/doctype/route_history/route_history.py
+++ b/frappe/desk/doctype/route_history/route_history.py
@@ -4,12 +4,15 @@
import frappe
from frappe.deferred_insert import deferred_insert as _deferred_insert
from frappe.model.document import Document
-from frappe.query_builder import DocType
-from frappe.query_builder.functions import Count
+from frappe.query_builder import DocType, Interval
+from frappe.query_builder.functions import Count, Now
class RouteHistory(Document):
- pass
+ @staticmethod
+ def clear_old_logs(days=30):
+ table = frappe.qb.DocType("Route History")
+ frappe.db.delete(table, filters=(table.modified < (Now() - Interval(days=days))))
def flush_old_route_records():
diff --git a/frappe/patches/v14_0/log_settings_migration.py b/frappe/patches/v14_0/log_settings_migration.py
index 87f6f6e082..b855cac355 100644
--- a/frappe/patches/v14_0/log_settings_migration.py
+++ b/frappe/patches/v14_0/log_settings_migration.py
@@ -6,6 +6,9 @@ def execute():
"Error Log": get_current_setting("clear_error_log_after") or 30,
"Activity Log": get_current_setting("clear_activity_log_after") or 90,
"Email Queue": get_current_setting("clear_email_queue_after") or 30,
+ "Route History": 90,
+ "Error Snapshot": 30,
+ "Scheduled Job Log": 90,
}
frappe.reload_doc("core", "doctype", "Logs To Clear")
From 8b8552b0e49f1353dc937a86882ccb1030d3e065 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Sat, 11 Jun 2022 16:04:43 +0530
Subject: [PATCH 086/205] feat: clear-log-table to clear large log tables
---
frappe/commands/site.py | 76 +++++++++++++++++++++++++++++++++++
frappe/tests/test_commands.py | 14 +++++++
2 files changed, 90 insertions(+)
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index 70b48e1f0d..2477de859f 100644
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -1088,6 +1088,81 @@ def build_search_index(context):
frappe.destroy()
+LOG_DOCTYPES = [
+ "Scheduled Job Log",
+ "Activity Log",
+ "Route History",
+ "Email Queue",
+ "Error Snapshot",
+ "Error Log",
+]
+
+
+@click.command("clear-log-table")
+@click.option("--doctype", default="text", type=click.Choice(LOG_DOCTYPES), help="Log DocType")
+@click.option("--days", type=int, help="Keep records for days")
+@click.option("--no-backup", is_flag=True, default=False, help="Do not backup the table")
+@pass_context
+def clear_log_table(context, doctype, days, no_backup):
+ """If any logtype table grows too large then clearing it with DELETE query
+ is not feasible in reasonable time. This command copies recent data to new
+ table and replaces current table with new smaller table.
+
+
+ ref: https://mariadb.com/kb/en/big-deletes/#deleting-more-than-half-a-table
+ """
+ from frappe.utils.backups import scheduled_backup
+
+ if not context.sites:
+ raise SiteNotSpecifiedError
+
+ if doctype not in LOG_DOCTYPES:
+ raise frappe.ValidationError(f"Unsupported logging DocType: {doctype}")
+
+ for site in context.sites:
+ frappe.init(site=site)
+ frappe.connect()
+
+ if frappe.db.db_type != "mariadb":
+ click.echo("Postgres database isn't supported by this command")
+ sys.exit(1)
+
+ if not no_backup:
+ scheduled_backup(
+ ignore_conf=False,
+ include_doctypes=doctype,
+ ignore_files=True,
+ force=True,
+ )
+ click.echo(f"Backed up {doctype}")
+
+ original = f"`tab{doctype}`"
+ temporary = f"`tab{doctype} temp_table`"
+ backup = f"`tab{doctype} backup_table`"
+
+ try:
+ frappe.db.sql(f"CREATE TABLE {temporary} LIKE {original}")
+
+ click.echo(f"Copying {doctype} records from last {days} days to temporary table.")
+ # Copy all recent data to new table
+ frappe.db.sql(
+ f"""INSERT INTO {temporary}
+ SELECT * FROM {original}
+ WHERE {original}.`modified` > NOW() - INTERVAL '{days}' DAY"""
+ )
+ frappe.db.sql(f"RENAME TABLE {original} TO {backup}, {temporary} TO {original}")
+ except Exception as e:
+ # Discard created tables
+ frappe.db.rollback()
+ frappe.db.sql(f"DROP TABLE IF EXISTS {temporary}")
+ click.echo(f"Log cleanup for {doctype} failed:\n{e}")
+ sys.exit(1)
+ else:
+ frappe.db.commit()
+ frappe.db.sql(f"DROP TABLE {backup}")
+ click.secho(f"Cleared {doctype} records older than {days} days", fg="green")
+
+
@click.command("trim-database")
@click.option("--dry-run", is_flag=True, default=False, help="Show what would be deleted")
@click.option(
@@ -1260,4 +1335,5 @@ commands = [
partial_restore,
trim_tables,
trim_database,
+ clear_log_table,
]
diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py
index be968511a8..e2a94a24dd 100644
--- a/frappe/tests/test_commands.py
+++ b/frappe/tests/test_commands.py
@@ -27,6 +27,8 @@ import frappe.commands.site
import frappe.commands.utils
import frappe.recorder
from frappe.installer import add_to_installed_apps, remove_app
+from frappe.query_builder.utils import db_type_is
+from frappe.tests.test_query_builder import run_only_if
from frappe.utils import add_to_date, get_bench_path, get_bench_relative_path, now
from frappe.utils.backups import fetch_latest_backups
@@ -518,6 +520,18 @@ class TestBackups(BaseTestCommands):
self.assertIsNotNone(after_backup["public"])
self.assertIsNotNone(after_backup["private"])
+ @run_only_if(db_type_is.MARIADB)
+ def test_clear_log_table(self):
+ d = frappe.get_doc(doctype="Error Log", title="Something").insert()
+ d.db_set("modified", "2010-01-01", update_modified=False)
+ frappe.db.commit()
+
+ self.execute("bench --site {site} clear-log-table --days=30 --doctype='Error Log'")
+ self.assertEqual(self.returncode, 0)
+ frappe.db.commit()
+
+ self.assertFalse(frappe.db.exists("Error Log", d.name))
+
def test_backup_with_custom_path(self):
"""Backup to a custom path (--backup-path)"""
backup_path = os.path.join(self.home, "backups")
From aeb5034a9660020235c5204232177b47b6bbc165 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Sun, 12 Jun 2022 21:59:54 +0530
Subject: [PATCH 087/205] refactor: slightly resilient patch and defaults
---
.../core/doctype/log_settings/log_settings.py | 31 +++++++++++++++++++
.../patches/v14_0/log_settings_migration.py | 17 +++++-----
2 files changed, 39 insertions(+), 9 deletions(-)
diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py
index 82d6afb385..550ee1a353 100644
--- a/frappe/core/doctype/log_settings/log_settings.py
+++ b/frappe/core/doctype/log_settings/log_settings.py
@@ -11,6 +11,15 @@ from frappe.model.document import Document
from frappe.utils import cint
from frappe.utils.caching import site_cache
+DEFAULT_LOGTYPES = {
+ "Error Log": 30,
+ "Activity Log": 90,
+ "Email Queue": 30,
+ "Route History": 90,
+ "Error Snapshot": 30,
+ "Scheduled Job Log": 90,
+}
+
@runtime_checkable
class LogType(Protocol):
@@ -34,6 +43,7 @@ class LogSettings(Document):
def validate(self):
self.validate_supported_doctypes()
self.validate_duplicates()
+ self.add_default_logtypes()
def validate_supported_doctypes(self):
for entry in self.logs_to_clear:
@@ -54,6 +64,19 @@ class LogSettings(Document):
)
seen.add(entry.ref_doctype)
+ def add_default_logtypes(self):
+ existing_logtypes = {d.ref_doctype for d in self.logs_to_clear}
+ added_logtypes = set()
+ for logtype, frequency in DEFAULT_LOGTYPES.items():
+ if logtype not in existing_logtypes and _supports_log_clearing(logtype):
+ self.append("logs_to_clear", {"ref_doctype": logtype, "days": cint(frequency)})
+ added_logtypes.add(logtype)
+
+ if added_logtypes:
+ frappe.msgprint(
+ _("Added default log doctypes: {}").format(",".join(added_logtypes)), alert=True
+ )
+
def clear_logs(self):
"""
Log settings can clear any log type that's registered to it and provides a method to delete old logs.
@@ -73,12 +96,20 @@ class LogSettings(Document):
def register_doctype(self, doctype: str, days=30):
existing_logtypes = {d.ref_doctype for d in self.logs_to_clear}
+
if doctype not in existing_logtypes and _supports_log_clearing(doctype):
self.append("logs_to_clear", {"ref_doctype": doctype, "days": cint(days)})
+ else:
+ for entry in self.logs_to_clear:
+ if entry.ref_doctype == doctype:
+ entry.days = days
+ break
def run_log_clean_up():
doc = frappe.get_doc("Log Settings")
+ doc.add_default_logtypes()
+ doc.save()
doc.clear_logs()
diff --git a/frappe/patches/v14_0/log_settings_migration.py b/frappe/patches/v14_0/log_settings_migration.py
index b855cac355..04a55a84d8 100644
--- a/frappe/patches/v14_0/log_settings_migration.py
+++ b/frappe/patches/v14_0/log_settings_migration.py
@@ -2,22 +2,21 @@ import frappe
def execute():
- logging_doctypes = {
- "Error Log": get_current_setting("clear_error_log_after") or 30,
- "Activity Log": get_current_setting("clear_activity_log_after") or 90,
- "Email Queue": get_current_setting("clear_email_queue_after") or 30,
- "Route History": 90,
- "Error Snapshot": 30,
- "Scheduled Job Log": 90,
+ old_settings = {
+ "Error Log": get_current_setting("clear_error_log_after"),
+ "Activity Log": get_current_setting("clear_activity_log_after"),
+ "Email Queue": get_current_setting("clear_email_queue_after"),
}
frappe.reload_doc("core", "doctype", "Logs To Clear")
frappe.reload_doc("core", "doctype", "Log Settings")
log_settings = frappe.get_doc("Log Settings")
+ log_settings.add_default_logtypes()
- for doctype, days in logging_doctypes.items():
- log_settings.register_doctype(doctype, days)
+ for doctype, days in old_settings.items():
+ if days:
+ log_settings.register_doctype(doctype, days)
log_settings.save()
From d3bb434e3c7099b167a3b11385494e28d9d0b723 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 13 Jun 2022 15:01:37 +0530
Subject: [PATCH 088/205] fix(ux): make log setting grid editable
---
frappe/core/doctype/logs_to_clear/logs_to_clear.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/frappe/core/doctype/logs_to_clear/logs_to_clear.json b/frappe/core/doctype/logs_to_clear/logs_to_clear.json
index 5e242bc12f..6c2a3f879a 100644
--- a/frappe/core/doctype/logs_to_clear/logs_to_clear.json
+++ b/frappe/core/doctype/logs_to_clear/logs_to_clear.json
@@ -3,6 +3,7 @@
"autoname": "autoincrement",
"creation": "2022-06-11 02:02:39.472511",
"doctype": "DocType",
+ "editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"ref_doctype",
@@ -30,7 +31,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-06-11 04:35:54.706775",
+ "modified": "2022-06-13 02:51:36.857786",
"modified_by": "Administrator",
"module": "Core",
"name": "Logs To Clear",
From edbb44925e6db2a2b7b0215b7b37c1cb31533458 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 13 Jun 2022 17:01:23 +0530
Subject: [PATCH 089/205] refactor: better varname and use get_table_name
Co-Authored-By: gavin
---
frappe/commands/site.py | 22 +++++++++----------
.../core/doctype/log_settings/log_settings.py | 4 ++--
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index 2477de859f..a23a3d2a6e 100644
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -1111,6 +1111,7 @@ def clear_log_table(context, doctype, days, no_backup):
ref: https://mariadb.com/kb/en/big-deletes/#deleting-more-than-half-a-table
"""
+ from frappe.utils import get_table_name
from frappe.utils.backups import scheduled_backup
if not context.sites:
@@ -1136,30 +1137,29 @@ def clear_log_table(context, doctype, days, no_backup):
)
click.echo(f"Backed up {doctype}")
- original = f"`tab{doctype}`"
- temporary = f"`tab{doctype} temp_table`"
- backup = f"`tab{doctype} backup_table`"
+ original = get_table_name(doctype)
+ temporary = f"{original} temp_table"
+ backup = f"{original} backup_table"
try:
- frappe.db.sql(f"CREATE TABLE {temporary} LIKE {original}")
+ frappe.db.sql_ddl(f"CREATE TABLE `{temporary}` LIKE `{original}`")
click.echo(f"Copying {doctype} records from last {days} days to temporary table.")
# Copy all recent data to new table
frappe.db.sql(
- f"""INSERT INTO {temporary}
- SELECT * FROM {original}
- WHERE {original}.`modified` > NOW() - INTERVAL '{days}' DAY"""
+ f"""INSERT INTO `{temporary}`
+ SELECT * FROM `{original}`
+ WHERE `{original}`.`modified` > NOW() - INTERVAL '{days}' DAY"""
)
- frappe.db.sql(f"RENAME TABLE {original} TO {backup}, {temporary} TO {original}")
+ frappe.db.sql_ddl(f"RENAME TABLE `{original}` TO `{backup}`, `{temporary}` TO `{original}`")
except Exception as e:
- # Discard created tables
frappe.db.rollback()
- frappe.db.sql(f"DROP TABLE IF EXISTS {temporary}")
+ frappe.db.sql_list(f"DROP TABLE IF EXISTS `{temporary}`")
click.echo(f"Log cleanup for {doctype} failed:\n{e}")
sys.exit(1)
else:
frappe.db.commit()
- frappe.db.sql(f"DROP TABLE {backup}")
+ frappe.db.sql_ddl(f"DROP TABLE `{backup}`")
click.secho(f"Cleared {doctype} records older than {days} days", fg="green")
diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py
index 550ee1a353..c3b67bc389 100644
--- a/frappe/core/doctype/log_settings/log_settings.py
+++ b/frappe/core/doctype/log_settings/log_settings.py
@@ -11,7 +11,7 @@ from frappe.model.document import Document
from frappe.utils import cint
from frappe.utils.caching import site_cache
-DEFAULT_LOGTYPES = {
+DEFAULT_LOGTYPES_RETENTION = {
"Error Log": 30,
"Activity Log": 90,
"Email Queue": 30,
@@ -67,7 +67,7 @@ class LogSettings(Document):
def add_default_logtypes(self):
existing_logtypes = {d.ref_doctype for d in self.logs_to_clear}
added_logtypes = set()
- for logtype, frequency in DEFAULT_LOGTYPES.items():
+ for logtype, frequency in DEFAULT_LOGTYPES_RETENTION.items():
if logtype not in existing_logtypes and _supports_log_clearing(logtype):
self.append("logs_to_clear", {"ref_doctype": logtype, "days": cint(frequency)})
added_logtypes.add(logtype)
From f1271b78def0b548704204a01ae2a3529fbba6cf Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 13 Jun 2022 17:08:33 +0530
Subject: [PATCH 090/205] test: ensure temp tables are removed exist
---
frappe/tests/test_commands.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py
index e2a94a24dd..aeb7f364bc 100644
--- a/frappe/tests/test_commands.py
+++ b/frappe/tests/test_commands.py
@@ -526,11 +526,16 @@ class TestBackups(BaseTestCommands):
d.db_set("modified", "2010-01-01", update_modified=False)
frappe.db.commit()
+ tables_before = frappe.db.get_tables(cached=False)
+
self.execute("bench --site {site} clear-log-table --days=30 --doctype='Error Log'")
self.assertEqual(self.returncode, 0)
frappe.db.commit()
self.assertFalse(frappe.db.exists("Error Log", d.name))
+ tables_after = frappe.db.get_tables(cached=False)
+
+ self.assertEqual(set(tables_before), set(tables_after))
def test_backup_with_custom_path(self):
"""Backup to a custom path (--backup-path)"""
From a1fa2353c1f06fd4b47c893331c2888827cd28ec Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 13 Jun 2022 17:28:33 +0530
Subject: [PATCH 091/205] fix: clear pending logs using big-deletes
If some sites have long pending old data that doesn't get cleared
automcatically then this patch now attempts to discard old data by using
"big-delete" code instead of typical delete query.
---
frappe/commands/site.py | 35 ++-----------
.../core/doctype/log_settings/log_settings.py | 49 ++++++++++++++++++-
frappe/patches.txt | 3 +-
.../v14_0/clear_long_pending_stale_logs.py | 42 ++++++++++++++++
.../patches/v14_0/log_settings_migration.py | 7 +--
5 files changed, 98 insertions(+), 38 deletions(-)
create mode 100644 frappe/patches/v14_0/clear_long_pending_stale_logs.py
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index a23a3d2a6e..a8667d6595 100644
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -9,6 +9,7 @@ import click
# imports - module imports
import frappe
from frappe.commands import get_site, pass_context
+from frappe.core.doctype.log_settings.log_settings import LOG_DOCTYPES
from frappe.exceptions import SiteNotSpecifiedError
@@ -1088,16 +1089,6 @@ def build_search_index(context):
frappe.destroy()
-LOG_DOCTYPES = [
- "Scheduled Job Log",
- "Activity Log",
- "Route History",
- "Email Queue",
- "Error Snapshot",
- "Error Log",
-]
-
-
@click.command("clear-log-table")
@click.option("--doctype", default="text", type=click.Choice(LOG_DOCTYPES), help="Log DocType")
@click.option("--days", type=int, help="Keep records for days")
@@ -1111,7 +1102,7 @@ def clear_log_table(context, doctype, days, no_backup):
ref: https://mariadb.com/kb/en/big-deletes/#deleting-more-than-half-a-table
"""
- from frappe.utils import get_table_name
+ from frappe.core.doctype.log_settings.log_settings import clear_log_table as clear_logs
from frappe.utils.backups import scheduled_backup
if not context.sites:
@@ -1124,10 +1115,6 @@ def clear_log_table(context, doctype, days, no_backup):
frappe.init(site=site)
frappe.connect()
- if frappe.db.db_type != "mariadb":
- click.echo("Postgres database isn't supported by this command")
- sys.exit(1)
-
if not no_backup:
scheduled_backup(
ignore_conf=False,
@@ -1137,29 +1124,13 @@ def clear_log_table(context, doctype, days, no_backup):
)
click.echo(f"Backed up {doctype}")
- original = get_table_name(doctype)
- temporary = f"{original} temp_table"
- backup = f"{original} backup_table"
-
try:
- frappe.db.sql_ddl(f"CREATE TABLE `{temporary}` LIKE `{original}`")
-
click.echo(f"Copying {doctype} records from last {days} days to temporary table.")
- # Copy all recent data to new table
- frappe.db.sql(
- f"""INSERT INTO `{temporary}`
- SELECT * FROM `{original}`
- WHERE `{original}`.`modified` > NOW() - INTERVAL '{days}' DAY"""
- )
- frappe.db.sql_ddl(f"RENAME TABLE `{original}` TO `{backup}`, `{temporary}` TO `{original}`")
+ clear_logs(doctype, days=days)
except Exception as e:
- frappe.db.rollback()
- frappe.db.sql_list(f"DROP TABLE IF EXISTS `{temporary}`")
click.echo(f"Log cleanup for {doctype} failed:\n{e}")
sys.exit(1)
else:
- frappe.db.commit()
- frappe.db.sql_ddl(f"DROP TABLE `{backup}`")
click.secho(f"Cleared {doctype} records older than {days} days", fg="green")
diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py
index c3b67bc389..b691c61397 100644
--- a/frappe/core/doctype/log_settings/log_settings.py
+++ b/frappe/core/doctype/log_settings/log_settings.py
@@ -67,9 +67,9 @@ class LogSettings(Document):
def add_default_logtypes(self):
existing_logtypes = {d.ref_doctype for d in self.logs_to_clear}
added_logtypes = set()
- for logtype, frequency in DEFAULT_LOGTYPES_RETENTION.items():
+ for logtype, retention in DEFAULT_LOGTYPES_RETENTION.items():
if logtype not in existing_logtypes and _supports_log_clearing(logtype):
- self.append("logs_to_clear", {"ref_doctype": logtype, "days": cint(frequency)})
+ self.append("logs_to_clear", {"ref_doctype": logtype, "days": cint(retention)})
added_logtypes.add(logtype)
if added_logtypes:
@@ -142,3 +142,48 @@ def get_log_doctypes(doctype, txt, searchfield, start, page_len, filters):
supported_doctypes = [(d,) for d in doctypes if _supports_log_clearing(d)]
return supported_doctypes[start:page_len]
+
+
+LOG_DOCTYPES = [
+ "Scheduled Job Log",
+ "Activity Log",
+ "Route History",
+ "Email Queue",
+ "Email Queue Recipient",
+ "Error Snapshot",
+ "Error Log",
+]
+
+
+def clear_log_table(doctype, days=90):
+ """If any logtype table grows too large then clearing it with DELETE query
+ is not feasible in reasonable time. This command copies recent data to new
+ table and replaces current table with new smaller table.
+
+ ref: https://mariadb.com/kb/en/big-deletes/#deleting-more-than-half-a-table
+ """
+ from frappe.utils import get_table_name
+
+ if doctype not in LOG_DOCTYPES:
+ raise frappe.ValidationError(f"Unsupported logging DocType: {doctype}")
+
+ original = get_table_name(doctype)
+ temporary = f"{original} temp_table"
+ backup = f"{original} backup_table"
+
+ try:
+ frappe.db.sql_ddl(f"CREATE TABLE `{temporary}` LIKE `{original}`")
+
+ # Copy all recent data to new table
+ frappe.db.sql(
+ f"""INSERT INTO `{temporary}`
+ SELECT * FROM `{original}`
+ WHERE `{original}`.`modified` > NOW() - INTERVAL '{days}' DAY"""
+ )
+ frappe.db.sql_ddl(f"RENAME TABLE `{original}` TO `{backup}`, `{temporary}` TO `{original}`")
+ except Exception:
+ frappe.db.rollback()
+ frappe.db.sql_ddl(f"DROP TABLE IF EXISTS `{temporary}`")
+ raise
+ else:
+ frappe.db.sql_ddl(f"DROP TABLE `{backup}`")
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 853f02ff4c..d46d40655e 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -191,6 +191,7 @@ frappe.patches.v14_0.remove_post_and_post_comment
frappe.patches.v14_0.reset_creation_datetime
frappe.patches.v14_0.remove_is_first_startup
frappe.patches.v14_0.reload_workspace_child_tables
+frappe.patches.v14_0.clear_long_pending_stale_logs
frappe.patches.v14_0.log_settings_migration
[post_model_sync]
@@ -202,4 +203,4 @@ frappe.patches.v14_0.update_color_names_in_kanban_board_column
frappe.patches.v14_0.update_is_system_generated_flag
frappe.patches.v14_0.update_auto_account_deletion_duration
frappe.patches.v14_0.set_document_expiry_default
-frappe.patches.v14_0.delete_data_migration_tool
\ No newline at end of file
+frappe.patches.v14_0.delete_data_migration_tool
diff --git a/frappe/patches/v14_0/clear_long_pending_stale_logs.py b/frappe/patches/v14_0/clear_long_pending_stale_logs.py
new file mode 100644
index 0000000000..7b6719c69f
--- /dev/null
+++ b/frappe/patches/v14_0/clear_long_pending_stale_logs.py
@@ -0,0 +1,42 @@
+import frappe
+from frappe.core.doctype.log_settings.log_settings import clear_log_table
+from frappe.utils import add_to_date, today
+
+
+def execute():
+ """Due to large size of log tables on old sites some table cleanups never finished during daily log clean up. This patch discards such data by using "big delete" code.
+
+ ref: https://github.com/frappe/frappe/issues/16971
+ """
+
+ DOCTYPE_RETENTION_MAP = {
+ "Error Log": get_current_setting("clear_error_log_after") or 90,
+ "Activity Log": get_current_setting("clear_activity_log_after") or 90,
+ "Email Queue": get_current_setting("clear_email_queue_after") or 30,
+ # child table on email queue
+ "Email Queue Recipient": get_current_setting("clear_email_queue_after") or 30,
+ "Error Snapshot": get_current_setting("clear_error_log_after") or 90,
+ # newly added
+ "Scheduled Job Log": 90,
+ "Route History": 90,
+ }
+
+ for doctype, retention in DOCTYPE_RETENTION_MAP.items():
+ if is_log_cleanup_stuck(doctype, retention):
+ print(f"Clearing old {doctype} records")
+ clear_log_table(doctype, retention)
+
+
+def is_log_cleanup_stuck(doctype: str, retention: int) -> bool:
+ """Check if doctype has data significantly older than configured cleanup period"""
+ threshold = add_to_date(today(), days=retention * -2)
+
+ return bool(frappe.db.exists(doctype, {"modified": ("<", threshold)}))
+
+
+def get_current_setting(fieldname):
+ try:
+ return frappe.db.get_single_value("Log Settings", fieldname)
+ except Exception:
+ # Field might be gone if patch is reattempted
+ pass
diff --git a/frappe/patches/v14_0/log_settings_migration.py b/frappe/patches/v14_0/log_settings_migration.py
index 04a55a84d8..203405e69b 100644
--- a/frappe/patches/v14_0/log_settings_migration.py
+++ b/frappe/patches/v14_0/log_settings_migration.py
@@ -14,9 +14,9 @@ def execute():
log_settings = frappe.get_doc("Log Settings")
log_settings.add_default_logtypes()
- for doctype, days in old_settings.items():
- if days:
- log_settings.register_doctype(doctype, days)
+ for doctype, retention in old_settings.items():
+ if retention:
+ log_settings.register_doctype(doctype, retention)
log_settings.save()
@@ -25,4 +25,5 @@ def get_current_setting(fieldname):
try:
return frappe.db.get_single_value("Log Settings", fieldname)
except Exception:
+ # Field might be gone if patch is reattempted
pass
From c380d4a1b86f2fa84be8edc611931dea59133c39 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 14 Jun 2022 11:34:17 +0530
Subject: [PATCH 092/205] refactor: dont delete route history by default
It can still be added to log settings manually.
---
frappe/core/doctype/log_settings/log_settings.py | 1 -
frappe/patches/v14_0/clear_long_pending_stale_logs.py | 1 -
2 files changed, 2 deletions(-)
diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py
index b691c61397..1a7ce532cd 100644
--- a/frappe/core/doctype/log_settings/log_settings.py
+++ b/frappe/core/doctype/log_settings/log_settings.py
@@ -15,7 +15,6 @@ DEFAULT_LOGTYPES_RETENTION = {
"Error Log": 30,
"Activity Log": 90,
"Email Queue": 30,
- "Route History": 90,
"Error Snapshot": 30,
"Scheduled Job Log": 90,
}
diff --git a/frappe/patches/v14_0/clear_long_pending_stale_logs.py b/frappe/patches/v14_0/clear_long_pending_stale_logs.py
index 7b6719c69f..53127cb197 100644
--- a/frappe/patches/v14_0/clear_long_pending_stale_logs.py
+++ b/frappe/patches/v14_0/clear_long_pending_stale_logs.py
@@ -18,7 +18,6 @@ def execute():
"Error Snapshot": get_current_setting("clear_error_log_after") or 90,
# newly added
"Scheduled Job Log": 90,
- "Route History": 90,
}
for doctype, retention in DOCTYPE_RETENTION_MAP.items():
From 8a7458ac5719b192b82fadb2b599191d5449449e Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 14 Jun 2022 13:05:32 +0530
Subject: [PATCH 093/205] feat(ux): show log retention policy in sidebar
for discoverablity
---
.../doctype/activity_log/activity_log_list.js | 9 ++++--
.../core/doctype/error_log/error_log_list.js | 14 +++++----
.../error_snapshot/error_snapshot_list.js | 7 ++++-
.../scheduled_job_log_list.js | 7 +++++
.../route_history/route_history_list.js | 7 +++++
.../doctype/email_queue/email_queue_list.js | 6 ++++
frappe/public/js/frappe/logtypes.js | 30 +++++++++++++++++++
frappe/public/js/logtypes.bundle.js | 1 +
8 files changed, 73 insertions(+), 8 deletions(-)
create mode 100644 frappe/core/doctype/scheduled_job_log/scheduled_job_log_list.js
create mode 100644 frappe/desk/doctype/route_history/route_history_list.js
create mode 100644 frappe/public/js/frappe/logtypes.js
create mode 100644 frappe/public/js/logtypes.bundle.js
diff --git a/frappe/core/doctype/activity_log/activity_log_list.js b/frappe/core/doctype/activity_log/activity_log_list.js
index 111a230827..e3a75a1941 100644
--- a/frappe/core/doctype/activity_log/activity_log_list.js
+++ b/frappe/core/doctype/activity_log/activity_log_list.js
@@ -4,5 +4,10 @@ frappe.listview_settings['Activity Log'] = {
return [__(doc.status), "green"];
else if(doc.operation == "Login" && doc.status == "Failed")
return [__(doc.status), "red"];
- }
-};
\ No newline at end of file
+ },
+ onload: function(listview) {
+ frappe.require("logtypes.bundle.js", () => {
+ frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
+ })
+ },
+};
diff --git a/frappe/core/doctype/error_log/error_log_list.js b/frappe/core/doctype/error_log/error_log_list.js
index 91e69452ff..e92773a9de 100644
--- a/frappe/core/doctype/error_log/error_log_list.js
+++ b/frappe/core/doctype/error_log/error_log_list.js
@@ -1,7 +1,7 @@
-frappe.listview_settings['Error Log'] = {
+frappe.listview_settings["Error Log"] = {
add_fields: ["seen"],
get_indicator: function(doc) {
- if(cint(doc.seen)) {
+ if (cint(doc.seen)) {
return [__("Seen"), "green", "seen,=,1"];
} else {
return [__("Not Seen"), "red", "seen,=,0"];
@@ -11,11 +11,15 @@ frappe.listview_settings['Error Log'] = {
onload: function(listview) {
listview.page.add_menu_item(__("Clear Error Logs"), function() {
frappe.call({
- method:'frappe.core.doctype.error_log.error_log.clear_error_logs',
+ method: "frappe.core.doctype.error_log.error_log.clear_error_logs",
callback: function() {
listview.refresh();
- }
+ },
});
});
- }
+
+ frappe.require("logtypes.bundle.js", () => {
+ frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
+ })
+ },
};
diff --git a/frappe/core/doctype/error_snapshot/error_snapshot_list.js b/frappe/core/doctype/error_snapshot/error_snapshot_list.js
index 1ba3e344ae..553495beb1 100644
--- a/frappe/core/doctype/error_snapshot/error_snapshot_list.js
+++ b/frappe/core/doctype/error_snapshot/error_snapshot_list.js
@@ -10,5 +10,10 @@ frappe.listview_settings["Error Snapshot"] = {
} else {
return [__("First Level"), !doc.seen ? "red" : "green", "parent_error_snapshot,=,"];
}
- }
+ },
+ onload: function(listview) {
+ frappe.require("logtypes.bundle.js", () => {
+ frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
+ })
+ },
}
diff --git a/frappe/core/doctype/scheduled_job_log/scheduled_job_log_list.js b/frappe/core/doctype/scheduled_job_log/scheduled_job_log_list.js
new file mode 100644
index 0000000000..5ddccb5d44
--- /dev/null
+++ b/frappe/core/doctype/scheduled_job_log/scheduled_job_log_list.js
@@ -0,0 +1,7 @@
+frappe.listview_settings["Scheduled Job Log"] = {
+ onload: function(listview) {
+ frappe.require("logtypes.bundle.js", () => {
+ frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
+ })
+ },
+};
diff --git a/frappe/desk/doctype/route_history/route_history_list.js b/frappe/desk/doctype/route_history/route_history_list.js
new file mode 100644
index 0000000000..84a441852c
--- /dev/null
+++ b/frappe/desk/doctype/route_history/route_history_list.js
@@ -0,0 +1,7 @@
+frappe.listview_settings["Route History"] = {
+ onload: function(listview) {
+ frappe.require("logtypes.bundle.js", () => {
+ frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
+ })
+ },
+};
diff --git a/frappe/email/doctype/email_queue/email_queue_list.js b/frappe/email/doctype/email_queue/email_queue_list.js
index 0445a3ca19..edc6250714 100644
--- a/frappe/email/doctype/email_queue/email_queue_list.js
+++ b/frappe/email/doctype/email_queue/email_queue_list.js
@@ -19,5 +19,11 @@ frappe.listview_settings['Email Queue'] = {
})
}
}
+ },
+
+ onload: function(listview) {
+ frappe.require("logtypes.bundle.js", () => {
+ frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
+ })
}
}
diff --git a/frappe/public/js/frappe/logtypes.js b/frappe/public/js/frappe/logtypes.js
new file mode 100644
index 0000000000..d6e386c4d2
--- /dev/null
+++ b/frappe/public/js/frappe/logtypes.js
@@ -0,0 +1,30 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+// MIT License. See license.txt
+
+// Common utility functions for logging doctypes.
+
+frappe.provide("frappe.utils.logtypes");
+
+frappe.utils.logtypes.show_log_retention_message = (doctype) => {
+ if (!frappe.model.can_write("Log Settings")) {
+ return;
+ }
+
+ const add_sidebar_message = (message) => {
+ let sidebar_entry = $('
').appendTo(cur_list.page.sidebar);
+ $(`
${message}
`).appendTo(sidebar_entry);
+ };
+
+ const log_settings_link = `${__('Log Settings')}`;
+ const cta = __("You can change the retention policy from {0}.", [log_settings_link,]);
+ let message = __("{0} records are not automatically deleted.", [__(doctype),]);
+
+ frappe.db
+ .get_value("Logs To Clear", { ref_doctype: doctype }, "days", null, "Log Settings")
+ .then((r) => {
+ if (!r.exc && r.message && r.message.days) {
+ message = __("{0} records are retained for {1} days.", [__(doctype), r.message.days,]);
+ }
+ add_sidebar_message(`${message} ${cta}`);
+ });
+};
diff --git a/frappe/public/js/logtypes.bundle.js b/frappe/public/js/logtypes.bundle.js
new file mode 100644
index 0000000000..775ac730ad
--- /dev/null
+++ b/frappe/public/js/logtypes.bundle.js
@@ -0,0 +1 @@
+import "./frappe/logtypes"
From 57a4d590d0e80dd5a4a0e1df05fe72dfc84bfe64 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Wed, 15 Jun 2022 12:49:29 +0530
Subject: [PATCH 094/205] test: test_user_date_label_dashboard_chart
---
.../dashboard_chart/test_dashboard_chart.py | 48 +++++++++++++------
1 file changed, 33 insertions(+), 15 deletions(-)
diff --git a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py
index 94ea1af35c..51ad5ca78a 100644
--- a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py
+++ b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and Contributors
# License: MIT. See LICENSE
-import unittest
+
from datetime import datetime
from unittest.mock import patch
@@ -9,11 +9,12 @@ from dateutil.relativedelta import relativedelta
import frappe
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get
+from frappe.tests.utils import FrappeTestCase
from frappe.utils import formatdate, get_last_day, getdate
from frappe.utils.dateutils import get_period, get_period_ending
-class TestDashboardChart(unittest.TestCase):
+class TestDashboardChart(FrappeTestCase):
def test_period_ending(self):
self.assertEqual(get_period_ending("2019-04-10", "Daily"), getdate("2019-04-10"))
@@ -57,8 +58,6 @@ class TestDashboardChart(unittest.TestCase):
self.assertEqual(result.get("labels")[idx], get_period(month))
cur_date += relativedelta(months=1)
- frappe.db.rollback()
-
def test_empty_dashboard_chart(self):
if frappe.db.exists("Dashboard Chart", "Test Empty Dashboard Chart"):
frappe.delete_doc("Dashboard Chart", "Test Empty Dashboard Chart")
@@ -89,8 +88,6 @@ class TestDashboardChart(unittest.TestCase):
self.assertEqual(result.get("labels")[idx], get_period(month))
cur_date += relativedelta(months=1)
- frappe.db.rollback()
-
def test_chart_wih_one_value(self):
if frappe.db.exists("Dashboard Chart", "Test Empty Dashboard Chart 2"):
frappe.delete_doc("Dashboard Chart", "Test Empty Dashboard Chart 2")
@@ -127,8 +124,6 @@ class TestDashboardChart(unittest.TestCase):
# only 1 data point with value
self.assertEqual(result.get("datasets")[0].get("values")[2], 0)
- frappe.db.rollback()
-
def test_group_by_chart_type(self):
if frappe.db.exists("Dashboard Chart", "Test Group By Dashboard Chart"):
frappe.delete_doc("Dashboard Chart", "Test Group By Dashboard Chart")
@@ -151,8 +146,6 @@ class TestDashboardChart(unittest.TestCase):
self.assertEqual(result.get("datasets")[0].get("values")[0], todo_status_count)
- frappe.db.rollback()
-
def test_daily_dashboard_chart(self):
insert_test_records()
@@ -183,8 +176,6 @@ class TestDashboardChart(unittest.TestCase):
result.get("labels"), ["06-01-19", "07-01-19", "08-01-19", "09-01-19", "10-01-19", "11-01-19"]
)
- frappe.db.rollback()
-
def test_weekly_dashboard_chart(self):
insert_test_records()
@@ -214,8 +205,6 @@ class TestDashboardChart(unittest.TestCase):
self.assertEqual(result.get("datasets")[0].get("values"), [50.0, 300.0, 800.0, 0.0])
self.assertEqual(result.get("labels"), ["30-12-18", "06-01-19", "13-01-19", "20-01-19"])
- frappe.db.rollback()
-
def test_avg_dashboard_chart(self):
insert_test_records()
@@ -244,7 +233,36 @@ class TestDashboardChart(unittest.TestCase):
self.assertEqual(result.get("labels"), ["30-12-18", "06-01-19", "13-01-19", "20-01-19"])
self.assertEqual(result.get("datasets")[0].get("values"), [50.0, 150.0, 266.6666666666667, 0.0])
- frappe.db.rollback()
+ def test_user_date_label_dashboard_chart(self):
+ frappe.delete_doc_if_exists("Dashboard Chart", "Test Dashboard Chart Date Label")
+
+ frappe.get_doc(
+ dict(
+ doctype="Dashboard Chart",
+ chart_name="Test Dashboard Chart Date Label",
+ chart_type="Count",
+ document_type="DocType",
+ based_on="creation",
+ timespan="Select Date Range",
+ time_interval="Weekly",
+ from_date=datetime(2018, 12, 30),
+ to_date=datetime(2019, 1, 15),
+ filters_json="[]",
+ timeseries=1,
+ )
+ ).insert()
+
+ with patch.object(frappe.utils.data, "get_user_date_format", return_value="dd.mm.yyyy"):
+ result = get(chart_name="Test Dashboard Chart Date Label")
+ self.assertEqual(
+ sorted(result.get("labels")), sorted(["01.05.2019", "01.12.2019", "19.01.2019"])
+ )
+
+ with patch.object(frappe.utils.data, "get_user_date_format", return_value="mm-dd-yyyy"):
+ result = get(chart_name="Test Dashboard Chart Date Label")
+ self.assertEqual(
+ sorted(result.get("labels")), sorted(["01-19-2019", "05-01-2019", "12-01-2019"])
+ )
def insert_test_records():
From 4937b3d17f73656c1ae6235f8e0ae5bd55fa16c0 Mon Sep 17 00:00:00 2001
From: RJPvT <48353029+RJPvT@users.noreply.github.com>
Date: Wed, 8 Jun 2022 15:23:14 +0200
Subject: [PATCH 095/205] fix: no add/change image-field if user is not allowed
no add/change image-field (dropdown) if user is not allowed to change
(cherry picked from commit 4af5006470739a11ddcf3141c95ae6fb157d6825)
---
.../js/frappe/form/sidebar/user_image.js | 20 ++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/frappe/public/js/frappe/form/sidebar/user_image.js b/frappe/public/js/frappe/form/sidebar/user_image.js
index f657cbca02..bd6487fd52 100644
--- a/frappe/public/js/frappe/form/sidebar/user_image.js
+++ b/frappe/public/js/frappe/form/sidebar/user_image.js
@@ -65,15 +65,17 @@ frappe.ui.form.setup_user_image_event = function(frm) {
});
}
- frm.sidebar.image_wrapper.on('click', ':not(.sidebar-image-actions)', (e) => {
- let $target = $(e.currentTarget);
- if ($target.is('a.dropdown-toggle, .dropdown')) {
- return;
- }
- let dropdown = frm.sidebar.image_wrapper.find('.sidebar-image-actions .dropdown');
- dropdown.toggleClass('open');
- e.stopPropagation();
- });
+ if (frm.fields_dict[frm.meta.image_field].df.read_only == 0) {
+ frm.sidebar.image_wrapper.on('click', ':not(.sidebar-image-actions)', (e) => {
+ let $target = $(e.currentTarget);
+ if ($target.is('a.dropdown-toggle, .dropdown')) {
+ return;
+ }
+ let dropdown = frm.sidebar.image_wrapper.find('.sidebar-image-actions .dropdown');
+ dropdown.toggleClass('open');
+ e.stopPropagation();
+ });
+ }
// bind click on image_wrapper
frm.sidebar.image_wrapper.on('click', '.sidebar-image-change, .sidebar-image-remove', function(e) {
From 70c4305b534d0291f083afaa5a6ac627c29fe410 Mon Sep 17 00:00:00 2001
From: RJPvT <48353029+RJPvT@users.noreply.github.com>
Date: Wed, 8 Jun 2022 21:22:31 +0200
Subject: [PATCH 096/205] Update user_image.js
(cherry picked from commit d9d3b1421e5516fa8471e5afedebed99ed3bd095)
---
frappe/public/js/frappe/form/sidebar/user_image.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/form/sidebar/user_image.js b/frappe/public/js/frappe/form/sidebar/user_image.js
index bd6487fd52..e07a426b75 100644
--- a/frappe/public/js/frappe/form/sidebar/user_image.js
+++ b/frappe/public/js/frappe/form/sidebar/user_image.js
@@ -66,7 +66,7 @@ frappe.ui.form.setup_user_image_event = function(frm) {
}
if (frm.fields_dict[frm.meta.image_field].df.read_only == 0) {
- frm.sidebar.image_wrapper.on('click', ':not(.sidebar-image-actions)', (e) => {
+ frm.sidebar.image_wrapper.on('click', ':not(.sidebar-image-actions)', (e) => {
let $target = $(e.currentTarget);
if ($target.is('a.dropdown-toggle, .dropdown')) {
return;
From dcf7252fba3ba7c0b1cf640622e7cd386b5f4b8e Mon Sep 17 00:00:00 2001
From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com>
Date: Wed, 15 Jun 2022 12:52:03 +0530
Subject: [PATCH 097/205] fix: Add image_field check
(cherry picked from commit e48f6620646e213526b7ba5614779558e729c638)
---
frappe/public/js/frappe/form/sidebar/user_image.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/form/sidebar/user_image.js b/frappe/public/js/frappe/form/sidebar/user_image.js
index e07a426b75..d08e17bced 100644
--- a/frappe/public/js/frappe/form/sidebar/user_image.js
+++ b/frappe/public/js/frappe/form/sidebar/user_image.js
@@ -65,7 +65,7 @@ frappe.ui.form.setup_user_image_event = function(frm) {
});
}
- if (frm.fields_dict[frm.meta.image_field].df.read_only == 0) {
+ if (frm.meta.image_field && !frm.fields_dict[frm.meta.image_field].df.read_only) {
frm.sidebar.image_wrapper.on('click', ':not(.sidebar-image-actions)', (e) => {
let $target = $(e.currentTarget);
if ($target.is('a.dropdown-toggle, .dropdown')) {
From c2ce0ed8c1f47bfb50f77cc5e181a14f9a60f2e8 Mon Sep 17 00:00:00 2001
From: Nabin Hait
Date: Mon, 6 Jun 2022 12:19:18 +0530
Subject: [PATCH 098/205] fix: set first tab active after routing
---
frappe/public/js/frappe/form/tab.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/form/tab.js b/frappe/public/js/frappe/form/tab.js
index fd23595d80..69c573186b 100644
--- a/frappe/public/js/frappe/form/tab.js
+++ b/frappe/public/js/frappe/form/tab.js
@@ -78,7 +78,7 @@ export default class Tab {
set_active() {
this.parent.find('.nav-link').tab('show');
- this.wrapper.addClass('show');
+ this.wrapper.addClass('active');
this.frm.active_tab = this;
}
From b8a1d06363470262fb7918a842c6617d7fdc7d8b Mon Sep 17 00:00:00 2001
From: Nabin Hait
Date: Mon, 6 Jun 2022 12:20:30 +0530
Subject: [PATCH 099/205] fix: Global datetime format: show AM/PM in capital
letter
---
frappe/public/js/frappe/utils/datetime.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/utils/datetime.js b/frappe/public/js/frappe/utils/datetime.js
index 2bf64fd88e..12cefb4fd9 100644
--- a/frappe/public/js/frappe/utils/datetime.js
+++ b/frappe/public/js/frappe/utils/datetime.js
@@ -191,7 +191,7 @@ $.extend(frappe.datetime, {
global_date_format: function(d) {
var m = moment(d);
if(m._f && m._f.indexOf("HH")!== -1) {
- return m.format("Do MMMM YYYY, h:mma")
+ return m.format("Do MMMM YYYY, hh:mm A");
} else {
return m.format('Do MMMM YYYY');
}
From b36bf64261a44b078f77e94b550ab2ceaf8d1305 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 15 Jun 2022 13:35:21 +0530
Subject: [PATCH 100/205] chore: revert naming to default (#17194)
---
frappe/core/doctype/logs_to_clear/logs_to_clear.json | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/frappe/core/doctype/logs_to_clear/logs_to_clear.json b/frappe/core/doctype/logs_to_clear/logs_to_clear.json
index 6c2a3f879a..df19ccd9e7 100644
--- a/frappe/core/doctype/logs_to_clear/logs_to_clear.json
+++ b/frappe/core/doctype/logs_to_clear/logs_to_clear.json
@@ -1,6 +1,6 @@
{
"actions": [],
- "autoname": "autoincrement",
+ "autoname": "hash",
"creation": "2022-06-11 02:02:39.472511",
"doctype": "DocType",
"editable_grid": 1,
@@ -35,10 +35,9 @@
"modified_by": "Administrator",
"module": "Core",
"name": "Logs To Clear",
- "naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
-}
\ No newline at end of file
+}
From 5affa2248d3e964f79e3bf84e14084b9017304ba Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 15 Jun 2022 14:59:25 +0530
Subject: [PATCH 101/205] fix: pass parent doctype on client.get_value (#17196)
---
frappe/client.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/client.py b/frappe/client.py
index 3b7e0c397d..4afe0898bc 100644
--- a/frappe/client.py
+++ b/frappe/client.py
@@ -100,7 +100,7 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren
if frappe.is_table(doctype):
check_parent_permission(parent, doctype)
- if not frappe.has_permission(doctype):
+ if not frappe.has_permission(doctype, parent_doctype=parent):
frappe.throw(_("No permission for {0}").format(doctype), frappe.PermissionError)
filters = get_safe_filters(filters)
From 59ab6531c9f6c1ff4a5e1447bf7c0b1c30d4eb3d Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 15 Jun 2022 14:39:48 +0530
Subject: [PATCH 102/205] ci: Invalidate GHA pip cache on pyproject or setup
changes
- Cache invalidated if any of the files change: *requirements.txt,
pyproject.toml, setup.py or setup.cfg
- Updated boilerplate for new apps too
---
.github/workflows/patch-mariadb-tests.yml | 2 +-
.github/workflows/server-mariadb-tests.yml | 2 +-
.github/workflows/server-postgres-tests.yml | 2 +-
.github/workflows/ui-tests.yml | 2 +-
frappe/utils/boilerplate.py | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml
index 224e380925..9eb08be94e 100644
--- a/.github/workflows/patch-mariadb-tests.yml
+++ b/.github/workflows/patch-mariadb-tests.yml
@@ -59,7 +59,7 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml
index 48104b8f16..33fc221e80 100644
--- a/.github/workflows/server-mariadb-tests.yml
+++ b/.github/workflows/server-mariadb-tests.yml
@@ -70,7 +70,7 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml
index 241b7ddf96..2b4a2edae0 100644
--- a/.github/workflows/server-postgres-tests.yml
+++ b/.github/workflows/server-postgres-tests.yml
@@ -73,7 +73,7 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 06ad921a6a..08bf3584f5 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -69,7 +69,7 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+ key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py
index 28eefd0085..edb742feb4 100644
--- a/frappe/utils/boilerplate.py
+++ b/frappe/utils/boilerplate.py
@@ -490,7 +490,7 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.cache/pip
- key: ${{{{ runner.os }}}}-pip-${{{{ hashFiles('**/*requirements.txt') }}}}
+ key: ${{{{ runner.os }}}}-pip-${{{{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py', '**/setup.cfg') }}}}
restore-keys: |
${{{{ runner.os }}}}-pip-
${{{{ runner.os }}}}-
From 21bad02b72764642926933d7d2f45c33cbdd9a1f Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 15 Jun 2022 14:40:29 +0530
Subject: [PATCH 103/205] ci: Remove dependency on requirements.txt for builds
---
.github/workflows/patch-mariadb-tests.yml | 2 +-
CODEOWNERS | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml
index 9eb08be94e..7dffc30dc0 100644
--- a/.github/workflows/patch-mariadb-tests.yml
+++ b/.github/workflows/patch-mariadb-tests.yml
@@ -124,7 +124,7 @@ jobs:
git fetch --depth 1 upstream $branch_name:$branch_name
git checkout -q -f $branch_name
- pip install -q -r requirements.txt
+ bench setup requirements --python
bench --site test_site migrate
done
diff --git a/CODEOWNERS b/CODEOWNERS
index 170334a4b4..59832e8636 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -12,7 +12,7 @@ data_import* @netchampfaris
core/ @surajshetty3416
database @gavindsouza
model @gavindsouza
-requirements.txt @gavindsouza
+pyproject.toml @gavindsouza
query_builder/ @gavindsouza
commands/ @gavindsouza
workspace @shariquerik
From 36fa2122a3fca266c38e141665a8ce720c0bfc48 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 15 Jun 2022 15:50:09 +0530
Subject: [PATCH 104/205] fix: Patch qb for different schemas in same process
You would want to switch schemas in the same process. Eversince the
change
https://github.com/frappe/frappe/commit/64e52737646d1661e0d295868fc85aa0da47b1f2
we stopped patching on every frappe.init call which meant, if a MariaDB
site was initialized first, frappe._qb_patched would be set to True and
if a Postgres site was initialized after, _qb_patched would be lying as
the PG engine isn't patched yet. Sooooo we need a Dict instead to
maintain this record of patching. This issue caused weird errors lol -
Traceback:
File "/home/frappe/Desktop/frappe-bench-dev/env/lib/python3.10/site-packages/click/decorators.py", line 21, in new_func
return f(get_current_context(), *args, **kwargs)
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/commands/__init__.py", line 29, in _func
ret = f(frappe._dict(ctx.obj), *args, **kwargs)
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/commands/site.py", line 524, in migrate
SiteMigration(
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/migrate.py", line 169, in run
self.setUp()
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/migrate.py", line 73, in setUp
clear_global_cache()
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/cache_manager.py", line 102, in clear_global_cache
clear_website_cache()
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/website/utils.py", line 374, in clear_website_cache
clear_cache(path)
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/website/utils.py", line 369, in clear_cache
for method in frappe.get_hooks("website_clear_cache"):
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/__init__.py", line 1440, in get_hooks
hooks = _dict(_load_app_hooks())
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/utils/caching.py", line 57, in wrapper
return_val = func(*args, **kwargs)
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/__init__.py", line 1407, in _load_app_hooks
apps = [app_name] if app_name else get_installed_apps(sort=True)
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/utils/caching.py", line 57, in wrapper
return_val = func(*args, **kwargs)
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/__init__.py", line 1374, in get_installed_apps
installed = json.loads(db.get_global("installed_apps") or "[]")
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/database/database.py", line 917, in get_global
return self.get_default(key, user)
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/database/database.py", line 921, in get_default
d = self.get_defaults(key, parent)
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/database/database.py", line 938, in get_defaults
defaults = frappe.defaults.get_defaults(parent)
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/defaults.py", line 88, in get_defaults
globald = get_defaults_for()
File "/home/frappe/Desktop/frappe-bench-dev/apps/frappe/frappe/defaults.py", line 218, in get_defaults_for
frappe.qb.from_(table)
TypeError: 'Field' object is not callable
---
frappe/__init__.py | 4 ++--
frappe/query_builder/utils.py | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 4b64732245..76888c1824 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -45,7 +45,7 @@ local = Local()
STANDARD_USERS = ("Guest", "Administrator")
_dev_server = int(sbool(os.environ.get("DEV_SERVER", False)))
-_qb_patched = False
+_qb_patched = {}
re._MAXCACHE = (
50 # reduced from default 512 given we are already maintaining this on parent worker
)
@@ -243,7 +243,7 @@ def init(site, sites_path=None, new_site=False):
setup_module_map()
- if not _qb_patched:
+ if not _qb_patched.get(local.conf.db_type):
patch_query_execute()
patch_query_aggregation()
diff --git a/frappe/query_builder/utils.py b/frappe/query_builder/utils.py
index c75e380d49..10bab38a63 100644
--- a/frappe/query_builder/utils.py
+++ b/frappe/query_builder/utils.py
@@ -104,7 +104,7 @@ def patch_query_execute():
builder_class.run = execute_query
builder_class.walk = prepare_query
- frappe._qb_patched = True
+ frappe._qb_patched[frappe.conf.db_type] = True
def patch_query_aggregation():
@@ -115,4 +115,4 @@ def patch_query_aggregation():
frappe.qb.min = _min
frappe.qb.avg = _avg
frappe.qb.sum = _sum
- frappe._qb_patched = True
+ frappe._qb_patched[frappe.conf.db_type] = True
From 77a6c103ec6c55b5964835a85fad158ed3d2be4b Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Wed, 15 Jun 2022 16:11:55 +0530
Subject: [PATCH 105/205] test: Fix dashboard chart test cases
---
.../desk/doctype/dashboard_chart/test_dashboard_chart.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py
index 51ad5ca78a..ca84b2c301 100644
--- a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py
+++ b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py
@@ -173,7 +173,8 @@ class TestDashboardChart(FrappeTestCase):
self.assertEqual(result.get("datasets")[0].get("values"), [200.0, 400.0, 300.0, 0.0, 100.0, 0.0])
self.assertEqual(
- result.get("labels"), ["06-01-19", "07-01-19", "08-01-19", "09-01-19", "10-01-19", "11-01-19"]
+ result.get("labels"),
+ ["06-01-2019", "07-01-2019", "08-01-2019", "09-01-2019", "10-01-2019", "11-01-2019"],
)
def test_weekly_dashboard_chart(self):
@@ -203,7 +204,7 @@ class TestDashboardChart(FrappeTestCase):
result = get(chart_name="Test Weekly Dashboard Chart", refresh=1)
self.assertEqual(result.get("datasets")[0].get("values"), [50.0, 300.0, 800.0, 0.0])
- self.assertEqual(result.get("labels"), ["30-12-18", "06-01-19", "13-01-19", "20-01-19"])
+ self.assertEqual(result.get("labels"), ["12-30-2018", "06-01-2019", "01-13-2019", "01-20-2019"])
def test_avg_dashboard_chart(self):
insert_test_records()
@@ -230,7 +231,7 @@ class TestDashboardChart(FrappeTestCase):
with patch.object(frappe.utils.data, "get_first_day_of_the_week", return_value="Monday"):
result = get(chart_name="Test Average Dashboard Chart", refresh=1)
- self.assertEqual(result.get("labels"), ["30-12-18", "06-01-19", "13-01-19", "20-01-19"])
+ self.assertEqual(result.get("labels"), ["12-30-2018", "06-01-2019", "01-13-2019", "01-20-2019"])
self.assertEqual(result.get("datasets")[0].get("values"), [50.0, 150.0, 266.6666666666667, 0.0])
def test_user_date_label_dashboard_chart(self):
From f7e00633b935951829d05f48cd831a135b66716d Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 15 Jun 2022 17:27:04 +0530
Subject: [PATCH 106/205] chore: useless flake8 warnings
---
.flake8 | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.flake8 b/.flake8
index 56c9b9a369..4b852abd7c 100644
--- a/.flake8
+++ b/.flake8
@@ -28,6 +28,10 @@ ignore =
B007,
B950,
W191,
+ E124, # closing bracket, irritating while writing QB code
+ E131, # continuation line unaligned for hanging indent
+ E123, # closing bracket does not match indentation of opening bracket's line
+ E101, # ensured by use of black
max-line-length = 200
exclude=.github/helper/semgrep_rules
From 42f3013699d5cf1cb61021937a11d2b14ed365e6 Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Thu, 16 Jun 2022 11:35:58 +0530
Subject: [PATCH 107/205] fix: daterange value not updating
---
frappe/public/js/frappe/form/controls/date_range.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/form/controls/date_range.js b/frappe/public/js/frappe/form/controls/date_range.js
index 02cac1cf28..a1e796604f 100644
--- a/frappe/public/js/frappe/form/controls/date_range.js
+++ b/frappe/public/js/frappe/form/controls/date_range.js
@@ -41,7 +41,8 @@ frappe.ui.form.ControlDateRange = class ControlDateRange extends frappe.ui.form.
this.set_mandatory && this.set_mandatory(value);
}
parse(value) {
- if (!value || (value && !value.includes('to'))) return value;
+ if (value == undefined || typeof value == 'object') return value;
+
// replace the separator (which can be in user language) with comma
const to = __('{0} to {1}').replace('{0}', '').replace('{1}', '');
value = value && value.replace(to, ',');
From 1298b39bb3877c72b5dbbcf30041ad724bac56a9 Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Thu, 16 Jun 2022 12:07:19 +0530
Subject: [PATCH 108/205] ci: Add one more instance for faster UI tests
execution
---
.github/workflows/ui-tests.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 06ad921a6a..3850f33627 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- containers: [1, 2]
+ containers: [1, 2, 3]
name: UI Tests (Cypress)
From 6cebacaf6fad3a534484884ae3e35192b42d29ad Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Thu, 16 Jun 2022 12:07:55 +0530
Subject: [PATCH 109/205] ci: Update mergify.yml
---
.mergify.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.mergify.yml b/.mergify.yml
index 97df91a927..d9896df921 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -21,13 +21,13 @@ pull_request_rules:
- name: Automatic merge on CI success and review
conditions:
- status-success=Sider
- - status-success=Semantic Pull Request
- status-success=Python Unit Tests (MariaDB) (1)
- status-success=Python Unit Tests (MariaDB) (2)
- status-success=Python Unit Tests (Postgres) (1)
- status-success=Python Unit Tests (Postgres) (2)
- status-success=UI Tests (Cypress) (1)
- status-success=UI Tests (Cypress) (2)
+ - status-success=UI Tests (Cypress) (3)
- status-success=security/snyk (frappe)
- label!=dont-merge
- label!=squash
@@ -44,6 +44,7 @@ pull_request_rules:
- status-success=Python Unit Tests (Postgres) (2)
- status-success=UI Tests (Cypress) (1)
- status-success=UI Tests (Cypress) (2)
+ - status-success=UI Tests (Cypress) (3)
- status-success=security/snyk (frappe)
- label!=dont-merge
- label=squash
From d9e848ef882721427466601b8768f1a2bbb295fe Mon Sep 17 00:00:00 2001
From: Shariq Ansari
Date: Thu, 16 Jun 2022 13:00:34 +0530
Subject: [PATCH 110/205] test: added UI test for date range control
---
cypress/integration/control_date_range.js | 42 +++++++++++++++++++++++
1 file changed, 42 insertions(+)
create mode 100644 cypress/integration/control_date_range.js
diff --git a/cypress/integration/control_date_range.js b/cypress/integration/control_date_range.js
new file mode 100644
index 0000000000..6f26b35f84
--- /dev/null
+++ b/cypress/integration/control_date_range.js
@@ -0,0 +1,42 @@
+context('Date Range Control', () => {
+ before(() => {
+ cy.login();
+ cy.visit('/app');
+ });
+
+ function get_dialog() {
+ return cy.dialog({
+ title: 'Date Range',
+ fields: [{
+ "label": "Date Range",
+ "fieldname": "date_range",
+ "fieldtype": "Date Range",
+ }]
+ });
+ }
+
+ it('Selecting a date range from the datepicker', () => {
+ cy.clear_dialogs();
+ cy.clear_datepickers();
+
+ get_dialog().as('dialog');
+ cy.get_field('date_range', 'Date Range').click();
+ cy.get('.datepicker--nav-title').click();
+ cy.get('.datepicker--nav-title').click({force: true});
+
+ //Inputing date range values in the date range field
+ cy.get('.datepicker--years > .datepicker--cells > .datepicker--cell[data-year=2020]').click();
+ cy.get('.datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]').click();
+ cy.get('.datepicker--cell[data-date=1]:first').click({force: true});
+ cy.get('.datepicker--cell[data-date=15]:first').click({force: true});
+
+ // Verify if the selected date range values is set in the date range field
+ cy.window()
+ .its('cur_dialog')
+ .then(dialog => {
+ let date_range = dialog.get_value("date_range");
+ expect(date_range[0]).to.equal('2020-01-01');
+ expect(date_range[1]).to.equal('2020-01-15');
+ });
+ });
+});
\ No newline at end of file
From 214be6a7e861a94d99af91bbdf9fded5e4785bc6 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 15 Jun 2022 17:18:03 +0530
Subject: [PATCH 111/205] chore: discard stale patches, add relevant ones to
patches.txt
---
frappe/patches.txt | 1 +
..._user_permission_doctype_before_migrate.py | 7 --
.../patches/v12_0/delete_gsuite_if_exists.py | 9 ---
frappe/patches/v12_0/init_desk_settings.py | 11 ---
.../patches/v12_0/remove_gcalendar_gmaps.py | 11 ---
...webpage_migrate_description_to_meta_tag.py | 12 ---
.../patches/v12_0/website_meta_tag_parent.py | 12 ---
frappe/patches/v13_0/cleanup_desk_cards.py | 75 -------------------
8 files changed, 1 insertion(+), 137 deletions(-)
delete mode 100644 frappe/patches/v11_0/sync_user_permission_doctype_before_migrate.py
delete mode 100644 frappe/patches/v12_0/delete_gsuite_if_exists.py
delete mode 100644 frappe/patches/v12_0/init_desk_settings.py
delete mode 100644 frappe/patches/v12_0/remove_gcalendar_gmaps.py
delete mode 100644 frappe/patches/v12_0/webpage_migrate_description_to_meta_tag.py
delete mode 100644 frappe/patches/v12_0/website_meta_tag_parent.py
delete mode 100644 frappe/patches/v13_0/cleanup_desk_cards.py
diff --git a/frappe/patches.txt b/frappe/patches.txt
index d46d40655e..66422c7db0 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -202,5 +202,6 @@ frappe.patches.v14_0.remove_db_aggregation
frappe.patches.v14_0.update_color_names_in_kanban_board_column
frappe.patches.v14_0.update_is_system_generated_flag
frappe.patches.v14_0.update_auto_account_deletion_duration
+frappe.patches.v14_0.update_integration_request
frappe.patches.v14_0.set_document_expiry_default
frappe.patches.v14_0.delete_data_migration_tool
diff --git a/frappe/patches/v11_0/sync_user_permission_doctype_before_migrate.py b/frappe/patches/v11_0/sync_user_permission_doctype_before_migrate.py
deleted file mode 100644
index 6b7a7695f6..0000000000
--- a/frappe/patches/v11_0/sync_user_permission_doctype_before_migrate.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import frappe
-
-
-def execute():
- frappe.flags.in_patch = True
- frappe.reload_doc("core", "doctype", "user_permission")
- frappe.db.commit()
diff --git a/frappe/patches/v12_0/delete_gsuite_if_exists.py b/frappe/patches/v12_0/delete_gsuite_if_exists.py
deleted file mode 100644
index 1fb3a8c2d0..0000000000
--- a/frappe/patches/v12_0/delete_gsuite_if_exists.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import frappe
-
-
-def execute():
- """
- Remove GSuite Template and GSuite Settings
- """
- frappe.delete_doc_if_exists("DocType", "GSuite Settings")
- frappe.delete_doc_if_exists("DocType", "GSuite Templates")
diff --git a/frappe/patches/v12_0/init_desk_settings.py b/frappe/patches/v12_0/init_desk_settings.py
deleted file mode 100644
index 5ec9764e8f..0000000000
--- a/frappe/patches/v12_0/init_desk_settings.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import json
-
-import frappe
-from frappe.config import get_modules_from_all_apps_for_user
-from frappe.desk.moduleview import get_onboard_items
-
-
-def execute():
- """Reset the initial customizations for desk, with modules, indices and links."""
- frappe.reload_doc("core", "doctype", "user")
- frappe.db.sql("""update tabUser set home_settings = ''""")
diff --git a/frappe/patches/v12_0/remove_gcalendar_gmaps.py b/frappe/patches/v12_0/remove_gcalendar_gmaps.py
deleted file mode 100644
index 1177441130..0000000000
--- a/frappe/patches/v12_0/remove_gcalendar_gmaps.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import frappe
-
-
-def execute():
- """
- Remove GCalendar and GCalendar Settings
- Remove Google Maps Settings as its been merged with Delivery Trips
- """
- frappe.delete_doc_if_exists("DocType", "GCalendar Account")
- frappe.delete_doc_if_exists("DocType", "GCalendar Settings")
- frappe.delete_doc_if_exists("DocType", "Google Maps Settings")
diff --git a/frappe/patches/v12_0/webpage_migrate_description_to_meta_tag.py b/frappe/patches/v12_0/webpage_migrate_description_to_meta_tag.py
deleted file mode 100644
index 32473481b8..0000000000
--- a/frappe/patches/v12_0/webpage_migrate_description_to_meta_tag.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import frappe
-
-
-def execute():
- web_pages = frappe.get_all("Web Page", ["name", "description"])
-
- for web_page in web_pages:
- if web_page.description and web_page.route:
- doc = frappe.new_doc("Website Route Meta")
- doc.name = web_page.route
- doc.append("meta_tags", {"key": "description", "value": web_page.description})
- doc.save()
diff --git a/frappe/patches/v12_0/website_meta_tag_parent.py b/frappe/patches/v12_0/website_meta_tag_parent.py
deleted file mode 100644
index 8920189826..0000000000
--- a/frappe/patches/v12_0/website_meta_tag_parent.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import frappe
-
-
-def execute():
- # convert all /path to path
- frappe.db.sql(
- """
- UPDATE `tabWebsite Meta Tag`
- SET parent = SUBSTR(parent, 2)
- WHERE parent like '/%'
- """
- )
diff --git a/frappe/patches/v13_0/cleanup_desk_cards.py b/frappe/patches/v13_0/cleanup_desk_cards.py
deleted file mode 100644
index 988e98a647..0000000000
--- a/frappe/patches/v13_0/cleanup_desk_cards.py
+++ /dev/null
@@ -1,75 +0,0 @@
-from json import loads
-
-import frappe
-from frappe.desk.doctype.workspace.workspace import get_link_type, get_report_type
-
-
-def execute():
- frappe.reload_doc("desk", "doctype", "workspace")
-
- pages = frappe.db.sql("Select `name` from `tabDesk Page`")
- # pages = frappe.get_all("Workspace", filters={"is_standard": 0}, pluck="name")
-
- for page in pages:
- rebuild_links(page[0])
-
- frappe.delete_doc("DocType", "Desk Card")
-
-
-def rebuild_links(page):
- # Empty links table
-
- try:
- doc = frappe.get_doc("Workspace", page)
- except frappe.DoesNotExistError:
- db_doc = get_doc_from_db(page)
-
- doc = frappe.get_doc(db_doc)
- doc.insert(ignore_permissions=True)
-
- doc.links = []
-
- for card in get_all_cards(page):
- if isinstance(card.links, str):
- links = loads(card.links)
- else:
- links = card.links
-
- doc.append(
- "links",
- {"label": card.label, "type": "Card Break", "icon": card.icon, "hidden": card.hidden or False},
- )
-
- for link in links:
- if not frappe.db.exists(get_link_type(link.get("type")), link.get("name")):
- continue
-
- doc.append(
- "links",
- {
- "label": link.get("label") or link.get("name"),
- "type": "Link",
- "link_type": get_link_type(link.get("type")),
- "link_to": link.get("name"),
- "onboard": link.get("onboard"),
- "dependencies": ", ".join(link.get("dependencies", [])),
- "is_query_report": get_report_type(link.get("name"))
- if link.get("type").lower() == "report"
- else 0,
- },
- )
-
- try:
- doc.save(ignore_permissions=True)
- except frappe.LinkValidationError:
- print(doc.as_dict())
-
-
-def get_doc_from_db(page):
- result = frappe.db.sql("SELECT * FROM `tabDesk Page` WHERE name=%s", [page], as_dict=True)
- if result:
- return result[0].update({"doctype": "Workspace"})
-
-
-def get_all_cards(page):
- return frappe.db.get_all("Desk Card", filters={"parent": page}, fields=["*"], order_by="idx")
From 1c385826ef1c5e848627446c23c237bbdfdb0ab1 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 15 Jun 2022 17:18:23 +0530
Subject: [PATCH 112/205] test: make sure all patches are in patch.txt
---
frappe/tests/test_patches.py | 40 ++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/frappe/tests/test_patches.py b/frappe/tests/test_patches.py
index 6ddd8e01d3..aefd6da4e5 100644
--- a/frappe/tests/test_patches.py
+++ b/frappe/tests/test_patches.py
@@ -1,8 +1,10 @@
import unittest
+from pathlib import Path
from unittest.mock import mock_open, patch
import frappe
from frappe.modules import patch_handler
+from frappe.utils import get_bench_path
EMTPY_FILE = ""
EMTPY_SECTION = """
@@ -135,3 +137,41 @@ class TestPatchReader(unittest.TestCase):
def test_ignore_comments(self, _file):
all, pre, post = self.get_patches()
self.assertEqual(pre, ["app.module.patch1", "app.module.patch3"])
+
+ def test_verify_patch_txt(self):
+ """Make sure all patches/**.py files are part of patches.txt"""
+ check_patch_files("frappe")
+
+
+# Do not remove/rename this function, other apps depend on it to test their patches
+def check_patch_files(app):
+ """Make sure all patches/**.py files are part of patches.txt"""
+
+ patch_dir = Path(frappe.get_app_path(app)) / "patches"
+
+ app_patches = [p.split()[0] for p in patch_handler.get_patches_from_app(app)]
+
+ missing_patches = []
+
+ for file in patch_dir.glob("**/*.py"):
+ module = _get_dotted_path(file, app)
+ try:
+ patch_module = frappe.get_module(module)
+ if hasattr(patch_module, "execute"):
+ if module not in app_patches:
+ missing_patches.append(module)
+ except Exception:
+ # patch so bad it doesn't even import :shrug:
+ missing_patches.append(module)
+
+ if missing_patches:
+ raise Exception(f"Patches missing in patch.txt: \n" + "\n".join(missing_patches))
+
+
+def _get_dotted_path(file: Path, app) -> str:
+ app_path = Path(get_bench_path()) / "apps" / app
+
+ *path, filename = file.relative_to(app_path).parts
+ base_filename = Path(filename).stem
+
+ return ".".join(path + [base_filename])
From a7f9c2a6729c35c81d2732eacfe6d2f22de36fce Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 16 Jun 2022 11:48:57 +0530
Subject: [PATCH 113/205] chore: remove migrate
---
frappe/hooks.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/hooks.py b/frappe/hooks.py
index ae2abcec68..a5f532ad4d 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -278,7 +278,7 @@ setup_wizard_exception = [
"frappe.desk.page.setup_wizard.setup_wizard.log_setup_wizard_exception",
]
-before_migrate = ["frappe.patches.v11_0.sync_user_permission_doctype_before_migrate.execute"]
+before_migrate = []
after_migrate = ["frappe.website.doctype.website_theme.website_theme.after_migrate"]
otp_methods = ["OTP App", "Email", "SMS"]
From 782fb801ecf63bef43b886968c131463a9ca0f1c Mon Sep 17 00:00:00 2001
From: phot0n
Date: Thu, 16 Jun 2022 14:28:04 +0530
Subject: [PATCH 114/205] fix: ignore if integration_type column doesn't exist
(for direct v14 installations) for integration request patch
---
frappe/patches/v14_0/update_integration_request.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/frappe/patches/v14_0/update_integration_request.py b/frappe/patches/v14_0/update_integration_request.py
index 7d491461e3..d067411166 100644
--- a/frappe/patches/v14_0/update_integration_request.py
+++ b/frappe/patches/v14_0/update_integration_request.py
@@ -3,6 +3,10 @@ import frappe
def execute():
doctype = "Integration Request"
+
+ if not frappe.db.has_column(doctype, "integration_type"):
+ return
+
frappe.db.set_value(
doctype,
{"integration_type": "Remote", "integration_request_service": ("!=", "PayPal")},
From 27b0b3669dd6e786a4eb1d026a811bdf04720b3e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 16 Jun 2022 10:24:59 +0000
Subject: [PATCH 115/205] build(deps): bump bruceadams/get-release from 1.2.0
to 1.2.3
Bumps [bruceadams/get-release](https://github.com/bruceadams/get-release) from 1.2.0 to 1.2.3.
- [Release notes](https://github.com/bruceadams/get-release/releases)
- [Commits](https://github.com/bruceadams/get-release/compare/v1.2.0...v1.2.3)
---
updated-dependencies:
- dependency-name: bruceadams/get-release
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
---
.github/workflows/publish-assets-releases.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/publish-assets-releases.yml b/.github/workflows/publish-assets-releases.yml
index 2582632fa0..faf41ed23e 100644
--- a/.github/workflows/publish-assets-releases.yml
+++ b/.github/workflows/publish-assets-releases.yml
@@ -36,7 +36,7 @@ jobs:
- name: Get release
id: get_release
- uses: bruceadams/get-release@v1.2.0
+ uses: bruceadams/get-release@v1.2.3
- name: Upload built Assets to Release
uses: actions/upload-release-asset@v1.0.2
From 86f94654325874aa2ed22d09ec208cb74fb4b403 Mon Sep 17 00:00:00 2001
From: Michelle Alva <50285544+michellealva@users.noreply.github.com>
Date: Thu, 16 Jun 2022 16:16:00 +0530
Subject: [PATCH 116/205] chore: typo
---
frappe/core/page/permission_manager/permission_manager.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js
index cb218b2eae..8a06a9aac5 100644
--- a/frappe/core/page/permission_manager/permission_manager.js
+++ b/frappe/core/page/permission_manager/permission_manager.js
@@ -284,7 +284,7 @@ frappe.PermissionEngine = class PermissionEngine {
}
setup_if_owner(d, role_cell) {
- this.add_check(role_cell, d, "if_owner", "Only If Creator")
+ this.add_check(role_cell, d, "if_owner", "Only if Creator")
.removeClass("col-md-4")
.css({ "margin-top": "15px" });
}
From 335edc14f3b66cd34c97deab0e0f807d71afe723 Mon Sep 17 00:00:00 2001
From: Ritwik Puri
Date: Thu, 16 Jun 2022 20:25:01 +0530
Subject: [PATCH 117/205] fix: number card ui inconsistencies (#17160)
---
frappe/desk/doctype/number_card/number_card.json | 5 +++--
frappe/desk/doctype/number_card/number_card.py | 9 ++++-----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/frappe/desk/doctype/number_card/number_card.json b/frappe/desk/doctype/number_card/number_card.json
index 7975d878ba..ab33715d12 100644
--- a/frappe/desk/doctype/number_card/number_card.json
+++ b/frappe/desk/doctype/number_card/number_card.json
@@ -51,7 +51,7 @@
"options": "Count\nSum\nAverage\nMinimum\nMaximum"
},
{
- "depends_on": "eval: doc.function !== 'Count'",
+ "depends_on": "eval: doc.type === 'Document Type' && doc.function !== 'Count'",
"fieldname": "aggregate_function_based_on",
"fieldtype": "Select",
"label": "Aggregate Function Based On",
@@ -192,6 +192,7 @@
},
{
"description": "The document type selected is a child table, so the parent document type is required.",
+ "depends_on": "eval: doc.type === 'Document Type'",
"fieldname": "parent_document_type",
"fieldtype": "Link",
"label": "Parent Document Type",
@@ -199,7 +200,7 @@
}
],
"links": [],
- "modified": "2022-03-10 15:34:38.210910",
+ "modified": "2022-06-12 15:34:38.210910",
"modified_by": "Administrator",
"module": "Desk",
"name": "Number Card",
diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py
index d6d4f00b69..2290e49656 100644
--- a/frappe/desk/doctype/number_card/number_card.py
+++ b/frappe/desk/doctype/number_card/number_card.py
@@ -26,11 +26,10 @@ class NumberCard(Document):
if not (self.document_type and self.function):
frappe.throw(_("Document Type and Function are required to create a number card"))
- if (
- self.document_type
- and frappe.get_meta(self.document_type).istable
- and not self.parent_document_type
- ):
+ if self.function != "Count" and not self.aggregate_function_based_on:
+ frappe.throw(_("Aggregate Field is required to create a number card"))
+
+ if frappe.get_meta(self.document_type).istable and not self.parent_document_type:
frappe.throw(_("Parent Document Type is required to create a number card"))
elif self.type == "Report":
From 2ea87532e7e4b511295714d719a1591288a26303 Mon Sep 17 00:00:00 2001
From: Vladislav
Date: Thu, 16 Jun 2022 23:15:51 +0300
Subject: [PATCH 118/205] Update ru.csv
Fix logic!!!
---
frappe/translations/ru.csv | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/translations/ru.csv b/frappe/translations/ru.csv
index 81b8bdecd2..31f94dfcf6 100644
--- a/frappe/translations/ru.csv
+++ b/frappe/translations/ru.csv
@@ -3825,14 +3825,14 @@ Clear,Отчистить,
Comment,Комментарий,
Comments,Комментарии,
DRAFT,ЧЕРНОВИК,
-Dashboard,панель инструментов,
+Dashboard,Панель инструментов,
DocType,DocType,
Download,Скачать,
EMail,Эл. адрес,
Edit in Full Page,Редактировать на полной странице,
Email Inbox,Email Inbox,
File,Файл,
-Forward,Дальше,
+Forward,Переслать,
Icon,Икона,
In,В,
Inbox,Входящие,
From e1154314fe245a39b4dcacfef5053ffd6b847c4d Mon Sep 17 00:00:00 2001
From: Vladislav
Date: Fri, 17 Jun 2022 00:29:16 +0300
Subject: [PATCH 119/205] Update ru.csv
---
frappe/translations/ru.csv | 50 +++++++++++++++++++-------------------
1 file changed, 25 insertions(+), 25 deletions(-)
diff --git a/frappe/translations/ru.csv b/frappe/translations/ru.csv
index 31f94dfcf6..96b48ea99c 100644
--- a/frappe/translations/ru.csv
+++ b/frappe/translations/ru.csv
@@ -43,7 +43,7 @@ Client Secret,Секрет клиента,
Closed,Закрыт,
Code,Код,
Collapse All,Свернуть все,
-Color,цвет,
+Color,Цвет,
Company Name,Название компании,
Condition,Условия,
Contact,Контакты,
@@ -118,13 +118,13 @@ Is Active,Активен,
Is Completed,Выполнен,
Is Default,По умолчанию,
Kanban Board,Канбан-доска,
-Label,Имя поля,
+Label,Имя метки,
Language Name,Название языка,
Last Name,Фамилия,
Leaderboard,Доска почёта,
Letter Head,Печатный бланк,
Level,Уровень,
-Limit,Предел,
+Limit,Лимит,
Log,Запись в журнале,
Logs,Журналы,
Low,Низкий,
@@ -302,8 +302,8 @@ old_parent,old_parent,
1 month ago,1 месяц назад,
1 year ago,1 год назад,
; not allowed in condition,; не допускается в состоянии,
-"
Default Template
\n
Uses Jinja Templating and all the fields of Address (including Custom Fields if any) will be available
\n
{{ address_line1 }}<br>\n{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}\n{{ city }}<br>\n{% if state %}{{ state }}<br>{% endif -%}\n{% if pincode %} PIN: {{ pincode }}<br>{% endif -%}\n{{ country }}<br>\n{% if phone %}Phone: {{ phone }}<br>{% endif -%}\n{% if fax %}Fax: {{ fax }}<br>{% endif -%}\n{% if email_id %}Email: {{ email_id }}<br>{% endif -%}\n
","
Шаблон по умолчанию \n <р> Использование дзиндзя Templating и все поля адрес ( в том числе Пользовательские поля если таковые имеются) будут доступны \n
",
-A Lead with this Email Address should exist,Обращение с этим адресом электронной почты должно существовать,
+"
Default Template
\n
Uses Jinja Templating and all the fields of Address (including Custom Fields if any) will be available
\n
{{ address_line1 }}<br>\n{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}\n{{ city }}<br>\n{% if state %}{{ state }}<br>{% endif -%}\n{% if pincode %} PIN: {{ pincode }}<br>{% endif -%}\n{{ country }}<br>\n{% if phone %}Phone: {{ phone }}<br>{% endif -%}\n{% if fax %}Fax: {{ fax }}<br>{% endif -%}\n{% if email_id %}Email: {{ email_id }}<br>{% endif -%}\n
","
Шаблон по умолчанию
\n
Используя шаблонизатор Jinja и все поля адреса (включая настраиваемые поля, если таковые имеются) будет доступно
\n
{{ address_line1 }}<br>\n{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}\n{{ city }}<br>\n{% if state %}{{ state }}<br>{% endif -%}\n{% if pincode %} PIN: {{ pincode }}<br>{% endif -%}\n{{ country }}<br>\n{% if phone %}Телефон: {{ phone }}<br>{% endif -%}\n{% if fax %}Факс: {{ fax }}<br>{% endif -%}\n{% if email_id %}Email: {{ email_id }}<br>{% endif -%}\n
",
+A Lead with this Email Address should exist,Лид с этим адресом электронной почты должно существовать,
A list of resources which the Client App will have access to after the user allows it. e.g. project,"Список ресурсов, к которым клиентское приложение будет иметь доступ после того, как пользователь разрешит это. например. проект",
A log of request errors,Журнал ошибок запроса,
A new account has been created for you at {0},Новая учетная запись была создана для вас в {0},
@@ -373,12 +373,12 @@ Administrator accessed {0} on {1} via IP Address {2}.,Администратор
Advanced,Продвинутый,
Advanced Control,Расширенный контроль,
Advanced Search,Расширенный поиск,
-Align Labels to the Right,Выровнять метки справа,
-Align Value,Align значение,
+Align Labels to the Right,Выровнять метки по правому краю,
+Align Value,Значение выравнивания,
All Images attached to Website Slideshow should be public,"Все изображения, прикрепленные к Слайд-шоу веб-сайта, должны быть общедоступными",
All customizations will be removed. Please confirm.,"Все настройки будут удалены. Пожалуйста, подтвердите.",
-"All possible Workflow States and roles of the workflow. Docstatus Options: 0 is""Saved"", 1 is ""Submitted"" and 2 is ""Cancelled""","Все возможные статусы и роли бизнес-процесса. Варианты статуса документа: 0 — ""Сохранён (черновик)"", 1 — ""Проведён/ Утвержден"" и 2 — ""Отменен""",
-All-uppercase is almost as easy to guess as all-lowercase.,"All-прописные почти так же легко догадаться, как все-строчными буквами.",
+"All possible Workflow States and roles of the workflow. Docstatus Options: 0 is""Saved"", 1 is ""Submitted"" and 2 is ""Cancelled""","Все возможные статусы и роли бизнес-процесса. Варианты статуса документа: 0 — ""Сохранён"", 1 — ""Проведён/Утвержден"" и 2 — ""Отменен""",
+All-uppercase is almost as easy to guess as all-lowercase.,"Все заглавные почти так же легко угадать, как и все строчные.",
Allocated To,Выделяемых на,
Allow,Разрешить,
Allow Bulk Edit,Разрешить массовое редактирование,
@@ -415,7 +415,7 @@ Allowed In Mentions,Разрешено в упоминаниях,
"Allowing DocType, DocType. Be careful!","Разрешение DocType, DocType. Будьте осторожны!",
Already Registered,Уже регистрировались,
Also adding the dependent currency field {0},Также добавление зависимого валютного поля {0},
-"Always add ""Draft"" Heading for printing draft documents","Всегда добавляйте заголовок "Черновик" для печати черновых документов",
+"Always add ""Draft"" Heading for printing draft documents","Всегда добавлять в заголовок "Черновик" при печати черновых документов",
Always use Account's Email Address as Sender,Всегда использовать почту учетной записи в качестве отправителя,
Always use Account's Name as Sender's Name,Всегда использовать имя учетной записи в качестве имени отправителя,
Amend,Изменен,
@@ -470,7 +470,7 @@ Assigned By,Назначаются,
Assigned By Full Name,Присваиваемый Полное имя,
Assigned By Me,Назначенные мной,
Assigned To,Назначено для,
-Assigned To/Owner,Назначено / Владельца,
+Assigned To/Owner,Назначено для/Владелец,
Assignment,Назначение,
Assignment Complete,Задание выполнено,
Assignment Completed,Задание выполнено,
@@ -481,9 +481,9 @@ Assignment closed by {0},Назначение закрыт {0},
Assignment for {0} {1},Назначение для {0} {1},
Atleast one field of Parent Document Type is mandatory,По крайней мере одно поле родительского документа является обязательным,
Attach,Прикрепить,
-Attach Document Print,Прикрепите документе печать,
+Attach Document Print,Прикрепить печатный документ,
Attach Image,Прикрепить изображение,
-Attach Print,Прикрепите Печать,
+Attach Print,Прикрепите печать,
Attach Your Picture,Прикрепите свою фотографию,
Attach file for Import,Прикрепить файл для импорта,
Attach files / urls and add in table.,Прикрепить файлы / URL-адреса и добавить в таблицу.,
@@ -494,7 +494,7 @@ Attachment Limit (MB),Лимит вложения (MB),
Attachment Removed,Вложение удалено,
Attempting Connection to QZ Tray...,Попытка подключения к QZ Tray...,
Attempting to launch QZ Tray...,Попытка запустить QZ Tray...,
-Auth URL Data,Данные URL-адреса Auth,
+Auth URL Data,Данные URL-адреса аутентификации,
Authenticating...,Проверка подлинности...,
Authentication,Аутентификация,
Authentication Apps you can use are: ,"Приложения аутентификации, которые вы можете использовать:",
@@ -572,7 +572,7 @@ Bulk Update,Массовое обновление,
Busy,Занятый,
Button,Кнопка,
Button Help,Кнопка помощь,
-Button Label,Кнопка ярлык,
+Button Label,Кнопка метка,
Bypass Two Factor Auth for users who login from restricted IP Address,"Обход двух факторов для пользователей, которые подключаются с ограниченным IP-адресом",
Bypass restricted IP Address check If Two Factor Auth Enabled,Обход ограниченного IP-адреса. Если включен параметр Two Factor Auth,
CC,Копия,
@@ -630,7 +630,7 @@ Cent,Цент,
"Certain documents, like an Invoice, should not be changed once final. The final state for such documents is called Submitted. You can restrict which roles can Submit.","Некоторые документы, как, например, счета-фактуры, не подлежат изменению, если являются финальными. Конечное состояние для таких документов называется Проведенный. Вы можете ограничить, какие роли имеют право проводить документы.",
Chain Integrity,Целостность цепей,
Chaining Hash,Цепочный хэш,
-Change Label (via Custom Translation),Изменение этикетки (с помощью пользовательского перевода),
+Change Label (via Custom Translation),Изменение метки (с помощью пользовательского перевода),
Change Password,Изменить пароль,
"Change field properties (hide, readonly, permission etc.)","Изменение свойств поля (скрыть, только для чтения, доступ и т.д.)",
Channel,Канал,
@@ -684,7 +684,7 @@ Collapsible Depends On,Складные Зависит от,
Column,Колонка,
Column {0} already exist.,Столбец {0} уже существует.,
Column Break,Разрыв столбца,
-Column Labels:,Колонка ярлыки:,
+Column Labels:,Колонка меток:,
Column Name,Имя столбца,
Column Name cannot be empty,Имя столбца не может быть пустым,
Columns,Колонки,
@@ -789,7 +789,7 @@ Customizations Reset,Сброс настроек,
Customizations for {0} exported to: {1},Настройки для {0} экспортированы в: {1},
Customize Form,Настроить форму,
Customize Form Field,Настроить поля формы,
-"Customize Label, Print Hide, Default etc.","Настроить Label, распечатать спрятать, Default т.д.",
+"Customize Label, Print Hide, Default etc.","Пользовательские метки, скрыть печать, по умолчанию и т.д.",
Customize...,Пользовательские настройки...,
"Customized Formats for Printing, Email","Индивидуальные форматы для печати, электронной почты",
Customized HTML Templates for printing transactions.,Индивидуальные шаблоны HTML для печатных операций.,
@@ -1421,9 +1421,9 @@ LDAP Security,LDAP Security,
LDAP Server Url,URL cервера LDAP,
LDAP Username Field,LDAP Имя пользователя Поле,
LDAP is not enabled.,LDAP не включен.,
-Label Help,Ярлык Помощь,
-Label and Type,Ярлык и Тип,
-Label is mandatory,Ярлык является обязательным,
+Label Help,Метка Помощь,
+Label and Type,Метка и Тип,
+Label is mandatory,Метка является обязательной,
Landing Page,Страница входа,
Language,Язык,
Language Code,Языковой код,
@@ -1796,7 +1796,7 @@ Page to show on the website\n,Страница для показа на сайт
Pages in Desk (place holders),Страницы-заглушки,
Parent,Родитель,
Parent Error Snapshot,Родитель снимка ошибки,
-Parent Label,Родительская этикетка,
+Parent Label,Родительская метка,
Parent Table,Родитель Таблица,
Parent is required to get child table data,Родитель обязан получать данные дочерней таблицы,
Parent is the name of the document to which the data will get added to.,"Родитель - это имя документа, к которому будут добавлены данные.",
@@ -4300,7 +4300,7 @@ Application Version,Версия приложения,
Git Branch,Git Branch,
Installed Applications,Установленные приложения,
Navbar Item,Элемент навигационной панели,
-Item Label,Этикетка товара,
+Item Label,Метка товара,
Item Type,Тип объекта,
Separator,Разделитель,
Navbar Settings,Настройки навигационной панели,
@@ -4434,7 +4434,7 @@ Meta Title,Мета-заголовок,
Enable Social Sharing,Включить обмен в социальных сетях,
Show CTA in Blog,Показать CTA в блоге,
CTA,CTA,
-CTA Label,Ярлык CTA,
+CTA Label,Метка CTA,
CTA URL,CTA URL,
Default Portal Home,Главная страница портала по умолчанию,
"Example: ""/desk""",Пример: "/ стол",
@@ -4521,7 +4521,7 @@ Select Fields,Выбрать поля,
Warning: Unable to find {0} in any table related to {1},"Предупреждение: невозможно найти {0} ни в одной таблице, связанной с {1}",
Tree view is not available for {0},Просмотр в виде дерева недоступен для {0},
Create Card,Создать карту,
-Card Label,Этикетка карты,
+Card Label,Метка карты,
Reports already in Queue,Отчеты уже в очереди,
Proceed Anyway,Все равно продолжайте,
Delete and Generate New,Удалить и создать новое,
From 2a75b2b48099bdd5a62e51c3567b5c25d3125215 Mon Sep 17 00:00:00 2001
From: Vladislav
Date: Fri, 17 Jun 2022 01:04:46 +0300
Subject: [PATCH 120/205] Update ru.csv
Fix logic!!!
---
frappe/translations/ru.csv | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/translations/ru.csv b/frappe/translations/ru.csv
index 96b48ea99c..60e140c2a5 100644
--- a/frappe/translations/ru.csv
+++ b/frappe/translations/ru.csv
@@ -2023,7 +2023,7 @@ Register OAuth Client App,Регистрация OAuth Client App,
Registered but disabled,Зарегистрированный но отключен,
Relapsed,Повторный,
Relapses,Повторные,
-Relink,Перередактируйте,
+Relink,Связь,
Relink Communication,Повторно связать коммуникации,
Relinked,Связать повторно,
Reload,Обновить страницу,
From 6d6a67e9e322abd831d9669dc93c54c0b14dd833 Mon Sep 17 00:00:00 2001
From: Himanshu
Date: Fri, 17 Jun 2022 12:07:07 +0530
Subject: [PATCH 121/205] feat: set image as footer in letter head (#17119)
* feat: set image as footer in letter head
* fix: do not hide footer section
* fix: hide footer source
* style: black
* fix: reduce code for setting image as html
* fix: Force system admin role only if active
* fix(ui): tab refresh was not implemented
* fix(minor): Onboarding: add option to view list view in create action
* fix(minor): js lint
* fix: allow All to select a User
* test: user permissions affecting User
* refactor: filter_dynamic_link_doctypes API
* Added typing, better variable naming
* Remove unnecessary re-iterations
* Optimize queries and membership processing
* perf: Check query type via is_query_type
* chore: Drop duplicate get_frontmatter definition
* perf: Login Page
Improves performance 3x - from 0.047s to 0.017s
* Use frappe.get_*_settings to query table once
* Use cached LDAP Settings' document via get_ldap_client_settings
* Use single get_all to query all Social Login providers and related
data
* Skip provider if client_secret doesn't exist
* perf: About Us Settings
Use cached document for building /about page
* perf: App Page
Reduced time taken for get_context to execute from 0.035s to 0.02s (75%
reduction)
* perf: Patch qb only once - not on every init
* perf: Fetch and cache entire settings' dicts
* refactor!: frappe.db.get_singles_dict
* Cast single's values as their fieldtypes before returning
* Support previously dead debug parameter
* Consider single with no meta as non-existent; skip query
Decided to go ahead with the breaking change given the nature of the
existing usages of get_singles_dict :crie:
* ci: Run tests bypassing roulette with labels "Run UI Tests", "Run Server Tests"
* test: Scheduler tests cleanup
* chore: linter changes
* chore: linter changes
* chore: linter changes
* fix: set HTML as default
to keep existing formats working
* refactor: no **kwargs
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Deepesh Garg
Co-authored-by: Rushabh Mehta
Co-authored-by: Gavin D'souza
---
.../doctype/letter_head/letter_head.json | 48 +++++++++++++-
.../doctype/letter_head/letter_head.py | 62 ++++++++++++++-----
2 files changed, 92 insertions(+), 18 deletions(-)
diff --git a/frappe/printing/doctype/letter_head/letter_head.json b/frappe/printing/doctype/letter_head/letter_head.json
index f723a6b489..d49b65ab36 100644
--- a/frappe/printing/doctype/letter_head/letter_head.json
+++ b/frappe/printing/doctype/letter_head/letter_head.json
@@ -9,6 +9,7 @@
"field_order": [
"letter_head_name",
"source",
+ "footer_source",
"column_break_3",
"disabled",
"is_default",
@@ -20,7 +21,12 @@
"header_section",
"content",
"footer_section",
- "footer"
+ "footer",
+ "footer_image_section",
+ "footer_image",
+ "footer_image_height",
+ "footer_image_width",
+ "footer_align"
],
"fields": [
{
@@ -93,7 +99,7 @@
"oldfieldtype": "Text Editor"
},
{
- "collapsible": 1,
+ "depends_on": "eval:doc.footer_source==='HTML' && doc.letter_head_name",
"fieldname": "footer_section",
"fieldtype": "Section Break",
"label": "Footer"
@@ -121,13 +127,48 @@
"fieldname": "image_width",
"fieldtype": "Float",
"label": "Image Width"
+ },
+ {
+ "depends_on": "eval:doc.footer_source==='Image' && doc.letter_head_name",
+ "fieldname": "footer_image_section",
+ "fieldtype": "Section Break",
+ "label": "Footer Image"
+ },
+ {
+ "fieldname": "footer_image",
+ "fieldtype": "Attach Image",
+ "label": "Image"
+ },
+ {
+ "fieldname": "footer_image_height",
+ "fieldtype": "Float",
+ "label": "Image Height"
+ },
+ {
+ "fieldname": "footer_image_width",
+ "fieldtype": "Float",
+ "label": "Image Width"
+ },
+ {
+ "fieldname": "footer_align",
+ "fieldtype": "Select",
+ "label": "Align",
+ "options": "Left\nRight\nCenter"
+ },
+ {
+ "default": "HTML",
+ "depends_on": "letter_head_name",
+ "fieldname": "footer_source",
+ "fieldtype": "Select",
+ "label": "Footer Based On",
+ "options": "Image\nHTML"
}
],
"icon": "fa fa-font",
"idx": 1,
"links": [],
"max_attachments": 3,
- "modified": "2021-10-03 14:37:58.314696",
+ "modified": "2022-06-16 23:10:46.852116",
"modified_by": "Administrator",
"module": "Printing",
"name": "Letter Head",
@@ -152,5 +193,6 @@
],
"sort_field": "modified",
"sort_order": "ASC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/printing/doctype/letter_head/letter_head.py b/frappe/printing/doctype/letter_head/letter_head.py
index 98c2fc7c2b..9edd84a425 100644
--- a/frappe/printing/doctype/letter_head/letter_head.py
+++ b/frappe/printing/doctype/letter_head/letter_head.py
@@ -26,21 +26,53 @@ class LetterHead(Document):
def set_image(self):
if self.source == "Image":
- if self.image and is_image(self.image):
- self.image_width = flt(self.image_width)
- self.image_height = flt(self.image_height)
- dimension = "width" if self.image_width > self.image_height else "height"
- dimension_value = self.get("image_" + dimension)
- self.content = f"""
-
-
-
- """
- frappe.msgprint(frappe._("Header HTML set from attachment {0}").format(self.image), alert=True)
- else:
- frappe.msgprint(
- frappe._("Please attach an image file to set HTML"), alert=True, indicator="orange"
- )
+ self.set_image_as_html(
+ field="image",
+ width="image_width",
+ height="image_height",
+ align="align",
+ html_field="content",
+ dimension_prefix="image_",
+ success_msg=_("Header HTML set from attachment {0}").format(self.image),
+ failure_msg=_("Please attach an image file to set HTML for Letter Head."),
+ )
+
+ if self.footer_source == "Image":
+ self.set_image_as_html(
+ field="footer_image",
+ width="footer_image_width",
+ height="footer_image_height",
+ align="footer_align",
+ html_field="footer",
+ dimension_prefix="footer_image_",
+ success_msg=_("Footer HTML set from attachment {0}").format(self.footer_image),
+ failure_msg=_("Please attach an image file to set HTML for Footer."),
+ )
+
+ def set_image_as_html(
+ self, field, width, height, dimension_prefix, align, html_field, success_msg, failure_msg
+ ):
+ if not self.get(field) or not is_image(self.get(field)):
+ frappe.msgprint(failure_msg, alert=True, indicator="orange")
+ return
+
+ self.set(width, flt(self.get(width)))
+ self.set(height, flt(self.get(height)))
+
+ # To preserve the aspect ratio of the image, apply constraints only on
+ # the greater dimension and allow the other to scale accordingly
+ dimension = "width" if width > height else "height"
+ dimension_value = self.get(f"{dimension_prefix}{dimension}")
+
+ self.set(
+ html_field,
+ f"""
\ No newline at end of file
diff --git a/frappe/www/app.html b/frappe/www/app.html
index 4402dbb481..f4de074d32 100644
--- a/frappe/www/app.html
+++ b/frappe/www/app.html
@@ -26,10 +26,7 @@
{% include "public/icons/timeless/symbol-defs.svg" %}
-
-
-
+ {% include "templates/includes/splash_screen.html" %}
From d6f2d34bf4b9e214c8dff273ba05302766ff9a10 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Sat, 18 Jun 2022 22:20:49 +0530
Subject: [PATCH 159/205] perf: send boot string instead of JSON literal
changes:
- compact boot info in /app HTML
/app size went from 451kb to 393kb - ~13% less
Verified that regex applied on this JSON aren't affecting perf, infact
found them to be faster with compact JSON.
- Send json string instead of placing JSON literal in code using Jinja.
JS takes more time to pass object literal than parsing a plain JSON string.
Overall content transfer size remains roughly same (albeit slightly
lower) since double escaping ends up adding extra `\` around quotes.
Co-authored-by: Suraj Shetty
---
frappe/www/app.html | 2 +-
frappe/www/app.py | 4 +++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/frappe/www/app.html b/frappe/www/app.html
index f4de074d32..9189570674 100644
--- a/frappe/www/app.html
+++ b/frappe/www/app.html
@@ -42,7 +42,7 @@
if (!window.frappe) window.frappe = {};
- frappe.boot = {{ boot }};
+ frappe.boot = JSON.parse({{ boot }});
frappe._messages = frappe.boot["__messages"];
frappe.csrf_token = "{{ csrf_token }}";
diff --git a/frappe/www/app.py b/frappe/www/app.py
index 9a8c80d6b1..0447d00f89 100644
--- a/frappe/www/app.py
+++ b/frappe/www/app.py
@@ -2,6 +2,7 @@
# License: MIT. See LICENSE
no_cache = 1
+import json
import os
import re
@@ -32,13 +33,14 @@ def get_context(context):
frappe.db.commit()
- boot_json = frappe.as_json(boot)
+ boot_json = frappe.as_json(boot, indent=None, separators=(",", ":"))
# remove script tags from boot
boot_json = SCRIPT_TAG_PATTERN.sub("", boot_json)
# TODO: Find better fix
boot_json = CLOSING_SCRIPT_TAG_PATTERN.sub("", boot_json)
+ boot_json = json.dumps(boot_json)
context.update(
{
From 64cc07227eaac493865a851bde90ca5ca0480b7d Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Mon, 20 Jun 2022 07:53:41 +0530
Subject: [PATCH 160/205] refactor: Replace usage of deprecated attribute
---
.../print_format_builder_column_selector.html | 2 +-
.../print_format_builder_field.html | 6 +++---
.../print_format_builder_layout.html | 4 ++--
.../print_format_builder_section.html | 4 ++--
.../print_format_builder_sidebar.html | 2 +-
frappe/public/icons/social/fair.svg | 4 ++--
frappe/public/icons/social/office_365.svg | 4 ++--
frappe/public/images/leaflet/spritesheet.svg | 4 ++--
frappe/public/js/frappe/file_uploader/FileUploader.vue | 2 +-
.../public/js/frappe/form/templates/print_layout.html | 2 +-
.../js/frappe/form/templates/timeline_message_box.html | 2 +-
frappe/public/js/frappe/list/list_sidebar.html | 4 ++--
frappe/public/js/frappe/ui/group_by/group_by.html | 2 +-
frappe/public/js/frappe/ui/page.html | 10 +++++-----
frappe/public/js/frappe/ui/sort_selector.html | 2 +-
frappe/public/js/frappe/ui/toolbar/navbar.html | 2 +-
.../public/js/frappe/views/kanban/kanban_column.html | 2 +-
frappe/public/js/frappe/widgets/chart_widget.js | 2 +-
.../js/print_format_builder/ConfigureColumns.vue | 4 ++--
frappe/public/js/print_format_builder/Field.vue | 4 ++--
.../js/print_format_builder/PrintFormatSection.vue | 2 +-
frappe/templates/discussions/reply_card.html | 2 +-
.../section_with_cta/section_with_cta.html | 2 +-
.../section_with_small_cta/section_with_small_cta.html | 2 +-
frappe/www/me.html | 8 ++++----
25 files changed, 42 insertions(+), 42 deletions(-)
diff --git a/frappe/printing/page/print_format_builder/print_format_builder_column_selector.html b/frappe/printing/page/print_format_builder/print_format_builder_column_selector.html
index 15d029704b..5292531e4c 100644
--- a/frappe/printing/page/print_format_builder/print_format_builder_column_selector.html
+++ b/frappe/printing/page/print_format_builder/print_format_builder_column_selector.html
@@ -13,7 +13,7 @@
Вам необходимо установить и запустить приложение QZ Tray, чтобы использовать функцию Raw Print.
Нажмите здесь, чтобы загрузить и установить QZ Tray . Нажмите здесь, чтобы узнать больше о Raw Printing .",
-No email account associated with the User. Please add an account under User > Email Inbox.,"Нет учетной записи электронной почты, связанной с пользователем. Пожалуйста, добавьте электронную почту в настройках пользователя",
+No email account associated with the User. Please add an account under User > Email Inbox.,"Нет учетной записи электронной почты, связанной с пользователем. Пожалуйста, добавьте учетную запись в разделе Пользователь > Электронная почта Входящие",
"For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10).","Для сравнения используйте> 5, <10 или = 324. Для диапазонов используйте 5:10 (для значений от 5 до 10).",
No default Address Template found. Please create a new one from Setup > Printing and Branding > Address Template.,"Шаблон адреса по умолчанию не найден. Создайте новый, выбрав «Настройка» > «Печать и брендинг» > «Шаблон адреса».",
Please setup default Email Account from Setup > Email > Email Account,"Пожалуйста, настройте учетную запись электронной почты по умолчанию из меню «Настройка» > «Электронная почта»>",
From 1349a73e145ac631f146d6b332d85668172217b0 Mon Sep 17 00:00:00 2001
From: gruener
Date: Thu, 23 Jun 2022 13:38:29 +0200
Subject: [PATCH 180/205] fix: Virtual DocTypes currently breaking
Parent-DocType Update and Deletion (#16977)
When you create a Virtual DocType as a Child Table (which is possible without any warning), then it will lead to several errors when updating or deleting of the parent document.
This is because the following files just execute a SQL Statement for the doctype (which doesnt have a DB Table, as this is the nature of a virtual doctype ;-)
**apps/frappe/frappe/model/document.py**
```py
frappe.db.sql("""delete from `tab{0}` where parent=%s and parenttype=%s and parentfield=%s""".format(df.options), (self.name, self.doctype, fieldname))
```
**apps/frappe/frappe/model/delete_doc.py**
```py
frappe.db.sql(
"delete from `tab%s` where parenttype=%s and parent = %s" % (t, "%s", "%s"), (doctype, name)
)
```
So at these points, I added a check to not perform any sql command for virtual doctypes. With these changes, my affected situation is solved. Perhaps there are other situations, I didn't encounter yet.
As an additional feature, those virtual doctype models should also get an information about the parent is deleted, to propagate the deletion to the remote data pools; but for now I hope this bugfix can be approved.
---
frappe/core/doctype/doctype/doctype.py | 2 +-
frappe/core/doctype/doctype/test_doctype.py | 40 +++++++++++++++++++++
frappe/model/delete_doc.py | 5 ++-
frappe/model/document.py | 16 ++++++++-
4 files changed, 60 insertions(+), 3 deletions(-)
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index e834b698d5..b4cbbd6233 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -818,7 +818,7 @@ class DocType(Document):
self.nsm_parent_field = parent_field_name
def validate_child_table(self):
- if not self.get("istable") or self.is_new():
+ if not self.get("istable") or self.is_new() or self.get("is_virtual"):
# if the doctype is not a child table then return
# if the doctype is a new doctype and also a child table then
# don't move forward as it will be handled via schema
diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py
index 0bcd972c68..569cf9af2f 100644
--- a/frappe/core/doctype/doctype/test_doctype.py
+++ b/frappe/core/doctype/doctype/test_doctype.py
@@ -564,6 +564,46 @@ class TestDocType(unittest.TestCase):
self.assertEqual(doc.is_virtual, 1)
self.assertFalse(frappe.db.table_exists("Test Virtual Doctype"))
+ def test_create_virtual_doctype_as_child_table(self):
+ """Test virtual DocType as Child Table below a normal DocType."""
+ frappe.delete_doc_if_exists("DocType", "Test Parent Virtual DocType", force=1)
+ frappe.delete_doc_if_exists("DocType", "Test Virtual DocType as Child Table", force=1)
+
+ virtual_doc = new_doctype("Test Virtual DocType as Child Table")
+ virtual_doc.is_virtual = 1
+ virtual_doc.istable = 1
+ virtual_doc.insert(ignore_permissions=True)
+
+ doc = frappe.get_doc("DocType", "Test Virtual DocType as Child Table")
+
+ self.assertEqual(doc.is_virtual, 1)
+ self.assertEqual(doc.istable, 1)
+ self.assertFalse(frappe.db.table_exists("Test Virtual DocType as Child Table"))
+
+ parent_doc = new_doctype("Test Parent Virtual DocType")
+ parent_doc.append(
+ "fields",
+ {
+ "fieldname": "virtual_child_table",
+ "fieldtype": "Table",
+ "options": "Test Virtual DocType as Child Table",
+ },
+ )
+ parent_doc.insert(ignore_permissions=True)
+
+ # create entry for parent doctype
+ parent_doc_entry = frappe.get_doc(
+ {"doctype": "Test Parent Virtual DocType", "some_fieldname": "Test"}
+ )
+ parent_doc_entry.insert(ignore_permissions=True)
+
+ # update the parent doc (should not abort because of any DB query to a virtual child table, as there is none)
+ parent_doc_entry.some_fieldname = "Test update"
+ parent_doc_entry.save(ignore_permissions=True)
+
+ # delete the parent doc (should not abort because of any DB query to a virtual child table, as there is none)
+ parent_doc_entry.delete()
+
def test_default_fieldname(self):
fields = [
{"label": "title", "fieldname": "title", "fieldtype": "Data", "default": "{some_fieldname}"}
diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py
index 985cc53682..20dac14458 100644
--- a/frappe/model/delete_doc.py
+++ b/frappe/model/delete_doc.py
@@ -196,7 +196,10 @@ def delete_from_table(doctype: str, name: str, ignore_doctypes: List[str], doc):
else:
frappe.db.delete(doctype, {"name": name})
if doc:
- child_doctypes = [d.options for d in doc.meta.get_table_fields()]
+ child_doctypes = [
+ d.options for d in doc.meta.get_table_fields() if frappe.get_meta(d.options).is_virtual == 0
+ ]
+
else:
child_doctypes = frappe.get_all(
"DocField",
diff --git a/frappe/model/document.py b/frappe/model/document.py
index 22514df75a..898c40861c 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -152,6 +152,17 @@ class Document(BaseDocument):
super(Document, self).__init__(d)
for df in self._get_table_fields():
+ # Make sure not to query the DB for a child table, if it is a virtual one.
+ # During frappe is installed, the property "is_virtual" is not available in tabDocType, so
+ # we need to filter those cases for the access to frappe.db.get_value() as it would crash otherwise.
+ if (
+ hasattr(self, "doctype")
+ and not hasattr(self, "module")
+ and frappe.db.get_value("DocType", df.options, "is_virtual", cache=True)
+ ):
+ self.set(df.fieldname, [])
+ continue
+
children = (
frappe.db.get_values(
df.options,
@@ -379,7 +390,10 @@ class Document(BaseDocument):
d.db_update()
rows.append(d.name)
- if df.options in (self.flags.ignore_children_type or []):
+ if (
+ df.options in (self.flags.ignore_children_type or [])
+ or frappe.get_meta(df.options).is_virtual == 1
+ ):
# do not delete rows for this because of flags
# hack for docperm :(
return
From 3bc636ce30ba702a77c4d1e77a0fb46ffeed2e83 Mon Sep 17 00:00:00 2001
From: Samuel Danieli <23150094+scdanieli@users.noreply.github.com>
Date: Fri, 24 Jun 2022 11:27:30 +0200
Subject: [PATCH 181/205] fix(Image View): translate info field
---
frappe/public/js/frappe/views/image/image_view.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/public/js/frappe/views/image/image_view.js b/frappe/public/js/frappe/views/image/image_view.js
index 57647642a3..94494e72e9 100644
--- a/frappe/public/js/frappe/views/image/image_view.js
+++ b/frappe/public/js/frappe/views/image/image_view.js
@@ -75,8 +75,8 @@ frappe.views.ImageView = class ImageView extends frappe.views.ListView {
let set = false;
info_fields.forEach((field, index) => {
if (item[field] && !set) {
- if (index == 0) info_html += `
${item[field]}
`;
- else info_html += `
${item[field]}
`;
+ if (index == 0) info_html += `
${__(item[field])}
`;
+ else info_html += `
${__(item[field])}
`;
set = true;
}
});
From 47483f833fc132d32a9c6166b911882859ffe35f Mon Sep 17 00:00:00 2001
From: Danny <66078599+mmdanny89@users.noreply.github.com>
Date: Fri, 24 Jun 2022 07:34:49 -0400
Subject: [PATCH 182/205] feat(translations-cli): Fetch translations for a
particular app (#17276)
* translataions for particular app
get-untranslated/update-translations string for particular app frappe#17268 (feature request)
* fix: linting, making stuff DRY-er
Co-authored-by: gavin
---
frappe/commands/translate.py | 10 ++++++----
frappe/translate.py | 24 ++++++++++++++++++------
2 files changed, 24 insertions(+), 10 deletions(-)
diff --git a/frappe/commands/translate.py b/frappe/commands/translate.py
index 0b14e03002..69970d8d97 100644
--- a/frappe/commands/translate.py
+++ b/frappe/commands/translate.py
@@ -48,11 +48,12 @@ def new_language(context, lang_code, app):
@click.command("get-untranslated")
+@click.option("--app", default="_ALL_APPS")
@click.argument("lang")
@click.argument("untranslated_file")
@click.option("--all", default=False, is_flag=True, help="Get all message strings")
@pass_context
-def get_untranslated(context, lang, untranslated_file, all=None):
+def get_untranslated(context, lang, untranslated_file, app="_ALL_APPS", all=None):
"Get untranslated strings for language"
import frappe.translate
@@ -60,17 +61,18 @@ def get_untranslated(context, lang, untranslated_file, all=None):
try:
frappe.init(site=site)
frappe.connect()
- frappe.translate.get_untranslated(lang, untranslated_file, get_all=all)
+ frappe.translate.get_untranslated(lang, untranslated_file, get_all=all, app=app)
finally:
frappe.destroy()
@click.command("update-translations")
+@click.option("--app", default="_ALL_APPS")
@click.argument("lang")
@click.argument("untranslated_file")
@click.argument("translated-file")
@pass_context
-def update_translations(context, lang, untranslated_file, translated_file):
+def update_translations(context, lang, untranslated_file, translated_file, app="_ALL_APPS"):
"Update translated strings"
import frappe.translate
@@ -78,7 +80,7 @@ def update_translations(context, lang, untranslated_file, translated_file):
try:
frappe.init(site=site)
frappe.connect()
- frappe.translate.update_translations(lang, untranslated_file, translated_file)
+ frappe.translate.update_translations(lang, untranslated_file, translated_file, app=app)
finally:
frappe.destroy()
diff --git a/frappe/translate.py b/frappe/translate.py
index eb26124ba8..460c7db9b0 100644
--- a/frappe/translate.py
+++ b/frappe/translate.py
@@ -808,7 +808,7 @@ def write_csv_file(path, app_messages, lang_dict):
w.writerow([message, translated_string, context])
-def get_untranslated(lang, untranslated_file, get_all=False):
+def get_untranslated(lang, untranslated_file, get_all=False, app="_ALL_APPS"):
"""Returns all untranslated strings for a language and writes in a file
:param lang: Language code.
@@ -816,11 +816,16 @@ def get_untranslated(lang, untranslated_file, get_all=False):
:param get_all: Return all strings, translated or not."""
clear_cache()
apps = frappe.get_all_apps(True)
+ if app != "_ALL_APPS":
+ if app not in apps:
+ print(f"Application {app} not found!")
+ return
+ apps = [app]
messages = []
untranslated = []
- for app in apps:
- messages.extend(get_messages_for_app(app))
+ for app_name in apps:
+ messages.extend(get_messages_for_app(app_name))
messages = deduplicate_messages(messages)
@@ -850,7 +855,7 @@ def get_untranslated(lang, untranslated_file, get_all=False):
print("all translated!")
-def update_translations(lang, untranslated_file, translated_file):
+def update_translations(lang, untranslated_file, translated_file, app="_ALL_APPS"):
"""Update translations from a source and target file for a given language.
:param lang: Language code (e.g. `en`).
@@ -879,9 +884,16 @@ def update_translations(lang, untranslated_file, translated_file):
translation_dict[restore_newlines(key)] = restore_newlines(value)
full_dict.update(translation_dict)
+ apps = frappe.get_all_apps(True)
- for app in frappe.get_all_apps(True):
- write_translations_file(app, lang, full_dict)
+ if app != "_ALL_APPS":
+ if app not in apps:
+ print(f"Application {app} not found!")
+ return
+ apps = [app]
+
+ for app_name in apps:
+ write_translations_file(app_name, lang, full_dict)
def import_translations(lang, path):
From ac842872666e5ff92a9a58183efd2b6ef12c2e58 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Fri, 24 Jun 2022 18:31:43 +0530
Subject: [PATCH 183/205] fix: improve SVG for expenses icon
---
frappe/public/icons/timeless/icons.svg | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/public/icons/timeless/icons.svg b/frappe/public/icons/timeless/icons.svg
index fbd72d6fb5..af63d0c8ff 100644
--- a/frappe/public/icons/timeless/icons.svg
+++ b/frappe/public/icons/timeless/icons.svg
@@ -613,8 +613,8 @@
-
+
From 3c617e3b06e128192dc2bbab5abccf198145d55c Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sun, 26 Jun 2022 11:51:52 +0530
Subject: [PATCH 184/205] fix!: dont delete custom permissions when doctype is
deleted
---
frappe/model/delete_doc.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py
index 20dac14458..04e25fe198 100644
--- a/frappe/model/delete_doc.py
+++ b/frappe/model/delete_doc.py
@@ -89,8 +89,6 @@ def delete_doc(
update_flags(doc, flags, ignore_permissions)
check_permission_and_not_submitted(doc)
-
- frappe.db.delete("Custom DocPerm", {"parent": name})
frappe.db.delete("__global_search", {"doctype": name})
delete_from_table(doctype, name, ignore_doctypes, None)
From b6963b1dec8a9c2e8abea61df48e3c905fa02a63 Mon Sep 17 00:00:00 2001
From: Smit Vora
Date: Sun, 26 Jun 2022 12:53:38 +0530
Subject: [PATCH 185/205] fix: ignore integration request when deleting doc
---
frappe/model/delete_doc.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py
index 20dac14458..a71cfa6053 100644
--- a/frappe/model/delete_doc.py
+++ b/frappe/model/delete_doc.py
@@ -31,6 +31,7 @@ doctypes_to_skip = (
"Notification Log",
"Email Queue",
"Document Share Key",
+ "Integration Request",
)
From 217644ac07aca387da760217686c42635c76804e Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sun, 26 Jun 2022 19:31:03 +0000
Subject: [PATCH 186/205] fix!: remove unnecessary `get_default` API (#17295)
---
frappe/client.py | 6 ------
.../js/print_format_builder/LetterHeadEditor.vue | 10 ++--------
2 files changed, 2 insertions(+), 14 deletions(-)
diff --git a/frappe/client.py b/frappe/client.py
index 154fcf31e2..d5dc890f56 100644
--- a/frappe/client.py
+++ b/frappe/client.py
@@ -281,12 +281,6 @@ def set_default(key, value, parent=None):
frappe.clear_cache(user=frappe.session.user)
-@frappe.whitelist()
-def get_default(key, parent=None):
- """set a user default value"""
- return frappe.db.get_default(key, parent)
-
-
@frappe.whitelist(methods=["POST", "PUT"])
def make_width_property_setter(doc):
"""Set width Property Setter
diff --git a/frappe/public/js/print_format_builder/LetterHeadEditor.vue b/frappe/public/js/print_format_builder/LetterHeadEditor.vue
index 1eae56f81a..5dd0ee3575 100644
--- a/frappe/public/js/print_format_builder/LetterHeadEditor.vue
+++ b/frappe/public/js/print_format_builder/LetterHeadEditor.vue
@@ -150,14 +150,8 @@ export default {
}
},
mounted() {
- if (!this.letterhead) {
- frappe
- .call("frappe.client.get_default", { key: "letter_head" })
- .then(r => {
- if (r.message) {
- this.set_letterhead(r.message);
- }
- });
+ if (!this.letterhead && frappe.boot.sysdefaults.letter_head) {
+ this.set_letterhead(frappe.boot.sysdefaults.letter_head);
}
this.$watch(
From c99dd7c5efb8467dcf686807980ef6169c14dd71 Mon Sep 17 00:00:00 2001
From: Abhirup
Date: Thu, 16 Jun 2022 12:57:06 +0530
Subject: [PATCH 187/205] Add data titles to sidebar workspace fields for
easier selection through CSS.
---
frappe/public/js/frappe/views/workspace/workspace.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/views/workspace/workspace.js b/frappe/public/js/frappe/views/workspace/workspace.js
index d95925eea6..6c719d5dc1 100644
--- a/frappe/public/js/frappe/views/workspace/workspace.js
+++ b/frappe/public/js/frappe/views/workspace/workspace.js
@@ -118,7 +118,7 @@ frappe.views.Workspace = class Workspace {
}
build_sidebar_section(title, root_pages) {
- let sidebar_section = $(``);
+ let sidebar_section = $(``);
let $title = $(`
${frappe.utils.icon("small-down", "xs")}
From 99388652c9966b5826fafffdb2c0301a5704e467 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Mon, 27 Jun 2022 01:56:58 +0530
Subject: [PATCH 188/205] chore(redis): remove unnecessary exception handling
---
frappe/utils/redis_wrapper.py | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py
index 06480f0b7b..178c3d4416 100644
--- a/frappe/utils/redis_wrapper.py
+++ b/frappe/utils/redis_wrapper.py
@@ -105,10 +105,7 @@ class RedisWrapper(redis.Redis):
def delete_keys(self, key):
"""Delete keys with wildcard `*`."""
- try:
- self.delete_value(self.get_keys(key), make_keys=False)
- except redis.exceptions.ConnectionError:
- pass
+ self.delete_value(self.get_keys(key), make_keys=False)
def delete_key(self, *args, **kwargs):
self.delete_value(*args, **kwargs)
@@ -202,10 +199,7 @@ class RedisWrapper(redis.Redis):
frappe.local.cache[_name][key] = value
elif generator:
value = generator()
- try:
- self.hset(name, key, value)
- except redis.exceptions.ConnectionError:
- pass
+ self.hset(name, key, value)
return value
def hdel(self, name, key, shared=False):
From f89dc40b94e30e3a00b1c54434df2f3a05d4c610 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Mon, 27 Jun 2022 14:46:06 +0530
Subject: [PATCH 189/205] fix: Allow permitted number card of type report
(#17316)
---
frappe/desk/doctype/number_card/number_card.py | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py
index 2290e49656..74c8e9eb99 100644
--- a/frappe/desk/doctype/number_card/number_card.py
+++ b/frappe/desk/doctype/number_card/number_card.py
@@ -4,6 +4,7 @@
import frappe
from frappe import _
+from frappe.boot import get_allowed_reports
from frappe.config import get_modules_from_all_apps_for_user
from frappe.model.document import Document
from frappe.model.naming import append_number_if_name_exists
@@ -90,9 +91,16 @@ def has_permission(doc, ptype, user):
if "System Manager" in roles:
return True
- allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read())
- if doc.document_type in allowed_doctypes:
- return True
+ if doc.type == "Report":
+ allowed_reports = [
+ key if type(key) == str else key.encode("UTF8") for key in get_allowed_reports()
+ ]
+ if doc.report_name in allowed_reports:
+ return True
+ else:
+ allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read())
+ if doc.document_type in allowed_doctypes:
+ return True
return False
From 084a1e6c31520de09d639dbb5db129fb7e757d4b Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 27 Jun 2022 15:18:06 +0530
Subject: [PATCH 190/205] refactor: get_permissions
* Show page even if dangling Custom DocPerm records encountered
* Add typing hints
* Cleanup APIs
---
.../permission_manager/permission_manager.py | 25 +++++++++++++------
frappe/permissions.py | 23 ++++++++---------
2 files changed, 28 insertions(+), 20 deletions(-)
diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py
index ad12e0fd4c..e2d08488c0 100644
--- a/frappe/core/page/permission_manager/permission_manager.py
+++ b/frappe/core/page/permission_manager/permission_manager.py
@@ -1,6 +1,8 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
+from typing import Optional
+
import frappe
import frappe.defaults
from frappe import _
@@ -8,6 +10,7 @@ from frappe.core.doctype.doctype.doctype import (
clear_permissions_cache,
validate_permissions_for_doctype,
)
+from frappe.exceptions import DoesNotExistError
from frappe.modules.import_file import get_file_path, read_doc_from_file
from frappe.permissions import (
add_permission,
@@ -68,17 +71,19 @@ def get_roles_and_doctypes():
@frappe.whitelist()
-def get_permissions(doctype=None, role=None):
+def get_permissions(doctype: Optional[str] = None, role: Optional[str] = None):
frappe.only_for("System Manager")
+
if role:
out = get_all_perms(role)
if doctype:
out = [p for p in out if p.parent == doctype]
+
else:
- filters = dict(parent=doctype)
+ filters = {"parent": doctype}
if frappe.session.user != "Administrator":
- custom_roles = frappe.get_all("Role", filters={"is_custom": 1})
- filters["role"] = ["not in", [row.name for row in custom_roles]]
+ custom_roles = frappe.get_all("Role", filters={"is_custom": 1}, pluck="name")
+ filters["role"] = ["not in", custom_roles]
out = frappe.get_all("Custom DocPerm", fields="*", filters=filters, order_by="permlevel")
if not out:
@@ -86,11 +91,15 @@ def get_permissions(doctype=None, role=None):
linked_doctypes = {}
for d in out:
- if not d.parent in linked_doctypes:
- linked_doctypes[d.parent] = get_linked_doctypes(d.parent)
+ if d.parent not in linked_doctypes:
+ try:
+ linked_doctypes[d.parent] = get_linked_doctypes(d.parent)
+ except DoesNotExistError:
+ # exclude & continue if linked doctype is not found
+ frappe.clear_last_message()
+ continue
d.linked_doctypes = linked_doctypes[d.parent]
- meta = frappe.get_meta(d.parent)
- if meta:
+ if meta := frappe.get_meta(d.parent):
d.is_submittable = meta.is_submittable
d.in_create = meta.in_create
diff --git a/frappe/permissions.py b/frappe/permissions.py
index 8980d2e63e..55a7c0e5f3 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import copy
+from typing import List
import frappe
import frappe.share
@@ -605,19 +606,17 @@ def reset_perms(doctype):
frappe.db.delete("Custom DocPerm", {"parent": doctype})
-def get_linked_doctypes(dt):
- return list(
- set(
- [dt]
- + [
- d.options
- for d in frappe.get_meta(dt).get(
- "fields",
- {"fieldtype": "Link", "ignore_user_permissions": ("!=", 1), "options": ("!=", "[Select]")},
- )
- ]
+def get_linked_doctypes(dt: str) -> List:
+ meta = frappe.get_meta(dt)
+ linked_doctypes = [dt] + [
+ d.options
+ for d in meta.get(
+ "fields",
+ {"fieldtype": "Link", "ignore_user_permissions": ("!=", 1), "options": ("!=", "[Select]")},
)
- )
+ ]
+
+ return list(set(linked_doctypes))
def get_doc_name(doc):
From fba75c35951982631322a17b3af1778ed2d129aa Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 27 Jun 2022 15:55:54 +0530
Subject: [PATCH 191/205] fix: use console.error for logging errors (#17318)
Currently if uncaught exception occurs on server side, there's no simple
way to find it out. Converting these console.log to console.error
simplifies this for UI tests.
Also converted `console.trace to` `console.error`; Both are practically same
and give stack trace too, `trace` expands tracebacks by default. This is
done to make spying on window.console.error easier.
---
frappe/public/js/frappe/request.js | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js
index 87ab06ce18..dd4c352f41 100644
--- a/frappe/public/js/frappe/request.js
+++ b/frappe/public/js/frappe/request.js
@@ -280,7 +280,7 @@ frappe.request.call = function(opts) {
}
} catch(e) {
console.log("Unable to handle success response", data); // eslint-disable-line
- console.trace(e); // eslint-disable-line
+ console.error(e); // eslint-disable-line
}
})
@@ -332,7 +332,7 @@ frappe.request.call = function(opts) {
opts.error_callback && opts.error_callback(xhr);
} catch(e) {
console.log("Unable to handle failed response"); // eslint-disable-line
- console.trace(e); // eslint-disable-line
+ console.error(e); // eslint-disable-line
}
});
}
@@ -433,13 +433,13 @@ frappe.request.cleanup = function(opts, r) {
if(r.exc) {
r.exc = JSON.parse(r.exc);
if(r.exc instanceof Array) {
- $.each(r.exc, function(i, v) {
- if(v) {
- console.log(v);
+ r.exc.forEach(exc => {
+ if(exc) {
+ console.error(exc);
}
- })
+ });
} else {
- console.log(r.exc);
+ console.error(r.exc);
}
}
From 112f11359847cd286752177b42a357d4b6910cc3 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 27 Jun 2022 17:04:34 +0530
Subject: [PATCH 192/205] fix!: remove dangerous "rollback_on_exception" flag
(#17321)
---
frappe/__init__.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index ee199c8aed..9104cd5109 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -432,9 +432,6 @@ def msgprint(
def _raise_exception():
if raise_exception:
- if flags.rollback_on_exception:
- db.rollback()
-
if inspect.isclass(raise_exception) and issubclass(raise_exception, Exception):
raise raise_exception(msg)
else:
From b7514a05ca9039bf9336ece3938b69bdeecb6585 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 28 Jun 2022 00:37:31 +0530
Subject: [PATCH 193/205] fix(redis): pass shared param when setting value
based on generator
---
frappe/utils/redis_wrapper.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py
index 178c3d4416..c29836af32 100644
--- a/frappe/utils/redis_wrapper.py
+++ b/frappe/utils/redis_wrapper.py
@@ -199,7 +199,7 @@ class RedisWrapper(redis.Redis):
frappe.local.cache[_name][key] = value
elif generator:
value = generator()
- self.hset(name, key, value)
+ self.hset(name, key, value, shared=shared)
return value
def hdel(self, name, key, shared=False):
From 5604b090c0b2e891a19d1b122ea2e30ff23b543d Mon Sep 17 00:00:00 2001
From: Samuel Danieli <23150094+scdanieli@users.noreply.github.com>
Date: Mon, 27 Jun 2022 22:15:23 +0200
Subject: [PATCH 194/205] feat: enable further translations
---
frappe/public/js/frappe/ui/sort_selector.html | 2 +-
frappe/public/js/frappe/ui/sort_selector.js | 2 +-
frappe/public/js/frappe/ui/toolbar/toolbar.js | 2 +-
frappe/public/js/frappe/utils/dashboard_utils.js | 4 +++-
frappe/public/js/frappe/views/kanban/kanban_column.html | 2 +-
5 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/frappe/public/js/frappe/ui/sort_selector.html b/frappe/public/js/frappe/ui/sort_selector.html
index 6f79f986a1..4e1945c46c 100644
--- a/frappe/public/js/frappe/ui/sort_selector.html
+++ b/frappe/public/js/frappe/ui/sort_selector.html
@@ -2,7 +2,7 @@