def prepare(self, context={}): from esp.program.controllers.studentclassregmodule import RegistrationTypeController as RTC verbs = RTC.getVisibleRegistrationTypeNames(prog=self.program) regProf = RegistrationProfile.getLastForProgram( get_current_request().user, self.program) timeslots = self.program.getTimeSlots( types=['Class Time Block', 'Compulsory']) classList = ClassSection.prefetch_catalog_data( regProf.preregistered_classes(verbs=verbs)) prevTimeSlot = None blockCount = 0 user = get_current_request().user is_onsite = user.isOnsite(self.program) scrmi = self.program.studentclassregmoduleinfo # Filter out volunteer timeslots timeslots = [ x for x in timeslots if x.event_type.description != 'Volunteer' ] schedule = [] timeslot_dict = {} for sec in classList: # Get the verbs all the time in order for the schedule to show # the student's detailed enrollment status. (Performance hit, I know.) # - Michael P, 6/23/2009 # if scrmi.use_priority: sec.verbs = sec.getRegVerbs(user, allowed_verbs=verbs) sec.verb_names = [v.name for v in sec.verbs] sec.is_enrolled = True if "Enrolled" in sec.verb_names else False # While iterating through the meeting times for a section, # we use this variable to keep track of the first timeslot. # In the section_dict appended to timeslot_dict, # we save whether or not this is the first timeslot for this # section. If it isn't, the student schedule will indicate # this, and will not display the option to remove the # section. This is to prevent students from removing what # they have mistaken to be duplicated classes from their # schedules. first_meeting_time = True for mt in sec.get_meeting_times().order_by('start'): section_dict = { 'section': sec, 'first_meeting_time': first_meeting_time } first_meeting_time = False if mt.id in timeslot_dict: timeslot_dict[mt.id].append(section_dict) else: timeslot_dict[mt.id] = [section_dict] for i in range(len(timeslots)): timeslot = timeslots[i] daybreak = False if prevTimeSlot != None: if not Event.contiguous(prevTimeSlot, timeslot): blockCount += 1 daybreak = True if timeslot.id in timeslot_dict: cls_list = timeslot_dict[timeslot.id] doesnt_have_enrollment = not any(sec['section'].is_enrolled for sec in cls_list) schedule.append((timeslot, cls_list, blockCount + 1, doesnt_have_enrollment)) else: schedule.append((timeslot, [], blockCount + 1, False)) prevTimeSlot = timeslot context['num_classes'] = len(classList) context['timeslots'] = schedule context['use_priority'] = scrmi.use_priority context['allow_removal'] = self.deadline_met('/Removal') return 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 prepare(self, context={}): from esp.program.controllers.studentclassregmodule import RegistrationTypeController as RTC verbs = RTC.getVisibleRegistrationTypeNames(prog=self.program) regProf = RegistrationProfile.getLastForProgram( get_current_request().user, self.program) timeslots = self.program.getTimeSlotList(exclude_compulsory=False) classList = ClassSection.prefetch_catalog_data( regProf.preregistered_classes(verbs=verbs)) prevTimeSlot = None blockCount = 0 if not isinstance(get_current_request().user, ESPUser): user = ESPUser(get_current_request().user) else: user = get_current_request().user is_onsite = user.isOnsite(self.program) scrmi = self.program.getModuleExtension('StudentClassRegModuleInfo') # Hack, to hide the Saturday night timeslots from grades 7-8 if not is_onsite and not user.getGrade() > 8: timeslots = [x for x in timeslots if x.start.hour < 19] # Filter out volunteer timeslots timeslots = [ x for x in timeslots if x.event_type.description != 'Volunteer' ] schedule = [] timeslot_dict = {} for sec in classList: # TODO: Fix this bit (it was broken, and may need additional queries # or a parameter added to ClassRegModuleInfo). show_changeslot = False # Get the verbs all the time in order for the schedule to show # the student's detailed enrollment status. (Performance hit, I know.) # - Michael P, 6/23/2009 # if scrmi.use_priority: sec.verbs = sec.getRegVerbs(user, allowed_verbs=verbs) for mt in sec.get_meeting_times(): section_dict = {'section': sec, 'changeable': show_changeslot} if mt.id in timeslot_dict: timeslot_dict[mt.id].append(section_dict) else: timeslot_dict[mt.id] = [section_dict] user_priority = user.getRegistrationPriorities( self.program, [t.id for t in timeslots]) for i in range(len(timeslots)): timeslot = timeslots[i] daybreak = False if prevTimeSlot != None: if not Event.contiguous(prevTimeSlot, timeslot): blockCount += 1 daybreak = True if timeslot.id in timeslot_dict: cls_list = timeslot_dict[timeslot.id] schedule.append( (timeslot, cls_list, blockCount + 1, user_priority[i])) else: schedule.append( (timeslot, [], blockCount + 1, user_priority[i])) prevTimeSlot = timeslot context['num_classes'] = len(classList) context['timeslots'] = schedule context['use_priority'] = scrmi.use_priority context['allow_removal'] = self.deadline_met('/Removal') return 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", len(classes))) class_num_list.append(("Total # of Class Sections", len(prog.sections().select_related()))) class_num_list.append( ("Total # of Lunch Classes", len(classes.filter(status=10)))) class_num_list.append( ("Total # of Classes <span style='color: #00C;'>Unreviewed</span>", len(classes.filter(status=0)))) class_num_list.append( ("Total # of Classes <span style='color: #0C0;'>Accepted</span>", len(classes.filter(status=10)))) class_num_list.append( ("Total # of Classes <span style='color: #C00;'>Rejected</span>", len(classes.filter(status=-10)))) class_num_list.append( ("Total # of Classes <span style='color: #990;'>Cancelled</span>", len(classes.filter(status=-20)))) 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, len(teachers[key]))) else: vitals['teachernum'].append((key, len(teachers[key]))) 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], len(students[key]))) else: vitals['studentnum'].append((key, len(students[key]))) 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)) ## 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.getModuleExtension('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')).order_by( '-num_subjects').values('id', 'num_sections', 'num_subjects', 'category').distinct() dictOut["stats"].append({ "id": "categories", "data": filter(lambda x: x['id'] in program_categories, annotated_categories) }) # 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 }) return dictOut
def prepare(self, context={}): from esp.program.controllers.studentclassregmodule import RegistrationTypeController as RTC verbs = RTC.getVisibleRegistrationTypeNames(prog=self.program) regProf = RegistrationProfile.getLastForProgram(get_current_request().user, self.program) timeslots = self.program.getTimeSlots(types=['Class Time Block', 'Compulsory']) classList = ClassSection.prefetch_catalog_data(regProf.preregistered_classes(verbs=verbs)) prevTimeSlot = None blockCount = 0 user = get_current_request().user is_onsite = user.isOnsite(self.program) scrmi = self.program.studentclassregmoduleinfo # Filter out volunteer timeslots timeslots = [x for x in timeslots if x.event_type.description != 'Volunteer'] schedule = [] timeslot_dict = {} for sec in classList: # Get the verbs all the time in order for the schedule to show # the student's detailed enrollment status. (Performance hit, I know.) # - Michael P, 6/23/2009 # if scrmi.use_priority: sec.verbs = sec.getRegVerbs(user, allowed_verbs=verbs) sec.verb_names = [v.name for v in sec.verbs] sec.is_enrolled = True if "Enrolled" in sec.verb_names else False # While iterating through the meeting times for a section, # we use this variable to keep track of the first timeslot. # In the section_dict appended to timeslot_dict, # we save whether or not this is the first timeslot for this # section. If it isn't, the student schedule will indicate # this, and will not display the option to remove the # section. This is to prevent students from removing what # they have mistaken to be duplicated classes from their # schedules. first_meeting_time = True for mt in sec.get_meeting_times().order_by('start'): section_dict = {'section': sec, 'first_meeting_time': first_meeting_time} first_meeting_time = False if mt.id in timeslot_dict: timeslot_dict[mt.id].append(section_dict) else: timeslot_dict[mt.id] = [section_dict] for i in range(len(timeslots)): timeslot = timeslots[i] daybreak = False if prevTimeSlot != None: if not Event.contiguous(prevTimeSlot, timeslot): blockCount += 1 daybreak = True if timeslot.id in timeslot_dict: cls_list = timeslot_dict[timeslot.id] doesnt_have_enrollment = not any(sec['section'].is_enrolled for sec in cls_list) schedule.append((timeslot, cls_list, blockCount + 1, doesnt_have_enrollment)) else: schedule.append((timeslot, [], blockCount + 1, False)) prevTimeSlot = timeslot context['num_classes'] = len(classList) context['timeslots'] = schedule context['use_priority'] = scrmi.use_priority context['allow_removal'] = self.deadline_met('/Removal') return context
def prepare(self, context=None): import operator if context is None: context = {} classes = self.program.classes().select_related() vitals = {'classtotal': classes} vitals['classsections'] = self.program.sections().select_related() vitals['classapproved'] = classes.filter(status=10) vitals['classunreviewed'] = classes.filter(status=0) vitals['classrejected'] = classes.filter(status=-10) vitals['classcancelled'] = classes.filter(status=-20) proganchor = self.program_anchor_cached() # Display pretty labels for teacher and student numbers teacher_labels_dict = {} for module in self.program.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 = self.program.teachers() for key in teachers.keys(): if key in teacher_labels_dict: vitals['teachernum'].append((teacher_labels_dict[key], ## Unfortunately, teachers[key])) else: vitals['teachernum'].append((key, teachers[key])) student_labels_dict = {} for module in self.program.getModules(): student_labels_dict.update(module.studentDesc()) vitals['studentnum'] = [] ## Ew, another set of queries in a for loop... ## Same justification, though. students = self.program.students() for key in students.keys(): if key in student_labels_dict: vitals['studentnum'].append((student_labels_dict[key], students[key])) else: vitals['studentnum'].append((key, students[key])) timeslots = self.program.getTimeSlots() vitals['timeslots'] = [] ## 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 = self.program)) ## 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]) class studentcounter: self.clslist = [] def count(self): lst = [0] + [x.num_students() for x in self.clslist if x.category.category != 'Lunch'] return reduce(operator.add, lst) def max_count(self): lst = [0] + [x.capacity for x in self.clslist if x.category.category != 'Lunch'] return reduce(operator.add, lst) def __init__(self, newclslist): self.clslist = newclslist curTimeslot['studentcount'] = studentcounter(timeslot_dict[timeslot]) vitals['timeslots'].append(curTimeslot) context['vitals'] = vitals adminvitals_shirt = self.program.getShirtInfo() context['shirt_sizes'] = adminvitals_shirt['shirt_sizes'] context['shirt_types'] = adminvitals_shirt['shirt_types'] context['shirts'] = adminvitals_shirt['shirts'] shours = Decimal('0.0') chours = Decimal('0.0') crhours = Decimal('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 self.program.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 += cls['subject_duration'] shours += cls['subject_duration'] * (cls['class_size_max'] if cls['class_size_max'] else 0) crhours += cls['subject_duration'] * cls['subject_students'] / cls['num_sections'] context['classhours'] = chours context['classpersonhours'] = shours context['classreghours'] = crhours Q_categories = Q(program=self.program) crmi = self.program.getModuleExtension('ClassRegModuleInfo') if crmi.open_class_registration: Q_categories |= Q(pk=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=self.program, cls__status__gte=0).annotate(num_subjects=Count('cls', distinct=True), num_sections=Count('cls__sections')).order_by('-num_subjects').values('id', 'num_sections', 'num_subjects', 'category').distinct() context['categories'] = filter(lambda x: x['id'] in program_categories, annotated_categories) return context
def setUp(self): """ Create a Program to work with """ super(self, ProgramModuleTestCase).setUp() self._max_prog_modules_id = ProgramModule.objects.order_by( '-id')[0].id # We need all the Program Modules loaded up to do this properly from esp.program.modules.models import install as program_modules_install program_modules_install() self.module = moduleClass() self.programmodule = ProgramModule.objects.get( handler=self.module.module_properties_autopopulated() ['handler']) self.prog_anchor = GetNode("Q/Programs/TestProgram/%s" % Random().sample(string.letters, 12)) self.prog_urlname = "%s/%s" % (self.prog_anchor.parent.name, self.prog_anchor.name) self.prog = Program() # Create a random anchor, so that we can keep creating new programs self.prog.anchor = self.prog_anchor self.prog.grade_min = 8 self.prog.grade_max = 11 self.prog.director_email = "root@localhost" self.prog.program_size_max = 1000 self.prog.program_modules.add(self.programmodule) self.prog.save() # Now, we need some class categories to play with self.class_categories = [ ClassCategories.objects.get_or_create(category='Academic', symbol='A')[0], ClassCategories.objects.get_or_create(category='Comedic', symbol='C')[0], ClassCategories.objects.get_or_create( category='Extracurricular', symbol='E')[0] ] # Stick some classes in this program self.classes = [ClassSubject(), ClassSubject(), ClassSubject()] for klass in self.classes: klass.anchor = self.prog_anchor klass.parent_program = self.prog klass.category = self.class_categories[0] klass.grade_min = 7 klass.grade_max = 12 klass.class_size_min = 0 klass.class_size_max = 100 klass.duration = 2 klass.save() # And do the sketchtastic thing to get the DataTree node ID klass.anchor = GetNode( "%s/Classes/%s" % (klass.anchor.get_uri(), klass.emailcode())) klass.anchor.friendly_name = "Sample Class" klass.anchor.save() klass.save() # And make a class section too, just for good measure. # After all, we're not much of a class if we don't have a section. section = ClassSection() section.anchor = GetNode("%s/Section1" % klass.anchor.get_uri()) section.duration = 2 section.parent_class = klass section.save() self.students = [ESPUser(), ESPUser(), ESPUser(), ESPUser()] for student in self.students: # Set up some students pass self.teachers = [ESPUser(), ESPUser(), ESPUser()] for teacher in self.teachers: # Set up some teachers pass
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