Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-analytics-data-api
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-analytics-data-api
Commits
8103ad0e
Commit
8103ad0e
authored
Nov 30, 2016
by
Dennis Jen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Updated endpoint to return counts per mode, updated and added tests
parent
d925f797
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
287 additions
and
56 deletions
+287
-56
analytics_data_api/v0/serializers.py
+33
-7
analytics_data_api/v0/tests/views/test_course_summaries.py
+164
-0
analytics_data_api/v0/tests/views/test_courses.py
+1
-29
analytics_data_api/v0/tests/views/test_utils.py
+4
-7
analytics_data_api/v0/urls/course_summaries.py
+0
-2
analytics_data_api/v0/views/__init__.py
+1
-0
analytics_data_api/v0/views/course_summaries.py
+83
-10
requirements/base.txt
+1
-0
requirements/local.txt
+0
-1
No files found.
analytics_data_api/v0/serializers.py
View file @
8103ad0e
...
@@ -509,22 +509,48 @@ class CourseLearnerMetadataSerializer(serializers.Serializer):
...
@@ -509,22 +509,48 @@ class CourseLearnerMetadataSerializer(serializers.Serializer):
return
engagement_ranges
return
engagement_ranges
class
CourseMetaSummaryEnrollmentSerializer
(
ModelSerializerWithCreatedField
):
class
DynamicFieldsModelSerializer
(
serializers
.
ModelSerializer
):
"""
"""
Serializer for problems.
A ModelSerializer that takes an additional `fields` argument that controls which
fields should be displayed.
Blatantly taken from http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
"""
def
__init__
(
self
,
*
args
,
**
kwargs
):
# Don't pass the 'fields' arg up to the superclass
fields
=
kwargs
.
pop
(
'fields'
,
None
)
# Instantiate the superclass normally
super
(
DynamicFieldsModelSerializer
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
if
fields
is
not
None
:
# Drop any fields that are not specified in the `fields` argument.
allowed
=
set
(
fields
)
existing
=
set
(
self
.
fields
.
keys
())
for
field_name
in
existing
-
allowed
:
self
.
fields
.
pop
(
field_name
)
class
CourseMetaSummaryEnrollmentSerializer
(
ModelSerializerWithCreatedField
,
DynamicFieldsModelSerializer
):
"""
Serializer for course and enrollment counts per mode.
"""
"""
course_id
=
serializers
.
CharField
()
course_id
=
serializers
.
CharField
()
catalog_course_title
=
serializers
.
CharField
()
catalog_course_title
=
serializers
.
CharField
()
catalog_course
=
serializers
.
CharField
()
catalog_course
=
serializers
.
CharField
()
start_date
=
serializers
.
DateTimeField
()
start_date
=
serializers
.
DateTimeField
(
format
=
settings
.
DATETIME_FORMAT
)
end_date
=
serializers
.
DateTimeField
()
end_date
=
serializers
.
DateTimeField
(
format
=
settings
.
DATETIME_FORMAT
)
pacing_type
=
serializers
.
CharField
()
pacing_type
=
serializers
.
CharField
()
availability
=
serializers
.
CharField
()
availability
=
serializers
.
CharField
()
mode
=
serializers
.
CharField
()
count
=
serializers
.
IntegerField
(
default
=
0
)
count
=
serializers
.
IntegerField
(
default
=
0
)
cumulative_count
=
serializers
.
IntegerField
(
default
=
0
)
cumulative_count
=
serializers
.
IntegerField
(
default
=
0
)
count_change_7_days
=
serializers
.
IntegerField
(
default
=
0
)
# TODO: 0 as default?
count_change_7_days
=
serializers
.
IntegerField
(
default
=
0
)
modes
=
serializers
.
SerializerMethodField
()
def
get_modes
(
self
,
obj
):
return
obj
.
get
(
'modes'
,
None
)
class
Meta
(
object
):
class
Meta
(
object
):
model
=
models
.
CourseMetaSummaryEnrollment
model
=
models
.
CourseMetaSummaryEnrollment
exclude
=
(
'id'
,)
exclude
=
(
'id'
,
'mode'
)
analytics_data_api/v0/tests/views/test_course_summaries.py
0 → 100644
View file @
8103ad0e
import
datetime
from
urllib
import
urlencode
import
ddt
from
django_dynamic_fixture
import
G
import
pytz
from
django.conf
import
settings
from
analytics_data_api.constants
import
enrollment_modes
from
analytics_data_api.v0
import
models
,
serializers
from
analytics_data_api.v0.tests.views
import
CourseSamples
,
VerifyCourseIdMixin
from
analyticsdataserver.tests
import
TestCaseWithAuthentication
@ddt.ddt
class
CourseSummariesViewTests
(
VerifyCourseIdMixin
,
TestCaseWithAuthentication
):
model
=
models
.
CourseMetaSummaryEnrollment
serializer
=
serializers
.
CourseMetaSummaryEnrollmentSerializer
expected_summaries
=
[]
def
setUp
(
self
):
super
(
CourseSummariesViewTests
,
self
)
.
setUp
()
self
.
now
=
datetime
.
datetime
.
utcnow
()
def
tearDown
(
self
):
self
.
model
.
objects
.
all
()
.
delete
()
def
path
(
self
,
course_ids
=
None
,
fields
=
None
):
query_params
=
{}
for
query_arg
,
data
in
zip
([
'course_ids'
,
'fields'
],
[
course_ids
,
fields
]):
if
data
:
query_params
[
query_arg
]
=
','
.
join
(
data
)
query_string
=
'?{}'
.
format
(
urlencode
(
query_params
))
return
'/api/v0/course_summaries/{}'
.
format
(
query_string
)
def
generate_data
(
self
,
course_ids
=
None
,
modes
=
None
):
"""Generate course summary data for """
if
course_ids
is
None
:
course_ids
=
CourseSamples
.
course_ids
if
modes
is
None
:
modes
=
enrollment_modes
.
ALL
for
course_id
in
course_ids
:
for
mode
in
modes
:
G
(
self
.
model
,
course_id
=
course_id
,
catalog_course_title
=
'Title'
,
catalog_course
=
'Catalog'
,
start_date
=
datetime
.
datetime
(
2016
,
10
,
11
,
tzinfo
=
pytz
.
utc
),
end_date
=
datetime
.
datetime
(
2016
,
12
,
18
,
tzinfo
=
pytz
.
utc
),
pacing_type
=
'instructor'
,
availability
=
'current'
,
mode
=
mode
,
count
=
5
,
cumulative_count
=
10
,
count_change_7_days
=
1
,
create
=
self
.
now
,)
def
expected_summary
(
self
,
course_id
,
modes
=
None
):
"""Expected summary information for a course and modes to populate with data."""
if
modes
is
None
:
modes
=
enrollment_modes
.
ALL
num_modes
=
len
(
modes
)
count_factor
=
5
cumulative_count_factor
=
10
count_change_factor
=
1
summary
=
{
'course_id'
:
course_id
,
'catalog_course_title'
:
'Title'
,
'catalog_course'
:
'Catalog'
,
'start_date'
:
datetime
.
datetime
(
2016
,
10
,
11
,
tzinfo
=
pytz
.
utc
)
.
strftime
(
settings
.
DATETIME_FORMAT
),
'end_date'
:
datetime
.
datetime
(
2016
,
12
,
18
,
tzinfo
=
pytz
.
utc
)
.
strftime
(
settings
.
DATETIME_FORMAT
),
'pacing_type'
:
'instructor'
,
'availability'
:
'current'
,
'modes'
:
{},
'count'
:
count_factor
*
num_modes
,
'cumulative_count'
:
cumulative_count_factor
*
num_modes
,
'count_change_7_days'
:
count_change_factor
*
num_modes
,
'created'
:
self
.
now
.
strftime
(
settings
.
DATETIME_FORMAT
),
}
summary
[
'modes'
]
.
update
({
mode
:
{
'count'
:
count_factor
,
'cumulative_count'
:
cumulative_count_factor
,
'count_change_7_days'
:
count_change_factor
,
}
for
mode
in
modes
})
summary
[
'modes'
]
.
update
({
mode
:
{
'count'
:
0
,
'cumulative_count'
:
0
,
'count_change_7_days'
:
0
,
}
for
mode
in
set
(
enrollment_modes
.
ALL
)
-
set
(
modes
)
})
no_prof
=
summary
[
'modes'
]
.
pop
(
enrollment_modes
.
PROFESSIONAL_NO_ID
)
prof
=
summary
[
'modes'
]
.
get
(
enrollment_modes
.
PROFESSIONAL
)
prof
.
update
({
'count'
:
prof
[
'count'
]
+
no_prof
[
'count'
],
'cumulative_count'
:
prof
[
'cumulative_count'
]
+
no_prof
[
'cumulative_count'
],
'count_change_7_days'
:
prof
[
'count_change_7_days'
]
+
no_prof
[
'count_change_7_days'
],
})
return
summary
def
all_expected_summaries
(
self
,
modes
=
None
):
if
modes
is
None
:
modes
=
enrollment_modes
.
ALL
return
[
self
.
expected_summary
(
course_id
,
modes
)
for
course_id
in
CourseSamples
.
course_ids
]
@ddt.data
(
None
,
CourseSamples
.
course_ids
,
[
'not/real/course'
]
.
extend
(
CourseSamples
.
course_ids
),
)
def
test_all_courses
(
self
,
course_ids
):
self
.
generate_data
()
response
=
self
.
authenticated_get
(
self
.
path
(
course_ids
=
course_ids
))
self
.
assertEquals
(
response
.
status_code
,
200
)
self
.
assertItemsEqual
(
response
.
data
,
self
.
all_expected_summaries
())
@ddt.data
(
*
CourseSamples
.
course_ids
)
def
test_one_course
(
self
,
course_id
):
self
.
generate_data
()
response
=
self
.
authenticated_get
(
self
.
path
(
course_ids
=
[
course_id
]))
self
.
assertEquals
(
response
.
status_code
,
200
)
self
.
assertItemsEqual
(
response
.
data
,
[
self
.
expected_summary
(
course_id
)])
@ddt.data
(
[
'availability'
],
[
'modes'
,
'course_id'
],
)
def
test_fields
(
self
,
fields
):
self
.
generate_data
()
response
=
self
.
authenticated_get
(
self
.
path
(
fields
=
fields
))
self
.
assertEquals
(
response
.
status_code
,
200
)
# remove fields not requested from expected results
expected_summaries
=
self
.
all_expected_summaries
()
for
expected_summary
in
expected_summaries
:
for
field_to_remove
in
set
(
expected_summary
.
keys
())
-
set
(
fields
):
expected_summary
.
pop
(
field_to_remove
)
self
.
assertItemsEqual
(
response
.
data
,
expected_summaries
)
@ddt.data
(
[
enrollment_modes
.
VERIFIED
],
[
enrollment_modes
.
HONOR
,
enrollment_modes
.
PROFESSIONAL
],
)
def
test_empty_modes
(
self
,
modes
):
self
.
generate_data
(
modes
=
modes
)
response
=
self
.
authenticated_get
(
self
.
path
())
self
.
assertEquals
(
response
.
status_code
,
200
)
self
.
assertItemsEqual
(
response
.
data
,
self
.
all_expected_summaries
(
modes
))
def
test_no_summaries
(
self
):
response
=
self
.
authenticated_get
(
self
.
path
())
self
.
assertEquals
(
response
.
status_code
,
404
)
def
test_no_matching_courses
(
self
):
self
.
generate_data
()
response
=
self
.
authenticated_get
(
self
.
path
(
course_ids
=
[
'no/course/found'
]))
self
.
assertEquals
(
response
.
status_code
,
404
)
@ddt.data
(
[
'malformed-course-id'
],
[
CourseSamples
.
course_ids
[
0
],
'malformed-course-id'
],
)
def
test_bad_course_id
(
self
,
course_ids
):
response
=
self
.
authenticated_get
(
self
.
path
(
course_ids
=
course_ids
))
self
.
verify_bad_course_id
(
response
)
analytics_data_api/v0/tests/views/test_courses.py
View file @
8103ad0e
...
@@ -16,7 +16,7 @@ from mock import patch, Mock
...
@@ -16,7 +16,7 @@ from mock import patch, Mock
from
analytics_data_api.constants
import
country
,
enrollment_modes
,
genders
from
analytics_data_api.constants
import
country
,
enrollment_modes
,
genders
from
analytics_data_api.constants.country
import
get_country
from
analytics_data_api.constants.country
import
get_country
from
analytics_data_api.v0
import
models
,
serializers
from
analytics_data_api.v0
import
models
from
analytics_data_api.v0.tests.views
import
CourseSamples
,
VerifyCsvResponseMixin
from
analytics_data_api.v0.tests.views
import
CourseSamples
,
VerifyCsvResponseMixin
from
analytics_data_api.utils
import
get_filename_safe_course_id
from
analytics_data_api.utils
import
get_filename_safe_course_id
from
analyticsdataserver.tests
import
TestCaseWithAuthentication
from
analyticsdataserver.tests
import
TestCaseWithAuthentication
...
@@ -889,31 +889,3 @@ class CourseReportDownloadViewTests(TestCaseWithAuthentication):
...
@@ -889,31 +889,3 @@ class CourseReportDownloadViewTests(TestCaseWithAuthentication):
'expiration_date'
:
datetime
.
datetime
(
2014
,
1
,
1
,
tzinfo
=
pytz
.
utc
)
.
strftime
(
settings
.
DATETIME_FORMAT
)
'expiration_date'
:
datetime
.
datetime
(
2014
,
1
,
1
,
tzinfo
=
pytz
.
utc
)
.
strftime
(
settings
.
DATETIME_FORMAT
)
}
}
self
.
assertEqual
(
response
.
data
,
expected
)
self
.
assertEqual
(
response
.
data
,
expected
)
class
CourseSummariesViewTests
(
TestCaseWithAuthentication
):
model
=
models
.
CourseMetaSummaryEnrollment
serializer
=
serializers
.
CourseMetaSummaryEnrollmentSerializer
path
=
'/course_summaries'
expected_summaries
=
[]
fake_course_ids
=
[
'edX/DemoX/Demo_Course'
,
'edX/DemoX/2'
,
'edX/DemoX/3'
,
'edX/DemoX/4'
]
# csv_filename_slug = u'course_summaries'
def
setUp
(
self
):
super
(
CourseSummariesViewTests
,
self
)
.
setUp
()
self
.
generate_data
()
def
generate_data
(
self
):
for
course_id
in
self
.
fake_course_ids
:
self
.
expected_summaries
.
append
(
self
.
serializer
(
G
(
self
.
model
,
course_id
=
course_id
,
count
=
10
,
cumulative_count
=
15
))
.
data
)
def
test_get
(
self
):
response
=
self
.
authenticated_get
(
u'/api/v0/course_summaries/?course_ids=
%
s'
%
','
.
join
(
self
.
fake_course_ids
))
self
.
assertEquals
(
response
.
status_code
,
200
)
self
.
assertItemsEqual
(
response
.
data
,
self
.
expected_summaries
)
def
test_no_summaries
(
self
):
self
.
model
.
objects
.
all
()
.
delete
()
response
=
self
.
authenticated_get
(
u'/api/v0/course_summaries/?course_ids=
%
s'
%
','
.
join
(
self
.
fake_course_ids
))
self
.
assertEquals
(
response
.
status_code
,
404
)
analytics_data_api/v0/tests/views/test_utils.py
View file @
8103ad0e
...
@@ -5,6 +5,7 @@ from django.http import Http404
...
@@ -5,6 +5,7 @@ from django.http import Http404
from
django.test
import
TestCase
from
django.test
import
TestCase
from
analytics_data_api.v0.exceptions
import
CourseKeyMalformedError
from
analytics_data_api.v0.exceptions
import
CourseKeyMalformedError
from
analytics_data_api.v0.tests.views
import
CourseSamples
import
analytics_data_api.v0.views.utils
as
utils
import
analytics_data_api.v0.views.utils
as
utils
...
@@ -19,11 +20,7 @@ class UtilsTest(TestCase):
...
@@ -19,11 +20,7 @@ class UtilsTest(TestCase):
with
self
.
assertRaises
(
CourseKeyMalformedError
):
with
self
.
assertRaises
(
CourseKeyMalformedError
):
utils
.
validate_course_id
(
course_id
)
utils
.
validate_course_id
(
course_id
)
# TODO: DDT w/ the refactored CourseSamples once https://github.com/edx/edx-analytics-data-api/pull/143 merges
@ddt.data
(
*
CourseSamples
.
course_ids
)
@ddt.data
(
'edX/DemoX/Demo_Course'
,
'course-v1:edX+DemoX+Demo_2014'
,
)
def
test_valid_course_id
(
self
,
course_id
):
def
test_valid_course_id
(
self
,
course_id
):
try
:
try
:
utils
.
validate_course_id
(
course_id
)
utils
.
validate_course_id
(
course_id
)
...
@@ -38,8 +35,8 @@ class UtilsTest(TestCase):
...
@@ -38,8 +35,8 @@ class UtilsTest(TestCase):
(
'one,two'
,
[
'one'
,
'two'
]),
(
'one,two'
,
[
'one'
,
'two'
]),
)
)
@ddt.unpack
@ddt.unpack
def
test_split_query_argument
(
self
,
input
,
expected
):
def
test_split_query_argument
(
self
,
query_args
,
expected
):
self
.
assertListEqual
(
utils
.
split_query_argument
(
input
),
expected
)
self
.
assertListEqual
(
utils
.
split_query_argument
(
query_args
),
expected
)
def
test_raise_404_if_none_raises_error
(
self
):
def
test_raise_404_if_none_raises_error
(
self
):
decorated_func
=
utils
.
raise_404_if_none
(
Mock
(
return_value
=
None
))
decorated_func
=
utils
.
raise_404_if_none
(
Mock
(
return_value
=
None
))
...
...
analytics_data_api/v0/urls/course_summaries.py
View file @
8103ad0e
...
@@ -2,8 +2,6 @@ from django.conf.urls import url
...
@@ -2,8 +2,6 @@ from django.conf.urls import url
from
analytics_data_api.v0.views
import
course_summaries
as
views
from
analytics_data_api.v0.views
import
course_summaries
as
views
USERNAME_PATTERN
=
r'(?P<username>[\w.+-]+)'
urlpatterns
=
[
urlpatterns
=
[
url
(
r'^course_summaries/$'
,
views
.
CourseSummariesView
.
as_view
(),
name
=
'course_summaries'
),
url
(
r'^course_summaries/$'
,
views
.
CourseSummariesView
.
as_view
(),
name
=
'course_summaries'
),
]
]
analytics_data_api/v0/views/__init__.py
View file @
8103ad0e
...
@@ -4,6 +4,7 @@ from django.utils import timezone
...
@@ -4,6 +4,7 @@ from django.utils import timezone
from
analytics_data_api.v0.exceptions
import
CourseNotSpecifiedError
from
analytics_data_api.v0.exceptions
import
CourseNotSpecifiedError
import
analytics_data_api.utils
as
utils
import
analytics_data_api.utils
as
utils
class
CourseViewMixin
(
object
):
class
CourseViewMixin
(
object
):
"""
"""
Captures the course_id from the url and validates it.
Captures the course_id from the url and validates it.
...
...
analytics_data_api/v0/views/course_summaries.py
View file @
8103ad0e
"""
from
itertools
import
groupby
TODO: TBD
"""
from
django.db.models
import
Q
from
django.db.models
import
Q
from
rest_framework
import
generics
from
rest_framework
import
generics
from
analytics_data_api.constants
import
enrollment_modes
from
analytics_data_api.v0
import
models
,
serializers
from
analytics_data_api.v0
import
models
,
serializers
from
analytics_data_api.v0.views.utils
import
(
from
analytics_data_api.v0.views.utils
import
(
raise_404_if_none
,
raise_404_if_none
,
...
@@ -16,7 +15,7 @@ from analytics_data_api.v0.views.utils import (
...
@@ -16,7 +15,7 @@ from analytics_data_api.v0.views.utils import (
class
CourseSummariesView
(
generics
.
ListAPIView
):
class
CourseSummariesView
(
generics
.
ListAPIView
):
"""
"""
TBD
.
Returns summary information for courses
.
**Example Request**
**Example Request**
...
@@ -24,14 +23,32 @@ class CourseSummariesView(generics.ListAPIView):
...
@@ -24,14 +23,32 @@ class CourseSummariesView(generics.ListAPIView):
**Response Values**
**Response Values**
TBD
Returns the count of each gender specified by users:
* course_id: The ID of the course for which data is returned.
* catalog_course_title: The name of the course.
* catalog_course: Course identifier without run.
* start_date: The date and time that the course begins
* end_date: The date and time that the course ends
* pacing_type: The type of pacing for this course
* availability: Availability status of the course
* count: The total count of currently enrolled learners across modes.
* cumulative_count: The total cumulative total of all users ever enrolled across modes.
* count_change_7_days: Total difference in enrollment counts over the past 7 days across modes.
* modes: For each enrollment mode, the count, cumulative_count, and count_change_7_days.
* created: The date the counts were computed.
**Parameters**
**Parameters**
You can specify the course IDs for which you want data
.
Results can be filed to the course IDs specified or limited to the fields
.
course_ids -- The comma-separated course identifiers for which user data is requested.
course_ids -- The comma-separated course identifiers for which summaries are requested.
For example, edX/DemoX/Demo_Course,course-v1:edX+DemoX+Demo_2016
For example, 'edX/DemoX/Demo_Course,course-v1:edX+DemoX+Demo_2016'. Default is to
return call courses.
fields -- The comma-separated fields to return in the response.
For example, 'course_id,created_mode'. Default is to return all fields.
fields -- Fields to include in response. Default is all.
"""
"""
course_ids
=
None
course_ids
=
None
...
@@ -39,6 +56,13 @@ class CourseSummariesView(generics.ListAPIView):
...
@@ -39,6 +56,13 @@ class CourseSummariesView(generics.ListAPIView):
serializer_class
=
serializers
.
CourseMetaSummaryEnrollmentSerializer
serializer_class
=
serializers
.
CourseMetaSummaryEnrollmentSerializer
model
=
models
.
CourseMetaSummaryEnrollment
model
=
models
.
CourseMetaSummaryEnrollment
def
get_serializer
(
self
,
*
args
,
**
kwargs
):
kwargs
.
update
({
'context'
:
self
.
get_serializer_context
(),
'fields'
:
self
.
fields
,
})
return
self
.
get_serializer_class
()(
*
args
,
**
kwargs
)
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
query_params
=
self
.
request
.
query_params
query_params
=
self
.
request
.
query_params
self
.
fields
=
split_query_argument
(
query_params
.
get
(
'fields'
))
self
.
fields
=
split_query_argument
(
query_params
.
get
(
'fields'
))
...
@@ -49,12 +73,61 @@ class CourseSummariesView(generics.ListAPIView):
...
@@ -49,12 +73,61 @@ class CourseSummariesView(generics.ListAPIView):
return
super
(
CourseSummariesView
,
self
)
.
get
(
request
,
*
args
,
**
kwargs
)
return
super
(
CourseSummariesView
,
self
)
.
get
(
request
,
*
args
,
**
kwargs
)
def
default_summary
(
self
,
course_id
,
count_fields
):
"""Default summary with fields populated to default levels."""
summary
=
{
'course_id'
:
course_id
,
'created'
:
None
,
'modes'
:
{},
}
summary
.
update
({
field
:
0
for
field
in
count_fields
})
summary
[
'modes'
]
.
update
({
mode
:
{
count_field
:
0
for
count_field
in
count_fields
}
for
mode
in
enrollment_modes
.
ALL
})
return
summary
def
group_by_mode
(
self
,
queryset
):
"""Return enrollment counts for nested in each mode and top-level enrollment counts."""
formatted_data
=
[]
for
course_id
,
summaries
in
groupby
(
queryset
,
lambda
x
:
(
x
.
course_id
)):
count_fields
=
[
'count'
,
'count_change_7_days'
,
'cumulative_count'
]
item
=
self
.
default_summary
(
course_id
,
count_fields
)
# aggregate the enrollment counts for each mode
for
summary
in
summaries
:
summary_meta_fields
=
[
'catalog_course_title'
,
'catalog_course'
,
'start_date'
,
'end_date'
,
'pacing_type'
,
'availability'
]
item
.
update
({
field
:
getattr
(
summary
,
field
)
for
field
in
summary_meta_fields
})
item
[
'modes'
]
.
update
({
summary
.
mode
:
{
field
:
getattr
(
summary
,
field
)
for
field
in
count_fields
}
})
# treat the most recent as the authoritative created date -- should be all the same
item
[
'created'
]
=
max
(
summary
.
created
,
item
[
'created'
])
if
item
[
'created'
]
else
summary
.
created
# update totals for all counts
item
.
update
({
field
:
item
[
field
]
+
getattr
(
summary
,
field
)
for
field
in
count_fields
})
# Merge professional with non verified professional
modes
=
item
[
'modes'
]
prof_no_id_mode
=
modes
.
pop
(
enrollment_modes
.
PROFESSIONAL_NO_ID
,
{})
prof_mode
=
modes
[
enrollment_modes
.
PROFESSIONAL
]
for
count_key
in
count_fields
:
prof_mode
[
count_key
]
=
prof_mode
.
get
(
count_key
,
0
)
+
prof_no_id_mode
.
pop
(
count_key
,
0
)
formatted_data
.
append
(
item
)
return
formatted_data
@raise_404_if_none
@raise_404_if_none
def
get_queryset
(
self
):
def
get_queryset
(
self
):
if
self
.
course_ids
:
if
self
.
course_ids
:
# create an OR query for course IDs that match
# create an OR query for course IDs that match
query
=
reduce
(
lambda
q
,
course_id
:
q
|
Q
(
course_id
=
course_id
),
self
.
course_ids
,
Q
())
query
=
reduce
(
lambda
q
,
course_id
:
q
|
Q
(
course_id
=
course_id
),
self
.
course_ids
,
Q
())
queryset
=
self
.
model
.
objects
.
filter
(
query
)
queryset
=
self
.
model
.
objects
.
filter
(
query
)
else
:
else
:
queryset
=
self
.
model
.
objects
.
all
()
queryset
=
self
.
model
.
objects
.
all
()
return
queryset
return
self
.
group_by_mode
(
queryset
)
requirements/base.txt
View file @
8103ad0e
...
@@ -8,6 +8,7 @@ djangorestframework-csv==1.4.1 # BSD
...
@@ -8,6 +8,7 @@ djangorestframework-csv==1.4.1 # BSD
django-storages==1.4.1 # BSD
django-storages==1.4.1 # BSD
elasticsearch-dsl==0.0.11 # Apache 2.0
elasticsearch-dsl==0.0.11 # Apache 2.0
ordered-set==2.0.1 # MIT
ordered-set==2.0.1 # MIT
tqdm==4.10.0 # MIT
# markdown is used by swagger for rendering the api docs
# markdown is used by swagger for rendering the api docs
Markdown==2.6.6 # BSD
Markdown==2.6.6 # BSD
...
...
requirements/local.txt
View file @
8103ad0e
# Local development dependencies go here
# Local development dependencies go here
-r base.txt
-r base.txt
tqdm==4.10.0 # MIT
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