Exemple #1
0
def test_action_find_by_action_distinguish_actions(db, assignment_tree, user_johaannes):

    # Add a fetched action
    orm_action = Action(
        user_id=user_johaannes.id,
        assignment_id=assignment_tree.id,
        action=AssignmentActions.fetched,
        location="/some/random/path/to/a/file.tzg",
    )
    db.add(orm_action)
    db.commit()

    found_by_pk = Action.find_by_pk(db, 1)

    # Without an action, we just get the last action
    found_recent = Action.find_most_recent_action(db, found_by_pk.assignment_id)
    assert found_recent.action == AssignmentActions.fetched

    # We can get different entries if we define the action
    found_recent_a = Action.find_most_recent_action(db, found_by_pk.assignment_id, AssignmentActions.fetched)
    assert found_recent_a.action == AssignmentActions.fetched

    found_recent_b = Action.find_most_recent_action(db, found_by_pk.assignment_id, AssignmentActions.released)
    assert found_recent_b.action == AssignmentActions.released

    assert found_recent_a.id != found_recent_b.id
Exemple #2
0
def test_feedback_find_all_for_student_again(db, assignment_tree, user_johaannes, user_kaylee):
    notebook = Notebook.find_by_name(db, "Exam 2", assignment_tree.id)
    released = Action.find_most_recent_action(db, assignment_tree.id, AssignmentActions.fetched)
    orm_feedback = Feedback(
        notebook_id=notebook.id,
        instructor_id=user_kaylee.id,
        student_id=user_johaannes.id,
        location=released.location,
        checksum="234567890abcdef1",
        timestamp=released.timestamp,
    )
    db.add(orm_feedback)
    db.commit()

    # Note this this swaps instructor & user, so should *not* be included
    orm_feedback_2 = Feedback(
        notebook_id=notebook.id,
        instructor_id=user_johaannes.id,
        student_id=user_kaylee.id,
        location=released.location,
        checksum="34567890abcdef12",
        timestamp=released.timestamp,
    )
    db.add(orm_feedback_2)
    db.commit()

    feedback = Feedback.find_all_for_student(db, user_johaannes.id, assignment_tree.id)

    assert len(feedback) == 2
Exemple #3
0
def test_action_object_creation_errors(db, course_strange, assignment_tree, user_johaannes):
    role = "instructor"
    release_file = "/some/random/path/to/a/file.tzg"

    orm_subscription = Subscription(user_id=user_johaannes.id, course_id=course_strange.id, role=role)
    db.add(orm_subscription)
    db.commit()

    action = Action(
        user_id=user_johaannes.id,
        assignment_id=assignment_tree.id,
        location=release_file,
    )
    db.add(action)
    with pytest.raises(IntegrityError):
        db.commit()
    db.rollback()

    # #### Why won't you work in github Actions, you bar steward
    # action = Action(
    #     user_id=user_johaannes.id,
    #     assignment_id=assignment_tree.id,
    #     location="/some/random/path/to/a/file.tzg",
    #     action="foo",
    # )
    # db.add(action)
    # with pytest.raises(Exception):
    #     db.commit()
    # db.rollback()

    orm_action = Action(
        action=AssignmentActions.released,
        location="/some/random/path/to/a/file.tzg",
    )
    # # Why does that work??

    db.add(orm_action)
    db.commit()
    orm_action.user_id = user_johaannes.id
    orm_action.assignment_id = assignment_tree.id
    db.commit()
Exemple #4
0
def test_action_base_mathods_and_find_by_pk(db, assignment_tree, user_johaannes):

    # subscription set up earlier
    release_file = "/some/random/path/to/a/file.tzg"

    with pytest.raises(TypeError):
        found_by_pk = Action.find_by_pk()
    with pytest.raises(TypeError):
        found_by_pk = Action.find_by_pk(db)
    with pytest.raises(ValueError):
        found_by_pk = Action.find_by_pk(db, None)
    with pytest.raises(TypeError):
        found_by_pk = Action.find_by_pk(db, "abc")

    found_by_pk = Action.find_by_pk(db, 1)
    assert found_by_pk.id == 1
    assert found_by_pk.action == AssignmentActions.released
    assert found_by_pk.location == release_file

    # check the relationships
    assert found_by_pk.user.name == user_johaannes.name
    assert found_by_pk.assignment.assignment_code == assignment_tree.assignment_code

    found_by_pk = Action.find_by_pk(db, 11)
    assert found_by_pk is None
