Example #1
0
def start_flow(repo, course, participation, flow_id, flow_desc, access_rules_tag, now_datetime):
    from course.content import get_course_commit_sha

    course_commit_sha = get_course_commit_sha(course, participation)

    session = FlowSession(
        course=course,
        participation=participation,
        active_git_commit_sha=course_commit_sha.decode(),
        flow_id=flow_id,
        in_progress=True,
        expiration_mode=flow_session_expiration_mode.end,
        access_rules_tag=access_rules_tag,
    )

    session.save()

    # Create flow grading opportunity. This makes the flow
    # show up in the grade book.

    from course.utils import get_flow_rules
    from course.models import get_flow_grading_opportunity

    for grading_rule in get_flow_rules(
        flow_desc, flow_rule_kind.grading, participation, flow_id, now_datetime, consider_exceptions=False
    ):
        identifier = getattr(grading_rule, "grade_identifier", None)
        if identifier is not None:
            get_flow_grading_opportunity(
                course,
                flow_id,
                flow_desc,
                FlowSessionGradingRule(
                    grade_identifier=identifier,
                    grade_aggregation_strategy=getattr(grading_rule, "grade_aggregation_strategy"),
                ),
            )

    # will implicitly modify and save the session if there are changes
    from course.content import adjust_flow_session_page_data

    adjust_flow_session_page_data(repo, session, course.identifier, flow_desc, course_commit_sha)

    return session
