Рубрики
Без рубрики

Django Transactional электронные письма просты

Как немного инкапсулирования и оснастки могут сделать транзакционные электронные письма намного менее болезненным для развития. Помечено Джанго, Python, производительность, Showdev.

Трансуляционные электронные письма являются важным способом сохранения пользователей, когда вы строите общественный сайт, как Фактеруид Отказ Это импорт, чтобы обратить внимание на детали, чтобы пользователи могли нажать на них и возвращаться.

К сожалению, разработка работа по электронной почте немного рутина. Вы должны либо засорять ваш почтовый ящик с помощью тестовых электронных писем для тестирования каждого небольшого изменения или в лучшем случае настроить еще одно неловкое решение, чтобы открыть их в браузере. ограничительные требования к HTML больно работать, особенно необходимость использования встроенных стилей.

Я построил какую-то простую инфраструктуру, чтобы сделать рабочий процесс разработки электронной почты намного проще. Я построил Фактеруид В Джангу, но основные идеи должны обратиться к любой платформе.

Основная настройка

Отправка отформатированной электронной почты означает генерацию трех строк: тема, тело HTML и простой текстовый корпус. Наиболее очевидный способ сделать это – выйти на каждую часть в своем собственном шаблоне (скажем, Woment_email_subject.txt, welcent_email_plain.txt, wellow_email_html.txt) и использование Render_to_String. Это может потребоваться немного раздражать для управления, потому что у вас есть три отдельных файла для редактирования и сохранения синхронизации.

Почему бы не использовать Блоки ? Django-Render-Block Пакет идеально подходит для этого. Это дает вам Render_block_to_string Функция, которая работает так же, как Render_to_String, но дает вам содержимое определенного блока. Все, что вам нужно сделать сейчас, определяют один шаблон с отдельными блоками для трех частей.

Давайте инкапсулируем, что в классе

class Email:
    template = "emails/base.html"
    from_email = settings.DEFAULT_FROM_EMAIL

    def __init__(self, ctx, to_email):
        self.to_email = to_email
        self.subject = render_block_to_string(self.template, 'subject', ctx)
        self.plain = render_block_to_string(self.template, 'plain', ctx)
        self.body = render_block_to_string(self.template, 'html', ctx)

    def send(self):
        send_mail(self.subject, self.plain, self.from_email, 
                [self.to_email],  html_message=self.html)
{% block subject %}Welcome to Facteroid{% endblock %}

{% block plain %}Hi,
This is an example email.

Thanks,
- The Facteroid Team
{% endblock %}

{% block html %}

Hi,

This is an example email.

Thanks,

The Facteroid Team

{% endblock %}

Все, что нам нужно сделать сейчас, это создать шаблон, который расширяет base.html и переопределить Шаблон В детском классе Электронное письмо И мы на нашем пути. Конечно, HTML и тело для этого, скорее всего, будет намного больше, чем это, с большим количеством котельной. На самом деле, стандартный способ структурирования HTML электронных писем включает в себя Две вложенные столы Со всем контентом, входящим в одну ячейку внутреннего стола. Было бы довольно расточительно, чтобы продолжать повторять это. Конечно, мы можем приятно использовать наследство шаблона, чтобы избежать этого.

{% block subject %}{% endblock %}

{% block plain %}{% endblock %}

{% block html %}

  
  
  
    
Facteroid
{% block html_main %} {% endblock %}

You received this email because you signed up for an Facteroid account with this email. If you do not want to to hear from us, please reply to let us know.

{% endblock %}

Вы все еще можете наследовать от Base.html, но вам не нужно переопределить все HTML Большую часть времени вы можете просто переопределить html_main Блок и напишите намного более простой документ.

Хотя мы высыхаем вещи, почему бы не генерировать простое тело от HTML-адреса? Мы всегда можем указать один вручную для самых важных электронных писем, но сгенерированное тело должно быть достаточно хорошо в большинстве случаев. HTML2Text в помощь.

class Email:
    template = "emails/base.html"
    from_email = settings.DEFAULT_FROM_EMAIL

    def __init__(self, ctx, to_email):
        self.to_email = to_email
        self.subject = render_block_to_string(self.template, 'subject', ctx)
        self.plain = render_block_to_string(self.template, 'plain', ctx)
        self.body = render_block_to_string(self.template, 'html', ctx)

        if self.plain == "":
            h = HTML2Text()
            h.ignore_images = True
            h.ignore_emphasis = True
            h.ignore_tables = True
            self.plain = h.handle(self.html)

    def send(self):
        send_mail(self.subject, self.plain, self.from_email, 
                [self.to_email],  html_message=self.html)

Это довольно распространено для электронного письма, чтобы взять Пользователь Объект и представить некоторую информацию от связанных объектов. Вы также хотите облегчить пользователям отписаться от конкретных типов электронных писем. Те, кто обрабатывается с логическими полями в пользователе Профиль Отказ Давайте инкапсулируем это.