Exemple #5
0
def test_action_find_by_action(db):

    # subscription & action set up earlier
    with pytest.raises(TypeError):
        found_by_pk = Action.find_most_recent_action(db, None)
    with pytest.raises(TypeError):
        found_by_pk = Action.find_most_recent_action(db, "abc")
    with pytest.raises(TypeError):
        found_by_pk = Action.find_most_recent_action(db, 1, dict())

    found_by_pk = Action.find_by_pk(db, 1)  # released
    found_recent = Action.find_most_recent_action(db, found_by_pk.assignment_id, found_by_pk.action)
    assert found_recent.action == found_by_pk.action
    found_recent = Action.find_most_recent_action(db, found_by_pk.assignment_id, "released")
    assert found_recent.action == found_by_pk.action
    found_recent = Action.find_most_recent_action(db, found_by_pk.assignment_id, AssignmentActions.released)
    assert found_recent.action == found_by_pk.action
    found_recent = Action.find_most_recent_action(db, found_by_pk.assignment_id, AssignmentActions.feedback_fetched)
    assert found_recent is None
Exemple #6
0
def test_feedback_base_mathods_and_find_by_pk(db, assignment_tree, user_kaylee, user_johaannes):

    # previous subscriptions & notebooks still in the db
    notebook = Notebook.find_by_name(db, "Exam 2", assignment_tree.id)
    released = Action.find_most_recent_action(db, assignment_tree.id, AssignmentActions.released)
    orm_feedback = Feedback(
        notebook_id=notebook.id,
        instructor_id=user_kaylee.id,
        student_id=user_johaannes.id,
        location=released.location,
        checksum="1234567890abcdef",
        timestamp=released.timestamp,
    )
    db.add(orm_feedback)
    db.commit()

    with pytest.raises(TypeError):
        found_by_pk = Feedback.find_by_pk()
    with pytest.raises(TypeError):
        found_by_pk = Feedback.find_by_pk(db)
    with pytest.raises(ValueError):
        found_by_pk = Feedback.find_by_pk(db, None)
    with pytest.raises(TypeError):
        found_by_pk = Feedback.find_by_pk(db, "abc")

    found_by_pk = Feedback.find_by_pk(db, orm_feedback.id)
    assert found_by_pk.id == orm_feedback.id
    assert (
        str(found_by_pk)
        == f"Feedback<Notebook-{found_by_pk.notebook_id}/Student-{found_by_pk.student_id}/{found_by_pk.checksum}>"
    )

    assert found_by_pk.notebook_id == notebook.id
    assert found_by_pk.instructor_id == user_kaylee.id
    assert found_by_pk.student_id == user_johaannes.id

    # relationships
    assert found_by_pk.notebook.name == notebook.name
    assert found_by_pk.instructor.name == user_kaylee.name
    assert found_by_pk.student.name == user_johaannes.name

    found_by_pk = Feedback.find_by_pk(db, orm_feedback.id + 10)
    assert found_by_pk is None
Exemple #7
0
def test_all_the_unicode(db, assignment_a2ovi, user_rur, course_strange):
    # subscribe user to course
    # add assignment to course

    role = "instructor"
    release_file = "/some/random/path/to/a/file.tzg"
    orm_subscription = Subscription(user_id=user_rur.id, course_id=course_strange.id, role=role)
    db.add(orm_subscription)
    assignment_a2ovi.course_id = course_strange.id
    db.commit()

    found_by_pk = Subscription.find_by_pk(db, orm_subscription.id)
    assert found_by_pk.id == orm_subscription.id
    assert orm_subscription.course.course_title == "Damnation Alley"

    # release
    orm_action = Action(
        action=AssignmentActions.released,
        location=release_file,
    )
    db.add(orm_action)
    db.commit()
    orm_action.user_id = user_rur.id
    orm_action.assignment_id = assignment_a2ovi.id
    db.commit()

    # fetch
    orm_action = Action(
        user_id=user_rur.id,
        assignment_id=assignment_a2ovi.id,
        action=AssignmentActions.fetched,
        location=release_file,
    )
    db.add(orm_action)
    db.commit()

    found = Action.find_most_recent_action(db, assignment_a2ovi.id)
    assert found.user.name == user_rur.name