Example #2
0
def view_page_sandbox(pctx):
    # type: (CoursePageContext) -> http.HttpResponse

    if not pctx.has_permission(pperm.use_page_sandbox):
        raise PermissionDenied()

    from course.validation import ValidationError
    from relate.utils import dict_to_struct, Struct
    import yaml

    PAGE_SESSION_KEY = make_sandbox_session_key(  # noqa
        PAGE_SESSION_KEY_PREFIX, pctx.course.identifier)
    ANSWER_DATA_SESSION_KEY = make_sandbox_session_key(  # noqa
        ANSWER_DATA_SESSION_KEY_PREFIX, pctx.course.identifier)
    PAGE_DATA_SESSION_KEY = make_sandbox_session_key(  # noqa
        PAGE_DATA_SESSION_KEY_PREFIX, pctx.course.identifier)

    request = pctx.request
    page_source = pctx.request.session.get(PAGE_SESSION_KEY)

    page_errors = None
    page_warnings = None

    is_clear_post = (request.method == "POST" and "clear" in request.POST)
    is_clear_response_post = (request.method == "POST"
                              and "clear_response" in request.POST)
    is_preview_post = (request.method == "POST" and "preview" in request.POST)

    def make_form(data=None):
        # type: (Optional[Text]) -> PageSandboxForm
        return PageSandboxForm(page_source, "yaml", request.user.editor_mode,
                               ugettext("Enter YAML markup for a flow page."),
                               data)

    if is_preview_post:
        edit_form = make_form(pctx.request.POST)
        new_page_source = None

        if edit_form.is_valid():
            try:
                from pytools.py_codegen import remove_common_indentation
                new_page_source = remove_common_indentation(
                    edit_form.cleaned_data["content"],
                    require_leading_newline=False)
                from course.content import expand_yaml_macros
                new_page_source = expand_yaml_macros(pctx.repo,
                                                     pctx.course_commit_sha,
                                                     new_page_source)

                yaml_data = yaml.load(new_page_source)  # type: ignore
                page_desc = dict_to_struct(yaml_data)

                if not isinstance(page_desc, Struct):
                    raise ValidationError(
                        "Provided page source code is not "
                        "a dictionary. Do you need to remove a leading "
                        "list marker ('-') or some stray indentation?")

                from course.validation import validate_flow_page, ValidationContext
                vctx = ValidationContext(repo=pctx.repo,
                                         commit_sha=pctx.course_commit_sha)

                validate_flow_page(vctx, "sandbox", page_desc)

                page_warnings = vctx.warnings

            except Exception:
                import sys
                tp, e, _ = sys.exc_info()

                page_errors = (ugettext("Page failed to load/validate") +
                               ": " + "%(err_type)s: %(err_str)s" % {
                                   "err_type": tp.__name__,
                                   "err_str": e
                               })  # type: ignore

            else:
                # Yay, it did validate.
                request.session[
                    PAGE_SESSION_KEY] = page_source = new_page_source

            del new_page_source

        edit_form = make_form(pctx.request.POST)

    elif is_clear_post:
        page_source = None
        pctx.request.session[PAGE_DATA_SESSION_KEY] = None
        pctx.request.session[ANSWER_DATA_SESSION_KEY] = None
        del pctx.request.session[PAGE_DATA_SESSION_KEY]
        del pctx.request.session[ANSWER_DATA_SESSION_KEY]
        edit_form = make_form()

    elif is_clear_response_post:
        page_source = None
        pctx.request.session[PAGE_DATA_SESSION_KEY] = None
        pctx.request.session[ANSWER_DATA_SESSION_KEY] = None
        del pctx.request.session[PAGE_DATA_SESSION_KEY]
        del pctx.request.session[ANSWER_DATA_SESSION_KEY]
        edit_form = make_form(pctx.request.POST)

    else:
        edit_form = make_form()

    have_valid_page = page_source is not None
    if have_valid_page:
        yaml_data = yaml.load(page_source)  # type: ignore
        page_desc = cast(FlowPageDesc, dict_to_struct(yaml_data))

        from course.content import instantiate_flow_page
        try:
            page = instantiate_flow_page("sandbox", pctx.repo, page_desc,
                                         pctx.course_commit_sha)
        except Exception:
            import sys
            tp, e, _ = sys.exc_info()

            page_errors = (ugettext("Page failed to load/validate") + ": " +
                           "%(err_type)s: %(err_str)s" % {
                               "err_type": tp.__name__,
                               "err_str": e
                           })  # type: ignore
            have_valid_page = False

    if have_valid_page:
        page_desc = cast(FlowPageDesc, page_desc)

        # Try to recover page_data, answer_data
        page_data = get_sandbox_data_for_page(pctx, page_desc,
                                              PAGE_DATA_SESSION_KEY)

        answer_data = get_sandbox_data_for_page(pctx, page_desc,
                                                ANSWER_DATA_SESSION_KEY)

        from course.models import FlowSession
        from course.page import PageContext
        page_context = PageContext(
            course=pctx.course,
            repo=pctx.repo,
            commit_sha=pctx.course_commit_sha,

            # This helps code pages retrieve the editor pref.
            flow_session=FlowSession(course=pctx.course,
                                     participation=pctx.participation),
            in_sandbox=True)

        if page_data is None:
            page_data = page.initialize_page_data(page_context)
            pctx.request.session[PAGE_DATA_SESSION_KEY] = (page_desc.type,
                                                           page_desc.id,
                                                           page_data)

        title = page.title(page_context, page_data)
        body = page.body(page_context, page_data)

        feedback = None
        page_form_html = None

        if page.expects_answer():
            from course.page.base import PageBehavior
            page_behavior = PageBehavior(show_correctness=True,
                                         show_answer=True,
                                         may_change_answer=True)

            if request.method == "POST" and not is_preview_post:
                page_form = page.process_form_post(page_context, page_data,
                                                   request.POST, request.FILES,
                                                   page_behavior)

                if page_form.is_valid():

                    answer_data = page.answer_data(page_context, page_data,
                                                   page_form, request.FILES)

                    feedback = page.grade(page_context,
                                          page_data,
                                          answer_data,
                                          grade_data=None)

                    pctx.request.session[ANSWER_DATA_SESSION_KEY] = (
                        page_desc.type, page_desc.id, answer_data)

            else:
                try:
                    page_form = page.make_form(page_context, page_data,
                                               answer_data, page_behavior)

                except Exception:
                    import sys
                    tp, e, _ = sys.exc_info()

                    page_errors = (
                        ugettext("Page failed to load/validate "
                                 "(change page ID to clear faults)") + ": " +
                        "%(err_type)s: %(err_str)s" % {
                            "err_type": tp.__name__,
                            "err_str": e
                        })  # type: ignore  # noqa: E501
                    have_valid_page = False

                    page_form = None

            if page_form is not None:
                page_form.helper.add_input(
                    Submit("submit", ugettext("Submit answer"), accesskey="g"))
                page_form_html = page.form_to_html(pctx.request, page_context,
                                                   page_form, answer_data)

        correct_answer = page.correct_answer(page_context,
                                             page_data,
                                             answer_data,
                                             grade_data=None)

        return render_course_page(
            pctx,
            "course/sandbox-page.html",
            {
                "edit_form": edit_form,
                "page_errors": page_errors,
                "page_warnings": page_warnings,
                "form": edit_form,  # to placate form.media
                "have_valid_page": True,
                "title": title,
                "body": body,
                "page_form_html": page_form_html,
                "feedback": feedback,
                "correct_answer": correct_answer,
            })

    else:

        return render_course_page(
            pctx,
            "course/sandbox-page.html",
            {
                "edit_form": edit_form,
                "form": edit_form,  # to placate form.media
                "have_valid_page": False,
                "page_errors": page_errors,
                "page_warnings": page_warnings,
            })
