Esempio n. 1
0
    def copy_files(self):
        if self.coursedir.student_id_exclude:
            exclude_students = set(
                self.coursedir.student_id_exclude.split(','))
        else:
            exclude_students = set()

        html_files = glob.glob(os.path.join(self.src_path, "*.html"))
        for html_file in html_files:
            if 'hashcode' in html_file:
                self.log.debug('Skipping hashcode info')
                continue
            regexp = re.escape(os.path.sep).join([
                self.coursedir.format_path(self.coursedir.feedback_directory,
                                           "(?P<student_id>.*)",
                                           self.coursedir.assignment_id,
                                           escape=True),
                "(?P<notebook_id>.*).html"
            ])

            m = re.match(regexp, html_file)
            if m is None:
                msg = "Could not match '%s' with regexp '%s'" % (html_file,
                                                                 regexp)
                self.log.error(msg)
                continue

            gd = m.groupdict()
            student_id = gd['student_id']
            notebook_id = gd['notebook_id']
            if student_id in exclude_students:
                self.log.debug("Skipping student '{}'".format(student_id))
                continue

            feedback_dir = os.path.split(html_file)[0]
            submission_dir = self.coursedir.format_path(
                self.coursedir.submitted_directory, student_id,
                self.coursedir.assignment_id)

            timestamp = open(os.path.join(feedback_dir,
                                          'timestamp.txt')).read()
            nbfile = os.path.join(submission_dir,
                                  "{}.ipynb".format(notebook_id))
            unique_key = make_unique_key(self.coursedir.course_id,
                                         self.coursedir.assignment_id,
                                         notebook_id, student_id, timestamp)

            self.log.debug("Unique key is: {}".format(unique_key))
            checksum = notebook_hash(nbfile, unique_key)
            dest = os.path.join(self.dest_path, "{}.html".format(checksum))

            self.log.info(
                "Releasing feedback for student '{}' on assignment '{}/{}/{}' ({})"
                .format(student_id, self.coursedir.course_id,
                        self.coursedir.assignment_id, notebook_id, timestamp))
            shutil.copy(html_file, dest)
            self.log.info("Feedback released to: {}".format(dest))
def test_feedback_post_authenticated_with_correct_params_student_submitter(
        app, clear_database):
    assignment_id = "assign_a"
    course_id = "course_2"
    notebook = "notebook"
    student = user_kiz_student
    timestamp = datetime.datetime.utcnow().isoformat(" ")
    checksum = notebook_hash(
        feedback_filename,
        make_unique_key(course_id, assignment_id, notebook, student["name"],
                        timestamp),
    )

    # XXX: Doing this in a separate function doesn't work for some reason (Exchange doesn't get called)
    kwargs = {"data": {"notebooks": [notebook]}}
    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_instructor):
        r = yield async_requests.post(
            app.url +
            f"/assignment?course_id={course_id}&assignment_id={assignment_id}",
            files=files,
            **kwargs,
        )
    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_student):
        r = yield async_requests.get(
            app.url +
            f"/assignment?course_id={course_id}&assignment_id={assignment_id}")
    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_student):
        r = yield async_requests.post(
            app.url +
            f"/submission?course_id={course_id}&assignment_id={assignment_id}",
            files=files,
        )

    url = (f"/feedback?assignment_id={assignment_id}"
           f"&course_id={course_id}"
           f"&notebook={notebook}"
           f"&student={student['name']}"
           f"&timestamp={timestamp}"
           f"&checksum={checksum}")

    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_student):
        r = yield async_requests.post(app.url + url, files=feedbacks)

    response_data = r.json()
    assert response_data["success"] is False
    assert response_data[
        "note"] == f"User not an instructor to course {course_id}"