Exemple #8
0
    def get(self):

        [course_id,
         assignment_id] = self.get_params(["course_id", "assignment_id"])

        if not assignment_id or not course_id:
            note = "Feedback call requires an assignment id and a course id"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        self.log.debug(
            f"checking for feedback for {assignment_id} on {course_id}")

        this_user = self.nbex_user

        with scoped_session() as session:

            course = Course.find_by_code(db=session,
                                         code=course_id,
                                         org_id=this_user["org_id"],
                                         log=self.log)
            if not course:
                note = f"Course {course_id} not found"
                self.log.info(note)
                # self.finish({"success": False, "note": note, "value": []})
                # return
                raise web.HTTPError(404, note)

            assignment = AssignmentModel.find_by_code(db=session,
                                                      code=assignment_id,
                                                      course_id=course.id,
                                                      log=self.log)
            if not assignment:
                note = f"Assignment {assignment_id} for Course {course_id} not found"
                self.log.info(note)
                # self.finish({"success": False, "note": note, "value": []})
                # return
                raise web.HTTPError(404, note)

            student = User.find_by_name(db=session,
                                        name=this_user["name"],
                                        log=self.log)

            res = Feedback.find_all_for_student(
                db=session,
                student_id=student.id,
                assignment_id=assignment.id,
                log=self.log,
            )
            feedbacks = []
            for r in res:
                f = {}
                notebook = Notebook.find_by_pk(db=session,
                                               pk=r.notebook_id,
                                               log=self.log)
                if notebook is not None:
                    feedback_name = "{0}.html".format(notebook.name)
                else:
                    feedback_name = os.path.basename(r.location)
                with open(r.location, "r+b") as fp:
                    f["content"] = base64.b64encode(fp.read()).decode("utf-8")
                f["filename"] = feedback_name
                # This matches self.timestamp_format
                f["timestamp"] = r.timestamp.strftime(
                    "%Y-%m-%d %H:%M:%S.%f %Z")
                f["checksum"] = r.checksum
                feedbacks.append(f)

                # Add action
                action = Action(
                    user_id=this_user["id"],
                    assignment_id=assignment.id,
                    action=AssignmentActions.feedback_fetched,
                    location=r.location,
                )
                session.add(action)
            self.finish({"success": True, "feedback": feedbacks})