Example #3
0
def view_page_sandbox(pctx):
    if pctx.role not in [
            participation_role.instructor,
            participation_role.teaching_assistant]:
        raise PermissionDenied(
                ugettext("must be instructor or TA to access sandbox"))

    from course.validation import ValidationError
    from relate.utils import dict_to_struct, Struct
    import yaml

    PAGE_SESSION_KEY = (  # noqa
            "cf_validated_sandbox_page:" + pctx.course.identifier)
    ANSWER_DATA_SESSION_KEY = (  # noqa
        "cf_page_sandbox_answer_data:" + pctx.course.identifier)

    request = pctx.request
    page_source = pctx.request.session.get(PAGE_SESSION_KEY)

    page_errors = None
    page_warnings = None

    is_preview_post = (request.method == "POST" and "preview" in request.POST)

    from course.models import get_user_status
    ustatus = get_user_status(request.user)

    def make_form(data=None):
        return SandboxForm(
                page_source, "yaml", ustatus.editor_mode,
                ugettext("Enter YAML markup for a flow page."),
                data)

    if is_preview_post:
        edit_form = make_form(pctx.request.POST)

        if edit_form.is_valid():
            try:
                new_page_source = edit_form.cleaned_data["content"]
                page_desc = dict_to_struct(yaml.load(new_page_source))

                if not isinstance(page_desc, Struct):
                    raise ValidationError("Provided page source code is not "
                            "a dictionary. Do you need to remove a leading "
                            "list marker ('-') or some stray indentation?")

                from course.validation import validate_flow_page, ValidationContext
                vctx = ValidationContext(
                        repo=pctx.repo,
                        commit_sha=pctx.course_commit_sha)

                validate_flow_page(vctx, "sandbox", page_desc)

                page_warnings = vctx.warnings

            except:
                import sys
                tp, e, _ = sys.exc_info()

                page_errors = (
                        ugettext("Page failed to load/validate")
                        + ": "
                        + "%(err_type)s: %(err_str)s" % {
                            "err_type": tp.__name__, "err_str": e})

            else:
                # Yay, it did validate.
                request.session[PAGE_SESSION_KEY] = page_source = new_page_source

            del new_page_source

        edit_form = make_form(pctx.request.POST)

    else:
        edit_form = make_form()

    have_valid_page = page_source is not None
    if have_valid_page:
        page_desc = dict_to_struct(yaml.load(page_source))

        from course.content import instantiate_flow_page
        try:
            page = instantiate_flow_page("sandbox", pctx.repo, page_desc,
                    pctx.course_commit_sha)
        except:
            import sys
            tp, e, _ = sys.exc_info()

            page_errors = (
                    ugettext("Page failed to load/validate")
                    + ": "
                    + "%(err_type)s: %(err_str)s" % {
                        "err_type": tp.__name__, "err_str": e})
            have_valid_page = False

    if have_valid_page:
        page_data = page.make_page_data()

        from course.models import FlowSession
        from course.page import PageContext
        page_context = PageContext(
                course=pctx.course,
                repo=pctx.repo,
                commit_sha=pctx.course_commit_sha,

                # This helps code pages retrieve the editor pref.
                flow_session=FlowSession(
                    course=pctx.course,
                    participation=pctx.participation),

                in_sandbox=True)

        title = page.title(page_context, page_data)
        body = page.body(page_context, page_data)

        # {{{ try to recover answer_data

        answer_data = None

        stored_answer_data_tuple = \
                pctx.request.session.get(ANSWER_DATA_SESSION_KEY)

        # Session storage uses JSON and may turn tuples into lists.
        if (isinstance(stored_answer_data_tuple, (list, tuple))
                and len(stored_answer_data_tuple) == 3):
            stored_answer_data_page_type, stored_answer_data_page_id, stored_answer_data = \
                    stored_answer_data_tuple

            if (
                    stored_answer_data_page_type == page_desc.type
                    and
                    stored_answer_data_page_id == page_desc.id):
                answer_data = stored_answer_data

        # }}}

        feedback = None
        page_form_html = None

        if page.expects_answer():
            from course.page.base import PageBehavior
            page_behavior = PageBehavior(
                    show_correctness=True,
                    show_answer=True,
                    may_change_answer=True)

            if request.method == "POST" and not is_preview_post:
                page_form = page.process_form_post(page_context, page_data,
                        request.POST, request.FILES,
                        page_behavior)

                if page_form.is_valid():

                    answer_data = page.answer_data(page_context, page_data,
                            page_form, request.FILES)

                    feedback = page.grade(page_context, page_data, answer_data,
                            grade_data=None)

                    pctx.request.session[ANSWER_DATA_SESSION_KEY] = (
                            page_desc.type, page_desc.id, answer_data)

            else:
                page_form = page.make_form(page_context, page_data,
                        answer_data, page_behavior)

            if page_form is not None:
                page_form.helper.add_input(
                        Submit("submit",
                            ugettext("Submit answer"),
                            accesskey="g"))
                page_form_html = page.form_to_html(
                        pctx.request, page_context, page_form, answer_data)

        correct_answer = page.correct_answer(
                page_context, page_data, answer_data,
                grade_data=None)

        return render_course_page(pctx, "course/sandbox-page.html", {
            "edit_form": edit_form,
            "page_errors": page_errors,
            "page_warnings": page_warnings,
            "form": edit_form,  # to placate form.media
            "have_valid_page": True,
            "title": title,
            "body": body,
            "page_form_html": page_form_html,
            "feedback": feedback,
            "correct_answer": correct_answer,
        })

    else:

        return render_course_page(pctx, "course/sandbox-page.html", {
            "edit_form": edit_form,
            "form": edit_form,  # to placate form.media
            "have_valid_page": False,
            "page_errors": page_errors,
            "page_warnings": page_warnings,
        })
