Пример #1
0
    def __init__(
            self,
            repo,  # type: Repo_ish
            course,  # type: Course
            flow_id,  # type: Text
            page_ordinal,  # type: int
            participation,  # type: Optional[Participation]
            flow_session,  # type: FlowSession
            request=None,  # type: Optional[http.HttpRequest]
    ):
        # type: (...) -> None
        super(FlowPageContext, self).__init__(repo, course, flow_id,
                                              participation)

        if page_ordinal >= flow_session.page_count:
            raise PageOrdinalOutOfRange()

        from course.models import FlowPageData  # noqa
        page_data = self.page_data = get_object_or_404(
            FlowPageData, flow_session=flow_session, page_ordinal=page_ordinal)

        from course.content import get_flow_page_desc
        try:
            self.page_desc = get_flow_page_desc(
                flow_session.flow_id, self.flow_desc, page_data.group_id,
                page_data.page_id)  # type: Optional[FlowPageDesc]
        except ObjectDoesNotExist:
            self.page_desc = None
            self.page = None  # type: Optional[PageBase]
            self.page_context = None  # type: Optional[PageContext]
        else:
            self.page = instantiate_flow_page_with_ctx(self, page_data)

            page_uri = None
            if request is not None:
                from django.urls import reverse
                page_uri = request.build_absolute_uri(
                    reverse("relate-view_flow_page",
                            args=(course.identifier, flow_session.id,
                                  page_ordinal)))

            self.page_context = PageContext(course=self.course,
                                            repo=self.repo,
                                            commit_sha=self.course_commit_sha,
                                            flow_session=flow_session,
                                            page_uri=page_uri)

        self._prev_answer_visit = False
Пример #2
0
def get_flow_session_content(api_ctx, course_identifier):
    # type: (APIContext, Text) -> http.HttpResponse

    if not api_ctx.has_permission(pperm.view_gradebook):
        raise PermissionDenied("token role does not have required permissions")

    try:
        session_id_str = api_ctx.request.GET["flow_session_id"]
    except KeyError:
        raise APIError("must specify flow_id GET parameter")

    session_id = int(session_id_str)

    flow_session = get_object_or_404(FlowSession, id=session_id)

    if flow_session.course != api_ctx.course:
        raise PermissionDenied(
                "session's course does not match auth context")

    from course.content import get_course_repo
    from course.flow import adjust_flow_session_page_data, assemble_answer_visits

    with get_course_repo(api_ctx.course) as repo:
        from course.utils import FlowContext, instantiate_flow_page_with_ctx
        fctx = FlowContext(repo, api_ctx.course, flow_session.flow_id)

        adjust_flow_session_page_data(repo, flow_session, api_ctx.course.identifier,
                fctx.flow_desc)

        from course.flow import get_all_page_data
        all_page_data = get_all_page_data(flow_session)
        answer_visits = assemble_answer_visits(flow_session)

        pages = []
        for i, page_data in enumerate(all_page_data):
            page = instantiate_flow_page_with_ctx(fctx, page_data)

            assert i == page_data.page_ordinal

            page_data_json = dict(
                    ordinal=i,
                    page_type=page_data.page_type,
                    group_id=page_data.group_id,
                    page_id=page_data.page_id,
                    page_data=page_data.data,
                    title=page_data.title,
                    bookmarked=page_data.bookmarked,
                    )
            answer_json = None
            grade_json = None

            visit = answer_visits[i]
            if visit is not None:
                from course.page.base import PageContext
                pctx = PageContext(api_ctx.course, repo, fctx.course_commit_sha,
                        flow_session)
                norm_bytes_answer_tup = page.normalized_bytes_answer(
                        pctx, page_data.data, visit.answer)

                # norm_answer needs to be JSON-encodable
                norm_answer = None  # type: Any

                if norm_bytes_answer_tup is not None:
                    answer_file_ext, norm_bytes_answer = norm_bytes_answer_tup

                    if answer_file_ext in [".txt", ".py"]:
                        norm_answer = norm_bytes_answer.decode("utf-8")
                    elif answer_file_ext == ".json":
                        import json
                        norm_answer = json.loads(norm_bytes_answer)
                    else:
                        from base64 import b64encode
                        norm_answer = [answer_file_ext,
                                       b64encode(norm_bytes_answer).decode("utf-8")]

                answer_json = dict(
                    visit_time=visit.visit_time.isoformat(),
                    remote_address=repr(visit.remote_address),
                    user=visit.user.username if visit.user is not None else None,
                    impersonated_by=(visit.impersonated_by.username
                        if visit.impersonated_by is not None else None),
                    is_synthetic_visit=visit.is_synthetic,
                    answer_data=visit.answer,
                    answer=norm_answer,
                    )

                grade = visit.get_most_recent_grade()
                if grade is not None:
                    grade_json = dict(
                        grader=(grade.grader.username
                                if grade.grader is not None else None),
                        grade_time=grade.grade_time.isoformat(),
                        graded_at_git_commit_sha=grade.graded_at_git_commit_sha,
                        max_points=grade.max_points,
                        correctness=grade.correctness,
                        feedback=grade.feedback)

            pages.append({
                "page": page_data_json,
                "answer": answer_json,
                "grade": grade_json,
                })

    result = {
        "session": flow_session_to_json(flow_session),
        "pages": pages,
        }

    return http.JsonResponse(result, safe=False)
