Exemple #1
0
def assignment_tree(db):
    orm_thing = AssignmentModel.find_by_code(db, "tree 1", 1)
    if not orm_thing:
        orm_thing = AssignmentModel(assignment_code="tree 1", course_id=1)
        db.add(orm_thing)
        db.commit()
    return orm_thing
Exemple #2
0
def assignment_a2ovi(db):
    orm_thing = AssignmentModel.find_by_code(db, "⍺ to ⍵ via ∞", 1)
    if not orm_thing:
        orm_thing = AssignmentModel(assignment_code="⍺ to ⍵ via ∞", course_id=1)
        db.add(orm_thing)
        db.commit()
    return orm_thing
Exemple #3
0
def assignment_false(db):
    orm_thing = AssignmentModel.find_by_code(db=db, code="not used", course_id=1, active=False)
    if not orm_thing:
        orm_thing = AssignmentModel(assignment_code="not used", course_id=1, active=False)
        db.add(orm_thing)
        db.commit()
    return orm_thing
Exemple #4
0
    def delete(self):

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

        self.log.debug(
            f"Called DELETE /assignment with arguments: course {course_code}, assignment {assignment_code}, and purge {purge}"  # noqa: E501
        )
        if not (course_code and assignment_code):
            note = "Unreleasing 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 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

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

        note = f"Assignment '{assignment_code}' on course '{course_code}' marked as unreleased"
        with scoped_session() as session:
            course = Course.find_by_code(db=session,
                                         code=course_code,
                                         org_id=this_user["org_id"],
                                         log=self.log)

            assignment = AssignmentModel.find_by_code(db=session,
                                                      code=assignment_code,
                                                      course_id=course.id)

            if not assignment:
                note = f"Missing assignment for {assignment_code} and {course_code}, cannot delete"
                self.log.info(note)
                self.finish({"success": False, "note": note})
                return

            # Set assignment to inactive
            assignment.active = False
            # Delete the associated notebook
            for notebook in assignment.notebooks:
                session.delete(notebook)

            # If we have the purge parameter, we actually delete the data
            # The various 'cascade on delete' settings should clear all the sub-tables
            if purge:
                session.delete(assignment)
                note = f"Assignment '{assignment_code}' on course '{course_code}' deleted and purged from the database"
        self.log.info(f"{note} by user {this_user['id']} ")
        self.finish({"success": True, "note": note})
Exemple #5
0
def test_action_can_restrict_assignment_searches(db, assignment_tree):
    found = AssignmentModel.find_by_code(db, assignment_tree.assignment_code, assignment_tree.course_id)
    assert found.id == assignment_tree.id
    found = AssignmentModel.find_by_code(
        db=db,
        code=assignment_tree.assignment_code,
        course_id=assignment_tree.course_id,
        action=AssignmentActions.released,
    )
    assert found.id == assignment_tree.id
    found = AssignmentModel.find_by_code(
        db=db,
        code=assignment_tree.assignment_code,
        course_id=assignment_tree.course_id,
        action=AssignmentActions.feedback_released,
    )
    assert found is None
Exemple #6
0
def test_assignment(db, course_strange):

    orm_assignment = AssignmentModel(
        # assignment_code="tree 1",
        course_id=course_strange.id,
    )
    db.add(orm_assignment)
    with pytest.raises(IntegrityError):
        db.commit()
    db.rollback()

    assignment_tree = AssignmentModel(
        assignment_code="tree 1",
        # course_id=course_strange.id,
    )
    db.add(assignment_tree)
    db.commit()
    assignment_tree.course_id = course_strange.id
    db.commit()

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

    found_by_pk = AssignmentModel.find_by_pk(db, assignment_tree.id)
    assert found_by_pk.id == assignment_tree.id
    assert str(found_by_pk) == f"Assignment {assignment_tree.assignment_code} for course {assignment_tree.course_id}"

    found_by_pk = AssignmentModel.find_by_pk(db, assignment_tree.id + 10)
    assert found_by_pk is None

    with pytest.raises(ValueError):
        found_by_code = AssignmentModel.find_by_code(db, assignment_tree.assignment_code)
    with pytest.raises(TypeError):
        found_by_code = AssignmentModel.find_by_code(db, assignment_tree.assignment_code, "abc")
    with pytest.raises(TypeError):
        found_by_code = AssignmentModel.find_by_code(db, assignment_tree, assignment_tree.course_id)
    found_by_code = AssignmentModel.find_by_code(db, assignment_tree.assignment_code, assignment_tree.course_id)
    assert found_by_code.assignment_code == assignment_tree.assignment_code
    found_by_code = AssignmentModel.find_by_code(db, "SANE", assignment_tree.course_id)
    assert found_by_code is None
Exemple #7
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 #8
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 #9
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 #10
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 #11
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 #12
0
    def get(self):

        models = []

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

        if not (course_code and assignment_code):
            note = "Collections call requires both a course code and an assignment code"
            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
        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

            assignment = AssignmentModel.find_by_code(
                db=session,
                course_id=course.id,
                log=self.log,
                code=assignment_code,
                action=AssignmentActions.submitted.value,
            )

            if not assignment:
                note = f"Assignment {assignment_code} does not exist"
                self.log.info(note)
                self.finish({"success": True, "value": []})
                return

            self.log.debug(f"Assignment: {assignment}")

            filters = [
                Action.assignment_id == assignment.id,
                Action.action == AssignmentActions.submitted.value,
            ]

            if user_id:
                student = session.query(User).filter(
                    User.name == user_id).first()
                filters.append(Action.user_id == student.id)

            actions = session.query(Action).filter(*filters)

            for action in actions:
                models.append({
                    "student_id":
                    action.user.name,
                    "full_name":
                    action.user.full_name,
                    "assignment_id":
                    assignment.assignment_code,
                    "course_id":
                    assignment.course.course_code,
                    "status":
                    action.action.value,  # currently called 'action' in our db
                    "path":
                    action.location,
                    # 'name' in db, 'notebook_id' id nbgrader
                    "notebooks": [{
                        "notebook_id": x.name
                    } for x in assignment.notebooks],
                    "timestamp":
                    action.timestamp.strftime("%Y-%m-%d %H:%M:%S.%f %Z"),
                })

            self.log.debug(f"Assignments: {models}")
        self.finish({"success": True, "value": models})