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
43c7a517
Commit
43c7a517
authored
Oct 13, 2017
by
Nimisha Asthagiri
Committed by
GitHub
Oct 13, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #16219 from edx/ret/dynamic-pacing-email
Course Update emails (initial)
parents
f0ee9ee8
741917e9
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
236 additions
and
15 deletions
+236
-15
common/djangoapps/student/models.py
+0
-2
openedx/core/djangoapps/schedules/admin.py
+6
-3
openedx/core/djangoapps/schedules/config.py
+6
-0
openedx/core/djangoapps/schedules/exceptions.py
+2
-0
openedx/core/djangoapps/schedules/management/commands/send_course_update.py
+14
-0
openedx/core/djangoapps/schedules/migrations/0005_auto_20171010_1722.py
+24
-0
openedx/core/djangoapps/schedules/models.py
+2
-0
openedx/core/djangoapps/schedules/resolvers.py
+17
-1
openedx/core/djangoapps/schedules/tasks.py
+97
-2
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/courseupdate/email/body.html
+51
-0
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/courseupdate/email/body.txt
+9
-0
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/courseupdate/email/from_name.txt
+1
-0
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/courseupdate/email/head.html
+1
-0
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/courseupdate/email/subject.txt
+3
-0
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.txt
+3
-7
No files found.
common/djangoapps/student/models.py
View file @
43c7a517
...
...
@@ -54,9 +54,7 @@ from courseware.models import DynamicUpgradeDeadlineConfiguration, CourseDynamic
from
enrollment.api
import
_default_course_mode
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.schedules.models
import
ScheduleConfig
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.theming.helpers
import
get_current_site
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
,
NoneToEmptyManager
from
track
import
contexts
from
util.milestones_helpers
import
is_entrance_exams_enabled
...
...
openedx/core/djangoapps/schedules/admin.py
View file @
43c7a517
...
...
@@ -30,6 +30,9 @@ class ScheduleAdmin(admin.ModelAdmin):
@admin.register
(
models
.
ScheduleConfig
)
class
ScheduleConfigAdmin
(
admin
.
ModelAdmin
):
search_fields
=
(
'site'
,)
list_display
=
(
'site'
,
'create_schedules'
,
'enqueue_recurring_nudge'
,
'deliver_recurring_nudge'
,
'enqueue_upgrade_reminder'
,
'deliver_upgrade_reminder'
)
list_display
=
(
'site'
,
'create_schedules'
,
'enqueue_recurring_nudge'
,
'deliver_recurring_nudge'
,
'enqueue_upgrade_reminder'
,
'deliver_upgrade_reminder'
,
'enqueue_course_update'
,
'deliver_course_update'
,
)
openedx/core/djangoapps/schedules/config.py
View file @
43c7a517
...
...
@@ -9,4 +9,10 @@ CREATE_SCHEDULE_WAFFLE_FLAG = CourseWaffleFlag(
flag_undefined_default
=
False
)
COURSE_UPDATE_WAFFLE_FLAG
=
CourseWaffleFlag
(
waffle_namespace
=
WAFFLE_FLAG_NAMESPACE
,
flag_name
=
u'send_updates_for_course'
,
flag_undefined_default
=
False
)
DEBUG_MESSAGE_WAFFLE_FLAG
=
WaffleFlag
(
WAFFLE_FLAG_NAMESPACE
,
u'enable_debugging'
)
openedx/core/djangoapps/schedules/exceptions.py
0 → 100644
View file @
43c7a517
class
CourseUpdateDoesNotExist
(
Exception
):
pass
openedx/core/djangoapps/schedules/management/commands/send_course_update.py
0 → 100644
View file @
43c7a517
from
openedx.core.djangoapps.schedules.management.commands
import
SendEmailBaseCommand
from
openedx.core.djangoapps.schedules.resolvers
import
CourseUpdateResolver
class
Command
(
SendEmailBaseCommand
):
resolver_class
=
CourseUpdateResolver
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
Command
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
log_prefix
=
'Upgrade Reminder'
def
send_emails
(
self
,
resolver
,
*
args
,
**
options
):
for
day_offset
in
xrange
(
-
7
,
-
77
,
-
7
):
resolver
.
send
(
day_offset
,
options
.
get
(
'override_recipient_email'
))
openedx/core/djangoapps/schedules/migrations/0005_auto_20171010_1722.py
0 → 100644
View file @
43c7a517
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'schedules'
,
'0004_auto_20170922_1428'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'scheduleconfig'
,
name
=
'deliver_course_update'
,
field
=
models
.
BooleanField
(
default
=
False
),
),
migrations
.
AddField
(
model_name
=
'scheduleconfig'
,
name
=
'enqueue_course_update'
,
field
=
models
.
BooleanField
(
default
=
False
),
),
]
openedx/core/djangoapps/schedules/models.py
View file @
43c7a517
...
...
@@ -37,3 +37,5 @@ class ScheduleConfig(ConfigurationModel):
deliver_recurring_nudge
=
models
.
BooleanField
(
default
=
False
)
enqueue_upgrade_reminder
=
models
.
BooleanField
(
default
=
False
)
deliver_upgrade_reminder
=
models
.
BooleanField
(
default
=
False
)
enqueue_course_update
=
models
.
BooleanField
(
default
=
False
)
deliver_course_update
=
models
.
BooleanField
(
default
=
False
)
openedx/core/djangoapps/schedules/resolvers.py
View file @
43c7a517
...
...
@@ -8,8 +8,10 @@ from openedx.core.djangoapps.schedules.tasks import (
DEFAULT_NUM_BINS
,
RECURRING_NUDGE_NUM_BINS
,
UPGRADE_REMINDER_NUM_BINS
,
COURSE_UPDATE_NUM_BINS
,
recurring_nudge_schedule_bin
,
upgrade_reminder_schedule_bin
upgrade_reminder_schedule_bin
,
course_update_schedule_bin
,
)
from
openedx.core.djangoapps.schedules.utils
import
PrefixedDebugLoggerMixin
from
openedx.core.djangoapps.site_configuration.models
import
SiteConfiguration
...
...
@@ -118,3 +120,17 @@ class UpgradeReminderResolver(BinnedSchedulesBaseResolver):
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
UpgradeReminderResolver
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
log_prefix
=
'Upgrade Reminder'
class
CourseUpdateResolver
(
BinnedSchedulesBaseResolver
):
"""
Send a message to all users whose schedule started at ``self.current_date`` + ``day_offset`` and the
course has updates.
"""
async_send_task
=
course_update_schedule_bin
num_bins
=
COURSE_UPDATE_NUM_BINS
enqueue_config_var
=
'enqueue_course_update'
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
CourseUpdateResolver
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
log_prefix
=
'Course Update'
openedx/core/djangoapps/schedules/tasks.py
View file @
43c7a517
...
...
@@ -23,6 +23,8 @@ from opaque_keys.edx.keys import CourseKey
from
courseware.date_summary
import
verified_upgrade_deadline_link
,
verified_upgrade_link_is_valid
from
edxmako.shortcuts
import
marketing_link
from
openedx.core.djangoapps.schedules.config
import
COURSE_UPDATE_WAFFLE_FLAG
from
openedx.core.djangoapps.schedules.exceptions
import
CourseUpdateDoesNotExist
from
openedx.core.djangoapps.schedules.message_type
import
ScheduleMessageType
from
openedx.core.djangoapps.schedules.models
import
Schedule
,
ScheduleConfig
from
openedx.core.djangoapps.schedules.template_context
import
(
...
...
@@ -31,6 +33,8 @@ from openedx.core.djangoapps.schedules.template_context import (
encode_urls_in_dict
,
get_base_template_context
)
from
request_cache.middleware
import
request_cached
from
xmodule.modulestore.django
import
modulestore
LOG
=
logging
.
getLogger
(
__name__
)
...
...
@@ -44,6 +48,7 @@ KNOWN_RETRY_ERRORS = ( # Errors we expect occasionally that could resolve on re
DEFAULT_NUM_BINS
=
24
RECURRING_NUDGE_NUM_BINS
=
DEFAULT_NUM_BINS
UPGRADE_REMINDER_NUM_BINS
=
DEFAULT_NUM_BINS
COURSE_UPDATE_NUM_BINS
=
DEFAULT_NUM_BINS
@task
(
bind
=
True
,
default_retry_delay
=
30
,
routing_key
=
ROUTING_KEY
)
...
...
@@ -222,8 +227,98 @@ def _upgrade_reminder_schedules_for_bin(site, target_day, bin_num, org_list, exc
yield
(
user
,
first_schedule
.
enrollment
.
course
.
language
,
template_context
)
class
CourseUpdate
(
ScheduleMessageType
):
pass
@task
(
ignore_result
=
True
,
routing_key
=
ROUTING_KEY
)
def
course_update_schedule_bin
(
site_id
,
target_day_str
,
day_offset
,
bin_num
,
org_list
,
exclude_orgs
=
False
,
override_recipient_email
=
None
,
):
target_day
=
deserialize
(
target_day_str
)
msg_type
=
CourseUpdate
()
for
(
user
,
language
,
context
)
in
_course_update_schedules_for_bin
(
Site
.
objects
.
get
(
id
=
site_id
),
target_day
,
day_offset
,
bin_num
,
org_list
,
exclude_orgs
):
msg
=
msg_type
.
personalize
(
Recipient
(
user
.
username
,
override_recipient_email
or
user
.
email
,
),
language
,
context
,
)
_course_update_schedule_send
.
apply_async
((
site_id
,
str
(
msg
)),
retry
=
False
)
@task
(
ignore_result
=
True
,
routing_key
=
ROUTING_KEY
)
def
_course_update_schedule_send
(
site_id
,
msg_str
):
site
=
Site
.
objects
.
get
(
pk
=
site_id
)
if
not
ScheduleConfig
.
current
(
site
)
.
deliver_course_update
:
return
msg
=
Message
.
from_string
(
msg_str
)
ace
.
send
(
msg
)
def
_course_update_schedules_for_bin
(
site
,
target_day
,
day_offset
,
bin_num
,
org_list
,
exclude_orgs
=
False
):
week_num
=
abs
(
day_offset
)
/
7
beginning_of_day
=
target_day
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
)
schedules
=
get_schedules_with_target_date_by_bin_and_orgs
(
schedule_date_field
=
'start'
,
target_date
=
beginning_of_day
,
bin_num
=
bin_num
,
num_bins
=
COURSE_UPDATE_NUM_BINS
,
org_list
=
org_list
,
exclude_orgs
=
exclude_orgs
,
order_by
=
'enrollment__course'
,
)
LOG
.
debug
(
'Course Update: Query =
%
r'
,
schedules
.
query
.
sql_with_params
())
for
schedule
in
schedules
:
enrollment
=
schedule
.
enrollment
try
:
week_summary
=
get_course_week_summary
(
enrollment
.
course_id
,
week_num
)
except
CourseUpdateDoesNotExist
:
continue
user
=
enrollment
.
user
course_id_str
=
str
(
enrollment
.
course_id
)
template_context
=
get_base_template_context
(
site
)
template_context
.
update
({
'student_name'
:
user
.
profile
.
name
,
'user_personal_address'
:
user
.
profile
.
name
if
user
.
profile
.
name
else
user
.
username
,
'course_name'
:
schedule
.
enrollment
.
course
.
display_name
,
'course_url'
:
absolute_url
(
site
,
reverse
(
'course_root'
,
args
=
[
str
(
schedule
.
enrollment
.
course_id
)])),
'week_num'
:
week_num
,
'week_summary'
:
week_summary
,
# This is used by the bulk email optout policy
'course_ids'
:
[
course_id_str
],
})
yield
(
user
,
schedule
.
enrollment
.
course
.
language
,
template_context
)
@request_cached
def
get_course_week_summary
(
course_id
,
week_num
):
if
COURSE_UPDATE_WAFFLE_FLAG
.
is_enabled
(
course_id
):
course
=
modulestore
()
.
get_course
(
course_id
)
return
course
.
week_summary
(
week_num
)
else
:
raise
CourseUpdateDoesNotExist
()
def
get_schedules_with_target_date_by_bin_and_orgs
(
schedule_date_field
,
target_date
,
bin_num
,
num_bins
=
DEFAULT_NUM_BINS
,
org_list
=
None
,
exclude_orgs
=
False
):
org_list
=
None
,
exclude_orgs
=
False
,
order_by
=
'enrollment__user__id'
):
"""
Returns Schedules with the target_date, related to Users whose id matches the bin_num, and filtered by org_list.
...
...
@@ -263,7 +358,7 @@ def get_schedules_with_target_date_by_bin_and_orgs(schedule_date_field, target_d
enrollment__user__in
=
users
,
enrollment__is_active
=
True
,
**
schedule_date_equals_target_date_filter
)
.
order_by
(
'enrollment__user__id'
)
)
.
order_by
(
order_by
)
if
org_list
is
not
None
:
if
exclude_orgs
:
...
...
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/courseupdate/email/body.html
0 → 100644
View file @
43c7a517
{% extends 'schedules/edx_ace/common/base_body.html' %}
{% load i18n %}
{% load static %}
{% block preview_text %}
{% blocktrans trimmed %}
Welcome to week {{ week_num }} of our {{ course_name }} course!
{% endblocktrans %}
{% endblock %}
{% block content %}
<table
width=
"100%"
align=
"left"
border=
"0"
cellpadding=
"0"
cellspacing=
"0"
role=
"presentation"
>
<tr>
<td>
<p>
{% blocktrans trimmed %}
Welcome to week {{ week_num }} of
<strong>
{{ course_name }}
</strong>
!
{% endblocktrans %}
</p>
<p>
{% blocktrans trimmed %}
Here is what you can look forward to learning this week:
<p>
{{ week_summary }}
</p>
{% endblocktrans %}
</p>
<p>
<!-- email client support for style sheets is pretty spotty, so we have to inline all of these styles -->
<a
href=
"{{ course_url }}"
style=
"
color: #ffffff;
text-decoration: none;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
background-color: #005686;
border-top: 10px solid #005686;
border-bottom: 10px solid #005686;
border-right: 16px solid #005686;
border-left: 16px solid #005686;
display: inline-block;
"
>
<!-- old email clients require the use of the font tag :( -->
<font
color=
"#ffffff"
><b>
{% trans "Resume your course now" %}
</b></font>
</a>
</p>
</td>
</tr>
</table>
{% endblock %}
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/courseupdate/email/body.txt
0 → 100644
View file @
43c7a517
{% load i18n %}
{% blocktrans trimmed %}
Welcome to week {{ week_num }} of our {{ course_name }} course!
Here is what you can look forward to learning this week:
{{ week_summary }}
{% endblocktrans %}
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/courseupdate/email/from_name.txt
0 → 100644
View file @
43c7a517
{{ course_name }}
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/courseupdate/email/head.html
0 → 100644
View file @
43c7a517
{% extends 'schedules/edx_ace/common/base_head.html' %}
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/courseupdate/email/subject.txt
0 → 100644
View file @
43c7a517
{% load i18n %}
{% blocktrans %}{{ course_name }} - Welcome to Week {{ week_num }} {% endblocktrans %}
openedx/core/djangoapps/schedules/templates/schedules/edx_ace/upgradereminder/email/body.txt
View file @
43c7a517
{% load i18n %}
{% blocktrans trimmed %}
Dear {{ user_personal_address }},
{% endblocktrans %}
{% blocktrans trimmed %}
We hope you are enjoying learning with us so far in {{ course_name }}! A verified certificate
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 in {{ course_name }}! A verified certificate
will allow you to highlight your new knowledge and skills. It's official, and easily shareable.
Upgrade by {{ user_schedule_upgrade_deadline_time }}.
Upgrade by {{ user_schedule_upgrade_deadline_time }}.
{% endblocktrans %}
{% trans "Upgrade now at" %} <{{ upsell_link }}>
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