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
600a7574
Commit
600a7574
authored
Jun 07, 2016
by
Dennis Jen
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #115 from CredoReference/api-to-get-tags-distribution
New API method to return tags distribution info
parents
a4b39021
c345ca79
Show 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 @
600a7574
...
...
@@ -6,3 +6,4 @@ Ed Zarecor <ed@edx.org>
Gabe Mulley <gabe@edx.org>
Jason Bau <jbau@stanford.edu>
John Jarvis <jarv@edx.org>
Dmitry Viskov <dmitry.viskov@webenterprise.ru>
analytics_data_api/management/commands/generate_fake_course_data.py
View file @
600a7574
...
...
@@ -225,6 +225,34 @@ class Command(BaseCommand):
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
)
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
):
course_id
=
options
[
'course_id'
]
username
=
options
[
'username'
]
...
...
@@ -245,3 +273,4 @@ class Command(BaseCommand):
self
.
generate_video_timeline_data
(
video_id
)
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_tags_distribution_data
(
course_id
)
analytics_data_api/v0/models.py
View file @
600a7574
...
...
@@ -131,6 +131,21 @@ class ProblemResponseAnswerDistribution(BaseProblemResponseAnswerDistribution):
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
):
""" Updated model for answer_distribution table with counts of first and last attempts at problems. """
...
...
analytics_data_api/v0/serializers.py
View file @
600a7574
...
...
@@ -57,6 +57,18 @@ class ProblemSerializer(serializers.Serializer):
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
):
"""
Representation of the Answer Distribution table, without id.
...
...
analytics_data_api/v0/tests/views/test_courses.py
View file @
600a7574
...
...
@@ -650,6 +650,82 @@ class CourseProblemsListViewTests(DemoCourseMixin, TestCaseWithAuthentication):
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
):
def
_get_data
(
self
,
course_id
=
None
):
"""
...
...
analytics_data_api/v0/urls/courses.py
View file @
600a7574
...
...
@@ -13,6 +13,7 @@ COURSE_URLS = [
(
'enrollment/gender'
,
views
.
CourseEnrollmentByGenderView
,
'enrollment_by_gender'
),
(
'enrollment/location'
,
views
.
CourseEnrollmentByLocationView
,
'enrollment_by_location'
),
(
'problems'
,
views
.
ProblemsListView
,
'problems'
),
(
'problems_and_tags'
,
views
.
ProblemsAndTagsListView
,
'problems_and_tags'
),
(
'videos'
,
views
.
VideosListView
,
'videos'
)
]
...
...
analytics_data_api/v0/views/courses.py
View file @
600a7574
...
...
@@ -628,9 +628,9 @@ class ProblemsListView(BaseCourseView):
Returns a collection of submission counts and part IDs for each problem. Each collection contains:
* 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.
* part_ids: List of problem part IDs
* part_ids: List of problem part IDs
.
"""
serializer_class
=
serializers
.
ProblemSerializer
allow_empty
=
False
...
...
@@ -689,6 +689,53 @@ GROUP BY module_id;
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
):
"""
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