Esempio n. 3
0
    def copy_files(self):
        """
        Overried copy_files and add personalized-feedback generation
        """
        if self.coursedir.student_id_exclude:
            exclude_students = set(
                self.coursedir.student_id_exclude.split(","))
        else:
            exclude_students = set()

        html_files = glob.glob(os.path.join(self.src_path, "*.html"))
        for html_file in html_files:
            if "hashcode" in html_file:
                self.log.debug("Skipping hashcode info")
                continue
            regexp = re.escape(os.path.sep).join([
                self.coursedir.format_path(
                    self.coursedir.feedback_directory,
                    "(?P<student_id>.*)",
                    self.coursedir.assignment_id,
                    escape=True,
                ),
                "(?P<notebook_id>.*).html",
            ])

            m = re.match(regexp, html_file)
            if m is None:
                msg = "Could not match '%s' with regexp '%s'" % (html_file,
                                                                 regexp)
                self.log.error(msg)
                continue

            gd = m.groupdict()
            student_id = gd["student_id"]
            notebook_id = gd["notebook_id"]
            if student_id in exclude_students:
                self.log.debug("Skipping student '{}'".format(student_id))
                continue

            feedback_dir = os.path.split(html_file)[0]
            submission_dir = self.coursedir.format_path(
                self.coursedir.submitted_directory,
                student_id,
                self.coursedir.assignment_id,
            )

            if self.personalized_feedback:
                dest = os.path.join(self.dest_path, student_id,
                                    self.coursedir.assignment_id)
                # u+rwx, g+wx, o+wx
                self.ensure_directory(
                    dest,
                    (S_IRUSR
                     | S_IWUSR
                     | S_IXUSR
                     | S_IRGRP
                     | S_IXGRP
                     | S_IXOTH
                     | S_IROTH
                     | ((S_IRGRP | S_IWGRP
                         | S_ISGID) if self.coursedir.groupshared else 0)),
                )

                dest = os.path.join(dest, notebook_id + ".html")

                self.log.info(
                    "Releasing feedback for student '{}' on assignment '{}/{}/{}' "
                    .format(
                        student_id,
                        self.coursedir.course_id,
                        self.coursedir.assignment_id,
                        notebook_id,
                    ))
            else:
                timestamp = open(os.path.join(feedback_dir,
                                              "timestamp.txt")).read()
                nbfile = os.path.join(submission_dir,
                                      "{}.ipynb".format(notebook_id))
                unique_key = make_unique_key(
                    self.coursedir.course_id,
                    self.coursedir.assignment_id,
                    notebook_id,
                    student_id,
                    timestamp,
                )

                self.log.debug("Unique key is: {}".format(unique_key))
                checksum = notebook_hash(nbfile, unique_key)
                dest = os.path.join(self.dest_path, "{}.html".format(checksum))

                self.log.info(
                    "Releasing feedback for student '{}' on assignment '{}/{}/{}' ({})"
                    .format(
                        student_id,
                        self.coursedir.course_id,
                        self.coursedir.assignment_id,
                        notebook_id,
                        timestamp,
                    ))

            shutil.copy(html_file, dest)
            self.log.info("Feedback released to: {}".format(dest))
