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
9e16ed1c
Unverified
Commit
9e16ed1c
authored
Oct 19, 2017
by
Gabe Mulley
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
only send upgrade reminders to learners who are eligible
parent
e6abdf7e
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
202 additions
and
50 deletions
+202
-50
openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py
+150
-33
openedx/core/djangoapps/schedules/resolvers.py
+52
-17
No files found.
openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py
View file @
9e16ed1c
import
datetime
import
datetime
import
itertools
from
copy
import
deepcopy
from
copy
import
deepcopy
import
logging
from
unittest
import
skipUnless
from
unittest
import
skipUnless
import
attr
import
attr
import
ddt
import
ddt
import
pytz
import
pytz
from
django.conf
import
settings
from
django.conf
import
settings
from
edx_ace
import
Message
from
freezegun
import
freeze_time
from
edx_ace.channel
import
ChannelType
from
edx_ace.channel
import
ChannelType
from
edx_ace.test_utils
import
StubPolicy
,
patch_channels
,
patch_policies
from
edx_ace.test_utils
import
StubPolicy
,
patch_channels
,
patch_policies
from
edx_ace.utils.date
import
serialize
from
edx_ace.utils.date
import
serialize
...
@@ -17,42 +19,71 @@ from opaque_keys.edx.locator import CourseLocator
...
@@ -17,42 +19,71 @@ from opaque_keys.edx.locator import CourseLocator
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
course_modes.tests.factories
import
CourseModeFactory
from
course_modes.tests.factories
import
CourseModeFactory
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.schedules
import
resolvers
,
tasks
from
openedx.core.djangoapps.schedules
import
resolvers
,
tasks
from
openedx.core.djangoapps.schedules.management.commands
import
send_upgrade_reminder
as
reminder
from
openedx.core.djangoapps.schedules.management.commands
import
send_upgrade_reminder
as
reminder
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleConfigFactory
,
ScheduleFactory
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.site_configuration.tests.factories
import
SiteConfigurationFactory
,
SiteFactory
from
openedx.core.djangoapps.waffle_utils.testutils
import
WAFFLE_TABLES
from
openedx.core.djangoapps.waffle_utils.testutils
import
WAFFLE_TABLES
from
openedx.core.djangolib.testing.utils
import
CacheIsolationTestCase
,
skip_unless_lms
,
FilteredQueryCountMixin
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
# 1) Load the current django site
SITE_QUERY
=
1
# 2) Query the schedules to find all of the template context information
SCHEDULES_QUERY
=
1
NUM_QUERIES_NO_MATCHING_SCHEDULES
=
2
COURSE_MODES_QUERY
=
1
GLOBAL_DEADLINE_SWITCH_QUERY
=
1
COMMERCE_CONFIG_QUERY
=
1
# 3) Query all course modes for all courses in returned schedules
NUM_QUERIES_NO_MATCHING_SCHEDULES
=
SITE_QUERY
+
SCHEDULES_QUERY
NUM_QUERIES_WITH_MATCHES
=
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
1
# 1) Global dynamic deadline switch
NUM_QUERIES_WITH_MATCHES
=
(
# 2) Course dynamic deadline switch
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
# 2) E-commerce configuration
COURSE_MODES_QUERY
NUM_QUERIES_WITH_DEADLINE
=
3
)
NUM_COURSE_MODES_QUERIES
=
1
NUM_QUERIES_FIRST_MATCH
=
(
NUM_QUERIES_WITH_MATCHES
+
GLOBAL_DEADLINE_SWITCH_QUERY
+
COMMERCE_CONFIG_QUERY
)
LOG
=
logging
.
getLogger
(
__name__
)
@ddt.ddt
@ddt.ddt
@skip_unless_lms
@skip_unless_lms
@skipUnless
(
'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
in
settings
.
INSTALLED_APPS
,
@skipUnless
(
'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
in
settings
.
INSTALLED_APPS
,
"Can't test schedules if the app isn't installed"
)
"Can't test schedules if the app isn't installed"
)
class
TestUpgradeReminder
(
FilteredQueryCountMixin
,
CacheIsolationTestCase
):
@freeze_time
(
'2017-08-01 00:00:00'
,
tz_offset
=
0
,
tick
=
True
)
# pylint: disable=protected-access
class
TestUpgradeReminder
(
SharedModuleStoreTestCase
):
ENABLED_CACHES
=
[
'default'
]
ENABLED_CACHES
=
[
'default'
]
@classmethod
def
setUpClass
(
cls
):
super
(
TestUpgradeReminder
,
cls
)
.
setUpClass
()
cls
.
course
=
CourseFactory
.
create
(
org
=
'edX'
,
number
=
'test'
,
display_name
=
'Test Course'
,
self_paced
=
True
,
start
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
-
datetime
.
timedelta
(
days
=
30
),
)
cls
.
course_overview
=
CourseOverview
.
get_from_id
(
cls
.
course
.
id
)
def
setUp
(
self
):
def
setUp
(
self
):
super
(
TestUpgradeReminder
,
self
)
.
setUp
()
super
(
TestUpgradeReminder
,
self
)
.
setUp
()
CourseModeFactory
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
,
expiration_datetime
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
30
),
)
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
1
,
15
,
44
,
30
,
tzinfo
=
pytz
.
UTC
))
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
1
,
15
,
44
,
30
,
tzinfo
=
pytz
.
UTC
))
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
1
,
17
,
34
,
30
,
tzinfo
=
pytz
.
UTC
))
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
1
,
17
,
34
,
30
,
tzinfo
=
pytz
.
UTC
))
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
2
,
15
,
34
,
30
,
tzinfo
=
pytz
.
UTC
))
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
2
,
15
,
34
,
30
,
tzinfo
=
pytz
.
UTC
))
...
@@ -96,22 +127,37 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -96,22 +127,37 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
@patch.object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
def
test_schedule_bin
(
self
,
schedule_count
,
mock_schedule_send
,
mock_ace
):
def
test_schedule_bin
(
self
,
schedule_count
,
mock_schedule_send
,
mock_ace
):
upgrade_deadline
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
2
)
schedules
=
[
schedules
=
[
ScheduleFactory
.
create
(
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
18
,
44
,
30
,
tzinfo
=
pytz
.
UTC
)
,
upgrade_deadline
=
upgrade_deadline
,
enrollment__course
__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Bin'
)
enrollment__course
=
self
.
course_overview
,
)
for
i
in
range
(
schedule_count
)
)
for
i
in
range
(
schedule_count
)
]
]
bins_in_use
=
frozenset
((
s
.
enrollment
.
user
.
id
%
resolvers
.
UPGRADE_REMINDER_NUM_BINS
)
for
s
in
schedules
)
bins_in_use
=
frozenset
((
s
.
enrollment
.
user
.
id
%
resolvers
.
UPGRADE_REMINDER_NUM_BINS
)
for
s
in
schedules
)
is_first_match
=
True
course_switch_queries
=
len
(
set
(
s
.
enrollment
.
course
.
id
for
s
in
schedules
))
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
18
,
tzinfo
=
pytz
.
UTC
)
test_datetime
=
datetime
.
datetime
(
upgrade_deadline
.
year
,
upgrade_deadline
.
month
,
upgrade_deadline
.
day
,
18
,
tzinfo
=
pytz
.
UTC
)
test_datetime_str
=
serialize
(
test_datetime
)
test_datetime_str
=
serialize
(
test_datetime
)
for
b
in
range
(
resolvers
.
UPGRADE_REMINDER_NUM_BINS
):
for
b
in
range
(
resolvers
.
UPGRADE_REMINDER_NUM_BINS
):
LOG
.
debug
(
'Running bin
%
d'
,
b
)
expected_queries
=
NUM_QUERIES_NO_MATCHING_SCHEDULES
expected_queries
=
NUM_QUERIES_NO_MATCHING_SCHEDULES
if
b
in
bins_in_use
:
if
b
in
bins_in_use
:
# to fetch course modes for valid schedules
if
is_first_match
:
expected_queries
+=
NUM_COURSE_MODES_QUERIES
expected_queries
=
(
# Since this is the first match, we need to cache all of the config models, so we run a query
# for each of those...
NUM_QUERIES_FIRST_MATCH
+
course_switch_queries
)
is_first_match
=
False
else
:
expected_queries
=
NUM_QUERIES_WITH_MATCHES
with
self
.
assertNumQueries
(
expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
with
self
.
assertNumQueries
(
expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleUpgradeReminder
.
delay
(
tasks
.
ScheduleUpgradeReminder
.
delay
(
...
@@ -194,22 +240,27 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -194,22 +240,27 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
ScheduleFactory
.
create
(
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__course__org
=
filtered_org
,
enrollment__course__org
=
filtered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user1
,
enrollment__user
=
user1
,
)
)
ScheduleFactory
.
create
(
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__course__org
=
unfiltered_org
,
enrollment__course__org
=
unfiltered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user1
,
enrollment__user
=
user1
,
)
)
ScheduleFactory
.
create
(
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__course__org
=
unfiltered_org
,
enrollment__course__org
=
unfiltered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user2
,
enrollment__user
=
user2
,
)
)
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
tzinfo
=
pytz
.
UTC
)
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
17
,
tzinfo
=
pytz
.
UTC
)
test_datetime_str
=
serialize
(
test_datetime
)
test_datetime_str
=
serialize
(
test_datetime
)
with
self
.
assertNumQueries
(
NUM_QUERIES_WITH_MATCHES
,
table_blacklist
=
WAFFLE_TABLES
):
course_switch_queries
=
1
with
self
.
assertNumQueries
(
NUM_QUERIES_FIRST_MATCH
+
course_switch_queries
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleUpgradeReminder
.
delay
(
tasks
.
ScheduleUpgradeReminder
.
delay
(
limited_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
bin_num
=
0
,
limited_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
bin_num
=
0
,
org_list
=
org_list
,
exclude_orgs
=
exclude_orgs
,
org_list
=
org_list
,
exclude_orgs
=
exclude_orgs
,
...
@@ -226,14 +277,18 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -226,14 +277,18 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
ScheduleFactory
.
create
(
ScheduleFactory
.
create
(
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
19
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
upgrade_deadline
=
datetime
.
datetime
(
2017
,
8
,
3
,
19
,
44
,
30
,
tzinfo
=
pytz
.
UTC
),
enrollment__user
=
user
,
enrollment__user
=
user
,
enrollment__course__self_paced
=
True
,
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
course_num
))
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
course_num
))
)
)
for
course_num
in
(
1
,
2
,
3
)
for
course_num
in
(
1
,
2
,
3
)
]
]
num_courses
=
len
(
set
(
s
.
enrollment
.
course
.
id
for
s
in
schedules
))
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
19
,
44
,
30
,
tzinfo
=
pytz
.
UTC
)
test_datetime
=
datetime
.
datetime
(
2017
,
8
,
3
,
19
,
44
,
30
,
tzinfo
=
pytz
.
UTC
)
test_datetime_str
=
serialize
(
test_datetime
)
test_datetime_str
=
serialize
(
test_datetime
)
with
self
.
assertNumQueries
(
NUM_QUERIES_WITH_MATCHES
,
table_blacklist
=
WAFFLE_TABLES
):
with
self
.
assertNumQueries
(
NUM_QUERIES_FIRST_MATCH
+
num_courses
,
table_blacklist
=
WAFFLE_TABLES
):
tasks
.
ScheduleUpgradeReminder
.
delay
(
tasks
.
ScheduleUpgradeReminder
.
delay
(
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
bin_num
=
user
.
id
%
resolvers
.
UPGRADE_REMINDER_NUM_BINS
,
bin_num
=
user
.
id
%
resolvers
.
UPGRADE_REMINDER_NUM_BINS
,
...
@@ -242,9 +297,8 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -242,9 +297,8 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
1
)
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
(
1
,
10
,
100
)
@ddt.unpack
def
test_templates
(
self
,
message_count
):
def
test_templates
(
self
,
message_count
,
day
):
now
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
now
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
future_datetime
=
now
+
datetime
.
timedelta
(
days
=
21
)
future_datetime
=
now
+
datetime
.
timedelta
(
days
=
21
)
...
@@ -253,22 +307,22 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -253,22 +307,22 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
ScheduleFactory
.
create
(
ScheduleFactory
.
create
(
upgrade_deadline
=
future_datetime
,
upgrade_deadline
=
future_datetime
,
enrollment__user
=
user
,
enrollment__user
=
user
,
enrollment__course__self_paced
=
True
,
enrollment__course__end
=
future_datetime
+
datetime
.
timedelta
(
days
=
30
),
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
course_num
))
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
course_num
))
)
)
for
course_num
in
range
(
message_count
)
for
course_num
in
range
(
message_count
)
]
]
for
schedule
in
schedules
:
for
schedule
in
schedules
:
schedule
.
enrollment
.
course
.
self_paced
=
True
schedule
.
enrollment
.
course
.
end
=
future_datetime
+
datetime
.
timedelta
(
days
=
30
)
schedule
.
enrollment
.
course
.
save
()
CourseModeFactory
(
CourseModeFactory
(
course_id
=
schedule
.
enrollment
.
course
.
id
,
course_id
=
schedule
.
enrollment
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
,
mode_slug
=
CourseMode
.
VERIFIED
,
expiration_datetime
=
future_datetime
expiration_datetime
=
future_datetime
)
)
num_courses
=
len
(
set
(
s
.
enrollment
.
course
.
id
for
s
in
schedules
))
test_datetime
=
future_datetime
test_datetime
=
future_datetime
test_datetime_str
=
serialize
(
test_datetime
)
test_datetime_str
=
serialize
(
test_datetime
)
...
@@ -285,12 +339,11 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -285,12 +339,11 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
with
patch
.
object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
as
mock_schedule_send
:
with
patch
.
object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
as
mock_schedule_send
:
mock_schedule_send
.
apply_async
=
lambda
args
,
*
_a
,
**
_kw
:
sent_messages
.
append
(
args
)
mock_schedule_send
.
apply_async
=
lambda
args
,
*
_a
,
**
_kw
:
sent_messages
.
append
(
args
)
# 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
# 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_FIRST_MATCH
+
num_courses
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
.
ScheduleUpgradeReminder
.
delay
(
tasks
.
ScheduleUpgradeReminder
.
delay
(
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
=
2
,
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
org_list
=
[
schedules
[
0
]
.
enrollment
.
course
.
org
],
org_list
=
[
schedules
[
0
]
.
enrollment
.
course
.
org
],
)
)
...
@@ -316,4 +369,68 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
...
@@ -316,4 +369,68 @@ class TestUpgradeReminder(FilteredQueryCountMixin, CacheIsolationTestCase):
return
templates_override
return
templates_override
def
_calculate_bin_for_user
(
self
,
user
):
def
_calculate_bin_for_user
(
self
,
user
):
return
user
.
id
%
resolvers
.
RECURRING_NUDGE_NUM_BINS
return
user
.
id
%
resolvers
.
UPGRADE_REMINDER_NUM_BINS
@patch.object
(
tasks
,
'_upgrade_reminder_schedule_send'
)
def
test_dont_send_to_verified_learner
(
self
,
mock_schedule_send
):
upgrade_deadline
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
2
)
ScheduleFactory
.
create
(
upgrade_deadline
=
upgrade_deadline
,
enrollment__user
=
UserFactory
.
create
(
id
=
resolvers
.
UPGRADE_REMINDER_NUM_BINS
),
enrollment__course
=
self
.
course_overview
,
enrollment__mode
=
CourseMode
.
VERIFIED
,
)
test_datetime_str
=
serialize
(
datetime
.
datetime
.
now
(
pytz
.
UTC
))
tasks
.
ScheduleUpgradeReminder
.
delay
(
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
bin_num
=
0
,
org_list
=
[
self
.
course
.
org
],
)
self
.
assertFalse
(
mock_schedule_send
.
called
)
self
.
assertFalse
(
mock_schedule_send
.
apply_async
.
called
)
def
test_filter_out_verified_schedules
(
self
):
now
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
future_datetime
=
now
+
datetime
.
timedelta
(
days
=
21
)
user
=
UserFactory
.
create
()
schedules
=
[
ScheduleFactory
.
create
(
upgrade_deadline
=
future_datetime
,
enrollment__user
=
user
,
enrollment__course__self_paced
=
True
,
enrollment__course__end
=
future_datetime
+
datetime
.
timedelta
(
days
=
30
),
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
i
)),
enrollment__mode
=
CourseMode
.
VERIFIED
if
i
in
(
0
,
3
)
else
CourseMode
.
AUDIT
,
)
for
i
in
range
(
5
)
]
for
schedule
in
schedules
:
CourseModeFactory
(
course_id
=
schedule
.
enrollment
.
course
.
id
,
mode_slug
=
CourseMode
.
VERIFIED
,
expiration_datetime
=
future_datetime
)
test_datetime
=
future_datetime
test_datetime_str
=
serialize
(
test_datetime
)
bin_num
=
self
.
_calculate_bin_for_user
(
user
)
sent_messages
=
[]
with
patch
.
object
(
tasks
.
ScheduleUpgradeReminder
,
'async_send_task'
)
as
mock_schedule_send
:
mock_schedule_send
.
apply_async
=
lambda
args
,
*
_a
,
**
_kw
:
sent_messages
.
append
(
args
[
1
])
tasks
.
ScheduleUpgradeReminder
.
delay
(
self
.
site_config
.
site
.
id
,
target_day_str
=
test_datetime_str
,
day_offset
=
2
,
bin_num
=
bin_num
,
org_list
=
[
schedules
[
0
]
.
enrollment
.
course
.
org
],
)
messages
=
[
Message
.
from_string
(
m
)
for
m
in
sent_messages
]
self
.
assertEqual
(
len
(
messages
),
1
)
message
=
messages
[
0
]
self
.
assertItemsEqual
(
message
.
context
[
'course_ids'
],
[
str
(
schedules
[
i
]
.
enrollment
.
course
.
id
)
for
i
in
(
1
,
2
,
4
)]
)
openedx/core/djangoapps/schedules/resolvers.py
View file @
9e16ed1c
...
@@ -165,11 +165,10 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
...
@@ -165,11 +165,10 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
template_context
[
'course_ids'
]
=
course_id_strs
template_context
[
'course_ids'
]
=
course_id_strs
first_schedule
=
user_schedules
[
0
]
first_schedule
=
user_schedules
[
0
]
template_context
.
update
(
self
.
get_template_context
(
user
,
user_schedules
))
try
:
template_context
.
update
(
self
.
get_template_context
(
user
,
user_schedules
))
# Information for including upsell messaging in template.
except
InvalidContextError
:
_add_upsell_button_information_to_template_context
(
continue
user
,
first_schedule
,
template_context
)
yield
(
user
,
first_schedule
.
enrollment
.
course
.
language
,
template_context
)
yield
(
user
,
first_schedule
.
enrollment
.
course
.
language
,
template_context
)
...
@@ -188,10 +187,19 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
...
@@ -188,10 +187,19 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
dict: This dict must be JSON serializable (no datetime objects!). When rendering the message templates it
dict: This dict must be JSON serializable (no datetime objects!). When rendering the message templates it
it will be used as the template context. Note that it will also include several default values that
it will be used as the template context. Note that it will also include several default values that
injected into all template contexts. See `get_base_template_context` for more information.
injected into all template contexts. See `get_base_template_context` for more information.
Raises:
InvalidContextError: If this user and set of schedules are not valid for this type of message. Raising this
exception will prevent this user from receiving the message, but allow other messages to be sent to other
users.
"""
"""
return
{}
return
{}
class
InvalidContextError
(
Exception
):
pass
class
ScheduleStartResolver
(
BinnedSchedulesBaseResolver
):
class
ScheduleStartResolver
(
BinnedSchedulesBaseResolver
):
"""
"""
Send a message to all users whose schedule started at ``self.current_date`` + ``day_offset``.
Send a message to all users whose schedule started at ``self.current_date`` + ``day_offset``.
...
@@ -202,13 +210,18 @@ class ScheduleStartResolver(BinnedSchedulesBaseResolver):
...
@@ -202,13 +210,18 @@ class ScheduleStartResolver(BinnedSchedulesBaseResolver):
def
get_template_context
(
self
,
user
,
user_schedules
):
def
get_template_context
(
self
,
user
,
user_schedules
):
first_schedule
=
user_schedules
[
0
]
first_schedule
=
user_schedules
[
0
]
return
{
context
=
{
'course_name'
:
first_schedule
.
enrollment
.
course
.
display_name
,
'course_name'
:
first_schedule
.
enrollment
.
course
.
display_name
,
'course_url'
:
absolute_url
(
'course_url'
:
absolute_url
(
self
.
site
,
reverse
(
'course_root'
,
args
=
[
str
(
first_schedule
.
enrollment
.
course_id
)])
self
.
site
,
reverse
(
'course_root'
,
args
=
[
str
(
first_schedule
.
enrollment
.
course_id
)])
),
),
}
}
# Information for including upsell messaging in template.
context
.
update
(
_get_upsell_information_for_schedule
(
user
,
first_schedule
))
return
context
def
_get_datetime_beginning_of_day
(
dt
):
def
_get_datetime_beginning_of_day
(
dt
):
"""
"""
...
@@ -226,20 +239,41 @@ class UpgradeReminderResolver(BinnedSchedulesBaseResolver):
...
@@ -226,20 +239,41 @@ class UpgradeReminderResolver(BinnedSchedulesBaseResolver):
num_bins
=
UPGRADE_REMINDER_NUM_BINS
num_bins
=
UPGRADE_REMINDER_NUM_BINS
def
get_template_context
(
self
,
user
,
user_schedules
):
def
get_template_context
(
self
,
user
,
user_schedules
):
first_schedule
=
user_schedules
[
0
]
course_id_strs
=
[]
return
{
course_links
=
[]
'course_links'
:
[
first_valid_upsell_context
=
None
{
first_schedule
=
None
'url'
:
absolute_url
(
self
.
site
,
reverse
(
'course_root'
,
args
=
[
str
(
s
.
enrollment
.
course_id
)])),
for
schedule
in
user_schedules
:
'name'
:
s
.
enrollment
.
course
.
display_name
upsell_context
=
_get_upsell_information_for_schedule
(
user
,
schedule
)
}
for
s
in
user_schedules
if
not
upsell_context
[
'show_upsell'
]:
],
continue
if
first_valid_upsell_context
is
None
:
first_schedule
=
schedule
first_valid_upsell_context
=
upsell_context
course_id_str
=
str
(
schedule
.
enrollment
.
course_id
)
course_id_strs
.
append
(
course_id_str
)
course_links
.
append
({
'url'
:
absolute_url
(
self
.
site
,
reverse
(
'course_root'
,
args
=
[
course_id_str
])),
'name'
:
schedule
.
enrollment
.
course
.
display_name
})
if
first_schedule
is
None
:
self
.
log_debug
(
'No courses eligible for upgrade for user.'
)
raise
InvalidContextError
()
context
=
{
'course_links'
:
course_links
,
'first_course_name'
:
first_schedule
.
enrollment
.
course
.
display_name
,
'first_course_name'
:
first_schedule
.
enrollment
.
course
.
display_name
,
'cert_image'
:
absolute_url
(
self
.
site
,
static
(
'course_experience/images/verified-cert.png'
)),
'cert_image'
:
absolute_url
(
self
.
site
,
static
(
'course_experience/images/verified-cert.png'
)),
'course_ids'
:
course_id_strs
,
}
}
context
.
update
(
first_valid_upsell_context
)
return
context
def
_add_upsell_button_information_to_template_context
(
user
,
schedule
,
template_context
):
def
_get_upsell_information_for_schedule
(
user
,
schedule
):
template_context
=
{}
enrollment
=
schedule
.
enrollment
enrollment
=
schedule
.
enrollment
course
=
enrollment
.
course
course
=
enrollment
.
course
...
@@ -258,6 +292,7 @@ def _add_upsell_button_information_to_template_context(user, schedule, template_
...
@@ -258,6 +292,7 @@ def _add_upsell_button_information_to_template_context(user, schedule, template_
)
)
template_context
[
'show_upsell'
]
=
has_verified_upgrade_link
template_context
[
'show_upsell'
]
=
has_verified_upgrade_link
return
template_context
def
_get_verified_upgrade_link
(
user
,
schedule
):
def
_get_verified_upgrade_link
(
user
,
schedule
):
...
@@ -293,10 +328,9 @@ class CourseUpdateResolver(BinnedSchedulesBaseResolver):
...
@@ -293,10 +328,9 @@ class CourseUpdateResolver(BinnedSchedulesBaseResolver):
course_id_str
=
str
(
enrollment
.
course_id
)
course_id_str
=
str
(
enrollment
.
course_id
)
template_context
.
update
({
template_context
.
update
({
'student_name'
:
user
.
profile
.
name
,
'course_name'
:
schedule
.
enrollment
.
course
.
display_name
,
'course_name'
:
schedule
.
enrollment
.
course
.
display_name
,
'course_url'
:
absolute_url
(
'course_url'
:
absolute_url
(
self
.
site
,
reverse
(
'course_root'
,
args
=
[
str
(
schedule
.
enrollment
.
course_id
)
])
self
.
site
,
reverse
(
'course_root'
,
args
=
[
course_id_str
])
),
),
'week_num'
:
week_num
,
'week_num'
:
week_num
,
'week_summary'
:
week_summary
,
'week_summary'
:
week_summary
,
...
@@ -304,6 +338,7 @@ class CourseUpdateResolver(BinnedSchedulesBaseResolver):
...
@@ -304,6 +338,7 @@ class CourseUpdateResolver(BinnedSchedulesBaseResolver):
# This is used by the bulk email optout policy
# This is used by the bulk email optout policy
'course_ids'
:
[
course_id_str
],
'course_ids'
:
[
course_id_str
],
})
})
template_context
.
update
(
_get_upsell_information_for_schedule
(
user
,
schedule
))
yield
(
user
,
schedule
.
enrollment
.
course
.
language
,
template_context
)
yield
(
user
,
schedule
.
enrollment
.
course
.
language
,
template_context
)
...
...
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