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
7fd643fa
Unverified
Commit
7fd643fa
authored
Oct 30, 2017
by
Gabe Mulley
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add tests for experience types, ensure courses have a verified mode
parent
57bf89c8
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
245 additions
and
180 deletions
+245
-180
common/djangoapps/student/models.py
+15
-9
common/djangoapps/student/tests/factories.py
+29
-3
common/djangoapps/student/tests/test_certificates.py
+2
-0
lms/djangoapps/certificates/tests/test_webview_views.py
+5
-1
lms/djangoapps/courseware/date_summary.py
+9
-6
lms/djangoapps/courseware/tests/test_view_authentication.py
+1
-0
lms/djangoapps/courseware/tests/test_views.py
+6
-1
lms/djangoapps/discussion/tests/test_views.py
+4
-4
openedx/core/djangoapps/schedules/management/commands/tests/send_email_base.py
+83
-55
openedx/core/djangoapps/schedules/management/commands/tests/test_experiences.py
+0
-59
openedx/core/djangoapps/schedules/management/commands/tests/test_send_course_update.py
+17
-2
openedx/core/djangoapps/schedules/management/commands/tests/test_send_recurring_nudge.py
+16
-1
openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py
+26
-11
openedx/core/djangoapps/schedules/management/commands/tests/upsell_base.py
+2
-5
openedx/core/djangoapps/schedules/models.py
+6
-7
openedx/core/djangoapps/schedules/resolvers.py
+7
-6
openedx/core/djangoapps/schedules/signals.py
+3
-3
openedx/core/djangoapps/schedules/tests/factories.py
+1
-1
openedx/core/djangoapps/schedules/tests/test_signals.py
+13
-6
No files found.
common/djangoapps/student/models.py
View file @
7fd643fa
...
@@ -1686,9 +1686,13 @@ class CourseEnrollment(models.Model):
...
@@ -1686,9 +1686,13 @@ class CourseEnrollment(models.Model):
"""
"""
if
not
self
.
_course_overview
:
if
not
self
.
_course_overview
:
try
:
try
:
self
.
_course_overview
=
CourseOverview
.
get_from_id
(
self
.
course_id
)
self
.
_course_overview
=
self
.
course
except
(
CourseOverview
.
DoesNotExist
,
IOError
):
except
CourseOverview
.
DoesNotExist
:
self
.
_course_overview
=
None
log
.
info
(
'Course Overviews: unable to find course overview for enrollment, loading from modulestore.'
)
try
:
self
.
_course_overview
=
CourseOverview
.
get_from_id
(
self
.
course_id
)
except
(
CourseOverview
.
DoesNotExist
,
IOError
):
self
.
_course_overview
=
None
return
self
.
_course_overview
return
self
.
_course_overview
@cached_property
@cached_property
...
@@ -1717,7 +1721,14 @@ class CourseEnrollment(models.Model):
...
@@ -1717,7 +1721,14 @@ class CourseEnrollment(models.Model):
# When course modes expire they aren't found any more and None would be returned.
# When course modes expire they aren't found any more and None would be returned.
# Replicate that behavior here by returning None if the personalized deadline is in the past.
# Replicate that behavior here by returning None if the personalized deadline is in the past.
if
datetime
.
now
(
UTC
)
>=
self
.
dynamic_upgrade_deadline
:
if
datetime
.
now
(
UTC
)
>=
self
.
dynamic_upgrade_deadline
:
log
.
debug
(
'Schedules: Returning None since dynamic upgrade deadline has already passed.'
)
return
None
return
None
if
self
.
verified_mode
is
None
:
log
.
debug
(
'Schedules: Returning None for dynamic upgrade deadline since the course does not have a '
'verified mode.'
)
return
None
return
self
.
dynamic_upgrade_deadline
return
self
.
dynamic_upgrade_deadline
return
self
.
course_upgrade_deadline
return
self
.
course_upgrade_deadline
...
@@ -1733,12 +1744,7 @@ class CourseEnrollment(models.Model):
...
@@ -1733,12 +1744,7 @@ class CourseEnrollment(models.Model):
Returns:
Returns:
datetime|None
datetime|None
"""
"""
try
:
if
not
self
.
course_overview
.
self_paced
:
course_overview
=
self
.
course
except
CourseOverview
.
DoesNotExist
:
course_overview
=
self
.
course_overview
if
not
course_overview
.
self_paced
:
return
None
return
None
if
not
DynamicUpgradeDeadlineConfiguration
.
is_enabled
():
if
not
DynamicUpgradeDeadlineConfiguration
.
is_enabled
():
...
...
common/djangoapps/student/tests/factories.py
View file @
7fd643fa
...
@@ -12,6 +12,8 @@ from opaque_keys.edx.keys import CourseKey
...
@@ -12,6 +12,8 @@ from opaque_keys.edx.keys import CourseKey
from
pytz
import
UTC
from
pytz
import
UTC
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.content.course_overviews.tests.factories
import
CourseOverviewFactory
from
student.models
import
(
from
student.models
import
(
CourseAccessRole
,
CourseAccessRole
,
CourseEnrollment
,
CourseEnrollment
,
...
@@ -126,9 +128,33 @@ class CourseEnrollmentFactory(DjangoModelFactory):
...
@@ -126,9 +128,33 @@ class CourseEnrollmentFactory(DjangoModelFactory):
model
=
CourseEnrollment
model
=
CourseEnrollment
user
=
factory
.
SubFactory
(
UserFactory
)
user
=
factory
.
SubFactory
(
UserFactory
)
course
=
factory
.
SubFactory
(
'openedx.core.djangoapps.content.course_overviews.tests.factories.CourseOverviewFactory'
,
@classmethod
)
def
_create
(
cls
,
model_class
,
*
args
,
**
kwargs
):
manager
=
cls
.
_get_manager
(
model_class
)
course_kwargs
=
{}
for
key
in
kwargs
.
keys
():
if
key
.
startswith
(
'course__'
):
course_kwargs
[
key
.
split
(
'__'
)[
1
]]
=
kwargs
.
pop
(
key
)
if
'course'
not
in
kwargs
:
course_id
=
kwargs
.
get
(
'course_id'
)
course_overview
=
None
if
course_id
is
not
None
:
if
isinstance
(
course_id
,
basestring
):
course_id
=
CourseKey
.
from_string
(
course_id
)
course_kwargs
.
setdefault
(
'id'
,
course_id
)
try
:
course_overview
=
CourseOverview
.
get_from_id
(
course_id
)
except
CourseOverview
.
DoesNotExist
:
pass
if
course_overview
is
None
:
course_overview
=
CourseOverviewFactory
(
**
course_kwargs
)
kwargs
[
'course'
]
=
course_overview
return
manager
.
create
(
*
args
,
**
kwargs
)
class
CourseAccessRoleFactory
(
DjangoModelFactory
):
class
CourseAccessRoleFactory
(
DjangoModelFactory
):
...
...
common/djangoapps/student/tests/test_certificates.py
View file @
7fd643fa
...
@@ -111,6 +111,8 @@ class CertificateDashboardMessageDisplayTest(CertificateDisplayTestBase):
...
@@ -111,6 +111,8 @@ class CertificateDashboardMessageDisplayTest(CertificateDisplayTestBase):
Tests the certificates messages for a course in the dashboard.
Tests the certificates messages for a course in the dashboard.
"""
"""
ENABLED_SIGNALS
=
[
'course_published'
]
@classmethod
@classmethod
def
setUpClass
(
cls
):
def
setUpClass
(
cls
):
super
(
CertificateDashboardMessageDisplayTest
,
cls
)
.
setUpClass
()
super
(
CertificateDashboardMessageDisplayTest
,
cls
)
.
setUpClass
()
...
...
lms/djangoapps/certificates/tests/test_webview_views.py
View file @
7fd643fa
...
@@ -12,8 +12,9 @@ from django.conf import settings
...
@@ -12,8 +12,9 @@ from django.conf import settings
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.test.client
import
Client
,
RequestFactory
from
django.test.client
import
Client
,
RequestFactory
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
from
util.date_utils
import
strftime_localized
from
util.date_utils
import
strftime_localized
from
mock
import
Mock
,
patch
from
mock
import
patch
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
from
certificates.api
import
get_certificate_url
from
certificates.api
import
get_certificate_url
...
@@ -73,6 +74,9 @@ class CommonCertificatesTestCase(ModuleStoreTestCase):
...
@@ -73,6 +74,9 @@ class CommonCertificatesTestCase(ModuleStoreTestCase):
"""
"""
Common setUp and utility methods for Certificate tests
Common setUp and utility methods for Certificate tests
"""
"""
ENABLED_SIGNALS
=
[
'course_published'
]
def
setUp
(
self
):
def
setUp
(
self
):
super
(
CommonCertificatesTestCase
,
self
)
.
setUp
()
super
(
CommonCertificatesTestCase
,
self
)
.
setUp
()
self
.
client
=
Client
()
self
.
client
=
Client
()
...
...
lms/djangoapps/courseware/date_summary.py
View file @
7fd643fa
...
@@ -405,16 +405,19 @@ def verified_upgrade_deadline_link(user, course=None, course_id=None):
...
@@ -405,16 +405,19 @@ def verified_upgrade_deadline_link(user, course=None, course_id=None):
ecommerce_service
=
EcommerceService
()
ecommerce_service
=
EcommerceService
()
if
ecommerce_service
.
is_enabled
(
user
):
if
ecommerce_service
.
is_enabled
(
user
):
if
course
is
not
None
and
isinstance
(
course
,
CourseOverview
):
course_mode
=
CourseMode
.
verified_mode_for_course
(
course_id
)
course_mode
=
course
.
modes
.
get
(
mode_slug
=
CourseMode
.
VERIFIED
)
if
course_mode
is
not
None
:
return
ecommerce_service
.
get_checkout_page_url
(
course_mode
.
sku
)
else
:
else
:
course_mode
=
CourseMode
.
objects
.
get
(
raise
CourseModeNotFoundException
(
'Cannot generate a verified upgrade link without a valid verified mode'
course_id
=
course_id
,
mode_slug
=
CourseMode
.
VERIFIED
' for course {}'
.
format
(
unicode
(
course_id
)))
)
return
ecommerce_service
.
get_checkout_page_url
(
course_mode
.
sku
)
return
reverse
(
'verify_student_upgrade_and_verify'
,
args
=
(
course_id
,))
return
reverse
(
'verify_student_upgrade_and_verify'
,
args
=
(
course_id
,))
class
CourseModeNotFoundException
(
Exception
):
pass
def
verified_upgrade_link_is_valid
(
enrollment
=
None
):
def
verified_upgrade_link_is_valid
(
enrollment
=
None
):
"""
"""
Return whether this enrollment can be upgraded.
Return whether this enrollment can be upgraded.
...
...
lms/djangoapps/courseware/tests/test_view_authentication.py
View file @
7fd643fa
...
@@ -29,6 +29,7 @@ class TestViewAuth(EnterpriseTestConsentRequired, ModuleStoreTestCase, LoginEnro
...
@@ -29,6 +29,7 @@ class TestViewAuth(EnterpriseTestConsentRequired, ModuleStoreTestCase, LoginEnro
"""
"""
ACCOUNT_INFO
=
[(
'view@test.com'
,
'foo'
),
(
'view2@test.com'
,
'foo'
)]
ACCOUNT_INFO
=
[(
'view@test.com'
,
'foo'
),
(
'view2@test.com'
,
'foo'
)]
ENABLED_SIGNALS
=
[
'course_published'
]
@staticmethod
@staticmethod
def
_reverse_urls
(
names
,
course
):
def
_reverse_urls
(
names
,
course
):
...
...
lms/djangoapps/courseware/tests/test_views.py
View file @
7fd643fa
...
@@ -807,7 +807,12 @@ class ViewsTestCase(ModuleStoreTestCase):
...
@@ -807,7 +807,12 @@ class ViewsTestCase(ModuleStoreTestCase):
CourseModeFactory
.
create
(
mode_slug
=
CourseMode
.
VERIFIED
,
course_id
=
course
)
CourseModeFactory
.
create
(
mode_slug
=
CourseMode
.
VERIFIED
,
course_id
=
course
)
# Enroll user in the course
# Enroll user in the course
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course
,
user
=
self
.
user
,
mode
=
CourseMode
.
AUDIT
)
# Don't use the CourseEnrollmentFactory since it ensures a CourseOverview is available
enrollment
=
CourseEnrollment
.
objects
.
create
(
course_id
=
course
,
user
=
self
.
user
,
mode
=
CourseMode
.
AUDIT
,
)
self
.
assertEqual
(
enrollment
.
course_overview
,
None
)
self
.
assertEqual
(
enrollment
.
course_overview
,
None
)
...
...
lms/djangoapps/discussion/tests/test_views.py
View file @
7fd643fa
...
@@ -403,15 +403,15 @@ class SingleThreadQueryCountTestCase(ForumsEnableMixin, ModuleStoreTestCase):
...
@@ -403,15 +403,15 @@ class SingleThreadQueryCountTestCase(ForumsEnableMixin, ModuleStoreTestCase):
# course is outside the context manager that is verifying the number of queries,
# course is outside the context manager that is verifying the number of queries,
# and with split mongo, that method ends up querying disabled_xblocks (which is then
# and with split mongo, that method ends up querying disabled_xblocks (which is then
# cached and hence not queried as part of call_single_thread).
# cached and hence not queried as part of call_single_thread).
(
ModuleStoreEnum
.
Type
.
mongo
,
False
,
1
,
6
,
4
,
1
7
,
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
False
,
1
,
6
,
4
,
1
6
,
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
False
,
50
,
6
,
4
,
1
7
,
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
False
,
50
,
6
,
4
,
1
6
,
4
),
# split mongo: 3 queries, regardless of thread response size.
# split mongo: 3 queries, regardless of thread response size.
(
ModuleStoreEnum
.
Type
.
split
,
False
,
1
,
3
,
3
,
16
,
4
),
(
ModuleStoreEnum
.
Type
.
split
,
False
,
1
,
3
,
3
,
16
,
4
),
(
ModuleStoreEnum
.
Type
.
split
,
False
,
50
,
3
,
3
,
16
,
4
),
(
ModuleStoreEnum
.
Type
.
split
,
False
,
50
,
3
,
3
,
16
,
4
),
# Enabling Enterprise integration should have no effect on the number of mongo queries made.
# Enabling Enterprise integration should have no effect on the number of mongo queries made.
(
ModuleStoreEnum
.
Type
.
mongo
,
True
,
1
,
6
,
4
,
1
7
,
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
True
,
1
,
6
,
4
,
1
6
,
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
True
,
50
,
6
,
4
,
1
7
,
4
),
(
ModuleStoreEnum
.
Type
.
mongo
,
True
,
50
,
6
,
4
,
1
6
,
4
),
# split mongo: 3 queries, regardless of thread response size.
# split mongo: 3 queries, regardless of thread response size.
(
ModuleStoreEnum
.
Type
.
split
,
True
,
1
,
3
,
3
,
16
,
4
),
(
ModuleStoreEnum
.
Type
.
split
,
True
,
1
,
3
,
3
,
16
,
4
),
(
ModuleStoreEnum
.
Type
.
split
,
True
,
50
,
3
,
3
,
16
,
4
),
(
ModuleStoreEnum
.
Type
.
split
,
True
,
50
,
3
,
3
,
16
,
4
),
...
...
openedx/core/djangoapps/schedules/management/commands/tests/send_email_base.py
View file @
7fd643fa
from
collections
import
namedtuple
,
defaultdict
from
copy
import
deepcopy
from
copy
import
deepcopy
import
datetime
import
datetime
import
ddt
import
ddt
...
@@ -9,6 +10,9 @@ from freezegun import freeze_time
...
@@ -9,6 +10,9 @@ from freezegun import freeze_time
from
mock
import
Mock
,
patch
from
mock
import
Mock
,
patch
import
pytz
import
pytz
from
commerce.models
import
CommerceConfiguration
from
course_modes.models
import
CourseMode
from
course_modes.tests.factories
import
CourseModeFactory
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
edx_ace.channel
import
ChannelType
from
edx_ace.channel
import
ChannelType
from
edx_ace.utils.date
import
serialize
from
edx_ace.utils.date
import
serialize
...
@@ -20,10 +24,12 @@ from openedx.core.djangoapps.schedules.resolvers import _get_datetime_beginning_
...
@@ -20,10 +24,12 @@ from openedx.core.djangoapps.schedules.resolvers import _get_datetime_beginning_
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleConfigFactory
,
ScheduleFactory
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleConfigFactory
,
ScheduleFactory
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
FilteredQueryCountMixin
,
CacheIsolationTestCase
from
openedx.core.djangolib.testing.utils
import
FilteredQueryCountMixin
,
CacheIsolationTestCase
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
SITE_QUERY
=
2
# django_site, site_configuration_siteconfiguration
SITE_QUERY
=
1
# django_site
SITE_CONFIG_QUERY
=
1
# site_configuration_siteconfiguration
SCHEDULES_QUERY
=
1
# schedules_schedule
SCHEDULES_QUERY
=
1
# schedules_schedule
COURSE_MODES_QUERY
=
1
# course_modes_coursemode
COURSE_MODES_QUERY
=
1
# course_modes_coursemode
...
@@ -33,18 +39,14 @@ ORG_DEADLINE_QUERY = 1 # courseware_orgdynamicupgradedeadlineconfiguration
...
@@ -33,18 +39,14 @@ ORG_DEADLINE_QUERY = 1 # courseware_orgdynamicupgradedeadlineconfiguration
COURSE_DEADLINE_QUERY
=
1
# courseware_coursedynamicupgradedeadlineconfiguration
COURSE_DEADLINE_QUERY
=
1
# courseware_coursedynamicupgradedeadlineconfiguration
COMMERCE_CONFIG_QUERY
=
1
# commerce_commerceconfiguration
COMMERCE_CONFIG_QUERY
=
1
# commerce_commerceconfiguration
NUM_QUERIES_
NO_MATCHING
_SCHEDULES
=
(
NUM_QUERIES_
SITE
_SCHEDULES
=
(
SITE_QUERY
+
SITE_QUERY
+
SITE_CONFIG_QUERY
+
SCHEDULES_QUERY
SCHEDULES_QUERY
)
)
NUM_QUERIES_WITH_MATCHES
=
(
NUM_QUERIES_NO_MATCHING_SCHEDULES
+
COURSE_MODES_QUERY
)
NUM_QUERIES_FIRST_MATCH
=
(
NUM_QUERIES_FIRST_MATCH
=
(
NUM_QUERIES_
WITH_MATCH
ES
NUM_QUERIES_
SITE_SCHEDUL
ES
+
GLOBAL_DEADLINE_QUERY
+
GLOBAL_DEADLINE_QUERY
+
ORG_DEADLINE_QUERY
+
ORG_DEADLINE_QUERY
+
COURSE_DEADLINE_QUERY
+
COURSE_DEADLINE_QUERY
...
@@ -54,6 +56,9 @@ NUM_QUERIES_FIRST_MATCH = (
...
@@ -54,6 +56,9 @@ NUM_QUERIES_FIRST_MATCH = (
LOG
=
logging
.
getLogger
(
__name__
)
LOG
=
logging
.
getLogger
(
__name__
)
ExperienceTest
=
namedtuple
(
'ExperienceTest'
,
'experience offset email_sent'
)
@ddt.ddt
@ddt.ddt
@freeze_time
(
'2017-08-01 00:00:00'
,
tz_offset
=
0
,
tick
=
True
)
@freeze_time
(
'2017-08-01 00:00:00'
,
tz_offset
=
0
,
tick
=
True
)
class
ScheduleSendEmailTestBase
(
FilteredQueryCountMixin
,
CacheIsolationTestCase
):
class
ScheduleSendEmailTestBase
(
FilteredQueryCountMixin
,
CacheIsolationTestCase
):
...
@@ -73,6 +78,9 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -73,6 +78,9 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
ScheduleConfigFactory
.
create
(
site
=
self
.
site_config
.
site
)
ScheduleConfigFactory
.
create
(
site
=
self
.
site_config
.
site
)
DynamicUpgradeDeadlineConfiguration
.
objects
.
create
(
enabled
=
True
)
DynamicUpgradeDeadlineConfiguration
.
objects
.
create
(
enabled
=
True
)
CommerceConfiguration
.
objects
.
create
(
checkout_on_ecommerce_service
=
True
)
self
.
_courses_with_verified_modes
=
set
()
def
_calculate_bin_for_user
(
self
,
user
):
def
_calculate_bin_for_user
(
self
,
user
):
return
user
.
id
%
self
.
task
.
num_bins
return
user
.
id
%
self
.
task
.
num_bins
...
@@ -92,6 +100,24 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -92,6 +100,24 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
templates_override
[
0
][
'OPTIONS'
][
'string_if_invalid'
]
=
"TEMPLATE WARNING - MISSING VARIABLE [
%
s]"
templates_override
[
0
][
'OPTIONS'
][
'string_if_invalid'
]
=
"TEMPLATE WARNING - MISSING VARIABLE [
%
s]"
return
templates_override
return
templates_override
def
_schedule_factory
(
self
,
offset
=
None
,
**
factory_kwargs
):
_
,
_
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
(
offset
=
offset
)
factory_kwargs
.
setdefault
(
'start'
,
target_day
)
factory_kwargs
.
setdefault
(
'upgrade_deadline'
,
upgrade_deadline
)
factory_kwargs
.
setdefault
(
'enrollment__course__self_paced'
,
True
)
if
hasattr
(
self
,
'experience_type'
):
factory_kwargs
.
setdefault
(
'experience__experience_type'
,
self
.
experience_type
)
schedule
=
ScheduleFactory
(
**
factory_kwargs
)
course_id
=
schedule
.
enrollment
.
course_id
if
course_id
not
in
self
.
_courses_with_verified_modes
:
CourseModeFactory
(
course_id
=
course_id
,
mode_slug
=
CourseMode
.
VERIFIED
,
expiration_datetime
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
30
),
)
self
.
_courses_with_verified_modes
.
add
(
course_id
)
return
schedule
def
test_command_task_binding
(
self
):
def
test_command_task_binding
(
self
):
self
.
assertEqual
(
self
.
command
.
async_send_task
,
self
.
task
)
self
.
assertEqual
(
self
.
command
.
async_send_task
,
self
.
task
)
...
@@ -130,11 +156,7 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -130,11 +156,7 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
with
patch
.
object
(
self
.
task
,
'async_send_task'
)
as
mock_schedule_send
:
with
patch
.
object
(
self
.
task
,
'async_send_task'
)
as
mock_schedule_send
:
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
schedules
=
[
schedules
=
[
ScheduleFactory
.
create
(
self
.
_schedule_factory
()
for
_
in
range
(
schedule_count
)
start
=
target_day
,
upgrade_deadline
=
upgrade_deadline
,
enrollment__course__self_paced
=
True
,
)
for
_
in
range
(
schedule_count
)
]
]
bins_in_use
=
frozenset
((
self
.
_calculate_bin_for_user
(
s
.
enrollment
.
user
))
for
s
in
schedules
)
bins_in_use
=
frozenset
((
self
.
_calculate_bin_for_user
(
s
.
enrollment
.
user
))
for
s
in
schedules
)
...
@@ -142,18 +164,17 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -142,18 +164,17 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
target_day_str
=
serialize
(
target_day
)
target_day_str
=
serialize
(
target_day
)
for
b
in
range
(
self
.
task
.
num_bins
):
for
b
in
range
(
self
.
task
.
num_bins
):
LOG
.
debug
(
'
Runn
ing bin
%
d'
,
b
)
LOG
.
debug
(
'
Check
ing bin
%
d'
,
b
)
expected_queries
=
NUM_QUERIES_
NO_MATCHING
_SCHEDULES
expected_queries
=
NUM_QUERIES_
SITE
_SCHEDULES
if
b
in
bins_in_use
:
if
b
in
bins_in_use
:
if
is_first_match
:
if
is_first_match
:
expected_queries
=
(
expected_queries
=
(
# Since this is the first match, we need to cache all of the config models, so we run a
# Since this is the first match, we need to cache all of the config models, so we run a
# query for each of those...
# query for each of those...
NUM_QUERIES_FIRST_MATCH
NUM_QUERIES_FIRST_MATCH
+
COURSE_MODES_QUERY
# to cache the course modes for this course
)
)
is_first_match
=
False
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
):
self
.
task
.
apply
(
kwargs
=
dict
(
self
.
task
.
apply
(
kwargs
=
dict
(
...
@@ -171,13 +192,12 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -171,13 +192,12 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
def
test_no_course_overview
(
self
):
def
test_no_course_overview
(
self
):
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
schedule
=
ScheduleFactory
.
create
(
# Don't use CourseEnrollmentFactory since it creates a course overview
start
=
target_day
,
enrollment
=
CourseEnrollment
.
objects
.
create
(
upgrade_deadline
=
upgrade_deadline
,
course_id
=
CourseKey
.
from_string
(
'edX/toy/Not_2012_Fall'
)
,
enrollment__course__self_paced
=
True
,
user
=
UserFactory
.
create
()
,
)
)
schedule
.
enrollment
.
course_id
=
CourseKey
.
from_string
(
'edX/toy/Not_2012_Fall'
)
schedule
=
self
.
_schedule_factory
(
enrollment
=
enrollment
)
schedule
.
enrollment
.
save
()
with
patch
.
object
(
self
.
task
,
'async_send_task'
)
as
mock_schedule_send
:
with
patch
.
object
(
self
.
task
,
'async_send_task'
)
as
mock_schedule_send
:
for
b
in
range
(
self
.
task
.
num_bins
):
for
b
in
range
(
self
.
task
.
num_bins
):
...
@@ -249,25 +269,16 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -249,25 +269,16 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
user2
=
UserFactory
.
create
(
id
=
self
.
task
.
num_bins
*
2
)
user2
=
UserFactory
.
create
(
id
=
self
.
task
.
num_bins
*
2
)
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
ScheduleFactory
.
create
(
self
.
_schedule_factory
(
upgrade_deadline
=
upgrade_deadline
,
start
=
target_day
,
enrollment__course__org
=
filtered_org
,
enrollment__course__org
=
filtered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user1
,
enrollment__user
=
user1
,
)
)
ScheduleFactory
.
create
(
self
.
_schedule_factory
(
upgrade_deadline
=
upgrade_deadline
,
start
=
target_day
,
enrollment__course__org
=
unfiltered_org
,
enrollment__course__org
=
unfiltered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user1
,
enrollment__user
=
user1
,
)
)
ScheduleFactory
.
create
(
self
.
_schedule_factory
(
upgrade_deadline
=
upgrade_deadline
,
start
=
target_day
,
enrollment__course__org
=
unfiltered_org
,
enrollment__course__org
=
unfiltered_org
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user2
,
enrollment__user
=
user2
,
)
)
...
@@ -284,18 +295,13 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -284,18 +295,13 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
user1
=
UserFactory
.
create
(
id
=
self
.
task
.
num_bins
)
user1
=
UserFactory
.
create
(
id
=
self
.
task
.
num_bins
)
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
schedule
=
ScheduleFactory
.
create
(
end_date_offset
=
-
2
if
has_course_ended
else
2
start
=
target_day
,
self
.
_schedule_factory
(
upgrade_deadline
=
upgrade_deadline
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user1
,
enrollment__user
=
user1
,
enrollment__course__start
=
current_day
-
datetime
.
timedelta
(
days
=
30
),
enrollment__course__end
=
current_day
+
datetime
.
timedelta
(
days
=
end_date_offset
)
)
)
schedule
.
enrollment
.
course
.
start
=
current_day
-
datetime
.
timedelta
(
days
=
30
)
end_date_offset
=
-
2
if
has_course_ended
else
2
schedule
.
enrollment
.
course
.
end
=
current_day
+
datetime
.
timedelta
(
days
=
end_date_offset
)
schedule
.
enrollment
.
course
.
save
()
with
patch
.
object
(
self
.
task
,
'async_send_task'
)
as
mock_schedule_send
:
with
patch
.
object
(
self
.
task
,
'async_send_task'
)
as
mock_schedule_send
:
self
.
task
.
apply
(
kwargs
=
dict
(
self
.
task
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
0
,
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
0
,
...
@@ -312,15 +318,14 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -312,15 +318,14 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
num_courses
=
3
num_courses
=
3
for
course_index
in
range
(
num_courses
):
for
course_index
in
range
(
num_courses
):
ScheduleFactory
.
create
(
self
.
_schedule_factory
(
start
=
target_day
,
upgrade_deadline
=
upgrade_deadline
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user
,
enrollment__user
=
user
,
enrollment__course__id
=
CourseKey
.
from_string
(
'edX/toy/course{}'
.
format
(
course_index
))
enrollment__course__id
=
CourseKey
.
from_string
(
'edX/toy/course{}'
.
format
(
course_index
))
)
)
additional_course_queries
=
num_courses
-
1
if
self
.
queries_deadline_for_each_course
else
0
# 2 queries per course, one for the course opt out and one for the course modes
# one query for course modes for the first schedule if we aren't checking the deadline for each course
additional_course_queries
=
(
num_courses
*
2
)
-
1
if
self
.
queries_deadline_for_each_course
else
1
expected_query_count
=
NUM_QUERIES_FIRST_MATCH
+
additional_course_queries
expected_query_count
=
NUM_QUERIES_FIRST_MATCH
+
additional_course_queries
with
self
.
assertNumQueries
(
expected_query_count
,
table_blacklist
=
WAFFLE_TABLES
):
with
self
.
assertNumQueries
(
expected_query_count
,
table_blacklist
=
WAFFLE_TABLES
):
with
patch
.
object
(
self
.
task
,
'async_send_task'
)
as
mock_schedule_send
:
with
patch
.
object
(
self
.
task
,
'async_send_task'
)
as
mock_schedule_send
:
...
@@ -333,7 +338,9 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -333,7 +338,9 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
expected_call_count
)
self
.
assertEqual
(
mock_schedule_send
.
apply_async
.
call_count
,
expected_call_count
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
self
.
assertFalse
(
mock_ace
.
send
.
called
)
@ddt.data
(
1
,
10
,
100
)
@ddt.data
(
1
,
10
)
def
test_templates
(
self
,
message_count
):
def
test_templates
(
self
,
message_count
):
for
offset
in
self
.
expected_offsets
:
for
offset
in
self
.
expected_offsets
:
self
.
_assert_template_for_offset
(
offset
,
message_count
)
self
.
_assert_template_for_offset
(
offset
,
message_count
)
...
@@ -344,10 +351,8 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -344,10 +351,8 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
user
=
UserFactory
.
create
()
user
=
UserFactory
.
create
()
for
course_index
in
range
(
message_count
):
for
course_index
in
range
(
message_count
):
ScheduleFactory
.
create
(
self
.
_schedule_factory
(
start
=
target_day
,
offset
=
offset
,
upgrade_deadline
=
upgrade_deadline
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user
,
enrollment__user
=
user
,
enrollment__course__id
=
CourseKey
.
from_string
(
'edX/toy/course{}'
.
format
(
course_index
))
enrollment__course__id
=
CourseKey
.
from_string
(
'edX/toy/course{}'
.
format
(
course_index
))
)
)
...
@@ -366,7 +371,10 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -366,7 +371,10 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
num_expected_queries
=
NUM_QUERIES_FIRST_MATCH
num_expected_queries
=
NUM_QUERIES_FIRST_MATCH
if
self
.
queries_deadline_for_each_course
:
if
self
.
queries_deadline_for_each_course
:
num_expected_queries
+=
(
message_count
-
1
)
# one query per course for opt-out and one for course modes
num_expected_queries
+=
(
message_count
*
2
)
-
1
else
:
num_expected_queries
+=
1
with
self
.
assertNumQueries
(
num_expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
with
self
.
assertNumQueries
(
num_expected_queries
,
table_blacklist
=
WAFFLE_TABLES
):
self
.
task
.
apply
(
kwargs
=
dict
(
self
.
task
.
apply
(
kwargs
=
dict
(
...
@@ -385,3 +393,23 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
...
@@ -385,3 +393,23 @@ class ScheduleSendEmailTestBase(FilteredQueryCountMixin, CacheIsolationTestCase)
self
.
assertNotIn
(
"TEMPLATE WARNING"
,
template
)
self
.
assertNotIn
(
"TEMPLATE WARNING"
,
template
)
self
.
assertNotIn
(
"{{"
,
template
)
self
.
assertNotIn
(
"{{"
,
template
)
self
.
assertNotIn
(
"}}"
,
template
)
self
.
assertNotIn
(
"}}"
,
template
)
def
_check_if_email_sent_for_experience
(
self
,
test_config
):
current_day
,
offset
,
target_day
,
_
=
self
.
_get_dates
(
offset
=
test_config
.
offset
)
kwargs
=
{
'offset'
:
offset
}
if
test_config
.
experience
is
None
:
kwargs
[
'experience'
]
=
None
else
:
kwargs
[
'experience__experience_type'
]
=
test_config
.
experience
schedule
=
self
.
_schedule_factory
(
**
kwargs
)
with
patch
.
object
(
tasks
,
'ace'
)
as
mock_ace
:
self
.
task
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
self
.
_calculate_bin_for_user
(
schedule
.
enrollment
.
user
),
))
self
.
assertEqual
(
mock_ace
.
send
.
called
,
test_config
.
email_sent
)
openedx/core/djangoapps/schedules/management/commands/tests/test_experiences.py
deleted
100644 → 0
View file @
57bf89c8
from
collections
import
namedtuple
import
datetime
import
ddt
import
pytz
from
edx_ace.utils.date
import
serialize
from
freezegun
import
freeze_time
from
mock
import
patch
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
openedx.core.djangoapps.schedules
import
tasks
from
openedx.core.djangoapps.schedules.models
import
ScheduleExperience
from
openedx.core.djangoapps.schedules.resolvers
import
_get_datetime_beginning_of_day
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleFactory
,
ScheduleConfigFactory
from
openedx.core.djangoapps.site_configuration.tests.factories
import
SiteFactory
,
SiteConfigurationFactory
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
,
FilteredQueryCountMixin
,
CacheIsolationTestCase
@ddt.ddt
@skip_unless_lms
@freeze_time
(
'2017-08-01 00:00:00'
,
tz_offset
=
0
,
tick
=
True
)
class
TestExperiences
(
FilteredQueryCountMixin
,
CacheIsolationTestCase
):
ENABLED_CACHES
=
[
'default'
]
ExperienceTest
=
namedtuple
(
'ExperienceTest'
,
'experience offset email_sent'
)
def
setUp
(
self
):
super
(
TestExperiences
,
self
)
.
setUp
()
site
=
SiteFactory
.
create
()
self
.
site_config
=
SiteConfigurationFactory
.
create
(
site
=
site
)
ScheduleConfigFactory
.
create
(
site
=
self
.
site_config
.
site
)
DynamicUpgradeDeadlineConfiguration
.
objects
.
create
(
enabled
=
True
)
@ddt.data
(
ExperienceTest
(
experience
=
ScheduleExperience
.
DEFAULT
,
offset
=-
3
,
email_sent
=
True
),
ExperienceTest
(
experience
=
ScheduleExperience
.
DEFAULT
,
offset
=-
10
,
email_sent
=
True
),
ExperienceTest
(
experience
=
ScheduleExperience
.
COURSE_UPDATES
,
offset
=-
3
,
email_sent
=
True
),
ExperienceTest
(
experience
=
ScheduleExperience
.
COURSE_UPDATES
,
offset
=-
10
,
email_sent
=
False
),
)
@patch.object
(
tasks
,
'ace'
)
def
test_experience_type_exclusion
(
self
,
test_config
,
mock_ace
):
current_day
=
_get_datetime_beginning_of_day
(
datetime
.
datetime
.
now
(
pytz
.
UTC
))
target_day
=
current_day
+
datetime
.
timedelta
(
days
=
test_config
.
offset
)
schedule
=
ScheduleFactory
.
create
(
start
=
target_day
,
enrollment__course__self_paced
=
True
,
experience__experience_type
=
test_config
.
experience
,
)
tasks
.
ScheduleRecurringNudge
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
test_config
.
offset
,
bin_num
=
(
schedule
.
enrollment
.
user
.
id
%
tasks
.
ScheduleRecurringNudge
.
num_bins
),
))
self
.
assertEqual
(
mock_ace
.
send
.
called
,
test_config
.
email_sent
)
openedx/core/djangoapps/schedules/management/commands/tests/test_send_course_update.py
View file @
7fd643fa
import
ddt
from
mock
import
patch
from
mock
import
patch
from
unittest
import
skipUnless
from
unittest
import
skipUnless
...
@@ -5,11 +6,16 @@ from django.conf import settings
...
@@ -5,11 +6,16 @@ from django.conf import settings
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_course_update
as
nudge
from
openedx.core.djangoapps.schedules.management.commands
import
send_course_update
as
nudge
from
openedx.core.djangoapps.schedules.management.commands.tests.send_email_base
import
ScheduleSendEmailTestBase
from
openedx.core.djangoapps.schedules.management.commands.tests.send_email_base
import
(
ScheduleSendEmailTestBase
,
ExperienceTest
)
from
openedx.core.djangoapps.schedules.management.commands.tests.upsell_base
import
ScheduleUpsellTestMixin
from
openedx.core.djangoapps.schedules.management.commands.tests.upsell_base
import
ScheduleUpsellTestMixin
from
openedx.core.djangoapps.schedules.models
import
ScheduleExperience
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
@ddt.ddt
@skip_unless_lms
@skip_unless_lms
@skipUnless
(
@skipUnless
(
'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
in
settings
.
INSTALLED_APPS
,
'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
in
settings
.
INSTALLED_APPS
,
...
@@ -25,7 +31,8 @@ class TestSendCourseUpdate(ScheduleUpsellTestMixin, ScheduleSendEmailTestBase):
...
@@ -25,7 +31,8 @@ class TestSendCourseUpdate(ScheduleUpsellTestMixin, ScheduleSendEmailTestBase):
command
=
nudge
.
Command
command
=
nudge
.
Command
deliver_config
=
'deliver_course_update'
deliver_config
=
'deliver_course_update'
enqueue_config
=
'enqueue_course_update'
enqueue_config
=
'enqueue_course_update'
expected_offsets
=
xrange
(
-
7
,
-
77
,
-
7
)
expected_offsets
=
range
(
-
7
,
-
77
,
-
7
)
experience_type
=
ScheduleExperience
.
EXPERIENCES
.
course_updates
queries_deadline_for_each_course
=
True
queries_deadline_for_each_course
=
True
...
@@ -35,3 +42,11 @@ class TestSendCourseUpdate(ScheduleUpsellTestMixin, ScheduleSendEmailTestBase):
...
@@ -35,3 +42,11 @@ class TestSendCourseUpdate(ScheduleUpsellTestMixin, ScheduleSendEmailTestBase):
mock_highlights
=
patcher
.
start
()
mock_highlights
=
patcher
.
start
()
mock_highlights
.
return_value
=
[
'Highlight {}'
.
format
(
num
+
1
)
for
num
in
range
(
3
)]
mock_highlights
.
return_value
=
[
'Highlight {}'
.
format
(
num
+
1
)
for
num
in
range
(
3
)]
self
.
addCleanup
(
patcher
.
stop
)
self
.
addCleanup
(
patcher
.
stop
)
@ddt.data
(
ExperienceTest
(
experience
=
ScheduleExperience
.
EXPERIENCES
.
default
,
offset
=
expected_offsets
[
0
],
email_sent
=
False
),
ExperienceTest
(
experience
=
ScheduleExperience
.
EXPERIENCES
.
course_updates
,
offset
=
expected_offsets
[
0
],
email_sent
=
True
),
ExperienceTest
(
experience
=
None
,
offset
=
expected_offsets
[
0
],
email_sent
=
False
),
)
def
test_schedule_in_different_experience
(
self
,
test_config
):
self
.
_check_if_email_sent_for_experience
(
test_config
)
openedx/core/djangoapps/schedules/management/commands/tests/test_send_recurring_nudge.py
View file @
7fd643fa
from
unittest
import
skipUnless
from
unittest
import
skipUnless
import
ddt
from
django.conf
import
settings
from
django.conf
import
settings
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_recurring_nudge
as
nudge
from
openedx.core.djangoapps.schedules.management.commands
import
send_recurring_nudge
as
nudge
from
openedx.core.djangoapps.schedules.management.commands.tests.send_email_base
import
ScheduleSendEmailTestBase
from
openedx.core.djangoapps.schedules.management.commands.tests.send_email_base
import
ScheduleSendEmailTestBase
,
\
ExperienceTest
from
openedx.core.djangoapps.schedules.management.commands.tests.upsell_base
import
ScheduleUpsellTestMixin
from
openedx.core.djangoapps.schedules.management.commands.tests.upsell_base
import
ScheduleUpsellTestMixin
from
openedx.core.djangoapps.schedules.models
import
ScheduleExperience
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
@ddt.ddt
@skip_unless_lms
@skip_unless_lms
@skipUnless
(
@skipUnless
(
'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
in
settings
.
INSTALLED_APPS
,
'openedx.core.djangoapps.schedules.apps.SchedulesConfig'
in
settings
.
INSTALLED_APPS
,
...
@@ -27,3 +31,14 @@ class TestSendRecurringNudge(ScheduleUpsellTestMixin, ScheduleSendEmailTestBase)
...
@@ -27,3 +31,14 @@ class TestSendRecurringNudge(ScheduleUpsellTestMixin, ScheduleSendEmailTestBase)
expected_offsets
=
(
-
3
,
-
10
)
expected_offsets
=
(
-
3
,
-
10
)
consolidates_emails_for_learner
=
True
consolidates_emails_for_learner
=
True
@ddt.data
(
ExperienceTest
(
experience
=
ScheduleExperience
.
EXPERIENCES
.
default
,
offset
=-
3
,
email_sent
=
True
),
ExperienceTest
(
experience
=
ScheduleExperience
.
EXPERIENCES
.
default
,
offset
=-
10
,
email_sent
=
True
),
ExperienceTest
(
experience
=
ScheduleExperience
.
EXPERIENCES
.
course_updates
,
offset
=-
3
,
email_sent
=
True
),
ExperienceTest
(
experience
=
ScheduleExperience
.
EXPERIENCES
.
course_updates
,
offset
=-
10
,
email_sent
=
False
),
ExperienceTest
(
experience
=
None
,
offset
=-
3
,
email_sent
=
True
),
ExperienceTest
(
experience
=
None
,
offset
=-
10
,
email_sent
=
True
),
)
def
test_nudge_experience
(
self
,
test_config
):
self
.
_check_if_email_sent_for_experience
(
test_config
)
openedx/core/djangoapps/schedules/management/commands/tests/test_send_upgrade_reminder.py
View file @
7fd643fa
...
@@ -11,8 +11,9 @@ from opaque_keys.edx.locator import CourseLocator
...
@@ -11,8 +11,9 @@ from opaque_keys.edx.locator import CourseLocator
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
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.management.commands.tests.send_email_base
import
ScheduleSendEmailTestBase
from
openedx.core.djangoapps.schedules.management.commands.tests.send_email_base
import
ScheduleSendEmailTestBase
,
\
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleFactory
ExperienceTest
from
openedx.core.djangoapps.schedules.models
import
ScheduleExperience
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
from
openedx.core.djangolib.testing.utils
import
skip_unless_lms
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
...
@@ -41,18 +42,14 @@ class TestUpgradeReminder(ScheduleSendEmailTestBase):
...
@@ -41,18 +42,14 @@ class TestUpgradeReminder(ScheduleSendEmailTestBase):
@ddt.data
(
True
,
False
)
@ddt.data
(
True
,
False
)
@patch.object
(
tasks
,
'ace'
)
@patch.object
(
tasks
,
'ace'
)
def
test_verified_learner
(
self
,
is_verified
,
mock_ace
):
def
test_verified_learner
(
self
,
is_verified
,
mock_ace
):
user
=
UserFactory
.
create
(
id
=
self
.
task
.
num_bins
)
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
ScheduleFactory
.
create
(
schedule
=
self
.
_schedule_factory
(
upgrade_deadline
=
upgrade_deadline
,
enrollment__course__self_paced
=
True
,
enrollment__user
=
user
,
enrollment__mode
=
CourseMode
.
VERIFIED
if
is_verified
else
CourseMode
.
AUDIT
,
enrollment__mode
=
CourseMode
.
VERIFIED
if
is_verified
else
CourseMode
.
AUDIT
,
)
)
self
.
task
.
apply
(
kwargs
=
dict
(
self
.
task
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
self
.
_calculate_bin_for_user
(
user
),
bin_num
=
self
.
_calculate_bin_for_user
(
schedule
.
enrollment
.
user
),
))
))
self
.
assertEqual
(
mock_ace
.
send
.
called
,
not
is_verified
)
self
.
assertEqual
(
mock_ace
.
send
.
called
,
not
is_verified
)
...
@@ -62,10 +59,8 @@ class TestUpgradeReminder(ScheduleSendEmailTestBase):
...
@@ -62,10 +59,8 @@ class TestUpgradeReminder(ScheduleSendEmailTestBase):
user
=
UserFactory
.
create
()
user
=
UserFactory
.
create
()
schedules
=
[
schedules
=
[
ScheduleFactory
.
create
(
self
.
_schedule_factory
(
upgrade_deadline
=
upgrade_deadline
,
enrollment__user
=
user
,
enrollment__user
=
user
,
enrollment__course__self_paced
=
True
,
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
i
)),
enrollment__course__id
=
CourseLocator
(
'edX'
,
'toy'
,
'Course{}'
.
format
(
i
)),
enrollment__mode
=
CourseMode
.
VERIFIED
if
i
in
(
0
,
3
)
else
CourseMode
.
AUDIT
,
enrollment__mode
=
CourseMode
.
VERIFIED
if
i
in
(
0
,
3
)
else
CourseMode
.
AUDIT
,
)
)
...
@@ -88,3 +83,23 @@ class TestUpgradeReminder(ScheduleSendEmailTestBase):
...
@@ -88,3 +83,23 @@ class TestUpgradeReminder(ScheduleSendEmailTestBase):
message
.
context
[
'course_ids'
],
message
.
context
[
'course_ids'
],
[
str
(
schedules
[
i
]
.
enrollment
.
course
.
id
)
for
i
in
(
1
,
2
,
4
)]
[
str
(
schedules
[
i
]
.
enrollment
.
course
.
id
)
for
i
in
(
1
,
2
,
4
)]
)
)
@patch.object
(
tasks
,
'ace'
)
def
test_course_without_verified_mode
(
self
,
mock_ace
):
current_day
,
offset
,
target_day
,
upgrade_deadline
=
self
.
_get_dates
()
schedule
=
self
.
_schedule_factory
()
schedule
.
enrollment
.
course
.
modes
.
filter
(
mode_slug
=
CourseMode
.
VERIFIED
)
.
delete
()
self
.
task
.
apply
(
kwargs
=
dict
(
site_id
=
self
.
site_config
.
site
.
id
,
target_day_str
=
serialize
(
target_day
),
day_offset
=
offset
,
bin_num
=
self
.
_calculate_bin_for_user
(
schedule
.
enrollment
.
user
),
))
self
.
assertEqual
(
mock_ace
.
send
.
called
,
False
)
@ddt.data
(
ExperienceTest
(
experience
=
ScheduleExperience
.
EXPERIENCES
.
default
,
offset
=
expected_offsets
[
0
],
email_sent
=
True
),
ExperienceTest
(
experience
=
ScheduleExperience
.
EXPERIENCES
.
course_updates
,
offset
=
expected_offsets
[
0
],
email_sent
=
False
),
ExperienceTest
(
experience
=
None
,
offset
=
expected_offsets
[
0
],
email_sent
=
True
),
)
def
test_upgrade_reminder_experience
(
self
,
test_config
):
self
.
_check_if_email_sent_for_experience
(
test_config
)
openedx/core/djangoapps/schedules/management/commands/tests/upsell_base.py
View file @
7fd643fa
...
@@ -8,7 +8,6 @@ from edx_ace.utils.date import serialize
...
@@ -8,7 +8,6 @@ from edx_ace.utils.date import serialize
from
edx_ace.message
import
Message
from
edx_ace.message
import
Message
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
courseware.models
import
DynamicUpgradeDeadlineConfiguration
from
openedx.core.djangoapps.schedules.tests.factories
import
ScheduleFactory
@ddt.ddt
@ddt.ddt
...
@@ -34,10 +33,8 @@ class ScheduleUpsellTestMixin(object):
...
@@ -34,10 +33,8 @@ class ScheduleUpsellTestMixin(object):
if
testcase
.
set_deadline
:
if
testcase
.
set_deadline
:
upgrade_deadline
=
current_day
+
datetime
.
timedelta
(
days
=
testcase
.
deadline_offset
)
upgrade_deadline
=
current_day
+
datetime
.
timedelta
(
days
=
testcase
.
deadline_offset
)
schedule
=
ScheduleFactory
.
create
(
schedule
=
self
.
_schedule_factory
(
start
=
target_day
,
upgrade_deadline
=
upgrade_deadline
upgrade_deadline
=
upgrade_deadline
,
enrollment__course__self_paced
=
True
,
)
)
sent_messages
=
[]
sent_messages
=
[]
...
...
openedx/core/djangoapps/schedules/models.py
View file @
7fd643fa
from
django.db
import
models
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.contrib.sites.models
import
Site
from
django.contrib.sites.models
import
Site
from
model_utils
import
Choices
from
model_utils.models
import
TimeStampedModel
from
model_utils.models
import
TimeStampedModel
from
config_models.models
import
ConfigurationModel
from
config_models.models
import
ConfigurationModel
...
@@ -27,7 +28,7 @@ class Schedule(TimeStampedModel):
...
@@ -27,7 +28,7 @@ class Schedule(TimeStampedModel):
try
:
try
:
return
self
.
experience
.
experience_type
return
self
.
experience
.
experience_type
except
ScheduleExperience
.
DoesNotExist
:
except
ScheduleExperience
.
DoesNotExist
:
return
ScheduleExperience
.
DEFAULT
return
ScheduleExperience
.
EXPERIENCES
.
default
class
Meta
(
object
):
class
Meta
(
object
):
verbose_name
=
_
(
'Schedule'
)
verbose_name
=
_
(
'Schedule'
)
...
@@ -48,12 +49,10 @@ class ScheduleConfig(ConfigurationModel):
...
@@ -48,12 +49,10 @@ class ScheduleConfig(ConfigurationModel):
class
ScheduleExperience
(
models
.
Model
):
class
ScheduleExperience
(
models
.
Model
):
DEFAULT
=
0
EXPERIENCES
=
Choices
(
COURSE_UPDATES
=
1
(
0
,
'default'
,
'Recurring Nudge and Upgrade Reminder'
),
EXPERIENCES
=
(
(
1
,
'course_updates'
,
'Course Updates'
)
(
DEFAULT
,
'Recurring Nudge and Upgrade Reminder'
),
(
COURSE_UPDATES
,
'Course Updates'
)
)
)
schedule
=
models
.
OneToOneField
(
Schedule
,
related_name
=
'experience'
)
schedule
=
models
.
OneToOneField
(
Schedule
,
related_name
=
'experience'
)
experience_type
=
models
.
PositiveSmallIntegerField
(
choices
=
EXPERIENCES
,
default
=
DEFAULT
)
experience_type
=
models
.
PositiveSmallIntegerField
(
choices
=
EXPERIENCES
,
default
=
EXPERIENCES
.
default
)
openedx/core/djangoapps/schedules/resolvers.py
View file @
7fd643fa
...
@@ -77,7 +77,8 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
...
@@ -77,7 +77,8 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
schedule_date_field
=
None
schedule_date_field
=
None
num_bins
=
DEFAULT_NUM_BINS
num_bins
=
DEFAULT_NUM_BINS
experience_filter
=
Q
(
experience__experience_type
=
ScheduleExperience
.
DEFAULT
)
|
Q
(
experience__isnull
=
True
)
experience_filter
=
(
Q
(
experience__experience_type
=
ScheduleExperience
.
EXPERIENCES
.
default
)
|
Q
(
experience__isnull
=
True
))
def
__attrs_post_init__
(
self
):
def
__attrs_post_init__
(
self
):
# TODO: in the next refactor of this task, pass in current_datetime instead of reproducing it here
# TODO: in the next refactor of this task, pass in current_datetime instead of reproducing it here
...
@@ -126,8 +127,6 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
...
@@ -126,8 +127,6 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
schedules
=
Schedule
.
objects
.
select_related
(
schedules
=
Schedule
.
objects
.
select_related
(
'enrollment__user__profile'
,
'enrollment__user__profile'
,
'enrollment__course'
,
'enrollment__course'
,
)
.
prefetch_related
(
'enrollment__course__modes'
,
)
.
filter
(
)
.
filter
(
Q
(
enrollment__course__end__isnull
=
True
)
|
Q
(
Q
(
enrollment__course__end__isnull
=
True
)
|
Q
(
enrollment__course__end__gte
=
self
.
current_datetime
),
enrollment__course__end__gte
=
self
.
current_datetime
),
...
@@ -148,6 +147,8 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
...
@@ -148,6 +147,8 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
# This will run the query and cache all of the results in memory.
# This will run the query and cache all of the results in memory.
num_schedules
=
len
(
schedules
)
num_schedules
=
len
(
schedules
)
LOG
.
debug
(
'Number of schedules =
%
d'
,
num_schedules
)
# This should give us a sense of the volume of data being processed by each task.
# This should give us a sense of the volume of data being processed by each task.
set_custom_metric
(
'num_schedules'
,
num_schedules
)
set_custom_metric
(
'num_schedules'
,
num_schedules
)
...
@@ -240,10 +241,10 @@ class RecurringNudgeResolver(BinnedSchedulesBaseResolver):
...
@@ -240,10 +241,10 @@ class RecurringNudgeResolver(BinnedSchedulesBaseResolver):
@property
@property
def
experience_filter
(
self
):
def
experience_filter
(
self
):
if
self
.
day_offset
==
-
3
:
if
self
.
day_offset
==
-
3
:
experiences
=
[
ScheduleExperience
.
DEFAULT
,
ScheduleExperience
.
COURSE_UPDATES
]
experiences
=
[
ScheduleExperience
.
EXPERIENCES
.
default
,
ScheduleExperience
.
EXPERIENCES
.
course_updates
]
return
Q
(
experience__experience_type__in
=
experiences
)
|
Q
(
experience__isnull
=
True
)
return
Q
(
experience__experience_type__in
=
experiences
)
|
Q
(
experience__isnull
=
True
)
else
:
else
:
return
Q
(
experience__experience_type
=
ScheduleExperience
.
DEFAULT
)
|
Q
(
experience__isnull
=
True
)
return
Q
(
experience__experience_type
=
ScheduleExperience
.
EXPERIENCES
.
default
)
|
Q
(
experience__isnull
=
True
)
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
]
...
@@ -346,7 +347,7 @@ class CourseUpdateResolver(BinnedSchedulesBaseResolver):
...
@@ -346,7 +347,7 @@ class CourseUpdateResolver(BinnedSchedulesBaseResolver):
log_prefix
=
'Course Update'
log_prefix
=
'Course Update'
schedule_date_field
=
'start'
schedule_date_field
=
'start'
num_bins
=
COURSE_UPDATE_NUM_BINS
num_bins
=
COURSE_UPDATE_NUM_BINS
experience_filter
=
Q
(
experience__experience_type
=
ScheduleExperience
.
COURSE_UPDATES
)
experience_filter
=
Q
(
experience__experience_type
=
ScheduleExperience
.
EXPERIENCES
.
course_updates
)
def
schedules_for_bin
(
self
):
def
schedules_for_bin
(
self
):
week_num
=
abs
(
self
.
day_offset
)
/
7
week_num
=
abs
(
self
.
day_offset
)
/
7
...
...
openedx/core/djangoapps/schedules/signals.py
View file @
7fd643fa
...
@@ -64,14 +64,14 @@ def create_schedule(sender, **kwargs):
...
@@ -64,14 +64,14 @@ def create_schedule(sender, **kwargs):
try
:
try
:
get_week_highlights
(
enrollment
.
course_id
,
1
)
get_week_highlights
(
enrollment
.
course_id
,
1
)
experience_type
=
ScheduleExperience
.
COURSE_UPDATES
experience_type
=
ScheduleExperience
.
EXPERIENCES
.
course_updates
except
CourseUpdateDoesNotExist
:
except
CourseUpdateDoesNotExist
:
experience_type
=
ScheduleExperience
.
DEFAULT
experience_type
=
ScheduleExperience
.
EXPERIENCES
.
default
ScheduleExperience
(
schedule
=
schedule
,
experience_type
=
experience_type
)
.
save
()
ScheduleExperience
(
schedule
=
schedule
,
experience_type
=
experience_type
)
.
save
()
log
.
debug
(
'Schedules: created a new schedule starting at
%
s with an upgrade deadline of
%
s and experience type:
%
s'
,
log
.
debug
(
'Schedules: created a new schedule starting at
%
s with an upgrade deadline of
%
s and experience type:
%
s'
,
content_availability_date
,
upgrade_deadline
,
ScheduleExperience
.
EXPERIENCES
[
experience_type
]
[
1
]
)
content_availability_date
,
upgrade_deadline
,
ScheduleExperience
.
EXPERIENCES
[
experience_type
])
@receiver
(
COURSE_START_DATE_CHANGED
,
dispatch_uid
=
"update_schedules_on_course_start_changed"
)
@receiver
(
COURSE_START_DATE_CHANGED
,
dispatch_uid
=
"update_schedules_on_course_start_changed"
)
...
...
openedx/core/djangoapps/schedules/tests/factories.py
View file @
7fd643fa
...
@@ -10,7 +10,7 @@ class ScheduleExperienceFactory(factory.DjangoModelFactory):
...
@@ -10,7 +10,7 @@ class ScheduleExperienceFactory(factory.DjangoModelFactory):
class
Meta
(
object
):
class
Meta
(
object
):
model
=
models
.
ScheduleExperience
model
=
models
.
ScheduleExperience
experience_type
=
models
.
ScheduleExperience
.
DEFAULT
experience_type
=
models
.
ScheduleExperience
.
EXPERIENCES
.
default
class
ScheduleFactory
(
factory
.
DjangoModelFactory
):
class
ScheduleFactory
(
factory
.
DjangoModelFactory
):
...
...
openedx/core/djangoapps/schedules/tests/test_signals.py
View file @
7fd643fa
...
@@ -6,6 +6,7 @@ from pytz import utc
...
@@ -6,6 +6,7 @@ from pytz import utc
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.models
import
ScheduleExperience
from
openedx.core.djangoapps.schedules.models
import
ScheduleExperience
from
openedx.core.djangoapps.schedules.signals
import
CREATE_SCHEDULE_WAFFLE_FLAG
from
openedx.core.djangoapps.schedules.signals
import
CREATE_SCHEDULE_WAFFLE_FLAG
from
openedx.core.djangoapps.site_configuration.tests.factories
import
SiteFactory
from
openedx.core.djangoapps.site_configuration.tests.factories
import
SiteFactory
...
@@ -24,16 +25,22 @@ from ..tests.factories import ScheduleConfigFactory
...
@@ -24,16 +25,22 @@ from ..tests.factories import ScheduleConfigFactory
@skip_unless_lms
@skip_unless_lms
class
CreateScheduleTests
(
SharedModuleStoreTestCase
):
class
CreateScheduleTests
(
SharedModuleStoreTestCase
):
def
assert_schedule_created
(
self
,
experience_type
=
ScheduleExperience
.
DEFAULT
):
def
assert_schedule_created
(
self
,
experience_type
=
ScheduleExperience
.
EXPERIENCES
.
default
):
course
=
_create_course_run
(
self_paced
=
True
)
course
=
_create_course_run
(
self_paced
=
True
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course
.
id
,
mode
=
CourseMode
.
AUDIT
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course
.
id
,
mode
=
CourseMode
.
AUDIT
,
)
self
.
assertIsNotNone
(
enrollment
.
schedule
)
self
.
assertIsNotNone
(
enrollment
.
schedule
)
self
.
assertIsNone
(
enrollment
.
schedule
.
upgrade_deadline
)
self
.
assertIsNone
(
enrollment
.
schedule
.
upgrade_deadline
)
self
.
assertEquals
(
enrollment
.
schedule
.
experience
.
experience_type
,
experience_type
)
self
.
assertEquals
(
enrollment
.
schedule
.
experience
.
experience_type
,
experience_type
)
def
assert_schedule_not_created
(
self
):
def
assert_schedule_not_created
(
self
):
course
=
_create_course_run
(
self_paced
=
True
)
course
=
_create_course_run
(
self_paced
=
True
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course
.
id
,
mode
=
CourseMode
.
AUDIT
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course
.
id
,
mode
=
CourseMode
.
AUDIT
,
)
with
self
.
assertRaises
(
Schedule
.
DoesNotExist
):
with
self
.
assertRaises
(
Schedule
.
DoesNotExist
):
enrollment
.
schedule
enrollment
.
schedule
...
@@ -86,7 +93,7 @@ class CreateScheduleTests(SharedModuleStoreTestCase):
...
@@ -86,7 +93,7 @@ class CreateScheduleTests(SharedModuleStoreTestCase):
site
=
SiteFactory
.
create
()
site
=
SiteFactory
.
create
()
mock_get_week_highlights
.
return_value
=
True
mock_get_week_highlights
.
return_value
=
True
mock_get_current_site
.
return_value
=
site
mock_get_current_site
.
return_value
=
site
self
.
assert_schedule_created
(
experience_type
=
ScheduleExperience
.
COURSE_UPDATES
)
self
.
assert_schedule_created
(
experience_type
=
ScheduleExperience
.
EXPERIENCES
.
course_updates
)
@ddt.ddt
@ddt.ddt
...
@@ -114,7 +121,7 @@ class UpdateScheduleTests(SharedModuleStoreTestCase):
...
@@ -114,7 +121,7 @@ class UpdateScheduleTests(SharedModuleStoreTestCase):
course
=
_create_course_run
(
self_paced
=
True
,
start_day_offset
=
5
)
# course starts in future
course
=
_create_course_run
(
self_paced
=
True
,
start_day_offset
=
5
)
# course starts in future
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course
.
id
,
mode
=
CourseMode
.
AUDIT
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course
.
id
,
mode
=
CourseMode
.
AUDIT
)
self
.
assert_schedule_dates
(
enrollment
.
schedule
,
enrollment
.
course
_overview
.
start
)
self
.
assert_schedule_dates
(
enrollment
.
schedule
,
enrollment
.
course
.
start
)
course
.
start
=
course
.
start
+
datetime
.
timedelta
(
days
=
3
)
# new course start changes to another future date
course
.
start
=
course
.
start
+
datetime
.
timedelta
(
days
=
3
)
# new course start changes to another future date
self
.
store
.
update_item
(
course
,
ModuleStoreEnum
.
UserID
.
test
)
self
.
store
.
update_item
(
course
,
ModuleStoreEnum
.
UserID
.
test
)
...
@@ -138,7 +145,7 @@ class UpdateScheduleTests(SharedModuleStoreTestCase):
...
@@ -138,7 +145,7 @@ class UpdateScheduleTests(SharedModuleStoreTestCase):
course
=
_create_course_run
(
self_paced
=
True
,
start_day_offset
=
5
)
# course starts in future
course
=
_create_course_run
(
self_paced
=
True
,
start_day_offset
=
5
)
# course starts in future
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course
.
id
,
mode
=
CourseMode
.
AUDIT
)
enrollment
=
CourseEnrollmentFactory
(
course_id
=
course
.
id
,
mode
=
CourseMode
.
AUDIT
)
previous_start
=
enrollment
.
course
_overview
.
start
previous_start
=
enrollment
.
course
.
start
self
.
assert_schedule_dates
(
enrollment
.
schedule
,
previous_start
)
self
.
assert_schedule_dates
(
enrollment
.
schedule
,
previous_start
)
course
.
start
=
course
.
start
+
datetime
.
timedelta
(
days
=-
10
)
# new course start changes to a past date
course
.
start
=
course
.
start
+
datetime
.
timedelta
(
days
=-
10
)
# new course start changes to a past date
...
...
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