conference-website/emails/admin.py

191 lines
6.9 KiB
Python

import json
import logging
import os.path
from django.conf import settings
from django.contrib import admin, messages
from django.contrib.auth import get_user_model
from django.template import Template, Context
from django.utils.html import format_html
from django.utils.safestring import mark_safe
import django.core.mail
from emails.models import Email
from emails.util import construct_email, get_template_context
import conference_main.admin_mixins
import conference_main.models
import tickets.models
logger = logging.getLogger(__name__)
User = get_user_model()
current_dir = os.path.dirname(__file__)
fake_order = json.load(open(os.path.join(current_dir, 'fake_order.json')))
@admin.register(Email)
class EmailAdmin(admin.ModelAdmin):
class Media:
css = {'all': (f'{settings.STATIC_URL}/emails/admin/email.css',)}
def rendered_html(self, obj) -> str:
"""Preview the HTML version of the email."""
# Escape " and & to avoid breaking srcdoc. Order of escaping is important.
body = obj.render_html().replace('&', '&').replace('"', '"')
context = Context({'body': body})
iframe_template = Template(
'<iframe sandbox style="width: 100%; height: 100vh; min-width: 60vw;"'
' srcdoc="{{ body|safe }}"></iframe>'
)
rendered: str = iframe_template.render(context)
return mark_safe(rendered)
rendered_html.allow_tags = True
rendered_html.short_description = "Preview"
def was_sent(self, obj):
"""Display yes/no icon sent status."""
return obj.date_sent is not None
was_sent.boolean = True
list_display = ['subject', 'from_email', 'to', 'was_sent']
list_filter = ['reply_to', 'base_html_template', 'date_sent']
readonly_fields = ['rendered_html', 'date_sent']
actions = ['send']
search_fields = ['to', 'subject']
def send(self, request, queryset):
"""Send an email. This is a custom admin action."""
for email in queryset:
try:
email.send()
msg = f'Message "{email.subject}" sent to {email.to}'
self.message_user(request, msg, messages.SUCCESS)
except Exception as e:
self.message_user(request, str(e), messages.ERROR)
send.short_description = "Send selected emails"
class EmailPreview(Email):
class Meta:
proxy = True
managed = False
def render_html(self):
"""Return already rendered HTML of the email."""
return self.html_message
@admin.register(EmailPreview)
class EmailPreviewAdmin(conference_main.admin_mixins.NoAddDeleteMixin, EmailAdmin):
save_as_continue = True
save_on_top = True
list_display = ['subject', 'from_email']
actions = []
def _get_email_sent_message(self, obj):
return f'Sent a test email "{obj.subject}" to {obj.to} from {obj.from_email}'
def get_object(self, request, object_id, from_field=None):
"""Construct the Email on th fly from known subscription email templates."""
user = User(profile=conference_main.models.Profile(full_name='John Doe'))
ticket = tickets.models.Ticket(
id=1234567890,
user=user,
quantity=2,
order_id='fake-order-id==',
order_number='fake-ticket-order-number',
order_token='fake-order-token-foobar',
edition=conference_main.models.Edition.objects.order_by('-id').first(),
is_paid=True,
)
context = {
'user': user,
'ticket': ticket,
'order': fake_order,
**get_template_context(),
}
mail_name = object_id
email_body_html, email_body_txt, subject = construct_email(mail_name, context)
return EmailPreview(
id=mail_name,
subject=subject,
from_email=settings.DEFAULT_FROM_EMAIL,
reply_to=settings.DEFAULT_REPLY_TO_EMAIL,
html_message=email_body_html,
message=email_body_txt,
)
def _get_emails_list(self, request):
emails = []
for mail_name in (
'bank_transfer_required',
'tickets_paid',
'ticket_confirmed',
'general_information',
'unclaimed_reminder',
'unpaid_reminder',
'badge_reminder',
'one_day_ticket_confirm',
'feedback',
):
emails.append(self.get_object(request, object_id=mail_name))
return emails
def _changeform_view(self, request, object_id, form_url, extra_context):
"""Change title of the change view to make it clear it's not actually changing anything."""
template_name = object_id.replace('_5F', '_')
extra_context = {
**(extra_context or {}),
'title': format_html(
f'Previewing email rendered from the "<b>{template_name}</b>" template.'
'<br/>You can adjust the content of the email before sending it, '
'but these changes will not be persisted.'
),
}
return super()._changeform_view(request, object_id, form_url, extra_context=extra_context)
def save_model(self, request, obj, form, change):
"""Send an email instead, display a notification about it."""
for recipient in obj.to.split(','):
django.core.mail.send_mail(
obj.subject,
message=obj.message,
html_message=obj.html_message,
from_email=obj.from_email, # just use the configured default From-address.
recipient_list=[recipient.strip()],
fail_silently=False,
)
def construct_change_message(self, request, form, formsets, add=False):
"""Log the fact that an email was sent."""
obj = form.instance
return self._get_email_sent_message(obj)
def response_change(self, request, obj):
"""Override notification messages on change. Notify about successfully sent test email."""
response = super().response_change(request, obj)
# Clear the usual change messages: nothing was actually changed/saved, so they are confusing
list(messages.get_messages(request))
message = self._get_email_sent_message(obj)
self.message_user(request, message, messages.INFO)
return response
def get_changelist_instance(self, request):
"""Monkey-patch changelist replacing the queryset with the existing transactional emails."""
try:
cl = super().get_changelist_instance(request)
emails = self._get_emails_list(request)
cl.result_list = emails
cl.result_count = len(emails)
cl.can_show_all = True
cl.multi_page = False
cl.title = 'Select an email to preview it'
return cl
except Exception as e:
logger.exception(e)
raise