Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-analytics-dashboard
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-dashboard
Commits
0ae6802d
Commit
0ae6802d
authored
Mar 04, 2015
by
Clinton Blackburn
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #271 from edx/no-submissions
Returning HTTP 200 if Course is Missing Submissions or Assignments
parents
3a9c8d89
aa51f660
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
64 additions
and
23 deletions
+64
-23
analytics_dashboard/courses/exceptions.py
+15
-0
analytics_dashboard/courses/presenters/performance.py
+14
-2
analytics_dashboard/courses/tests/factories.py
+32
-17
analytics_dashboard/courses/tests/test_presenters.py
+0
-1
analytics_dashboard/courses/tests/test_views/test_performance.py
+3
-2
analytics_dashboard/courses/views/performance.py
+0
-1
No files found.
analytics_dashboard/courses/exceptions.py
View file @
0ae6802d
...
@@ -24,3 +24,18 @@ class PermissionsRetrievalFailedError(PermissionsError):
...
@@ -24,3 +24,18 @@ class PermissionsRetrievalFailedError(PermissionsError):
Raise if permissions retrieval fails (e.g. the backend is unreachable).
Raise if permissions retrieval fails (e.g. the backend is unreachable).
"""
"""
pass
pass
class
NoAnswerSubmissionsError
(
Exception
):
"""
Raise if the course has no answer submissions.
"""
course_id
=
None
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
course_id
=
kwargs
.
pop
(
'course_id'
)
super
(
NoAnswerSubmissionsError
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
message
=
'No answers have been submitted for course {}.'
.
format
(
self
.
course_id
)
def
__str__
(
self
):
return
self
.
message
analytics_dashboard/courses/presenters/performance.py
View file @
0ae6802d
...
@@ -11,6 +11,7 @@ import slumber
...
@@ -11,6 +11,7 @@ import slumber
import
common
import
common
from
courses
import
utils
from
courses
import
utils
from
courses.exceptions
import
NoAnswerSubmissionsError
from
courses.presenters
import
BasePresenter
from
courses.presenters
import
BasePresenter
from
core.utils
import
sanitize_cache_key
from
core.utils
import
sanitize_cache_key
...
@@ -212,7 +213,11 @@ class CoursePerformancePresenter(BasePresenter):
...
@@ -212,7 +213,11 @@ class CoursePerformancePresenter(BasePresenter):
if
not
problems
:
if
not
problems
:
# Get the problems from the API
# Get the problems from the API
logger
.
debug
(
'Retrieving problem submissions for course:
%
s'
,
self
.
course_id
)
logger
.
debug
(
'Retrieving problem submissions for course:
%
s'
,
self
.
course_id
)
problems
=
self
.
client
.
courses
(
self
.
course_id
)
.
problems
()
try
:
problems
=
self
.
client
.
courses
(
self
.
course_id
)
.
problems
()
except
NotFoundError
:
raise
NoAnswerSubmissionsError
(
course_id
=
self
.
course_id
)
# Create a lookup table so that submission data can be quickly retrieved by downstream consumers.
# Create a lookup table so that submission data can be quickly retrieved by downstream consumers.
table
=
{}
table
=
{}
...
@@ -258,17 +263,23 @@ class CoursePerformancePresenter(BasePresenter):
...
@@ -258,17 +263,23 @@ class CoursePerformancePresenter(BasePresenter):
'part_ids'
:
[]
'part_ids'
:
[]
}
}
course_problems
=
self
.
_course_problems
()
try
:
course_problems
=
self
.
_course_problems
()
except
NoAnswerSubmissionsError
as
e
:
logger
.
warning
(
e
)
course_problems
=
{}
for
assignment
in
assignments
:
for
assignment
in
assignments
:
problems
=
assignment
[
'problems'
]
problems
=
assignment
[
'problems'
]
for
index
,
problem
in
enumerate
(
problems
):
for
index
,
problem
in
enumerate
(
problems
):
data
=
course_problems
.
get
(
problem
[
'id'
],
DEFAULT_DATA
)
data
=
course_problems
.
get
(
problem
[
'id'
],
DEFAULT_DATA
)
# map empty names to None so that the UI catches them and displays as '(empty)'
# map empty names to None so that the UI catches them and displays as '(empty)'
if
len
(
problem
[
'name'
])
<
1
:
if
len
(
problem
[
'name'
])
<
1
:
problem
[
'name'
]
=
None
problem
[
'name'
]
=
None
data
[
'index'
]
=
index
+
1
data
[
'index'
]
=
index
+
1
# not all problems have submissions
# not all problems have submissions
if
len
(
data
[
'part_ids'
])
>
0
:
if
len
(
data
[
'part_ids'
])
>
0
:
utils
.
sorting
.
natural_sort
(
data
[
'part_ids'
])
utils
.
sorting
.
natural_sort
(
data
[
'part_ids'
])
...
@@ -317,6 +328,7 @@ class CoursePerformancePresenter(BasePresenter):
...
@@ -317,6 +328,7 @@ class CoursePerformancePresenter(BasePresenter):
assignment
[
'assignment_type'
]
.
lower
()
==
assignment_type
]
assignment
[
'assignment_type'
]
.
lower
()
==
assignment_type
]
self
.
_add_submissions_and_part_ids
(
assignments
)
self
.
_add_submissions_and_part_ids
(
assignments
)
for
index
,
assignment
in
enumerate
(
assignments
):
for
index
,
assignment
in
enumerate
(
assignments
):
problems
=
assignment
[
'problems'
]
problems
=
assignment
[
'problems'
]
total_submissions
=
sum
(
problem
.
get
(
'total_submissions'
,
0
)
for
problem
in
problems
)
total_submissions
=
sum
(
problem
.
get
(
'total_submissions'
,
0
)
for
problem
in
problems
)
...
...
analytics_dashboard/courses/tests/factories.py
View file @
0ae6802d
...
@@ -80,7 +80,7 @@ class CoursePerformanceDataFactory(object):
...
@@ -80,7 +80,7 @@ class CoursePerformanceDataFactory(object):
self
.
_assignments
.
append
(
assignment
)
self
.
_assignments
.
append
(
assignment
)
def
present_assignments
(
self
):
def
present_assignments
(
self
,
include_submissions
=
True
):
presented
=
[]
presented
=
[]
for
assignment_index
,
assignment
in
enumerate
(
self
.
_assignments
):
for
assignment_index
,
assignment
in
enumerate
(
self
.
_assignments
):
...
@@ -95,22 +95,36 @@ class CoursePerformanceDataFactory(object):
...
@@ -95,22 +95,36 @@ class CoursePerformanceDataFactory(object):
correct_percent
=
0
correct_percent
=
0
url_template
=
'/courses/{}/performance/graded_content/assignments/{}/problems/'
\
url_template
=
'/courses/{}/performance/graded_content/assignments/{}/problems/'
\
'{}/parts/{}/answer_distribution/'
'{}/parts/{}/answer_distribution/'
problems
.
append
({
problem
=
{
'index'
:
problem_index
+
1
,
'index'
:
problem_index
+
1
,
'total_submissions'
:
problem_index
,
'correct_submissions'
:
problem_index
,
'correct_percent'
:
correct_percent
,
'incorrect_submissions'
:
0.0
,
'incorrect_percent'
:
0
,
'id'
:
_id
,
'id'
:
_id
,
'name'
:
block
[
'display_name'
],
'name'
:
block
[
'display_name'
],
'part_ids'
:
[
part_id
],
'total_submissions'
:
0
,
'url'
:
urllib
.
quote
(
url_template
.
format
(
'correct_submissions'
:
0
,
CoursePerformanceDataFactory
.
course_id
,
assignment
[
'id'
],
_id
,
part_id
))
'correct_percent'
:
0
,
})
'incorrect_submissions'
:
0
,
'incorrect_percent'
:
0
,
'part_ids'
:
[]
}
if
include_submissions
:
problem
.
update
({
'part_ids'
:
[
part_id
],
'total_submissions'
:
problem_index
,
'correct_submissions'
:
problem_index
,
'correct_percent'
:
correct_percent
,
'incorrect_submissions'
:
0
,
'incorrect_percent'
:
0
,
'url'
:
urllib
.
quote
(
url_template
.
format
(
self
.
course_id
,
assignment
[
'id'
],
_id
,
part_id
)),
})
problems
.
append
(
problem
)
num_problems
=
len
(
problems
)
num_problems
=
len
(
problems
)
url_template
=
'/courses/{}/performance/graded_content/assignments/{}/'
url_template
=
'/courses/{}/performance/graded_content/assignments/{}/'
total_submissions
=
sum
([
problem
[
'total_submissions'
]
for
problem
in
problems
])
correct_submissions
=
sum
([
problem
[
'correct_submissions'
]
for
problem
in
problems
])
presented_assignment
=
{
presented_assignment
=
{
'index'
:
assignment_index
+
1
,
'index'
:
assignment_index
+
1
,
'id'
:
assignment
[
'id'
],
'id'
:
assignment
[
'id'
],
...
@@ -118,15 +132,16 @@ class CoursePerformanceDataFactory(object):
...
@@ -118,15 +132,16 @@ class CoursePerformanceDataFactory(object):
'assignment_type'
:
assignment
[
'format'
],
'assignment_type'
:
assignment
[
'format'
],
'problems'
:
problems
,
'problems'
:
problems
,
'num_problems'
:
num_problems
,
'num_problems'
:
num_problems
,
'total_submissions'
:
num_problem
s
,
'total_submissions'
:
total_submission
s
,
'correct_submissions'
:
num_problem
s
,
'correct_submissions'
:
correct_submission
s
,
'correct_percent'
:
1.0
,
'correct_percent'
:
0.0
if
not
total_submissions
else
correct_submissions
/
total_submissions
,
'incorrect_submissions'
:
0
,
'incorrect_submissions'
:
0
,
'incorrect_percent'
:
0.0
,
'incorrect_percent'
:
0.0
'url'
:
urllib
.
quote
(
url_template
.
format
(
CoursePerformanceDataFactory
.
course_id
,
assignment
[
'id'
]))
}
}
if
total_submissions
>
0
:
presented_assignment
[
'url'
]
=
urllib
.
quote
(
url_template
.
format
(
self
.
course_id
,
assignment
[
'id'
]))
presented
.
append
(
presented_assignment
)
presented
.
append
(
presented_assignment
)
return
presented
return
presented
...
...
analytics_dashboard/courses/tests/test_presenters.py
View file @
0ae6802d
...
@@ -365,7 +365,6 @@ class CoursePerformancePresenterTests(TestCase):
...
@@ -365,7 +365,6 @@ class CoursePerformancePresenterTests(TestCase):
self
.
assertEqual
(
self
.
presenter
.
last_updated
,
utils
.
CREATED_DATETIME
)
self
.
assertEqual
(
self
.
presenter
.
last_updated
,
utils
.
CREATED_DATETIME
)
# With an assignment type set, the presenter should return only the assignments of the specified type.
# With an assignment type set, the presenter should return only the assignments of the specified type.
self
.
maxDiff
=
None
for
assignment_type
in
self
.
factory
.
assignment_types
:
for
assignment_type
in
self
.
factory
.
assignment_types
:
cache
.
clear
()
cache
.
clear
()
expected
=
[
assignment
for
assignment
in
expected_assignments
if
expected
=
[
assignment
for
assignment
in
expected_assignments
if
...
...
analytics_dashboard/courses/tests/test_views/test_performance.py
View file @
0ae6802d
...
@@ -299,7 +299,8 @@ class CoursePerformanceGradedContentByTypeViewTests(CoursePerformanceViewTestMix
...
@@ -299,7 +299,8 @@ class CoursePerformanceGradedContentByTypeViewTests(CoursePerformanceViewTestMix
@patch
(
'courses.presenters.performance.CoursePerformancePresenter.assignments'
,
Mock
(
return_value
=
[]))
@patch
(
'courses.presenters.performance.CoursePerformancePresenter.assignments'
,
Mock
(
return_value
=
[]))
def
test_missing_assignments
(
self
):
def
test_missing_assignments
(
self
):
"""
"""
The view should return HTTP 404 if there are no assignments.
The view should return HTTP 200 if there are no assignments. The template will adjust to inform the user
of the issue.
Assignments might be missing if the assignment type is invalid or the course is incomplete.
Assignments might be missing if the assignment type is invalid or the course is incomplete.
"""
"""
...
@@ -310,7 +311,7 @@ class CoursePerformanceGradedContentByTypeViewTests(CoursePerformanceViewTestMix
...
@@ -310,7 +311,7 @@ class CoursePerformanceGradedContentByTypeViewTests(CoursePerformanceViewTestMix
self
.
mock_course_detail
(
course_id
)
self
.
mock_course_detail
(
course_id
)
response
=
self
.
client
.
get
(
self
.
path
(
course_id
=
course_id
,
assignment_type
=
'Invalid'
))
response
=
self
.
client
.
get
(
self
.
path
(
course_id
=
course_id
,
assignment_type
=
'Invalid'
))
self
.
assertEqual
(
response
.
status_code
,
404
)
self
.
assertEqual
(
response
.
status_code
,
200
)
class
CoursePerformanceAssignmentViewTests
(
CoursePerformanceViewTestMixin
,
TestCase
):
class
CoursePerformanceAssignmentViewTests
(
CoursePerformanceViewTestMixin
,
TestCase
):
...
...
analytics_dashboard/courses/views/performance.py
View file @
0ae6802d
...
@@ -169,7 +169,6 @@ class PerformanceGradedContentByType(PerformanceTemplateView):
...
@@ -169,7 +169,6 @@ class PerformanceGradedContentByType(PerformanceTemplateView):
# If there are no assignments, either the course is incomplete or the assignment type is invalid.
# If there are no assignments, either the course is incomplete or the assignment type is invalid.
# It is more likely that the assignment type is invalid, so return a 404.
# It is more likely that the assignment type is invalid, so return a 404.
logger
.
info
(
'No assignments of type
%
s were found for course
%
s'
,
assignment_type
,
self
.
course_id
)
logger
.
info
(
'No assignments of type
%
s were found for course
%
s'
,
assignment_type
,
self
.
course_id
)
raise
Http404
context
.
update
({
context
.
update
({
'page_data'
:
self
.
get_page_data
(
context
),
'page_data'
:
self
.
get_page_data
(
context
),
...
...
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