def gradebook(request, course_id, for_export=False): course = get_object_or_404(Course, pk=course_id) # 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')) school_year = course.marking_period.all()[0].school_year try: calculation_rule = benchmark_find_calculation_rule(school_year) except Exception as e: if "There is no suitable calculation rule for the school year" not in unicode(e): raise messages.add_message(request, messages.ERROR, e) return HttpResponseRedirect(reverse('admin:index')) teacher_courses = get_teacher_courses(request.user.username) extra_info = Configuration.get_or_default('Gradebook extra information').value.lower().strip() quantizer = Decimal(10) ** (-1 * calculation_rule.decimal_places) 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')) students = Student.objects.filter(is_active=True,course=course) #students = Student.objects.filter(course=course) items = Item.objects.filter(course=course) filtered = False temporary_aggregate = False totals = { 'filtered_average': Decimal(0), 'filtered_average_count': Decimal(0), 'course_average': Decimal(0), 'course_average_count': Decimal(0), 'filtered_standards_passing': 0, 'filtered_standards_all': 0, 'standards_passing': 0, 'standards_all': 0 } 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) temporary_aggregate = True if filter_key == 'marking_period': items = items.filter(marking_period=filter_value) if filter_key == 'benchmark': items = items.filter(benchmark__in=filter_value) temporary_aggregate = True if filter_key == 'category': items = items.filter(category=filter_value) if filter_key == 'assignment_type': items = items.filter(assignment_type=filter_value) temporary_aggregate = True if filter_key == 'name': items = items.filter(name__icontains=filter_value) temporary_aggregate = True if filter_key == 'date_begin': items = items.filter(date__gt=filter_value) temporary_aggregate = True if filter_key == 'date_end': items = items.filter(date__lt=filter_value) temporary_aggregate = True filtered = True else: # show only the active marking period by default active_mps = course.marking_period.filter(active=True) if active_mps and not for_export: # no default filtering on export requests filter_form = GradebookFilterForm(initial={'marking_period': active_mps[0]}) items = items.filter(marking_period=active_mps[0]) filtered = True 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, aggregatetask__in=AggregateTask.objects.all()).values_list('pk', flat=True).distinct() # 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 student.average is not None: totals['course_average'] += Aggregate.objects.get(pk=student.average_pk).cached_value # can't use a substitution totals['course_average_count'] += 1 if filtered: cleaned_or_initial = getattr(filter_form, 'cleaned_data', filter_form.initial) filter_category = cleaned_or_initial.get('category', None) filter_marking_period = cleaned_or_initial.get('marking_period', None) filter_items = items if temporary_aggregate else None student.filtered_average, student.filtered_average_pk = gradebook_get_average_and_pk( student, course, filter_category, filter_marking_period, filter_items) if student.filtered_average is not None: totals['filtered_average'] += Aggregate.objects.get(pk=student.filtered_average_pk).cached_value # can't use a substitution totals['filtered_average_count'] += 1 if school_year.benchmark_grade and extra_info == 'demonstrations': # 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() totals['standards_passing'] += standards_count_passing totals['standards_all'] += standards_count_total 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() totals['filtered_standards_passing'] += standards_count_passing totals['filtered_standards_all'] += standards_count_total 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 if extra_info == 'averages': for item in items: # listify the QuerySet now so we can modify it and use it in the template # if the template just reads the DB and instantiates new objects, they will not have our class_average attribute item.demonstration_list = list(item.demonstration_set.all()) for demonstration in item.demonstration_list: # TODO: make sure we only count enrolled students demonstration.class_average = demonstration.mark_set.aggregate(Avg('mark'))['mark__avg'] try: demonstration.class_average = Decimal(demonstration.class_average).quantize(quantizer) except TypeError: # e.g. Decimal(None) pass item.class_average = item.mark_set.aggregate(Avg('mark'))['mark__avg'] try: item.class_average = Decimal(item.class_average).quantize(quantizer) except TypeError: # e.g. Decimal(None) pass # Gather visual flagging criteria absolute_category_flag_criteria = {} normalized_category_flag_criteria = {} for category in Category.objects.filter(item__in=items).distinct(): if category.fixed_points_possible: # assume the criterion is absolute if the category has fixed # of points possible use_dict = absolute_category_flag_criteria else: # assume we need to divide the mark by points possible before comparing to criterion use_dict = normalized_category_flag_criteria use_dict[category.pk] = [] substitutions = calculation_rule.substitution_set.filter(apply_to_departments=course.department, apply_to_categories=category, flag_visually=True) for substitution in substitutions: use_dict[category.pk].append(substitution.operator + ' ' + str(substitution.match_value)) # calculate course-wide averages and counts if totals['course_average_count']: totals['course_average'] = Decimal(totals['course_average'] / totals['course_average_count']).quantize(quantizer) else: totals['course_average'] = None if totals['filtered_average_count']: totals['filtered_average'] = Decimal(totals['filtered_average'] / totals['filtered_average_count']).quantize(quantizer) else: totals['filtered_average'] = None if totals['standards_all']: totals['standards_text'] = '{} / {} ({:.0f}%)'.format(totals['standards_passing'], totals['standards_all'], 100.0 * totals['standards_passing'] / totals['standards_all']) else: totals['standards_text'] = None if totals['filtered_standards_all']: totals['filtered_standards_text'] = '{} / {} ({:.0f}%)'.format(totals['filtered_standards_passing'], totals['filtered_standards_all'], 100.0 * totals['filtered_standards_passing'] / totals['filtered_standards_all']) else: totals['filtered_standards_text'] = None data_dictionary = { '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, 'absolute_category_flag_criteria': absolute_category_flag_criteria, 'normalized_category_flag_criteria': normalized_category_flag_criteria, 'extra_info': extra_info, 'totals': totals, 'item_form_exclude': ItemForm().get_user_excludes(), } if for_export: return data_dictionary else: return render_to_response('benchmark_grade/gradebook.html', data_dictionary, RequestContext(request, {}),)
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) 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, {}), )
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 temporary_aggregate = 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) temporary_aggregate = True if filter_key == 'marking_period': items = items.filter(marking_period=filter_value) if filter_key == 'benchmark': items = items.filter(benchmark__in=filter_value) temporary_aggregate = True if filter_key == 'category': items = items.filter(category=filter_value) if filter_key == 'assignment_type': items = items.filter(assignment_type=filter_value) temporary_aggregate = True if filter_key == 'name': items = items.filter(name__icontains=filter_value) temporary_aggregate = True if filter_key == 'date_begin': items = items.filter(date__gt=filter_value) temporary_aggregate = True if filter_key == 'date_end': items = items.filter(date__lt=filter_value) temporary_aggregate = True 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]) filtered = True 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, aggregatetask__in=AggregateTask.objects.all()).values_list('pk', flat=True).distinct() # 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: cleaned_or_initial = getattr(filter_form, 'cleaned_data', filter_form.initial) filter_category = cleaned_or_initial.get('category', None) filter_marking_period = cleaned_or_initial.get('marking_period', None) filter_items = items if temporary_aggregate else None student.filtered_average = gradebook_get_average(student, course, filter_category, filter_marking_period, filter_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, {}),)