Exemple #9
0
    def post(self):
        """
        This endpoint accepts feedback files for a notebook.
        It requires a notebook id, student id, feedback timestamp and
        a checksum.

        The endpoint return {'success': true} for all successful feedback releases.
        """

        [
            course_id,
            assignment_id,
            notebook_id,
            student_id,
            timestamp,
            checksum,
        ] = self.get_params([
            "course_id",
            "assignment_id",
            "notebook",
            "student",
            "timestamp",
            "checksum",
        ])

        if not (course_id and assignment_id and notebook_id and student_id
                and timestamp and checksum):
            note = "Feedback call requires a course id, assignment id, notebook name, student id, checksum and timestamp."
            self.log.debug(note)
            self.finish({"success": False, "note": note})
            return

        this_user = self.nbex_user
        if course_id not in this_user["courses"]:
            note = f"User not subscribed to course {course_id}"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        if ("instructor" != this_user["current_role"].casefold()
            ):  # we may need to revisit this
            note = f"User not an instructor to course {course_id}"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        with scoped_session() as session:

            # Start building feedback object

            course = Course.find_by_code(db=session,
                                         code=course_id,
                                         org_id=this_user["org_id"],
                                         log=self.log)

            if not course:
                self.log.info(
                    f"Could not find requested resource course {course_id}")
                raise web.HTTPError(
                    404,
                    f"Could not find requested resource course {course_id}")

            assignment = AssignmentModel.find_by_code(
                db=session,
                code=assignment_id,
                course_id=course.id,
                action=AssignmentActions.released.value,
            )

            if not assignment:
                note = f"Could not find requested resource assignment {assignment_id}"
                self.log.info(note)
                raise web.HTTPError(404, note)

            notebook = Notebook.find_by_name(db=session,
                                             name=notebook_id,
                                             assignment_id=assignment.id,
                                             log=self.log)
            if not notebook:
                note = f"Could not find requested resource notebook {notebook_id}"
                self.log.info(note)
                raise web.HTTPError(404, note)

            student = User.find_by_name(db=session,
                                        name=student_id,
                                        log=self.log)

            if not student:
                note = f"Could not find requested resource student {student_id}"
                self.log.info(note)
                raise web.HTTPError(404, note)

            # # raise Exception(f"{res}")
            # self.log.info(f"Notebook: {notebook}")
            # self.log.info(f"Student: {student}")
            # self.log.info(f"Instructor: {this_user}")

            # TODO: check access. Is the user an instructor on the course to which the notebook belongs

            # Check whether there is an HTML file attached to the request
            if not self.request.files:
                self.log.warning(f"Error: No file supplied in upload"
                                 )  # TODO: improve error message
                raise web.HTTPError(412)  # precondition failed

            try:
                # Grab the file
                file_info = self.request.files["feedback"][0]
                filename, content_type = (
                    file_info["filename"],
                    file_info["content_type"],
                )
                note = f"Received file {filename}, of type {content_type}"
                self.log.info(note)
                fbfile = tempfile.NamedTemporaryFile()
                fbfile.write(file_info["body"])
                fbfile.seek(0)

            except Exception as e:
                # Could not grab the feedback file
                self.log.error(f"Error: {e}")
                raise web.HTTPError(412)
            # TODO: should we check the checksum?
            # unique_key = make_unique_key(
            #     course_id,
            #     assignment_id,
            #     notebook_id,
            #     student_id,
            #     str(timestamp).strip(),
            # )
            # check_checksum = notebook_hash(fbfile.name, unique_key)
            #
            # if check_checksum != checksum:
            #     self.log.info(f"Checksum {checksum} does not match {check_checksum}")
            #     raise web.HTTPError(403, f"Checksum {checksum} does not match {check_checksum}")

            # TODO: What is file of the original notebook we are getting the feedback for?
            # assignment_dir = "collected/student_id/assignment_name"
            # nbfile = os.path.join(assignment_dir, "{}.ipynb".format(notebook.name))
            # calc_checksum = notebook_hash(nbfile.name, unique_key)
            # if calc_checksum != checksum:
            #     self.log.info(f"Mismatched checksums {calc_checksum} and {checksum}.")
            #     raise web.HTTPError(412)

            location = "/".join([
                self.base_storage_location,
                str(this_user["org_id"]),
                "feedback",
                notebook.assignment.course.course_code,
                notebook.assignment.assignment_code,
                str(int(time.time())),
            ])

            # This should be abstracted, so it can be overloaded to store in other manners (eg AWS)
            feedback_file = location + "/" + checksum + ".html"

            try:
                # Ensure the directory exists
                os.makedirs(os.path.dirname(feedback_file), exist_ok=True)
                with open(feedback_file, "w+b") as handle:
                    handle.write(file_info["body"])
            except Exception as e:
                self.log.error(f"Could not save file. \n {e}")
                raise web.HTTPError(500)

            feedback = Feedback(
                notebook_id=notebook.id,
                checksum=checksum,
                location=feedback_file,
                student_id=student.id,
                instructor_id=this_user.get("id"),
                timestamp=parser.parse(timestamp),
            )

            session.add(feedback)

            # Add action
            action = Action(
                user_id=this_user["id"],
                assignment_id=notebook.assignment.id,
                action=AssignmentActions.feedback_released,
                location=feedback_file,
            )
            session.add(action)

        self.finish({"success": True, "note": "Feedback released"})
