def gradebook(request, course_id): course = get_object_or_404(Course, pk=course_id) teacher_courses = get_teacher_courses(request.user.username) if not request.user.is_superuser and not request.user.groups.filter(name='registrar').count() and \ (teacher_courses is None or course not in teacher_courses): return HttpResponse(status=403, content='You do not have access to this course.') students = Student.objects.filter(inactive=False, course=course) #students = Student.objects.filter(course=course) items = Item.objects.filter(course=course) filtered = False if request.GET: filter_form = GradebookFilterForm(request.GET) filter_form.update_querysets(course) if filter_form.is_valid(): for filter_key, filter_value in filter_form.cleaned_data.iteritems( ): if filter_value is not None: try: if not len(filter_value): continue except TypeError: # not everything has a len pass if filter_key == 'cohort': students = students.filter(cohorts=filter_value) if filter_key == 'marking_period': items = items.filter(marking_period=filter_value) if filter_key == 'benchmark': items = items.filter(benchmark__in=filter_value) if filter_key == 'category': items = items.filter(category=filter_value) if filter_key == 'assignment_type': items = items.filter(assignment_type=filter_value) if filter_key == 'name': items = items.filter(name__icontains=filter_value) if filter_key == 'date_begin': items = items.filter(date__gt=filter_value) if filter_key == 'date_end': items = items.filter(date__lt=filter_value) filtered = True else: # show only the active marking period by default active_mps = course.marking_period.filter(active=True) if active_mps: filter_form = GradebookFilterForm( initial={'marking_period': active_mps[0]}) items = items.filter(marking_period=active_mps[0]) else: filter_form = GradebookFilterForm() filter_form.update_querysets(course) # Freeze these now in case someone else gets in here! items = items.order_by('id').all() # whoa, super roll of the dice. is Item.demonstration_set really guaranteed to be ordered by id? # precarious; sorting must match items (and demonstrations!) exactly marks = Mark.objects.filter(item__in=items).order_by( 'item__id', 'demonstration__id').all() items_count = items.filter( demonstration=None).count() + Demonstration.objects.filter( item__in=items).count() for student in students: student_marks = marks.filter(student=student) if student_marks.count() < items_count: # maybe student enrolled after assignments were created for item in items: if len(item.demonstration_set.all()): # must create mark for each demonstration for demonstration in item.demonstration_set.all(): mark, created = Mark.objects.get_or_create( item=item, demonstration=demonstration, student=student) if created: mark.save() else: # a regular item without demonstrations; make only one mark mark, created = Mark.objects.get_or_create(item=item, student=student) if created: mark.save() if student_marks.count() > items_count: # Yikes, there are multiple marks per student per item. Stop loading the gradebook now. if 'dangerous' in request.GET: pass else: raise Exception('Multiple marks per student per item.') student.marks = student_marks student.average = gradebook_get_average(student, course, None, None, None) if filtered: student.filtered_average = gradebook_get_average( student, course, filter_form.cleaned_data['category'], filter_form.cleaned_data['marking_period'], items) # TC's column of counts # TODO: don't hardcode standards_category = Category.objects.get(name='Standards') PASSING_GRADE = 3 standards_objects = Item.objects.filter( course=course, category=standards_category, mark__student=student).annotate( best_mark=Max('mark__mark')).exclude(best_mark=None) standards_count_passing = standards_objects.filter( best_mark__gte=PASSING_GRADE).count() standards_count_total = standards_objects.count() if standards_count_total: student.standards_counts = '{} / {} ({:.0f}%)'.format( standards_count_passing, standards_count_total, 100.0 * standards_count_passing / standards_count_total) else: student.standards_counts_ = None if filtered: standards_objects = items.filter( course=course, category=standards_category, mark__student=student).annotate( best_mark=Max('mark__mark')).exclude(best_mark=None) standards_count_passing = standards_objects.filter( best_mark__gte=PASSING_GRADE).count() standards_count_total = standards_objects.count() if standards_count_total: student.filtered_standards_counts = '{} / {} ({:.0f}%)'.format( standards_count_passing, standards_count_total, 100.0 * standards_count_passing / standards_count_total) else: student.filtered_standards_counts = None # TC's row of counts # TODO: don't hardcode for item in items: if item.category != standards_category: item.marks_counts = 'N/A' continue marks_count_passing = item.mark_set.filter( mark__gte=PASSING_GRADE).count() marks_count_total = item.mark_set.exclude(mark=None).count() if marks_count_total: item.marks_counts = '{} / {} ({:.0f}%)'.format( marks_count_passing, marks_count_total, 100.0 * marks_count_passing / marks_count_total) else: item.marks_counts = None return render_to_response( 'benchmark_grade/gradebook.html', { 'items': items, 'item_pks': ','.join(map(str, items.values_list('pk', flat=True))), 'students': students, 'course': course, 'teacher_courses': teacher_courses, 'filtered': filtered, 'filter_form': filter_form, }, RequestContext(request, {}), )
def gradebook(request, course_id): course = get_object_or_404(Course, pk=course_id) school_year = course.marking_period.all()[0].school_year teacher_courses = get_teacher_courses(request.user.username) if not request.user.is_superuser and not request.user.groups.filter(name='registrar').count() and \ (teacher_courses is None or course not in teacher_courses): messages.add_message( request, messages.ERROR, 'You do not have access to the gradebook for ' + course.fullname + '.') return HttpResponseRedirect(reverse('admin:index')) # lots of stuff will fail unceremoniously if there are no MPs assigned if not course.marking_period.count(): messages.add_message( request, messages.ERROR, 'The gradebook cannot be opened because there are no marking periods assigned to the course ' + course.fullname + '.') return HttpResponseRedirect(reverse('admin:index')) students = Student.objects.filter(inactive=False, course=course) #students = Student.objects.filter(course=course) items = Item.objects.filter(course=course) filtered = False if request.GET: filter_form = GradebookFilterForm(request.GET) filter_form.update_querysets(course) if filter_form.is_valid(): for filter_key, filter_value in filter_form.cleaned_data.iteritems( ): if filter_value is not None: try: if not len(filter_value): continue except TypeError: # not everything has a len pass if filter_key == 'cohort': students = students.filter(cohorts=filter_value) if filter_key == 'marking_period': items = items.filter(marking_period=filter_value) if filter_key == 'benchmark': items = items.filter(benchmark__in=filter_value) if filter_key == 'category': items = items.filter(category=filter_value) if filter_key == 'assignment_type': items = items.filter(assignment_type=filter_value) if filter_key == 'name': items = items.filter(name__icontains=filter_value) if filter_key == 'date_begin': items = items.filter(date__gt=filter_value) if filter_key == 'date_end': items = items.filter(date__lt=filter_value) filtered = True else: # show only the active marking period by default active_mps = course.marking_period.filter(active=True) if active_mps: filter_form = GradebookFilterForm( initial={'marking_period': active_mps[0]}) items = items.filter(marking_period=active_mps[0]) else: filter_form = GradebookFilterForm() filter_form.update_querysets(course) # make a note of any aggregates pending recalculation pending_aggregate_pks = Aggregate.objects.filter(course=course).annotate( Count('aggregatetask')).filter(aggregatetask__count__gt=0).values_list( 'pk', flat=True) # Freeze these now in case someone else gets in here! # TODO: something that actually works. all() does not evaluate a QuerySet. # https://docs.djangoproject.com/en/dev/ref/models/querysets/#when-querysets-are-evaluated items = items.order_by('id').all() # whoa, super roll of the dice. is Item.demonstration_set really guaranteed to be ordered by id? # precarious; sorting must match items (and demonstrations!) exactly marks = Mark.objects.filter(item__in=items).order_by( 'item__id', 'demonstration__id').all() items_count = items.filter( demonstration=None).count() + Demonstration.objects.filter( item__in=items).count() for student in students: student_marks = marks.filter( student=student).select_related('item__category_id') student_marks_count = student_marks.count() if student_marks_count < items_count: # maybe student enrolled after assignments were created for item in items: if len(item.demonstration_set.all()): # must create mark for each demonstration for demonstration in item.demonstration_set.all(): mark, created = Mark.objects.get_or_create( item=item, demonstration=demonstration, student=student) else: # a regular item without demonstrations; make only one mark mark, created = Mark.objects.get_or_create(item=item, student=student) if student_marks_count > items_count: # Yikes, there are multiple marks per student per item. Stop loading the gradebook now. if 'dangerous' in request.GET: pass else: raise Exception('Multiple marks per student per item.') for mark in student_marks: mark.category_id = mark.item.category_id student.marks = student_marks student.average, student.average_pk = gradebook_get_average_and_pk( student, course, None, None, None) if filtered: student.filtered_average = gradebook_get_average( student, course, filter_form.cleaned_data['category'], filter_form.cleaned_data['marking_period'], items) if school_year.benchmark_grade: # TC's column of counts # TODO: don't hardcode standards_category = Category.objects.get(name='Standards') PASSING_GRADE = 3 standards_objects = Item.objects.filter( course=course, category=standards_category, mark__student=student).annotate( best_mark=Max('mark__mark')).exclude(best_mark=None) standards_count_passing = standards_objects.filter( best_mark__gte=PASSING_GRADE).count() standards_count_total = standards_objects.count() if standards_count_total: student.standards_counts = '{} / {} ({:.0f}%)'.format( standards_count_passing, standards_count_total, 100.0 * standards_count_passing / standards_count_total) else: student.standards_counts_ = None if filtered: standards_objects = items.filter( course=course, category=standards_category, mark__student=student).annotate( best_mark=Max('mark__mark')).exclude(best_mark=None) standards_count_passing = standards_objects.filter( best_mark__gte=PASSING_GRADE).count() standards_count_total = standards_objects.count() if standards_count_total: student.filtered_standards_counts = '{} / {} ({:.0f}%)'.format( standards_count_passing, standards_count_total, 100.0 * standards_count_passing / standards_count_total) else: student.filtered_standards_counts = None # TC's row of counts # TODO: don't hardcode for item in items: if item.category != standards_category: item.marks_counts = 'N/A' continue marks_count_passing = item.mark_set.filter( mark__gte=PASSING_GRADE).count() marks_count_total = item.mark_set.exclude(mark=None).count() if marks_count_total: item.marks_counts = '{} / {} ({:.0f}%)'.format( marks_count_passing, marks_count_total, 100.0 * marks_count_passing / marks_count_total) else: item.marks_counts = None # Gather visual flagging criteria calculation_rule = benchmark_find_calculation_rule(school_year) category_flag_criteria = {} for category in Category.objects.filter(item__in=items).distinct(): category_flag_criteria[category.pk] = [] substitutions = calculation_rule.substitution_set.filter( apply_to_departments=course.department, apply_to_categories=category, flag_visually=True) for substitution in substitutions: category_flag_criteria[category.pk].append( substitution.operator + ' ' + str(substitution.match_value)) return render_to_response( 'benchmark_grade/gradebook.html', { 'items': items, 'item_pks': ','.join(map(str, items.values_list('pk', flat=True))), 'pending_aggregate_pks': json.dumps(map(str, pending_aggregate_pks)), 'students': students, 'course': course, 'teacher_courses': teacher_courses, 'filtered': filtered, 'filter_form': filter_form, 'category_flag_criteria': category_flag_criteria, }, RequestContext(request, {}), )