Esempio n. 4
0
    def parse_assignments(self):
        if self.coursedir.student_id:
            courses = self.authenticator.get_student_courses(
                self.coursedir.student_id)
        else:
            courses = None

        assignments = []
        for path in self.assignments:
            info = self.parse_assignment(path)
            if courses is not None and info['course_id'] not in courses:
                continue

            if self.path_includes_course:
                assignment_dir = os.path.join(self.assignment_dir,
                                              info['course_id'],
                                              info['assignment_id'])
            else:
                assignment_dir = os.path.join(self.assignment_dir,
                                              info['assignment_id'])

            if self.inbound or self.cached:
                info['status'] = 'submitted'
                info['path'] = path
            elif os.path.exists(assignment_dir):
                info['status'] = 'fetched'
                info['path'] = os.path.abspath(assignment_dir)
            else:
                info['status'] = 'released'
                info['path'] = path

            if self.remove:
                info['status'] = 'removed'

            notebooks = sorted(glob.glob(os.path.join(info['path'],
                                                      '*.ipynb')))
            if not notebooks:
                self.log.warning("No notebooks found in {}".format(
                    info['path']))

            info['notebooks'] = []
            for notebook in notebooks:
                nb_info = {
                    'notebook_id':
                    os.path.splitext(os.path.split(notebook)[1])[0],
                    'path': os.path.abspath(notebook)
                }
                if info['status'] != 'submitted':
                    info['notebooks'].append(nb_info)
                    continue

                nb_info['has_local_feedback'] = False
                nb_info['has_exchange_feedback'] = False
                nb_info['local_feedback_path'] = None
                nb_info['feedback_updated'] = False

                # Check whether feedback has been fetched already.
                local_feedback_dir = os.path.join(assignment_dir, 'feedback',
                                                  info['timestamp'])
                local_feedback_path = os.path.join(
                    local_feedback_dir,
                    '{0}.html'.format(nb_info['notebook_id']))
                has_local_feedback = os.path.isfile(local_feedback_path)
                if has_local_feedback:
                    local_feedback_checksum = _checksum(local_feedback_path)
                else:
                    local_feedback_checksum = None

                # Also look to see if there is feedback available to fetch.
                unique_key = make_unique_key(info['course_id'],
                                             info['assignment_id'],
                                             nb_info['notebook_id'],
                                             info['student_id'],
                                             info['timestamp'])
                self.log.debug("Unique key is: {}".format(unique_key))
                nb_hash = notebook_hash(notebook, unique_key)
                exchange_feedback_path = os.path.join(
                    self.root, info['course_id'], 'feedback',
                    '{0}.html'.format(nb_hash))
                has_exchange_feedback = os.path.isfile(exchange_feedback_path)
                if not has_exchange_feedback:
                    # Try looking for legacy feedback.
                    nb_hash = notebook_hash(notebook)
                    exchange_feedback_path = os.path.join(
                        self.root, info['course_id'], 'feedback',
                        '{0}.html'.format(nb_hash))
                    has_exchange_feedback = os.path.isfile(
                        exchange_feedback_path)
                if has_exchange_feedback:
                    exchange_feedback_checksum = _checksum(
                        exchange_feedback_path)
                else:
                    exchange_feedback_checksum = None

                nb_info['has_local_feedback'] = has_local_feedback
                nb_info['has_exchange_feedback'] = has_exchange_feedback
                if has_local_feedback:
                    nb_info['local_feedback_path'] = local_feedback_path
                if has_local_feedback and has_exchange_feedback:
                    nb_info[
                        'feedback_updated'] = exchange_feedback_checksum != local_feedback_checksum
                info['notebooks'].append(nb_info)

            if info['status'] == 'submitted':
                if info['notebooks']:
                    # has_local_feedback = all([nb['has_local_feedback'] for nb in info['notebooks']])
                    # has_exchange_feedback = all([nb['has_exchange_feedback'] for nb in info['notebooks']])
                    has_local_feedback = any(
                        [nb['has_local_feedback'] for nb in info['notebooks']])
                    has_exchange_feedback = any([
                        nb['has_exchange_feedback'] for nb in info['notebooks']
                    ])
                    feedback_updated = any(
                        [nb['feedback_updated'] for nb in info['notebooks']])
                else:
                    has_local_feedback = False
                    has_exchange_feedback = False
                    feedback_updated = False

                info['has_local_feedback'] = has_local_feedback
                info['has_exchange_feedback'] = has_exchange_feedback
                info['feedback_updated'] = feedback_updated
                if has_local_feedback:
                    info['local_feedback_path'] = os.path.join(
                        assignment_dir, 'feedback', info['timestamp'])
                else:
                    info['local_feedback_path'] = None

            assignments.append(info)

        # partition the assignments into groups for course/student/assignment
        if self.inbound or self.cached:
            _get_key = lambda info: (info['course_id'], info['student_id'],
                                     info['assignment_id'])
            _match_key = lambda info, key: (info['course_id'] == key[
                0] and info['student_id'] == key[1] and info['assignment_id']
                                            == key[2])
            assignment_keys = sorted(
                list(set([_get_key(info) for info in assignments])))
            assignment_submissions = []
            for key in assignment_keys:
                submissions = [x for x in assignments if _match_key(x, key)]
                submissions = sorted(submissions, key=lambda x: x['timestamp'])
                info = {
                    'course_id': key[0],
                    'student_id': key[1],
                    'assignment_id': key[2],
                    'status': submissions[0]['status'],
                    'submissions': submissions
                }
                assignment_submissions.append(info)
            assignments = assignment_submissions

        return assignments