Exemple #10
0
    def post(self):

        # Do a content-length check, before we go any further
        if "Content-Length" in self.request.headers and int(
            self.request.headers["Content-Length"]
        ) > int(self.max_buffer_size):
            note = "File upload oversize, and rejected. Please reduce the contents of the assignment, re-generate, and re-release"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        [course_code, assignment_code] = self.get_params(["course_id", "assignment_id"])
        self.log.debug(
            f"Called POST /assignment with arguments: course {course_code} and  assignment {assignment_code}"
        )
        if not (course_code and assignment_code):
            note = f"Posting an Assigment requires a course code and an assignment code"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        this_user = self.nbex_user
        if not course_code in this_user["courses"]:
            note = f"User not subscribed to course {course_code}"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        if (
            not "instructor" == this_user["current_role"].casefold()
        ):  # we may need to revisit this
            note = f"User not an instructor to course {course_code}"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        # The course will exist: the user object creates it if it doesn't exist
        #  - and we know the user is subscribed to the course as an instructor (above)
        with scoped_session() as session:
            course = Course.find_by_code(
                db=session, code=course_code, org_id=this_user["org_id"], log=self.log
            )

            # We need to find this assignment, or make a new one.
            assignment = AssignmentModel.find_by_code(
                db=session, code=assignment_code, course_id=course.id
            )

            if assignment is None:
                # Look for inactive assignments
                assignment = AssignmentModel.find_by_code(
                    db=session, code=assignment_code, course_id=course.id, active=False
                )

            if assignment is None:
                self.log.info(
                    f"New Assignment details: assignment_code:{assignment_code}, course_id:{course.id}"
                )
                # defaults active
                assignment = AssignmentModel(
                    assignment_code=assignment_code, course_id=course.id
                )
                session.add(assignment)
                # deliberately no commit: we need to be able to roll-back if there's no data!

            # Set assignment to active
            assignment.active = True

            # storage is dynamically in $path/release/$course_code/$assignment_code/<timestamp>/
            # Note - this means we can have multiple versions of the same release on the system
            release_file = "/".join(
                [
                    self.base_storage_location,
                    str(this_user["org_id"]),
                    AssignmentActions.released.value,
                    course_code,
                    assignment_code,
                    str(int(time.time())),
                ]
            )

            if not self.request.files:
                self.log.warning(
                    f"Error: No file supplies in upload"
                )  # TODO: improve error message
                raise web.HTTPError(412)  # precondition failed

            try:
                # Write the uploaded file to the desired location
                file_info = self.request.files["assignment"][0]

                filename, content_type = (
                    file_info["filename"],
                    file_info["content_type"],
                )
                note = f"Received file {filename}, of type {content_type}"
                self.log.info(note)
                extn = os.path.splitext(filename)[1]
                cname = str(uuid.uuid4()) + extn

                # store to disk.
                # This should be abstracted, so it can be overloaded to store in other manners (eg AWS)
                release_file = release_file + "/" + cname
                # Ensure the directory exists
                os.makedirs(os.path.dirname(release_file), exist_ok=True)
                with open(release_file, "w+b") as handle:
                    handle.write(file_info["body"])

            except Exception as e:  # TODO: exception handling
                self.log.warning(f"Error: {e}")  # TODO: improve error message

                self.log.info(f"Upload failed")
                # error 500??
                raise Exception

            # Check the file exists on disk
            if not (
                os.path.exists(release_file)
                and os.access(release_file, os.R_OK)
                and os.path.getsize(release_file) > 0
            ):
                note = "File upload failed."
                self.log.info(note)
                self.finish({"success": False, "note": note})
                return

            # We shouldn't get here, but a double-check is good
            if os.path.getsize(release_file) > self.max_buffer_size:
                os.remove(release_file)
                note = "File upload oversize, and rejected. Please reduce the contents of the assignment, re-generate, and re-release"
                self.log.info(note)
                self.finish({"success": False, "note": note})
                return

            # now commit the assignment, and get it back to find the id
            assignment = AssignmentModel.find_by_code(
                db=session, code=assignment_code, course_id=course.id
            )

            # Record the notebooks associated with this assignment
            notebooks = self.get_arguments("notebooks")

            for notebook in notebooks:
                self.log.debug(f"Adding notebook {notebook}")
                new_notebook = Notebook(name=notebook)
                assignment.notebooks.append(new_notebook)

            # Record the action.
            # Note we record the path to the files.
            self.log.info(
                f"Adding action {AssignmentActions.released.value} for user {this_user['id']} against assignment {assignment.id}"
            )
            action = Action(
                user_id=this_user["id"],
                assignment_id=assignment.id,
                action=AssignmentActions.released,
                location=release_file,
            )
            session.add(action)
            self.finish({"success": True, "note": "Released"})
