Source code for inginious.frontend.pages.api.submissions

# -*- coding: utf-8 -*-
#
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
# more information about the licensing of this file.

""" Submissions """

import base64
import gettext
import flask

from inginious.frontend.pages.api._api_page import APIAuthenticatedPage, APINotFound, APIForbidden, APIInvalidArguments, APIError


def _get_submissions(taskset_factory, submission_manager, user_manager, translations, courseid, taskid, with_input, submissionid=None):
    """
        Helper for the GET methods of the two following classes
    """

    try:
        course = taskset_factory.get_course(courseid)
    except:
        raise APINotFound("Course not found")

    if not user_manager.course_is_open_to_user(course, lti=False):
        raise APIForbidden("You are not registered to this course")

    try:
        task = course.get_task(taskid)
    except:
        raise APINotFound("Task not found")

    if submissionid is None:
        submissions = submission_manager.get_user_submissions(course, task)
    else:
        try:
            submissions = [submission_manager.get_submission(submissionid)]
        except:
            raise APINotFound("Submission not found")
        if submissions[0]["taskid"] != task.get_id() or submissions[0]["courseid"] != course.get_id():
            raise APINotFound("Submission not found")

    output = []

    for submission in submissions:
        submission = submission_manager.get_feedback_from_submission(
            submission,
            show_everything=user_manager.has_admin_rights_on_course(course, user_manager.session_username()),
            translation=translations.get(user_manager.session_language(), gettext.NullTranslations())
        )
        data = {
            "id": str(submission["_id"]),
            "submitted_on": str(submission["submitted_on"]),
            "status": submission["status"]
        }

        if with_input:
            data["input"] = submission_manager.get_input_from_submission(submission, True)

            # base64 encode file to allow JSON encoding
            for d in data["input"]:
                if isinstance(d, dict) and d.keys() == {"filename", "value"}:
                    d["value"] = base64.b64encode(d["value"]).decode("utf8")

        if submission["status"] == "done":
            data["grade"] = submission.get("grade", 0)
            data["result"] = submission.get("result", "crash")
            data["feedback"] = submission.get("text", "")
            data["problems_feedback"] = submission.get("problems", {})

        output.append(data)

    return 200, output


[docs]class APISubmissionSingle(APIAuthenticatedPage): r""" Endpoint :: /api/v0/courses/[a-zA-Z_\-\.0-9]+/tasks/[a-zA-Z_\-\.0-9]+/submissions/[a-zA-Z_\-\.0-9]+ """
[docs] def API_GET(self, courseid, taskid, submissionid): # pylint: disable=arguments-differ """ List all the submissions that the connected user made. Returns list of the form :: [ { "id": "submission_id1", "submitted_on": "date", "status" : "done", #can be "done", "waiting", "error" (execution status of the task). "grade": 0.0, "input": {}, #the input data. File are base64 encoded. "result" : "success" #only if status=done. Result of the execution. "feedback": "" #only if status=done. the HTML global feedback for the task "problems_feedback": #only if status=done. HTML feedback per problem. Some pid may be absent. { "pid1": "feedback1", #... } } #... ] If you use the endpoint /api/v0/courses/the_course_id/tasks/the_task_id/submissions/submissionid, this dict will contain one entry or the page will return 404 Not Found. """ with_input = "input" in flask.request.args return _get_submissions(self.taskset_factory, self.submission_manager, self.user_manager, self.app.l10n_manager.translations, courseid, taskid, with_input, submissionid)
[docs]class APISubmissions(APIAuthenticatedPage): r""" Endpoint :: /api/v0/courses/[a-zA-Z_\-\.0-9]+/tasks/[a-zA-Z_\-\.0-9]+/submissions """
[docs] def API_GET(self, courseid, taskid): # pylint: disable=arguments-differ """ List all the submissions that the connected user made. Returns dicts in the form :: [ { "id": "submission_id1", "submitted_on": "date", "status" : "done", #can be "done", "waiting", "error" (execution status of the task). "grade": 0.0, "input": {}, #the input data. File are base64 encoded. "result" : "success" #only if status=done. Result of the execution. "feedback": "" #only if status=done. the HTML global feedback for the task "problems_feedback": #only if status=done. HTML feedback per problem. Some pid may be absent. { "pid1": "feedback1", #... } } #... ] If you use the endpoint /api/v0/courses/the_course_id/tasks/the_task_id/submissions/submissionid, this dict will contain one entry or the page will return 404 Not Found. """ with_input = "input" in flask.request.args return _get_submissions(self.taskset_factory, self.submission_manager, self.user_manager, self.app.l10n_manager.translations, courseid, taskid, with_input)
[docs] def API_POST(self, courseid, taskid): # pylint: disable=arguments-differ """ Creates a new submissions. Takes as (POST) input the key of the subproblems, with the value assigned each time. Returns - an error 400 Bad Request if all the input is not (correctly) given, - an error 403 Forbidden if you are not allowed to create a new submission for this task - an error 404 Not found if the course/task id not found - an error 500 Internal server error if the grader is not available, - 200 Ok, with {"submissionid": "the submission id"} as output. """ try: course = self.taskset_factory.get_course(courseid) except: raise APINotFound("Course not found") username = self.user_manager.session_username() if not self.user_manager.course_is_open_to_user(course, username, False): raise APIForbidden("You are not registered to this course") try: task = course.get_task(taskid) except: raise APINotFound("Task not found") self.user_manager.user_saw_task(username, courseid, taskid) # Verify rights if not self.user_manager.task_can_user_submit(course, task, username, False): raise APIForbidden("You are not allowed to submit for this task") user_input = flask.request.form.copy() for problem in task.get_problems(): pid = problem.get_id() if problem.input_type() == list: user_input[pid] = flask.request.form.getlist(pid) elif problem.input_type() == dict: user_input[pid] = flask.request.files.get(pid) else: user_input[pid] = flask.request.form.get(pid) user_input = task.adapt_input_for_backend(user_input) if not task.input_is_consistent(user_input, self.default_allowed_file_extensions, self.default_max_file_size): raise APIInvalidArguments() # Get debug info if the current user is an admin debug = self.user_manager.has_admin_rights_on_course(course, username) # Start the submission try: submissionid, _ = self.submission_manager.add_job(course, task, user_input, course.get_task_dispenser(), debug) return 200, {"submissionid": str(submissionid)} except Exception as ex: raise APIError(500, str(ex))