Esempio n. 5
0
    def parse_assignments(self):
        if self.coursedir.student_id:
            courses = self.authenticator.get_student_courses(
                self.coursedir.student_id)
        else:
            courses = None

        assignments = []
        released_assignments = []
        for path in self.assignments:
            info = self.parse_assignment(path)
            # if grader and the assignment is already known as released assignment, skip looking
            if (self.personalized_outbound and self.grader
                    and info["assignment_id"] in released_assignments):
                self.log.debug(
                    "Grader role and personalized-outbound are enabled, and the assignment is known to be released already"
                )
                continue

            if courses is not None and info["course_id"] not in courses:
                continue

            if self.path_includes_course:
                assignment_dir = os.path.join(self.assignment_dir,
                                              info["course_id"],
                                              info["assignment_id"])
            else:
                assignment_dir = os.path.join(self.assignment_dir,
                                              info["assignment_id"])

            if self.inbound or self.cached:
                info["status"] = "submitted"
                info["path"] = path
            elif os.path.exists(assignment_dir):
                info["status"] = "fetched"
                info["path"] = os.path.abspath(assignment_dir)
            else:
                info["status"] = "released"
                info["path"] = path
                # update released assignments
                if self.personalized_outbound and self.grader:
                    released_assignments.append(info["assignment_id"])

            if self.remove:
                info["status"] = "removed"

            notebooks = sorted(glob.glob(os.path.join(info["path"],
                                                      "*.ipynb")))
            if not notebooks:
                self.log.warning("No notebooks found in {}".format(
                    info["path"]))

            info["notebooks"] = []
            for notebook in notebooks:
                nb_info = {
                    "notebook_id":
                    os.path.splitext(os.path.split(notebook)[1])[0],
                    "path": os.path.abspath(notebook),
                }
                if info["status"] != "submitted":
                    info["notebooks"].append(nb_info)
                    continue

                nb_info["has_local_feedback"] = False
                nb_info["has_exchange_feedback"] = False
                nb_info["local_feedback_path"] = None
                nb_info["feedback_updated"] = False

                # Check whether feedback has been fetched already.
                local_feedback_dir = os.path.join(assignment_dir, "feedback",
                                                  info["timestamp"])
                local_feedback_path = os.path.join(
                    local_feedback_dir,
                    "{0}.html".format(nb_info["notebook_id"]))
                has_local_feedback = os.path.isfile(local_feedback_path)
                if has_local_feedback:
                    local_feedback_checksum = _checksum(local_feedback_path)
                else:
                    local_feedback_checksum = None

                # Also look to see if there is feedback available to fetch.
                # and check whether personalized-feedback is enabled
                if self.personalized_feedback:
                    exchange_feedback_path = os.path.join(
                        self.root,
                        info["course_id"],
                        self.feedback_directory,
                        info["student_id"],
                        info["assignment_id"],
                        "{0}.html".format(nb_info["notebook_id"]),
                    )
                else:
                    unique_key = make_unique_key(
                        info["course_id"],
                        info["assignment_id"],
                        nb_info["notebook_id"],
                        info["student_id"],
                        info["timestamp"],
                    )
                    self.log.debug("Unique key is: {}".format(unique_key))
                    nb_hash = notebook_hash(notebook, unique_key)
                    exchange_feedback_path = os.path.join(
                        self.root,
                        info["course_id"],
                        self.feedback_directory,
                        "{0}.html".format(nb_hash),
                    )

                has_exchange_feedback = os.path.isfile(exchange_feedback_path)
                if not has_exchange_feedback:
                    # Try looking for legacy feedback.
                    nb_hash = notebook_hash(notebook)
                    exchange_feedback_path = os.path.join(
                        self.root,
                        info["course_id"],
                        "feedback",
                        "{0}.html".format(nb_hash),
                    )
                    has_exchange_feedback = os.path.isfile(
                        exchange_feedback_path)
                if has_exchange_feedback:
                    exchange_feedback_checksum = _checksum(
                        exchange_feedback_path)
                else:
                    exchange_feedback_checksum = None

                nb_info["has_local_feedback"] = has_local_feedback
                nb_info["has_exchange_feedback"] = has_exchange_feedback
                if has_local_feedback:
                    nb_info["local_feedback_path"] = local_feedback_path
                if has_local_feedback and has_exchange_feedback:
                    nb_info["feedback_updated"] = (exchange_feedback_checksum
                                                   != local_feedback_checksum)
                info["notebooks"].append(nb_info)

            if info["status"] == "submitted":
                if info["notebooks"]:
                    has_local_feedback = all(
                        [nb["has_local_feedback"] for nb in info["notebooks"]])
                    has_exchange_feedback = all([
                        nb["has_exchange_feedback"] for nb in info["notebooks"]
                    ])
                    feedback_updated = any(
                        [nb["feedback_updated"] for nb in info["notebooks"]])
                else:
                    has_local_feedback = False
                    has_exchange_feedback = False
                    feedback_updated = False

                info["has_local_feedback"] = has_local_feedback
                info["has_exchange_feedback"] = has_exchange_feedback
                info["feedback_updated"] = feedback_updated
                if has_local_feedback:
                    info["local_feedback_path"] = os.path.join(
                        assignment_dir, "feedback", info["timestamp"])
                else:
                    info["local_feedback_path"] = None

            assignments.append(info)

        # partition the assignments into groups for course/student/assignment
        if self.inbound or self.cached:
            assignment_keys = sorted(
                list(set([_get_key(info) for info in assignments])))
            assignment_submissions = []
            for key in assignment_keys:
                submissions = [x for x in assignments if _match_key(x, key)]
                submissions = sorted(submissions, key=lambda x: x["timestamp"])
                info = {
                    "course_id": key[0],
                    "student_id": key[1],
                    "assignment_id": key[2],
                    "status": submissions[0]["status"],
                    "submissions": submissions,
                }
                assignment_submissions.append(info)
            assignments = assignment_submissions

        return assignments
