def students(self, QObject=False): Enrolled = Q(studentregistration__relationship__name='Enrolled') Par = Q(studentregistration__section__parent_class__parent_program=self .program) Unexpired = nest_Q(StudentRegistration.is_valid_qobject(), 'studentregistration') # Force Django to generate two subqueries without joining SRs to SSIs, # as efficiently as possible since it's still a big query. sr_ids = StudentRegistration.valid_objects().filter( section__parent_class__parent_program=self.program).values( 'user').distinct() ssi_ids = StudentSubjectInterest.valid_objects().filter( subject__parent_program=self.program).values('user').distinct() any_reg_q = Q(id__in=sr_ids) | Q(id__in=ssi_ids) qobjects = { 'enrolled': Enrolled & Par & Unexpired, 'classreg': any_reg_q, } if QObject: return qobjects else: return { k: ESPUser.objects.filter(v).distinct() for k, v in qobjects.iteritems() }
def students(self, QObject = False): Enrolled = Q(studentregistration__relationship__name='Enrolled') Par = Q(studentregistration__section__parent_class__parent_program=self.program) Unexpired = nest_Q(StudentRegistration.is_valid_qobject(), 'studentregistration') # Force Django to generate two subqueries without joining SRs to SSIs, # as efficiently as possible since it's still a big query. sr_ids = StudentRegistration.valid_objects().filter( section__parent_class__parent_program=self.program ).values('user').distinct() ssi_ids = StudentSubjectInterest.valid_objects().filter( subject__parent_program=self.program).values('user').distinct() any_reg_q = Q(id__in = sr_ids) | Q(id__in = ssi_ids) qobjects = { 'enrolled': Enrolled & Par & Unexpired, 'classreg': any_reg_q, } if QObject: return qobjects else: return {k: ESPUser.objects.filter(v).distinct() for k, v in qobjects.iteritems()}
def interested_classes(self, request, tl, one, two, module, extra, prog): ssis = StudentSubjectInterest.valid_objects().filter( user=request.user) subject_ids = ssis.values('subject') section_ids = ssis.values('subject__sections') return {'interested_subjects': subject_ids, 'interested_sections': section_ids}
def interested_classes(self, request, tl, one, two, module, extra, prog): ssis = StudentSubjectInterest.valid_objects().filter(user=request.user) subject_ids = ssis.values('subject') section_ids = ssis.values('subject__sections') return { 'interested_subjects': subject_ids, 'interested_sections': section_ids }
def num_users_with_lottery(self, prog): # Past empirical observation has shown that doing the union in SQL is # much, much slower for unknown reasons; it also means we would have to # query over the users table, so this saves us joining that table. users_with_ssis = set(StudentSubjectInterest.valid_objects().filter( subject__parent_program=prog).values_list('user').distinct()) users_with_srs = set(StudentRegistration.valid_objects().filter( Q(relationship__name='Interested') | Q(relationship__name__contains='Priority/'), section__parent_class__parent_program=prog).values_list( 'user').distinct()) return len(users_with_ssis | users_with_srs)
def mark_classes_interested(self, request, tl, one, two, module, extra, prog): """ Saves the set of classes marked as interested by the student. Ex: request.POST['json_data'] = { 'interested': [1,5,3,9], 'not_interested': [4,6,10] } """ if not 'json_data' in request.POST: return HttpResponseBadRequest('JSON data not included in request.') try: json_data = json.loads(request.POST['json_data']) except ValueError: return HttpResponseBadRequest('JSON data mis-formatted.') if not isinstance(json_data.get('interested'), list) or \ not isinstance(json_data.get('not_interested'), list): return HttpResponseBadRequest('JSON data mis-formatted.') # Determine which of the given class ids are valid valid_classes = ClassSubject.objects.filter( pk__in=json_data['interested'], parent_program=prog, status__gte=0, grade_min__lte=request.user.getGrade(prog), grade_max__gte=request.user.getGrade(prog)) # Unexpire any matching SSIs that exist already (to avoid # creating duplicate objects). to_unexpire = StudentSubjectInterest.objects.filter( user=request.user, subject__in=valid_classes) to_unexpire.update(end_date=None) # Determine which valid ids haven't had SSIs created yet # and bulk create those objects. valid_ids = valid_classes.values_list('pk', flat=True) existing_ids = to_unexpire.values_list('subject__pk', flat=True) to_create_ids = set(valid_ids) - set(existing_ids) StudentSubjectInterest.objects.bulk_create([ StudentSubjectInterest(user=request.user, subject_id=subj_id) for subj_id in to_create_ids ]) # Expire any matching SSIs that are in 'not_interested' to_expire = StudentSubjectInterest.objects.filter( user=request.user, subject__pk__in=json_data['not_interested']) to_expire.update(end_date=datetime.datetime.now()) if request.is_ajax(): return HttpResponse() else: return self.goToCore(tl)
def num_users_with_lottery(self, prog): # Past empirical observation has shown that doing the union in SQL is # much, much slower for unknown reasons; it also means we would have to # query over the users table, so this saves us joining that table. users_with_ssis = set( StudentSubjectInterest.valid_objects() .filter(subject__parent_program=prog) .values_list('user').distinct()) users_with_srs = set( StudentRegistration.valid_objects() .filter( Q(relationship__name='Interested') | Q(relationship__name__contains='Priority/'), section__parent_class__parent_program=prog) .values_list('user').distinct()) return len(users_with_ssis | users_with_srs)
def students(self, QObject=False): q_sr = Q( studentregistration__section__parent_class__parent_program=self. program) & nest_Q(StudentRegistration.is_valid_qobject(), 'studentregistration') q_ssi = Q(studentsubjectinterest__subject__parent_program=self.program ) & nest_Q(StudentSubjectInterest.is_valid_qobject(), 'studentsubjectinterest') if QObject: return { 'twophase_star_students': q_ssi, 'twophase_priority_students': q_sr } else: return { 'twophase_star_students': ESPUser.objects.filter(q_ssi).distinct(), 'twophase_priority_students': ESPUser.objects.filter(q_sr).distinct() }
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 num_ssis(self, prog): return StudentSubjectInterest.valid_objects().filter( subject__parent_program=prog).count()
def studentreg2phase(self, request, tl, one, two, module, extra, prog): """ Serves the two-phase student reg page. This page includes instructions for registration, and links to the phase1/phase2 sub-pages. """ context = {} timeslot_dict = {} # Populate the timeslot dictionary with the priority to class title # mappings for each timeslot. priority_regs = StudentRegistration.valid_objects().filter( user=request.user, relationship__name__startswith='Priority') priority_regs = priority_regs.select_related('relationship', 'section', 'section__parent_class') for student_reg in priority_regs: rel = student_reg.relationship title = student_reg.section.parent_class.title sec = student_reg.section times = sec.meeting_times.all().order_by('start') if times.count() == 0: continue timeslot = times[0].id if not timeslot in timeslot_dict: timeslot_dict[timeslot] = {rel: title} else: timeslot_dict[timeslot][rel] = title star_counts = {} interests = StudentSubjectInterest.valid_objects().filter( user=request.user, subject__parent_program=prog) interests = interests.select_related('subject').prefetch_related( 'subject__sections__meeting_times') for interest in interests: cls = interest.subject for sec in cls.sections.all(): times = sec.meeting_times.all() if len(times) == 0: continue timeslot = min(times, key=lambda t: t.start).id if not timeslot in star_counts: star_counts[timeslot] = 1 else: star_counts[timeslot] += 1 # Iterate through timeslots and create a list of tuples of information prevTimeSlot = None blockCount = 0 schedule = [] timeslots = prog.getTimeSlots(types=['Class Time Block', 'Compulsory']) context['num_priority'] = prog.priorityLimit() context['num_star'] = Tag.getProgramTag("num_stars", program=prog, default=10) for i in range(len(timeslots)): timeslot = timeslots[i] if prevTimeSlot != None: if not Event.contiguous(prevTimeSlot, timeslot): blockCount += 1 if timeslot.id in timeslot_dict: priority_dict = timeslot_dict[timeslot.id] # (relationship, class_title) -> relationship.name priority_list = sorted(priority_dict.items(), key=lambda item: item[0].name) else: priority_list = [] temp_list = [] for i in range(0, context['num_priority']): if i < len(priority_list): temp_list.append( ("Priority " + str(i + 1), priority_list[i][1])) else: temp_list.append(("Priority " + str(i + 1), "")) priority_list = temp_list star_count = 0 if timeslot.id in star_counts: star_count = star_counts[timeslot.id] schedule.append( (timeslot, priority_list, blockCount + 1, star_count, float(star_count) / context['num_star'] * 100)) prevTimeSlot = timeslot context['timeslots'] = schedule return render_to_response(self.baseDir() + 'studentregtwophase.html', request, context)
def studentreg2phase(self, request, tl, one, two, module, extra, prog): """ Serves the two-phase student reg page. This page includes instructions for registration, and links to the phase1/phase2 sub-pages. """ timeslot_dict = {} # Populate the timeslot dictionary with the priority to class title # mappings for each timeslot. priority_regs = StudentRegistration.valid_objects().filter( user=request.user, relationship__name__startswith='Priority') priority_regs = priority_regs.select_related( 'relationship', 'section', 'section__parent_class') for student_reg in priority_regs: rel = student_reg.relationship title = student_reg.section.parent_class.title sec = student_reg.section times = sec.meeting_times.all().order_by('start') if times.count() == 0: continue timeslot = times[0].id if not timeslot in timeslot_dict: timeslot_dict[timeslot] = {rel: title} else: timeslot_dict[timeslot][rel] = title star_counts = {} interests = StudentSubjectInterest.valid_objects().filter( user=request.user, subject__parent_program=prog) interests = interests.select_related( 'subject').prefetch_related('subject__sections__meeting_times') for interest in interests: cls = interest.subject for sec in cls.sections.all(): times = sec.meeting_times.all() if len(times) == 0: continue timeslot = min(times, key=lambda t: t.start).id if not timeslot in star_counts: star_counts[timeslot] = 1 else: star_counts[timeslot] += 1 # Iterate through timeslots and create a list of tuples of information prevTimeSlot = None blockCount = 0 schedule = [] timeslots = prog.getTimeSlots(types=['Class Time Block', 'Compulsory']) for i in range(len(timeslots)): timeslot = timeslots[i] if prevTimeSlot != None: if not Event.contiguous(prevTimeSlot, timeslot): blockCount += 1 if timeslot.id in timeslot_dict: priority_dict = timeslot_dict[timeslot.id] priority_list = sorted(priority_dict.items()) else: priority_list = [] if timeslot.id in star_counts: priority_list.append(( 'Starred', "(%d classes)" % star_counts[timeslot.id])) schedule.append((timeslot, priority_list, blockCount + 1)) prevTimeSlot = timeslot context = {} context['timeslots'] = schedule return render_to_response( self.baseDir()+'studentregtwophase.html', request, context)
def students(self, QObject = False): q_sr = Q(studentregistration__section__parent_class__parent_program=self.program) & nest_Q(StudentRegistration.is_valid_qobject(), 'studentregistration') q_ssi = Q(studentsubjectinterest__subject__parent_program=self.program) & nest_Q(StudentSubjectInterest.is_valid_qobject(), 'studentsubjectinterest') if QObject: return {'twophase_star_students': q_ssi, 'twophase_priority_students' : q_sr} else: return {'twophase_star_students': ESPUser.objects.filter(q_ssi).distinct(), 'twophase_priority_students': ESPUser.objects.filter(q_sr).distinct()}