Exemple #11
0
    def get(self):  # def get(self, course_code, assignment_code=None):

        [course_code, assignment_code] = self.get_params(["course_id", "assignment_id"])

        if not (course_code and assignment_code):
            note = "Assigment call requires both a course code and an assignment code!!"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        this_user = self.nbex_user

        if not course_code in this_user["courses"]:
            note = f"User not subscribed to course {course_code}"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        # Find the course being referred to
        with scoped_session() as session:
            course = Course.find_by_code(
                db=session, code=course_code, org_id=this_user["org_id"], log=self.log
            )
            if course is None:
                note = f"Course {course_code} does not exist"
                self.log.info(note)
                self.finish({"success": False, "note": note})
                return  # needs a proper 'fail' here

            note = ""
            self.log.debug(f"Course:{course_code} assignment:{assignment_code}")

            # The location for the data-object is actually held in the 'released' action for the given assignment
            # We want the last one...
            assignment = AssignmentModel.find_by_code(
                db=session,
                code=assignment_code,
                course_id=course.id,
                action=AssignmentActions.released.value,
            )

            if assignment is None:
                note = f"Assignment {assignment_code} does not exist"
                self.log.info(note)
                self.finish({"success": False, "note": note})
                return  # needs a proper 'fail' here

            self._headers = httputil.HTTPHeaders(
                {
                    "Content-Type": "application/gzip",
                    "Date": httputil.format_timestamp(time.time()),
                }
            )

            data = b""

            release_file = None

            action = Action.find_most_recent_action(
                db=session,
                assignment_id=assignment.id,
                action=AssignmentActions.released,
                log=self.log,
            )
            release_file = action.location

            if release_file:
                try:
                    with open(release_file, "r+b") as handle:
                        data = handle.read()
                except Exception as e:  # TODO: exception handling
                    self.log.warning(f"Error: {e}")  # TODO: improve error message
                    self.log.info(f"Unable to open file")

                    # error 500??
                    raise Exception

                self.log.info(
                    f"Adding action {AssignmentActions.fetched.value} for user {this_user['id']} against assignment {assignment.id}"
                )
                action = Action(
                    user_id=this_user["id"],
                    assignment_id=assignment.id,
                    action=AssignmentActions.fetched,
                    location=release_file,
                )
                session.add(action)
                self.log.info("record of fetch action committed")
                self.finish(data)
            else:
                self.log.info("no release file found")
                raise Exception
Exemple #12
0
    def post(self):

        if "Content-Length" in self.request.headers and int(
                self.request.headers["Content-Length"]) > int(
                    self.max_buffer_size):
            note = "File upload oversize, and rejected. Please reduce the files in your submission and try again."
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        [course_code,
         assignment_code] = self.get_params(["course_id", "assignment_id"])
        self.log.debug(
            f"Called POST /submission with arguments: course {course_code} and  assignment {assignment_code}"
        )
        if not (course_code and assignment_code):
            note = f"Submission call requires both a course code and an assignment code"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        this_user = self.nbex_user
        if not course_code in this_user["courses"]:
            note = f"User not subscribed to course {course_code}"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        # The course will exist: the user object creates it if it doesn't exist
        #  - and we know the user is subscribed to the course as an instructor (above)
        with scoped_session() as session:
            course = Course.find_by_code(db=session,
                                         code=course_code,
                                         org_id=this_user["org_id"],
                                         log=self.log)

            # We need to find this assignment, or make a new one.
            assignment = Assignment.find_by_code(db=session,
                                                 code=assignment_code,
                                                 course_id=course.id)
            if assignment is None:
                note = f"User not fetched assignment {assignment_code}"
                self.log.info(note)
                self.finish({"success": False, "note": note})
                return

            # storage is dynamically in $path/submitted/$course_code/$assignment_code/$username/<timestamp>/
            # Note - this means that a user can submit multiple times, and we have all copies
            release_file = "/".join([
                self.base_storage_location,
                str(this_user["org_id"]),
                AssignmentActions.submitted.value,
                course_code,
                assignment_code,
                this_user["name"],
                str(int(time.time())),
            ])

            if not self.request.files:
                self.log.warning(f"Error: No file supplies in upload"
                                 )  # TODO: improve error message
                raise web.HTTPError(412)  # precondition failed

            try:
                # Write the uploaded file to the desired location
                file_info = self.request.files["assignment"][0]

                filename, content_type = (
                    file_info["filename"],
                    file_info["content_type"],
                )
                note = f"Received file {filename}, of type {content_type}"
                self.log.info(note)
                extn = os.path.splitext(filename)[1]
                cname = str(uuid.uuid4()) + extn

                # store to disk.
                # This should be abstracted, so it can be overloaded to store in other manners (eg AWS)
                release_file = release_file + "/" + cname
                # Ensure the directory exists
                os.makedirs(os.path.dirname(release_file), exist_ok=True)
                with open(release_file, "w+b") as handle:
                    handle.write(file_info["body"])

            except Exception as e:  # TODO: exception handling
                self.log.warning(f"Error: {e}")  # TODO: improve error message

                self.log.info(f"Upload failed")
                # error 500??
                raise web.HTTPError(418)

            # Check the file exists on disk
            if not (os.path.exists(release_file)
                    and os.access(release_file, os.R_OK)
                    and os.path.getsize(release_file) > 0):
                note = "File upload failed."
                self.log.info(note)
                self.finish({"success": False, "note": note})
                return

            # We shouldn't need this, but it's good to double-check
            if os.path.getsize(release_file) > self.max_buffer_size:
                os.remove(release_file)
                note = "File upload oversize, and rejected. Please reduce the files in your submission and try again."
                self.log.info(note)
                self.finish({"success": False, "note": note})
                return

            # now commit the assignment, and get it back to find the id
            assignment = Assignment.find_by_code(db=session,
                                                 code=assignment_code,
                                                 course_id=course.id)

            # Record the action.
            # Note we record the path to the files.
            self.log.info(
                f"Adding action {AssignmentActions.submitted.value} for user {this_user['id']} against assignment {assignment.id}"
            )
            action = Action(
                user_id=this_user["id"],
                assignment_id=assignment.id,
                action=AssignmentActions.submitted,
                location=release_file,
            )
            session.add(action)
        self.finish({"success": True, "note": "Submitted"})
