Пример #1
0
    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}
Пример #4
0
 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
     }
Пример #5
0
 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)
Пример #6
0
    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)
Пример #8
0
 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()
         }
Пример #9
0
    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()
Пример #11
0
    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()}
Пример #14
0
 def num_ssis(self, prog):
     return StudentSubjectInterest.valid_objects().filter(
         subject__parent_program=prog).count()