Example #4
0
def start_flow(pctx, flow_identifier):
    request = pctx.request

    now_datetime = get_now_or_fake_time(request)
    fctx = FlowContext(pctx.repo, pctx.course, flow_identifier,
            participation=pctx.participation)

    current_access_rule = fctx.get_current_access_rule(
            None, pctx.role, pctx.participation, now_datetime)

    may_view = flow_permission.view in current_access_rule.permissions

    have_in_progress_session = (FlowSession.objects
            .filter(
                participation=pctx.participation,
                flow_id=fctx.flow_identifier,
                in_progress=True,
                participation__isnull=False,
                )).count() > 0
    past_sessions = (FlowSession.objects
            .filter(
                participation=pctx.participation,
                flow_id=fctx.flow_identifier,
                participation__isnull=False)
           .order_by("start_time"))
    past_session_count = past_sessions.count()

    if current_access_rule.allowed_session_count is not None:
        allowed_another_session = (
                past_session_count < current_access_rule.allowed_session_count)
    else:
        allowed_another_session = True

    if request.method == "POST":
        from course.content import set_up_flow_session_page_data

        resume_match = None
        for post_key in request.POST:
            resume_match = RESUME_RE.match(post_key)
            if resume_match is not None:
                break

        if resume_match is not None:
            resume_session_id = int(resume_match.group(1))

            resume_session = get_object_or_404(FlowSession, pk=resume_session_id)

            if resume_session.participation != pctx.participation:
                raise PermissionDenied("not your session")

            if not may_view:
                raise PermissionDenied("may not resume session without "
                        "'view' permission")

            if resume_session.participation is None:
                raise PermissionDenied("can't resume anonymous session")

            if resume_session.flow_id != fctx.flow_identifier:
                raise SuspiciousOperation("flow id mismatch on resume")

            if not (flow_permission.view_past in current_access_rule.permissions
                    or resume_session.in_progress):
                raise PermissionDenied("not allowed to resume session")

            request.session["flow_session_id"] = resume_session_id

            return redirect("course.flow.view_flow_page",
                    pctx.course.identifier,
                    flow_identifier,
                    0)

        elif ("start_no_credit" in request.POST
                or "start_credit" in request.POST):

            if not may_view:
                raise PermissionDenied("may not start session without "
                        "'view' permission")

            if not allowed_another_session:
                raise PermissionDenied("new session would exceed "
                        "allowed session count limit exceed")

            if have_in_progress_session:
                raise PermissionDenied("cannot start flow when other flow "
                        "session is already in progress")

            session = FlowSession()
            session.course = fctx.course
            session.participation = pctx.participation
            session.active_git_commit_sha = fctx.flow_commit_sha.decode()
            session.flow_id = flow_identifier
            session.in_progress = True
            session.for_credit = "start_credit" in request.POST
            session.access_rules_id = current_access_rule.id
            session.save()

            request.session["flow_session_id"] = session.id

            page_count = set_up_flow_session_page_data(fctx.repo, session,
                    pctx.course.identifier, fctx.flow_desc, fctx.flow_commit_sha)
            session.page_count = page_count
            session.save()

            return redirect("course.flow.view_flow_page",
                    pctx.course.identifier,
                    flow_identifier,
                    0)

        else:
            raise SuspiciousOperation("unrecognized POST action")

    else:
        may_start_credit = (
                may_view
                and not have_in_progress_session
                and allowed_another_session
                and flow_permission.start_credit in current_access_rule.permissions)
        may_start_no_credit = (
                may_view
                and not have_in_progress_session
                and allowed_another_session
                and (flow_permission.start_no_credit
                    in current_access_rule.permissions))
        may_review = (
                may_view
                and flow_permission.view_past in current_access_rule.permissions)

        if hasattr(fctx.flow_desc, "grade_aggregation_strategy"):
            from course.models import GRADE_AGGREGATION_STRATEGY_CHOICES
            grade_aggregation_strategy_text = (
                    dict(GRADE_AGGREGATION_STRATEGY_CHOICES)
                    [fctx.flow_desc.grade_aggregation_strategy])
        else:
            grade_aggregation_strategy_text = None

        # {{{ fish out relevant rules

        from course.utils import (
                get_flow_access_rules,
                get_relevant_rules)
        rules = get_flow_access_rules(fctx.course, pctx.participation,
                flow_identifier, fctx.flow_desc)

        # }}}

        return render_course_page(pctx, "course/flow-start.html", {
            "flow_desc": fctx.flow_desc,
            "grade_aggregation_strategy":
            grade_aggregation_strategy_text,
            "flow_identifier": flow_identifier,

            "rules": get_relevant_rules(rules, pctx.role, now_datetime),
            "now": now_datetime,

            "may_start_credit": may_start_credit,
            "may_start_no_credit": may_start_no_credit,
            "may_review": may_review,

            "past_sessions": past_sessions,
            },
            allow_instant_flow_requests=False)
