support theming of ACE emails

parent d037a19c
......@@ -22,6 +22,7 @@ from openedx.core.djangoapps.schedules import resolvers, tasks
from openedx.core.djangoapps.schedules.resolvers import _get_datetime_beginning_of_day
from openedx.core.djangoapps.schedules.tests.factories import ScheduleConfigFactory, ScheduleFactory
from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory, SiteFactory
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, FilteredQueryCountMixin
from student.models import CourseEnrollment
......@@ -38,6 +39,11 @@ ORG_DEADLINE_QUERY = 1 # courseware_orgdynamicupgradedeadlineconfiguration
COURSE_DEADLINE_QUERY = 1 # courseware_coursedynamicupgradedeadlineconfiguration
COMMERCE_CONFIG_QUERY = 1 # commerce_commerceconfiguration
USER_QUERY = 1
THEME_PREVIEW_QUERY = 1
THEME_QUERY = 1
SCHEDULE_CONFIG_QUERY = 1
NUM_QUERIES_SITE_SCHEDULES = (
SITE_QUERY +
SITE_CONFIG_QUERY +
......@@ -52,6 +58,14 @@ NUM_QUERIES_FIRST_MATCH = (
+ COMMERCE_CONFIG_QUERY
)
NUM_QUERIES_PER_MESSAGE_DELIVERY = (
SITE_QUERY +
SCHEDULE_CONFIG_QUERY +
USER_QUERY +
THEME_PREVIEW_QUERY +
THEME_QUERY
)
LOG = logging.getLogger(__name__)
......@@ -219,10 +233,12 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
@patch.object(tasks, 'ace')
@patch.object(tasks, 'Message')
def test_deliver_config(self, is_enabled, mock_message, mock_ace):
user = UserFactory.create()
schedule_config_kwargs = {
'site': self.site_config.site,
self.deliver_config: is_enabled,
}
mock_message.from_string.return_value.recipient.username = user.username
ScheduleConfigFactory.create(**schedule_config_kwargs)
mock_msg = Mock()
......@@ -383,7 +399,7 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
num_expected_messages = 1 if self.consolidates_emails_for_learner else message_count
self.assertEqual(len(sent_messages), num_expected_messages)
with self.assertNumQueries(2):
with self.assertNumQueries(NUM_QUERIES_PER_MESSAGE_DELIVERY):
self.deliver_task(*sent_messages[0])
self.assertEqual(mock_channel.deliver.call_count, 1)
......@@ -393,6 +409,8 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
self.assertNotIn("{{", template)
self.assertNotIn("}}", template)
return mock_channel.deliver.mock_calls
def _check_if_email_sent_for_experience(self, test_config):
current_day, offset, target_day, _ = self._get_dates(offset=test_config.offset)
......@@ -412,3 +430,10 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
))
self.assertEqual(mock_ace.send.called, test_config.email_sent)
@with_comprehensive_theme('red-theme')
def test_templates_with_theme(self):
calls_to_deliver = self._assert_template_for_offset(self.expected_offsets[0], 1)
_name, (_msg, email), _kwargs = calls_to_deliver[0]
self.assertIn('TEST RED THEME MARKER', email.body_html)
......@@ -2,7 +2,9 @@ import datetime
import logging
from celery.task import task, Task
from crum import CurrentRequestUserMiddleware
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.exceptions import ValidationError
......@@ -17,7 +19,8 @@ from openedx.core.djangoapps.monitoring_utils import set_custom_metric
from openedx.core.djangoapps.schedules import message_types
from openedx.core.djangoapps.schedules.models import Schedule, ScheduleConfig
from openedx.core.djangoapps.schedules import resolvers
from openedx.core.djangoapps.theming.middleware import CurrentSiteThemeMiddleware
from openedx.core.lib.celery.task_utils import emulate_http_request
LOG = logging.getLogger(__name__)
......@@ -177,15 +180,22 @@ class ScheduleCourseUpdate(ScheduleMessageBaseTask):
def _schedule_send(msg_str, site_id, delivery_config_var, log_prefix):
if _is_delivery_enabled(site_id, delivery_config_var, log_prefix):
site = Site.objects.get(pk=site_id)
if _is_delivery_enabled(site, delivery_config_var, log_prefix):
msg = Message.from_string(msg_str)
_annonate_send_task_for_monitoring(msg)
LOG.debug('%s: Sending message = %s', log_prefix, msg_str)
ace.send(msg)
user = User.objects.get(username=msg.recipient.username)
middleware_classes = [
CurrentRequestUserMiddleware,
CurrentSiteThemeMiddleware,
]
with emulate_http_request(site=site, user=user, middleware_classes=middleware_classes):
_annonate_send_task_for_monitoring(msg)
LOG.debug('%s: Sending message = %s', log_prefix, msg_str)
ace.send(msg)
def _is_delivery_enabled(site_id, delivery_config_var, log_prefix):
site = Site.objects.get(pk=site_id)
def _is_delivery_enabled(site, delivery_config_var, log_prefix):
if getattr(ScheduleConfig.current(site), delivery_config_var, False):
return True
else:
......
from contextlib import contextmanager
from django.http import HttpRequest, HttpResponse
@contextmanager
def emulate_http_request(site=None, user=None, middleware_classes=None):
"""
Generate a fake HTTP request and run selected middleware on it.
This is used to enable features that assume they are running as part of an HTTP request handler. Many of these
features retrieve the "current" request from a thread local managed by crum. They will make a call like
crum.get_current_request() or something similar.
Since some tasks are kicked off by a management commands (which does not have an HTTP request) and then executed
in celery workers there is no "current HTTP request". Instead we just populate the global state that is most
commonly used on request objects.
Arguments:
site (Site): The site that this request should emulate. Defaults to None.
user (User): The user that initiated this fake request. Defaults to None
middleware_classes (list): A list of classes that implement Django's middleware interface.
"""
request = HttpRequest()
request.user = user
request.site = site
middleware_classes = middleware_classes or []
middleware_instances = [klass() for klass in middleware_classes]
response = HttpResponse()
for middleware in middleware_instances:
_run_method_if_implemented(middleware, 'process_request', request)
try:
yield
except Exception as exc:
for middleware in reversed(middleware_instances):
_run_method_if_implemented(middleware, 'process_exception', request, exc)
else:
for middleware in reversed(middleware_instances):
_run_method_if_implemented(middleware, 'process_response', request, response)
def _run_method_if_implemented(instance, method_name, *args, **kwargs):
if hasattr(instance, method_name):
return getattr(instance, method_name)(*args, **kwargs)
else:
return None
{% load i18n %}
<p>
{# email client support for style sheets is pretty spotty, so we have to inline all of these styles #}
<a
{% if course_ids|length > 1 %}
href="{{ dashboard_url }}"
{% else %}
href="{{ course_url }}"
{% endif %}
style="
color: #ffffff;
text-decoration: none;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
background-color: #960909;
border-top: 12px solid #960909;
border-bottom: 12px solid #960909;
border-right: 50px solid #960909;
border-left: 50px solid #960909;
display: inline-block;
">
{# old email clients require the use of the font tag :( #}
<font color="#ffffff"><b>{{ course_cta_text }}</b></font>
</a>
</p>
{% load i18n %}
This is the RED theme!
{% if course_ids|length > 1 %}
{% blocktrans trimmed %}
Remember when you enrolled in {{ course_name }}, and other courses on edX.org? We do, and we’re glad
to have you! Come see what everyone is learning.
{% endblocktrans %}
{% trans "Start learning now" %} <{{ dashboard_url }}>
{% else %}
{% blocktrans trimmed %}
Remember when you enrolled in {{ course_name }} on edX.org? We do, and we’re glad
to have you! Come see what everyone is learning.
{% endblocktrans %}
{% trans "Start learning now" %} <{{ course_url }}>
{% endif %}
{% include "schedules/edx_ace/common/upsell_cta.txt"%}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment