From bf93eae0417382a2f1f77c5ad47b083dcad9051c Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Sun, 1 Feb 2026 16:54:56 +0530 Subject: [PATCH] feat: create base notification templates --- .../doctype/notification/notification.py | 72 +++++++++++ .../templates/error_log/error_log.html | 66 ++++++++++ .../templates/error_log/error_log.json | 16 +++ .../integration_request.html | 122 ++++++++++++++++++ .../integration_request.json | 17 +++ frappe/patches.txt | 1 + frappe/utils/install.py | 4 + 7 files changed, 298 insertions(+) create mode 100644 frappe/email/doctype/notification/templates/error_log/error_log.html create mode 100644 frappe/email/doctype/notification/templates/error_log/error_log.json create mode 100644 frappe/email/doctype/notification/templates/integration_request/integration_request.html create mode 100644 frappe/email/doctype/notification/templates/integration_request/integration_request.json diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 57a6d70b73..584df7b5f8 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -867,3 +867,75 @@ def _parse_receiver_by_document_field(s): else: data_field, child_field = fragments[0], None return data_field, child_field + + +def create_notifications(notifications: list[dict], update: bool = False): + """ + Unlike standard notifications, these are NOT marked as is_standard=1, + so they won't be overwritten during migrations. Users can freely customize them. + + Args: + notifications: List of notification dicts. + update: If True, update existing notification. If False (default), skip if exists. + """ + for notif_dict in notifications: + name = notif_dict.get("name") + existing = frappe.db.exists("Notification", name) + + if existing and not update: + continue + + if existing and update: + doc = frappe.get_doc("Notification", name) + doc.update(notif_dict) + doc.flags.ignore_validate = True + doc.save(ignore_permissions=True) + continue + + notif_dict["doctype"] = "Notification" + notif_dict["is_standard"] = 0 + notif_dict["owner"] = "Administrator" + + doc = frappe.get_doc(notif_dict) + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True) + + +def get_notification_templates(templates_dir: str) -> list[dict]: + """ + Load notification templates from the templates directory. + + Templates are stored in subdirectories: + //.json + //.html|.md|.txt (optional message content based on message_type) + """ + templates = [] + + if not os.path.exists(templates_dir): + return templates + + for folder_name in os.listdir(templates_dir): + folder_path = os.path.join(templates_dir, folder_name) + if not os.path.isdir(folder_path): + continue + + json_file = os.path.join(folder_path, f"{folder_name}.json") + template = frappe.get_file_json(json_file) if os.path.exists(json_file) else None + if not template: + continue + + message_type = template.get("message_type", "HTML") + ext = FORMATS.get(message_type, ".html") + message_file = os.path.join(folder_path, f"{folder_name}{ext}") + if message := frappe.read_file(message_file): + template["message"] = message + + templates.append(template) + + return templates + + +def install_notification_templates(): + templates_dir = frappe.get_module_path("Email", "doctype", "notification", "templates") + templates = get_notification_templates(templates_dir) + create_notifications(templates, update=False) diff --git a/frappe/email/doctype/notification/templates/error_log/error_log.html b/frappe/email/doctype/notification/templates/error_log/error_log.html new file mode 100644 index 0000000000..a663f22129 --- /dev/null +++ b/frappe/email/doctype/notification/templates/error_log/error_log.html @@ -0,0 +1,66 @@ +{% set error_lines = (doc.error or "").split('\n') %} +{% set first_lines = 10 %} +{% set last_lines = 15 %} +{% set max_lines = first_lines + last_lines %} +{% set total_lines = error_lines | length %} +{% set needs_truncation = total_lines > max_lines %} + + + + + + + + + + + + + + + + + + + + + + + + + {% if doc.reference_doctype and doc.reference_name %} + + + + + {% endif %} +
Site{{ frappe.utils.get_url() }}
Error ID{{ frappe.utils.get_link_to_form("Error Log", doc.name, doc.name) }}
Title{{ doc.method or "N/A" }}
Logged At{{ doc.creation }}
Reference{{ frappe.utils.get_link_to_form(doc.reference_doctype, doc.reference_name) }}
+ +
+
+ Error Details + {% if needs_truncation %} + ({{ max_lines }} of {{ total_lines }} lines) + {% endif %} +
+
+
{% if needs_truncation %}{{ error_lines[:first_lines] | join('\n') }}
+
+... {{ total_lines - max_lines }} lines omitted ...
+
+{{ error_lines[-last_lines:] | join('\n') }}{% else %}{{ error_lines | join('\n') }}{% endif %}
+
+
+ + + +

+ This is an automated notification from {{ frappe.utils.get_host_name() }}. +

