Example #1
0
 def teacherDesc(self):
     fts = ClassFlagType.get_flag_types(self.program)
     descs = {}
     for flag_type in fts:
         descs[
             'flag_%s' % flag_type.
             id] = """Teachers who have a class with the "%s" flag.""" % flag_type.name
     return descs
 def teachers(self, QObject = False):
     fts = ClassFlagType.get_flag_types(self.program)
     t = {}
     for flag_type in fts:
         q = Q(classsubject__flags__flag_type=flag_type.id,
               classsubject__parent_program=self.program)
         if QObject:
             t['flag_%s' % flag_type.id] = q
         else:
             t['flag_%s' % flag_type.id] = ESPUser.objects.filter(q).distinct()
     return t
Example #3
0
 def teachers(self, QObject=False):
     fts = ClassFlagType.get_flag_types(self.program)
     t = {}
     for flag_type in fts:
         q = Q(classsubject__flags__flag_type=flag_type.id,
               classsubject__parent_program=self.program)
         if QObject:
             t['flag_%s' % flag_type.id] = q
         else:
             t['flag_%s' %
               flag_type.id] = ESPUser.objects.filter(q).distinct()
     return t
            context['isopenclass']]['type']
        context['otherclass'] = context['classes'][1 - context['isopenclass']]
        context['qsd_name'] = 'classedit_' + context['classtype']

        context['manage'] = False
        if ((request.method == "POST"
             and request.POST.get('manage') == 'manage') or
            (request.method == "GET" and request.GET.get('manage') == 'manage')
                or
            (tl == 'manage'
             and 'class' in context)) and request.user.isAdministrator():
            context['manage'] = True
            if self.program.program_modules.filter(
                    handler='ClassFlagModule').exists():
                context['show_flags'] = True
                context['flag_types'] = ClassFlagType.get_flag_types(
                    self.program)

        return render_to_response(self.baseDir() + 'classedit.html', request,
                                  context)

    @aux_call
    @needs_teacher
    def teacherlookup(self,
                      request,
                      tl,
                      one,
                      two,
                      module,
                      extra,
                      prog,
                      newclass=None):