Exemple #13
0
    def get(self):

        [course_code, assignment_code,
         path] = self.get_params(["course_id", "assignment_id", "path"])

        if not (course_code and assignment_code and path):
            note = "Collection call requires a course code, an assignment code, and a path"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        # Who is my user?
        this_user = self.nbex_user

        self.log.debug(f"User: {this_user.get('name')}")
        # For what course do we want to see the assignments?
        self.log.debug(f"Course: {course_code}")
        # Is our user subscribed to this course?
        if course_code not in this_user["courses"]:
            note = f"User not subscribed to course {course_code}"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return
        self.log.info(f"user: {this_user}")

        if not "instructor" == this_user["current_role"].casefold(
        ):  # we may need to revisit this
            note = f"User not an instructor to course {course_code}"
            self.log.info(note)
            self.finish({"success": False, "note": note})
            return

        # Find the course being referred to
        with scoped_session() as session:
            course = Course.find_by_code(db=session,
                                         code=course_code,
                                         org_id=this_user["org_id"],
                                         log=self.log)
            if not course:
                note = f"Course {course_code} does not exist"
                self.log.info(note)
                self.finish({"success": False, "note": note})
                return

            # We need to key off the assignment, but we're actually looking
            # for the action with a action and a specific path
            assignments = AssignmentModel.find_for_course(
                db=session,
                course_id=course.id,
                log=self.log,
                action=AssignmentActions.submitted.value,
                path=path,
            )

            self.set_header("Content-Type", "application/gzip")

            # I do not want to assume there will just be one.
            for assignment in assignments:
                self.log.debug(f"Assignment: {assignment}")

                try:
                    with open(path, "r+b") as handle:
                        data = handle.read()
                except Exception as e:  # TODO: exception handling
                    self.log.warning(
                        f"Error: {e}")  # TODO: improve error message

                    # error 500??
                    raise Exception

                self.log.info(
                    f"Adding action {AssignmentActions.collected.value} for user {this_user['id']} against assignment {assignment.id}"  # noqa: E501
                )
                action = Action(
                    user_id=this_user["id"],
                    assignment_id=assignment.id,
                    action=AssignmentActions.collected,
                    location=path,
                )
                session.add(action)

                self.finish(data)
                return
Exemple #14
0
def test_action_relationships(db, user_johaannes):
    found_by_pk = Action.find_by_pk(db, 1)
    assert found_by_pk.user.name == user_johaannes.name
    assert found_by_pk.assignment.assignment_code == "tree 1"
    assert found_by_pk.assignment.course.course_code == "Strange"