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
853cd9a0
Commit
853cd9a0
authored
Dec 08, 2017
by
McKenzie Welter
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
show programs in which user holds a course entitlement on programs listing page
parent
25eb3187
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
111 additions
and
8 deletions
+111
-8
common/djangoapps/entitlements/models.py
+4
-0
common/djangoapps/entitlements/tests/factories.py
+1
-0
openedx/core/djangoapps/programs/tests/test_utils.py
+88
-5
openedx/core/djangoapps/programs/utils.py
+18
-3
No files found.
common/djangoapps/entitlements/models.py
View file @
853cd9a0
...
...
@@ -219,3 +219,7 @@ class CourseEntitlement(TimeStampedModel):
Fulfills an entitlement by specifying a session.
"""
cls
.
objects
.
filter
(
id
=
entitlement
.
id
)
.
update
(
enrollment_course_run
=
enrollment
)
@classmethod
def
unexpired_entitlements_for_user
(
cls
,
user
):
return
cls
.
objects
.
filter
(
user
=
user
,
expired_at
=
None
)
.
select_related
(
'user'
)
common/djangoapps/entitlements/tests/factories.py
View file @
853cd9a0
...
...
@@ -27,6 +27,7 @@ class CourseEntitlementFactory(factory.django.DjangoModelFactory):
uuid
=
factory
.
LazyFunction
(
uuid4
)
course_uuid
=
factory
.
LazyFunction
(
uuid4
)
expired_at
=
None
mode
=
FuzzyChoice
([
CourseMode
.
VERIFIED
,
CourseMode
.
PROFESSIONAL
])
user
=
factory
.
SubFactory
(
UserFactory
)
order_number
=
FuzzyText
(
prefix
=
'TEXTX'
,
chars
=
string
.
digits
)
...
...
openedx/core/djangoapps/programs/tests/test_utils.py
View file @
853cd9a0
...
...
@@ -67,6 +67,11 @@ class TestProgramProgressMeter(TestCase):
for
course_run_id
in
course_run_ids
:
CourseEnrollmentFactory
(
user
=
self
.
user
,
course_id
=
course_run_id
,
mode
=
CourseMode
.
VERIFIED
)
def
_create_entitlements
(
self
,
*
course_uuids
):
""" Variadic helper used to create course entitlements. """
for
course_uuid
in
course_uuids
:
CourseEntitlementFactory
(
user
=
self
.
user
,
course_uuid
=
course_uuid
)
def
_assert_progress
(
self
,
meter
,
*
progresses
):
"""Variadic helper used to verify progress calculations."""
self
.
assertEqual
(
meter
.
progress
(),
list
(
progresses
))
...
...
@@ -93,8 +98,8 @@ class TestProgramProgressMeter(TestCase):
return
result
def
test_no_enrollments
(
self
,
mock_get_programs
):
"""Verify behavior when programs exist, but no relevant enrollments do."""
def
test_no_enrollments
_or_entitlements
(
self
,
mock_get_programs
):
"""Verify behavior when programs exist, but no relevant enrollments
or entitlements
do."""
data
=
[
ProgramFactory
()]
mock_get_programs
.
return_value
=
data
...
...
@@ -104,7 +109,7 @@ class TestProgramProgressMeter(TestCase):
self
.
_assert_progress
(
meter
)
self
.
assertEqual
(
meter
.
completed_programs
,
[])
def
test_no_programs
(
self
,
mock_get_programs
):
def
test_
enrollments_but_
no_programs
(
self
,
mock_get_programs
):
"""Verify behavior when enrollments exist, but no matching programs do."""
mock_get_programs
.
return_value
=
[]
...
...
@@ -116,7 +121,16 @@ class TestProgramProgressMeter(TestCase):
self
.
_assert_progress
(
meter
)
self
.
assertEqual
(
meter
.
completed_programs
,
[])
def
test_single_program_engagement
(
self
,
mock_get_programs
):
def
test_entitlements_but_no_programs
(
self
,
mock_get_programs
):
""" Verify engaged_programs is empty when entitlements exist, but no matching programs do. """
mock_get_programs
.
return_value
=
[]
self
.
_create_entitlements
(
uuid
.
uuid4
())
meter
=
ProgramProgressMeter
(
self
.
site
,
self
.
user
)
self
.
assertEqual
(
meter
.
engaged_programs
,
[])
def
test_single_program_enrollment
(
self
,
mock_get_programs
):
"""
Verify that correct program is returned when the user is enrolled in a
course run appearing in one program.
...
...
@@ -146,6 +160,25 @@ class TestProgramProgressMeter(TestCase):
)
self
.
assertEqual
(
meter
.
completed_programs
,
[])
def
test_single_program_entitlement
(
self
,
mock_get_programs
):
"""
Verify that the correct program is returned when the user holds an entitlement
to a course appearing in one program.
"""
course_uuid
=
uuid
.
uuid4
()
data
=
[
ProgramFactory
(
courses
=
[
CourseFactory
(
uuid
=
str
(
course_uuid
))]),
ProgramFactory
(),
]
mock_get_programs
.
return_value
=
data
self
.
_create_entitlements
(
course_uuid
)
meter
=
ProgramProgressMeter
(
self
.
site
,
self
.
user
)
self
.
_attach_detail_url
(
data
)
program
=
data
[
0
]
self
.
assertEqual
(
meter
.
engaged_programs
,
[
program
])
def
test_course_progress
(
self
,
mock_get_programs
):
"""
Verify that the progress meter can represent progress in terms of
...
...
@@ -259,7 +292,7 @@ class TestProgramProgressMeter(TestCase):
self
.
assertEqual
(
meter
.
progress
(
count_only
=
True
),
expected
)
def
test_mutiple_program_en
gage
ment
(
self
,
mock_get_programs
):
def
test_mutiple_program_en
roll
ment
(
self
,
mock_get_programs
):
"""
Verify that correct programs are returned in the correct order when the
user is enrolled in course runs appearing in programs.
...
...
@@ -303,6 +336,28 @@ class TestProgramProgressMeter(TestCase):
)
self
.
assertEqual
(
meter
.
completed_programs
,
[])
def
test_multiple_program_entitlement
(
self
,
mock_get_programs
):
"""
Verify that the correct programs are returned in the correct order
when the user holds entitlements to courses appearing in those programs.
"""
newer_course_uuid
,
older_course_uuid
=
(
uuid
.
uuid4
()
for
__
in
range
(
2
))
data
=
[
ProgramFactory
(
courses
=
[
CourseFactory
(
uuid
=
str
(
older_course_uuid
)),
]),
ProgramFactory
(
courses
=
[
CourseFactory
(
uuid
=
str
(
newer_course_uuid
)),
]),
ProgramFactory
(),
]
mock_get_programs
.
return_value
=
data
# The creation time of the entitlements matters to the test. We want
# the newer_course_uuid to represent the newest entitlement.
self
.
_create_entitlements
(
older_course_uuid
,
newer_course_uuid
)
meter
=
ProgramProgressMeter
(
self
.
site
,
self
.
user
)
self
.
_attach_detail_url
(
data
)
programs
=
data
[:
2
]
self
.
assertEqual
(
meter
.
engaged_programs
,
programs
)
def
test_shared_enrollment_engagement
(
self
,
mock_get_programs
):
"""
Verify that correct programs are returned when the user is enrolled in a
...
...
@@ -354,6 +409,34 @@ class TestProgramProgressMeter(TestCase):
)
self
.
assertEqual
(
meter
.
completed_programs
,
[])
def
test_shared_entitlement_engagement
(
self
,
mock_get_programs
):
"""
Verify that correct programs are returned when the user holds an entitlement
to a single course appearing in multiple programs.
"""
shared_course_uuid
,
solo_course_uuid
=
(
uuid
.
uuid4
()
for
__
in
range
(
2
))
batch
=
[
ProgramFactory
(
courses
=
[
CourseFactory
(
uuid
=
str
(
shared_course_uuid
)),
])
for
__
in
range
(
2
)
]
joint_programs
=
sorted
(
batch
,
key
=
lambda
program
:
program
[
'title'
])
data
=
joint_programs
+
[
ProgramFactory
(
courses
=
[
CourseFactory
(
uuid
=
str
(
solo_course_uuid
)),
]),
ProgramFactory
(),
]
mock_get_programs
.
return_value
=
data
# Entitlement for the shared course created last (most recently).
self
.
_create_entitlements
(
shared_course_uuid
,
solo_course_uuid
)
meter
=
ProgramProgressMeter
(
self
.
site
,
self
.
user
)
self
.
_attach_detail_url
(
data
)
programs
=
data
[:
3
]
self
.
assertEqual
(
meter
.
engaged_programs
,
programs
)
@mock.patch
(
UTILS_MODULE
+
'.ProgramProgressMeter.completed_course_runs'
,
new_callable
=
mock
.
PropertyMock
)
def
test_simulate_progress
(
self
,
mock_completed_course_runs
,
mock_get_programs
):
"""Simulate the entirety of a user's progress through a program."""
...
...
openedx/core/djangoapps/programs/utils.py
View file @
853cd9a0
...
...
@@ -19,6 +19,7 @@ from pytz import utc
from
requests.exceptions
import
ConnectionError
,
Timeout
from
course_modes.models
import
CourseMode
from
entitlements.models
import
CourseEntitlement
from
lms.djangoapps.certificates
import
api
as
certificate_api
from
lms.djangoapps.commerce.utils
import
EcommerceService
from
lms.djangoapps.courseware.access
import
has_access
...
...
@@ -90,6 +91,9 @@ class ProgramProgressMeter(object):
# We can't use dict.keys() for this because the course run ids need to be ordered
self
.
course_run_ids
.
append
(
enrollment_id
)
self
.
entitlements
=
list
(
CourseEntitlement
.
unexpired_entitlements_for_user
(
self
.
user
))
self
.
course_uuids
=
[
str
(
entitlement
.
course_uuid
)
for
entitlement
in
self
.
entitlements
]
self
.
course_grade_factory
=
CourseGradeFactory
()
if
uuid
:
...
...
@@ -100,9 +104,9 @@ class ProgramProgressMeter(object):
def
invert_programs
(
self
):
"""Intersect programs and enrollments.
Builds a dictionary of program dict lists keyed by course run ID
. The
resulting dictionary is suitable in applications where programs must be
filtered by the course runs they contain (e.g., the student dashboard).
Builds a dictionary of program dict lists keyed by course run ID
and by course UUID.
The
resulting dictionary is suitable in applications where programs must be
filtered by the course runs
or courses
they contain (e.g., the student dashboard).
Returns:
defaultdict, programs keyed by course run ID
...
...
@@ -111,6 +115,12 @@ class ProgramProgressMeter(object):
for
program
in
self
.
programs
:
for
course
in
program
[
'courses'
]:
course_uuid
=
course
[
'uuid'
]
if
course_uuid
in
self
.
course_uuids
:
program_list
=
inverted_programs
[
course_uuid
]
if
program
not
in
program_list
:
program_list
.
append
(
program
)
continue
for
course_run
in
course
[
'course_runs'
]:
course_run_id
=
course_run
[
'key'
]
if
course_run_id
in
self
.
course_run_ids
:
...
...
@@ -145,6 +155,11 @@ class ProgramProgressMeter(object):
if
program
not
in
programs
:
programs
.
append
(
program
)
for
course_uuid
in
self
.
course_uuids
:
for
program
in
inverted_programs
[
course_uuid
]:
if
program
not
in
programs
:
programs
.
append
(
program
)
return
programs
def
_is_course_in_progress
(
self
,
now
,
course
):
...
...
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