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
e9c0c069
Commit
e9c0c069
authored
Dec 14, 2015
by
Dennis Jen
Committed by
Daniel Friedman
Apr 11, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added fields for learner endpoints.
parent
9fb50c73
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
60 additions
and
28 deletions
+60
-28
analytics_data_api/v0/models.py
+31
-10
analytics_data_api/v0/serializers.py
+18
-15
analytics_data_api/v0/tests/views/test_learners.py
+0
-0
analytics_data_api/v0/views/learners.py
+11
-3
No files found.
analytics_data_api/v0/models.py
View file @
e9c0c069
...
@@ -3,7 +3,9 @@ from itertools import groupby
...
@@ -3,7 +3,9 @@ from itertools import groupby
from
django.conf
import
settings
from
django.conf
import
settings
from
django.db
import
models
from
django.db
import
models
from
django.db.models
import
Sum
from
django.db.models
import
Sum
from
elasticsearch_dsl
import
DocType
,
Q
# some fields (e.g. Float, Integer) are dynamic and your IDE may highlight them as unavailable
from
elasticsearch_dsl
import
Date
,
DocType
,
Float
,
Integer
,
Q
,
String
from
analytics_data_api.constants
import
country
,
engagement_entity_types
,
genders
,
learner
from
analytics_data_api.constants
import
country
,
engagement_entity_types
,
genders
,
learner
...
@@ -214,6 +216,27 @@ class Video(BaseVideo):
...
@@ -214,6 +216,27 @@ class Video(BaseVideo):
class
RosterEntry
(
DocType
):
class
RosterEntry
(
DocType
):
course_id
=
String
()
username
=
String
()
name
=
String
()
email
=
String
()
enrollment_mode
=
String
()
cohort
=
String
()
segments
=
String
(
fields
=
{
'raw'
:
String
()})
problems_attempted
=
Integer
()
problems_completed
=
Integer
()
problem_attempts_per_completed
=
Float
()
# Useful for ordering problem_attempts_per_completed (because results can include null, which is
# different from zero). attempt_ratio_order is equal to the number of problem attempts if
# problem_attempts_per_completed is > 1 and set to -problem_attempts if
# problem_attempts_per_completed = 1.
attempt_ratio_order
=
Integer
()
discussions_contributed
=
Integer
()
videos_watched
=
Integer
()
enrollment_date
=
Date
()
last_updated
=
Date
()
# pylint: disable=old-style-class
# pylint: disable=old-style-class
class
Meta
:
class
Meta
:
index
=
settings
.
ELASTICSEARCH_LEARNERS_INDEX
index
=
settings
.
ELASTICSEARCH_LEARNERS_INDEX
...
@@ -230,8 +253,7 @@ class RosterEntry(DocType):
...
@@ -230,8 +253,7 @@ class RosterEntry(DocType):
course_id
,
course_id
,
segments
=
None
,
segments
=
None
,
ignore_segments
=
None
,
ignore_segments
=
None
,
# TODO: enable during https://openedx.atlassian.net/browse/AN-6319
cohort
=
None
,
# cohort=None,
enrollment_mode
=
None
,
enrollment_mode
=
None
,
text_search
=
None
,
text_search
=
None
,
order_by
=
'username'
,
order_by
=
'username'
,
...
@@ -251,13 +273,14 @@ class RosterEntry(DocType):
...
@@ -251,13 +273,14 @@ class RosterEntry(DocType):
segment
=
segment
,
segments
=
', '
.
join
(
learner
.
SEGMENTS
)
segment
=
segment
,
segments
=
', '
.
join
(
learner
.
SEGMENTS
)
))
))
order_by_options
=
(
order_by_options
=
(
'username'
,
'email'
,
'discussions_contributed'
,
'problems_attempted'
,
'problems_completed'
,
'videos_viewed'
'username'
,
'email'
,
'discussions_contributed'
,
'problems_attempted'
,
'problems_completed'
,
'attempt_ratio_order'
,
'videos_viewed'
)
)
sort_order_options
=
(
'asc'
,
'desc'
)
if
order_by
not
in
order_by_options
:
if
order_by
not
in
order_by_options
:
raise
ValueError
(
"order_by value '{order_by}' must be one of: ({order_by_options})"
.
format
(
raise
ValueError
(
"order_by value '{order_by}' must be one of: ({order_by_options})"
.
format
(
order_by
=
order_by
,
order_by_options
=
', '
.
join
(
order_by_options
)
order_by
=
order_by
,
order_by_options
=
', '
.
join
(
order_by_options
)
))
))
sort_order_options
=
(
'asc'
,
'desc'
)
if
sort_order
not
in
sort_order_options
:
if
sort_order
not
in
sort_order_options
:
raise
ValueError
(
"sort_order value '{sort_order}' must be one of: ({sort_order_options})"
.
format
(
raise
ValueError
(
"sort_order value '{sort_order}' must be one of: ({sort_order_options})"
.
format
(
sort_order
=
sort_order
,
sort_order_options
=
', '
.
join
(
sort_order_options
)
sort_order
=
sort_order
,
sort_order_options
=
', '
.
join
(
sort_order_options
)
...
@@ -272,9 +295,8 @@ class RosterEntry(DocType):
...
@@ -272,9 +295,8 @@ class RosterEntry(DocType):
elif
ignore_segments
:
elif
ignore_segments
:
for
segment
in
ignore_segments
:
for
segment
in
ignore_segments
:
search
=
search
.
query
(
~
Q
(
'term'
,
segments
=
segment
))
search
=
search
.
query
(
~
Q
(
'term'
,
segments
=
segment
))
# TODO: enable during https://openedx.atlassian.net/browse/AN-6319
if
cohort
:
# if cohort:
search
=
search
.
query
(
'term'
,
cohort
=
cohort
)
# search = search.query('term', cohort=cohort)
if
enrollment_mode
:
if
enrollment_mode
:
search
=
search
.
query
(
'term'
,
enrollment_mode
=
enrollment_mode
)
search
=
search
.
query
(
'term'
,
enrollment_mode
=
enrollment_mode
)
if
text_search
:
if
text_search
:
...
@@ -309,8 +331,7 @@ class RosterEntry(DocType):
...
@@ -309,8 +331,7 @@ class RosterEntry(DocType):
search
.
query
=
Q
(
'bool'
,
must
=
[
Q
(
'term'
,
course_id
=
course_id
)])
search
.
query
=
Q
(
'bool'
,
must
=
[
Q
(
'term'
,
course_id
=
course_id
)])
search
.
aggs
.
bucket
(
'enrollment_modes'
,
'terms'
,
field
=
'enrollment_mode'
)
search
.
aggs
.
bucket
(
'enrollment_modes'
,
'terms'
,
field
=
'enrollment_mode'
)
search
.
aggs
.
bucket
(
'segments'
,
'terms'
,
field
=
'segments'
)
search
.
aggs
.
bucket
(
'segments'
,
'terms'
,
field
=
'segments'
)
# TODO: enable during https://openedx.atlassian.net/browse/AN-6319
search
.
aggs
.
bucket
(
'cohorts'
,
'terms'
,
field
=
'cohort'
)
# search.aggs.bucket('group_by_cohorts', 'terms', field='cohort')
response
=
search
.
execute
()
response
=
search
.
execute
()
# Build up the map of aggregation name to count
# Build up the map of aggregation name to count
aggregations
=
{
aggregations
=
{
...
...
analytics_data_api/v0/serializers.py
View file @
e9c0c069
...
@@ -317,7 +317,7 @@ class VideoTimelineSerializer(ModelSerializerWithCreatedField):
...
@@ -317,7 +317,7 @@ class VideoTimelineSerializer(ModelSerializerWithCreatedField):
)
)
class
LearnerSerializer
(
serializers
.
Serializer
):
class
LearnerSerializer
(
serializers
.
Serializer
,
DefaultIfNoneMixin
):
username
=
serializers
.
CharField
()
username
=
serializers
.
CharField
()
enrollment_mode
=
serializers
.
CharField
()
enrollment_mode
=
serializers
.
CharField
()
name
=
serializers
.
CharField
()
name
=
serializers
.
CharField
()
...
@@ -325,11 +325,9 @@ class LearnerSerializer(serializers.Serializer):
...
@@ -325,11 +325,9 @@ class LearnerSerializer(serializers.Serializer):
email
=
serializers
.
CharField
()
email
=
serializers
.
CharField
()
segments
=
serializers
.
Field
(
source
=
'segments'
)
segments
=
serializers
.
Field
(
source
=
'segments'
)
engagements
=
serializers
.
SerializerMethodField
(
'get_engagements'
)
engagements
=
serializers
.
SerializerMethodField
(
'get_engagements'
)
enrollment_date
=
serializers
.
DateField
(
format
=
settings
.
DATE_FORMAT
)
# TODO: add these back in when the index returns them
last_updated
=
serializers
.
DateField
(
format
=
settings
.
DATE_FORMAT
)
# enrollment_date = serializers.DateField(format=settings.DATE_FORMAT, allow_empty=True)
cohort
=
serializers
.
CharField
()
# last_updated = serializers.DateField(format=settings.DATE_FORMAT)
# cohort = serializers.CharField(allow_none=True)
def
get_account_url
(
self
,
obj
):
def
get_account_url
(
self
,
obj
):
if
settings
.
LMS_USER_ACCOUNT_BASE_URL
:
if
settings
.
LMS_USER_ACCOUNT_BASE_URL
:
...
@@ -342,10 +340,17 @@ class LearnerSerializer(serializers.Serializer):
...
@@ -342,10 +340,17 @@ class LearnerSerializer(serializers.Serializer):
Add the engagement totals.
Add the engagement totals.
"""
"""
engagements
=
{}
engagements
=
{}
for
entity_type
in
engagement_entity_types
.
AGGREGATE_TYPES
:
for
event
in
engagement_events
.
EVENTS
[
entity_type
]:
# fill in these fields will 0 if values not returned/found
metric
=
'{0}_{1}'
.
format
(
entity_type
,
event
)
default_if_none_fields
=
[
'discussions_contributed'
,
'problems_attempted'
,
engagements
[
metric
]
=
getattr
(
obj
,
metric
,
0
)
'problems_completed'
,
'videos_viewed'
]
for
field
in
default_if_none_fields
:
engagements
[
field
]
=
self
.
default_if_none
(
getattr
(
obj
,
field
,
None
),
0
)
# preserve null values for problem attempts per completed
no_default_field
=
'problem_attempts_per_completed'
engagements
[
no_default_field
]
=
getattr
(
obj
,
no_default_field
,
None
)
return
engagements
return
engagements
...
@@ -424,8 +429,7 @@ class EnagementRangeMetricSerializer(serializers.Serializer):
...
@@ -424,8 +429,7 @@ class EnagementRangeMetricSerializer(serializers.Serializer):
class
CourseLearnerMetadataSerializer
(
serializers
.
Serializer
):
class
CourseLearnerMetadataSerializer
(
serializers
.
Serializer
):
enrollment_modes
=
serializers
.
SerializerMethodField
(
'get_enrollment_modes'
)
enrollment_modes
=
serializers
.
SerializerMethodField
(
'get_enrollment_modes'
)
segments
=
serializers
.
SerializerMethodField
(
'get_segments'
)
segments
=
serializers
.
SerializerMethodField
(
'get_segments'
)
# TODO: enable during https://openedx.atlassian.net/browse/AN-6319
cohorts
=
serializers
.
SerializerMethodField
(
'get_cohorts'
)
# cohorts = serializers.SerializerMethodField('get_cohorts')
engagement_ranges
=
serializers
.
SerializerMethodField
(
'get_engagement_ranges'
)
engagement_ranges
=
serializers
.
SerializerMethodField
(
'get_engagement_ranges'
)
def
get_enrollment_modes
(
self
,
obj
):
def
get_enrollment_modes
(
self
,
obj
):
...
@@ -434,9 +438,8 @@ class CourseLearnerMetadataSerializer(serializers.Serializer):
...
@@ -434,9 +438,8 @@ class CourseLearnerMetadataSerializer(serializers.Serializer):
def
get_segments
(
self
,
obj
):
def
get_segments
(
self
,
obj
):
return
obj
[
'es_data'
][
'segments'
]
return
obj
[
'es_data'
][
'segments'
]
# TODO: enable during https://openedx.atlassian.net/browse/AN-6319
def
get_cohorts
(
self
,
obj
):
# def get_cohorts(self, obj):
return
obj
[
'es_data'
][
'cohorts'
]
# return obj['es_data']['cohorts']
def
get_engagement_ranges
(
self
,
obj
):
def
get_engagement_ranges
(
self
,
obj
):
query_set
=
obj
[
'engagement_ranges'
]
query_set
=
obj
[
'engagement_ranges'
]
...
...
analytics_data_api/v0/tests/views/test_learners.py
View file @
e9c0c069
This diff is collapsed.
Click to expand it.
analytics_data_api/v0/views/learners.py
View file @
e9c0c069
...
@@ -174,14 +174,21 @@ class LearnerListView(CourseViewMixin, generics.ListAPIView):
...
@@ -174,14 +174,21 @@ class LearnerListView(CourseViewMixin, generics.ListAPIView):
"""
"""
self
.
_validate_query_params
()
self
.
_validate_query_params
()
query_params
=
self
.
request
.
QUERY_PARAMS
query_params
=
self
.
request
.
QUERY_PARAMS
# Ordering by problem_attempts_per_completed can be ambiguous because
# it's a ratio values could be infinite (e.g. divide by zero) if no problems
# were completed. Instead, sorting by attempt_ratio_order will produce
# a sensible ordering
order_by
=
query_params
.
get
(
'order_by'
)
order_by
=
'attempt_ratio_order'
if
order_by
==
'problem_attempts_per_completed'
else
order_by
params
=
{
params
=
{
'segments'
:
split_query_argument
(
query_params
.
get
(
'segments'
)),
'segments'
:
split_query_argument
(
query_params
.
get
(
'segments'
)),
'ignore_segments'
:
split_query_argument
(
query_params
.
get
(
'ignore_segments'
)),
'ignore_segments'
:
split_query_argument
(
query_params
.
get
(
'ignore_segments'
)),
# TODO: enable during https://openedx.atlassian.net/browse/AN-6319
'cohort'
:
query_params
.
get
(
'cohort'
),
# 'cohort': query_params.get('cohort'),
'enrollment_mode'
:
query_params
.
get
(
'enrollment_mode'
),
'enrollment_mode'
:
query_params
.
get
(
'enrollment_mode'
),
'text_search'
:
query_params
.
get
(
'text_search'
),
'text_search'
:
query_params
.
get
(
'text_search'
),
'order_by'
:
query_params
.
get
(
'order_by'
)
,
'order_by'
:
order_by
,
'sort_order'
:
query_params
.
get
(
'sort_order'
)
'sort_order'
:
query_params
.
get
(
'sort_order'
)
}
}
# Remove None values from `params` so that we don't overwrite default
# Remove None values from `params` so that we don't overwrite default
...
@@ -211,6 +218,7 @@ class EngagementTimelineView(CourseViewMixin, generics.ListAPIView):
...
@@ -211,6 +218,7 @@ class EngagementTimelineView(CourseViewMixin, generics.ListAPIView):
* problems_completed: Unique number of problems completed.
* problems_completed: Unique number of problems completed.
* discussions_contributed: Number of discussions participated in (e.g. forum posts)
* discussions_contributed: Number of discussions participated in (e.g. forum posts)
* videos_viewed: Number of videos watched.
* videos_viewed: Number of videos watched.
* problem_attempts_per_completed: TBD
**Parameters**
**Parameters**
...
...
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