Commit d76cbd32 by Will Daly

Merge pull request #5 from UQ-UQx/master

Added get_top_submissions to submissions API
parents ce5fd2c7 c724f314
...@@ -18,6 +18,7 @@ from submissions.models import Submission, StudentItem, Score, ScoreSummary ...@@ -18,6 +18,7 @@ from submissions.models import Submission, StudentItem, Score, ScoreSummary
logger = logging.getLogger("submissions.api") logger = logging.getLogger("submissions.api")
MAX_TOP_SUBMISSIONS = 100
class SubmissionError(Exception): class SubmissionError(Exception):
"""An error that occurs during submission actions. """An error that occurs during submission actions.
...@@ -346,6 +347,71 @@ def get_submissions(student_item_dict, limit=None): ...@@ -346,6 +347,71 @@ def get_submissions(student_item_dict, limit=None):
return SubmissionSerializer(submission_models, many=True).data return SubmissionSerializer(submission_models, many=True).data
def get_top_submissions(course_id, item_id, item_type, number_of_top_scores):
"""Get a number of top scores for an assessment based on a particular student item
This function will return top scores for the piece of assessment.
A score is only calculated for a student item if it has completed the workflow for
a particular assessment module.
Args:
course_id (str): The course to retrieve for the top scores
item_id (str): The item within the course to retrieve for the top scores
item_type (str): The type of item to retrieve
number_of_top_scores (int): The number of scores to return, greater than 0 and no
more than 100.
Returns:
topscores (dict): The top scores for the assessment for the student item.
An empty array if there are no scores.
Raises:
SubmissionNotFoundError: Raised when a submission cannot be found for
the associated student item.
SubmissionRequestError: Raised when the number of top scores is higher than the
MAX_TOP_SUBMISSIONS constant.
Examples:
>>> course_id = "TestCourse"
>>> item_id = "u_67"
>>> item_type = "openassessment"
>>> number_of_top_scores = 10
>>>
>>> get_top_submissions(course_id, item_id, item_type, number_of_top_scores)
[{
'score': 20,
'content': "Platypus"
},{
'score': 16,
'content': "Frog"
}]
"""
if number_of_top_scores < 1 or number_of_top_scores > MAX_TOP_SUBMISSIONS:
error_msg = (
u"Number of top scores must be a number between 1 and {}.".format(MAX_TOP_SUBMISSIONS)
)
logger.exception(error_msg)
raise SubmissionRequestError(error_msg)
try:
scores = Score.objects.filter(
student_item__course_id=course_id,
student_item__item_id=item_id,
student_item__item_type=item_type,
).select_related("submission").order_by("-points_earned")[:number_of_top_scores]
except DatabaseError:
msg = u"Could not fetch top scores for course {}, item {} of type {}".format(
course_id, item_id, item_type
)
logger.exception(msg)
raise SubmissionInternalError(msg)
topsubmissions = []
for score in scores:
answer = SubmissionSerializer(score.submission).data['answer']
topsubmissions.append({'score': score.points_earned, 'content': answer})
return topsubmissions
def get_score(student_item): def get_score(student_item):
"""Get the score for a particular student item """Get the score for a particular student item
...@@ -729,4 +795,4 @@ def _use_read_replica(queryset): ...@@ -729,4 +795,4 @@ def _use_read_replica(queryset):
queryset.using("read_replica") queryset.using("read_replica")
if "read_replica" in settings.DATABASES if "read_replica" in settings.DATABASES
else queryset else queryset
) )
\ No newline at end of file
...@@ -10,7 +10,7 @@ from mock import patch ...@@ -10,7 +10,7 @@ from mock import patch
import pytz import pytz
from submissions import api as api from submissions import api as api
from submissions.models import ScoreSummary, Submission, StudentItem from submissions.models import ScoreSummary, Submission, StudentItem, Score
from submissions.serializers import StudentItemSerializer from submissions.serializers import StudentItemSerializer
STUDENT_ITEM = dict( STUDENT_ITEM = dict(
...@@ -282,6 +282,108 @@ class TestSubmissionsApi(TestCase): ...@@ -282,6 +282,108 @@ class TestSubmissionsApi(TestCase):
} }
) )
def test_get_top_submissions(self):
student_item = copy.deepcopy(STUDENT_ITEM)
student_item["course_id"] = "get_scores_course"
student_item["item_id"] = "i4x://a/b/c/s1"
student_1 = api.create_submission(student_item, "Hello World")
student_2 = api.create_submission(student_item, "Hello World")
student_3 = api.create_submission(student_item, "Hello World")
api.set_score(student_1['uuid'], 8, 10)
api.set_score(student_2['uuid'], 4, 10)
api.set_score(student_3['uuid'], 2, 10)
#Get top scores works correctly
with self.assertNumQueries(1):
top_scores = api.get_top_submissions(student_item["course_id"], student_item["item_id"], "Peer_Submission", 3)
self.assertEqual(
top_scores,
[
{
'content': "Hello World",
'score': 8
},
{
'content': "Hello World",
'score': 4
},
{
'content': "Hello World",
'score': 2
},
]
)
#Fewer top scores available than the number requested.
top_scores = api.get_top_submissions(student_item["course_id"], student_item["item_id"], "Peer_Submission", 10)
self.assertEqual(
top_scores,
[
{
'content': "Hello World",
'score': 8
},
{
'content': "Hello World",
'score': 4
},
{
'content': "Hello World",
'score': 2
},
]
)
#More top scores available than the number requested.
top_scores = api.get_top_submissions(student_item["course_id"], student_item["item_id"], "Peer_Submission", 2)
self.assertEqual(
top_scores,
[
{
'content': "Hello World",
'score': 8
},
{
'content': "Hello World",
'score': 4
}
]
)
@raises(api.SubmissionRequestError)
def test_error_on_get_top_submissions_too_few(self):
student_item = copy.deepcopy(STUDENT_ITEM)
student_item["course_id"] = "get_scores_course"
student_item["item_id"] = "i4x://a/b/c/s1"
api.get_top_submissions(student_item["course_id"], student_item["item_id"], "Peer_Submission", -1)
@raises(api.SubmissionRequestError)
def test_error_on_get_top_submissions_too_many(self):
student_item = copy.deepcopy(STUDENT_ITEM)
student_item["course_id"] = "get_scores_course"
student_item["item_id"] = "i4x://a/b/c/s1"
api.get_top_submissions(student_item["course_id"], student_item["item_id"], "Peer_Submission", api.MAX_TOP_SUBMISSIONS+1)
@patch.object(Score.objects, 'filter')
@raises(api.SubmissionInternalError)
def test_error_on_get_top_submissions_db_error(self, mock_filter):
mock_filter.side_effect = DatabaseError("Bad things happened")
student_item = copy.deepcopy(STUDENT_ITEM)
api.get_top_submissions(student_item["course_id"], student_item["item_id"], "Peer_Submission", 1)
@patch.object(ScoreSummary.objects, 'filter') @patch.object(ScoreSummary.objects, 'filter')
@raises(api.SubmissionInternalError) @raises(api.SubmissionInternalError)
def test_error_on_get_scores(self, mock_filter): def test_error_on_get_scores(self, mock_filter):
......
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