Example #5
0
    def stats(prog):
        # Create a dictionary to assemble the output
        dictOut = {"stats": []}

        classes = prog.classes().select_related()
        vitals = {'id': 'vitals'}

        class_num_list = []
        class_num_list.append(
            ("Total # of Classes", classes.distinct().count()))
        class_num_list.append(
            ("Total # of Class Sections",
             prog.sections().select_related().distinct().count()))
        class_num_list.append(
            ("Total # of Lunch Classes",
             classes.filter(category__category="Lunch").filter(
                 status=10).distinct().count()))
        class_num_list.append(
            ("Total # of Classes <span style='color: #00C;'>Unreviewed</span>",
             classes.filter(status=0).distinct().count()))
        class_num_list.append(
            ("Total # of Classes <span style='color: #0C0;'>Accepted</span>",
             classes.filter(status=10).distinct().count()))
        class_num_list.append(
            ("Total # of Classes <span style='color: #C00;'>Rejected</span>",
             classes.filter(status=-10).distinct().count()))
        class_num_list.append(
            ("Total # of Classes <span style='color: #990;'>Cancelled</span>",
             classes.filter(status=-20).distinct().count()))
        for ft in ClassFlagType.get_flag_types(prog):
            class_num_list.append(
                ('Total # of Classes with the "%s" flag' % ft.name,
                 classes.filter(flags__flag_type=ft).distinct().count()))
        vitals['classnum'] = class_num_list

        #   Display pretty labels for teacher and student numbers
        teacher_labels_dict = {}
        for module in prog.getModules():
            teacher_labels_dict.update(module.teacherDesc())
        vitals['teachernum'] = []

        ## Ew, queries in a for loop...
        ## Not much to be done about it, though;
        ## the loop is iterating over a list of independent queries and running each.
        teachers = prog.teachers()
        for key in teachers.keys():
            if key in teacher_labels_dict:
                vitals['teachernum'].append((
                    teacher_labels_dict[key],  ## Unfortunately,
                    teachers[key].filter(is_active=True).distinct().count()))
            else:
                vitals['teachernum'].append(
                    (key,
                     teachers[key].filter(is_active=True).distinct().count()))

        student_labels_dict = {}
        for module in prog.getModules():
            student_labels_dict.update(module.studentDesc())
        vitals['studentnum'] = []

        ## Ew, another set of queries in a for loop...
        ## Same justification, though.
        students = prog.students()
        for key in students.keys():
            if key in student_labels_dict:
                vitals['studentnum'].append(
                    (student_labels_dict[key],
                     students[key].filter(is_active=True).distinct().count()))
            else:
                vitals['studentnum'].append(
                    (key,
                     students[key].filter(is_active=True).distinct().count()))

        timeslots = prog.getTimeSlots()
        vitals['timeslots'] = []

        shours = 0.0
        chours = 0.0
        crhours = 0.0
        ## Write this as a 'for' loop because PostgreSQL can't do it in
        ## one go without a subquery or duplicated logic, and Django
        ## doesn't have enough power to expose either approach directly.
        ## At least there aren't any queries in the for loop...
        ## (In MySQL, this could I believe be done with a minimally-painful extra() clause.)
        ## Also, since we're iterating over a big data set, use .values()
        ## minimize the number of objects that we're creating.
        ## One dict and two Decimals per row, as opposed to
        ## an Object per field and all kinds of stuff...
        for cls in prog.classes().exclude(category__category='Lunch').annotate(
                num_sections=Count('sections'),
                subject_duration=Sum('sections__duration'),
                subject_students=Sum('sections__enrolled_students')).values(
                    'num_sections', 'subject_duration', 'subject_students',
                    'class_size_max'):
            if cls['subject_duration']:
                chours += float(cls['subject_duration'])
                shours += float(cls['subject_duration']) * (float(
                    cls['class_size_max']) if cls['class_size_max'] else 0)
                crhours += float(cls['subject_duration']) * float(
                    cls['subject_students']) / float(cls['num_sections'])
        vitals["hournum"] = []
        vitals["hournum"].append(("Total # of Class-Hours", chours))
        vitals["hournum"].append(
            ("Total # of Class-Student-Hours (capacity)", shours))
        vitals["hournum"].append(
            ("Total # of Class-Student-Hours (registered)", crhours))

        ## Prefetch enough data that get_meeting_times() and num_students() don't have to hit the db
        curclasses = ClassSection.prefetch_catalog_data(
            ClassSection.objects.filter(
                parent_class__parent_program=prog).select_related(
                    'parent_class', 'parent_class__category'))

        ## Is it really faster to do this logic in Python?
        ## It'd be even faster to just write a raw SQL query to do it.
        ## But this is probably good enough.
        timeslot_dict = defaultdict(list)
        timeslot_set = set(timeslots)
        for section in curclasses:
            for timeslot in set.intersection(timeslot_set,
                                             section.get_meeting_times()):
                timeslot_dict[timeslot].append(section)

        for timeslot in timeslots:
            curTimeslot = {'slotname': timeslot.short_description}

            curTimeslot['classcount'] = len(timeslot_dict[timeslot])

            def student_count(clslist):
                lst = [0] + [
                    x.num_students()
                    for x in clslist if x.category.category != 'Lunch'
                ]
                return reduce(operator.add, lst)

            def student_max_count(clslist):
                lst = [0] + [
                    x.capacity
                    for x in clslist if x.category.category != 'Lunch'
                ]
                return reduce(operator.add, lst)

            curTimeslot['studentcount'] = {
                'count': student_count(timeslot_dict[timeslot]),
                'max_count': student_max_count(timeslot_dict[timeslot])
            }

            vitals['timeslots'].append(curTimeslot)

        dictOut["stats"].append(vitals)

        shirt_data = {
            "id": "shirtnum"
        }
        adminvitals_shirt = prog.getShirtInfo()
        shirt_data["sizes"] = adminvitals_shirt['shirt_sizes']
        shirt_data["types"] = adminvitals_shirt['shirt_types']
        shirt_data["data"] = adminvitals_shirt['shirts']
        dictOut["stats"].append(shirt_data)

        Q_categories = Q(program=prog)
        crmi = prog.classregmoduleinfo
        if crmi.open_class_registration:
            Q_categories |= Q(pk=prog.open_class_category.pk)
        #   Introduce a separate query to get valid categories, since the single query seemed to introduce duplicates
        program_categories = ClassCategories.objects.filter(
            Q_categories).distinct().values_list('id', flat=True)
        annotated_categories = ClassCategories.objects.filter(
            cls__parent_program=prog, cls__status__gte=0).annotate(
                num_subjects=Count('cls', distinct=True),
                num_sections=Count('cls__sections'),
                num_class_hours=Sum('cls__sections__duration')).order_by(
                    '-num_subjects').values('id', 'num_sections',
                                            'num_subjects', 'num_class_hours',
                                            'category').distinct()
        #   Convert Decimal values to float for serialization
        for i in range(len(annotated_categories)):
            annotated_categories[i]['num_class_hours'] = float(
                annotated_categories[i]['num_class_hours'])
        dictOut["stats"].append({
            "id":
            "categories",
            "data":
            filter(lambda x: x['id'] in program_categories,
                   annotated_categories)
        })

        ## Calculate the grade data:
        grades = [i for i in range(prog.grade_min, prog.grade_max + 1)]
        # We can't perfectly trust most_recent_profile, but it's good enough for stats
        students_grades = students['enrolled'].filter(
            registrationprofile__most_recent_profile=True)
        students_grades = students_grades.values_list(
            'registrationprofile__student_info__graduation_year')
        students_grades = students_grades.annotate(Count('id', distinct=True))
        grades_dict = {result[0]: result[1] for result in students_grades}
        grades_results = []
        for g in grades:
            year = ESPUser.YOGFromGrade(g, ESPUser.program_schoolyear(prog))
            grade_classes = classes.filter(status__gte=0,
                                           grade_min__lte=g,
                                           grade_max__gte=g)
            grade_sections = prog.sections().filter(
                status__gte=0, parent_class__in=grade_classes)
            grades_results.append({
                'grade':
                g,
                'num_subjects':
                grade_classes.count(),
                'num_sections':
                grade_sections.count(),
                'num_students':
                grades_dict[year] if year in grades_dict else 0
            })
        dictOut["stats"].append({"id": "grades", "data": grades_results})

        #   Add SplashInfo statistics if our program has them
        splashinfo_data = {}
        splashinfo_modules = filter(lambda x: isinstance(x, SplashInfoModule),
                                    prog.getModules('learn'))
        if len(splashinfo_modules) > 0:
            splashinfo_module = splashinfo_modules[0]
            tag_data = Tag.getProgramTag('splashinfo_choices', prog)
            if tag_data:
                splashinfo_choices = json.loads(tag_data)
            else:
                splashinfo_choices = {
                    'lunchsat': SplashInfoForm.default_choices,
                    'lunchsun': SplashInfoForm.default_choices
                }
            for key in splashinfo_choices:
                counts = {}
                for item in splashinfo_choices[key]:
                    filter_kwargs = {'program': prog}
                    filter_kwargs[key] = item[0]
                    counts[item[1]] = SplashInfo.objects.filter(
                        **filter_kwargs).distinct().count()
                splashinfo_data[key] = counts
            splashinfo_data['siblings'] = {
                'yes':
                SplashInfo.objects.filter(
                    program=prog, siblingdiscount=True).distinct().count(),
                'no':
                SplashInfo.objects.filter(program=prog).exclude(
                    siblingdiscount=True).distinct().count()
            }
            dictOut["stats"].append({
                "id": "splashinfo",
                "data": splashinfo_data
            })

        #   Add accounting stats
        pac = ProgramAccountingController(prog)
        (num_payments, total_payment) = pac.payments_summary()
        accounting_data = {
            'num_payments': num_payments,
            # We need to convert to a float in order for json to serialize it.
            # Since we're not doing any computation client-side with these
            # numbers, this doesn't cause accuracy issues.  If the
            # total_payment is None, just coerce it to zero for display
            # purposes.
            'total_payments': float(total_payment or 0),
        }
        dictOut["stats"].append({"id": "accounting", "data": accounting_data})

        return dictOut