Example #5
0
def start_flow(request, course_identifier, flow_identifier):
    fctx = FlowContext(request, course_identifier, flow_identifier)

    from course.models import flow_permission
    if flow_permission.view not in fctx.permissions:
        raise PermissionDenied()

    have_in_progress_session = (FlowSession.objects
            .filter(
                participation=fctx.participation,
                flow_id=fctx.flow_identifier,
                in_progress=True,
                participation__isnull=False,
                )).count() > 0
    past_sessions = (FlowSession.objects
            .filter(
                participation=fctx.participation,
                flow_id=fctx.flow_identifier,
                participation__isnull=False)
           .order_by("start_time"))
    past_session_count = past_sessions.count()

    if hasattr(fctx.stipulations, "allowed_session_count"):
        allowed_another_session = (
                past_session_count < fctx.stipulations.allowed_session_count)
    else:
        allowed_another_session = True

    if request.method == "POST":
        from course.content import set_up_flow_session_page_data

        resume_match = None
        for post_key in request.POST:
            resume_match = RESUME_RE.match(post_key)
            if resume_match is not None:
                break

        if resume_match is not None:
            resume_session_id = int(resume_match.group(1))

            resume_session = get_object_or_404(FlowSession, pk=resume_session_id)

            if resume_session.participation != fctx.participation:
                raise PermissionDenied("not your session")

            if resume_session.participation is None:
                raise PermissionDenied("can't resume anonymous session")

            if resume_session.flow_id != fctx.flow_identifier:
                raise SuspiciousOperation("flow id mismatch on resume")

            if not (flow_permission.view_past in fctx.permissions
                    or resume_session.in_progress):
                raise PermissionDenied("not allowed to resume session")

            request.session["flow_session_id"] = resume_session_id

            return redirect("course.flow.view_flow_page",
                    course_identifier,
                    flow_identifier,
                    0)

        elif ("start_no_credit" in request.POST
                or "start_credit" in request.POST):

            if not allowed_another_session:
                raise PermissionDenied("new session would exceed "
                        "allowed session count limit exceed")

            if have_in_progress_session:
                raise PermissionDenied("cannot start flow when other flow "
                        "is already in progress")

            session = FlowSession()
            session.course = fctx.course
            session.participation = fctx.participation
            session.active_git_commit_sha = fctx.flow_commit_sha.decode()
            session.flow_id = flow_identifier
            session.in_progress = True
            session.for_credit = "start_credit" in request.POST
            session.save()

            request.session["flow_session_id"] = session.id

            page_count = set_up_flow_session_page_data(fctx.repo, session,
                    course_identifier, fctx.flow_desc, fctx.flow_commit_sha)
            session.page_count = page_count
            session.save()

            return redirect("course.flow.view_flow_page",
                    course_identifier,
                    flow_identifier,
                    0)

        else:
            raise SuspiciousOperation("unrecognized POST action")

    else:
        may_start_credit = (
                not have_in_progress_session
                and allowed_another_session
                and flow_permission.start_credit in fctx.permissions)
        may_start_no_credit = (
                not have_in_progress_session
                and allowed_another_session
                and flow_permission.start_no_credit in fctx.permissions)
        may_review = (
                flow_permission.view_past in fctx.permissions)

        if hasattr(fctx.flow_desc, "grade_aggregation_strategy"):
            from course.models import GRADE_AGGREGATION_STRATEGY_CHOICES
            grade_aggregation_strategy_text = (
                    dict(GRADE_AGGREGATION_STRATEGY_CHOICES)
                    [fctx.flow_desc.grade_aggregation_strategy])
        else:
            grade_aggregation_strategy_text = None

        return render(request, "course/flow-start.html", {
            "participation": fctx.participation,
            "course_desc": fctx.course_desc,
            "course": fctx.course,
            "flow_desc": fctx.flow_desc,
            "role": fctx.role,
            "participation_role": participation_role,
            "grade_aggregation_strategy":
            grade_aggregation_strategy_text,
            "flow_identifier": flow_identifier,

            "may_start_credit": may_start_credit,
            "may_start_no_credit": may_start_no_credit,
            "may_review": may_review,

            "past_sessions": past_sessions,
            "stipulations": fctx.stipulations,
            })