Commit aa51f660 by Clinton Blackburn Committed by Clinton Blackburn

Returning HTTP 200 if Course is Missing Submissions or Assignments

The templates will adjust accordingly to inform the user of the problem.
parent cd261944
...@@ -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
...@@ -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)
......
...@@ -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_problems, 'total_submissions': total_submissions,
'correct_submissions': num_problems, 'correct_submissions': correct_submissions,
'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
......
...@@ -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
......
...@@ -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):
......
...@@ -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),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment