def schoolyear(request): path_parts = request.path.lstrip('/').split('/') if len(path_parts) > 3: program_url = '/'.join(path_parts[1:3]) if Program.objects.filter(url=program_url).count() == 1: program = Program.objects.get(url=program_url) return {'schoolyear': ESPUser.program_schoolyear(program)} return {'schoolyear': ESPUser.current_schoolyear()}
def initialize(self): """ Gather all of the information needed to run the lottery assignment. This includes: - Students' interest (priority and interested bits) - Class schedules and capacities - Timeslots (incl. lunch periods for each day) """ self.enroll_orig = numpy.zeros( (self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool) self.enroll_final = numpy.zeros( (self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool) self.request = numpy.zeros( (self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool) self.waitlist = [ numpy.zeros( (self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool) for i in range(self.priority_limit + 1) ] self.section_schedules = numpy.zeros( (self.num_sections, self.num_timeslots), dtype=numpy.bool) self.section_capacities = numpy.zeros((self.num_sections, ), dtype=numpy.int32) self.section_scores = numpy.zeros((self.num_sections, ), dtype=numpy.int32) self.same_subject = numpy.zeros((self.num_sections, self.num_sections), dtype=numpy.bool) self.section_conflict = numpy.zeros( (self.num_sections, self.num_sections), dtype=numpy.bool ) # is this a section that takes place in the same timeblock # Get student, section, timeslot IDs and prepare lookup table (self.student_ids, self.student_indices) = self.get_ids_and_indices(self.students) (self.section_ids, self.section_indices) = self.get_ids_and_indices(self.sections) (self.timeslot_ids, self.timeslot_indices) = self.get_ids_and_indices(self.timeslots) self.parent_classes = numpy.array( self.sections.values_list('parent_class__id', flat=True)) self.student_not_checked_in = numpy.zeros((self.num_students, ), dtype=numpy.bool) self.student_not_checked_in[self.student_indices[ self.students_not_checked_in]] = True # Get IDs of timeslots allocated to lunch by day # (note: requires that this is constant across days) self.lunch_schedule = numpy.zeros((self.num_timeslots, )) lunch_timeslots = Event.objects.filter( meeting_times__parent_class__parent_program=self.program, meeting_times__parent_class__category__category='Lunch').order_by( 'start').distinct() # Note: this code should not be necessary once lunch-constraints branch is merged (provides Program.dates()) dates = [] for ts in self.timeslots: ts_day = date(ts.start.year, ts.start.month, ts.start.day) if ts_day not in dates: dates.append(ts_day) lunch_by_day = [[] for x in dates] ts_count = 0 for ts in lunch_timeslots: d = date(ts.start.year, ts.start.month, ts.start.day) lunch_by_day[dates.index(d)].append(ts.id) self.lunch_schedule[self.timeslot_indices[ts.id]] = True for i in range(len(lunch_by_day)): if len(lunch_by_day[i]) > ts_count: ts_count = len(lunch_by_day[i]) self.lunch_timeslots = numpy.zeros((len(lunch_by_day), ts_count), dtype=numpy.int32) for i in range(len(lunch_by_day)): self.lunch_timeslots[i, :len(lunch_by_day[i])] = numpy.array( lunch_by_day[i]) # Populate old enrollment matrix enroll_regs = StudentRegistration.objects.filter( self.Q_EN).values_list('user__id', 'section__id', 'section__meeting_times__id').distinct() era = numpy.array(enroll_regs, dtype=numpy.uint32) try: self.enroll_orig[self.student_indices[era[:, 0]], self.section_indices[era[:, 1]], self.timeslot_indices[era[:, 2]]] = True except IndexError: pass self.student_not_checked_in[numpy.transpose( numpy.nonzero(True - self.enroll_orig.any(axis=(1, 2))))] = True # Populate old enrollment matrix request_regs = StudentRegistration.objects.filter( self.Q_REQ).values_list('user__id', 'section__id', 'section__meeting_times__id').distinct() rra = numpy.array(request_regs, dtype=numpy.uint32) try: self.request[self.student_indices[rra[:, 0]], self.section_indices[rra[:, 1]], self.timeslot_indices[rra[:, 2]]] = True except IndexError: pass # Populate waitlist matrix waitlist_regs = [ StudentRegistration.objects.filter(self.Q_WAIT[i]).values_list( 'user__id', 'section__id', 'section__meeting_times__id').distinct() for i in range(self.priority_limit + 1) ] wra = [ numpy.array(waitlist_regs[i], dtype=numpy.uint32) for i in range(self.priority_limit + 1) ] self.waitlist[0][:, :, :] = True for i in range(1, self.priority_limit + 1): try: self.waitlist[i][self.student_indices[wra[i][:, 0]], self.section_indices[wra[i][:, 1]], self.timeslot_indices[wra[i][:, 2]]] = True self.waitlist[0][self.student_indices[wra[i][:, 0]], self.section_indices[wra[i][:, 1]], self.timeslot_indices[wra[i][:, 2]]] = False except IndexError: pass # Populate section schedule section_times = numpy.array( self.sections.values_list('id', 'meeting_times__id')) self.section_schedules[self.section_indices[section_times[:, 0]], self.timeslot_indices[section_times[:, 1]]] = True # Populate section overlap matrix for i in range(self.num_sections): group_ids = numpy.nonzero( self.parent_classes == self.parent_classes[i])[0] self.same_subject[numpy.meshgrid(group_ids, group_ids)] = True sec_times = numpy.transpose( numpy.nonzero(self.section_schedules[i, :])) for [ ts_ind, ] in sec_times: self.section_conflict[numpy.transpose( numpy.nonzero(self.section_schedules[:, ts_ind])), i] = True self.section_conflict[ i, numpy.transpose( numpy.nonzero(self.section_schedules[:, ts_ind]))] = True self.section_conflict[i, i] = False # Populate section grade limits self.section_grade_min = numpy.array(self.sections.values_list( 'parent_class__grade_min', flat=True), dtype=numpy.uint32) self.section_grade_max = numpy.array(self.sections.values_list( 'parent_class__grade_max', flat=True), dtype=numpy.uint32) # Populate student grades; grade will be assumed to be 0 if not entered on profile self.student_grades = numpy.zeros((self.num_students, )) gradyear_pairs = numpy.array(RegistrationProfile.objects.filter( user__id__in=list(self.student_ids), most_recent_profile=True, student_info__graduation_year__isnull=False).values_list( 'user__id', 'student_info__graduation_year'), dtype=numpy.uint32) self.student_grades[self.student_indices[ gradyear_pairs[:, 0]]] = 12 + ESPUser.program_schoolyear( self.program) - gradyear_pairs[:, 1] # Find section capacities (TODO: convert to single query) for sec in self.sections: sec_ind = self.section_indices[sec.id] self.section_capacities[sec_ind] = sec.capacity - sec.num_students( ) self.section_capacities[sec_ind] += numpy.count_nonzero( (self.enroll_orig[:, sec_ind, self.section_schedules[sec_ind, :]].any( axis=1) * self.request[:, :, self.section_schedules[sec_ind, :]].any( axis=(1, 2)))) # number enrolled but want to switch out self.enroll_final[numpy.transpose( numpy.nonzero( (self.enroll_orig[:, sec_ind, self.section_schedules[sec_ind, :]].any( axis=1) * (True - self.request[:, :, self.section_schedules[ sec_ind, :]].any(axis=(1, 2)))))), sec_ind, self.section_schedules[sec_ind, :]] = True self.section_scores[sec_ind] = -self.section_capacities[sec_ind] self.section_scores[sec_ind] += numpy.count_nonzero( self.request[:, sec_ind, :].any( axis=1)) # number who want to switch in self.section_capacities_orig = numpy.copy(self.section_capacities) self.section_scores_orig = numpy.copy(self.section_scores) self.enroll_final_orig = numpy.copy(self.enroll_final) self.clear_assignments()
def onsite_create(self, request, tl, one, two, module, extra, prog): if request.method == 'POST': form = OnSiteRegForm(request.POST) if form.is_valid(): new_data = form.cleaned_data username = ESPUser.get_unused_username(new_data['first_name'], new_data['last_name']) new_user = ESPUser.objects.create_user( username=username, first_name=new_data['first_name'], last_name=new_data['last_name'], email=new_data['email']) self.student = new_user regProf = RegistrationProfile.getLastForProgram( new_user, self.program) contact_user = ContactInfo(first_name=new_user.first_name, last_name=new_user.last_name, e_mail=new_user.email, user=new_user) contact_user.save() regProf.contact_user = contact_user student_info = StudentInfo( user=new_user, graduation_year=ESPUser.YOGFromGrade( new_data['grade'], ESPUser.program_schoolyear(self.program))) try: if isinstance(new_data['k12school'], K12School): student_info.k12school = new_data['k12school'] else: if isinstance(new_data['k12school'], int): student_info.k12school = K12School.objects.get( id=int(new_data['k12school'])) else: student_info.k12school = K12School.objects.filter( name__icontains=new_data['k12school'])[0] except: student_info.k12school = None student_info.school = new_data[ 'school'] if not student_info.k12school else student_info.k12school.name student_info.save() regProf.student_info = student_info regProf.save() if new_data['paid']: self.createBit('paid') self.updatePaid(True) else: self.updatePaid(False) self.createBit('Attended') if new_data['medical']: self.createBit('Med') if new_data['liability']: self.createBit('Liab') self.createBit('OnSite') new_user.groups.add(Group.objects.get(name="Student")) new_user.recoverPassword() return render_to_response( self.baseDir() + 'reg_success.html', request, { 'student': new_user, 'retUrl': '/onsite/%s/classchange_grid?student_id=%s' % (self.program.getUrlBase(), new_user.id) }) else: form = OnSiteRegForm() return render_to_response(self.baseDir() + 'reg_info.html', request, {'form': form})
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 initialize(self): """ Gather all of the information needed to run the lottery assignment. This includes: - Students' interest (priority and interested bits) - Class schedules and capacities - Timeslots (incl. lunch periods for each day) """ self.enroll_orig = numpy.zeros((self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool) self.enroll_final = numpy.zeros((self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool) self.request = numpy.zeros((self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool) self.waitlist = [numpy.zeros((self.num_students, self.num_sections, self.num_timeslots), dtype=numpy.bool) for i in range(self.priority_limit+1)] self.section_schedules = numpy.zeros((self.num_sections, self.num_timeslots), dtype=numpy.bool) self.section_capacities = numpy.zeros((self.num_sections,), dtype=numpy.int32) self.section_scores = numpy.zeros((self.num_sections,), dtype=numpy.int32) self.same_subject = numpy.zeros((self.num_sections, self.num_sections), dtype=numpy.bool) self.section_conflict = numpy.zeros((self.num_sections, self.num_sections), dtype=numpy.bool) # is this a section that takes place in the same timeblock # Get student, section, timeslot IDs and prepare lookup table (self.student_ids, self.student_indices) = self.get_ids_and_indices(self.students) (self.section_ids, self.section_indices) = self.get_ids_and_indices(self.sections) (self.timeslot_ids, self.timeslot_indices) = self.get_ids_and_indices(self.timeslots) self.parent_classes = numpy.array(self.sections.values_list('parent_class__id', flat=True)) self.student_not_checked_in = numpy.zeros((self.num_students,), dtype=numpy.bool) self.student_not_checked_in[self.student_indices[self.students_not_checked_in]] = True # Get IDs of timeslots allocated to lunch by day # (note: requires that this is constant across days) self.lunch_schedule = numpy.zeros((self.num_timeslots,)) lunch_timeslots = Event.objects.filter(meeting_times__parent_class__parent_program=self.program, meeting_times__parent_class__category__category='Lunch').order_by('start').distinct() # Note: this code should not be necessary once lunch-constraints branch is merged (provides Program.dates()) dates = [] for ts in self.timeslots: ts_day = date(ts.start.year, ts.start.month, ts.start.day) if ts_day not in dates: dates.append(ts_day) lunch_by_day = [[] for x in dates] ts_count = 0 for ts in lunch_timeslots: d = date(ts.start.year, ts.start.month, ts.start.day) lunch_by_day[dates.index(d)].append(ts.id) self.lunch_schedule[self.timeslot_indices[ts.id]] = True for i in range(len(lunch_by_day)): if len(lunch_by_day[i]) > ts_count: ts_count = len(lunch_by_day[i]) self.lunch_timeslots = numpy.zeros((len(lunch_by_day), ts_count), dtype=numpy.int32) for i in range(len(lunch_by_day)): self.lunch_timeslots[i, :len(lunch_by_day[i])] = numpy.array(lunch_by_day[i]) # Populate old enrollment matrix enroll_regs = StudentRegistration.objects.filter(self.Q_EN).values_list('user__id', 'section__id', 'section__meeting_times__id').distinct() era = numpy.array(enroll_regs, dtype=numpy.uint32) try: self.enroll_orig[self.student_indices[era[:, 0]], self.section_indices[era[:, 1]], self.timeslot_indices[era[:, 2]]] = True except IndexError: pass self.student_not_checked_in[numpy.transpose(numpy.nonzero(True-self.enroll_orig.any(axis=(1,2))))] = True # Populate old enrollment matrix request_regs = StudentRegistration.objects.filter(self.Q_REQ).values_list('user__id', 'section__id', 'section__meeting_times__id').distinct() rra = numpy.array(request_regs, dtype=numpy.uint32) try: self.request[self.student_indices[rra[:, 0]], self.section_indices[rra[:, 1]], self.timeslot_indices[rra[:, 2]]] = True except IndexError: pass # Populate waitlist matrix waitlist_regs = [StudentRegistration.objects.filter(self.Q_WAIT[i]).values_list('user__id', 'section__id', 'section__meeting_times__id').distinct() for i in range(self.priority_limit+1)] wra = [numpy.array(waitlist_regs[i], dtype=numpy.uint32) for i in range(self.priority_limit+1)] self.waitlist[0][:,:,:] = True for i in range(1, self.priority_limit+1): try: self.waitlist[i][self.student_indices[wra[i][:, 0]], self.section_indices[wra[i][:, 1]], self.timeslot_indices[wra[i][:, 2]]] = True self.waitlist[0][self.student_indices[wra[i][:, 0]], self.section_indices[wra[i][:, 1]], self.timeslot_indices[wra[i][:, 2]]] = False except IndexError: pass # Populate section schedule section_times = numpy.array(self.sections.values_list('id', 'meeting_times__id')) self.section_schedules[self.section_indices[section_times[:, 0]], self.timeslot_indices[section_times[:, 1]]] = True # Populate section overlap matrix for i in range(self.num_sections): group_ids = numpy.nonzero(self.parent_classes == self.parent_classes[i])[0] self.same_subject[numpy.meshgrid(group_ids, group_ids)] = True sec_times = numpy.transpose(numpy.nonzero(self.section_schedules[i, :])) for [ts_ind,] in sec_times: self.section_conflict[numpy.transpose(numpy.nonzero(self.section_schedules[:, ts_ind])), i] = True self.section_conflict[i, numpy.transpose(numpy.nonzero(self.section_schedules[:, ts_ind]))] = True self.section_conflict[i, i] = False # Populate section grade limits self.section_grade_min = numpy.array(self.sections.values_list('parent_class__grade_min', flat=True), dtype=numpy.uint32) self.section_grade_max = numpy.array(self.sections.values_list('parent_class__grade_max', flat=True), dtype=numpy.uint32) # Populate student grades; grade will be assumed to be 0 if not entered on profile self.student_grades = numpy.zeros((self.num_students,)) gradyear_pairs = numpy.array(RegistrationProfile.objects.filter(user__id__in=list(self.student_ids), most_recent_profile=True, student_info__graduation_year__isnull=False).values_list('user__id', 'student_info__graduation_year'), dtype=numpy.uint32) self.student_grades[self.student_indices[gradyear_pairs[:, 0]]] = 12 + ESPUser.program_schoolyear(self.program) - gradyear_pairs[:, 1] # Find section capacities (TODO: convert to single query) for sec in self.sections: sec_ind = self.section_indices[sec.id] self.section_capacities[sec_ind] = sec.capacity - sec.num_students() self.section_capacities[sec_ind] += numpy.count_nonzero((self.enroll_orig[:, sec_ind, self.section_schedules[sec_ind,:]].any(axis=1) * self.request[:,:,self.section_schedules[sec_ind,:]].any(axis=(1,2)))) # number enrolled but want to switch out self.enroll_final[numpy.transpose(numpy.nonzero((self.enroll_orig[:, sec_ind, self.section_schedules[sec_ind,:]].any(axis=1) * (True - self.request[:,:,self.section_schedules[sec_ind,:]].any(axis=(1,2)))))), sec_ind, self.section_schedules[sec_ind,:]] = True self.section_scores[sec_ind] = -self.section_capacities[sec_ind] self.section_scores[sec_ind] += numpy.count_nonzero(self.request[:, sec_ind, :].any(axis=1)) # number who want to switch in self.section_capacities_orig = numpy.copy(self.section_capacities) self.section_scores_orig = numpy.copy(self.section_scores) self.enroll_final_orig = numpy.copy(self.enroll_final) self.clear_assignments()
def initialize(self): """ Gather all of the information needed to run the lottery assignment. This includes: - Students' interest (priority and interested bits) - Class schedules and capacities - Timeslots (incl. lunch periods for each day) """ self.interest = numpy.zeros((self.num_students, self.num_sections), dtype=numpy.bool) self.priority = [numpy.zeros((self.num_students, self.num_sections), dtype=numpy.bool) for i in range(self.effective_priority_limit+1)] self.ranks = 10*numpy.ones((self.num_students, self.num_sections), dtype=numpy.int32) self.section_schedules = numpy.zeros((self.num_sections, self.num_timeslots), dtype=numpy.bool) self.section_start_schedules = numpy.zeros((self.num_sections, self.num_timeslots), dtype=numpy.bool) self.section_capacities = numpy.zeros((self.num_sections,), dtype=numpy.uint32) self.section_overlap = numpy.zeros((self.num_sections, self.num_sections), dtype=numpy.bool) # One array to keep track of the utility of each student # (defined as hours of interested class + 1.5*hours of priority classes) # and the other arrary to keep track of student weigths (defined as # of classes signed up for) self.student_utility_weights = numpy.zeros((self.num_students, ), dtype=numpy.float) self.student_utilities = numpy.zeros((self.num_students, ), dtype=numpy.float) # Get student, section, timeslot IDs and prepare lookup table (self.student_ids, self.student_indices) = self.get_ids_and_indices(self.lotteried_students) (self.section_ids, self.section_indices) = self.get_ids_and_indices(self.sections) (self.timeslot_ids, self.timeslot_indices) = self.get_ids_and_indices(self.timeslots) self.parent_classes = numpy.array(self.sections.values_list('parent_class__id', flat=True)) # Get IDs of timeslots allocated to lunch by day # (note: requires that this is constant across days) self.lunch_schedule = numpy.zeros((self.num_timeslots,)) lunch_timeslots = Event.objects.filter(meeting_times__parent_class__parent_program=self.program, meeting_times__parent_class__category__category='Lunch').order_by('start').distinct() # Note: this code should not be necessary once lunch-constraints branch is merged (provides Program.dates()) dates = [] for ts in self.timeslots: ts_day = date(ts.start.year, ts.start.month, ts.start.day) if ts_day not in dates: dates.append(ts_day) lunch_by_day = [[] for x in dates] ts_count = 0 for ts in lunch_timeslots: d = date(ts.start.year, ts.start.month, ts.start.day) lunch_by_day[dates.index(d)].append(ts.id) self.lunch_schedule[self.timeslot_indices[ts.id]] = True self.lunch_timeslots = numpy.array(lunch_by_day) # Populate interest matrix; this uses both the StudentRegistrations (which apply to a particular section) and StudentSubjectIntegests (which apply to all sections of the class). If one does not exist, ignore it. Be careful to only return SRs and SSIs for accepted sections of accepted classes; this might matter for SSIs where only some sections of the class are accepted. interest_regs_sr = StudentRegistration.valid_objects().filter(section__parent_class__parent_program=self.program, section__status__gt=0, section__parent_class__status__gt=0, section__registration_status=0, section__meeting_times__isnull=False, relationship__name='Interested').values_list('user__id', 'section__id').distinct() interest_regs_ssi = StudentSubjectInterest.valid_objects().filter(subject__parent_program=self.program, subject__status__gt=0, subject__sections__status__gt=0, subject__sections__registration_status=0, subject__sections__meeting_times__isnull=False).values_list('user__id', 'subject__sections__id').distinct() self.put_prefs_in_array(interest_regs_sr, self.interest) self.put_prefs_in_array(interest_regs_ssi, self.interest) # Populate priority matrix priority_regs = [StudentRegistration.valid_objects().filter(section__parent_class__parent_program=self.program, relationship__name='Priority/%s'%i).values_list('user__id', 'section__id').distinct() for i in range(self.real_priority_limit+1)] if self.grade_range_exceptions: priority_regs.append(StudentRegistration.valid_objects().filter(section__parent_class__parent_program=self.program, relationship__name='GradeRangeException').values_list('user__id', 'section__id').distinct()) for i in range(1,self.effective_priority_limit+1): self.put_prefs_in_array(priority_regs[i], self.priority[i]) if self.options['use_student_apps']: for i in range(1,self.effective_priority_limit+1): for (student_id,section_id) in priority_regs[i]: self.ranks[self.student_indices[student_id],self.section_indices[section_id]] = ESPUser.getRankInClass(student_id,self.parent_classes[self.section_indices[section_id]]) for (student_id,section_id) in interest_regs_sr + interest_regs_ssi: self.ranks[self.student_indices[student_id],self.section_indices[section_id]] = ESPUser.getRankInClass(student_id,self.parent_classes[self.section_indices[section_id]]) # Set student utility weights. Counts number of classes that students selected. Used only for computing the overall_utility stat self.student_utility_weights = numpy.sum(self.interest.astype(float), 1) + sum([numpy.sum(self.priority[i].astype(float), 1) for i in range(1,self.effective_priority_limit+1)]) # Populate section schedule section_times = numpy.array(self.sections.values_list('id', 'meeting_times__id')) start_times = numpy.array(self.sections.annotate(start_time=Min('meeting_times')).values_list('id','start_time')) self.section_schedules[self.section_indices[section_times[:, 0]], self.timeslot_indices[section_times[:, 1]]] = True self.section_start_schedules[self.section_indices[start_times[:, 0]], self.timeslot_indices[start_times[:, 1]]] = True # Populate section overlap matrix for i in range(self.num_sections): group_ids = numpy.nonzero(self.parent_classes == self.parent_classes[i])[0] self.section_overlap[numpy.meshgrid(group_ids, group_ids)] = True # Populate section grade limits self.section_grade_min = numpy.array(self.sections.values_list('parent_class__grade_min', flat=True), dtype=numpy.uint32) self.section_grade_max = numpy.array(self.sections.values_list('parent_class__grade_max', flat=True), dtype=numpy.uint32) # Populate student grades; grade will be assumed to be 0 if not entered on profile self.student_grades = numpy.zeros((self.num_students,)) gradyear_pairs = numpy.array(RegistrationProfile.objects.filter(user__id__in=list(self.student_ids), most_recent_profile=True, student_info__graduation_year__isnull=False).values_list('user__id', 'student_info__graduation_year'), dtype=numpy.uint32) self.student_grades[self.student_indices[gradyear_pairs[:, 0]]] = 12 + ESPUser.program_schoolyear(self.program) - gradyear_pairs[:, 1] # Find section capacities (TODO: convert to single query) for sec in self.sections: self.section_capacities[self.section_indices[sec.id]] = sec.capacity # Populate section lengths (hours) self.section_lengths = numpy.array([x.nonzero()[0].size for x in self.section_schedules]) if self.options['fill_low_priorities']: # Compute who has a priority when. Includes lower priorities, since this is used for places where we check not clobbering priorities. self.has_priority = [numpy.zeros((self.num_students, self.num_timeslots), dtype=numpy.bool) for i in range(self.effective_priority_limit+1)] for i in range(1,self.effective_priority_limit+1): priority_at_least_i = reduce(operator.or_,[self.priority[j] for j in range(i,self.effective_priority_limit+1)]) numpy.dot(priority_at_least_i,self.section_schedules,out=self.has_priority[i]) self.sections_at_same_time = numpy.dot(self.section_schedules, numpy.transpose(self.section_schedules)) # And the same, overlappingly. self.has_overlapping_priority = [numpy.zeros((self.num_students, self.num_timeslots), dtype=numpy.bool) for i in range(self.effective_priority_limit+1)] for i in range(1,self.effective_priority_limit+1): priority_at_least_i = reduce(operator.or_,[self.priority[j] for j in range(i,self.effective_priority_limit+1)]) numpy.dot(numpy.dot(priority_at_least_i,self.sections_at_same_time),self.section_schedules,out=self.has_overlapping_priority[i]) # Fill in preferences for students who haven't ranked them. In particular, if a student has ranked some level of class in a timeblock (i.e. they plan to be at Splash that timeblock), but has not ranked any priority/n or lower-priority classes overlapping it, add a random class from their interesteds. for i in range(1,self.real_priority_limit+1): #Use self.real_priority_limit since we don't want to give people free grade range exceptions! should_fill = numpy.transpose(numpy.nonzero(self.has_priority[1]&~self.has_overlapping_priority[i])) if len(should_fill): for student, timeslot in should_fill: # student is interested, and class starts in this timeslot, and class does not overlap any lower or equal priorities possible_classes = numpy.nonzero(self.interest[student] & self.section_start_schedules[:,timeslot] & ~numpy.dot(self.section_schedules, numpy.transpose(self.has_priority[i][student])))[0] if len(possible_classes): choice = numpy.random.choice(possible_classes) self.priority[i][student,choice]=True
def onsite_create(self, request, tl, one, two, module, extra, prog): if request.method == 'POST': form = OnSiteRegForm(request.POST) if form.is_valid(): new_data = form.cleaned_data username = ESPUser.get_unused_username(new_data['first_name'], new_data['last_name']) new_user = ESPUser.objects.create_user(username = username, first_name = new_data['first_name'], last_name = new_data['last_name'], email = new_data['email']) self.student = new_user regProf = RegistrationProfile.getLastForProgram(new_user, self.program) contact_user = ContactInfo(first_name = new_user.first_name, last_name = new_user.last_name, e_mail = new_user.email, user = new_user) contact_user.save() regProf.contact_user = contact_user student_info = StudentInfo(user = new_user, graduation_year = ESPUser.YOGFromGrade(new_data['grade'], ESPUser.program_schoolyear(self.program))) try: if isinstance(new_data['k12school'], K12School): student_info.k12school = new_data['k12school'] else: if isinstance(new_data['k12school'], int): student_info.k12school = K12School.objects.get(id=int(new_data['k12school'])) else: student_info.k12school = K12School.objects.filter(name__icontains=new_data['k12school'])[0] except: student_info.k12school = None student_info.school = new_data['school'] if not student_info.k12school else student_info.k12school.name student_info.save() regProf.student_info = student_info regProf.save() if new_data['paid']: self.createBit('paid') self.updatePaid(True) else: self.updatePaid(False) self.createBit('Attended') if new_data['medical']: self.createBit('Med') if new_data['liability']: self.createBit('Liab') self.createBit('OnSite') new_user.groups.add(Group.objects.get(name="Student")) new_user.recoverPassword() return render_to_response(self.baseDir()+'reg_success.html', request, { 'student': new_user, 'retUrl': '/onsite/%s/classchange_grid?student_id=%s' % (self.program.getUrlBase(), new_user.id) }) else: form = OnSiteRegForm() return render_to_response(self.baseDir()+'reg_info.html', request, {'form':form})
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