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>
{% endif %}
</div>
<p><a href="{{ url }}">{{ url }}</a></p>
<p><a href="{{ url }}">View it at Blender Extensions</a></p>
<p>
Read all notifications at {{ site_url }}{% url "notifications:notifications" %}
Read all notifications at {{ notifications_url }}
</p>
{# TODO: store follow flags on Notifications, otherwise it's impossible to tell why this email is sent #}
{% comment %}
You are receiving this email because you are a moderator subscribed to notification emails.
@ -22,5 +20,5 @@
{% endblock content %}
{% 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 %}

View File

@ -1,36 +1,17 @@
"""Utilities for rendering email templates."""
from typing import List, Optional, Tuple, Dict, Any
from typing import List, Tuple, Dict, Any
import logging
from django.conf import settings
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import get_connection, EmailMultiAlternatives
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.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:
"""Return True if the email address is a no-reply address."""
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]:
"""Return additional context for use in an email template."""
return {
'site_url': _get_site_url(),
# 'profile_url': absolute_url('profile_update'),
'site_url': absolute_url('extensions:home'),
'profile_url': absolute_url('users:my-profile'),
'notifications_url': absolute_url('notifications:notifications'),
'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)
except TemplateDoesNotExist:
# 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']

View File

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

View File

@ -1,3 +1,4 @@
from html.parser import HTMLParser
from typing import Optional
from urllib.parse import urljoin
import datetime
@ -20,6 +21,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import validate_ipv46_address
from django.http import HttpRequest
from django.http.response import HttpResponseRedirectBase
from django.urls import reverse
from django.utils.encoding import force_bytes, force_str
from django.utils.http import _urlparse
import django.utils.text
@ -189,3 +191,46 @@ def absolutify(url: str, request=None) -> str:
proto = 'http' if settings.DEBUG else 'https'
domain = get_current_site(request).domain
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