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
logger = logging.getLogger("submissions.api")
MAX_TOP_SUBMISSIONS = 100
class SubmissionError(Exception):
"""An error that occurs during submission actions.
......@@ -346,6 +347,71 @@ def get_submissions(student_item_dict, limit=None):
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):
"""Get the score for a particular student item
......@@ -729,4 +795,4 @@ def _use_read_replica(queryset):
queryset.using("read_replica")
if "read_replica" in settings.DATABASES
else queryset
)
\ No newline at end of file
)
......@@ -10,7 +10,7 @@ from mock import patch
import pytz
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
STUDENT_ITEM = dict(
......@@ -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')
@raises(api.SubmissionInternalError)
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