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
3809ba5b
Commit
3809ba5b
authored
Jul 29, 2016
by
Dennis Jen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update DRF to 3.4.6
parent
fee33f4c
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
66 additions
and
41 deletions
+66
-41
analytics_data_api/v0/models.py
+4
-4
analytics_data_api/v0/serializers.py
+0
-0
analytics_data_api/v0/tests/views/test_courses.py
+22
-18
analytics_data_api/v0/tests/views/test_learners.py
+1
-2
analytics_data_api/v0/views/__init__.py
+1
-1
analytics_data_api/v0/views/courses.py
+9
-4
analytics_data_api/v0/views/learners.py
+7
-11
analytics_data_api/v0/views/problems.py
+5
-0
analytics_data_api/v0/views/utils.py
+14
-0
analytics_data_api/v0/views/videos.py
+2
-0
requirements/base.txt
+1
-1
No files found.
analytics_data_api/v0/models.py
View file @
3809ba5b
...
...
@@ -234,7 +234,7 @@ class Video(BaseVideo):
class
RosterUpdate
(
DocType
):
date
=
Date
()
date
=
Date
(
format
=
settings
.
DATE_FORMAT
)
# pylint: disable=old-style-class
class
Meta
:
...
...
@@ -265,8 +265,8 @@ class RosterEntry(DocType):
attempt_ratio_order
=
Integer
()
discussion_contributions
=
Integer
()
videos_watched
=
Integer
()
enrollment_date
=
Date
()
last_updated
=
Date
()
enrollment_date
=
Date
(
format
=
settings
.
DATE_FORMAT
)
last_updated
=
Date
(
format
=
settings
.
DATE_FORMAT
)
# pylint: disable=old-style-class
class
Meta
:
...
...
@@ -460,7 +460,7 @@ class ModuleEngagement(models.Model):
course_id
=
models
.
CharField
(
db_index
=
True
,
max_length
=
255
)
username
=
models
.
CharField
(
max_length
=
255
)
date
=
models
.
Date
Time
Field
()
date
=
models
.
DateField
()
# This will be one of "problem", "video" or "discussion"
entity_type
=
models
.
CharField
(
max_length
=
255
)
# For problems this will be the usage key, for videos it will be the html encoded module ID,
...
...
analytics_data_api/v0/serializers.py
View file @
3809ba5b
This diff is collapsed.
Click to expand it.
analytics_data_api/v0/tests/views/test_courses.py
View file @
3809ba5b
...
...
@@ -210,10 +210,11 @@ class CourseActivityLastWeekTest(DemoCourseMixin, TestCaseWithAuthentication):
@staticmethod
def
get_activity_record
(
**
kwargs
):
datetime_format
=
"
%
Y-
%
m-
%
dT
%
H:
%
M:
%
SZ"
default
=
{
'course_id'
:
DEMO_COURSE_ID
,
'interval_start'
:
datetime
.
datetime
(
2014
,
1
,
1
,
0
,
0
,
tzinfo
=
pytz
.
utc
),
'interval_end'
:
datetime
.
datetime
(
2014
,
1
,
8
,
0
,
0
,
tzinfo
=
pytz
.
utc
),
'interval_start'
:
datetime
.
datetime
(
2014
,
1
,
1
,
0
,
0
,
tzinfo
=
pytz
.
utc
)
.
strftime
(
datetime_format
)
,
'interval_end'
:
datetime
.
datetime
(
2014
,
1
,
8
,
0
,
0
,
tzinfo
=
pytz
.
utc
)
.
strftime
(
datetime_format
)
,
'activity_type'
:
'any'
,
'count'
:
300
,
}
...
...
@@ -339,6 +340,9 @@ class CourseEnrollmentByGenderViewTests(CourseEnrollmentViewTestCaseMixin, Defau
super
(
CourseEnrollmentByGenderViewTests
,
self
)
.
setUp
()
self
.
generate_data
()
def
tearDown
(
self
):
self
.
destroy_data
()
def
serialize_enrollment
(
self
,
enrollment
):
return
{
'created'
:
enrollment
.
created
.
strftime
(
settings
.
DATETIME_FORMAT
),
...
...
@@ -606,8 +610,8 @@ class CourseProblemsListViewTests(DemoCourseMixin, TestCaseWithAuthentication):
# Create multiple objects here to test the grouping. Add a model with a different module_id to break up the
# natural order and ensure the view properly sorts the objects before grouping.
module_id
=
'i4x://test/problem/1'
alt_module_id
=
'i4x://test/problem/2'
module_id
=
u
'i4x://test/problem/1'
alt_module_id
=
u
'i4x://test/problem/2'
created
=
datetime
.
datetime
.
utcnow
()
alt_created
=
created
+
datetime
.
timedelta
(
seconds
=
2
)
date_time_format
=
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
...
...
@@ -624,21 +628,21 @@ class CourseProblemsListViewTests(DemoCourseMixin, TestCaseWithAuthentication):
'module_id'
:
module_id
,
'total_submissions'
:
150
,
'correct_submissions'
:
50
,
'part_ids'
:
[
o1
.
part_id
,
o3
.
part_id
]
,
'part_ids'
:
unicode
([
o1
.
part_id
,
o3
.
part_id
])
,
'created'
:
alt_created
.
strftime
(
settings
.
DATETIME_FORMAT
)
},
{
'module_id'
:
alt_module_id
,
'total_submissions'
:
100
,
'correct_submissions'
:
100
,
'part_ids'
:
[
o2
.
part_id
]
,
'created'
:
created
.
strftime
(
settings
.
DATETIME_FORMAT
)
'part_ids'
:
unicode
([
o2
.
part_id
])
,
'created'
:
unicode
(
created
.
strftime
(
settings
.
DATETIME_FORMAT
)
)
}
]
response
=
self
.
_get_data
(
self
.
course_id
)
self
.
assertEquals
(
response
.
status_code
,
200
)
self
.
assertListEqual
(
response
.
data
,
expected
)
self
.
assertListEqual
(
[
dict
(
d
)
for
d
in
response
.
data
]
,
expected
)
def
test_get_404
(
self
):
"""
...
...
@@ -669,8 +673,8 @@ class CourseProblemsAndTagsListViewTests(DemoCourseMixin, TestCaseWithAuthentica
# Create multiple objects here to test the grouping. Add a model with a different module_id to break up the
# natural order and ensure the view properly sorts the objects before grouping.
module_id
=
'i4x://test/problem/1'
alt_module_id
=
'i4x://test/problem/2'
module_id
=
u
'i4x://test/problem/1'
alt_module_id
=
u
'i4x://test/problem/2'
tags
=
{
'difficulty'
:
[
'Easy'
,
'Medium'
,
'Hard'
],
...
...
@@ -695,26 +699,26 @@ class CourseProblemsAndTagsListViewTests(DemoCourseMixin, TestCaseWithAuthentica
'module_id'
:
module_id
,
'total_submissions'
:
11
,
'correct_submissions'
:
4
,
'tags'
:
{
'difficulty'
:
'Easy'
,
'learning_outcome'
:
'Learned a few things'
,
},
'tags'
:
unicode
(
{
u'difficulty'
:
u
'Easy'
,
u'learning_outcome'
:
u
'Learned a few things'
,
}
)
,
'created'
:
alt_created
.
strftime
(
settings
.
DATETIME_FORMAT
)
},
{
'module_id'
:
alt_module_id
,
'total_submissions'
:
4
,
'correct_submissions'
:
0
,
'tags'
:
{
'learning_outcome'
:
'Learned everything'
,
},
'tags'
:
unicode
(
{
u'learning_outcome'
:
u
'Learned everything'
,
}
)
,
'created'
:
created
.
strftime
(
settings
.
DATETIME_FORMAT
)
}
]
response
=
self
.
_get_data
(
self
.
course_id
)
self
.
assertEquals
(
response
.
status_code
,
200
)
self
.
assertListEqual
(
sorted
(
response
.
data
),
sorted
(
expected
))
self
.
assertListEqual
(
sorted
(
[
dict
(
d
)
for
d
in
response
.
data
]
),
sorted
(
expected
))
def
test_get_404
(
self
):
"""
...
...
analytics_data_api/v0/tests/views/test_learners.py
View file @
3809ba5b
...
...
@@ -205,8 +205,7 @@ class LearnerListTests(LearnerAPITestMixin, VerifyCourseIdMixin, TestCaseWithAut
returned.
"""
self
.
assertEqual
(
response
.
status_code
,
200
)
payload
=
json
.
loads
(
response
.
content
)
returned_learners
=
payload
[
'results'
]
returned_learners
=
json
.
loads
(
response
.
content
)[
'results'
]
if
expected_learners
is
None
:
self
.
assertEqual
(
returned_learners
,
list
())
else
:
...
...
analytics_data_api/v0/views/__init__.py
View file @
3809ba5b
...
...
@@ -12,7 +12,7 @@ class CourseViewMixin(object):
course_id
=
None
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
course_id
=
self
.
kwargs
.
get
(
'course_id'
,
request
.
QUERY_PARAMS
.
get
(
'course_id'
,
None
))
self
.
course_id
=
self
.
kwargs
.
get
(
'course_id'
,
request
.
query_params
.
get
(
'course_id'
,
None
))
if
not
self
.
course_id
:
raise
CourseNotSpecifiedError
()
...
...
analytics_data_api/v0/views/courses.py
View file @
3809ba5b
...
...
@@ -15,6 +15,8 @@ from analytics_data_api.constants import enrollment_modes
from
analytics_data_api.utils
import
dictfetchall
from
analytics_data_api.v0
import
models
,
serializers
from
utils
import
raise_404_if_none
class
BaseCourseView
(
generics
.
ListAPIView
):
start_date
=
None
...
...
@@ -25,8 +27,8 @@ class BaseCourseView(generics.ListAPIView):
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
course_id
=
self
.
kwargs
.
get
(
'course_id'
)
start_date
=
request
.
QUERY_PARAMS
.
get
(
'start_date'
)
end_date
=
request
.
QUERY_PARAMS
.
get
(
'end_date'
)
start_date
=
request
.
query_params
.
get
(
'start_date'
)
end_date
=
request
.
query_params
.
get
(
'end_date'
)
timezone
=
utc
self
.
start_date
=
self
.
parse_date
(
start_date
,
timezone
)
...
...
@@ -46,6 +48,7 @@ class BaseCourseView(generics.ListAPIView):
def
apply_date_filtering
(
self
,
queryset
):
raise
NotImplementedError
@raise_404_if_none
def
get_queryset
(
self
):
queryset
=
self
.
model
.
objects
.
filter
(
course_id
=
self
.
course_id
)
queryset
=
self
.
apply_date_filtering
(
queryset
)
...
...
@@ -232,9 +235,9 @@ class CourseActivityMostRecentWeekView(generics.RetrieveAPIView):
""" Retrieve the activity type from the query string. """
# Support the old label param
activity_type
=
self
.
request
.
QUERY_PARAMS
.
get
(
'label'
,
None
)
activity_type
=
self
.
request
.
query_params
.
get
(
'label'
,
None
)
activity_type
=
activity_type
or
self
.
request
.
QUERY_PARAMS
.
get
(
'activity_type'
,
self
.
DEFAULT_ACTIVITY_TYPE
)
activity_type
=
activity_type
or
self
.
request
.
query_params
.
get
(
'activity_type'
,
self
.
DEFAULT_ACTIVITY_TYPE
)
activity_type
=
self
.
_format_activity_type
(
activity_type
)
return
activity_type
...
...
@@ -633,6 +636,7 @@ class ProblemsListView(BaseCourseView):
serializer_class
=
serializers
.
ProblemSerializer
allow_empty
=
False
@raise_404_if_none
def
get_queryset
(
self
):
# last_response_count is the number of submissions for the problem part and must
# be divided by the number of problem parts to get the problem submission rather
...
...
@@ -709,6 +713,7 @@ class ProblemsAndTagsListView(BaseCourseView):
allow_empty
=
False
model
=
models
.
ProblemsAndTags
@raise_404_if_none
def
get_queryset
(
self
):
queryset
=
self
.
model
.
objects
.
filter
(
course_id
=
self
.
course_id
)
items
=
queryset
.
all
()
...
...
analytics_data_api/v0/views/learners.py
View file @
3809ba5b
...
...
@@ -5,9 +5,6 @@ import logging
from
rest_framework
import
generics
,
status
from
analytics_data_api.constants
import
(
learner
)
from
analytics_data_api.v0.exceptions
import
(
LearnerEngagementTimelineNotFoundError
,
LearnerNotFoundError
,
...
...
@@ -21,7 +18,7 @@ from analytics_data_api.v0.models import (
)
from
analytics_data_api.v0.serializers
import
(
CourseLearnerMetadataSerializer
,
E
lasticsearchDSLSearch
Serializer
,
E
dxPagination
Serializer
,
EngagementDaySerializer
,
LastUpdatedSerializer
,
LearnerSerializer
,
...
...
@@ -187,14 +184,12 @@ class LearnerListView(LastUpdateMixin, CourseViewMixin, generics.ListAPIView):
"""
serializer_class
=
LearnerSerializer
pagination_serializer_class
=
ElasticsearchDSLSearchSerializer
paginate_by_param
=
'page_size'
paginate_by
=
learner
.
LEARNER_API_DEFAULT_LIST_PAGE_SIZE
pagination_class
=
EdxPaginationSerializer
max_paginate_by
=
100
# TODO -- tweak during load testing
def
_validate_query_params
(
self
):
"""Validates various querystring parameters."""
query_params
=
self
.
request
.
QUERY_PARAMS
query_params
=
self
.
request
.
query_params
page
=
query_params
.
get
(
'page'
)
if
page
:
try
:
...
...
@@ -222,8 +217,9 @@ class LearnerListView(LastUpdateMixin, CourseViewMixin, generics.ListAPIView):
"""
response
=
super
(
LearnerListView
,
self
)
.
list
(
request
,
args
,
kwargs
)
last_updated
=
self
.
get_last_updated
()
for
result
in
response
.
data
[
'results'
]:
result
.
update
(
last_updated
)
if
response
.
data
[
'results'
]
is
not
None
:
for
result
in
response
.
data
[
'results'
]:
result
.
update
(
last_updated
)
return
response
def
get_queryset
(
self
):
...
...
@@ -232,7 +228,7 @@ class LearnerListView(LastUpdateMixin, CourseViewMixin, generics.ListAPIView):
as a an array of dicts with fields "learner" and "last_updated".
"""
self
.
_validate_query_params
()
query_params
=
self
.
request
.
QUERY_PARAMS
query_params
=
self
.
request
.
query_params
order_by
=
query_params
.
get
(
'order_by'
)
sort_order
=
query_params
.
get
(
'sort_order'
)
...
...
analytics_data_api/v0/views/problems.py
View file @
3809ba5b
...
...
@@ -22,6 +22,8 @@ from analytics_data_api.v0.serializers import (
)
from
analytics_data_api.utils
import
matching_tuple
from
utils
import
raise_404_if_none
class
ProblemResponseAnswerDistributionView
(
generics
.
ListAPIView
):
"""
...
...
@@ -98,6 +100,7 @@ class ProblemResponseAnswerDistributionView(generics.ListAPIView):
return
consolidated_answers
@raise_404_if_none
def
get_queryset
(
self
):
"""Select all the answer distribution response having to do with this usage of the problem."""
problem_id
=
self
.
kwargs
.
get
(
'problem_id'
)
...
...
@@ -142,6 +145,7 @@ class GradeDistributionView(generics.ListAPIView):
serializer_class
=
GradeDistributionSerializer
allow_empty
=
False
@raise_404_if_none
def
get_queryset
(
self
):
"""Select all grade distributions for a particular module"""
problem_id
=
self
.
kwargs
.
get
(
'problem_id'
)
...
...
@@ -170,6 +174,7 @@ class SequentialOpenDistributionView(generics.ListAPIView):
serializer_class
=
SequentialOpenDistributionSerializer
allow_empty
=
False
@raise_404_if_none
def
get_queryset
(
self
):
"""Select the view count for a specific module"""
module_id
=
self
.
kwargs
.
get
(
'module_id'
)
...
...
analytics_data_api/v0/views/utils.py
View file @
3809ba5b
"""Utilities for view-level API logic."""
from
django.http
import
Http404
def
split_query_argument
(
argument
):
...
...
@@ -10,3 +11,15 @@ def split_query_argument(argument):
return
argument
.
split
(
','
)
else
:
return
None
def
raise_404_if_none
(
func
):
"""
Decorator for raiseing Http404 if function evaulation is falsey (e.g. empty queryset).
"""
def
func_wrapper
(
self
):
queryset
=
func
(
self
)
if
queryset
:
return
queryset
else
:
raise
Http404
return
func_wrapper
\ No newline at end of file
analytics_data_api/v0/views/videos.py
View file @
3809ba5b
...
...
@@ -7,6 +7,7 @@ from rest_framework import generics
from
analytics_data_api.v0.models
import
VideoTimeline
from
analytics_data_api.v0.serializers
import
VideoTimelineSerializer
from
utils
import
raise_404_if_none
class
VideoTimelineView
(
generics
.
ListAPIView
):
"""
...
...
@@ -30,6 +31,7 @@ class VideoTimelineView(generics.ListAPIView):
serializer_class
=
VideoTimelineSerializer
allow_empty
=
False
@raise_404_if_none
def
get_queryset
(
self
):
"""Select the view count for a specific module"""
video_id
=
self
.
kwargs
.
get
(
'video_id'
)
...
...
requirements/base.txt
View file @
3809ba5b
boto==2.22.1 # MIT
Django==1.8.14 # BSD License
django-model-utils==2.2 # BSD
djangorestframework==
2.4.4
# BSD
djangorestframework==
3.4.6
# BSD
django-rest-swagger==0.2.8 # BSD
djangorestframework-csv==1.3.3 # BSD
django-countries==3.2 # 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