Basic email template for notifications #96
@ -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 %}
|
||||
|
@ -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']
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
45
utils.py
45
utils.py
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user