Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
54ae1c62
Unverified
Commit
54ae1c62
authored
Nov 08, 2017
by
Gabe Mulley
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
support theming of ACE emails
parent
d037a19c
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
334 additions
and
8 deletions
+334
-8
openedx/core/djangoapps/schedules/management/commands/tests/send_email_base.py
+26
-1
openedx/core/djangoapps/schedules/tasks.py
+17
-7
openedx/core/lib/celery/task_utils.py
+49
-0
themes/red-theme/lms/templates/schedules/edx_ace/common/base_body.html
+196
-0
themes/red-theme/lms/templates/schedules/edx_ace/common/return_to_course_cta.html
+27
-0
themes/red-theme/lms/templates/schedules/edx_ace/recurringnudge_day3/email/body.txt
+19
-0
No files found.
openedx/core/djangoapps/schedules/management/commands/tests/send_email_base.py
View file @
54ae1c62
...
...
@@ -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
)
openedx/core/djangoapps/schedules/tasks.py
View file @
54ae1c62
...
...
@@ -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
:
...
...
openedx/core/lib/celery/task_utils.py
0 → 100644
View file @
54ae1c62
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
themes/red-theme/lms/templates/schedules/edx_ace/common/base_body.html
0 → 100644
View file @
54ae1c62
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
{# This is preview text that is visible in the inbox view of many email clients but not visible in the actual #}
{# email itself. #}
<div
lang=
"{{ LANGUAGE_CODE|default:"
en
"
}}"
style=
"
display:none;
font-size:1px;
line-height:1px;
max-height:0px;
max-width:0px;
opacity:0;
overflow:hidden;
visibility:hidden;
"
>
{% block preview_text %}{% endblock %}
</div>
<!-- TEST RED THEME MARKER: Do not remove this comment, it is used by the tests to tell if this theme was used -->
{# Note {beacon_src} is not a template variable that is evaluated by the Django template engine. It is evaluated by #}
{# Sailthru when the email is sent. Other email providers would need to replace this variable in the HTML as well. #}
<img
src=
"{beacon_src}"
alt=
""
role=
"presentation"
aria-hidden=
"true"
/>
<div
bgcolor=
"#f5f5f5"
lang=
"{{ LANGUAGE_CODE|default:"
en
"
}}"
dir=
"{{ LANGUAGE_BIDI|yesno:"
rtl
,
ltr
"
}}"
style=
"
margin: 0;
padding: 0;
min-width: 100%;
"
>
<!-- Hack for outlook 2010, which wants to render everything in Times New Roman -->
<!--[if mso]>
<style type="text/css">
body, table, td {font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif !important;}
</style>
<![endif]-->
<!--[if (gte mso 9)|(IE)]>
<table role="presentation" width="600" align="center" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<![endif]-->
<!-- CONTENT -->
<table
class=
"content"
role=
"presentation"
align=
"center"
cellpadding=
"0"
cellspacing=
"0"
border=
"0"
bgcolor=
"#ffd1d1"
width=
"100%"
style=
"
font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 1em;
line-height: 1.5;
max-width: 600px;
padding: 0 20px 0 20px;
"
>
<tr>
<!-- HEADER -->
<td
class=
"header"
style=
"
padding: 20px;
"
>
<table
role=
"presentation"
width=
"100%"
align=
"left"
border=
"0"
cellpadding=
"0"
cellspacing=
"0"
>
<tr>
<td
width=
"70"
>
<a
href=
"{{ homepage_url }}"
><img
src=
"http://localhost:18000/static/red-theme/images/logo.png"
alt=
"{% blocktrans %}Go to {{ platform_name }} Home Page{% endblocktrans %}"
/></a>
</td>
<td
align=
"right"
style=
"text-align: {{ LANGUAGE_BIDI|yesno:"
left
,
right
"
}};"
>
<a
class=
"login"
href=
"{{ dashboard_url }}"
style=
"color: #960909;"
>
{% trans "Sign In" %}
</a>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<!-- MAIN -->
<td
class=
"main"
bgcolor=
"#ffffff"
style=
"
padding: 30px 20px;
box-shadow: 0 1px 5px rgba(0,0,0,0.25);
"
>
{% block content %}{% endblock %}
</td>
</tr>
<tr>
<!-- FOOTER -->
<td
class=
"footer"
style=
"padding: 20px;"
>
<table
role=
"presentation"
width=
"100%"
align=
"left"
border=
"0"
cellpadding=
"0"
cellspacing=
"0"
>
<tr>
<td
style=
"padding-bottom: 20px;"
>
<!-- SOCIAL -->
<table
role=
"presentation"
align=
"{{ LANGUAGE_BIDI|yesno:"
right
,
left
"
}}"
border=
"0"
border=
"0"
cellpadding=
"0"
cellspacing=
"0"
width=
"210"
>
<tr>
{% if social_media_urls.linkedin %}
<td
height=
"32"
width=
"42"
>
<a
href=
"{{ social_media_urls.linkedin }}"
>
<img
src=
"https://media.sailthru.com/595/1k1/8/o/599f354ec70cb.png"
width=
"32"
height=
"32"
alt=
"{% blocktrans %}{{ platform_name }} on LinkedIn{% endblocktrans %}"
/>
</a>
</td>
{% endif %}
{% if social_media_urls.twitter %}
<td
height=
"32"
width=
"42"
>
<a
href=
"{{ social_media_urls.twitter }}"
>
<img
src=
"https://media.sailthru.com/595/1k1/8/o/599f354d9c26e.png"
width=
"32"
height=
"32"
alt=
"{% blocktrans %}{{ platform_name }} on Twitter{% endblocktrans %}"
/>
</a>
</td>
{% endif %}
{% if social_media_urls.facebook %}
<td
height=
"32"
width=
"42"
>
<a
href=
"{{ social_media_urls.facebook }}"
>
<img
src=
"https://media.sailthru.com/595/1k1/8/o/599f355052c8e.png"
width=
"32"
height=
"32"
alt=
"{% blocktrans %}{{ platform_name }} on Facebook{% endblocktrans %}"
/>
</a>
</td>
{% endif %}
{% if social_media_urls.google_plus %}
<td
height=
"32"
width=
"42"
>
<a
href=
"{{ social_media_urls.google_plus }}"
>
<img
src=
"https://media.sailthru.com/595/1k1/8/o/599f354fc554a.png"
width=
"32"
height=
"32"
alt=
"{% blocktrans %}{{ platform_name }} on Google Plus{% endblocktrans %}"
/>
</a>
</td>
{% endif %}
{% if social_media_urls.reddit %}
<td
height=
"32"
width=
"42"
>
<a
href=
"{{ social_media_urls.reddit }}"
>
<img
src=
"https://media.sailthru.com/595/1k1/8/o/599f354e326b9.png"
width=
"32"
height=
"32"
alt=
"{% blocktrans %}{{ platform_name }} on Reddit{% endblocktrans %}"
/>
</a>
</td>
{% endif %}
</tr>
</table>
</td>
</tr>
<tr>
<!-- APP BUTTONS -->
<td
style=
"padding-bottom: 20px;"
>
{% if mobile_store_urls.apple %}
<a
href=
"{{ mobile_store_urls.apple }}"
style=
"text-decoration: none"
>
<img
src=
"https://media.sailthru.com/595/1k1/6/2/5931cfbba391b.png"
alt=
"{% trans "
Download
the
iOS
app
on
the
Apple
Store
"
%}"
width=
"136"
height=
"50"
style=
"margin-{{ LANGUAGE_BIDI|yesno:"
left
,
right
"
}}
:
10px
"
/>
</a>
{% endif %}
{% if mobile_store_urls.google %}
<a
href=
"{{ mobile_store_urls.google }}"
style=
"text-decoration: none"
>
<img
src=
"https://media.sailthru.com/595/1k1/6/2/5931cf879a033.png"
alt=
"{% trans "
Download
the
Android
app
on
the
Google
Play
Store
"
%}"
width=
"136"
height=
"50"
/>
</a>
{% endif %}
</td>
</tr>
<tr>
<!-- Actions -->
<td
style=
"padding-bottom: 20px;"
>
{# Note that these variables are evaluated by Sailthru, not the Django template engine #}
<p>
<a
href=
"{view_url}"
style=
"color: #960909"
>
<font
color=
"#960909"
><b>
{% trans "View on Web" %}
</b></font>
</a>
</p>
<p>
<a
href=
"{optout_confirm_url}"
style=
"color: #960909"
>
<font
color=
"#960909"
><b>
{% trans "Unsubscribe from this list" %}
</b></font>
</a>
</p>
</td>
</tr>
<tr>
<!-- COPYRIGHT -->
<td>
©
{% now "Y" %} {{ platform_name }}, {% trans "All rights reserved" %}.
<br/>
<br/>
{% trans "Our mailing address is" %}:
<br/>
{{ contact_mailing_address }}
</td>
</tr>
</table>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</div>
{# Debug info that is not user-visible #}
<span
id=
"ace-message-id"
style=
"display:none;"
>
{{ message.log_id }}
</span>
<span
id=
"template-revision"
style=
"display:none;"
>
{{ template_revision }}
</span>
themes/red-theme/lms/templates/schedules/edx_ace/common/return_to_course_cta.html
0 → 100644
View file @
54ae1c62
{% 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>
themes/red-theme/lms/templates/schedules/edx_ace/recurringnudge_day3/email/body.txt
0 → 100644
View file @
54ae1c62
{% 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"%}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment