mirror of
https://github.com/makeplane/plane.git
synced 2026-02-24 12:11:39 +01:00
[WEB-5917] fix: generate clean plain text from HTML email template #8535
This commit is contained in:
committed by
GitHub
parent
e9b011896d
commit
f0dcf66167
@@ -13,7 +13,6 @@ from celery import shared_task
|
||||
# Django imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
from django.db.models import Q, Case, Value, When
|
||||
from django.db import models
|
||||
from django.db.models.functions import Concat
|
||||
@@ -22,6 +21,7 @@ from django.db.models.functions import Concat
|
||||
from plane.db.models import Issue
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.analytics_plot import build_graph_plot
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.utils.csv_utils import sanitize_csv_row
|
||||
@@ -53,7 +53,7 @@ def send_export_email(email, slug, csv_buffer, rows):
|
||||
"""Helper function to send export email."""
|
||||
subject = "Your Export is ready"
|
||||
html_content = render_to_string("emails/exports/analytics.html", {})
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
|
||||
csv_buffer.seek(0)
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@ from django.template.loader import render_to_string
|
||||
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import EmailNotificationLog, Issue, User
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.settings.redis import redis_instance
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ def send_email_notification(issue_id, notification_data, receiver_id, email_noti
|
||||
"entity_type": "issue",
|
||||
}
|
||||
html_content = render_to_string("emails/notifications/issue-updates.html", context)
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
|
||||
try:
|
||||
connection = get_connection(
|
||||
|
||||
@@ -12,10 +12,10 @@ from celery import shared_task
|
||||
# Third party imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
# Module imports
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ def forgot_password(first_name, email, uidb64, token, current_site):
|
||||
|
||||
html_content = render_to_string("emails/auth/forgot_password.html", context)
|
||||
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
|
||||
connection = get_connection(
|
||||
host=EMAIL_HOST,
|
||||
|
||||
@@ -12,10 +12,10 @@ from celery import shared_task
|
||||
# Third party imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
# Module imports
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ def magic_link(email, key, token):
|
||||
context = {"code": token, "email": email}
|
||||
|
||||
html_content = render_to_string("emails/auth/magic_signin.html", context)
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
|
||||
connection = get_connection(
|
||||
host=EMAIL_HOST,
|
||||
|
||||
@@ -11,11 +11,11 @@ from celery import shared_task
|
||||
# Third party imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
|
||||
# Module imports
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
from plane.db.models import ProjectMember
|
||||
from plane.db.models import User
|
||||
@@ -59,7 +59,7 @@ def project_add_user_email(current_site, project_member_id, invitor_id):
|
||||
|
||||
# Render the email template
|
||||
html_content = render_to_string("emails/notifications/project_addition.html", context)
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
# Initialize the connection
|
||||
connection = get_connection(
|
||||
host=EMAIL_HOST,
|
||||
|
||||
@@ -12,11 +12,11 @@ from celery import shared_task
|
||||
# Third party imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Project, ProjectMemberInvite, User
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ def project_invitation(email, project_id, token, current_site, invitor):
|
||||
|
||||
html_content = render_to_string("emails/invitations/project_invitation.html", context)
|
||||
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
|
||||
project_member_invite.message = text_content
|
||||
project_member_invite.save()
|
||||
|
||||
@@ -8,7 +8,6 @@ import logging
|
||||
# Django imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
# Third party imports
|
||||
from celery import shared_task
|
||||
@@ -16,6 +15,7 @@ from celery import shared_task
|
||||
# Module imports
|
||||
from plane.db.models import User
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ def user_activation_email(current_site, user_id):
|
||||
# Send email to user
|
||||
html_content = render_to_string("emails/user/user_activation.html", context)
|
||||
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
# Configure email connection from the database
|
||||
(
|
||||
EMAIL_HOST,
|
||||
|
||||
@@ -8,7 +8,6 @@ import logging
|
||||
# Django imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
# Third party imports
|
||||
from celery import shared_task
|
||||
@@ -16,6 +15,7 @@ from celery import shared_task
|
||||
# Module imports
|
||||
from plane.db.models import User
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ def user_deactivation_email(current_site, user_id):
|
||||
# Send email to user
|
||||
html_content = render_to_string("emails/user/user_deactivation.html", context)
|
||||
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
# Configure email connection from the database
|
||||
(
|
||||
EMAIL_HOST,
|
||||
|
||||
@@ -11,10 +11,10 @@ from celery import shared_task
|
||||
# Django imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
# Module imports
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ def send_email_update_magic_code(email, token):
|
||||
context = {"code": token, "email": email}
|
||||
|
||||
html_content = render_to_string("emails/auth/magic_signin.html", context)
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
|
||||
connection = get_connection(
|
||||
host=EMAIL_HOST,
|
||||
@@ -87,7 +87,7 @@ def send_email_update_confirmation(email):
|
||||
context = {"email": email}
|
||||
|
||||
html_content = render_to_string("emails/user/email_updated.html", context)
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
|
||||
connection = get_connection(
|
||||
host=EMAIL_HOST,
|
||||
|
||||
@@ -20,7 +20,6 @@ from django.db.models import Prefetch
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
# Module imports
|
||||
@@ -51,6 +50,7 @@ from plane.db.models import (
|
||||
IssueAssignee,
|
||||
)
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
from plane.settings.mongo import MongoConnection
|
||||
|
||||
@@ -222,7 +222,7 @@ def send_webhook_deactivation_email(webhook_id: str, receiver_id: str, current_s
|
||||
"webhook_url": f"{current_site}/{str(webhook.workspace.slug)}/settings/webhooks/{str(webhook.id)}",
|
||||
}
|
||||
html_content = render_to_string("emails/notifications/webhook-deactivate.html", context)
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
|
||||
# Set the email connection
|
||||
connection = get_connection(
|
||||
|
||||
@@ -11,11 +11,11 @@ from celery import shared_task
|
||||
# Django imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import User, Workspace, WorkspaceMemberInvite
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.email import generate_plain_text_from_html
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ def workspace_invitation(email, workspace_id, token, current_site, inviter):
|
||||
|
||||
html_content = render_to_string("emails/invitations/workspace_invitation.html", context)
|
||||
|
||||
text_content = strip_tags(html_content)
|
||||
text_content = generate_plain_text_from_html(html_content)
|
||||
|
||||
workspace_member_invite.message = text_content
|
||||
workspace_member_invite.save()
|
||||
|
||||
42
apps/api/plane/utils/email.py
Normal file
42
apps/api/plane/utils/email.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# SPDX-FileCopyrightText: 2023-present Plane Software, Inc.
|
||||
# SPDX-License-Identifier: LicenseRef-Plane-Commercial
|
||||
#
|
||||
# Licensed under the Plane Commercial License (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
# https://plane.so/legals/eula
|
||||
#
|
||||
# DO NOT remove or modify this notice.
|
||||
# NOTICE: Proprietary and confidential. Unauthorized use or distribution is prohibited.
|
||||
|
||||
# Python imports
|
||||
import re
|
||||
|
||||
# Django imports
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
|
||||
def generate_plain_text_from_html(html_content):
|
||||
"""
|
||||
Generate clean plain text from HTML email template.
|
||||
Removes all HTML tags, CSS styles, and excessive whitespace.
|
||||
|
||||
Args:
|
||||
html_content (str): The HTML content to convert to plain text
|
||||
|
||||
Returns:
|
||||
str: Clean plain text without HTML tags, styles, or excessive whitespace
|
||||
"""
|
||||
# Remove style tags and their content
|
||||
html_content = re.sub(r"<style[^>]*>.*?</style>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
|
||||
|
||||
# Strip HTML tags
|
||||
text_content = strip_tags(html_content)
|
||||
|
||||
# Remove excessive empty lines
|
||||
text_content = re.sub(r"\n\s*\n\s*\n+", "\n\n", text_content)
|
||||
|
||||
# Ensure there's a leading and trailing whitespace
|
||||
text_content = "\n\n" + text_content.lstrip().rstrip() + "\n\n"
|
||||
|
||||
return text_content
|
||||
Reference in New Issue
Block a user