null

Open edX: отправка писем через django manage.py

Примеры приведены для платформы edX версии Maple.

Для отправки письма мы хотим использовать готовый шаблон.
Шаблоны писем размазаны по всему проекту, что, конечно, очень удобно. Тем не менее, их можно найти:

cd edx-platform
find . -type d -name email

Вот несколько примеров:

  • ./common/templates/student/edx_ace/accountactivation/email
  • ./lms/templates/instructor/edx_ace/allowedenroll/email
  • ./openedx/core/djangoapps/user_authn/templates/user_authn/edx_ace/passwordreset/email

Остановимся на первом из них - accountactivation - и рассмотрим как его отправить. Дальнейшие действия мы будем выполнять в интерактивной оболочке django:

# cd edx-platform
./manage.py lms shell

Нужно импортировать класс, соответствующий письму активации. Его определение располагается в файле message_types.py:

from common.djangoapps.student.message_types import AccountActivation

Здесь хочется обратить внимание на два момента, которые позволят лучше понять, что происходит:

  • Определение класса "живёт" в том же приложении, что и шаблон письма. В данном случае, в приложении student.
  • Имя поддиректории accountactivation совпадает с именем класса в нижнем регистре.

Итак, мы разобрались с шаблоном письма. Далее нам потребуется адресат:

from django.contrib.auth.models import User
from edx_ace.recipient import Recipient

user = User.objects.get(email='test@example.com')
recipient = Recipient(user, user.email)

Теперь мы можем подготовить контекст. Контекст представляет собой словарь данных (dict), значения из которого подставляются в шаблон, а на выходе мы получаем готовый текст.

Описать контекст можно полностью вручную или воспользоваться вспомогательной функцией, при наличии. Для письма регистрации такая функция есть и мы ей воспользуемся.

На вход функция принимает два объекта: User и Registration. Пользователя мы уже получили ранее, второй объект получить просто:

registration = user.registration

А теперь подробней рассмотрим определение функции:

def generate_activation_email_context(user, registration):
    """
    Constructs a dictionary for use in activation email contexts

    Arguments:
        user (User): Currently logged-in user
        registration (Registration): Registration object for the currently logged-in user
    """
    context = get_base_template_context(None)
    context.update({
        'name': user.profile.name,
        'key': registration.activation_key,
        'lms_url': configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL),
        'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
        'contact_mailing_address': configuration_helpers.get_value(
            'contact_mailing_address',
            settings.CONTACT_MAILING_ADDRESS
        ),
        'support_url': configuration_helpers.get_value(
            'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK
        ) or settings.SUPPORT_SITE_LINK,
        'support_email': configuration_helpers.get_value('CONTACT_EMAIL', settings.CONTACT_EMAIL),
        'site_configuration_values': configuration_helpers.get_current_site_configuration_values(),
    })
    return context

Невооруженным взглядом можно заметить, что контекст зависит от конфигурации сайта, а та в свою очередь зависит от домена, на который поступил HTTP-запрос.

— Запрос? Какой запрос?

Верно подмечено! Мы работаем в интерактивной оболочке, поэтому запрос придётся эмулировать:

from openedx.core.lib.celery.task_utils import emulate_http_request
from django.contrib.sites.models import Site

from edx_django_utils.cache import RequestCache
RequestCache(namespace="site_config").clear()

site = Site.objects.get(domain='education.example.com')
with emulate_http_request(site=site, user=user):
    context = generate_activation_email_context(user, registration)
    ...

Здесь можно заметить вызов метода clear() у объекта RequestCache, что связано с особенностями реализации. Если этого не сделать, то конфигурация сайта не применится.

Остаётся лишь сформировать и отправить письмо внутри того же блока with ... для работы встроенной аналитики:

    ...
    from edx_ace import ace

    msg = AccountActivation().personalize(
        recipient=recipient,
        language='RU',
        user_context=context,
    )
    ace.send(msg)

Всё, можно ловить письмо!

Ну и напоследок соберём весь код вместе:

from django.contrib.auth.models import User
from django.contrib.sites.models import Site

from edx_ace import ace
from edx_ace.recipient import Recipient
from edx_django_utils.cache import RequestCache

from common.djangoapps.student.email_helpers import generate_activation_email_context
from common.djangoapps.student.message_types import AccountActivation
from openedx.core.lib.celery.task_utils import emulate_http_request

user = User.objects.get(email='test@example.com')
recipient = Recipient(user, user.email)
registration = user.registration

RequestCache(namespace="site_config").clear()

site = Site.objects.get(domain='education.example.com')
with emulate_http_request(site=site, user=user):
    context = generate_activation_email_context(user, registration)
    msg = AccountActivation().personalize(
        recipient=recipient,
        language='RU',
        user_context=context,
    )
    ace.send(msg)
Вперед