def test_feedback_get_correct_assignment_across_courses(app, clear_database):
    pass
    # set up the situation
    assignment_id = "assign_a"
    course_1 = "course_1"
    course_2 = "course_2"
    notebook = "notebook"
    student = user_kiz_student
    timestamp = datetime.datetime.utcnow().isoformat(" ")
    checksum = notebook_hash(
        feedback_filename,
        make_unique_key(course_2, assignment_id, notebook, student["name"],
                        timestamp),
    )
    # XXX: Doing this in a separate function doesn't work for some reason (Exchange doesn't get called)
    kwargs = {"data": {"notebooks": [notebook]}}

    # release assignment on course 1 & 2
    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_instructor):
        r = yield async_requests.post(
            app.url +
            f"/assignment?course_id={course_1}&assignment_id={assignment_id}",
            files=files,
            **kwargs,
        )
    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_instructor):
        r = yield async_requests.post(
            app.url +
            f"/assignment?course_id={course_2}&assignment_id={assignment_id}",
            files=files,
            **kwargs,
        )

    # Now fetch & submit on course 2 as student
    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_student):
        r = yield async_requests.get(
            app.url +
            f"/assignment?course_id={course_2}&assignment_id={assignment_id}")
    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_student):
        r = yield async_requests.post(
            app.url +
            f"/submission?course_id={course_2}&assignment_id={assignment_id}",
            files=files,
        )

    # Instructor releases for course 2
    url = (f"/feedback?assignment_id={assignment_id}"
           f"&course_id={course_2}"
           f"&notebook={notebook}"
           f"&student={student['name']}"
           f"&timestamp={timestamp}"
           f"&checksum={checksum}")
    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_instructor):
        r = yield async_requests.post(app.url + url, files=feedbacks)

    url = f"/feedback?assignment_id={assignment_id}&course_id={course_1}"

    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_student):
        r = yield async_requests.get(app.url + url)

    assert r.status_code == 404

    url = f"/feedback?assignment_id={assignment_id}&course_id={course_2}"

    with patch.object(BaseHandler,
                      "get_current_user",
                      return_value=user_kiz_student):
        r = yield async_requests.get(app.url + url)

    assert r.status_code == 200
    response_data = r.json()
    assert response_data["success"] is True
    assert len(response_data["feedback"]) >= 1
    assert response_data["feedback"][0].get(
        "content") == feedback_base64.decode("utf-8")