Example #6
0
    def manageclass(self, request, tl, one, two, module, extra, prog):
        cls = self.getClass(request,extra)
        sections = cls.sections.all().order_by('id')
        context = {}

        if cls.isCancelled():
            cls_cancel_form = None
        else:
            cls_cancel_form = ClassCancellationForm(subject=cls)
        sec_cancel_forms = []
        for sec in sections:
            if sec.isCancelled():
                sec_cancel_forms.append(None)
            else:
                sec_cancel_forms.append(SectionCancellationForm(section=sec, prefix='sec'+str(sec.index())))

        action = request.GET.get('action', None)

        if request.method == 'POST':
            if action == 'cancel_cls':
                cls_cancel_form.data = request.POST
                cls_cancel_form.is_bound = True
                if cls_cancel_form.is_valid():
                    #   Call the Class{Subject,Section}.cancel() method to e-mail and remove students, etc.
                    cls_cancel_form.cleaned_data['target'].cancel(email_students=True, include_lottery_students=cls_cancel_form.cleaned_data['email_lottery_students'], text_students=cls_cancel_form.cleaned_data['text_students'], email_teachers = cls_cancel_form.cleaned_data['email_teachers'], explanation=cls_cancel_form.cleaned_data['explanation'], unschedule=cls_cancel_form.cleaned_data['unschedule'])
                    cls_cancel_form = None
            else:
                j = 0
                for i in [sec.index() for sec in sections]:
                    if action == ('cancel_sec_%d' % i):
                        sec_cancel_forms[j].data = request.POST
                        sec_cancel_forms[j].is_bound = True
                        if sec_cancel_forms[j].is_valid():
                            sec_cancel_forms[j].cleaned_data['target'].cancel(email_students=True, include_lottery_students=sec_cancel_forms[j].cleaned_data['email_lottery_students'], text_students=sec_cancel_forms[j].cleaned_data['text_students'], email_teachers = sec_cancel_forms[j].cleaned_data['email_teachers'], explanation=sec_cancel_forms[j].cleaned_data['explanation'], unschedule=sec_cancel_forms[j].cleaned_data['unschedule'])
                            sec_cancel_forms[j] = None
                    j += 1

        cls_form = ClassManageForm(self, subject=cls)
        sec_forms = [SectionManageForm(self, section=sec, prefix='sec'+str(sec.index())) for sec in cls.sections.all().order_by('id')]

        if request.method == 'POST' and action == 'modify':
            cls_form.data = request.POST
            cls_form.is_bound = True
            valid = cls_form.is_valid()
            for sf in sec_forms:
                sf.data = request.POST
                sf.is_bound = True
                valid = (valid and sf.is_valid())

            if valid:
                # Leave a loophole:  You can set a class to "Unreviewed" (ie., status = 0),
                # then cancel it, and it won't kick all the students out
                cls_alter = ClassSubject.objects.get(id=cls_form.cleaned_data['clsid'])
                new_status = int(cls_form.cleaned_data['status'])
                should_cancel_sections = (int(cls_alter.status) > 0 and new_status < 0)

                for sf in sec_forms:
                    sec_alter = ClassSection.objects.get(id=sf.cleaned_data['secid'])
                    orig_sec_status = sec_alter.status
                    sf.save_data(sec_alter)

                    # If the parent class was canceled, cancel the sections
                    if should_cancel_sections and int(sec_alter.status) > 0:
                        sec_alter.status = new_status
                        sec_alter.save()

                    # Kick all the students out of a class if it was rejected
                    if int(sec_alter.status) < 0 and int(orig_sec_status) > 0:
                        for student in sec_alter.students():
                            sec_alter.unpreregister_student(student)

                #   Save class info after section info so that cls_form.save_data()
                #   can override section information if it's supposed to.
                #   This is needed for accepting/rejecting the sections of a
                #   class when the sections are unreviewed.
                cls_form.save_data(cls_alter)

                return HttpResponseRedirect(request.get_full_path())

        if self.program.program_modules.filter(handler='ClassFlagModule').exists():
            context['show_flags'] = True
            context['flag_types'] = ClassFlagType.get_flag_types(self.program)

        context['class'] = cls
        context['sections'] = sections
        context['cls_form'] = cls_form
        context['sec_forms'] = sec_forms
        context['cls_cancel_form'] = cls_cancel_form
        context['sec_cancel_forms'] = sec_cancel_forms
        context['module'] = self

        return render_to_response(self.baseDir()+'manageclass.html', request, context)
    def manageclass(self, request, tl, one, two, module, extra, prog):
        cls = self.getClass(request,extra)
        sections = cls.sections.all().order_by('id')
        context = {}

        if cls.isCancelled():
            cls_cancel_form = None
        else:
            cls_cancel_form = ClassCancellationForm(subject=cls)
        sec_cancel_forms = []
        for sec in sections:
            if sec.isCancelled():
                sec_cancel_forms.append(None)
            else:
                sec_cancel_forms.append(SectionCancellationForm(section=sec, prefix='sec'+str(sec.index())))

        action = request.GET.get('action', None)

        if request.method == 'POST':
            if action == 'cancel_cls':
                cls_cancel_form.data = request.POST
                cls_cancel_form.is_bound = True
                if cls_cancel_form.is_valid():
                    #   Call the Class{Subject,Section}.cancel() method to e-mail and remove students, etc.
                    cls_cancel_form.cleaned_data['target'].cancel(email_students=True, include_lottery_students=cls_cancel_form.cleaned_data['email_lottery_students'], explanation=cls_cancel_form.cleaned_data['explanation'], unschedule=cls_cancel_form.cleaned_data['unschedule'])
                    cls_cancel_form = None
            else:
                j = 0
                for i in [sec.index() for sec in sections]:
                    if action == ('cancel_sec_%d' % i):
                        sec_cancel_forms[j].data = request.POST
                        sec_cancel_forms[j].is_bound = True
                        if sec_cancel_forms[j].is_valid():
                            sec_cancel_forms[j].cleaned_data['target'].cancel(email_students=True, include_lottery_students=sec_cancel_forms[j].cleaned_data['email_lottery_students'], explanation=sec_cancel_forms[j].cleaned_data['explanation'], unschedule=sec_cancel_forms[j].cleaned_data['unschedule'])
                            sec_cancel_forms[j] = None
                    j += 1

        cls_form = ClassManageForm(self, subject=cls)
        sec_forms = [SectionManageForm(self, section=sec, prefix='sec'+str(sec.index())) for sec in cls.sections.all().order_by('id')]

        if request.method == 'POST' and action == 'modify':
            cls_form.data = request.POST
            cls_form.is_bound = True
            valid = cls_form.is_valid()
            for sf in sec_forms:
                sf.data = request.POST
                sf.is_bound = True
                valid = (valid and sf.is_valid())

            if valid:
                # Leave a loophole:  You can set a class to "Unreviewed" (ie., status = 0),
                # then cancel it, and it won't kick all the students out
                cls_alter = ClassSubject.objects.get(id=cls_form.cleaned_data['clsid'])
                new_status = int(cls_form.cleaned_data['status'])
                should_cancel_sections = (int(cls_alter.status) > 0 and new_status < 0)

                for sf in sec_forms:
                    sec_alter = ClassSection.objects.get(id=sf.cleaned_data['secid'])
                    orig_sec_status = sec_alter.status
                    sf.save_data(sec_alter)

                    # If the parent class was canceled, cancel the sections
                    if should_cancel_sections and int(sec_alter.status) > 0:
                        sec_alter.status = new_status
                        sec_alter.save()

                    # Kick all the students out of a class if it was rejected
                    if int(sec_alter.status) < 0 and int(orig_sec_status) > 0:
                        for student in sec_alter.students():
                            sec_alter.unpreregister_student(student)

                #   Save class info after section info so that cls_form.save_data()
                #   can override section information if it's supposed to.
                #   This is needed for accepting/rejecting the sections of a
                #   class when the sections are unreviewed.
                cls_form.save_data(cls_alter)

                return HttpResponseRedirect(request.get_full_path())

        if self.program.program_modules.filter(handler='ClassFlagModule').exists():
            context['show_flags'] = True
            context['flag_types'] = ClassFlagType.get_flag_types(self.program)

        context['class'] = cls
        context['sections'] = sections
        context['cls_form'] = cls_form
        context['sec_forms'] = sec_forms
        context['cls_cancel_form'] = cls_cancel_form
        context['sec_cancel_forms'] = sec_cancel_forms
        context['module'] = self

        return render_to_response(self.baseDir()+'manageclass.html', request, context)
    def stats(prog):
        # Create a dictionary to assemble the output
        dictOut = { "stats": [] }

        classes = prog.classes().select_related()
        vitals = {'id': 'vitals'}

        class_num_list = []
        class_num_list.append(("Total # of Classes", classes.distinct().count()))
        class_num_list.append(("Total # of Class Sections", prog.sections().select_related().distinct().count()))
        class_num_list.append(("Total # of Lunch Classes", classes.filter(category__category = "Lunch").filter(status=10).distinct().count()))
        class_num_list.append(("Total # of Classes <span style='color: #00C;'>Unreviewed</span>", classes.filter(status=0).distinct().count()))
        class_num_list.append(("Total # of Classes <span style='color: #0C0;'>Accepted</span>", classes.filter(status=10).distinct().count()))
        class_num_list.append(("Total # of Classes <span style='color: #C00;'>Rejected</span>", classes.filter(status=-10).distinct().count()))
        class_num_list.append(("Total # of Classes <span style='color: #990;'>Cancelled</span>", classes.filter(status=-20).distinct().count()))
        for ft in ClassFlagType.get_flag_types(prog):
            class_num_list.append(('Total # of Classes with the "%s" flag' % ft.name, classes.filter(flags__flag_type=ft).distinct().count()))
        vitals['classnum'] = class_num_list

        #   Display pretty labels for teacher and student numbers
        teacher_labels_dict = {}
        for module in prog.getModules():
            teacher_labels_dict.update(module.teacherDesc())
        vitals['teachernum'] = []

        ## Ew, queries in a for loop...
        ## Not much to be done about it, though;
        ## the loop is iterating over a list of independent queries and running each.
        teachers = prog.teachers()
        for key in teachers.keys():
            if key in teacher_labels_dict:
                vitals['teachernum'].append((teacher_labels_dict[key],         ## Unfortunately,
teachers[key].filter(is_active = True).distinct().count()))
            else:
                vitals['teachernum'].append((key, teachers[key].filter(is_active = True).distinct().count()))

        student_labels_dict = {}
        for module in prog.getModules():
            student_labels_dict.update(module.studentDesc())
        vitals['studentnum'] = []

        ## Ew, another set of queries in a for loop...
        ## Same justification, though.
        students = prog.students()
        for key in students.keys():
            if key in student_labels_dict:
                vitals['studentnum'].append((student_labels_dict[key], students[key].filter(is_active = True).distinct().count()))
            else:
                vitals['studentnum'].append((key, students[key].filter(is_active = True).distinct().count()))

        timeslots = prog.getTimeSlots()
        vitals['timeslots'] = []


        shours = 0.0
        chours = 0.0
        crhours = 0.0
        ## Write this as a 'for' loop because PostgreSQL can't do it in
        ## one go without a subquery or duplicated logic, and Django
        ## doesn't have enough power to expose either approach directly.
        ## At least there aren't any queries in the for loop...
        ## (In MySQL, this could I believe be done with a minimally-painful extra() clause.)
        ## Also, since we're iterating over a big data set, use .values()
        ## minimize the number of objects that we're creating.
        ## One dict and two Decimals per row, as opposed to
        ## an Object per field and all kinds of stuff...
        for cls in prog.classes().exclude(category__category='Lunch').annotate(num_sections=Count('sections'), subject_duration=Sum('sections__duration'), subject_students=Sum('sections__enrolled_students')).values('num_sections', 'subject_duration', 'subject_students', 'class_size_max'):
            if cls['subject_duration']:
                chours += float(cls['subject_duration'])
                shours += float(cls['subject_duration']) * (float(cls['class_size_max']) if cls['class_size_max'] else 0)
                crhours += float(cls['subject_duration']) * float(cls['subject_students']) / float(cls['num_sections'])
        vitals["hournum"] = []
        vitals["hournum"].append(("Total # of Class-Hours", chours))
        vitals["hournum"].append(("Total # of Class-Student-Hours (capacity)", shours))
        vitals["hournum"].append(("Total # of Class-Student-Hours (registered)", crhours))


        ## Prefetch enough data that get_meeting_times() and num_students() don't have to hit the db
        curclasses = ClassSection.prefetch_catalog_data(
            ClassSection.objects
            .filter(parent_class__parent_program=prog)
            .select_related('parent_class', 'parent_class__category'))

        ## Is it really faster to do this logic in Python?
        ## It'd be even faster to just write a raw SQL query to do it.
        ## But this is probably good enough.
        timeslot_dict = defaultdict(list)
        timeslot_set = set(timeslots)
        for section in curclasses:
            for timeslot in set.intersection(timeslot_set, section.get_meeting_times()):
                timeslot_dict[timeslot].append(section)

        for timeslot in timeslots:
            curTimeslot = {'slotname': timeslot.short_description}

            curTimeslot['classcount'] = len(timeslot_dict[timeslot])

            def student_count(clslist):
                lst = [0] + [x.num_students() for x in clslist if x.category.category != 'Lunch']
                return reduce(operator.add, lst)

            def student_max_count(clslist):
                lst = [0] + [x.capacity for x in clslist if x.category.category != 'Lunch']
                return reduce(operator.add, lst)

            curTimeslot['studentcount'] = {
                'count': student_count(timeslot_dict[timeslot]),
                'max_count': student_max_count(timeslot_dict[timeslot])
                }

            vitals['timeslots'].append(curTimeslot)

        dictOut["stats"].append(vitals)

        shirt_data = {"id": "shirtnum"};
        adminvitals_shirt = prog.getShirtInfo()
        shirt_data["sizes"] = adminvitals_shirt['shirt_sizes'];
        shirt_data["types"] = adminvitals_shirt['shirt_types'];
        shirt_data["data"] = adminvitals_shirt['shirts'];
        dictOut["stats"].append(shirt_data);

        Q_categories = Q(program=prog)
        crmi = prog.classregmoduleinfo
        if crmi.open_class_registration:
            Q_categories |= Q(pk=prog.open_class_category.pk)
        #   Introduce a separate query to get valid categories, since the single query seemed to introduce duplicates
        program_categories = ClassCategories.objects.filter(Q_categories).distinct().values_list('id', flat=True)
        annotated_categories = ClassCategories.objects.filter(cls__parent_program=prog, cls__status__gte=0).annotate(num_subjects=Count('cls', distinct=True), num_sections=Count('cls__sections'), num_class_hours=Sum('cls__sections__duration')).order_by('-num_subjects').values('id', 'num_sections', 'num_subjects', 'num_class_hours', 'category').distinct()
        #   Convert Decimal values to float for serialization
        for i in range(len(annotated_categories)):
            annotated_categories[i]['num_class_hours'] = float(annotated_categories[i]['num_class_hours'])
        dictOut["stats"].append({"id": "categories", "data": filter(lambda x: x['id'] in program_categories, annotated_categories)})

        ## Calculate the grade data:
        grades = [i for i in range(prog.grade_min, prog.grade_max+1)]
        # We can't perfectly trust most_recent_profile, but it's good enough for stats
        students_grades = students['enrolled'].filter(registrationprofile__most_recent_profile=True)
        students_grades = students_grades.values_list('registrationprofile__student_info__graduation_year')
        students_grades = students_grades.annotate(Count('id', distinct=True))
        grades_dict = {result[0]: result[1] for result in students_grades}
        grades_results = []
        for g in grades:
            year = ESPUser.YOGFromGrade(g, ESPUser.program_schoolyear(prog))
            grade_classes = classes.filter(status__gte=0, grade_min__lte=g, grade_max__gte=g)
            grade_sections = prog.sections().filter(status__gte=0, parent_class__in=grade_classes)
            grades_results.append({'grade': g, 'num_subjects': grade_classes.count(),
                                   'num_sections': grade_sections.count(),
                                   'num_students': grades_dict[year] if year in grades_dict else 0})
        dictOut["stats"].append({"id": "grades", "data": grades_results})

        #   Add SplashInfo statistics if our program has them
        splashinfo_data = {}
        splashinfo_modules = filter(lambda x: isinstance(x, SplashInfoModule), prog.getModules('learn'))
        if len(splashinfo_modules) > 0:
            splashinfo_module = splashinfo_modules[0]
            tag_data = Tag.getProgramTag('splashinfo_choices', prog)
            if tag_data:
                splashinfo_choices = json.loads(tag_data)
            else:
                splashinfo_choices = {'lunchsat': SplashInfoForm.default_choices, 'lunchsun': SplashInfoForm.default_choices}
            for key in splashinfo_choices:
                counts = {}
                for item in splashinfo_choices[key]:
                    filter_kwargs = {'program': prog}
                    filter_kwargs[key] = item[0]
                    counts[item[1]] = SplashInfo.objects.filter(**filter_kwargs).distinct().count()
                splashinfo_data[key] = counts
            splashinfo_data['siblings'] = {
                'yes': SplashInfo.objects.filter(program=prog, siblingdiscount=True).distinct().count(),
                'no':  SplashInfo.objects.filter(program=prog).exclude(siblingdiscount=True).distinct().count()
            }
            dictOut["stats"].append({"id": "splashinfo", "data": splashinfo_data})

        #   Add accounting stats
        pac = ProgramAccountingController(prog)
        (num_payments, total_payment) = pac.payments_summary()
        accounting_data = {
            'num_payments': num_payments,
            # We need to convert to a float in order for json to serialize it.
            # Since we're not doing any computation client-side with these
            # numbers, this doesn't cause accuracy issues.  If the
            # total_payment is None, just coerce it to zero for display
            # purposes.
            'total_payments': float(total_payment or 0),
        }
        dictOut["stats"].append({"id": "accounting", "data": accounting_data})

        return dictOut
 def teacherDesc(self):
     fts = ClassFlagType.get_flag_types(self.program)
     descs = {}
     for flag_type in fts:
         descs['flag_%s' % flag_type.id] = """Teachers who have a class with the "%s" flag.""" % flag_type.name
     return descs