diff --git a/frappe/email/doctype/notification/templates/error_log/error_log.json b/frappe/email/doctype/notification/templates/error_log/error_log.json new file mode 100644 index 0000000000..a76a7b3430 --- /dev/null +++ b/frappe/email/doctype/notification/templates/error_log/error_log.json @@ -0,0 +1,16 @@ +{ + "name": "Error Log", + "document_type": "Error Log", + "event": "New", + "channel": "Email", + "enabled": 0, + "subject": "[Error] {{ doc.method }}", + "message_type": "HTML", + "recipients": [ + { + "receiver_by_role": "System Manager" + } + ], + "send_system_notification": 0, + "send_to_all_assignees": 0 +} diff --git a/frappe/email/doctype/notification/templates/integration_request/integration_request.html b/frappe/email/doctype/notification/templates/integration_request/integration_request.html new file mode 100644 index 0000000000..55db1a1575 --- /dev/null +++ b/frappe/email/doctype/notification/templates/integration_request/integration_request.html @@ -0,0 +1,122 @@ +{% set error_lines = (doc.error or "").split('\n') %} +{% set output_lines = (doc.output or "").split('\n') %} +{% set first_lines = 10 %} +{% set last_lines = 15 %} +{% set max_lines = first_lines + last_lines %} +{% set total_error_lines = error_lines | length %} +{% set error_needs_truncation = total_error_lines > max_lines %} +{% set total_output_lines = output_lines | length %} +{% set output_needs_truncation = total_output_lines > max_lines %} + + + + + + + + + + + + + + + + + + + + + + + + + {% if doc.request_description %} + + + + + {% endif %} + + + + + {% if doc.url %} + + + + + {% endif %} + {% if doc.reference_doctype and doc.reference_docname %} + + + + + {% endif %} +
Site{{ frappe.utils.get_url() }}
Request ID{{ frappe.utils.get_link_to_form("Integration Request", doc.name, doc.request_id or + doc.name) }}
Service{{ doc.integration_request_service or "N/A" }}
Status + + {{ doc.status }} + +
Description{{ doc.request_description }}
Logged At{{ frappe.utils.format_datetime(doc.creation) }}
Endpoint URL{{ doc.url }}
Reference + + {{ doc.reference_doctype }}: {{ doc.reference_docname }} + +
+ +{% if doc.error %} +
+
+ Error Details + {% if error_needs_truncation %} + ({{ max_lines }} of {{ + total_error_lines }} lines) + {% endif %} +
+
+
{% if error_needs_truncation %}{{ error_lines[:first_lines] | join('\n') }}
+
+... {{ total_error_lines - max_lines }} lines omitted ...
+
+{{ error_lines[-last_lines:] | join('\n') }}{% else %}{{ error_lines | join('\n') }}{% endif %}
+
+
+{% endif %} + +{% if doc.output %} +
+
+ Response Output + {% if output_needs_truncation %} + ({{ max_lines }} of {{ + total_output_lines }} lines) + {% endif %} +
+
+
{% if output_needs_truncation %}{{ output_lines[:first_lines] | join('\n') }}
+
+... {{ total_output_lines - max_lines }} lines omitted ...
+
+{{ output_lines[-last_lines:] | join('\n') }}{% else %}{{ output_lines | join('\n') }}{% endif %}
+
+
+{% endif %} + + + +

+ This is an automated notification from {{ frappe.utils.get_host_name() }}. +

\ No newline at end of file diff --git a/frappe/email/doctype/notification/templates/integration_request/integration_request.json b/frappe/email/doctype/notification/templates/integration_request/integration_request.json new file mode 100644 index 0000000000..251749c26a --- /dev/null +++ b/frappe/email/doctype/notification/templates/integration_request/integration_request.json @@ -0,0 +1,17 @@ +{ + "name": "Integration Request", + "document_type": "Integration Request", + "event": "Save", + "channel": "Email", + "condition": "doc.status==\"Failed\"", + "enabled": 0, + "subject": "[Error] {{ doc.integration_request_service }}", + "message_type": "HTML", + "recipients": [ + { + "receiver_by_role": "System Manager" + } + ], + "send_system_notification": 0, + "send_to_all_assignees": 0 +} diff --git a/frappe/patches.txt b/frappe/patches.txt index 5d32252e25..85837e63c5 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -256,3 +256,4 @@ frappe.patches.v16_0.add_standard_field_in_workspace_sidebar execute:frappe.db.set_single_value("Desktop Settings", "icon_style", "Solid") execute:frappe.delete_doc_if_exists("Workspace Sidebar", "Productivity") frappe.patches.v16_0.unset_standard_field_for_auto_generated_icons +execute:frappe.email.doctype.notification.notification.install_notification_templates diff --git a/frappe/utils/install.py b/frappe/utils/install.py index 4dcda39fa1..51e5198681 100644 --- a/frappe/utils/install.py +++ b/frappe/utils/install.py @@ -3,6 +3,7 @@ import getpass import frappe +from frappe.email.doctype.notification.notification import install_notification_templates from frappe.geo.doctype.country.country import import_country_and_currency from frappe.utils import cint from frappe.utils.password import update_password @@ -53,6 +54,9 @@ def after_install(): add_standard_navbar_items() + # default templates + install_notification_templates() + frappe.db.commit()