Esempio n. 7
0
    def init_src(self):
        if self.coursedir.course_id == "":
            self.fail("No course id specified. Re-run with --course flag.")

        self.course_path = os.path.join(self.root, self.coursedir.course_id)
        self.outbound_path = os.path.join(self.course_path,
                                          self.feedback_directory)
        self.src_path = os.path.join(self.outbound_path)
        self.cache_path = os.path.join(self.cache, self.coursedir.course_id)

        if self.coursedir.student_id != "*":
            # An explicit student id has been specified on the command line; we use it as student_id
            if "*" in self.coursedir.student_id or "+" in self.coursedir.student_id:
                self.fail(
                    "The student ID should contain no '*' nor '+'; got {}".
                    format(self.coursedir.student_id))
            student_id = self.coursedir.student_id
        else:
            student_id = get_username()

        if not os.path.isdir(self.src_path):
            self._assignment_not_found(self.src_path,
                                       os.path.join(self.outbound_path, "*"))
        if not check_mode(self.src_path, execute=True):
            self.fail(
                "You don't have execute permissions for the directory: {}".
                format(self.src_path))

        assignment_id = (self.coursedir.assignment_id
                         if self.coursedir.assignment_id else "*")
        pattern = os.path.join(self.cache_path, "*+{}+*".format(assignment_id))
        self.log.debug(
            "Looking for submissions with pattern: {}".format(pattern))

        self.feedback_files = []
        submissions = [os.path.split(x)[-1] for x in glob.glob(pattern)]
        for submission in submissions:
            _, assignment_id, timestamp = submission.split("/")[-1].split("+")

            self.log.debug(
                "Looking for feedback for '{}/{}' submitted at {}".format(
                    self.coursedir.course_id, assignment_id, timestamp))

            pattern = os.path.join(self.cache_path, submission, "*.ipynb")
            notebooks = glob.glob(pattern)
            for notebook in notebooks:
                notebook_id = os.path.splitext(os.path.split(notebook)[-1])[0]

                # Check if personalized_feedback is used
                if self.personalized_feedback:
                    feedbackpath = os.path.join(
                        self.outbound_path,
                        student_id,
                        assignment_id,
                        "{}.html".format(notebook_id),
                    )
                    self.log.debug("Feedback file: ", feedbackpath)
                    if os.path.exists(feedbackpath):
                        self.feedback_files.append(
                            (notebook_id, timestamp, feedbackpath))
                        self.log.info(
                            "Found feedback for '{}/{}/{}' submitted at {}".
                            format(
                                self.coursedir.course_id,
                                assignment_id,
                                notebook_id,
                                timestamp,
                            ))
                        continue

                    # If we reached here, then there's no feedback available
                    self.log.warning(
                        "Could not find feedback for '{}/{}/{}' submitted at {}"
                        .format(
                            self.coursedir.course_id,
                            assignment_id,
                            notebook_id,
                            timestamp,
                        ))
                else:
                    unique_key = make_unique_key(
                        self.coursedir.course_id,
                        assignment_id,
                        notebook_id,
                        student_id,
                        timestamp,
                    )

                    # Look for the feedback using new-style of feedback
                    self.log.debug("Unique key is: {}".format(unique_key))
                    nb_hash = notebook_hash(notebook, unique_key)
                    feedbackpath = os.path.join(self.outbound_path,
                                                "{0}.html".format(nb_hash))
                    if os.path.exists(feedbackpath):
                        self.feedback_files.append(
                            (notebook_id, timestamp, feedbackpath))
                        self.log.info(
                            "Found feedback for '{}/{}/{}' submitted at {}".
                            format(
                                self.coursedir.course_id,
                                assignment_id,
                                notebook_id,
                                timestamp,
                            ))
                        continue

                    # If it doesn't exist, try the legacy hashing
                    nb_hash = notebook_hash(notebook)
                    feedbackpath = os.path.join(self.outbound_path,
                                                "{0}.html".format(nb_hash))
                    if os.path.exists(feedbackpath):
                        self.feedback_files.append(
                            (notebook_id, timestamp, feedbackpath))
                        self.log.warning(
                            "Found legacy feedback for '{}/{}/{}' submitted at {}"
                            .format(
                                self.coursedir.course_id,
                                assignment_id,
                                notebook_id,
                                timestamp,
                            ))
                        continue

                    # If we reached here, then there's no feedback available
                    self.log.warning(
                        "Could not find feedback for '{}/{}/{}' submitted at {}"
                        .format(
                            self.coursedir.course_id,
                            assignment_id,
                            notebook_id,
                            timestamp,
                        ))
