Basic email template for notifications #96

Merged
Anna Sirota merged 14 commits from notification-templates into main 2024-05-02 14:04:24 +02:00
4 changed files with 55 additions and 31 deletions
Showing only changes of commit 503d237a15 - Show all commits

View File

@ -8,12 +8,10 @@
</p> </p>
{% endif %} {% endif %}
</div> </div>
<p><a href="{{ url }}">{{ url }}</a></p> <p><a href="{{ url }}">View it at Blender Extensions</a></p>
<p> <p>
Read all notifications at {{ site_url }}{% url "notifications:notifications" %} Read all notifications at {{ notifications_url }}
</p> </p>
{# TODO: store follow flags on Notifications, otherwise it's impossible to tell why this email is sent #} {# TODO: store follow flags on Notifications, otherwise it's impossible to tell why this email is sent #}
{% comment %} {% comment %}
You are receiving this email because you are a moderator subscribed to notification emails. You are receiving this email because you are a moderator subscribed to notification emails.
@ -22,5 +20,5 @@
{% endblock content %} {% endblock content %}
{% block footer_links %} {% block footer_links %}
Unsubscribe by adjusting your preferences at {{ site_url }}{% url "users:my-profile" %} Unsubscribe by adjusting your preferences at {{ profile_url }}
{% endblock footer_links %} {% endblock footer_links %}

View File

@ -1,36 +1,17 @@
"""Utilities for rendering email templates.""" """Utilities for rendering email templates."""
from typing import List, Optional, Tuple, Dict, Any from typing import List, Tuple, Dict, Any
import logging import logging
from django.conf import settings from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import get_connection, EmailMultiAlternatives from django.core.mail import get_connection, EmailMultiAlternatives
from django.template import loader, TemplateDoesNotExist from django.template import loader, TemplateDoesNotExist
from django.urls import reverse
import html2text from utils import absolute_url, html_to_text
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
def _get_site_url():
domain = get_current_site(None).domain
return f'https://{domain}'
def absolute_url(
view_name: str, args: Optional[tuple] = None, kwargs: Optional[dict] = None
) -> str:
"""Same as django.urls.reverse() but then as absolute URL.
For simplicity this assumes HTTPS is used.
"""
from urllib.parse import urljoin
relative_url = reverse(view_name, args=args, kwargs=kwargs)
return urljoin(_get_site_url(), relative_url)
def is_noreply(email: str) -> bool: def is_noreply(email: str) -> bool:
"""Return True if the email address is a no-reply address.""" """Return True if the email address is a no-reply address."""
return email.startswith('noreply@') or email.startswith('no-reply@') return email.startswith('noreply@') or email.startswith('no-reply@')
@ -39,8 +20,9 @@ def is_noreply(email: str) -> bool:
def get_template_context() -> Dict[str, str]: def get_template_context() -> Dict[str, str]:
"""Return additional context for use in an email template.""" """Return additional context for use in an email template."""
return { return {
'site_url': _get_site_url(), 'site_url': absolute_url('extensions:home'),
# 'profile_url': absolute_url('profile_update'), 'profile_url': absolute_url('users:my-profile'),
'notifications_url': absolute_url('notifications:notifications'),
'DEFAULT_REPLY_TO_EMAIL': settings.DEFAULT_REPLY_TO_EMAIL, 'DEFAULT_REPLY_TO_EMAIL': settings.DEFAULT_REPLY_TO_EMAIL,
} }
@ -68,7 +50,7 @@ def construct_email(email_name: str, context: Dict[str, Any]) -> Tuple[str, str,
email_body_txt = loader.render_to_string(txt_tmpl, context) email_body_txt = loader.render_to_string(txt_tmpl, context)
except TemplateDoesNotExist: except TemplateDoesNotExist:
# Generate plain text content from the HTML one # Generate plain text content from the HTML one
email_body_txt = html2text.html2text(email_body_html) email_body_txt = html_to_text(email_body_html)
return email_body_html, email_body_txt, context['subject'] return email_body_html, email_body_txt, context['subject']

View File

@ -28,7 +28,6 @@ drf-spectacular-sidecar==2024.2.1
frozenlist==1.3.0 frozenlist==1.3.0
geoip2==4.6.0 geoip2==4.6.0
h11==0.13.0 h11==0.13.0
html2text==2024.2.26
idna==3.3 idna==3.3
Jinja2==3.1.2 Jinja2==3.1.2
jsmin==3.0.1 jsmin==3.0.1

View File

@ -1,3 +1,4 @@
from html.parser import HTMLParser
from typing import Optional from typing import Optional
from urllib.parse import urljoin from urllib.parse import urljoin
import datetime import datetime
@ -20,6 +21,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import validate_ipv46_address from django.core.validators import validate_ipv46_address
from django.http import HttpRequest from django.http import HttpRequest
from django.http.response import HttpResponseRedirectBase from django.http.response import HttpResponseRedirectBase
from django.urls import reverse
from django.utils.encoding import force_bytes, force_str from django.utils.encoding import force_bytes, force_str
from django.utils.http import _urlparse from django.utils.http import _urlparse
import django.utils.text import django.utils.text
@ -189,3 +191,46 @@ def absolutify(url: str, request=None) -> str:
proto = 'http' if settings.DEBUG else 'https' proto = 'http' if settings.DEBUG else 'https'
domain = get_current_site(request).domain domain = get_current_site(request).domain
return urljoin(f'{proto}://{domain}', url) return urljoin(f'{proto}://{domain}', url)
def absolute_url(
view_name: str, args: Optional[tuple] = None, kwargs: Optional[dict] = None
) -> str:
"""Same as django.urls.reverse() but then as absolute URL."""
relative_url = reverse(view_name, args=args, kwargs=kwargs)
return absolutify(relative_url)
class HTMLFilter(HTMLParser):
text = ''
skip = False
skip_one = False
def handle_starttag(self, tag, attrs):
if tag == 'style':
self.skip = True
for name, value in attrs:
if name == 'href':
self.skip_one = True
self.text += value
def handle_endtag(self, tag):
if tag == 'style':
self.skip = False
def handle_data(self, data):
if self.skip:
return
if self.skip_one:
self.skip_one = False
return
data = data.strip()
self.text += data
if not data.endswith('\n') and len(data) > 1:
self.text += '\n'
def html_to_text(data: str) -> str:
f = HTMLFilter()
f.feed(data)
return f.text