Пример #3
0
def _adjust_flow_session_page_data_inner(repo, flow_session, course_identifier,
                                         flow_desc):
    commit_sha = get_course_commit_sha(flow_session.course,
                                       flow_session.participation)
    revision_key = "2:" + commit_sha.decode()

    if flow_session.page_data_at_revision_key == revision_key:
        return

    from course.page.base import PageContext
    pctx = PageContext(course=flow_session.course,
                       repo=repo,
                       commit_sha=commit_sha,
                       flow_session=flow_session,
                       in_sandbox=False,
                       page_uri=None)

    from course.models import FlowPageData

    def remove_page(fpd):
        if fpd.ordinal is not None:
            fpd.ordinal = None
            fpd.save()

    desc_group_ids = []

    ordinal = [0]
    for grp in flow_desc.groups:
        desc_group_ids.append(grp.id)

        shuffle = getattr(grp, "shuffle", False)
        max_page_count = getattr(grp, "max_page_count", None)

        available_page_ids = [page_desc.id for page_desc in grp.pages]

        if max_page_count is None:
            max_page_count = len(available_page_ids)

        group_pages = []

        # {{{ helper functions

        def find_page_desc(page_id):
            new_page_desc = None

            for page_desc in grp.pages:
                if page_desc.id == page_id:
                    new_page_desc = page_desc
                    break

            assert new_page_desc is not None

            return new_page_desc

        def instantiate_page(page_desc):
            return instantiate_flow_page(
                "course '%s', flow '%s', page '%s/%s'" %
                (course_identifier, flow_session.flow_id, grp.id,
                 page_desc.id), repo, page_desc, commit_sha)

        def create_fpd(new_page_desc):
            page = instantiate_page(new_page_desc)

            data = page.make_page_data()
            return FlowPageData(flow_session=flow_session,
                                ordinal=None,
                                page_type=new_page_desc.type,
                                group_id=grp.id,
                                page_id=new_page_desc.id,
                                data=data,
                                title=page.title(pctx, data))

        def add_page(fpd):
            if fpd.ordinal != ordinal[0]:
                fpd.ordinal = ordinal[0]
                fpd.save()

            page_desc = find_page_desc(fpd.page_id)
            page = instantiate_page(page_desc)
            title = page.title(pctx, fpd.data)

            if fpd.title != title:
                fpd.title = title
                fpd.save()

            ordinal[0] += 1
            available_page_ids.remove(fpd.page_id)

            group_pages.append(fpd)

        # }}}

        if shuffle:
            # maintain order of existing pages as much as possible
            for fpd in (FlowPageData.objects.filter(
                    flow_session=flow_session,
                    group_id=grp.id,
                    ordinal__isnull=False).order_by("ordinal")):

                if (fpd.page_id in available_page_ids
                        and len(group_pages) < max_page_count):
                    add_page(fpd)
                else:
                    remove_page(fpd)

            assert len(group_pages) <= max_page_count

            from random import choice

            # then add randomly chosen new pages
            while len(group_pages) < max_page_count and available_page_ids:
                new_page_id = choice(available_page_ids)

                new_page_fpds = (FlowPageData.objects.filter(
                    flow_session=flow_session,
                    group_id=grp.id,
                    page_id=new_page_id))

                if new_page_fpds.count():
                    # We already have FlowPageData for this page, revive it
                    new_page_fpd, = new_page_fpds
                    assert new_page_fpd.id == new_page_id
                else:
                    # Make a new FlowPageData instance
                    page_desc = find_page_desc(new_page_id)
                    assert page_desc.id == new_page_id
                    new_page_fpd = create_fpd(page_desc)
                    assert new_page_fpd.page_id == new_page_id

                add_page(new_page_fpd)

        else:
            # reorder pages to order in flow
            id_to_fpd = dict(((fpd.group_id, fpd.page_id), fpd)
                             for fpd in FlowPageData.objects.filter(
                                 flow_session=flow_session, group_id=grp.id))

            for page_desc in grp.pages:
                key = (grp.id, page_desc.id)

                if key in id_to_fpd:
                    fpd = id_to_fpd.pop(key)
                else:
                    fpd = create_fpd(page_desc)

                if len(group_pages) < max_page_count:
                    add_page(fpd)

            for fpd in id_to_fpd.values():
                remove_page(fpd)

    # {{{ remove pages orphaned because of group renames

    for fpd in (FlowPageData.objects.filter(
            flow_session=flow_session,
            ordinal__isnull=False).exclude(group_id__in=desc_group_ids)):
        remove_page(fpd)

    # }}}

    flow_session.page_count = ordinal[0]
    flow_session.page_data_at_revision_key = revision_key
    flow_session.save()
