def __init__(self, request, course_identifier): # type: (http.HttpRequest, Text) -> None self.request = request self.course_identifier = course_identifier self._permissions_cache = None # type: Optional[FrozenSet[Tuple[Text, Optional[Text]]]] # noqa self._role_identifiers_cache = None # type: Optional[List[Text]] self.old_language = None # using this to prevent nested using as context manager self._is_in_context_manager = False from course.models import Course # noqa self.course = get_object_or_404(Course, identifier=course_identifier) from course.enrollment import get_participation_for_request self.participation = get_participation_for_request( request, self.course) from course.views import check_course_state check_course_state(self.course, self.participation) self.course_commit_sha = get_course_commit_sha(self.course, self.participation) self.repo = get_course_repo(self.course) # logic duplicated in course.content.get_course_commit_sha sha = self.course.active_git_commit_sha.encode() if self.participation is not None: if self.participation.preview_git_commit_sha: preview_sha = self.participation.preview_git_commit_sha.encode( ) with get_course_repo(self.course) as repo: from relate.utils import SubdirRepoWrapper if isinstance(repo, SubdirRepoWrapper): true_repo = repo.repo else: true_repo = cast(dulwich.repo.Repo, repo) try: true_repo[preview_sha] except KeyError: from django.contrib import messages messages.add_message( request, messages.ERROR, _("Preview revision '%s' does not exist--" "showing active course content instead.") % preview_sha.decode()) preview_sha = None finally: true_repo.close() if preview_sha is not None: sha = preview_sha self.course_commit_sha = sha
def __init__(self, request, course_identifier): # type: (http.HttpRequest, Text) -> None self.request = request self.course_identifier = course_identifier self._permissions_cache = None # type: Optional[FrozenSet[Tuple[Text, Optional[Text]]]] # noqa self._role_identifiers_cache = None # type: Optional[List[Text]] self.old_language = None # using this to prevent nested using as context manager self._is_in_context_manager = False from course.models import Course # noqa self.course = get_object_or_404(Course, identifier=course_identifier) from course.enrollment import get_participation_for_request self.participation = get_participation_for_request( request, self.course) from course.views import check_course_state check_course_state(self.course, self.participation) self.course_commit_sha = get_course_commit_sha( self.course, self.participation) self.repo = get_course_repo(self.course) # logic duplicated in course.content.get_course_commit_sha sha = self.course.active_git_commit_sha.encode() if self.participation is not None: if self.participation.preview_git_commit_sha: preview_sha = self.participation.preview_git_commit_sha.encode() with get_course_repo(self.course) as repo: from relate.utils import SubdirRepoWrapper if isinstance(repo, SubdirRepoWrapper): true_repo = repo.repo else: true_repo = cast(dulwich.repo.Repo, repo) try: true_repo[preview_sha] except KeyError: from django.contrib import messages messages.add_message(request, messages.ERROR, _("Preview revision '%s' does not exist--" "showing active course content instead.") % preview_sha.decode()) preview_sha = None finally: true_repo.close() if preview_sha is not None: sha = preview_sha self.course_commit_sha = sha
def get_current_repo_file(request, course_identifier, path): course = get_object_or_404(Course, identifier=course_identifier) role, participation = get_role_and_participation( request, course) from course.views import check_course_state check_course_state(course, role) from course.content import get_course_commit_sha commit_sha = get_course_commit_sha(course, participation) role, participation = get_role_and_participation(request, course) repo = get_course_repo(course) from course.content import is_repo_file_public if not is_repo_file_public(repo, commit_sha, path): raise PermissionDenied() from course.content import get_repo_blob_data_cached try: data = get_repo_blob_data_cached( repo, path, commit_sha.encode()) except ObjectDoesNotExist: raise http.Http404() from mimetypes import guess_type content_type, _ = guess_type(path) return http.HttpResponse(data, content_type=content_type)
def finish_in_progress_sessions(self, course_id, flow_id, rule_tag, now_datetime, past_due_only): course = Course.objects.get(id=course_id) repo = get_course_repo(course) sessions = (FlowSession.objects .filter( course=course, flow_id=flow_id, participation__isnull=False, access_rules_tag=rule_tag, in_progress=True, )) count = 0 nsessions = sessions.count() from course.flow import finish_flow_session_standalone for i, session in enumerate(sessions): from course.flow import adjust_flow_session_page_data adjust_flow_session_page_data(repo, session, course.identifier, respect_preview=False) if finish_flow_session_standalone(repo, course, session, now_datetime=now_datetime, past_due_only=past_due_only): count += 1 self.update_state( state='PROGRESS', meta={'current': i, 'total': nsessions}) repo.close() return {"message": _("%d sessions ended.") % count}
def git_endpoint(api_ctx, course_identifier, git_path): # type: (APIContext, Text, Text) -> http.HttpResponse course = api_ctx.course request = api_ctx.request if not api_ctx.has_permission(pperm.use_git_endpoint): raise PermissionDenied("insufficient privileges") from course.content import get_course_repo repo = get_course_repo(course) from course.content import SubdirRepoWrapper if isinstance(repo, SubdirRepoWrapper): true_repo = repo.repo else: true_repo = cast(dulwich.repo.Repo, repo) base_path = reverse(git_endpoint, args=(course_identifier, "")) assert base_path.endswith("/") base_path = base_path[:-1] import dulwich.web as dweb backend = dweb.DictBackend({"/": true_repo}) app = dweb.make_wsgi_chain(backend) return call_wsgi_app(app, request, base_path)
def home(request): courses_and_descs_and_invalid_flags = [] for course in Course.objects.all(): repo = get_course_repo(course) desc = get_course_desc(repo, course, course.active_git_commit_sha.encode()) role, participation = get_role_and_participation(request, course) show = True if course.hidden: if role not in [participation_role.teaching_assistant, participation_role.instructor]: show = False if not course.valid: if role != participation_role.instructor: show = False if show: courses_and_descs_and_invalid_flags.append( (course, desc, not course.valid)) def course_sort_key(entry): course, desc, invalid_flag = entry return course.identifier courses_and_descs_and_invalid_flags.sort(key=course_sort_key) return render(request, "course/home.html", { "courses_and_descs_and_invalid_flags": courses_and_descs_and_invalid_flags })
def finish_in_progress_sessions(self, course_id, flow_id, rule_tag, now_datetime, past_due_only): course = Course.objects.get(id=course_id) repo = get_course_repo(course) sessions = (FlowSession.objects.filter( course=course, flow_id=flow_id, participation__isnull=False, access_rules_tag=rule_tag, in_progress=True, )) count = 0 nsessions = sessions.count() from course.flow import finish_flow_session_standalone for i, session in enumerate(sessions): if finish_flow_session_standalone(repo, course, session, now_datetime=now_datetime, past_due_only=past_due_only): count += 1 self.update_state(state='PROGRESS', meta={ 'current': i, 'total': nsessions }) repo.close() return {"message": _("%d sessions ended.") % count}
def get_media(request, course_identifier, commit_sha, media_path): course = get_object_or_404(Course, identifier=course_identifier) role, participation = get_role_and_participation(request, course) repo = get_course_repo(course) return get_repo_file_response(repo, "media/" + media_path, commit_sha)
def regrade_flow_sessions(self, course_id, flow_id, access_rules_tag, inprog_value): course = Course.objects.get(id=course_id) repo = get_course_repo(course) sessions = (FlowSession.objects.filter(course=course, participation__isnull=False, flow_id=flow_id)) if access_rules_tag: sessions = sessions.filter(access_rules_tag=access_rules_tag) if inprog_value is not None: sessions = sessions.filter(in_progress=inprog_value) nsessions = sessions.count() count = 0 from course.flow import regrade_session for session in sessions: regrade_session(repo, course, session) count += 1 self.update_state(state='PROGRESS', meta={ 'current': count, 'total': nsessions }) repo.close() return {"message": _("%d sessions regraded.") % count}
def get_media(request, course_identifier, commit_sha, media_path): course = get_object_or_404(Course, identifier=course_identifier) role, participation = get_role_and_participation(request, course) repo = get_course_repo(course) return get_repo_file_response(repo, "media/" + media_path, commit_sha.encode())
def get_repo_file_backend(request, course, role, participation, commit_sha, path): """ Check if a file should be accessible. Then call for it if the permission is not denied. Order is important here. An in-exam request takes precedence. Note: an access_role of "public" is equal to "unenrolled" """ # check to see if the course is hidden from course.views import check_course_state check_course_state(course, role) # retrieve local path for the repo for the course repo = get_course_repo(course) # set access to public (or unenrolled), student, etc access_kind = role if request.relate_exam_lockdown: access_kind = "in_exam" from course.content import is_repo_file_accessible_as if not is_repo_file_accessible_as(access_kind, repo, commit_sha, path): raise PermissionDenied() return get_repo_file_response(repo, path, commit_sha)
def home(request): courses_and_descs_and_invalid_flags = [] for course in Course.objects.filter(listed=True): repo = get_course_repo(course) desc = get_course_desc(repo, course, course.active_git_commit_sha.encode()) role, participation = get_role_and_participation(request, course) show = True if course.hidden: if role not in [ participation_role.teaching_assistant, participation_role.instructor ]: show = False if not course.valid: if role != participation_role.instructor: show = False if show: courses_and_descs_and_invalid_flags.append( (course, desc, not course.valid)) def course_sort_key(entry): course, desc, invalid_flag = entry return course.identifier courses_and_descs_and_invalid_flags.sort(key=course_sort_key) return render(request, "course/home.html", { "courses_and_descs_and_invalid_flags": courses_and_descs_and_invalid_flags })
def get_current_repo_file(request, course_identifier, path): course = get_object_or_404(Course, identifier=course_identifier) role, participation = get_role_and_participation(request, course) from course.views import check_course_state check_course_state(course, role) from course.content import get_course_commit_sha commit_sha = get_course_commit_sha(course, participation) role, participation = get_role_and_participation(request, course) repo = get_course_repo(course) from course.content import is_repo_file_public if not is_repo_file_public(repo, commit_sha, path): raise PermissionDenied() from course.content import get_repo_blob_data_cached try: data = get_repo_blob_data_cached(repo, path, commit_sha.encode()) except ObjectDoesNotExist: raise http.Http404() from mimetypes import guess_type content_type, _ = guess_type(path) return http.HttpResponse(data, content_type=content_type)
def recalculate_ended_sessions(self, course_id, flow_id, rule_tag): course = Course.objects.get(id=course_id) repo = get_course_repo(course) sessions = (FlowSession.objects.filter( course=course, flow_id=flow_id, participation__isnull=False, access_rules_tag=rule_tag, in_progress=False, )) nsessions = sessions.count() count = 0 from course.flow import recalculate_session_grade for session in sessions: recalculate_session_grade(repo, course, session) count += 1 self.update_state(state='PROGRESS', meta={ 'current': count, 'total': nsessions }) repo.close() return {"message": _("Grades recalculated for %d sessions.") % count}
def recalculate_ended_sessions(self, course_id, flow_id, rule_tag): course = Course.objects.get(id=course_id) repo = get_course_repo(course) sessions = (FlowSession.objects .filter( course=course, flow_id=flow_id, participation__isnull=False, access_rules_tag=rule_tag, in_progress=False, )) nsessions = sessions.count() count = 0 from course.flow import recalculate_session_grade for session in sessions: recalculate_session_grade(repo, course, session) count += 1 self.update_state( state='PROGRESS', meta={'current': count, 'total': nsessions}) repo.close() return {"message": _("Grades recalculated for %d sessions.") % count}
def regrade_flow_sessions(self, course_id, flow_id, access_rules_tag, inprog_value): course = Course.objects.get(id=course_id) repo = get_course_repo(course) sessions = (FlowSession.objects .filter( course=course, participation__isnull=False, flow_id=flow_id)) if access_rules_tag: sessions = sessions.filter(access_rules_tag=access_rules_tag) if inprog_value is not None: sessions = sessions.filter(in_progress=inprog_value) nsessions = sessions.count() count = 0 from course.flow import regrade_session for session in sessions: regrade_session(repo, course, session) count += 1 self.update_state( state='PROGRESS', meta={'current': count, 'total': nsessions}) repo.close() return {"message": _("%d sessions regraded.") % count}
def __init__(self, request, course_identifier): self.request = request self.course_identifier = course_identifier from course.models import Course self.course = get_object_or_404(Course, identifier=course_identifier) from course.views import get_role_and_participation self.role, self.participation = get_role_and_participation( request, self.course) from course.views import check_course_state check_course_state(self.course, self.role) self.course_commit_sha = get_course_commit_sha( self.course, self.participation) self.repo = get_course_repo(self.course) # logic duplicated in course.content.get_course_commit_sha sha = self.course.active_git_commit_sha.encode() if (self.participation is not None and self.participation.preview_git_commit_sha): preview_sha = self.participation.preview_git_commit_sha.encode() repo = get_course_repo(self.course) from course.content import SubdirRepoWrapper if isinstance(repo, SubdirRepoWrapper): repo = repo.repo try: repo[preview_sha] except KeyError: from django.contrib import messages messages.add_message(request, messages.ERROR, _("Preview revision '%s' does not exist--" "showing active course content instead.") % preview_sha.decode()) preview_sha = None if preview_sha is not None: sha = preview_sha self.course_commit_sha = sha
def set_course_metadata(apps, schema_editor): Course = apps.get_model("course", "Course") # noqa for course in Course.objects.all(): from course.content import get_course_repo, get_course_desc, get_course_commit_sha repo = get_course_repo(course) course_desc = get_course_desc(repo, course, get_course_commit_sha(course, participation=None)) course.name = course_desc.name course.number = course_desc.number course.time_period = course_desc.run repo.close() course.save()
def clean(self): super(FlowRuleException, self).clean() if (self.kind == flow_rule_kind.grading and self.expiration is not None): raise ValidationError(_("grading rules may not expire")) from course.validation import ( ValidationError as ContentValidationError, validate_session_start_rule, validate_session_access_rule, validate_session_grading_rule, ValidationContext) from course.content import (get_course_repo, get_course_commit_sha, get_flow_desc) from relate.utils import dict_to_struct rule = dict_to_struct(self.rule) repo = get_course_repo(self.participation.course) commit_sha = get_course_commit_sha( self.participation.course, self.participation) ctx = ValidationContext( repo=repo, commit_sha=commit_sha) flow_desc = get_flow_desc(repo, self.participation.course, self.flow_id, commit_sha) tags = None if hasattr(flow_desc, "rules"): tags = getattr(flow_desc.rules, "tags", None) try: if self.kind == flow_rule_kind.start: validate_session_start_rule(ctx, unicode(self), rule, tags) elif self.kind == flow_rule_kind.access: validate_session_access_rule(ctx, unicode(self), rule, tags) elif self.kind == flow_rule_kind.grading: validate_session_grading_rule(ctx, unicode(self), rule, tags) else: # the rule refers to FlowRuleException rule raise ValidationError(_("invalid rule kind: ")+self.kind) except ContentValidationError as e: # the rule refers to FlowRuleException rule raise ValidationError(_("invalid existing_session_rules: ")+str(e))
def __init__(self, request, course_identifier): self.request = request self.course_identifier = course_identifier self.course = get_object_or_404(Course, identifier=course_identifier) self.role, self.participation = get_role_and_participation( request, self.course) from course.views import check_course_state check_course_state(self.course, self.role) self.course_commit_sha = get_course_commit_sha( self.course, self.participation) self.repo = get_course_repo(self.course)
def __init__(self, request, course_identifier): self.request = request self.course_identifier = course_identifier self.course = get_object_or_404(Course, identifier=course_identifier) self.role, self.participation = get_role_and_participation( request, self.course) from course.views import check_course_state check_course_state(self.course, self.role) self.course_commit_sha = get_course_commit_sha(self.course, self.participation) self.repo = get_course_repo(self.course)
def get_repo_file_backend(request, course, role, participation, commit_sha, path): from course.views import check_course_state check_course_state(course, role) repo = get_course_repo(course) access_kind = "public" if request.relate_exam_lockdown: access_kind = "in_exam" from course.content import is_repo_file_accessible_as if not is_repo_file_accessible_as(access_kind, repo, commit_sha, path): raise PermissionDenied() return get_repo_file_response(repo, path, commit_sha)
def test_repo_is_in_subdir(self): self.course.course_root_path = "/subdir" self.course.save() from course.content import get_course_repo, SubdirRepoWrapper self.assertIsInstance(get_course_repo(self.course), SubdirRepoWrapper) with mock.patch("course.versioning.run_course_update_command" ) as mock_run_update: self.post_update_course_content(self.course.active_git_commit_sha, command="update") self.assertEqual(mock_run_update.call_count, 1) from course.content import SubdirRepoWrapper from dulwich.repo import Repo self.assertIsInstance(mock_run_update.call_args[0][1], Repo)
def get_media(request, course_identifier, commit_sha, media_path): course = get_object_or_404(Course, identifier=course_identifier) role, participation = get_role_and_participation(request, course) repo = get_course_repo(course) from course.content import get_repo_blob_data_cached try: data = get_repo_blob_data_cached( repo, "media/"+media_path, commit_sha.encode()) except ObjectDoesNotExist: raise http.Http404() from mimetypes import guess_type content_type = guess_type(media_path) return http.HttpResponse(data, content_type=content_type)
def test_repo_is_in_subdir(self): self.course.course_root_path = "/subdir" self.course.save() from course.content import get_course_repo, SubdirRepoWrapper self.assertIsInstance(get_course_repo(self.course), SubdirRepoWrapper) with mock.patch( "course.versioning.run_course_update_command" ) as mock_run_update: self.post_update_course_content( self.course.active_git_commit_sha, command="update") self.assertEqual(mock_run_update.call_count, 1) from course.content import SubdirRepoWrapper from dulwich.repo import Repo self.assertIsInstance(mock_run_update.call_args[0][1], Repo)
def set_course_metadata(apps, schema_editor): Course = apps.get_model("course", "Course") # noqa for course in Course.objects.all(): from course.content import ( get_course_repo, get_course_desc, get_course_commit_sha) repo = get_course_repo(course) course_desc = get_course_desc( repo, course, get_course_commit_sha(course, participation=None)) course.name = course_desc.name course.number = course_desc.number course.time_period = course_desc.run repo.close() course.save()
def get_media(request, course_identifier, commit_sha, media_path): course = get_object_or_404(Course, identifier=course_identifier) role, participation = get_role_and_participation(request, course) repo = get_course_repo(course) from course.content import get_repo_blob_data_cached try: data = get_repo_blob_data_cached(repo, "media/" + media_path, commit_sha.encode()) except ObjectDoesNotExist: raise http.Http404() from mimetypes import guess_type content_type, _ = guess_type(media_path) return http.HttpResponse(data, content_type=content_type)
def clean(self): if (self.kind == flow_rule_kind.grading and self.expiration is not None): raise ValidationError("grading rules may not expire") from course.validation import (ValidationError as ContentValidationError, validate_session_start_rule, validate_session_access_rule, validate_session_grading_rule, ValidationContext) from course.content import (get_course_repo, get_course_commit_sha, get_flow_desc) from relate.utils import dict_to_struct rule = dict_to_struct(self.rule) repo = get_course_repo(self.participation.course) commit_sha = get_course_commit_sha(self.participation.course, self.participation) ctx = ValidationContext(repo=repo, commit_sha=commit_sha) flow_desc = get_flow_desc(repo, self.participation.course, self.flow_id, commit_sha) tags = None if hasattr(flow_desc, "rules"): tags = getattr(flow_desc.rules, "tags", None) try: if self.kind == flow_rule_kind.start: validate_session_start_rule(ctx, unicode(self), rule, tags) elif self.kind == flow_rule_kind.access: validate_session_access_rule(ctx, unicode(self), rule, tags) elif self.kind == flow_rule_kind.grading: validate_session_grading_rule(ctx, unicode(self), rule, tags) else: raise ValidationError("invalid rule kind: " + self.kind) except ContentValidationError as e: raise ValidationError("invalid existing_session_rules: " + str(e))
def get_repo_file_backend( request, # type: http.HttpRequest course, # type: Course participation, # type: Optional[Participation] commit_sha, # type: bytes path, # type: str ): # type: (...) -> http.HttpResponse # noqa """ Check if a file should be accessible. Then call for it if the permission is not denied. Order is important here. An in-exam request takes precedence. Note: an access_role of "public" is equal to "unenrolled" """ # check to see if the course is hidden check_course_state(course, participation) # set access to public (or unenrolled), student, etc if request.relate_exam_lockdown: access_kinds = ["in_exam"] else: from course.enrollment import get_participation_permissions access_kinds = [ arg for perm, arg in get_participation_permissions( course, participation) if perm == pperm.access_files_for and arg is not None ] from course.content import is_repo_file_accessible_as # retrieve local path for the repo for the course with get_course_repo(course) as repo: if not is_repo_file_accessible_as(access_kinds, repo, commit_sha, path): raise PermissionDenied() return get_repo_file_response(repo, path, commit_sha)
def get_repo_file_backend( request, # type: http.HttpRequest course, # type: Course participation, # type: Optional[Participation] commit_sha, # type: bytes path, # type: str ): # type: (...) -> http.HttpResponse # noqa """ Check if a file should be accessible. Then call for it if the permission is not denied. Order is important here. An in-exam request takes precedence. Note: an access_role of "public" is equal to "unenrolled" """ # check to see if the course is hidden check_course_state(course, participation) # retrieve local path for the repo for the course repo = get_course_repo(course) # set access to public (or unenrolled), student, etc if request.relate_exam_lockdown: access_kinds = ["in_exam"] else: from course.enrollment import get_participation_permissions access_kinds = [ arg for perm, arg in get_participation_permissions(course, participation) if perm == pperm.access_files_for and arg is not None] from course.content import is_repo_file_accessible_as if not is_repo_file_accessible_as(access_kinds, repo, commit_sha, path): raise PermissionDenied() return get_repo_file_response(repo, path, commit_sha)
def __init__(self, request, course_identifier): # type: (http.HttpRequest, Text) -> None self.request = request self.course_identifier = course_identifier self._permissions_cache = None # type: Optional[FrozenSet[Tuple[Text, Optional[Text]]]] # noqa self._role_identifiers_cache = None # type: Optional[List[Text]] self.old_language = None # using this to prevent nested using as context manager self._is_in_context_manager = False from course.models import Course # noqa self.course = get_object_or_404(Course, identifier=course_identifier) from course.enrollment import get_participation_for_request self.participation = get_participation_for_request( request, self.course) from course.views import check_course_state check_course_state(self.course, self.participation) self.repo = get_course_repo(self.course) try: sha = get_course_commit_sha( self.course, self.participation, repo=self.repo, raise_on_nonexistent_preview_commit=True) except CourseCommitSHADoesNotExist as e: from django.contrib import messages messages.add_message(request, messages.ERROR, str(e)) sha = self.course.active_git_commit_sha.encode() self.course_commit_sha = sha
def _get_current_access_rule(participation, flow_id): course = participation.course from course.content import ( get_course_repo, get_course_commit_sha, get_flow_desc) repo = get_course_repo(course) course_commit_sha = get_course_commit_sha(course, participation) try: flow_desc = get_flow_desc(repo, course, flow_id.encode(), course_commit_sha) except ObjectDoesNotExist: return None else: from course.utils import get_current_flow_access_rule from django.utils.timezone import now return get_current_flow_access_rule(course, participation, participation.role, flow_id, flow_desc, now(), rule_id=None, use_exceptions=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 get_media(request, course_identifier, commit_sha, media_path): course = get_object_or_404(Course, identifier=course_identifier) with get_course_repo(course) as repo: return get_repo_file_response(repo, "media/" + media_path, commit_sha.encode())
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)] 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, 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 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
def grade_page_visit(visit, visit_grade_model=FlowPageVisitGrade, grade_data=None, graded_at_git_commit_sha=None): if not visit.is_submitted_answer: raise RuntimeError(_("cannot grade ungraded answer")) flow_session = visit.flow_session course = flow_session.course page_data = visit.page_data most_recent_grade = visit.get_most_recent_grade() if most_recent_grade is not None and grade_data is None: grade_data = most_recent_grade.grade_data from course.content import ( get_course_repo, get_course_commit_sha, get_flow_desc, get_flow_page_desc, instantiate_flow_page, ) repo = get_course_repo(course) course_commit_sha = get_course_commit_sha(course, flow_session.participation) flow_desc = get_flow_desc(repo, course, flow_session.flow_id, course_commit_sha) page_desc = get_flow_page_desc(flow_session.flow_id, flow_desc, page_data.group_id, page_data.page_id) page = instantiate_flow_page( location="flow '%s', group, '%s', page '%s'" % (flow_session.flow_id, page_data.group_id, page_data.page_id), repo=repo, page_desc=page_desc, commit_sha=course_commit_sha, ) assert page.expects_answer() if not page.is_answer_gradable(): return from course.page import PageContext grading_page_context = PageContext( course=course, repo=repo, commit_sha=course_commit_sha, flow_session=flow_session ) answer_feedback = page.grade(grading_page_context, visit.page_data.data, visit.answer, grade_data=grade_data) grade = visit_grade_model() grade.visit = visit grade.grade_data = grade_data grade.max_points = page.max_points(visit.page_data) grade.graded_at_git_commit_sha = graded_at_git_commit_sha bulk_feedback_json = None if answer_feedback is not None: grade.correctness = answer_feedback.correctness grade.feedback, bulk_feedback_json = answer_feedback.as_json() grade.save() update_bulk_feedback(page_data, grade, bulk_feedback_json)
def grade_page_visit(visit, visit_grade_model=FlowPageVisitGrade, grade_data=None, graded_at_git_commit_sha=None): if not visit.is_graded_answer: raise RuntimeError("cannot grade ungraded answer") flow_session = visit.flow_session course = flow_session.course page_data = visit.page_data from course.content import ( get_course_repo, get_course_commit_sha, get_flow_commit_sha, get_flow_desc, get_flow_page_desc, instantiate_flow_page) repo = get_course_repo(course) course_commit_sha = get_course_commit_sha( course, flow_session.participation) flow_desc_pre = get_flow_desc(repo, course, flow_session.flow_id, course_commit_sha) flow_commit_sha = get_flow_commit_sha( course, flow_session.participation, flow_desc_pre, visit.flow_session) flow_desc = get_flow_desc(repo, course, flow_session.flow_id, flow_commit_sha) page_desc = get_flow_page_desc( flow_session.flow_id, flow_desc, page_data.group_id, page_data.page_id) page = instantiate_flow_page( location="flow '%s', group, '%s', page '%s'" % (flow_session.flow_id, page_data.group_id, page_data.page_id), repo=repo, page_desc=page_desc, commit_sha=flow_commit_sha) from course.page import PageContext grading_page_context = PageContext( course=course, repo=repo, commit_sha=flow_commit_sha) answer_feedback = page.grade( grading_page_context, visit.page_data.data, visit.answer, grade_data=grade_data) grade = visit_grade_model() grade.visit = visit grade.grade_data = grade_data grade.max_points = page.max_points(visit.page_data) grade.graded_at_git_commit_sha = graded_at_git_commit_sha if answer_feedback is not None: grade.correctness = answer_feedback.correctness grade.feedback = answer_feedback.as_json() grade.save()