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
5cc73bc7
Commit
5cc73bc7
authored
Oct 23, 2017
by
Tyler Hallada
Committed by
GitHub
Oct 23, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #16276 from edx/thallada/ret-dedupe-upgrade-reminder
Send only one upgrade reminder email per user per day
parents
5940342f
4ab477ea
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
113 additions
and
47 deletions
+113
-47
openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py
+7
-6
openedx/core/djangoapps/schedules/tasks.py
+13
-11
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.html
+62
-26
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.txt
+21
-2
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/from_name.txt
+5
-1
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/subject.txt
+5
-1
No files found.
openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py
View file @
5cc73bc7
...
@@ -34,8 +34,9 @@ NUM_QUERIES_NO_MATCHING_SCHEDULES = 2
...
@@ -34,8 +34,9 @@ NUM_QUERIES_NO_MATCHING_SCHEDULES = 2
NUM_QUERIES_WITH_MATCHES
=
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
1
NUM_QUERIES_WITH_MATCHES
=
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
1
# 1) Global dynamic deadline switch
# 1) Global dynamic deadline switch
# 2) Course dynamic deadline switch
# 2) E-commerce configuration
# 2) E-commerce configuration
NUM_QUERIES_WITH_DEADLINE
=
2
NUM_QUERIES_WITH_DEADLINE
=
3
NUM_COURSE_MODES_QUERIES
=
1
NUM_COURSE_MODES_QUERIES
=
1
...
@@ -233,7 +234,7 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -233,7 +234,7 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
bin_num
=
user
.
id
%
tasks
.
UPGRADE_REMINDER_NUM_BINS
,
bin_num
=
user
.
id
%
tasks
.
UPGRADE_REMINDER_NUM_BINS
,
org_list
=
[
schedules
[
0
]
.
enrollment
.
course
.
org
],
org_list
=
[
schedules
[
0
]
.
enrollment
.
course
.
org
],
)
)
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
3
)
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
1
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@ddt.data
(
*
itertools
.
product
((
1
,
10
,
100
),
(
2
,
10
)))
@ddt.data
(
*
itertools
.
product
((
1
,
10
,
100
),
(
2
,
10
)))
...
@@ -281,7 +282,7 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -281,7 +282,7 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
# we execute one query per course to see if it's opted out of dynamic upgrade deadlines, however,
# we execute one query per course to see if it's opted out of dynamic upgrade deadlines, however,
# since we create a new course for each schedule in this test, we expect there to be one per message
# since we create a new course for each schedule in this test, we expect there to be one per message
num_expected_queries
=
NUM_QUERIES_WITH_MATCHES
+
NUM_QUERIES_WITH_DEADLINE
+
message_count
num_expected_queries
=
NUM_QUERIES_WITH_MATCHES
+
NUM_QUERIES_WITH_DEADLINE
with
self
.
assertNumQueries
(
num_expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
with
self
.
assertNumQueries
(
num_expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
upgrade_reminder_schedule_bin
(
tasks
.
upgrade_reminder_schedule_bin
(
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
day
,
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
day
,
...
@@ -289,15 +290,15 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -289,15 +290,15 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
org_list
=
[
schedules
[
0
]
.
enrollment
.
course
.
org
],
org_list
=
[
schedules
[
0
]
.
enrollment
.
course
.
org
],
)
)
self
.
assertEqual
(
len
(
sent_messages
),
message_count
)
self
.
assertEqual
(
len
(
sent_messages
),
1
)
# Load the site (which we query per message sent)
# Load the site (which we query per message sent)
# Check the schedule config
# Check the schedule config
with
self
.
assertNumQueries
(
1
+
message_count
):
with
self
.
assertNumQueries
(
2
):
for
args
in
sent_messages
:
for
args
in
sent_messages
:
tasks
.
_upgrade_reminder_schedule_send
(
*
args
)
tasks
.
_upgrade_reminder_schedule_send
(
*
args
)
self
.
assertEqual
(
mock_channel
.
deliver
.
call_count
,
message_count
)
self
.
assertEqual
(
mock_channel
.
deliver
.
call_count
,
1
)
for
(
_name
,
(
_msg
,
email
),
_kwargs
)
in
mock_channel
.
deliver
.
mock_calls
:
for
(
_name
,
(
_msg
,
email
),
_kwargs
)
in
mock_channel
.
deliver
.
mock_calls
:
for
template
in
attr
.
astuple
(
email
):
for
template
in
attr
.
astuple
(
email
):
self
.
assertNotIn
(
"TEMPLATE WARNING"
,
template
)
self
.
assertNotIn
(
"TEMPLATE WARNING"
,
template
)
...
...
openedx/core/djangoapps/schedules/tasks.py
View file @
5cc73bc7
...
@@ -222,26 +222,28 @@ def _upgrade_reminder_schedules_for_bin(site, current_datetime, target_datetime,
...
@@ -222,26 +222,28 @@ def _upgrade_reminder_schedules_for_bin(site, current_datetime, target_datetime,
exclude_orgs
=
exclude_orgs
,
exclude_orgs
=
exclude_orgs
,
)
)
for
schedule
in
schedules
:
for
(
user
,
user_schedules
)
in
groupby
(
schedules
,
lambda
s
:
s
.
enrollment
.
user
):
enrollment
=
schedule
.
enrollment
user_schedules
=
list
(
user_schedules
)
user
=
enrollment
.
user
course_id_strs
=
[
str
(
schedule
.
enrollment
.
course_id
)
for
schedule
in
user_schedules
]
course_id_str
=
str
(
enrollment
.
course_id
)
# TODO: group by schedule and user like recurring nudge
course_id_strs
=
[
course_id_str
]
first_schedule
=
schedule
first_schedule
=
user_schedules
[
0
]
template_context
=
get_base_template_context
(
site
)
template_context
=
get_base_template_context
(
site
)
template_context
.
update
({
template_context
.
update
({
'student_name'
:
user
.
profile
.
name
,
'student_name'
:
user
.
profile
.
name
,
'user_personal_address'
:
user
.
profile
.
name
if
user
.
profile
.
name
else
user
.
username
,
'user_personal_address'
:
user
.
profile
.
name
if
user
.
profile
.
name
else
user
.
username
,
'course_name'
:
first_schedule
.
enrollment
.
course
.
display_name
,
'course_links'
:
[
'course_url'
:
absolute_url
(
site
,
reverse
(
'course_root'
,
args
=
[
str
(
first_schedule
.
enrollment
.
course_id
)])),
{
'url'
:
absolute_url
(
site
,
reverse
(
'course_root'
,
args
=
[
str
(
s
.
enrollment
.
course_id
)])),
'name'
:
s
.
enrollment
.
course
.
display_name
}
for
s
in
user_schedules
],
'first_course_name'
:
first_schedule
.
enrollment
.
course
.
display_name
,
# This is used by the bulk email optout policy
# This is used by the bulk email optout policy
'course_ids'
:
course_id_strs
,
'course_ids'
:
course_id_strs
,
'cert_image'
:
absolute_url
(
site
,
static
(
'course_experience/images/verified-cert.png'
)),
'cert_image'
:
absolute_url
(
site
,
static
(
'course_experience/images/verified-cert.png'
)),
})
})
...
...
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.html
View file @
5cc73bc7
...
@@ -3,11 +3,24 @@
...
@@ -3,11 +3,24 @@
{% load static %}
{% load static %}
{% block preview_text %}
{% block preview_text %}
{% blocktrans trimmed %}
{% if course_ids|length > 1 %}
We hope you are enjoying learning with us so far in {{ course_name }}! A verified certificate
{% blocktrans trimmed %}
will allow you to highlight your new knowledge and skills. It's official, and easily shareable.
We hope you are enjoying learning with us so far on {{ platform_name }}! A verified certificate allows you to
highlight your new knowledge and skills. An {{ platform_name }} certificate is official and easily
shareable.
Upgrade by {{ user_schedule_upgrade_deadline_time }}.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
We hope you are enjoying learning with us so far in {{ first_course_name }}! A verified certificate allows
you to highlight your new knowledge and skills. An {{ platform_name }} certificate is official and easily
shareable.
Upgrade by {{ user_schedule_upgrade_deadline_time }}.
Upgrade by {{ user_schedule_upgrade_deadline_time }}.
{% endblocktrans %}
{% endif %}
{% blocktrans trimmed %}
{% endblocktrans %}
{% endblocktrans %}
{% endblock %}
{% endblock %}
...
@@ -18,11 +31,19 @@
...
@@ -18,11 +31,19 @@
<h1>
{% trans "Upgrade now" %}
</h1>
<h1>
{% trans "Upgrade now" %}
</h1>
<p>
<p>
{% blocktrans trimmed %}
{% if course_ids|length > 1 %}
We hope you are enjoying learning with us so far in
<strong>
{{ course_name }}
</strong>
! A
{% blocktrans trimmed %}
verified certificate will allow you to highlight your new knowledge and skills. It's official,
We hope you are enjoying learning with us so far on
<strong>
{{ platform_name }}
</strong>
! A
and easily shareable.
verified certificate allows you to highlight your new knowledge and skills. An {{ platform_name
{% endblocktrans %}
}} certificate is official and easily shareable.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
We hope you are enjoying learning with us so far in
<strong>
{{ first_course_name }}
</strong>
! A
verified certificate allows you to highlight your new knowledge and skills. An {{ platform_name
}} certificate is official and easily shareable.
{% endblocktrans %}
{% endif %}
</p>
</p>
<p>
<p>
{% blocktrans trimmed %}
{% blocktrans trimmed %}
...
@@ -30,27 +51,42 @@
...
@@ -30,27 +51,42 @@
{% endblocktrans %}
{% endblocktrans %}
</p>
</p>
<a
href=
"{{ upsell_link }}"
>
{% if course_ids|length > 1 and course_ids|length
<
10
%}
<img
<
p
>
src=
"{{ cert_image }}"
{% trans "You are eligible to upgrade in these courses:" %}
alt=
"{% trans 'Example print-out of a verified certificate' %}"
</p>
style=
"
<ul
style=
"margin-bottom: 30px;"
>
display: block;
{% for course_link in course_links %}
margin-right: auto;
<li>
margin-left: auto;
<a
href=
"{{ course_link.url }}"
>
{{ course_link.name }}
</a>
margin-top: 50px;
</li>
margin-bottom: 50px;
{% endfor %}
border-top: 1px solid lightgray;
</ul>
border-bottom: 3px solid lightgray;
{% endif %}
border-right: 3px solid lightgray;
border-left: 1px solid lightgray;
<img
"
/>
src=
"{{ cert_image }}"
</a>
alt=
"{% trans 'Example print-out of a verified certificate' %}"
style=
"
display: block;
margin-right: auto;
margin-left: auto;
margin-top: 50px;
margin-bottom: 50px;
border-top: 1px solid lightgray;
border-bottom: 3px solid lightgray;
border-right: 3px solid lightgray;
border-left: 1px solid lightgray;
"
/>
<p>
<p>
{# email client support for style sheets is pretty spotty, so we have to inline all of these styles #}
{# email client support for style sheets is pretty spotty, so we have to inline all of these styles #}
<a
<a
href=
"{{ upsell_link }}"
{%
if
course_ids
|
length =
=
1
%}
href=
"{{ upsell_link }}"
{%
else
%}
href=
"{{ dashboard_url }}"
{%
endif
%}
style=
"
style=
"
color: #ffffff;
color: #ffffff;
text-decoration: none;
text-decoration: none;
...
...
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.txt
View file @
5cc73bc7
{% load i18n %}
{% load i18n %}
{% if course_ids|length > 1 %}
{% blocktrans trimmed %}
{% blocktrans trimmed %}
We hope you are enjoying learning with us so far in {{ course_name }}! A verified certificate
We hope you are enjoying learning with us so far on {{ platform_name }}! A verified certificate
will allow you to highlight your new knowledge and skills. It's official, and easily shareable.
allows you to highlight your new knowledge and skills. An {{ platform_name }} certificate is
official and easily shareable.
Upgrade by {{ user_schedule_upgrade_deadline_time }}.
{% endblocktrans %}
{% if course_ids|length > 1 and course_ids|length < 10 %}
{% for course_link in course_links %}
* {{ course_link.name }} <{{ course_link.url }}>
{% endfor %}
{% endif %}
{% trans "Upgrade now at" %} <{{ dashboard_url }}>
{% else %}
{% blocktrans trimmed %}
We hope you are enjoying learning with us so far in {{ first_course_name }}! A verified certificate
allows you to highlight your new knowledge and skills. An {{ platform_name }} certificate is
official and easily shareable.
Upgrade by {{ user_schedule_upgrade_deadline_time }}.
Upgrade by {{ user_schedule_upgrade_deadline_time }}.
{% endblocktrans %}
{% endblocktrans %}
{% trans "Upgrade now at" %} <{{ upsell_link }}>
{% trans "Upgrade now at" %} <{{ upsell_link }}>
{% endif %}
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/from_name.txt
View file @
5cc73bc7
{{ course_name }}
{% if course_ids|length > 1 %}
{{ platform_name }}
{% else %}
{{ first_course_name }}
{% endif %}
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/subject.txt
View file @
5cc73bc7
{% load i18n %}
{% load i18n %}
{% blocktrans %}Upgrade to earn a verified certificate in {{ course_name }}{% endblocktrans %}
{% if course_ids|length > 1 %}
{% blocktrans %}Upgrade to earn a verified certificate on {{ platform_name }}{% endblocktrans %}
{% else %}
{% blocktrans %}Upgrade to earn a verified certificate in {{ first_course_name }}{% endblocktrans %}
{% endif %}
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