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
c345ca79
Commit
c345ca79
authored
May 03, 2016
by
Dmitry Viskov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
new API method to return tags distribution info
parent
a4b39021
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
183 additions
and
2 deletions
+183
-2
AUTHORS
+1
-0
analytics_data_api/management/commands/generate_fake_course_data.py
+29
-0
analytics_data_api/v0/models.py
+15
-0
analytics_data_api/v0/serializers.py
+12
-0
analytics_data_api/v0/tests/views/test_courses.py
+76
-0
analytics_data_api/v0/urls/courses.py
+1
-0
analytics_data_api/v0/views/courses.py
+49
-2
No files found.
AUTHORS
View file @
c345ca79
...
@@ -6,3 +6,4 @@ Ed Zarecor <ed@edx.org>
...
@@ -6,3 +6,4 @@ Ed Zarecor <ed@edx.org>
Gabe Mulley <gabe@edx.org>
Gabe Mulley <gabe@edx.org>
Jason Bau <jbau@stanford.edu>
Jason Bau <jbau@stanford.edu>
John Jarvis <jarv@edx.org>
John Jarvis <jarv@edx.org>
Dmitry Viskov <dmitry.viskov@webenterprise.ru>
analytics_data_api/management/commands/generate_fake_course_data.py
View file @
c345ca79
...
@@ -225,6 +225,34 @@ class Command(BaseCommand):
...
@@ -225,6 +225,34 @@ class Command(BaseCommand):
course_id
=
course_id
,
start_date
=
start_date
,
end_date
=
end_date
,
metric
=
metric
,
course_id
=
course_id
,
start_date
=
start_date
,
end_date
=
end_date
,
metric
=
metric
,
range_type
=
'high'
,
low_value
=
high_floor
,
high_value
=
max_value
)
range_type
=
'high'
,
low_value
=
high_floor
,
high_value
=
max_value
)
def
generate_tags_distribution_data
(
self
,
course_id
):
logger
.
info
(
"Deleting existed tags distribution data..."
)
models
.
ProblemsAndTags
.
objects
.
all
()
.
delete
()
module_id_tpl
=
'i4x://test/problem/
%
d'
difficulty_tag
=
[
'Easy'
,
'Medium'
,
'Hard'
]
learning_outcome_tag
=
[
'Learned nothing'
,
'Learned a few things'
,
'Learned everything'
]
problems_num
=
50
chance_difficulty
=
5
logger
.
info
(
"Generating new tags distribution data..."
)
for
i
in
xrange
(
problems_num
):
module_id
=
module_id_tpl
%
i
total_submissions
=
random
.
randint
(
0
,
100
)
correct_submissions
=
random
.
randint
(
0
,
total_submissions
)
models
.
ProblemsAndTags
.
objects
.
create
(
course_id
=
course_id
,
module_id
=
module_id
,
tag_name
=
'learning_outcome'
,
tag_value
=
random
.
choice
(
learning_outcome_tag
),
total_submissions
=
total_submissions
,
correct_submissions
=
correct_submissions
)
if
random
.
randint
(
0
,
chance_difficulty
)
!=
chance_difficulty
:
models
.
ProblemsAndTags
.
objects
.
create
(
course_id
=
course_id
,
module_id
=
module_id
,
tag_name
=
'difficulty'
,
tag_value
=
random
.
choice
(
difficulty_tag
),
total_submissions
=
total_submissions
,
correct_submissions
=
correct_submissions
)
def
handle
(
self
,
*
args
,
**
options
):
def
handle
(
self
,
*
args
,
**
options
):
course_id
=
options
[
'course_id'
]
course_id
=
options
[
'course_id'
]
username
=
options
[
'username'
]
username
=
options
[
'username'
]
...
@@ -245,3 +273,4 @@ class Command(BaseCommand):
...
@@ -245,3 +273,4 @@ class Command(BaseCommand):
self
.
generate_video_timeline_data
(
video_id
)
self
.
generate_video_timeline_data
(
video_id
)
self
.
generate_learner_engagement_data
(
course_id
,
username
,
start_date
,
end_date
)
self
.
generate_learner_engagement_data
(
course_id
,
username
,
start_date
,
end_date
)
self
.
generate_learner_engagement_range_data
(
course_id
,
start_date
,
end_date
)
self
.
generate_learner_engagement_range_data
(
course_id
,
start_date
,
end_date
)
self
.
generate_tags_distribution_data
(
course_id
)
analytics_data_api/v0/models.py
View file @
c345ca79
...
@@ -131,6 +131,21 @@ class ProblemResponseAnswerDistribution(BaseProblemResponseAnswerDistribution):
...
@@ -131,6 +131,21 @@ class ProblemResponseAnswerDistribution(BaseProblemResponseAnswerDistribution):
count
=
models
.
IntegerField
()
count
=
models
.
IntegerField
()
class
ProblemsAndTags
(
models
.
Model
):
""" Model for the tags_distribution table """
class
Meta
(
object
):
db_table
=
'tags_distribution'
course_id
=
models
.
CharField
(
db_index
=
True
,
max_length
=
255
)
module_id
=
models
.
CharField
(
db_index
=
True
,
max_length
=
255
)
tag_name
=
models
.
CharField
(
max_length
=
255
)
tag_value
=
models
.
CharField
(
max_length
=
255
)
total_submissions
=
models
.
IntegerField
(
default
=
0
)
correct_submissions
=
models
.
IntegerField
(
default
=
0
)
created
=
models
.
DateTimeField
(
auto_now_add
=
True
)
class
ProblemFirstLastResponseAnswerDistribution
(
BaseProblemResponseAnswerDistribution
):
class
ProblemFirstLastResponseAnswerDistribution
(
BaseProblemResponseAnswerDistribution
):
""" Updated model for answer_distribution table with counts of first and last attempts at problems. """
""" Updated model for answer_distribution table with counts of first and last attempts at problems. """
...
...
analytics_data_api/v0/serializers.py
View file @
c345ca79
...
@@ -57,6 +57,18 @@ class ProblemSerializer(serializers.Serializer):
...
@@ -57,6 +57,18 @@ class ProblemSerializer(serializers.Serializer):
created
=
serializers
.
DateTimeField
(
format
=
settings
.
DATETIME_FORMAT
)
created
=
serializers
.
DateTimeField
(
format
=
settings
.
DATETIME_FORMAT
)
class
ProblemsAndTagsSerializer
(
serializers
.
Serializer
):
"""
Serializer for problems and tags.
"""
module_id
=
serializers
.
CharField
(
required
=
True
)
total_submissions
=
serializers
.
IntegerField
(
default
=
0
)
correct_submissions
=
serializers
.
IntegerField
(
default
=
0
)
tags
=
serializers
.
CharField
()
created
=
serializers
.
DateTimeField
(
format
=
settings
.
DATETIME_FORMAT
)
class
ProblemResponseAnswerDistributionSerializer
(
ModelSerializerWithCreatedField
):
class
ProblemResponseAnswerDistributionSerializer
(
ModelSerializerWithCreatedField
):
"""
"""
Representation of the Answer Distribution table, without id.
Representation of the Answer Distribution table, without id.
...
...
analytics_data_api/v0/tests/views/test_courses.py
View file @
c345ca79
...
@@ -650,6 +650,82 @@ class CourseProblemsListViewTests(DemoCourseMixin, TestCaseWithAuthentication):
...
@@ -650,6 +650,82 @@ class CourseProblemsListViewTests(DemoCourseMixin, TestCaseWithAuthentication):
self
.
assertEquals
(
response
.
status_code
,
404
)
self
.
assertEquals
(
response
.
status_code
,
404
)
class
CourseProblemsAndTagsListViewTests
(
DemoCourseMixin
,
TestCaseWithAuthentication
):
def
_get_data
(
self
,
course_id
=
None
):
"""
Retrieve data for the specified course.
"""
course_id
=
course_id
or
self
.
course_id
url
=
'/api/v0/courses/{}/problems_and_tags/'
.
format
(
course_id
)
return
self
.
authenticated_get
(
url
)
def
test_get
(
self
):
"""
The view should return data when data exists for the course.
"""
# This data should never be returned by the tests below because the course_id doesn't match.
G
(
models
.
ProblemsAndTags
)
# 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'
tags
=
{
'difficulty'
:
[
'Easy'
,
'Medium'
,
'Hard'
],
'learning_outcome'
:
[
'Learned nothing'
,
'Learned a few things'
,
'Learned everything'
]
}
created
=
datetime
.
datetime
.
utcnow
()
alt_created
=
created
+
datetime
.
timedelta
(
seconds
=
2
)
G
(
models
.
ProblemsAndTags
,
course_id
=
self
.
course_id
,
module_id
=
module_id
,
tag_name
=
'difficulty'
,
tag_value
=
tags
[
'difficulty'
][
0
],
total_submissions
=
11
,
correct_submissions
=
4
,
created
=
created
)
G
(
models
.
ProblemsAndTags
,
course_id
=
self
.
course_id
,
module_id
=
module_id
,
tag_name
=
'learning_outcome'
,
tag_value
=
tags
[
'learning_outcome'
][
1
],
total_submissions
=
11
,
correct_submissions
=
4
,
created
=
alt_created
)
G
(
models
.
ProblemsAndTags
,
course_id
=
self
.
course_id
,
module_id
=
alt_module_id
,
tag_name
=
'learning_outcome'
,
tag_value
=
tags
[
'learning_outcome'
][
2
],
total_submissions
=
4
,
correct_submissions
=
0
,
created
=
created
)
expected
=
[
{
'module_id'
:
module_id
,
'total_submissions'
:
11
,
'correct_submissions'
:
4
,
'tags'
:
{
'difficulty'
:
'Easy'
,
'learning_outcome'
:
'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'
,
},
'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
))
def
test_get_404
(
self
):
"""
The view should return 404 if no data exists for the course.
"""
response
=
self
.
_get_data
(
'foo/bar/course'
)
self
.
assertEquals
(
response
.
status_code
,
404
)
class
CourseVideosListViewTests
(
DemoCourseMixin
,
TestCaseWithAuthentication
):
class
CourseVideosListViewTests
(
DemoCourseMixin
,
TestCaseWithAuthentication
):
def
_get_data
(
self
,
course_id
=
None
):
def
_get_data
(
self
,
course_id
=
None
):
"""
"""
...
...
analytics_data_api/v0/urls/courses.py
View file @
c345ca79
...
@@ -13,6 +13,7 @@ COURSE_URLS = [
...
@@ -13,6 +13,7 @@ COURSE_URLS = [
(
'enrollment/gender'
,
views
.
CourseEnrollmentByGenderView
,
'enrollment_by_gender'
),
(
'enrollment/gender'
,
views
.
CourseEnrollmentByGenderView
,
'enrollment_by_gender'
),
(
'enrollment/location'
,
views
.
CourseEnrollmentByLocationView
,
'enrollment_by_location'
),
(
'enrollment/location'
,
views
.
CourseEnrollmentByLocationView
,
'enrollment_by_location'
),
(
'problems'
,
views
.
ProblemsListView
,
'problems'
),
(
'problems'
,
views
.
ProblemsListView
,
'problems'
),
(
'problems_and_tags'
,
views
.
ProblemsAndTagsListView
,
'problems_and_tags'
),
(
'videos'
,
views
.
VideosListView
,
'videos'
)
(
'videos'
,
views
.
VideosListView
,
'videos'
)
]
]
...
...
analytics_data_api/v0/views/courses.py
View file @
c345ca79
...
@@ -628,9 +628,9 @@ class ProblemsListView(BaseCourseView):
...
@@ -628,9 +628,9 @@ class ProblemsListView(BaseCourseView):
Returns a collection of submission counts and part IDs for each problem. Each collection contains:
Returns a collection of submission counts and part IDs for each problem. Each collection contains:
* module_id: The ID of the problem.
* module_id: The ID of the problem.
* total_submissions: Total number of submissions
* total_submissions: Total number of submissions
.
* correct_submissions: Total number of *correct* submissions.
* correct_submissions: Total number of *correct* submissions.
* part_ids: List of problem part IDs
* part_ids: List of problem part IDs
.
"""
"""
serializer_class
=
serializers
.
ProblemSerializer
serializer_class
=
serializers
.
ProblemSerializer
allow_empty
=
False
allow_empty
=
False
...
@@ -689,6 +689,53 @@ GROUP BY module_id;
...
@@ -689,6 +689,53 @@ GROUP BY module_id;
return
rows
return
rows
# pylint: disable=abstract-method
class
ProblemsAndTagsListView
(
BaseCourseView
):
"""
Get the problems with the connected tags.
**Example request**
GET /api/v0/courses/{course_id}/problems_and_tags/
**Response Values**
Returns a collection of submission counts and tags for each problem. Each collection contains:
* module_id: The ID of the problem.
* total_submissions: Total number of submissions.
* correct_submissions: Total number of *correct* submissions.
* tags: Dictionary that contains pairs "tag key: tag value".
"""
serializer_class
=
serializers
.
ProblemsAndTagsSerializer
allow_empty
=
False
model
=
models
.
ProblemsAndTags
def
get_queryset
(
self
):
queryset
=
self
.
model
.
objects
.
filter
(
course_id
=
self
.
course_id
)
items
=
queryset
.
all
()
result
=
{}
for
v
in
items
:
if
v
.
module_id
in
result
:
result
[
v
.
module_id
][
'tags'
][
v
.
tag_name
]
=
v
.
tag_value
if
result
[
v
.
module_id
][
'created'
]
<
v
.
created
:
result
[
v
.
module_id
][
'created'
]
=
v
.
created
else
:
result
[
v
.
module_id
]
=
{
'module_id'
:
v
.
module_id
,
'total_submissions'
:
v
.
total_submissions
,
'correct_submissions'
:
v
.
correct_submissions
,
'tags'
:
{
v
.
tag_name
:
v
.
tag_value
},
'created'
:
v
.
created
}
return
result
.
values
()
class
VideosListView
(
BaseCourseView
):
class
VideosListView
(
BaseCourseView
):
"""
"""
Get data for the videos in a course.
Get data for the videos in a 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