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
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)
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()
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