class UserEmail(Email):
    unsubscribe_field = None

    def __init__(self, ctx, user):
        if self.unsubscribe_field is None:
            raise ProgrammingError("Derived class must set unsubscribe_field")

        self.user = user

        ctx = { 
            'user': user, 
            'unsubscribe_link': user.profile.unsubscribe_link(self.unsubscribe_field)
            **ctx 
        }

        super().__init__(ctx, user.email)

    def send(self):
        if getattr(self.user.profile, self.unsubscribe_field):
            super().send()


class NotificationEmail(UserEmail):
    template = 'emails/notification_email.html'
    unsubscribe_field = 'notification_emails'

    def __init__(self, user):
        ctx = { 'notifications': user.notifications.filter(seen=False) }
        super().__init__(ctx, user)

Просмотр писем в браузере

Теперь довольно легко написать представление для предварительного просмотра электронной почты в браузере с очень небольшими усилиями.

# user_emails/views.py

@staff_member_required
def preview_email(request):
    email_types = {
        'email_confirmation': ConfirmationEmail,
        'notification': NotificationEmail,
        'welcome': WelcomeEmail,
    }
    email_type = request.GET.get('type')
    if email_type not in email_types:
        return HttpResponseBadRequest("Invalid email type")

    if 'user_id' in request.GET:
        user = get_object_or_404(User, request.GET['user_id'])
    else:
        user = request.user

    email = email_types[email_type](user)

    if request.GET.get('plain'):
        text = "Subject: %s\n\n%s" % (email.subject, email.plain)
        return HttpResponse(text, content_type='text/plain')
    else:
        # Insert a table with metadata like Subject, To etc. to top of body
        extra = render_to_string('user_email/metadata.html', {'email': email})
        soup = BeautifulSoup(email.html)
        soup.body.insert(0, BeautifulSoup(extra))

        return HttpResponse(soup.encode())

Теперь я могу легко просмотреть электронные письма в браузере. Я также использую Django-Live-Reload В разработке, поэтому я могу редактировать шаблонные файлы и код и мгновенно видеть эффект в окне браузера без необходимости перезагрузки страницы.

Держать Sane CSS

Еще одна вещь, которая делает разработку HTML Emails больно, это необходимость использования встроенных стилей. В отличие от обычных веб-страниц, вы не можете просто иметь блок стиля и полагаться на агентов пользователей, чтобы правильно сделать их. Вам действительно нужно поставить Стиль = "..." Атрибуты на каждом элементе, который вы хотите стиль, что делает самую простой вещь, как «сделать все мои ссылки светло-синие и удалить подчеркивание» довольно болезненно.

Я сделал это легче с небольшим количеством Пользовательский шаблон тег который читает стили из внешнего файла и выплевывает Стиль атрибут. Я мог бы просто определить стили в словаре, но с небольшим количеством помощи Cssutils Может держать его в файле .css, который делает его хорошо играть с редакторами кода поэтому я получаю правильный синтаксис, автозаполнение и т. Д.

# user_email/templatetags/email_tags.py

_styles = None


@register.simple_tag()
def style(names):
    global _styles
    if _styles is None or settings.DEBUG:
        _load_styles()

    style = ';'.join(_styles.get(name, '') for name in names.split())
    return mark_safe(% style)


def _load_styles():
    global _styles
    _styles = {}

    sheet = cssutils.parseFile(finders.find('user_email/email_styles.css'))
    for rule in sheet.cssRules:
        for selector in rule.selectorList:
            _styles[selector.selectorText] = rule.style.cssText

Теперь в моих HTML-файлах все, что мне нужно сделать, это это:

{% extends 'email/base.html' %}
{% load email_tags %}
{% block html_main %}

Hello {{user.profile.display_name}},

Thanks for signing up! Here's an article to help you get started.

GET STARTED

{% endblock %}

Вывод

Я выделил некоторый из этого примера, чтобы сделать его проще, но у меня есть все виды дополнительных функциональных возможностей, которые можно легко добавлять с помощью этой базы.

  • Email Базовый класс анализирует электронную почту и автоматически добавляет базовый URL к любым относительным ссылкам.
  • Отправить Функция записывает журнал отправленных электронных писем в базе данных и звонит After_send Метод, если он существует. After_send Функция в некоторых классах электронной почты занимается домашними задачами, как запись, какие уведомления уже были отправлены пользователю.
  • Функция My View немного сложнее, поэтому я могу просмотреть электронные письма, которые принимают больше, чем просто пользователь (но он строит по той же идее).

Я приветствую любые комментарии, предложения или вопросы!

Оригинал: “https://dev.to/vinaypai/django-transactional-emails-made-easy-1pf1”