Пример #4
0
def convert_flow_page_visit(stderr, fpv):
    course = fpv.flow_session.course

    from course.content import (get_course_repo, get_flow_desc,
                                get_flow_page_desc, instantiate_flow_page)
    repo = get_course_repo(course)
    flow_id = fpv.flow_session.flow_id
    commit_sha = course.active_git_commit_sha.encode()
    try:
        flow_desc = get_flow_desc(repo,
                                  course,
                                  flow_id,
                                  commit_sha,
                                  tolerate_tabs=True)
    except ObjectDoesNotExist:
        stderr.write("warning: no flow yaml file found for '%s' in '%s'" %
                     (flow_id, course.identifier))
        return

    try:
        page_desc = get_flow_page_desc(fpv.flow_session.flow_id, flow_desc,
                                       fpv.page_data.group_id,
                                       fpv.page_data.page_id)
    except ObjectDoesNotExist:
        stderr.write(
            f"warning: flow page visit {fpv.id}: no page yaml desc "
            "found for "
            f"'{flow_id}:{fpv.page_data.group_id}/{fpv.page_data.page_id}' "
            f"in '{course.identifier}'")
        return

    page = instantiate_flow_page(
        location="flow '%s', group, '%s', page '%s'" %
        (flow_id, fpv.page_data.group_id, fpv.page_data.page_id),
        repo=repo,
        page_desc=page_desc,
        commit_sha=commit_sha)

    from course.page.base import PageContext
    pctx = PageContext(course=course,
                       repo=repo,
                       commit_sha=commit_sha,
                       flow_session=fpv.flow_session,
                       page_uri=None)

    from course.page.upload import FileUploadQuestion
    from course.page.code import CodeQuestion

    if isinstance(page, FileUploadQuestion):
        content, mime_type = page.get_content_from_answer_data(fpv.answer)

        from django.core.files.base import ContentFile
        answer_data = page.file_to_answer_data(pctx, ContentFile(content),
                                               mime_type)
        fpv.answer = answer_data
        fpv.save()

        return True

    elif isinstance(page, CodeQuestion):
        code = page.get_code_from_answer_data(fpv.answer)
        answer_data = page.code_to_answer_data(pctx, code)
        fpv.answer = answer_data
        fpv.save()

        return True
    else:
        return False

    assert False