submissions.py 5.58 KB
Newer Older
1
from flask_restful import Resource, reqparse
2
from flask_restful.inputs import boolean
Otto Kaaij's avatar
Otto Kaaij committed
3

Otto Kaaij's avatar
Otto Kaaij committed
4
from zesje.api._helpers import _shuffle
5
from ..database import Exam, Submission, Problem
6

Jamy Mahabier's avatar
Jamy Mahabier committed
7

8
def sub_to_data(sub):
9
10
    """Transform a submission into a data structure frontend expects."""
    return {
11
        'id': sub.id,
Jamy Mahabier's avatar
Jamy Mahabier committed
12
        'student': {
13
14
15
16
17
            'id': sub.student.id,
            'firstName': sub.student.first_name,
            'lastName': sub.student.last_name,
            'email': sub.student.email
        } if sub.student else None,
18
        'validated': sub.validated,
Jamy Mahabier's avatar
Jamy Mahabier committed
19
        'problems': [
20
21
22
23
24
25
26
27
28
29
            {
                'id': sol.problem.id,
                'graded_by': {
                    'id': sol.graded_by.id,
                    'name': sol.graded_by.name
                } if sol.graded_by else None,
                'graded_at': sol.graded_at.isoformat() if sol.graded_at else None,
                'feedback': [
                    fb.id for fb in sol.feedback
                ],
30
31
                'remark': sol.remarks if sol.remarks else ""
            } for sol in sub.solutions  # Sorted by sol.problem_id
Otto Kaaij's avatar
Otto Kaaij committed
32
        ]
33
34
35
    }


Otto Kaaij's avatar
Otto Kaaij committed
36
def _find_submission(old_submission, problem_id, shuffle_seed, direction, ungraded):
Otto Kaaij's avatar
Otto Kaaij committed
37
38
39
40
41
    """
    Finds a submission based on the parameters of the function.
    Finds all solutions for the problem, and shuffles them based on the shuffle_seed.
    Then finds the first (ungraded) submission, either in 'next' or 'prev' direction.
    If no such submission exists, returns the old submission.
Otto Kaaij's avatar
Otto Kaaij committed
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

    Parameters
    ----------
    old_submission : submission
        the submission to base next or prev on.
    problem_id : int
        id of the current problem.
    shuffle_seed : int
        the seed to shuffle the submissions on.
    direction : string
        either 'next' or 'prev'
    ungraded : bool
        value indicating whether the found submission should be ungraded.
    Returns
    -------
    A new submission, or the old one if no submission matching the criteria is found.

Otto Kaaij's avatar
Otto Kaaij committed
59
60
    """

Hugo Kerstens's avatar
Hugo Kerstens committed
61
    if (problem := Problem.query.get(problem_id)) is None:
62
63
        return dict(status=404, message='Problem does not exist.'), 404

Otto Kaaij's avatar
Otto Kaaij committed
64
    shuffled_solutions = _shuffle(problem.solutions, shuffle_seed, key_extractor=lambda s: s.submission_id)
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
    old_submission_index = next(i for i, s in enumerate(shuffled_solutions) if s.submission_id == old_submission.id)

    if (old_submission_index == 0 and direction == 'prev') or \
            (old_submission_index == len(shuffled_solutions) - 1 and direction == 'next'):
        return sub_to_data(old_submission)

    if not ungraded:
        offset = 1 if direction == 'next' else -1
        return sub_to_data(shuffled_solutions[old_submission_index + offset].submission)

    # If direction is next, search submissions from the one after the old, up to the end of the list.
    # If direction is previous search from the start to the old, in reverse order.
    solutions_to_search = shuffled_solutions[old_submission_index + 1:] if direction == 'next' \
        else shuffled_solutions[old_submission_index - 1::-1]

    if len(solutions_to_search) == 0:
        return sub_to_data(old_submission)

    # Get the next submission for which the solution to our problem was not graded yet
    submission = next((solution.submission for solution in solutions_to_search if
                       solution.graded_by is None),
                      old_submission)  # Returns the old submission in case no suitable submission was found
    return sub_to_data(submission)


90
class Submissions(Resource):
91
    """Getting a list of submissions"""
92

93
94
    get_parser = reqparse.RequestParser()
    get_parser.add_argument('problem_id', type=int, required=False)
Otto Kaaij's avatar
Otto Kaaij committed
95
    get_parser.add_argument('shuffle_seed', type=int, required=False)
96
97
98
    get_parser.add_argument('ungraded', type=boolean, required=False)
    get_parser.add_argument('direction', type=str, required=False, choices=["next", "prev"])

Otto Kaaij's avatar
Otto Kaaij committed
99
    def get(self, exam_id, submission_id=None):
100
        """get submissions for the given exam
101

Otto Kaaij's avatar
Otto Kaaij committed
102
103
104
        if args.direction is specified, returns a submission based on
        the _find_submissions function.

105
106
107
        Parameters
        ----------
        exam_id : int
108
            The id of the exam for which the submissions must be returned
Otto Kaaij's avatar
Otto Kaaij committed
109
        submission_id : int, optional
110
111
            The id of the submission. This uniquely identifies
            the submission *across all exams*.
112
113
114

        Returns
        -------
Joseph Weston's avatar
Joseph Weston committed
115
        If 'submission_id' not provided provides a single instance of
116
        (otherwise a list of):
117
118
            id: int
            student: Student
119
                Student that completed this submission, null if not assigned.
120
            problems: list of problems
121
        """
122
        args = self.get_parser.parse_args()
Hugo Kerstens's avatar
Hugo Kerstens committed
123
        if (exam := Exam.query.get(exam_id)) is None:
124
125
            return dict(status=404, message='Exam does not exist.'), 404

Otto Kaaij's avatar
Otto Kaaij committed
126
        if submission_id is not None:
Hugo Kerstens's avatar
Hugo Kerstens committed
127
            if (sub := Submission.query.get(submission_id)) is None:
128
                return dict(status=404, message='Submission does not exist.'), 404
129

130
131
132
            if sub.exam != exam:
                return dict(status=400, message='Submission does not belong to this exam.'), 400

133
            if args.direction:
134
                if args.problem_id is None or args.shuffle_seed is None or args.ungraded is None:
135
                    return dict(status=400, message='One of problem_id, grader_id, ungraded, direction not provided')
Otto Kaaij's avatar
Otto Kaaij committed
136
                return _find_submission(sub, args.problem_id, args.shuffle_seed, args.direction, args.ungraded)
137

138
            return sub_to_data(sub)
139

140
        return [sub_to_data(sub) for sub in exam.submissions]