Esempio n. 8
0
    def copy_files(self):
        if self.coursedir.student_id_exclude:
            exclude_students = set(
                self.coursedir.student_id_exclude.split(","))
        else:
            exclude_students = set()

        html_files = glob.glob(os.path.join(self.src_path, "*.html"))
        for html_file in html_files:
            regexp = re.escape(os.path.sep).join([
                os.path.normpath(
                    self.coursedir.format_path(
                        self.coursedir.feedback_directory,
                        "(?P<student_id>.*)",
                        self.coursedir.assignment_id,
                        escape=True,
                    )),
                "(?P<notebook_id>.*).html",
            ])

            m = re.match(regexp, html_file)
            if m is None:
                msg = "Could not match '%s' with regexp '%s'" % (html_file,
                                                                 regexp)
                self.log.error(msg)
                continue

            gd = m.groupdict()
            student_id = gd["student_id"]
            notebook_id = gd["notebook_id"]
            if student_id in exclude_students:
                self.log.debug("Skipping student '{}'".format(student_id))
                continue

            feedback_dir = os.path.split(html_file)[0]
            submission_dir = self.coursedir.format_path(
                self.coursedir.submitted_directory,
                student_id,
                self.coursedir.assignment_id,
            )

            timestamp = open(os.path.join(feedback_dir,
                                          "timestamp.txt")).read().strip()
            nbfile = os.path.join(submission_dir,
                                  "{}.ipynb".format(notebook_id))
            unique_key = make_unique_key(
                self.coursedir.course_id,
                self.coursedir.assignment_id,
                notebook_id,
                student_id,
                timestamp,
            )

            self.log.debug("Unique key is: {}".format(unique_key))
            checksum = notebook_hash(nbfile, unique_key)

            timestamp = parser.parse(timestamp).strftime(
                self.timestamp_format).strip()

            self.log.info(
                "Releasing feedback for student '{}' on assignment '{}/{}/{}' ({})"
                .format(
                    student_id,
                    self.coursedir.course_id,
                    self.coursedir.assignment_id,
                    notebook_id,
                    timestamp,
                ))

            self.upload(
                html_file,
                self.coursedir.assignment_id,
                student_id,
                notebook_id,
                timestamp,
                checksum,
            )
Esempio n. 9
0
def test_release_feedback_fetch_normal(plugin_config, tmpdir):
    plugin_config.CourseDirectory.root = "/"
    plugin_config.CourseDirectory.feedback_directory = str(
        tmpdir.mkdir("feedback_test").realpath())
    plugin_config.CourseDirectory.submitted_directory = str(
        tmpdir.mkdir("submitted_test").realpath())
    plugin_config.CourseDirectory.assignment_id = assignment_id
    os.makedirs(
        os.path.join(plugin_config.CourseDirectory.feedback_directory,
                     student_id, assignment_id),
        exist_ok=True,
    )
    os.makedirs(
        os.path.join(plugin_config.CourseDirectory.submitted_directory,
                     student_id, assignment_id),
        exist_ok=True,
    )

    feedback_filename_uploaded = os.path.join(
        plugin_config.CourseDirectory.feedback_directory,
        student_id,
        assignment_id,
        "feedback.html",
    )
    copyfile(feedback1_filename, feedback_filename_uploaded)

    copyfile(
        notebook1_filename,
        os.path.join(
            plugin_config.CourseDirectory.submitted_directory,
            student_id,
            assignment_id,
            "feedback.ipynb",
        ),
    )
    with open(
            os.path.join(
                plugin_config.CourseDirectory.feedback_directory,
                student_id,
                assignment_id,
                "timestamp.txt",
            ),
            "w",
    ) as fp:
        fp.write("2020-01-01 00:00:00.0 UTC")

    unique_key = make_unique_key("no_course", assignment_id, "feedback",
                                 student_id, "2020-01-01 00:00:00.0 UTC")
    checksum = notebook_hash(
        os.path.join(
            plugin_config.CourseDirectory.submitted_directory,
            student_id,
            assignment_id,
            "feedback.ipynb",
        ),
        unique_key,
    )

    plugin = ExchangeReleaseFeedback(
        coursedir=CourseDirectory(config=plugin_config), config=plugin_config)

    def api_request(*args, **kwargs):
        assert args[0] == ("feedback?course_id=no_course"
                           f"&assignment_id={assignment_id}"
                           "&notebook=feedback"
                           f"&student={student_id}"
                           "&timestamp=2020-01-01+00%3A00%3A00.000000+UTC"
                           "&checksum=" + checksum)
        assert kwargs.get("method").lower() == "post"
        assert "feedback" in kwargs.get("files")
        assert ("feedback.html", open(feedback_filename_uploaded).read()
                ) == kwargs.get("files").get("feedback")
        return type(
            "Request",
            (object, ),
            {
                "status_code": 200,
                "json": (lambda: {
                    "success": True
                })
            },
        )

    with patch.object(Exchange, "api_request", side_effect=api_request):
        plugin.start()
