Commit ee08c5af by Eric Fischer

Merge pull request #41 from edx/diana/all-submissions

Create new get_all_course_submission_information endpoint.
parents 48dc2630 a5e721e4
...@@ -10,7 +10,7 @@ import json ...@@ -10,7 +10,7 @@ import json
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from django.db import IntegrityError, DatabaseError, transaction from django.db import IntegrityError, DatabaseError
from dogapi import dog_stats_api from dogapi import dog_stats_api
from submissions.serializers import ( from submissions.serializers import (
...@@ -220,7 +220,7 @@ def get_submission(submission_uuid, read_replica=False): ...@@ -220,7 +220,7 @@ def get_submission(submission_uuid, read_replica=False):
cache_key = Submission.get_cache_key(submission_uuid) cache_key = Submission.get_cache_key(submission_uuid)
try: try:
cached_submission_data = cache.get(cache_key) cached_submission_data = cache.get(cache_key)
except Exception as ex: except Exception:
# The cache backend could raise an exception # The cache backend could raise an exception
# (for example, memcache keys that contain spaces) # (for example, memcache keys that contain spaces)
logger.exception("Error occurred while retrieving submission from the cache") logger.exception("Error occurred while retrieving submission from the cache")
...@@ -413,6 +413,65 @@ def get_all_submissions(course_id, item_id, item_type, read_replica=True): ...@@ -413,6 +413,65 @@ def get_all_submissions(course_id, item_id, item_type, read_replica=True):
yield data yield data
def get_all_course_submission_information(course_id, item_type, read_replica=True):
""" For the given course, get all student items of the given item type, all the submissions for those itemes,
and the latest scores for each item. If a submission was given a score that is not the latest score for the
relevant student item, it will still be included but without score.
Args:
course_id (str): The course that we are getting submissions from.
item_type (str): The type of items that we are getting submissions for.
read_replica (bool): Try to use the database's read replica if it's available.
Yields:
A tuple of three dictionaries representing:
(1) a student item with the following fields:
student_id
course_id
student_item
item_type
(2) a submission with the following fields:
student_item
attempt_number
submitted_at
created_at
answer
(3) a score with the following fields, if one exists and it is the latest score:
(if both conditions are not met, an empty dict is returned here)
student_item
submission
points_earned
points_possible
created_at
submission_uuid
"""
submission_qs = Submission.objects
if read_replica:
submission_qs = _use_read_replica(submission_qs)
query = submission_qs.select_related('student_item__scoresummary__latest__submission').filter(
student_item__course_id=course_id,
student_item__item_type=item_type,
).iterator()
for submission in query:
student_item = submission.student_item
serialized_score = {}
if hasattr(student_item, 'scoresummary'):
latest_score = student_item.scoresummary.latest
# Only include the score if it is not a reset score (is_hidden), and if the current submission is the same
# as the student_item's latest score's submission. This matches the behavior of the API's get_score method.
if (not latest_score.is_hidden()) and latest_score.submission.uuid == submission.uuid:
serialized_score = ScoreSerializer(latest_score).data
yield (
StudentItemSerializer(student_item).data,
SubmissionSerializer(submission).data,
serialized_score
)
def get_top_submissions(course_id, item_id, item_type, number_of_top_scores, use_cache=True, read_replica=True): def get_top_submissions(course_id, item_id, item_type, number_of_top_scores, use_cache=True, read_replica=True):
"""Get a number of top scores for an assessment based on a particular student item """Get a number of top scores for an assessment based on a particular student item
...@@ -755,7 +814,7 @@ def set_score(submission_uuid, points_earned, points_possible, ...@@ -755,7 +814,7 @@ def set_score(submission_uuid, points_earned, points_possible,
u"No submission matching uuid {}".format(submission_uuid) u"No submission matching uuid {}".format(submission_uuid)
) )
except DatabaseError: except DatabaseError:
error_msg = u"Could not retrieve student item: {} or submission {}.".format( error_msg = u"Could not retrieve submission {}.".format(
submission_uuid submission_uuid
) )
logger.exception(error_msg) logger.exception(error_msg)
......
...@@ -101,6 +101,51 @@ class TestSubmissionsApi(TestCase): ...@@ -101,6 +101,51 @@ class TestSubmissionsApi(TestCase):
self._assert_submission(submissions[1], ANSWER_TWO, student_item.pk, 2) self._assert_submission(submissions[1], ANSWER_TWO, student_item.pk, 2)
self.assertEqual(submissions[1]['student_id'], STUDENT_ITEM['student_id']) self.assertEqual(submissions[1]['student_id'], STUDENT_ITEM['student_id'])
@ddt.data(True, False)
def test_get_course_submissions(self, set_scores):
submission1 = api.create_submission(STUDENT_ITEM, ANSWER_ONE)
submission2 = api.create_submission(STUDENT_ITEM, ANSWER_TWO)
submission3 = api.create_submission(SECOND_STUDENT_ITEM, ANSWER_ONE)
submission4 = api.create_submission(SECOND_STUDENT_ITEM, ANSWER_TWO)
if set_scores:
api.set_score(submission1['uuid'], 1, 4)
api.set_score(submission2['uuid'], 2, 4)
api.set_score(submission3['uuid'], 3, 4)
api.set_score(submission4['uuid'], 4, 4)
submissions_and_scores = list(api.get_all_course_submission_information(
STUDENT_ITEM['course_id'],
STUDENT_ITEM['item_type'],
read_replica=False,
))
student_item1 = self._get_student_item(STUDENT_ITEM)
student_item2 = self._get_student_item(SECOND_STUDENT_ITEM)
self.assertDictEqual(SECOND_STUDENT_ITEM, submissions_and_scores[0][0])
self._assert_submission(submissions_and_scores[0][1], submission4['answer'], student_item2.pk, 2)
self.assertDictEqual(SECOND_STUDENT_ITEM, submissions_and_scores[1][0])
self._assert_submission(submissions_and_scores[1][1], submission3['answer'], student_item2.pk, 1)
self.assertDictEqual(STUDENT_ITEM, submissions_and_scores[2][0])
self._assert_submission(submissions_and_scores[2][1], submission2['answer'], student_item1.pk, 2)
self.assertDictEqual(STUDENT_ITEM, submissions_and_scores[3][0])
self._assert_submission(submissions_and_scores[3][1], submission1['answer'], student_item1.pk, 1)
# These scores will always be empty
self.assertEqual(submissions_and_scores[1][2], {})
self.assertEqual(submissions_and_scores[3][2], {})
if set_scores:
self._assert_score(submissions_and_scores[0][2], 4, 4)
self._assert_score(submissions_and_scores[2][2], 2, 4)
else:
self.assertEqual(submissions_and_scores[0][2], {})
self.assertEqual(submissions_and_scores[2][2], {})
def test_get_submission(self): def test_get_submission(self):
# Test base case that we can create a submission and get it back # Test base case that we can create a submission and get it back
sub_dict1 = api.create_submission(STUDENT_ITEM, ANSWER_ONE) sub_dict1 = api.create_submission(STUDENT_ITEM, ANSWER_ONE)
......
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