def finalize(): if not my_grade_changes: return state_machine = GradeStateMachine() state_machine.consume(my_grade_changes) percentage = state_machine.percentage() if percentage is not None: grades.append(percentage) del my_grade_changes[:]
def get_grade_table(course): # NOTE: It's important that these queries are sorted consistently, # also consistently with the code below. grading_opps = list((GradingOpportunity.objects.filter( course=course, shown_in_grade_book=True, ).order_by("identifier"))) participations = list( Participation.objects.filter( course=course, status=participation_status.active).order_by( "id").select_related("user")) grade_changes = list( GradeChange.objects.filter( opportunity__course=course, opportunity__shown_in_grade_book=True).order_by( "participation__id", "opportunity__identifier", "grade_time").select_related("participation").select_related( "participation__user").select_related("opportunity")) idx = 0 grade_table = [] for participation in participations: while (idx < len(grade_changes) and grade_changes[idx].participation.id < participation.id): idx += 1 grade_row = [] for opp in grading_opps: while (idx < len(grade_changes) and grade_changes[idx].participation.pk == participation.pk and grade_changes[idx].opportunity.identifier < opp.identifier): idx += 1 my_grade_changes = [] while (idx < len(grade_changes) and grade_changes[idx].opportunity.pk == opp.pk and grade_changes[idx].participation.pk == participation.pk): my_grade_changes.append(grade_changes[idx]) idx += 1 state_machine = GradeStateMachine() state_machine.consume(my_grade_changes) grade_row.append( GradeInfo(opportunity=opp, grade_state_machine=state_machine)) grade_table.append(grade_row) return participations, grading_opps, grade_table
def view_single_grade(pctx, participation_id, opportunity_id): now_datetime = get_now_or_fake_time(pctx.request) participation = get_object_or_404(Participation, id=int(participation_id)) if participation.course != pctx.course: raise SuspiciousOperation(_("participation does not match course")) opportunity = get_object_or_404(GradingOpportunity, id=int(opportunity_id)) if pctx.role in [ participation_role.instructor, participation_role.teaching_assistant]: if not opportunity.shown_in_grade_book: messages.add_message(pctx.request, messages.INFO, _("This grade is not shown in the grade book.")) if not opportunity.shown_in_student_grade_book: messages.add_message(pctx.request, messages.INFO, _("This grade is not shown in the student grade book.")) elif pctx.role == participation_role.student: if participation != pctx.participation: raise PermissionDenied(_("may not view other people's grades")) if not (opportunity.shown_in_grade_book and opportunity.shown_in_student_grade_book): raise PermissionDenied(_("grade has not been released")) else: raise PermissionDenied() # {{{ modify sessions buttons if pctx.role in [ participation_role.instructor, participation_role.teaching_assistant]: allow_session_actions = True request = pctx.request if pctx.request.method == "POST": action_re = re.compile("^([a-z]+)_([0-9]+)$") for key in request.POST.keys(): action_match = action_re.match(key) if action_match: break if not action_match: raise SuspiciousOperation(_("unknown action")) session = FlowSession.objects.get(id=int(action_match.group(2))) op = action_match.group(1) from course.flow import ( regrade_session, recalculate_session_grade, expire_flow_session_standalone, finish_flow_session_standalone) try: if op == "expire": expire_flow_session_standalone( pctx.repo, pctx.course, session, now_datetime) messages.add_message(pctx.request, messages.SUCCESS, _("Session expired.")) elif op == "end": finish_flow_session_standalone( pctx.repo, pctx.course, session, now_datetime=now_datetime) messages.add_message(pctx.request, messages.SUCCESS, _("Session ended.")) elif op == "regrade": regrade_session( pctx.repo, pctx.course, session) messages.add_message(pctx.request, messages.SUCCESS, _("Session regraded.")) elif op == "recalculate": recalculate_session_grade( pctx.repo, pctx.course, session) messages.add_message(pctx.request, messages.SUCCESS, _("Session grade recalculated.")) else: raise SuspiciousOperation(_("invalid session operation")) except Exception as e: messages.add_message(pctx.request, messages.ERROR, string_concat( pgettext_lazy("Starting of Error message", "Error"), ": %(err_type)s %(err_str)s") % { "err_type": type(e).__name__, "err_str": str(e)}) else: allow_session_actions = False # }}} grade_changes = list(GradeChange.objects .filter( opportunity=opportunity, participation=participation) .order_by("grade_time") .select_related("participation") .select_related("participation__user") .select_related("creator") .select_related("opportunity")) state_machine = GradeStateMachine() state_machine.consume(grade_changes, set_is_superseded=True) if opportunity.flow_id: flow_sessions = list(FlowSession.objects .filter( participation=participation, flow_id=opportunity.flow_id, ) .order_by("start_time")) from collections import namedtuple SessionProperties = namedtuple( # noqa "SessionProperties", ["due", "grade_description"]) from course.utils import get_session_grading_rule from course.content import get_flow_desc try: flow_desc = get_flow_desc(pctx.repo, pctx.course, opportunity.flow_id, pctx.course_commit_sha) except ObjectDoesNotExist: flow_sessions_and_session_properties = None else: flow_sessions_and_session_properties = [] for session in flow_sessions: grading_rule = get_session_grading_rule( session, pctx.role, flow_desc, now_datetime) session_properties = SessionProperties( due=grading_rule.due, grade_description=grading_rule.description) flow_sessions_and_session_properties.append( (session, session_properties)) else: flow_sessions_and_session_properties = None avg_grade_percentage, avg_grade_population = average_grade(opportunity) return render_course_page(pctx, "course/gradebook-single.html", { "opportunity": opportunity, "avg_grade_percentage": avg_grade_percentage, "avg_grade_population": avg_grade_population, "grade_participation": participation, "grade_state_change_types": grade_state_change_types, "grade_changes": grade_changes, "state_machine": state_machine, "flow_sessions_and_session_properties": flow_sessions_and_session_properties, "allow_session_actions": allow_session_actions, "show_privileged_info": pctx.role in [ participation_role.instructor, participation_role.teaching_assistant ], })
def view_participant_grades(pctx, participation_id=None): if pctx.participation is None: raise PermissionDenied(_("must be enrolled to view grades")) if participation_id is not None: grade_participation = Participation.objects.get(id=int(participation_id)) else: grade_participation = pctx.participation if pctx.role in [ participation_role.instructor, participation_role.teaching_assistant]: is_student_viewing = False elif pctx.role == participation_role.student: if grade_participation != pctx.participation: raise PermissionDenied(_("may not view other people's grades")) is_student_viewing = True else: raise PermissionDenied() # NOTE: It's important that these two queries are sorted consistently, # also consistently with the code below. grading_opps = list((GradingOpportunity.objects .filter( course=pctx.course, shown_in_grade_book=True, ) .order_by("identifier"))) grade_changes = list(GradeChange.objects .filter( participation=grade_participation, opportunity__course=pctx.course, opportunity__shown_in_grade_book=True) .order_by( "participation__id", "opportunity__identifier", "grade_time") .select_related("participation") .select_related("participation__user") .select_related("opportunity")) idx = 0 grade_table = [] for opp in grading_opps: if is_student_viewing: if not (opp.shown_in_grade_book and opp.shown_in_student_grade_book): continue else: if not opp.shown_in_grade_book: continue while ( idx < len(grade_changes) and grade_changes[idx].opportunity.identifier < opp.identifier ): idx += 1 my_grade_changes = [] while ( idx < len(grade_changes) and grade_changes[idx].opportunity.pk == opp.pk): my_grade_changes.append(grade_changes[idx]) idx += 1 state_machine = GradeStateMachine() state_machine.consume(my_grade_changes) grade_table.append( GradeInfo( opportunity=opp, grade_state_machine=state_machine)) return render_course_page(pctx, "course/gradebook-participant.html", { "grade_table": grade_table, "grade_participation": grade_participation, "grading_opportunities": grading_opps, "grade_state_change_types": grade_state_change_types, })
def view_grades_by_opportunity(pctx, opp_id): from course.views import get_now_or_fake_time now_datetime = get_now_or_fake_time(pctx.request) if pctx.role not in [ participation_role.instructor, participation_role.teaching_assistant]: raise PermissionDenied(_("must be instructor or TA to view grades")) opportunity = get_object_or_404(GradingOpportunity, id=int(opp_id)) if pctx.course != opportunity.course: raise SuspiciousOperation(_("opportunity from wrong course")) # {{{ batch sessions form batch_session_ops_form = None if pctx.role == participation_role.instructor and opportunity.flow_id: cursor = connection.cursor() cursor.execute("select distinct access_rules_tag from course_flowsession " "where course_id = %s and flow_id = %s " "order by access_rules_tag", (pctx.course.id, opportunity.flow_id)) session_rule_tags = [ mangle_session_access_rule_tag(row[0]) for row in cursor.fetchall()] request = pctx.request if request.method == "POST": batch_session_ops_form = ModifySessionsForm( session_rule_tags, request.POST, request.FILES) if "expire" in request.POST: op = "expire" elif "end" in request.POST: op = "end" elif "regrade" in request.POST: op = "regrade" elif "recalculate" in request.POST: op = "recalculate" else: raise SuspiciousOperation(_("invalid operation")) if batch_session_ops_form.is_valid(): rule_tag = batch_session_ops_form.cleaned_data["rule_tag"] past_due_only = batch_session_ops_form.cleaned_data["past_due_only"] if rule_tag == RULE_TAG_NONE_STRING: rule_tag = None from course.tasks import ( expire_in_progress_sessions, finish_in_progress_sessions, regrade_flow_sessions, recalculate_ended_sessions) if op == "expire": async_res = expire_in_progress_sessions.delay( pctx.course.id, opportunity.flow_id, rule_tag, now_datetime, past_due_only=past_due_only) return redirect("relate-monitor_task", async_res.id) elif op == "end": async_res = finish_in_progress_sessions.delay( pctx.course.id, opportunity.flow_id, rule_tag, now_datetime, past_due_only=past_due_only) return redirect("relate-monitor_task", async_res.id) elif op == "regrade": async_res = regrade_flow_sessions.delay( pctx.course.id, opportunity.flow_id, rule_tag, inprog_value=False) return redirect("relate-monitor_task", async_res.id) elif op == "recalculate": async_res = recalculate_ended_sessions.delay( pctx.course.id, opportunity.flow_id, rule_tag) return redirect("relate-monitor_task", async_res.id) else: raise SuspiciousOperation("invalid operation") else: batch_session_ops_form = ModifySessionsForm(session_rule_tags) # }}} # NOTE: It's important that these queries are sorted consistently, # also consistently with the code below. participations = list(Participation.objects .filter( course=pctx.course, status=participation_status.active) .order_by("id") .select_related("user")) grade_changes = list(GradeChange.objects .filter(opportunity=opportunity) .order_by( "participation__id", "grade_time") .select_related("participation") .select_related("participation__user") .select_related("opportunity")) idx = 0 finished_sessions = 0 total_sessions = 0 grade_table = [] for participation in participations: while ( idx < len(grade_changes) and grade_changes[idx].participation.id < participation.id): idx += 1 my_grade_changes = [] while ( idx < len(grade_changes) and grade_changes[idx].participation.pk == participation.pk): my_grade_changes.append(grade_changes[idx]) idx += 1 state_machine = GradeStateMachine() state_machine.consume(my_grade_changes) if opportunity.flow_id: flow_sessions = (FlowSession.objects .filter( participation=participation, flow_id=opportunity.flow_id, ) .order_by("start_time")) for fsession in flow_sessions: total_sessions += 1 if not fsession.in_progress: finished_sessions += 1 else: flow_sessions = None grade_table.append( OpportunityGradeInfo( grade_state_machine=state_machine, flow_sessions=flow_sessions)) def grade_key(entry): (participation, grades) = entry return (participation.user.last_name.lower(), participation.user.first_name.lower()) grade_table = sorted(zip(participations, grade_table), key=grade_key) return render_course_page(pctx, "course/gradebook-by-opp.html", { "opportunity": opportunity, "participations": participations, "grade_state_change_types": grade_state_change_types, "grade_table": grade_table, "batch_session_ops_form": batch_session_ops_form, "total_sessions": total_sessions, "finished_sessions": finished_sessions, })
def get_grade_table(course): # NOTE: It's important that these queries are sorted consistently, # also consistently with the code below. grading_opps = list((GradingOpportunity.objects .filter( course=course, shown_in_grade_book=True, ) .order_by("identifier"))) participations = list(Participation.objects .filter( course=course, status=participation_status.active) .order_by("id") .select_related("user")) grade_changes = list(GradeChange.objects .filter( opportunity__course=course, opportunity__shown_in_grade_book=True) .order_by( "participation__id", "opportunity__identifier", "grade_time") .select_related("participation") .select_related("participation__user") .select_related("opportunity")) idx = 0 grade_table = [] for participation in participations: while ( idx < len(grade_changes) and grade_changes[idx].participation.id < participation.id): idx += 1 grade_row = [] for opp in grading_opps: while ( idx < len(grade_changes) and grade_changes[idx].participation.pk == participation.pk and grade_changes[idx].opportunity.identifier < opp.identifier ): idx += 1 my_grade_changes = [] while ( idx < len(grade_changes) and grade_changes[idx].opportunity.pk == opp.pk and grade_changes[idx].participation.pk == participation.pk): my_grade_changes.append(grade_changes[idx]) idx += 1 state_machine = GradeStateMachine() state_machine.consume(my_grade_changes) grade_row.append( GradeInfo( opportunity=opp, grade_state_machine=state_machine)) grade_table.append(grade_row) return participations, grading_opps, grade_table
def view_single_grade(pctx, participation_id, opportunity_id): from course.views import get_now_or_fake_time now_datetime = get_now_or_fake_time(pctx.request) participation = get_object_or_404(Participation, id=int(participation_id)) if participation.course != pctx.course: raise SuspiciousOperation("participation does not match course") opportunity = get_object_or_404(GradingOpportunity, id=int(opportunity_id)) if pctx.role in [ participation_role.instructor, participation_role.teaching_assistant]: if not opportunity.shown_in_grade_book: messages.add_message(pctx.request, messages.INFO, "This grade is not shown in the grade book.") if not opportunity.shown_in_student_grade_book: messages.add_message(pctx.request, messages.INFO, "This grade is not shown in the student grade book.") elif pctx.role == participation_role.student: if participation != pctx.participation: raise PermissionDenied("may not view other people's grades") if not (opportunity.shown_in_grade_book and opportunity.shown_in_student_grade_book): raise PermissionDenied("grade has not been released") else: raise PermissionDenied() # {{{ modify sessions buttons if pctx.role in [ participation_role.instructor, participation_role.teaching_assistant]: allow_session_actions = True request = pctx.request if pctx.request.method == "POST": action_re = re.compile("^(expire|end|reopen|regrade)_([0-9]+)$") for key in request.POST.keys(): action_match = action_re.match(key) if action_match: break if not action_match: raise SuspiciousOperation("unknown action") session = FlowSession.objects.get(id=int(action_match.group(2))) op = action_match.group(1) from course.flow import ( reopen_session, regrade_session, expire_flow_session_standalone, finish_flow_session_standalone) try: if op == "expire": expire_flow_session_standalone( pctx.repo, pctx.course, session, now_datetime) messages.add_message(pctx.request, messages.SUCCESS, "Session expired.") elif op == "end": finish_flow_session_standalone( pctx.repo, pctx.course, session, now_datetime=now_datetime) messages.add_message(pctx.request, messages.SUCCESS, "Session ended.") elif op == "regrade": regrade_session( pctx.repo, pctx.course, session) messages.add_message(pctx.request, messages.SUCCESS, "Session regraded.") elif op == "reopen": reopen_session(session) messages.add_message(pctx.request, messages.SUCCESS, "Session reopened.") else: raise SuspiciousOperation("invalid session operation") except Exception as e: messages.add_message(pctx.request, messages.ERROR, "Error: %s %s" % (type(e).__name__, str(e))) else: allow_session_actions = False # }}} grade_changes = list(GradeChange.objects .filter( opportunity=opportunity, participation=participation) .order_by("grade_time") .prefetch_related("participation") .prefetch_related("participation__user") .prefetch_related("creator") .prefetch_related("opportunity")) state_machine = GradeStateMachine() state_machine.consume(grade_changes, set_is_superseded=True) flow_grade_aggregation_strategy_text = None if opportunity.flow_id: flow_sessions = list(FlowSession.objects .filter( participation=participation, flow_id=opportunity.flow_id, ) .order_by("start_time")) # {{{ fish out grade rules from course.content import get_flow_desc flow_desc = get_flow_desc( pctx.repo, pctx.course, opportunity.flow_id, pctx.course_commit_sha) from course.utils import ( get_flow_access_rules, get_relevant_rules) all_flow_rules = get_flow_access_rules(pctx.course, participation, opportunity.flow_id, flow_desc) relevant_flow_rules = get_relevant_rules( all_flow_rules, pctx.participation.role, now()) if hasattr(flow_desc, "grade_aggregation_strategy"): from course.models import GRADE_AGGREGATION_STRATEGY_CHOICES flow_grade_aggregation_strategy_text = ( dict(GRADE_AGGREGATION_STRATEGY_CHOICES) [flow_desc.grade_aggregation_strategy]) # }}} else: flow_sessions = None relevant_flow_rules = None return render_course_page(pctx, "course/gradebook-single.html", { "opportunity": opportunity, "grade_participation": participation, "grade_state_change_types": grade_state_change_types, "grade_changes": grade_changes, "state_machine": state_machine, "flow_sessions": flow_sessions, "allow_session_actions": allow_session_actions, "show_page_grades": pctx.role in [ participation_role.instructor, participation_role.teaching_assistant ], "flow_rules": relevant_flow_rules, "flow_grade_aggregation_strategy": flow_grade_aggregation_strategy_text, })
def view_grades_by_opportunity(pctx, opp_id): from course.views import get_now_or_fake_time now_datetime = get_now_or_fake_time(pctx.request) if pctx.role not in [ participation_role.instructor, participation_role.teaching_assistant]: raise PermissionDenied("must be instructor or TA to view grades") opportunity = get_object_or_404(GradingOpportunity, id=int(opp_id)) if pctx.course != opportunity.course: raise SuspiciousOperation("opportunity from wrong course") # {{{ batch sessions form batch_session_ops_form = None if pctx.role == participation_role.instructor and opportunity.flow_id: cursor = connection.cursor() cursor.execute("select distinct access_rules_id from course_flowsession " "where course_id = %s and flow_id = %s " "order by access_rules_id", (pctx.course.id, opportunity.flow_id)) rule_ids = [mangle_rule_id(row[0]) for row in cursor.fetchall()] request = pctx.request if request.method == "POST": batch_session_ops_form = ModifySessionsForm( rule_ids, request.POST, request.FILES) if "expire" in request.POST: op = "expire" elif "end" in request.POST: op = "end" elif "regrade" in request.POST: op = "regrade" else: raise SuspiciousOperation("invalid operation") if batch_session_ops_form.is_valid(): rule_id = batch_session_ops_form.cleaned_data["rule_id"] past_end_only = batch_session_ops_form.cleaned_data["past_end_only"] if rule_id == RULE_ID_NONE_STRING: rule_id = None try: if op == "expire": count = expire_in_progress_sessions( pctx.repo, pctx.course, opportunity.flow_id, rule_id, now_datetime, past_end_only=past_end_only) messages.add_message(pctx.request, messages.SUCCESS, "%d session(s) expired." % count) elif op == "end": count = finish_in_progress_sessions( pctx.repo, pctx.course, opportunity.flow_id, rule_id, now_datetime, past_end_only=past_end_only) messages.add_message(pctx.request, messages.SUCCESS, "%d session(s) ended." % count) elif op == "regrade": count = regrade_ended_sessions( pctx.repo, pctx.course, opportunity.flow_id, rule_id) messages.add_message(pctx.request, messages.SUCCESS, "%d session(s) regraded." % count) else: raise SuspiciousOperation("invalid operation") except Exception as e: messages.add_message(pctx.request, messages.ERROR, "Error: %s %s" % (type(e).__name__, str(e))) else: batch_session_ops_form = ModifySessionsForm(rule_ids) # }}} # NOTE: It's important that these queries are sorted consistently, # also consistently with the code below. participations = list(Participation.objects .filter( course=pctx.course, status=participation_status.active) .order_by("id") .prefetch_related("user")) grade_changes = list(GradeChange.objects .filter(opportunity=opportunity) .order_by( "participation__id", "grade_time") .prefetch_related("participation") .prefetch_related("participation__user") .prefetch_related("opportunity")) idx = 0 grade_table = [] for participation in participations: while ( idx < len(grade_changes) and grade_changes[idx].participation.id < participation.id): idx += 1 my_grade_changes = [] while ( idx < len(grade_changes) and grade_changes[idx].participation.pk == participation.pk): my_grade_changes.append(grade_changes[idx]) idx += 1 state_machine = GradeStateMachine() state_machine.consume(my_grade_changes) if opportunity.flow_id: flow_sessions = (FlowSession.objects .filter( participation=participation, flow_id=opportunity.flow_id, ) .order_by("start_time")) else: flow_sessions = None grade_table.append( OpportunityGradeInfo( grade_state_machine=state_machine, flow_sessions=flow_sessions)) grade_table = sorted(zip(participations, grade_table), key=lambda (participation, grades): (participation.user.last_name.lower(), participation.user.first_name.lower())) return render_course_page(pctx, "course/gradebook-by-opp.html", { "opportunity": opportunity, "participations": participations, "grade_state_change_types": grade_state_change_types, "grade_table": grade_table, "batch_session_ops_form": batch_session_ops_form, })
def view_gradebook(pctx): if pctx.role not in [ participation_role.instructor, participation_role.teaching_assistant]: raise PermissionDenied("must be instructor or TA to view grades") # NOTE: It's important that these queries are sorted consistently, # also consistently with the code below. grading_opps = list((GradingOpportunity.objects .filter( course=pctx.course, shown_in_grade_book=True, ) .order_by("identifier"))) participations = list(Participation.objects .filter( course=pctx.course, status=participation_status.active) .order_by("id") .prefetch_related("user")) grade_changes = list(GradeChange.objects .filter( opportunity__course=pctx.course, opportunity__shown_in_grade_book=True) .order_by( "participation__id", "opportunity__identifier", "grade_time") .prefetch_related("participation") .prefetch_related("participation__user") .prefetch_related("opportunity")) idx = 0 grade_table = [] for participation in participations: while ( idx < len(grade_changes) and grade_changes[idx].participation.id < participation.id): idx += 1 grade_row = [] for opp in grading_opps: while ( idx < len(grade_changes) and grade_changes[idx].participation.pk == participation.pk and grade_changes[idx].opportunity.identifier < opp.identifier ): idx += 1 my_grade_changes = [] while ( idx < len(grade_changes) and grade_changes[idx].opportunity.pk == opp.pk and grade_changes[idx].participation.pk == participation.pk): my_grade_changes.append(grade_changes[idx]) idx += 1 state_machine = GradeStateMachine() state_machine.consume(my_grade_changes) grade_row.append( GradeInfo( opportunity=opp, grade_state_machine=state_machine)) grade_table.append(grade_row) grade_table = sorted(zip(participations, grade_table), key=lambda (participation, grades): (participation.user.last_name.lower(), participation.user.first_name.lower())) return render_course_page(pctx, "course/gradebook.html", { "grade_table": grade_table, "grading_opportunities": grading_opps, "participations": participations, "grade_state_change_types": grade_state_change_types, })
def view_single_grade(pctx, participation_id, opportunity_id): participation = get_object_or_404(Participation, id=int(participation_id)) if participation.course != pctx.course: raise SuspiciousOperation("participation does not match course") if pctx.role in [ participation_role.instructor, participation_role.teaching_assistant]: pass elif pctx.role == participation_role.student: if participation != pctx.participation: raise PermissionDenied("may not view other people's grades") else: raise PermissionDenied() opportunity = get_object_or_404(GradingOpportunity, id=int(opportunity_id)) # {{{ modify sessions form if pctx.role in [ participation_role.instructor, participation_role.teaching_assistant]: allow_session_actions = True request = pctx.request if pctx.request.method == "POST": action_re = re.compile("^(end|reopen|regrade)_([0-9]+)$") for key in request.POST.keys(): action_match = action_re.match(key) if action_match: break if not action_match: raise SuspiciousOperation("unknown action") session = FlowSession.objects.get(id=int(action_match.group(2))) op = action_match.group(1) from course.flow import ( reopen_session, regrade_session, finish_flow_session_standalone) try: if op == "end": finish_flow_session_standalone( pctx.repo, pctx.course, session) messages.add_message(pctx.request, messages.SUCCESS, "Session ended.") elif op == "regrade": regrade_session( pctx.repo, pctx.course, session) messages.add_message(pctx.request, messages.SUCCESS, "Session regraded.") elif op == "reopen": reopen_session(session) messages.add_message(pctx.request, messages.SUCCESS, "Session reopened.") else: raise SuspiciousOperation("invalid session operation") except Exception as e: messages.add_message(pctx.request, messages.ERROR, "Error: %s %s" % (type(e), str(e))) else: allow_session_actions = False # }}} grade_changes = list(GradeChange.objects .filter( opportunity=opportunity, participation=participation) .order_by("grade_time") .prefetch_related("participation") .prefetch_related("participation__user") .prefetch_related("creator") .prefetch_related("opportunity")) state_machine = GradeStateMachine() state_machine.consume(grade_changes, set_is_superseded=True) if opportunity.flow_id is not None: flow_sessions = list(FlowSession.objects .filter( participation=participation, flow_id=opportunity.flow_id, ) .order_by("start_time")) else: flow_sessions = None return render_course_page(pctx, "course/gradebook-single.html", { "opportunity": opportunity, "grade_participation": participation, "grade_state_change_types": grade_state_change_types, "grade_changes": grade_changes, "state_machine": state_machine, "flow_sessions": flow_sessions, "allow_session_actions": allow_session_actions, "show_page_grades": pctx.role in [ participation_role.instructor, participation_role.teaching_assistant ], })
def view_single_grade(pctx, participation_id, opportunity_id): from course.views import get_now_or_fake_time now_datetime = get_now_or_fake_time(pctx.request) participation = get_object_or_404(Participation, id=int(participation_id)) if participation.course != pctx.course: raise SuspiciousOperation("participation does not match course") opportunity = get_object_or_404(GradingOpportunity, id=int(opportunity_id)) if pctx.role in [ participation_role.instructor, participation_role.teaching_assistant]: if not opportunity.shown_in_grade_book: messages.add_message(pctx.request, messages.INFO, "This grade is not shown in the grade book.") if not opportunity.shown_in_student_grade_book: messages.add_message(pctx.request, messages.INFO, "This grade is not shown in the student grade book.") elif pctx.role == participation_role.student: if participation != pctx.participation: raise PermissionDenied("may not view other people's grades") if not (opportunity.shown_in_grade_book and opportunity.shown_in_student_grade_book): raise PermissionDenied("grade has not been released") else: raise PermissionDenied() # {{{ modify sessions buttons if pctx.role in [ participation_role.instructor, participation_role.teaching_assistant]: allow_session_actions = True request = pctx.request if pctx.request.method == "POST": action_re = re.compile("^([a-z]+)_([0-9]+)$") for key in request.POST.keys(): action_match = action_re.match(key) if action_match: break if not action_match: raise SuspiciousOperation("unknown action") session = FlowSession.objects.get(id=int(action_match.group(2))) op = action_match.group(1) from course.flow import ( reopen_session, regrade_session, recalculate_session_grade, expire_flow_session_standalone, finish_flow_session_standalone) try: if op == "expire": expire_flow_session_standalone( pctx.repo, pctx.course, session, now_datetime) messages.add_message(pctx.request, messages.SUCCESS, "Session expired.") elif op == "end": finish_flow_session_standalone( pctx.repo, pctx.course, session, now_datetime=now_datetime) messages.add_message(pctx.request, messages.SUCCESS, "Session ended.") elif op == "reopen": reopen_session(session) messages.add_message(pctx.request, messages.SUCCESS, "Session reopened.") elif op == "regrade": regrade_session( pctx.repo, pctx.course, session) messages.add_message(pctx.request, messages.SUCCESS, "Session regraded.") elif op == "recalculate": recalculate_session_grade( pctx.repo, pctx.course, session) messages.add_message(pctx.request, messages.SUCCESS, "Session grade recalculated.") else: raise SuspiciousOperation("invalid session operation") except Exception as e: messages.add_message(pctx.request, messages.ERROR, "Error: %s %s" % (type(e).__name__, str(e))) else: allow_session_actions = False # }}} grade_changes = list(GradeChange.objects .filter( opportunity=opportunity, participation=participation) .order_by("grade_time") .select_related("participation") .select_related("participation__user") .select_related("creator") .select_related("opportunity")) state_machine = GradeStateMachine() state_machine.consume(grade_changes, set_is_superseded=True) flow_grade_aggregation_strategy_text = None if opportunity.flow_id: flow_sessions = list(FlowSession.objects .filter( participation=participation, flow_id=opportunity.flow_id, ) .order_by("start_time")) from collections import namedtuple SessionProperties = namedtuple("SessionProperties", ["due", "grade_description"]) from course.utils import get_session_grading_rule from course.content import get_flow_desc flow_desc = get_flow_desc(pctx.repo, pctx.course, opportunity.flow_id, pctx.course_commit_sha) flow_sessions_and_session_properties = [] for session in flow_sessions: grading_rule = get_session_grading_rule( session, pctx.role, flow_desc, now_datetime) session_properties = SessionProperties( due=grading_rule.due, grade_description=grading_rule.description) flow_sessions_and_session_properties.append( (session, session_properties)) else: flow_sessions_and_session_properties = None return render_course_page(pctx, "course/gradebook-single.html", { "opportunity": opportunity, "grade_participation": participation, "grade_state_change_types": grade_state_change_types, "grade_changes": grade_changes, "state_machine": state_machine, "flow_sessions_and_session_properties": flow_sessions_and_session_properties, "allow_session_actions": allow_session_actions, "show_privileged_info": pctx.role in [ participation_role.instructor, participation_role.teaching_assistant ], "flow_grade_aggregation_strategy": flow_grade_aggregation_strategy_text, })
def view_participant_grades(pctx, participation_id=None): if pctx.participation is None: raise PermissionDenied("must be enrolled to view grades") if participation_id is not None: grade_participation = Participation.objects.get(id=int(participation_id)) else: grade_participation = pctx.participation if pctx.role in [ participation_role.instructor, participation_role.teaching_assistant]: is_student_viewing = False elif pctx.role == participation_role.student: if grade_participation != pctx.participation: raise PermissionDenied("may not view other people's grades") is_student_viewing = True else: raise PermissionDenied() # NOTE: It's important that these two queries are sorted consistently, # also consistently with the code below. grading_opps = list((GradingOpportunity.objects .filter( course=pctx.course, shown_in_grade_book=True, ) .order_by("identifier"))) grade_changes = list(GradeChange.objects .filter( participation=grade_participation, opportunity__course=pctx.course, opportunity__shown_in_grade_book=True) .order_by( "participation__id", "opportunity__identifier", "grade_time") .select_related("participation") .select_related("participation__user") .select_related("opportunity")) idx = 0 grade_table = [] for opp in grading_opps: if is_student_viewing: if not (opp.shown_in_grade_book and opp.shown_in_student_grade_book): continue else: if not opp.shown_in_grade_book: continue while ( idx < len(grade_changes) and grade_changes[idx].opportunity.identifier < opp.identifier ): idx += 1 my_grade_changes = [] while ( idx < len(grade_changes) and grade_changes[idx].opportunity.pk == opp.pk): my_grade_changes.append(grade_changes[idx]) idx += 1 state_machine = GradeStateMachine() state_machine.consume(my_grade_changes) grade_table.append( GradeInfo( opportunity=opp, grade_state_machine=state_machine)) return render_course_page(pctx, "course/gradebook-participant.html", { "grade_table": grade_table, "grade_participation": grade_participation, "grading_opportunities": grading_opps, "grade_state_change_types": grade_state_change_types, })
def view_grades_by_opportunity(pctx, opp_id): from course.views import get_now_or_fake_time now_datetime = get_now_or_fake_time(pctx.request) if pctx.role not in [ participation_role.instructor, participation_role.teaching_assistant]: raise PermissionDenied("must be instructor or TA to view grades") opportunity = get_object_or_404(GradingOpportunity, id=int(opp_id)) if pctx.course != opportunity.course: raise SuspiciousOperation("opportunity from wrong course") # {{{ batch sessions form batch_session_ops_form = None if pctx.role == participation_role.instructor and opportunity.flow_id: cursor = connection.cursor() cursor.execute("select distinct access_rules_tag from course_flowsession " "where course_id = %s and flow_id = %s " "order by access_rules_tag", (pctx.course.id, opportunity.flow_id)) session_rule_tags = [ mangle_session_access_rule_tag(row[0]) for row in cursor.fetchall()] request = pctx.request if request.method == "POST": batch_session_ops_form = ModifySessionsForm( session_rule_tags, request.POST, request.FILES) if "expire" in request.POST: op = "expire" elif "end" in request.POST: op = "end" elif "regrade" in request.POST: op = "regrade" elif "recalculate" in request.POST: op = "recalculate" else: raise SuspiciousOperation("invalid operation") if batch_session_ops_form.is_valid(): rule_tag = batch_session_ops_form.cleaned_data["rule_tag"] past_due_only = batch_session_ops_form.cleaned_data["past_due_only"] if rule_tag == RULE_TAG_NONE_STRING: rule_tag = None try: if op == "expire": count = expire_in_progress_sessions( pctx.repo, pctx.course, opportunity.flow_id, rule_tag, now_datetime, past_due_only=past_due_only) messages.add_message(pctx.request, messages.SUCCESS, "%d session(s) expired." % count) elif op == "end": count = finish_in_progress_sessions( pctx.repo, pctx.course, opportunity.flow_id, rule_tag, now_datetime, past_due_only=past_due_only) messages.add_message(pctx.request, messages.SUCCESS, "%d session(s) ended." % count) elif op == "regrade": count = regrade_ended_sessions( pctx.repo, pctx.course, opportunity.flow_id, rule_tag) messages.add_message(pctx.request, messages.SUCCESS, "%d session(s) regraded." % count) elif op == "recalculate": count = recalculate_ended_sessions( pctx.repo, pctx.course, opportunity.flow_id, rule_tag) messages.add_message(pctx.request, messages.SUCCESS, "Grade recalculated for %d session(s)." % count) else: raise SuspiciousOperation("invalid operation") except Exception as e: messages.add_message(pctx.request, messages.ERROR, "Error: %s %s" % (type(e).__name__, str(e))) raise else: batch_session_ops_form = ModifySessionsForm(session_rule_tags) # }}} # NOTE: It's important that these queries are sorted consistently, # also consistently with the code below. participations = list(Participation.objects .filter( course=pctx.course, status=participation_status.active) .order_by("id") .select_related("user")) grade_changes = list(GradeChange.objects .filter(opportunity=opportunity) .order_by( "participation__id", "grade_time") .select_related("participation") .select_related("participation__user") .select_related("opportunity")) idx = 0 finished_sessions = 0 total_sessions = 0 grade_table = [] for participation in participations: while ( idx < len(grade_changes) and grade_changes[idx].participation.id < participation.id): idx += 1 my_grade_changes = [] while ( idx < len(grade_changes) and grade_changes[idx].participation.pk == participation.pk): my_grade_changes.append(grade_changes[idx]) idx += 1 state_machine = GradeStateMachine() state_machine.consume(my_grade_changes) if opportunity.flow_id: flow_sessions = (FlowSession.objects .filter( participation=participation, flow_id=opportunity.flow_id, ) .order_by("start_time")) for fsession in flow_sessions: total_sessions += 1 if not fsession.in_progress: finished_sessions += 1 else: flow_sessions = None grade_table.append( OpportunityGradeInfo( grade_state_machine=state_machine, flow_sessions=flow_sessions)) grade_table = sorted(zip(participations, grade_table), key=lambda (participation, grades): (participation.user.last_name.lower(), participation.user.first_name.lower())) return render_course_page(pctx, "course/gradebook-by-opp.html", { "opportunity": opportunity, "participations": participations, "grade_state_change_types": grade_state_change_types, "grade_table": grade_table, "batch_session_ops_form": batch_session_ops_form, "total_sessions": total_sessions, "finished_sessions": finished_sessions, })