Esempio n. 10
0
def test_release_feedback_fetch_several_normal(plugin_config, tmpdir):
    # set up the submitted & feeback directories
    feedback_directory = str(tmpdir.mkdir("feedback_test").realpath())
    submitted_directory = str(tmpdir.mkdir("submitted_test").realpath())
    plugin_config.CourseDirectory.root = "/"
    plugin_config.CourseDirectory.feedback_directory = feedback_directory
    plugin_config.CourseDirectory.submitted_directory = submitted_directory
    plugin_config.CourseDirectory.assignment_id = assignment_id
    os.makedirs(os.path.join(feedback_directory, student_id, assignment_id),
                exist_ok=True)
    os.makedirs(os.path.join(submitted_directory, student_id, assignment_id),
                exist_ok=True)

    # Scenario:
    # two pieces of feedback for two notebooks, for the same submission
    # * submitted_directory is where the student .ipynb file was collected into
    # * feedback_directory is where the generated .html feeback was written to
    feedback1_filename_uploaded = os.path.join(feedback_directory, student_id,
                                               assignment_id, "feedback1.html")
    copyfile(feedback1_filename, feedback1_filename_uploaded)
    copyfile(
        notebook1_filename,
        os.path.join(submitted_directory, student_id, assignment_id,
                     "feedback1.ipynb"),
    )

    feedback2_filename_uploaded = os.path.join(feedback_directory, student_id,
                                               assignment_id, "feedback2.html")
    copyfile(feedback2_filename, feedback2_filename_uploaded)
    copyfile(
        notebook2_filename,
        os.path.join(submitted_directory, student_id, assignment_id,
                     "feedback2.ipynb"),
    )
    # ...... don't forget the timestamp for the submission
    with open(
            os.path.join(feedback_directory, student_id, assignment_id,
                         "timestamp.txt"),
            "w",
    ) as fp:
        fp.write("2020-01-01 00:01:00.0 UTC")

    # this makes the unique key & checksums for the submission
    unique_key1 = make_unique_key("no_course", assignment_id, "feedback1",
                                  student_id, "2020-01-01 00:01:00.0 UTC")
    checksum1 = notebook_hash(
        os.path.join(submitted_directory, student_id, assignment_id,
                     "feedback1.ipynb"),
        unique_key1,
    )
    unique_key2 = make_unique_key("no_course", assignment_id, "feedback2",
                                  student_id, "2020-01-01 00:01:00.0 UTC")
    checksum2 = notebook_hash(
        os.path.join(submitted_directory, student_id, assignment_id,
                     "feedback2.ipynb"),
        unique_key2,
    )

    plugin = ExchangeReleaseFeedback(
        coursedir=CourseDirectory(config=plugin_config), config=plugin_config)
    seen_feedback1 = False
    seen_feedback2 = False

    def api_request(*args, **kwargs):
        nonlocal seen_feedback1, seen_feedback2
        if "feedback1" in args[0]:
            assert seen_feedback1 is False
            seen_feedback1 = True
            assert args[0] == ("feedback?course_id=no_course"
                               f"&assignment_id={assignment_id}"
                               "&notebook=feedback1"
                               f"&student={student_id}"
                               "&timestamp=2020-01-01+00%3A01%3A00.000000+UTC"
                               "&checksum=" + checksum1)
            assert kwargs.get("method").lower() == "post"
            assert "feedback" in kwargs.get("files")
            assert (
                "feedback.html",
                open(feedback1_filename_uploaded).read(),
            ) == kwargs.get("files").get("feedback")

        elif "feedback2" in args[0]:
            assert seen_feedback2 is False
            seen_feedback2 = True
            assert args[0] == ("feedback?course_id=no_course"
                               f"&assignment_id={assignment_id}"
                               "&notebook=feedback2"
                               f"&student={student_id}"
                               "&timestamp=2020-01-01+00%3A01%3A00.000000+UTC"
                               "&checksum=" + checksum2)
            assert kwargs.get("method").lower() == "post"
            assert "feedback" in kwargs.get("files")
            assert (
                "feedback.html",
                open(feedback2_filename_uploaded).read(),
            ) == kwargs.get("files").get("feedback")
        else:
            assert False
        return type(
            "Request",
            (object, ),
            {
                "status_code": 200,
                "json": (lambda: {
                    "success": True
                })
            },
        )

    with patch.object(Exchange, "api_request", side_effect=api_request):
        plugin.start()
        assert seen_feedback1 and seen_feedback2