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
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, })
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, })
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)
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, })