diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index e6e13dafe7..c585a81826 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -29,6 +29,7 @@ class NotificationLog(Document): read: DF.Check subject: DF.Text | None type: DF.Literal["Mention", "Energy Point", "Assignment", "Share", "Alert"] + # end: auto-generated types def after_insert(self): frappe.publish_realtime("notification", after_commit=True, user=self.for_user) @@ -115,18 +116,17 @@ def _get_user_ids(user_emails): return [user for user in user_names if is_notifications_enabled(user)] -def send_notification_email(doc): - +def send_notification_email(doc: NotificationLog): if doc.type == "Energy Point" and doc.email_content is None: return from frappe.utils import get_url_to_form, strip_html - email = frappe.db.get_value("User", doc.for_user, "email") - if not email: + user = frappe.db.get_value("User", doc.for_user, fieldname=["email", "language"], as_dict=True) + if not user: return - header = get_email_header(doc) + header = get_email_header(doc, user.language) email_subject = strip_html(doc.subject) args = { "body_content": doc.subject, @@ -140,7 +140,7 @@ def send_notification_email(doc): args["doc_link"] = get_url_to_form(doc.document_type, doc.document_name) frappe.sendmail( - recipients=email, + recipients=user.email, subject=email_subject, template="new_notification", args=args, @@ -149,14 +149,14 @@ def send_notification_email(doc): ) -def get_email_header(doc): +def get_email_header(doc, language: str | None = None): docname = doc.document_name header_map = { - "Default": _("New Notification"), - "Mention": _("New Mention on {0}").format(docname), - "Assignment": _("Assignment Update on {0}").format(docname), - "Share": _("New Document Shared {0}").format(docname), - "Energy Point": _("Energy Point Update on {0}").format(docname), + "Default": _("New Notification", lang=language), + "Mention": _("New Mention on {0}", lang=language).format(docname), + "Assignment": _("Assignment Update on {0}", lang=language).format(docname), + "Share": _("New Document Shared {0}", lang=language).format(docname), + "Energy Point": _("Energy Point Update on {0}", lang=language).format(docname), } return header_map[doc.type or "Default"] diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index dc8dbc7cf5..e0de030303 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -253,8 +253,10 @@ def notify_assignment( if not (assigned_by and allocated_to and doc_type and doc_name): return + assigned_user = frappe.db.get_value("User", allocated_to, ["language", "enabled"], as_dict=True) + # return if self assigned or user disabled - if assigned_by == allocated_to or not frappe.db.get_value("User", allocated_to, "enabled"): + if assigned_by == allocated_to or not assigned_user.enabled: return # Search for email address in description -- i.e. assignee @@ -263,14 +265,16 @@ def notify_assignment( description_html = f"
{description}
" if description else None if action == "CLOSE": - subject = _("Your assignment on {0} {1} has been removed by {2}").format( - frappe.bold(_(doc_type)), get_title_html(title), frappe.bold(user_name) - ) + subject = _( + "Your assignment on {0} {1} has been removed by {2}", lang=assigned_user.language + ).format(frappe.bold(_(doc_type)), get_title_html(title), frappe.bold(user_name)) else: user_name = frappe.bold(user_name) - document_type = frappe.bold(_(doc_type)) + document_type = frappe.bold(_(doc_type, lang=assigned_user.language)) title = get_title_html(title) - subject = _("{0} assigned a new task {1} {2} to you").format(user_name, document_type, title) + subject = _("{0} assigned a new task {1} {2} to you", lang=assigned_user.language).format( + user_name, document_type, title + ) notification_doc = { "type": "Assignment", diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index 828ae2e419..91ca2bbdbb 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -6,7 +6,7 @@ import quopri import traceback from contextlib import suppress from email.parser import Parser -from email.policy import SMTPUTF8, default +from email.policy import SMTP import frappe from frappe import _, safe_encode, task @@ -169,7 +169,9 @@ class EmailQueue(Document): else: if not frappe.flags.in_test or frappe.flags.testing_email: ctx.smtp_server.session.sendmail( - from_addr=self.sender, to_addrs=recipient.recipient, msg=message + from_addr=self.sender, + to_addrs=recipient.recipient, + msg=message.decode("utf-8").encode(), ) ctx.update_recipient_status_to_sent(recipient) @@ -264,7 +266,7 @@ class SendMailContext: @savepoint(catch=Exception) def notify_failed_email(self): # Parse the email body to extract the subject - subject = Parser(policy=default).parsestr(self.queue_doc.message)["Subject"] + subject = Parser(policy=SMTP).parsestr(self.queue_doc.message)["Subject"] # Construct the notification notification = frappe.new_doc("Notification Log") @@ -281,7 +283,7 @@ class SendMailContext: recipient.update_db(status="Sent", commit=True) def get_message_object(self, message): - return Parser(policy=SMTPUTF8).parsestr(message) + return Parser(policy=SMTP).parsestr(message) def message_placeholder(self, placeholder_key): # sourcery skip: avoid-builtin-shadow @@ -293,9 +295,10 @@ class SendMailContext: } return map.get(placeholder_key) - def build_message(self, recipient_email): + def build_message(self, recipient_email) -> bytes: """Build message specific to the recipient.""" message = self.queue_doc.message + if not message: return "" diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index 449c0b5b15..80348df394 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -1,6 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE - import email.utils import os import re @@ -136,8 +135,8 @@ class EMail: self.subject = subject self.expose_recipients = expose_recipients - self.msg_root = MIMEMultipart("mixed", policy=policy.SMTPUTF8) - self.msg_alternative = MIMEMultipart("alternative", policy=policy.SMTPUTF8) + self.msg_root = MIMEMultipart("mixed", policy=policy.SMTP) + self.msg_alternative = MIMEMultipart("alternative", policy=policy.SMTP) self.msg_root.attach(self.msg_alternative) self.cc = cc or [] self.bcc = bcc or [] @@ -186,7 +185,7 @@ class EMail: """ from email.mime.text import MIMEText - part = MIMEText(message, "plain", "utf-8", policy=policy.SMTPUTF8) + part = MIMEText(message, "plain", "utf-8", policy=policy.SMTP) self.msg_alternative.attach(part) def set_part_html(self, message, inline_images): @@ -199,9 +198,9 @@ class EMail: message, _inline_images = replace_filename_with_cid(message) # prepare parts - msg_related = MIMEMultipart("related", policy=policy.SMTPUTF8) + msg_related = MIMEMultipart("related", policy=policy.SMTP) - html_part = MIMEText(message, "html", "utf-8", policy=policy.SMTPUTF8) + html_part = MIMEText(message, "html", "utf-8", policy=policy.SMTP) msg_related.attach(html_part) for image in _inline_images: @@ -215,7 +214,7 @@ class EMail: self.msg_alternative.attach(msg_related) else: - self.msg_alternative.attach(MIMEText(message, "html", "utf-8", policy=policy.SMTPUTF8)) + self.msg_alternative.attach(MIMEText(message, "html", "utf-8", policy=policy.SMTP)) def set_html_as_text(self, html): """Set plain text from HTML""" @@ -228,7 +227,7 @@ class EMail: from email.mime.text import MIMEText maintype, subtype = mime_type.split("/") - part = MIMEText(message, _subtype=subtype, policy=policy.SMTPUTF8) + part = MIMEText(message, _subtype=subtype, policy=policy.SMTP) if as_attachment: part.add_header("Content-Disposition", "attachment", filename=filename) @@ -342,7 +341,7 @@ class EMail: """validate, build message and convert to string""" self.validate() self.make() - return self.msg_root.as_string(policy=policy.SMTPUTF8) + return self.msg_root.as_string(policy=policy.SMTP) def get_formatted_html(