def student_family_grade_common(student): PASSING_GRADE = 3 # TODO: pull config value. Roche has it set to something crazy now and I don't want to deal with it school_year = SchoolYear.objects.get(active_year=True) mps = MarkingPeriod.objects.filter(school_year=school_year, start_date__lte=datetime.date.today()).order_by('-start_date') calculation_rule = benchmark_find_calculation_rule(school_year) for mp in mps: mp.courses = Course.objects.filter(courseenrollment__user=student, graded=True, marking_period=mp).order_by('fullname') for course in mp.courses: course.categories = Category.objects.filter(item__course=course, item__mark__student=student).distinct() course.category_by_name = {} for category in course.categories: category.percentage = calculation_rule.per_course_category_set.get( category=category, apply_to_departments=course.department).weight * 100 category.percentage = category.percentage.quantize(Decimal('0')) category.average = gradebook_get_average(student, course, category, mp, None) items = Item.objects.filter(course=course, category=category, marking_period=mp, mark__student=student).annotate(best_mark=Max('mark__mark')) counts = {} counts['total'] = items.exclude(best_mark=None).distinct().count() counts['missing'] = items.filter(best_mark__lt=PASSING_GRADE).distinct().count() counts['passing'] = items.filter(best_mark__gte=PASSING_GRADE).distinct().count() if counts['total']: counts['percentage'] = (Decimal(counts['passing']) / counts['total'] * 100).quantize(Decimal('0')) course.category_by_name[category.name] = counts course.average = gradebook_get_average(student, course, None, mp, None) return mps
def student_family_grade_course_detail_common(student, course, mp, items=None): # TODO: move into CalculationRule? CATEGORY_NAME_TO_FLAG_CRITERIA = { 'Standards': { 'best_mark__lt': 3 }, 'Engagement': { 'best_mark__lt': 3 }, 'Organization': { 'best_mark__lt': 3 }, 'Daily Practice': { 'best_mark__lte': 0 }, } if items is None: items = Item.objects specific_items = False else: specific_items = True # always filter in case a bad person passes us items from a different course items = items.filter(course=course, mark__student=student) if mp is None: mps = MarkingPeriod.objects.filter( item__in=items).distinct().order_by('-start_date') else: mps = (mp, ) for mp in mps: mp_items = items.filter(marking_period=mp) mp.categories = Category.objects.filter(item__in=mp_items).distinct() for category in mp.categories: category_items = mp_items.filter(category=category).annotate( best_mark=Max('mark__mark')).exclude(best_mark=None) item_names = category_items.values_list('name').distinct() category.item_groups = {} for item_name_tuple in item_names: item_name = item_name_tuple[0] category.item_groups[item_name] = category_items.filter( name=item_name).distinct() if specific_items: # get a disposable average for these specific items category.average = gradebook_get_average( student, course, category, mp, category_items) else: category.average = gradebook_get_average( student, course, category, mp, None) category.flagged_item_pks = [] if category.name in CATEGORY_NAME_TO_FLAG_CRITERIA: category.flagged_item_pks = category_items.filter( **CATEGORY_NAME_TO_FLAG_CRITERIA[ category.name]).values_list('pk', flat=True) return mps
def ajax_save_grade(request): if 'mark_id' in request.POST and 'value' in request.POST: mark_id = request.POST['mark_id'].strip() value = request.POST['value'].strip() try: mark = Mark.objects.get(id=mark_id) except Mark.DoesNotExist: return HttpResponse('NO MARK WITH ID ' + mark_id, status=404) if not request.user.is_superuser and not request.user.groups.filter(name='registrar').count() \ and request.user.username != mark.item.course.teacher.username \ and not mark.item.course.secondary_teachers.filter(username=request.user.username).count(): return HttpResponse(status=403) if len(value) and value.lower != 'none': mark.mark = value else: mark.mark = None value = 'None' # temporarily log who's changing stuff since i'll have to manually recalculate averages later mark.description += ',' + request.user.username try: mark.full_clean() mark.save() except Exception as e: return HttpResponse(e, status=400) gradebook_recalculate_on_mark_change(mark) # just the whole course average for now # TODO: update filtered average average = gradebook_get_average(mark.student, mark.item.course, None, None, None) return HttpResponse(json.dumps({'success': 'SUCCESS', 'value': value, 'average': str(average)})) else: return HttpResponse('POST DATA INCOMPLETE', status=400)
def student_family_grade_common(student): PASSING_GRADE = 3 # TODO: pull config value. Roche has it set to something crazy now and I don't want to deal with it school_year = SchoolYear.objects.get(active_year=True) mps = MarkingPeriod.objects.filter( school_year=school_year, start_date__lte=datetime.date.today()).order_by('-start_date') calculation_rule = benchmark_find_calculation_rule(school_year) for mp in mps: mp.courses = Course.objects.filter( courseenrollment__user=student, graded=True, marking_period=mp).order_by('fullname') for course in mp.courses: course.categories = Category.objects.filter( item__course=course, item__mark__student=student).distinct() course.category_by_name = {} for category in course.categories: category.percentage = calculation_rule.per_course_category_set.get( category=category, apply_to_departments=course.department).weight * 100 category.percentage = category.percentage.quantize( Decimal('0')) category.average = gradebook_get_average( student, course, category, mp, None) items = Item.objects.filter(course=course, category=category, marking_period=mp, mark__student=student).annotate( best_mark=Max('mark__mark')) counts = {} counts['total'] = items.exclude( best_mark=None).distinct().count() counts['missing'] = items.filter( best_mark__lt=PASSING_GRADE).distinct().count() counts['passing'] = items.filter( best_mark__gte=PASSING_GRADE).distinct().count() if counts['total']: counts['percentage'] = (Decimal(counts['passing']) / counts['total'] * 100).quantize( Decimal('0')) course.category_by_name[category.name] = counts course.average = gradebook_get_average(student, course, None, mp, None) return mps
def student_family_grade_course_detail_common(student, course, mp, items=None): # TODO: move into CalculationRule? CATEGORY_NAME_TO_FLAG_CRITERIA = { 'Standards': {'best_mark__lt': 3}, 'Engagement': {'best_mark__lt': 3}, 'Organization': {'best_mark__lt': 3}, 'Daily Practice': {'best_mark__lte': 0}, } if items is None: items = Item.objects specific_items = False else: specific_items = True # always filter in case a bad person passes us items from a different course items = items.filter(course=course, mark__student=student) if mp is None: mps = MarkingPeriod.objects.filter(item__in=items).distinct().order_by('-start_date') else: mps = (mp,) for mp in mps: mp_items = items.filter(marking_period=mp) mp.categories = Category.objects.filter(item__in=mp_items).distinct() for category in mp.categories: category_items = mp_items.filter(category=category).annotate(best_mark=Max('mark__mark')).exclude(best_mark=None) item_names = category_items.values_list('name').distinct() category.item_groups = {} for item_name_tuple in item_names: item_name = item_name_tuple[0] category.item_groups[item_name] = category_items.filter(name=item_name).distinct() if specific_items: # get a disposable average for these specific items category.average = gradebook_get_average(student, course, category, mp, category_items) else: category.average = gradebook_get_average(student, course, category, mp, None) category.flagged_item_pks = [] if category.name in CATEGORY_NAME_TO_FLAG_CRITERIA: category.flagged_item_pks = category_items.filter(**CATEGORY_NAME_TO_FLAG_CRITERIA[category.name]).values_list('pk', flat=True) return mps
def ajax_save_grade(request): if 'mark_id' in request.POST and 'value' in request.POST: mark_id = request.POST['mark_id'].strip() value = request.POST['value'].strip() try: mark = Mark.objects.get(id=mark_id) except Mark.DoesNotExist: return HttpResponse('NO MARK WITH ID ' + mark_id, status=404) if not request.user.is_superuser and not request.user.groups.filter(name='registrar').count() \ and request.user.username != mark.item.course.teacher.username \ and not mark.item.course.secondary_teachers.filter(username=request.user.username).count(): return HttpResponse(status=403) if len(value) and value.lower != 'none': mark.mark = value else: mark.mark = None value = 'None' # temporarily log who's changing stuff since i'll have to manually recalculate averages later mark.description += ',' + request.user.username try: mark.full_clean() mark.save() except Exception as e: return HttpResponse(e, status=400) gradebook_recalculate_on_mark_change(mark) # just the whole course average for now # TODO: update filtered average average = gradebook_get_average(mark.student, mark.item.course, None, None, None) return HttpResponse( json.dumps({ 'success': 'SUCCESS', 'value': value, 'average': str(average) })) else: return HttpResponse('POST DATA INCOMPLETE', status=400)
def gradebook(request, course_id): course = get_object_or_404(Course, pk=course_id) school_year = course.marking_period.all()[0].school_year calculation_rule = benchmark_find_calculation_rule(school_year) 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')) # 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(is_active=True,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 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() 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 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 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, 'extra_info': extra_info, }, 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 benchmark_report_card(template, options, students, format="odt"): PASSING_GRADE = 3 # TODO: pull config value. Roche has it set to something crazy now and I don't want to deal with it data = get_default_data() for_date = options['date'] try: omit_substitutions = options['omit_substitutions'] except KeyError: omit_substitutions = False school_year = SchoolYear.objects.filter( start_date__lt=for_date).order_by('-start_date')[0] calculation_rule = benchmark_find_calculation_rule(school_year) attendance_marking_periods = MarkingPeriod.objects.filter( school_year=school_year, start_date__lt=for_date, show_reports=True) marking_period = attendance_marking_periods.order_by('-start_date')[0] for student in students: student.year_courses = Course.objects.filter( courseenrollment__user=student, graded=True, marking_period__school_year=school_year, ).distinct().order_by('department') student.courses = [] student.count_total_by_category_name = {} student.count_missing_by_category_name = {} student.count_passing_by_category_name = {} for course in student.year_courses: course.average = gradebook_get_average( student, course, None, marking_period, None, omit_substitutions=omit_substitutions) course.current_marking_periods = course.marking_period.filter( start_date__lt=for_date).order_by('start_date') course.categories = Category.objects.filter( item__course=course, item__mark__student=student).distinct() course.category_by_name = {} for category in course.categories: try: category.weight_percentage = calculation_rule.per_course_category_set.get( category=category, apply_to_departments=course.department ).weight * Decimal(100) except CalculationRulePerCourseCategory.DoesNotExist: category.weight_percentage = Decimal(0) category.weight_percentage = category.weight_percentage.quantize( Decimal('0'), ROUND_HALF_UP) category.overall_count_total = 0 category.overall_count_missing = 0 category.overall_count_passing = 0 for course_marking_period in course.current_marking_periods: course_marking_period.category = category course_marking_period.category.average = gradebook_get_average( student, course, category, course_marking_period, None, omit_substitutions=omit_substitutions) items = Item.objects.filter( course=course, marking_period=course_marking_period, category=category, mark__student=student).annotate( best_mark=Max('mark__mark')).exclude( best_mark=None) course_marking_period.category.count_total = items.exclude( best_mark=None).distinct().count() course_marking_period.category.count_missing = items.filter( best_mark__lt=PASSING_GRADE).distinct().count() course_marking_period.category.count_passing = items.filter( best_mark__gte=PASSING_GRADE).distinct().count() if course_marking_period.category.count_total: course_marking_period.category.count_percentage = ( Decimal( course_marking_period.category.count_passing) / course_marking_period.category.count_total * 100).quantize(Decimal('0', ROUND_HALF_UP)) if course.department is not None and course.department.name == 'Corporate Work Study': # TODO: Remove this terrible hack course_marking_period.category.count_passing = course_marking_period.category.count_total course_marking_period.category.count_missing = 0 course_marking_period.category.count_percentage = 100 category.overall_count_total += course_marking_period.category.count_total category.overall_count_missing += course_marking_period.category.count_missing category.overall_count_passing += course_marking_period.category.count_passing item_names = items.values_list('name').distinct() course_marking_period.category.item_groups = [] for item_name_tuple in item_names: item_name = item_name_tuple[0] item_group = struct() item_group.name = item_name item_group.items = items.filter( name=item_name).distinct() course_marking_period.category.item_groups.append( item_group) course_marking_period.category_by_name = getattr( course_marking_period, 'category_by_name', {}) # make a copy so we don't overwrite the last marking period's data course_marking_period.category_by_name[ category.name] = copy.copy( course_marking_period.category) # the last time through the loop is the most current marking period, # so give that to anyone who doesn't request an explicit marking period #category = course_marking_period.category course.category_by_name[category.name] = category if category.overall_count_total: category.overall_count_percentage = ( Decimal(category.overall_count_passing) / category.overall_count_total * 100).quantize( Decimal('0', ROUND_HALF_UP)) student.count_total_by_category_name[ category.name] = student.count_total_by_category_name.get( category.name, 0) + category.overall_count_total student.count_missing_by_category_name[ category. name] = student.count_missing_by_category_name.get( category.name, 0) + category.overall_count_missing student.count_passing_by_category_name[ category. name] = student.count_passing_by_category_name.get( category.name, 0) + category.overall_count_passing # some components of report need access to courses for entire year (student.year_courses) # but we must keep student.courses restricted to the current marking period for compatibility if marking_period in course.marking_period.all(): student.courses.append(course) student.count_percentage_by_category_name = {} for category_name, value in student.count_total_by_category_name.items( ): if value: student.count_percentage_by_category_name[category_name] = ( Decimal( student.count_passing_by_category_name[category_name]) / value * 100).quantize(Decimal('0', ROUND_HALF_UP)) # make categories available student.session_gpa = student.calculate_gpa_mp(marking_period) # Cannot just rely on student.gpa for the cumulative GPA; it does not reflect report's date student.current_report_cumulative_gpa = student.calculate_gpa(for_date) #Attendance for marking period i = 1 student.absent_total = 0 student.tardy_total = 0 student.dismissed_total = 0 student.attendance_marking_periods = [] for mp in attendance_marking_periods.order_by('start_date'): absent = student.student_attn.filter( status__absent=True, date__range=(mp.start_date, mp.end_date)).count() tardy = student.student_attn.filter( status__tardy=True, date__range=(mp.start_date, mp.end_date)).count() dismissed = student.student_attn.filter( status__code="D", date__range=(mp.start_date, mp.end_date)).count() student.absent_total += absent student.tardy_total += tardy student.dismissed_total += dismissed amp = struct() amp.absent = absent amp.tardy = tardy amp.dismissed = dismissed amp.number = i student.attendance_marking_periods.append(amp) i += 1 data['students'] = students data['school_year'] = school_year data[ 'marking_period'] = marking_period.name # just passing object makes appy think it's undefined data['draw_gauge'] = draw_gauge filename = 'output' #return pod_save(filename, ".pdf", data, template) return pod_save(filename, "." + str(format), data, template)
def student_report(request, student_pk=None, course_pk=None, marking_period_pk=None): authorized = False family_available_students = None try: # is it a student? student = Student.objects.get(username=request.user.username) # ok! we'll ignore student_pk, and the student is authorized to see itself authorized = True except: student = None if not student: if request.user.is_staff: # hey, it's a staff member! student = get_object_or_404(Student, pk=student_pk) authorized = True else: # maybe it's a family member? family_available_students = Student.objects.filter(family_access_users=request.user) if student_pk: student = get_object_or_404(Student, pk=student_pk) if student in family_available_students: authorized = True elif family_available_students.count(): student = family_available_students[0] authorized = True # did all that make us comfortable with proceeding? if not authorized: error_message = 'Sorry, you are not authorized to see grades for this student. Please contact the school registrar.' return render_to_response('benchmark_grade/student_grade.html', { 'error_message': error_message, }, RequestContext(request, {}),) # is this a summary or detail report? if not course_pk: # summary report for all courses PASSING_GRADE = 3 # TODO: pull config value. Roche has it set to something crazy now and I don't want to deal with it school_year = SchoolYear.objects.get(active_year=True) all_mps = MarkingPeriod.objects.filter(school_year=school_year, start_date__lte=datetime.date.today()).order_by('-start_date') if marking_period_pk is None: if all_mps.count(): mps = (all_mps[0],) else: mps = () else: mps = all_mps.filter(pk=marking_period_pk) mp_pks = [x.pk for x in mps] other_mps = all_mps.exclude(pk__in=mp_pks) calculation_rule = benchmark_find_calculation_rule(school_year) for mp in mps: mp.courses = Course.objects.filter(courseenrollment__user=student, graded=True, marking_period=mp).order_by('fullname') for course in mp.courses: course.categories = Category.objects.filter(item__course=course, item__mark__student=student).distinct() course.category_by_name = {} for category in course.categories: try: category.percentage = calculation_rule.per_course_category_set.get( category=category, apply_to_departments=course.department).weight * 100 category.percentage = category.percentage.quantize(Decimal('0')) except CalculationRulePerCourseCategory.DoesNotExist: # sometimes a course has items belonging to categories that don't count in the course average # but we want to display them anyway category.percentage = 0 category.average = gradebook_get_average(student, course, category, mp, None) items = Item.objects.filter(course=course, category=category, marking_period=mp, mark__student=student).annotate(best_mark=Max('mark__mark')) counts = {} counts['total'] = items.exclude(best_mark=None).distinct().count() counts['missing'] = items.filter(best_mark__lt=PASSING_GRADE).distinct().count() counts['passing'] = items.filter(best_mark__gte=PASSING_GRADE).distinct().count() if counts['total']: counts['percentage'] = (Decimal(counts['passing']) / counts['total'] * 100).quantize(Decimal('0')) course.category_by_name[category.name] = counts course.average = gradebook_get_average(student, course, None, mp, None) try: course.legacy_grade = course.grade_set.get(student=student, marking_period=mp).get_grade() if course.legacy_grade == '': course.legacy_grade = None # be consistent except Grade.DoesNotExist: course.legacy_grade = None return render_to_response('benchmark_grade/student_grade.html', { 'student': student, 'available_students': family_available_students, 'mps': mps, 'other_mps': other_mps }, RequestContext(request, {}),) else: # detail report for a single course course = get_object_or_404(Course, pk=course_pk) # TODO: move into CalculationRule? CATEGORY_NAME_TO_FLAG_CRITERIA = { 'Standards': {'best_mark__lt': 3}, 'Engagement': {'best_mark__lt': 3}, 'Organization': {'best_mark__lt': 3}, 'Daily Practice': {'best_mark__lte': 0}, } # be careful of empty string in POST, as int('') raises ValueError if 'item_pks' in request.POST and len(request.POST['item_pks']): item_pks = request.POST['item_pks'].split(',') items = Item.objects.filter(pk__in=item_pks) specific_items = True else: items = Item.objects specific_items = False # always filter in case a bad person passes us items from a different course items = items.filter(course=course, mark__student=student) all_mps = MarkingPeriod.objects.filter(item__in=items).distinct().order_by('-start_date') if specific_items: mps = all_mps other_mps = () else: if marking_period_pk is None: if all_mps.count(): mps = (all_mps[0],) else: mps = () else: mps = all_mps.filter(pk=marking_period_pk) mp_pks = [x.pk for x in mps] other_mps = all_mps.exclude(pk__in=mp_pks) #if marking_period_pk: # mp = get_object_or_404(MarkingPeriod, pk=marking_period_pk) # mps = (mp,) #else: # mps = MarkingPeriod.objects.filter(item__in=items).distinct().order_by('-start_date') for mp in mps: mp_items = items.filter(marking_period=mp) mp.categories = Category.objects.filter(item__in=mp_items).distinct() for category in mp.categories: category_items = mp_items.filter(category=category).annotate(best_mark=Max('mark__mark')).exclude(best_mark=None) item_names = category_items.values_list('name').distinct() category.item_groups = {} for item_name_tuple in item_names: item_name = item_name_tuple[0] category.item_groups[item_name] = category_items.filter(name=item_name).distinct() if specific_items: # get a disposable average for these specific items category.average = gradebook_get_average(student, course, category, mp, category_items) else: category.average = gradebook_get_average(student, course, category, mp, None) category.flagged_item_pks = [] if category.name in CATEGORY_NAME_TO_FLAG_CRITERIA: category.flagged_item_pks = category_items.filter(**CATEGORY_NAME_TO_FLAG_CRITERIA[category.name]).values_list('pk', flat=True) return render_to_response('benchmark_grade/student_grade_course_detail.html', { 'student': student, 'available_students': family_available_students, 'course': course, 'mps': mps, 'other_mps': other_mps }, RequestContext(request, {}),)
def benchmark_report_card(grade_template_report, template, options, students, format="odt"): PASSING_GRADE = ( 3 ) # TODO: pull config value. Roche has it set to something crazy now and I don't want to deal with it data = grade_template_report.data for_date = grade_template_report.for_date try: omit_substitutions = options["omit_substitutions"] except KeyError: omit_substitutions = False school_year = SchoolYear.objects.filter(start_date__lt=for_date).order_by("-start_date")[0] calculation_rule = benchmark_find_calculation_rule(school_year) attendance_marking_periods = MarkingPeriod.objects.filter( school_year=school_year, start_date__lt=for_date, show_reports=True ) marking_period = attendance_marking_periods.order_by("-start_date")[0] for student in students: student.year_courses = ( Course.objects.filter(courseenrollment__user=student, graded=True, marking_period__school_year=school_year) .distinct() .order_by("department") ) student.courses = [] student.count_total_by_category_name = {} student.count_missing_by_category_name = {} student.count_passing_by_category_name = {} for course in student.year_courses: course.average = gradebook_get_average( student, course, None, marking_period, None, omit_substitutions=omit_substitutions ) course.current_marking_periods = course.marking_period.filter(start_date__lt=for_date).order_by( "start_date" ) course.categories = Category.objects.filter(item__course=course, item__mark__student=student).distinct() course.category_by_name = {} for category in course.categories: try: category.weight_percentage = calculation_rule.per_course_category_set.get( category=category, apply_to_departments=course.department ).weight * Decimal(100) except CalculationRulePerCourseCategory.DoesNotExist: category.weight_percentage = Decimal(0) category.weight_percentage = category.weight_percentage.quantize(Decimal("0"), ROUND_HALF_UP) category.overall_count_total = 0 category.overall_count_missing = 0 category.overall_count_passing = 0 for course_marking_period in course.current_marking_periods: course_marking_period.category = category course_marking_period.category.average = gradebook_get_average( student, course, category, course_marking_period, None, omit_substitutions=omit_substitutions ) items = ( Item.objects.filter( course=course, marking_period=course_marking_period, category=category, mark__student=student, ) .annotate(best_mark=Max("mark__mark")) .exclude(best_mark=None) ) course_marking_period.category.count_total = items.exclude(best_mark=None).distinct().count() course_marking_period.category.count_missing = ( items.filter(best_mark__lt=PASSING_GRADE).distinct().count() ) course_marking_period.category.count_passing = ( items.filter(best_mark__gte=PASSING_GRADE).distinct().count() ) if course_marking_period.category.count_total: course_marking_period.category.count_percentage = ( Decimal(course_marking_period.category.count_passing) / course_marking_period.category.count_total * 100 ).quantize(Decimal("0", ROUND_HALF_UP)) if ( course.department is not None and course.department.name == "Corporate Work Study" ): # TODO: Remove this terrible hack course_marking_period.category.count_passing = course_marking_period.category.count_total course_marking_period.category.count_missing = 0 course_marking_period.category.count_percentage = 100 category.overall_count_total += course_marking_period.category.count_total category.overall_count_missing += course_marking_period.category.count_missing category.overall_count_passing += course_marking_period.category.count_passing item_names = items.values_list("name").distinct() course_marking_period.category.item_groups = [] for item_name_tuple in item_names: item_name = item_name_tuple[0] item_group = struct() item_group.name = item_name item_group.items = items.filter(name=item_name).distinct() course_marking_period.category.item_groups.append(item_group) course_marking_period.category_by_name = getattr(course_marking_period, "category_by_name", {}) # make a copy so we don't overwrite the last marking period's data course_marking_period.category_by_name[category.name] = copy.copy(course_marking_period.category) # the last time through the loop is the most current marking period, # so give that to anyone who doesn't request an explicit marking period # category = course_marking_period.category course.category_by_name[category.name] = category if category.overall_count_total: category.overall_count_percentage = ( Decimal(category.overall_count_passing) / category.overall_count_total * 100 ).quantize(Decimal("0", ROUND_HALF_UP)) student.count_total_by_category_name[category.name] = ( student.count_total_by_category_name.get(category.name, 0) + category.overall_count_total ) student.count_missing_by_category_name[category.name] = ( student.count_missing_by_category_name.get(category.name, 0) + category.overall_count_missing ) student.count_passing_by_category_name[category.name] = ( student.count_passing_by_category_name.get(category.name, 0) + category.overall_count_passing ) # some components of report need access to courses for entire year (student.year_courses) # but we must keep student.courses restricted to the current marking period for compatibility if marking_period in course.marking_period.all(): student.courses.append(course) student.count_percentage_by_category_name = {} for category_name, value in student.count_total_by_category_name.items(): if value: student.count_percentage_by_category_name[category_name] = ( Decimal(student.count_passing_by_category_name[category_name]) / value * 100 ).quantize(Decimal("0", ROUND_HALF_UP)) # make categories available student.session_gpa = student.calculate_gpa_mp(marking_period) # Cannot just rely on student.gpa for the cumulative GPA; it does not reflect report's date student.current_report_cumulative_gpa = student.calculate_gpa(for_date) # Attendance for marking period i = 1 student.absent_total = 0 student.tardy_total = 0 student.dismissed_total = 0 student.attendance_marking_periods = [] for mp in attendance_marking_periods.order_by("start_date"): absent = student.student_attn.filter(status__absent=True, date__range=(mp.start_date, mp.end_date)).count() tardy = student.student_attn.filter(status__tardy=True, date__range=(mp.start_date, mp.end_date)).count() dismissed = student.student_attn.filter(status__code="D", date__range=(mp.start_date, mp.end_date)).count() student.absent_total += absent student.tardy_total += tardy student.dismissed_total += dismissed amp = struct() amp.absent = absent amp.tardy = tardy amp.dismissed = dismissed amp.number = i student.attendance_marking_periods.append(amp) i += 1 data["students"] = students data["school_year"] = school_year data["marking_period"] = marking_period.name # just passing object makes appy think it's undefined data["draw_gauge"] = draw_gauge return grade_template_report.pod_save(template)
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 get_benchmark_report_card_data(report_context, appy_context, students): PASSING_GRADE = 3 # TODO: pull config value. Roche has it set to something crazy now and I don't want to deal with it data = appy_context for_date = report_context['date_end'] try: omit_substitutions = report_context['omit_substitutions'] except KeyError: omit_substitutions = False school_year = SchoolYear.objects.filter( start_date__lt=for_date).order_by('-start_date')[0] calculation_rule = benchmark_find_calculation_rule(school_year) attendance_marking_periods = MarkingPeriod.objects.filter( school_year=school_year, start_date__lt=for_date, show_reports=True) marking_period = attendance_marking_periods.order_by('-start_date')[0] for student in students: # Backwards compatibility for existing templates student.fname = student.first_name student.lname = student.last_name student.year_course_sections = CourseSection.objects.filter( courseenrollment__user=student, course__graded=True, marking_period__school_year=school_year, ).distinct().order_by('course__department') student.course_sections = [] student.count_total_by_category_name = {} student.count_missing_by_category_name = {} student.count_passing_by_category_name = {} for course_section in student.year_course_sections: course_section.average = gradebook_get_average( student, course_section, None, marking_period, None, omit_substitutions=omit_substitutions) course_section.current_marking_periods = course_section.marking_period.filter( start_date__lt=for_date).order_by('start_date') course_section.categories = Category.objects.filter( item__course_section=course_section, item__mark__student=student).distinct() course_section.category_by_name = {} for category in course_section.categories: try: category.weight_percentage = calculation_rule.per_course_category_set.get( category=category, apply_to_departments=course_section.department ).weight * Decimal(100) except CalculationRulePerCourseCategory.DoesNotExist: category.weight_percentage = Decimal(0) category.weight_percentage = category.weight_percentage.quantize( Decimal('0'), ROUND_HALF_UP) category.overall_count_total = 0 category.overall_count_missing = 0 category.overall_count_passing = 0 for course_section_marking_period in course_section.current_marking_periods: course_section_marking_period.category = category course_section_marking_period.category.average = gradebook_get_average( student, course_section, category, course_section_marking_period, None, omit_substitutions=omit_substitutions) items = Item.objects.filter( course_section=course_section, marking_period=course_section_marking_period, category=category, mark__student=student).annotate( best_mark=Max('mark__mark')).exclude( best_mark=None) course_section_marking_period.category.count_total = items.exclude( best_mark=None).distinct().count() course_section_marking_period.category.count_missing = items.filter( best_mark__lt=PASSING_GRADE).distinct().count() course_section_marking_period.category.count_passing = items.filter( best_mark__gte=PASSING_GRADE).distinct().count() if course_section_marking_period.category.count_total: course_section_marking_period.category.count_percentage = ( Decimal(course_section_marking_period.category. count_passing) / course_section_marking_period.category.count_total * 100).quantize(Decimal('0', ROUND_HALF_UP)) # TODO: We assume here that flagging something visually means it's "missing." This should be done in a better way that's not opaque to users. if not calculation_rule.substitution_set.filter( apply_to_departments=course_section.department, flag_visually=True).exists(): course_section_marking_period.category.count_passing = course_section_marking_period.category.count_total course_section_marking_period.category.count_missing = 0 course_section_marking_period.category.count_percentage = 100 category.overall_count_total += course_section_marking_period.category.count_total category.overall_count_missing += course_section_marking_period.category.count_missing category.overall_count_passing += course_section_marking_period.category.count_passing item_names = items.values_list('name').distinct() course_section_marking_period.category.item_groups = [] for item_name_tuple in item_names: item_name = item_name_tuple[0] item_group = struct() item_group.name = item_name item_group.items = items.filter( name=item_name).distinct() course_section_marking_period.category.item_groups.append( item_group) course_section_marking_period.category_by_name = getattr( course_section_marking_period, 'category_by_name', {}) # make a copy so we don't overwrite the last marking period's data course_section_marking_period.category_by_name[ category.name] = copy.copy( course_section_marking_period.category) # the last time through the loop is the most current marking period, # so give that to anyone who doesn't request an explicit marking period #category = course_marking_period.category course_section.category_by_name[category.name] = category if category.overall_count_total: category.overall_count_percentage = ( Decimal(category.overall_count_passing) / category.overall_count_total * 100).quantize( Decimal('0', ROUND_HALF_UP)) student.count_total_by_category_name[ category.name] = student.count_total_by_category_name.get( category.name, 0) + category.overall_count_total student.count_missing_by_category_name[ category. name] = student.count_missing_by_category_name.get( category.name, 0) + category.overall_count_missing student.count_passing_by_category_name[ category. name] = student.count_passing_by_category_name.get( category.name, 0) + category.overall_count_passing # some components of report need access to course sections for entire year (student.year_course_sections) # but we must keep student.course_sections restricted to the current marking period for compatibility if marking_period in course_section.marking_period.all(): student.course_sections.append(course_section) student.count_percentage_by_category_name = {} for category_name, value in student.count_total_by_category_name.items( ): if value: student.count_percentage_by_category_name[category_name] = ( Decimal( student.count_passing_by_category_name[category_name]) / value * 100).quantize(Decimal('0', ROUND_HALF_UP)) # make categories available try: student.session_gpa = student.studentmarkingperiodgrade_set.get( marking_period=marking_period).grade except StudentMarkingPeriodGrade.DoesNotExist: student.session_gpa = None # Cannot just rely on student.gpa for the cumulative GPA; it does not reflect report's date student.current_report_cumulative_gpa = student.calculate_gpa(for_date) #Attendance for marking period i = 1 student.absent_total = 0 student.tardy_total = 0 student.dismissed_total = 0 student.attendance_marking_periods = [] for mp in attendance_marking_periods.order_by('start_date'): absent = student.student_attn.filter( status__absent=True, date__range=(mp.start_date, mp.end_date)).count() tardy = student.student_attn.filter( status__tardy=True, date__range=(mp.start_date, mp.end_date)).count() dismissed = student.student_attn.filter( status__code="D", date__range=(mp.start_date, mp.end_date)).count() student.absent_total += absent student.tardy_total += tardy student.dismissed_total += dismissed amp = struct() amp.absent = absent amp.tardy = tardy amp.dismissed = dismissed amp.number = i student.attendance_marking_periods.append(amp) i += 1 data['students'] = students data['school_year'] = school_year data[ 'marking_period'] = marking_period.name # just passing object makes appy think it's undefined data['draw_gauge'] = draw_gauge
def benchmark_report_card(template, options, students, format="odt"): PASSING_GRADE = 3 # TODO: pull config value. Roche has it set to something crazy now and I don't want to deal with it data = get_default_data() for_date = options['date'] school_year = SchoolYear.objects.filter(start_date__lt=for_date).order_by('-start_date')[0] calculation_rule = benchmark_find_calculation_rule(school_year) attendance_marking_periods = MarkingPeriod.objects.filter(school_year=school_year, start_date__lt=for_date, show_reports=True) marking_period = attendance_marking_periods.order_by('-start_date')[0] for student in students: student.courses = Course.objects.filter( courseenrollment__user=student, graded=True, marking_period=marking_period, ).distinct().order_by('department') student.count_total_by_category_name = {} student.count_missing_by_category_name = {} student.count_passing_by_category_name = {} for course in student.courses: course.average = gradebook_get_average(student, course, None, marking_period, None) course.current_marking_periods = course.marking_period.filter(start_date__lt=for_date).order_by('start_date') course.categories = Category.objects.filter(item__course=course, item__mark__student=student).distinct() course.category_by_name = {} for category in course.categories: try: category.weight_percentage = calculation_rule.per_course_category_set.get(category=category, apply_to_departments=course.department).weight * Decimal(100) except CalculationRulePerCourseCategory.DoesNotExist: category.weight_percentage = Decimal(0) category.weight_percentage = category.weight_percentage.quantize(Decimal('0'), ROUND_HALF_UP) category.overall_count_total = 0 category.overall_count_missing = 0 category.overall_count_passing = 0 for course_marking_period in course.current_marking_periods: course_marking_period.category = category course_marking_period.category.average = gradebook_get_average(student, course, category, course_marking_period, None) items = Item.objects.filter(course=course, marking_period=course_marking_period, category=category, mark__student=student).annotate(best_mark=Max('mark__mark')).exclude(best_mark=None) course_marking_period.category.count_total = items.exclude(best_mark=None).distinct().count() course_marking_period.category.count_missing = items.filter(best_mark__lt=PASSING_GRADE).distinct().count() course_marking_period.category.count_passing = items.filter(best_mark__gte=PASSING_GRADE).distinct().count() if course_marking_period.category.count_total: course_marking_period.category.count_percentage = (Decimal(course_marking_period.category.count_passing) / course_marking_period.category.count_total * 100).quantize(Decimal('0', ROUND_HALF_UP)) if course.department is not None and course.department.name == 'Corporate Work Study': # TODO: Remove this terrible hack course_marking_period.category.count_passing = course_marking_period.category.count_total course_marking_period.category.count_missing = 0 course_marking_period.category.count_percentage = 100 category.overall_count_total += course_marking_period.category.count_total category.overall_count_missing += course_marking_period.category.count_missing category.overall_count_passing += course_marking_period.category.count_passing item_names = items.values_list('name').distinct() course_marking_period.category.item_groups = [] for item_name_tuple in item_names: item_name = item_name_tuple[0] item_group = struct() item_group.name = item_name item_group.items = items.filter(name=item_name).distinct() course_marking_period.category.item_groups.append(item_group) course_marking_period.category_by_name = getattr(course_marking_period, 'category_by_name', {}) # make a copy so we don't overwrite the last marking period's data course_marking_period.category_by_name[category.name] = copy.copy(course_marking_period.category) # the last time through the loop is the most current marking period, # so give that to anyone who doesn't request an explicit marking period #category = course_marking_period.category course.category_by_name[category.name] = category if category.overall_count_total: category.overall_count_percentage = (Decimal(category.overall_count_passing) / category.overall_count_total * 100).quantize(Decimal('0', ROUND_HALF_UP)) student.count_total_by_category_name[category.name] = student.count_total_by_category_name.get(category.name, 0) + category.overall_count_total student.count_missing_by_category_name[category.name] = student.count_missing_by_category_name.get(category.name, 0) + category.overall_count_missing student.count_passing_by_category_name[category.name] = student.count_passing_by_category_name.get(category.name, 0) + category.overall_count_passing student.count_percentage_by_category_name = {} for category_name, value in student.count_total_by_category_name.items(): if value: student.count_percentage_by_category_name[category_name] = (Decimal(student.count_passing_by_category_name[category_name]) / value * 100).quantize(Decimal('0', ROUND_HALF_UP)) # make categories available student.session_gpa = student.calculate_gpa_mp(marking_period) # Cannot just rely on student.gpa for the cumulative GPA; it does not reflect report's date student.current_report_cumulative_gpa = student.calculate_gpa(for_date) #Attendance for marking period i = 1 student.absent_total = 0 student.tardy_total = 0 student.dismissed_total = 0 student.attendance_marking_periods = [] for mp in attendance_marking_periods.order_by('start_date'): absent = student.student_attn.filter(status__absent=True, date__range=(mp.start_date, mp.end_date)).count() tardy = student.student_attn.filter(status__tardy=True, date__range=(mp.start_date, mp.end_date)).count() dismissed = student.student_attn.filter(status__code="D", date__range=(mp.start_date, mp.end_date)).count() student.absent_total += absent student.tardy_total += tardy student.dismissed_total += dismissed amp = struct() amp.absent = absent amp.tardy = tardy amp.dismissed = dismissed amp.number = i student.attendance_marking_periods.append(amp) i += 1 data['students'] = students data['school_year'] = school_year data['marking_period'] = marking_period.name # just passing object makes appy think it's undefined filename = 'output' #return pod_save(filename, ".pdf", data, template) return pod_save(filename, "." + str(format), data, template)
def pod_report_grade(request, template, options, students, format="odt", transcript=True, report_card=True): """ Generate report card and transcript grades via appy variables for apply: students - contails all each student students.courses - courses for the student (usually for report cards, one year) students.years - years student is enrolled (and selected) students.years.courses - courses for one year (usually for transcripts that span multiple years) year - Selected school year students.phone - First phone number for student students.sat_highest - Highest possible combination of SAT test scores. Looks for test named "SAT" students.years.ave - Averaged grade for year students.years.total_days- School days this year students.years.absent - Absents for year students.years.tardy - Tardies for year students.years.dismissed - Dismissed for year studnets.years.credits - Total credits for year """ data = get_default_data() blank_grade = struct() blank_grade.comment = "" for_date = options["date"] # In case we want a transcript from a future date data["date_of_report"] = for_date # In case we want to include a python date on our template, which is a bit gross # if benchmark grading is installed and enabled for the current school year, # and this is a report card, bail out to another function if ( report_card and "ecwsp.benchmark_grade" in settings.INSTALLED_APPS and SchoolYear.objects.filter(start_date__lt=for_date).order_by("-start_date")[0].benchmark_grade ): from ecwsp.benchmark_grade.report import benchmark_report_card return benchmark_report_card(template, options, students, format) # benchmark_grade transcripts aren't radically different, # but they have some additional data if transcript and "ecwsp.benchmark_grade" in settings.INSTALLED_APPS: from ecwsp.benchmark_grade.models import Aggregate, Category from ecwsp.benchmark_grade.utility import ( gradebook_get_average, benchmark_find_calculation_rule, gradebook_get_category_average, ) marking_periods = MarkingPeriod.objects.filter( school_year=SchoolYear.objects.filter(start_date__lt=for_date).order_by("-start_date")[0] ).filter(show_reports=True) data["marking_periods"] = marking_periods.order_by("start_date") for student in students: # Cannot just rely on student.gpa for the cumulative GPA; it does not reflect report's date student.current_report_cumulative_gpa = student.calculate_gpa(for_date) # for report_card if report_card: courses = Course.objects.filter(courseenrollment__user=student, graded=True) courses = courses.filter(marking_period__in=marking_periods).distinct().order_by("department") for course in courses: grades = course.grade_set.filter(student=student).filter( marking_period__isnull=False, marking_period__show_reports=True ) i = 1 for grade in grades: # course.grade1, course.grade2, etc setattr(course, "grade" + str(i), grade) i += 1 while i <= 4: setattr(course, "grade" + str(i), blank_grade) i += 1 course.final = course.get_final_grade(student) student.courses = courses # Attendance for marking period i = 1 student.absent_total = 0 student.absent_unexcused_total = 0 student.tardy_total = 0 student.tardy_unexcused_total = 0 student.dismissed_total = 0 for mp in marking_periods.order_by("start_date"): absent = student.student_attn.filter(status__absent=True, date__range=(mp.start_date, mp.end_date)) tardy = student.student_attn.filter(status__tardy=True, date__range=(mp.start_date, mp.end_date)) dismissed = student.student_attn.filter( status__code="D", date__range=(mp.start_date, mp.end_date) ).count() absent_unexcused = absent.exclude(status__excused=True) tardy_unexcused = tardy.exclude(status__excused=True) student.absent_total += absent.count() student.tardy_total += tardy.count() student.absent_unexcused_total += absent_unexcused.count() student.tardy_unexcused_total += tardy_unexcused.count() student.dismissed_total += dismissed setattr(student, "absent" + str(i), absent.count()) setattr(student, "tardy" + str(i), tardy.count()) setattr(student, "tardy_unexcused" + str(i), tardy_unexcused.count()) setattr(student, "absent_unexcused" + str(i), absent_unexcused.count()) setattr(student, "dismissed" + str(i), dismissed) i += 1 while i <= 6: setattr(student, "absent" + str(i), "") setattr(student, "tardy" + str(i), "") setattr(student, "tardy_unexcused" + str(i), "") setattr(student, "absent_unexcused" + str(i), "") setattr(student, "dismissed" + str(i), "") i += 1 ## for transcripts if transcript: student.years = ( SchoolYear.objects.filter( markingperiod__show_reports=True, start_date__lt=for_date, markingperiod__course__courseenrollment__user=student, ) .exclude(omityeargpa__student=student) .distinct() .order_by("start_date") ) for year in student.years: year.credits = 0 year.possible_credits = 0 year.mps = ( MarkingPeriod.objects.filter( course__courseenrollment__user=student, school_year=year, show_reports=True ) .distinct() .order_by("start_date") ) i = 1 for mp in year.mps: setattr(year, "mp" + str(i), mp.shortname) i += 1 while i <= 6: setattr(year, "mp" + str(i), "") i += 1 year.courses = Course.objects.filter( courseenrollment__user=student, graded=True, marking_period__school_year=year, marking_period__show_reports=True, ).distinct() year.courses = UserPreference.objects.get_or_create(user=request.user)[0].sort_courses(year.courses) year_grades = student.grade_set.filter( marking_period__show_reports=True, marking_period__end_date__lte=for_date ) # course grades for course in year.courses: # Grades course_grades = year_grades.filter(course=course).distinct() course_aggregates = None if year.benchmark_grade: course_aggregates = Aggregate.objects.filter(course=course, student=student) i = 1 for mp in year.mps: if mp not in course.marking_period.all(): # Obey the registrar! Don't include grades from marking periods when the course didn't meet. setattr(course, "grade" + str(i), "") i += 1 continue if year.benchmark_grade: setattr(course, "grade" + str(i), gradebook_get_average(student, course, None, mp)) else: # We can't overwrite cells, so we have to get seperate variables for each mp grade. try: grade = course_grades.get(marking_period=mp).get_grade() grade = " " + str(grade) + " " except: grade = "" setattr(course, "grade" + str(i), grade) i += 1 while i <= 6: setattr(course, "grade" + str(i), "") i += 1 course.final = course.get_final_grade(student, date_report=for_date) if mp.end_date < for_date and course.is_passing(student) and course.credits: year.credits += course.credits if course.credits: year.possible_credits += course.credits year.categories_as_courses = [] if year.benchmark_grade: calculation_rule = benchmark_find_calculation_rule(year) for category_as_course in calculation_rule.category_as_course_set.filter( include_departments=course.department ): i = 1 for mp in year.mps: setattr( category_as_course.category, "grade{}".format(i), gradebook_get_category_average(student, category_as_course.category, mp), ) i += 1 year.categories_as_courses.append(category_as_course.category) # Averages per marking period i = 1 for mp in year.mps: if mp.end_date < for_date: setattr(year, "mp" + str(i) + "ave", student.calculate_gpa_mp(mp)) i += 1 while i <= 6: setattr(year, "mp" + str(i) + "ave", "") i += 1 year.ave = student.calculate_gpa_year(year, for_date) # Attendance for year year.total_days = year.get_number_days() year.nonmemb = student.student_attn.filter( status__code="nonmemb", date__range=(year.start_date, year.end_date) ).count() year.absent = student.student_attn.filter( status__absent=True, date__range=(year.start_date, year.end_date) ).count() year.tardy = student.student_attn.filter( status__tardy=True, date__range=(year.start_date, year.end_date) ).count() year.dismissed = student.student_attn.filter( status__code="D", date__range=(year.start_date, year.end_date) ).count() # credits per dept student.departments = Department.objects.filter(course__courseenrollment__user=student).distinct() student.departments_text = "" for dept in student.departments: c = 0 for course in student.course_set.filter( department=dept, marking_period__school_year__end_date__lt=for_date, graded=True ).distinct(): if course.credits and course.is_passing(student): c += course.credits dept.credits = c student.departments_text += "| %s: %s " % (dept, dept.credits) student.departments_text += "|" # Standardized tests if "ecwsp.standard_test" in settings.INSTALLED_APPS: from ecwsp.standard_test.models import StandardTest student.tests = [] student.highest_tests = [] for test_result in student.standardtestresult_set.filter( test__show_on_reports=True, show_on_reports=True ).order_by("test"): test_result.categories = "" for cat in test_result.standardcategorygrade_set.filter(category__is_total=False): test_result.categories += "%s: %s | " % (cat.category.name, strip_trailing_zeros(cat.grade)) test_result.categories = test_result.categories[:-3] student.tests.append(test_result) for test in StandardTest.objects.filter( standardtestresult__student=student, show_on_reports=True, standardtestresult__show_on_reports=True ).distinct(): test.total = strip_trailing_zeros(test.get_cherry_pick_total(student)) student.highest_tests.append(test) try: if options["student"].count == 1: data["student"] = options["student"][0] except: pass data["students"] = students data["strip_trailing_zeros"] = strip_trailing_zeros filename = "output" return pod_save(filename, "." + str(format), data, template)
def pod_report_grade(request, template, options, students, format="odt", transcript=True, report_card=True, benchmark_report_card=True): """ Generate report card and transcript grades via appy variables for apply: students - contails all each student students.courses - courses for the student (usually for report cards, one year) students.years - years student is enrolled (and selected) students.years.courses - courses for one year (usually for transcripts that span multiple years) year - Selected school year students.phone - First phone number for student students.sat_highest - Highest possible combination of SAT test scores. Looks for test named "SAT" students.years.ave - Averaged grade for year students.years.total_days- School days this year students.years.absent - Absents for year students.years.tardy - Tardies for year students.years.dismissed - Dismissed for year studnets.years.credits - Total credits for year """ data = get_default_data() blank_grade = struct() blank_grade.comment = "" for_date = options[ 'date'] # In case we want a transcript from a future date data[ 'date_of_report'] = for_date # In case we want to include a python date on our template, which is a bit gross try: omit_substitutions = options['omit_substitutions'] except KeyError: omit_substitutions = False # if benchmark grading is installed and enabled for the selected template, # and this is a report card, bail out to another function if (benchmark_report_card and "ecwsp.benchmark_grade" in settings.INSTALLED_APPS): from ecwsp.benchmark_grade.report import benchmark_report_card return benchmark_report_card(template, options, students, format) # benchmark_grade transcripts aren't radically different, # but they have some additional data if (transcript and "ecwsp.benchmark_grade" in settings.INSTALLED_APPS): from ecwsp.benchmark_grade.models import Aggregate, Category from ecwsp.benchmark_grade.utility import gradebook_get_average, benchmark_find_calculation_rule, gradebook_get_category_average marking_periods = MarkingPeriod.objects.filter( school_year=SchoolYear.objects.filter( start_date__lt=for_date).order_by('-start_date')[0]).filter( show_reports=True) data['marking_periods'] = marking_periods.order_by('start_date') for student in students: # Cannot just rely on student.gpa for the cumulative GPA; it does not reflect report's date student.current_report_cumulative_gpa = student.calculate_gpa(for_date) # for report_card if report_card: courses = Course.objects.filter( courseenrollment__user=student, graded=True, ) courses = courses.filter(marking_period__in=marking_periods ).distinct().order_by('department') for course in courses: grades = course.grade_set.filter(student=student).filter( marking_period__isnull=False, marking_period__show_reports=True) i = 1 for grade in grades: # course.grade1, course.grade2, etc setattr(course, "grade" + str(i), grade) i += 1 while i <= 4: setattr(course, "grade" + str(i), blank_grade) i += 1 course.final = course.get_final_grade(student) student.courses = courses #Attendance for marking period i = 1 student.absent_total = 0 student.absent_unexcused_total = 0 student.tardy_total = 0 student.tardy_unexcused_total = 0 student.dismissed_total = 0 for mp in marking_periods.order_by('start_date'): absent = student.student_attn.filter( status__absent=True, date__range=(mp.start_date, mp.end_date)) tardy = student.student_attn.filter(status__tardy=True, date__range=(mp.start_date, mp.end_date)) dismissed = student.student_attn.filter( status__code="D", date__range=(mp.start_date, mp.end_date)).count() absent_unexcused = absent.exclude(status__excused=True).count() tardy_unexcused = tardy.exclude(status__excused=True).count() absent = absent.count() tardy = tardy.count() student.absent_total += absent student.tardy_total += tardy student.absent_unexcused_total += absent_unexcused student.tardy_unexcused_total += tardy_unexcused student.dismissed_total += dismissed setattr(student, "absent" + str(i), absent) setattr(student, "tardy" + str(i), tardy) setattr(student, "tardy_unexcused" + str(i), tardy_unexcused) setattr(student, "absent_unexcused" + str(i), absent_unexcused) setattr(student, "dismissed" + str(i), dismissed) i += 1 while i <= 6: setattr(student, "absent" + str(i), "") setattr(student, "tardy" + str(i), "") setattr(student, "tardy_unexcused" + str(i), "") setattr(student, "absent_unexcused" + str(i), "") setattr(student, "dismissed" + str(i), "") i += 1 ## for transcripts if transcript: student.years = SchoolYear.objects.filter( markingperiod__show_reports=True, start_date__lt=for_date, markingperiod__course__courseenrollment__user=student).exclude( omityeargpa__student=student).distinct().order_by( 'start_date') for year in student.years: year.credits = 0 year.possible_credits = 0 year.mps = MarkingPeriod.objects.filter( course__courseenrollment__user=student, school_year=year, show_reports=True).distinct().order_by("start_date") i = 1 for mp in year.mps: setattr(year, "mp" + str(i), mp.shortname) i += 1 while i <= 6: setattr(year, "mp" + str(i), "") i += 1 year.courses = Course.objects.filter( courseenrollment__user=student, graded=True, marking_period__school_year=year, marking_period__show_reports=True).distinct() year.courses = UserPreference.objects.get_or_create( user=request.user)[0].sort_courses(year.courses) year_grades = student.grade_set.filter( marking_period__show_reports=True, marking_period__end_date__lte=for_date) # course grades for course in year.courses: # Grades course_grades = year_grades.filter( course=course).distinct() course_aggregates = None if year.benchmark_grade: course_aggregates = Aggregate.objects.filter( course=course, student=student) i = 1 for mp in year.mps: if mp not in course.marking_period.all(): # Obey the registrar! Don't include grades from marking periods when the course didn't meet. setattr(course, "grade" + str(i), "") i += 1 continue if year.benchmark_grade: setattr( course, "grade" + str(i), gradebook_get_average( student, course, None, mp, omit_substitutions=omit_substitutions)) else: # We can't overwrite cells, so we have to get seperate variables for each mp grade. try: grade = course_grades.get( marking_period=mp).get_grade() grade = " " + str(grade) + " " except: grade = "" setattr(course, "grade" + str(i), grade) i += 1 while i <= 6: setattr(course, "grade" + str(i), "") i += 1 course.final = course.get_final_grade(student, date_report=for_date) if mp.end_date < for_date and course.is_passing( student) and course.credits: year.credits += course.credits if course.credits: year.possible_credits += course.credits year.categories_as_courses = [] if year.benchmark_grade: calculation_rule = benchmark_find_calculation_rule(year) for category_as_course in calculation_rule.category_as_course_set.filter( include_departments=course.department): i = 1 for mp in year.mps: setattr( category_as_course.category, 'grade{}'.format(i), gradebook_get_category_average( student, category_as_course.category, mp)) i += 1 year.categories_as_courses.append( category_as_course.category) # Averages per marking period i = 1 for mp in year.mps: if mp.end_date < for_date: setattr(year, 'mp' + str(i) + 'ave', student.calculate_gpa_mp(mp)) i += 1 while i <= 6: setattr(year, 'mp' + str(i) + 'ave', "") i += 1 year.ave = student.calculate_gpa_year(year, for_date) # Attendance for year year.total_days = year.get_number_days() year.nonmemb = student.student_attn.filter( status__code="nonmemb", date__range=(year.start_date, year.end_date)).count() year.absent = student.student_attn.filter( status__absent=True, date__range=(year.start_date, year.end_date)).count() year.tardy = student.student_attn.filter( status__tardy=True, date__range=(year.start_date, year.end_date)).count() year.dismissed = student.student_attn.filter( status__code="D", date__range=(year.start_date, year.end_date)).count() # credits per dept student.departments = Department.objects.filter( course__courseenrollment__user=student).distinct() student.departments_text = "" for dept in student.departments: c = 0 for course in student.course_set.filter( department=dept, marking_period__school_year__end_date__lt=for_date, graded=True).distinct(): if course.credits and course.is_passing(student): c += course.credits dept.credits = c student.departments_text += "| %s: %s " % (dept, dept.credits) student.departments_text += "|" # Standardized tests if 'ecwsp.standard_test' in settings.INSTALLED_APPS: from ecwsp.standard_test.models import StandardTest student.tests = [] student.highest_tests = [] for test_result in student.standardtestresult_set.filter( test__show_on_reports=True, show_on_reports=True).order_by('test'): test_result.categories = "" for cat in test_result.standardcategorygrade_set.filter( category__is_total=False): test_result.categories += '%s: %s | ' % ( cat.category.name, strip_trailing_zeros(cat.grade)) test_result.categories = test_result.categories[:-3] student.tests.append(test_result) for test in StandardTest.objects.filter( standardtestresult__student=student, show_on_reports=True, standardtestresult__show_on_reports=True).distinct(): test.total = strip_trailing_zeros( test.get_cherry_pick_total(student)) student.highest_tests.append(test) try: if options['student'].count == 1: data['student'] = options['student'][0] except: pass data['students'] = students data['strip_trailing_zeros'] = strip_trailing_zeros filename = 'output' return pod_save(filename, "." + str(format), data, template)
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 get_benchmark_report_card_data(report_context, appy_context, students): PASSING_GRADE = 3 # TODO: pull config value. Roche has it set to something crazy now and I don't want to deal with it data = appy_context for_date = report_context['date_end'] try: omit_substitutions = report_context['omit_substitutions'] except KeyError: omit_substitutions = False school_year = SchoolYear.objects.filter(start_date__lt=for_date).order_by('-start_date')[0] calculation_rule = benchmark_find_calculation_rule(school_year) attendance_marking_periods = MarkingPeriod.objects.filter(school_year=school_year, start_date__lt=for_date, show_reports=True) marking_period = attendance_marking_periods.order_by('-start_date')[0] for student in students: # Backwards compatibility for existing templates student.fname = student.first_name student.lname = student.last_name student.year_course_sections = CourseSection.objects.filter( courseenrollment__user=student, course__graded=True, marking_period__school_year=school_year, ).distinct().order_by('course__department') student.course_sections = [] student.count_total_by_category_name = {} student.count_missing_by_category_name = {} student.count_passing_by_category_name = {} for course_section in student.year_course_sections: course_section.average = gradebook_get_average(student, course_section, None, marking_period, None, omit_substitutions = omit_substitutions) course_section.current_marking_periods = course_section.marking_period.filter(start_date__lt=for_date).order_by('start_date') course_section.categories = Category.objects.filter(item__course_section=course_section, item__mark__student=student).distinct() course_section.category_by_name = {} for category in course_section.categories: try: category.weight_percentage = calculation_rule.per_course_category_set.get(category=category, apply_to_departments=course_section.department).weight * Decimal(100) except CalculationRulePerCourseCategory.DoesNotExist: category.weight_percentage = Decimal(0) category.weight_percentage = category.weight_percentage.quantize(Decimal('0'), ROUND_HALF_UP) category.overall_count_total = 0 category.overall_count_missing = 0 category.overall_count_passing = 0 for course_section_marking_period in course_section.current_marking_periods: course_section_marking_period.category = category course_section_marking_period.category.average = gradebook_get_average(student, course_section, category, course_section_marking_period, None, omit_substitutions = omit_substitutions) items = Item.objects.filter(course_section=course_section, marking_period=course_section_marking_period, category=category, mark__student=student).annotate(best_mark=Max('mark__mark')).exclude(best_mark=None) course_section_marking_period.category.count_total = items.exclude(best_mark=None).distinct().count() course_section_marking_period.category.count_missing = items.filter(best_mark__lt=PASSING_GRADE).distinct().count() course_section_marking_period.category.count_passing = items.filter(best_mark__gte=PASSING_GRADE).distinct().count() if course_section_marking_period.category.count_total: course_section_marking_period.category.count_percentage = (Decimal(course_section_marking_period.category.count_passing) / course_section_marking_period.category.count_total * 100).quantize(Decimal('0', ROUND_HALF_UP)) # TODO: We assume here that flagging something visually means it's "missing." This should be done in a better way that's not opaque to users. if not calculation_rule.substitution_set.filter(apply_to_departments=course_section.department, flag_visually=True).exists(): course_section_marking_period.category.count_passing = course_section_marking_period.category.count_total course_section_marking_period.category.count_missing = 0 course_section_marking_period.category.count_percentage = 100 category.overall_count_total += course_section_marking_period.category.count_total category.overall_count_missing += course_section_marking_period.category.count_missing category.overall_count_passing += course_section_marking_period.category.count_passing item_names = items.values_list('name').distinct() course_section_marking_period.category.item_groups = [] for item_name_tuple in item_names: item_name = item_name_tuple[0] item_group = struct() item_group.name = item_name item_group.items = items.filter(name=item_name).distinct() course_section_marking_period.category.item_groups.append(item_group) course_section_marking_period.category_by_name = getattr(course_section_marking_period, 'category_by_name', {}) # make a copy so we don't overwrite the last marking period's data course_section_marking_period.category_by_name[category.name] = copy.copy(course_section_marking_period.category) # the last time through the loop is the most current marking period, # so give that to anyone who doesn't request an explicit marking period #category = course_marking_period.category course_section.category_by_name[category.name] = category if category.overall_count_total: category.overall_count_percentage = (Decimal(category.overall_count_passing) / category.overall_count_total * 100).quantize(Decimal('0', ROUND_HALF_UP)) student.count_total_by_category_name[category.name] = student.count_total_by_category_name.get(category.name, 0) + category.overall_count_total student.count_missing_by_category_name[category.name] = student.count_missing_by_category_name.get(category.name, 0) + category.overall_count_missing student.count_passing_by_category_name[category.name] = student.count_passing_by_category_name.get(category.name, 0) + category.overall_count_passing # some components of report need access to course sections for entire year (student.year_course_sections) # but we must keep student.course_sections restricted to the current marking period for compatibility if marking_period in course_section.marking_period.all(): student.course_sections.append(course_section) student.count_percentage_by_category_name = {} for category_name, value in student.count_total_by_category_name.items(): if value: student.count_percentage_by_category_name[category_name] = (Decimal(student.count_passing_by_category_name[category_name]) / value * 100).quantize(Decimal('0', ROUND_HALF_UP)) # make categories available try: student.session_gpa = student.studentmarkingperiodgrade_set.get( marking_period=marking_period).grade except StudentMarkingPeriodGrade.DoesNotExist: student.session_gpa = None # Cannot just rely on student.gpa for the cumulative GPA; it does not reflect report's date student.current_report_cumulative_gpa = student.calculate_gpa(for_date) #Attendance for marking period i = 1 student.absent_total = 0 student.tardy_total = 0 student.dismissed_total = 0 student.attendance_marking_periods = [] for mp in attendance_marking_periods.order_by('start_date'): absent = student.student_attn.filter(status__absent=True, date__range=(mp.start_date, mp.end_date)).count() tardy = student.student_attn.filter(status__tardy=True, date__range=(mp.start_date, mp.end_date)).count() dismissed = student.student_attn.filter(status__code="D", date__range=(mp.start_date, mp.end_date)).count() student.absent_total += absent student.tardy_total += tardy student.dismissed_total += dismissed amp = struct() amp.absent = absent amp.tardy = tardy amp.dismissed = dismissed amp.number = i student.attendance_marking_periods.append(amp) i += 1 data['students'] = students data['school_year'] = school_year data['marking_period'] = marking_period.name # just passing object makes appy think it's undefined data['draw_gauge'] = draw_gauge
def student_report(request, student_pk=None, course_pk=None, marking_period_pk=None): authorized = False family_available_students = None try: # is it a student? student = Student.objects.get(username=request.user.username) # ok! we'll ignore student_pk, and the student is authorized to see itself authorized = True except: student = None if not student: if request.user.is_staff: # hey, it's a staff member! student = get_object_or_404(Student, pk=student_pk) authorized = True else: # maybe it's a family member? family_available_students = Student.objects.filter( family_access_users=request.user) if student_pk: student = get_object_or_404(Student, pk=student_pk) if student in family_available_students: authorized = True elif family_available_students.count(): student = family_available_students[0] authorized = True # did all that make us comfortable with proceeding? if not authorized: error_message = 'Sorry, you are not authorized to see grades for this student. Please contact the school registrar.' return render_to_response( 'benchmark_grade/student_grade.html', { 'error_message': error_message, }, RequestContext(request, {}), ) # is this a summary or detail report? if not course_pk: # summary report for all courses PASSING_GRADE = 3 # TODO: pull config value. Roche has it set to something crazy now and I don't want to deal with it school_year = SchoolYear.objects.get(active_year=True) mps = MarkingPeriod.objects.filter( school_year=school_year, start_date__lte=datetime.date.today()).order_by('-start_date') calculation_rule = benchmark_find_calculation_rule(school_year) for mp in mps: mp.courses = Course.objects.filter( courseenrollment__user=student, graded=True, marking_period=mp).order_by('fullname') for course in mp.courses: course.categories = Category.objects.filter( item__course=course, item__mark__student=student).distinct() course.category_by_name = {} for category in course.categories: category.percentage = calculation_rule.per_course_category_set.get( category=category, apply_to_departments=course.department).weight * 100 category.percentage = category.percentage.quantize( Decimal('0')) category.average = gradebook_get_average( student, course, category, mp, None) items = Item.objects.filter( course=course, category=category, marking_period=mp, mark__student=student).annotate( best_mark=Max('mark__mark')) counts = {} counts['total'] = items.exclude( best_mark=None).distinct().count() counts['missing'] = items.filter( best_mark__lt=PASSING_GRADE).distinct().count() counts['passing'] = items.filter( best_mark__gte=PASSING_GRADE).distinct().count() if counts['total']: counts['percentage'] = (Decimal(counts['passing']) / counts['total'] * 100).quantize(Decimal('0')) course.category_by_name[category.name] = counts course.average = gradebook_get_average(student, course, None, mp, None) return render_to_response( 'benchmark_grade/student_grade.html', { 'student': student, 'available_students': family_available_students, 'mps': mps }, RequestContext(request, {}), ) else: # detail report for a single course course = get_object_or_404(Course, pk=course_pk) # TODO: move into CalculationRule? CATEGORY_NAME_TO_FLAG_CRITERIA = { 'Standards': { 'best_mark__lt': 3 }, 'Engagement': { 'best_mark__lt': 3 }, 'Organization': { 'best_mark__lt': 3 }, 'Daily Practice': { 'best_mark__lte': 0 }, } if 'item_pks' in request.POST: item_pks = request.POST['item_pks'].split(',') items = Item.objects.filter(pk__in=item_pks) specific_items = True else: items = Item.objects specific_items = False # always filter in case a bad person passes us items from a different course items = items.filter(course=course, mark__student=student) if marking_period_pk: mp = get_object_or_404(MarkingPeriod, pk=marking_period_pk) mps = (mp, ) else: mps = MarkingPeriod.objects.filter( item__in=items).distinct().order_by('-start_date') for mp in mps: mp_items = items.filter(marking_period=mp) mp.categories = Category.objects.filter( item__in=mp_items).distinct() for category in mp.categories: category_items = mp_items.filter(category=category).annotate( best_mark=Max('mark__mark')).exclude(best_mark=None) item_names = category_items.values_list('name').distinct() category.item_groups = {} for item_name_tuple in item_names: item_name = item_name_tuple[0] category.item_groups[item_name] = category_items.filter( name=item_name).distinct() if specific_items: # get a disposable average for these specific items category.average = gradebook_get_average( student, course, category, mp, category_items) else: category.average = gradebook_get_average( student, course, category, mp, None) category.flagged_item_pks = [] if category.name in CATEGORY_NAME_TO_FLAG_CRITERIA: category.flagged_item_pks = category_items.filter( **CATEGORY_NAME_TO_FLAG_CRITERIA[ category.name]).values_list('pk', flat=True) return render_to_response( 'benchmark_grade/student_grade_course_detail.html', { 'student': student, 'course': course, 'mps': mps }, RequestContext(request, {}), )
def get_student_transcript_data(self, student, omit_substitutions=False): # benchmark_grade transcripts aren't radically different, # but they have some additional data if "ecwsp.benchmark_grade" in settings.INSTALLED_APPS: from ecwsp.benchmark_grade.models import Aggregate from ecwsp.benchmark_grade.utility import gradebook_get_average, benchmark_find_calculation_rule, gradebook_get_category_average student.years = SchoolYear.objects.filter( markingperiod__show_reports=True, start_date__lt=self.for_date, markingperiod__course__courseenrollment__user=student, ).exclude(omityeargpa__student=student).distinct().order_by('start_date') for year in student.years: year.credits = 0 year.possible_credits = 0 year.mps = MarkingPeriod.objects.filter(course__courseenrollment__user=student, school_year=year, show_reports=True).distinct().order_by("start_date") i = 1 for mp in year.mps: setattr(year, "mp" + str(i), mp.shortname) i += 1 while i <= 6: setattr(year, "mp" + str(i), "") i += 1 year.courses = Course.objects.filter(courseenrollment__user=student, graded=True, marking_period__school_year=year, marking_period__show_reports=True).distinct() year.courses = UserPreference.objects.get_or_create(user=self.user)[0].sort_courses(year.courses) year_grades = student.grade_set.filter(marking_period__show_reports=True, marking_period__end_date__lte=self.for_date) # course grades for course in year.courses: # Grades course_grades = year_grades.filter(course=course).distinct() course_aggregates = None if year.benchmark_grade: course_aggregates = Aggregate.objects.filter(course=course, student=student) i = 1 for mp in year.mps: if mp not in course.marking_period.all(): # Obey the registrar! Don't include grades from marking periods when the course didn't meet. setattr(course, "grade" + str(i), "") i += 1 continue if year.benchmark_grade: setattr(course, "grade" + str(i), gradebook_get_average(student, course, None, mp, omit_substitutions = omit_substitutions)) else: # We can't overwrite cells, so we have to get seperate variables for each mp grade. try: grade = course_grades.get(marking_period=mp).get_grade() grade = " " + str(grade) + " " except: grade = "" setattr(course, "grade" + str(i), grade) i += 1 while i <= 6: setattr(course, "grade" + str(i), "") i += 1 course.final = course.get_final_grade(student, date_report=self.for_date) if (mp.end_date < self.for_date and course.is_passing( student, cache_grade=course.final, cache_passing=self.passing_grade, cache_letter_passing=self.letter_passing_grade) and course.credits): year.credits += course.credits if course.credits: year.possible_credits += course.credits year.categories_as_courses = [] if year.benchmark_grade: calculation_rule = benchmark_find_calculation_rule(year) for category_as_course in calculation_rule.category_as_course_set.filter(include_departments=course.department): i = 1 for mp in year.mps: setattr(category_as_course.category, 'grade{}'.format(i), gradebook_get_category_average(student, category_as_course.category, mp)) i += 1 year.categories_as_courses.append(category_as_course.category) # Averages per marking period i = 1 for mp in year.mps: if mp.end_date < self.for_date: setattr(year, 'mp' + str(i) + 'ave', student.calculate_gpa_mp(mp)) i += 1 while i <= 6: setattr(year, 'mp' + str(i) + 'ave', "") i += 1 year.ave = student.calculate_gpa_year(year, self.for_date) # Attendance for year if not year.id in self.year_days: self.year_days[year.id] = year.get_number_days() year.total_days = self.year_days[year.id] year.nonmemb = student.student_attn.filter(status__code="nonmemb", date__range=(year.start_date, year.end_date)).count() year.absent = student.student_attn.filter(status__absent=True, date__range=(year.start_date, year.end_date)).count() year.tardy = student.student_attn.filter(status__tardy=True, date__range=(year.start_date, year.end_date)).count() year.dismissed = student.student_attn.filter(status__code="D", date__range=(year.start_date, year.end_date)).count() # credits per dept student.departments = Department.objects.filter(course__courseenrollment__user=student).distinct() student.departments_text = "" for dept in student.departments: c = 0 for course in student.course_set.filter( department=dept, marking_period__school_year__end_date__lt=self.for_date, graded=True).distinct(): if course.credits and course.is_passing( student, cache_passing=self.passing_grade, cache_letter_passing=self.letter_passing_grade): c += course.credits dept.credits = c student.departments_text += "| %s: %s " % (dept, dept.credits) student.departments_text += "|" # Standardized tests if 'ecwsp.standard_test' in settings.INSTALLED_APPS: from ecwsp.standard_test.models import StandardTest student.tests = [] student.highest_tests = [] for test_result in student.standardtestresult_set.filter( test__show_on_reports=True, show_on_reports=True ).order_by('test'): test_result.categories = "" for cat in test_result.standardcategorygrade_set.filter(category__is_total=False): test_result.categories += '%s: %s | ' % (cat.category.name, strip_trailing_zeros(cat.grade)) test_result.categories = test_result.categories [:-3] student.tests.append(test_result) for test in StandardTest.objects.filter(standardtestresult__student=student, show_on_reports=True, standardtestresult__show_on_reports=True).distinct(): test.total = strip_trailing_zeros(test.get_cherry_pick_total(student)) student.highest_tests.append(test)
def get_student_transcript_data(self, student, omit_substitutions=False): # benchmark_grade transcripts aren't radically different, # but they have some additional data if "ecwsp.benchmark_grade" in settings.INSTALLED_APPS: from ecwsp.benchmark_grade.models import Aggregate from ecwsp.benchmark_grade.utility import gradebook_get_average, benchmark_find_calculation_rule, gradebook_get_category_average student.years = SchoolYear.objects.filter( markingperiod__show_reports=True, start_date__lt=self.for_date, markingperiod__course__courseenrollment__user=student, ).exclude( omityeargpa__student=student).distinct().order_by('start_date') for year in student.years: year.credits = 0 year.possible_credits = 0 year.mps = MarkingPeriod.objects.filter( course__courseenrollment__user=student, school_year=year, show_reports=True).distinct().order_by("start_date") i = 1 for mp in year.mps: setattr(year, "mp" + str(i), mp.shortname) i += 1 while i <= 6: setattr(year, "mp" + str(i), "") i += 1 year.courses = Course.objects.filter( courseenrollment__user=student, graded=True, marking_period__school_year=year, marking_period__show_reports=True).distinct() year.courses = UserPreference.objects.get_or_create( user=self.user)[0].sort_courses(year.courses) year_grades = student.grade_set.filter( marking_period__show_reports=True, marking_period__end_date__lte=self.for_date) # course grades for course in year.courses: # Grades course_grades = year_grades.filter(course=course).distinct() course_aggregates = None if year.benchmark_grade: course_aggregates = Aggregate.objects.filter( course=course, student=student) i = 1 for mp in year.mps: if mp not in course.marking_period.all(): # Obey the registrar! Don't include grades from marking periods when the course didn't meet. setattr(course, "grade" + str(i), "") i += 1 continue if year.benchmark_grade: setattr( course, "grade" + str(i), gradebook_get_average( student, course, None, mp, omit_substitutions=omit_substitutions)) else: # We can't overwrite cells, so we have to get seperate variables for each mp grade. try: grade = course_grades.get( marking_period=mp).get_grade() grade = " " + str(grade) + " " except: grade = "" setattr(course, "grade" + str(i), grade) i += 1 while i <= 6: setattr(course, "grade" + str(i), "") i += 1 course.final = course.get_final_grade( student, date_report=self.for_date) if (mp.end_date < self.for_date and course.is_passing( student, cache_grade=course.final, cache_passing=self.passing_grade, cache_letter_passing=self.letter_passing_grade) and course.credits): year.credits += course.credits if course.credits: year.possible_credits += course.credits year.categories_as_courses = [] if year.benchmark_grade: calculation_rule = benchmark_find_calculation_rule(year) for category_as_course in calculation_rule.category_as_course_set.filter( include_departments=course.department): i = 1 for mp in year.mps: setattr( category_as_course.category, 'grade{}'.format(i), gradebook_get_category_average( student, category_as_course.category, mp)) i += 1 year.categories_as_courses.append( category_as_course.category) # Averages per marking period i = 1 for mp in year.mps: if mp.end_date < self.for_date: setattr(year, 'mp' + str(i) + 'ave', student.calculate_gpa_mp(mp)) i += 1 while i <= 6: setattr(year, 'mp' + str(i) + 'ave', "") i += 1 year.ave = student.calculate_gpa_year(year, self.for_date) # Attendance for year if not year.id in self.year_days: self.year_days[year.id] = year.get_number_days() year.total_days = self.year_days[year.id] year.nonmemb = student.student_attn.filter( status__code="nonmemb", date__range=(year.start_date, year.end_date)).count() year.absent = student.student_attn.filter( status__absent=True, date__range=(year.start_date, year.end_date)).count() year.tardy = student.student_attn.filter( status__tardy=True, date__range=(year.start_date, year.end_date)).count() year.dismissed = student.student_attn.filter( status__code="D", date__range=(year.start_date, year.end_date)).count() # credits per dept student.departments = Department.objects.filter( course__courseenrollment__user=student).distinct() student.departments_text = "" for dept in student.departments: c = 0 for course in student.course_set.filter( department=dept, marking_period__school_year__end_date__lt=self. for_date, graded=True).distinct(): if course.credits and course.is_passing( student, cache_passing=self.passing_grade, cache_letter_passing=self.letter_passing_grade): c += course.credits dept.credits = c student.departments_text += "| %s: %s " % (dept, dept.credits) student.departments_text += "|" # Standardized tests if 'ecwsp.standard_test' in settings.INSTALLED_APPS: from ecwsp.standard_test.models import StandardTest student.tests = [] student.highest_tests = [] for test_result in student.standardtestresult_set.filter( test__show_on_reports=True, show_on_reports=True).order_by('test'): test_result.categories = "" for cat in test_result.standardcategorygrade_set.filter( category__is_total=False): test_result.categories += '%s: %s | ' % ( cat.category.name, strip_trailing_zeros(cat.grade)) test_result.categories = test_result.categories[:-3] student.tests.append(test_result) for test in StandardTest.objects.filter( standardtestresult__student=student, show_on_reports=True, standardtestresult__show_on_reports=True).distinct(): test.total = strip_trailing_zeros( test.get_cherry_pick_total(student)) student.highest_tests.append(test)