Exemplo n.º 1
0
    def makeaclass(self, user, reg_data, form_class=TeacherClassRegForm):

        reg_form, resource_formset, restype_formset = self.get_forms(reg_data, form_class=form_class)

        self.require_teacher_has_time(user, reg_form._get_total_time_requested())

        cls = ClassSubject()
        self.attach_class_to_program(cls)
        self.make_class_happen(cls, user, reg_form, resource_formset, restype_formset)
        
        self.force_availability(user)  ## So the default DB state reflects the default form state of "all times work"

        self.send_class_mail_to_directors(cls)

        return cls
Exemplo n.º 2
0
 def teacherDesc(self):
     capacity_factor = ClassSubject.get_capacity_factor()
     result = {
         'class_approved':
         """Teachers teaching an approved class""",
         'class_proposed':
         """Teachers teaching an unreviewed class""",
         'class_rejected':
         """Teachers teaching a rejected class""",
         'class_full':
         """Teachers teaching a completely full class""",
         'class_nearly_full':
         """Teachers teaching a nearly-full class (>%d%% of capacity)""" %
         (100 * capacity_factor),
         'taught_before':
         """Teachers who have taught for a previous program""",
     }
     for item in self.get_resource_pairs():
         result[item[0]] = item[1]
     return result
    def teachers(self, QObject=False):
        fields_to_defer = [
            x.name for x in ClassSubject._meta.fields
            if isinstance(x, models.TextField)
        ]
        classes_qs = self.program.classes().defer(*fields_to_defer)

        Q_isteacher = Q(classsubject__in=classes_qs)
        Q_rejected_teacher = Q(classsubject__in=classes_qs.filter(
            status__lt=0)) & Q_isteacher
        Q_approved_teacher = Q(classsubject__in=classes_qs.filter(
            status__gt=0)) & Q_isteacher
        Q_proposed_teacher = Q(classsubject__in=classes_qs.filter(
            status=0)) & Q_isteacher

        ## is_nearly_full() means at least one section is more than float(ClassSubject.get_capacity_factor()) full
        ## isFull() means that all *scheduled* sections are full
        ## Querying the full catalog is overkill here, but we do use a fair bit of it..., and hopefully it's
        ## better cached than other simpler queries that we might use.
        classes = ClassSubject.objects.catalog(self.program)
        capacity_factor = ClassSubject.get_capacity_factor()
        nearly_full_classes = [
            x for x in classes if x.is_nearly_full(capacity_factor)
        ]
        Q_nearly_full_teacher = Q(
            classsubject__in=nearly_full_classes) & Q_isteacher
        full_classes = [x for x in classes if x.isFull()]
        Q_full_teacher = Q(classsubject__in=full_classes) & Q_isteacher

        #   With the new schema it is impossible to make a single Q object for
        #   teachers who have taught for a previous program and teachers
        #   who are teaching for the current program.  You have to chain calls
        #   to .filter().
        Q_taught_before = Q(
            classsubject__status=10,
            classsubject__parent_program__in=Program.objects.exclude(
                pk=self.program.pk))

        #   Add dynamic queries for checking for teachers with particular resource requests
        additional_qs = {}
        for item in self.get_resource_pairs():
            additional_qs[item[0]] = Q_isteacher & (
                Q_rejected_teacher | Q_approved_teacher
                | Q_proposed_teacher) & item[2]

        if QObject:
            result = {
                'class_submitted': Q_isteacher,
                'class_approved': Q_approved_teacher,
                'class_proposed': Q_proposed_teacher,
                'class_rejected': Q_rejected_teacher,
                'class_nearly_full': Q_nearly_full_teacher,
                'class_full': Q_full_teacher,
                'taught_before':
                Q_taught_before,  #   not exactly correct, see above
            }
            for key in additional_qs:
                result[key] = additional_qs[key]
        else:
            result = {
                'class_submitted':
                ESPUser.objects.filter(Q_isteacher).distinct(),
                'class_approved':
                ESPUser.objects.filter(Q_approved_teacher).distinct(),
                'class_proposed':
                ESPUser.objects.filter(Q_proposed_teacher).distinct(),
                'class_rejected':
                ESPUser.objects.filter(Q_rejected_teacher).distinct(),
                'class_nearly_full':
                ESPUser.objects.filter(Q_nearly_full_teacher).distinct(),
                'class_full':
                ESPUser.objects.filter(Q_full_teacher).distinct(),
                'taught_before':
                ESPUser.objects.filter(Q_isteacher).filter(
                    Q_taught_before).distinct(),
            }
            for key in additional_qs:
                result[key] = ESPUser.objects.filter(
                    additional_qs[key]).distinct()

        return result
    def get_lunch_subject(self, day):
        """ Locate lunch subject with the appropriate day in the 'message for directors' field. """

        category = self.get_lunch_category()
        lunch_subjects = ClassSubject.objects.filter(parent_program__id=self.program.id, category=self.get_lunch_category(), message_for_directors=day.isoformat())
        lunch_subject = None
        example_timeslot = self.days[day]['lunch'][0]
        timeslot_length = (example_timeslot.end - example_timeslot.start).seconds / 3600.0

        if lunch_subjects.count() == 0:
            #   If no lunch was found, create a new subject
            new_subject = ClassSubject()
            new_subject.grade_min = 7
            new_subject.grade_max = 12
            new_subject.parent_program = self.program
            new_subject.category = category
            new_subject.class_info = 'Enjoy a break for lunch with your friends!  Please register for at least one lunch period on each day of the program.'
            new_subject.class_size_min = 0
            # If the program doesn't have a max size, we unfortunately still
            # need one here.  Set a really big one.
            new_subject.class_size_max = self.program.program_size_max or 10**6
            new_subject.status = 10
            new_subject.duration = '%.4f' % timeslot_length
            new_subject.message_for_directors = day.isoformat()
            new_subject.save()
            new_subject.title = 'Lunch Period'
            new_subject.save()
            lunch_subject = new_subject
        else:
            #   Otherwise, return an existing lunch subject
            lunch_subject = lunch_subjects[0]
        return lunch_subject
Exemplo n.º 5
0
    def get_lunch_subject(self, day):
        """ Locate lunch subject with the appropriate day in the 'message for directors' field. """

        category = self.get_lunch_category()
        lunch_subjects = ClassSubject.objects.filter(
            parent_program__id=self.program.id,
            category=self.get_lunch_category(),
            message_for_directors=day.isoformat())
        lunch_subject = None
        example_timeslot = self.days[day]['lunch'][0]
        timeslot_length = (example_timeslot.end -
                           example_timeslot.start).seconds / 3600.0

        if lunch_subjects.count() == 0:
            #   If no lunch was found, create a new subject
            new_subject = ClassSubject()
            new_subject.grade_min = 7
            new_subject.grade_max = 12
            new_subject.parent_program = self.program
            new_subject.category = category
            new_subject.class_info = 'Enjoy a break for lunch with your friends!  Please register for at least one lunch period on each day of the program.'
            new_subject.class_size_min = 0
            # If the program doesn't have a max size, we unfortunately still
            # need one here.  Set a really big one.
            new_subject.class_size_max = self.program.program_size_max or 10**6
            new_subject.status = 10
            new_subject.duration = '%.4f' % timeslot_length
            new_subject.message_for_directors = day.isoformat()
            new_subject.save()
            new_subject.title = 'Lunch Period'
            new_subject.save()
            lunch_subject = new_subject
        else:
            #   Otherwise, return an existing lunch subject
            lunch_subject = lunch_subjects[0]
        return lunch_subject
Exemplo n.º 6
0
    def test_teachers(self):
        # Get the instance of StudentClassRegModule
        pm = ProgramModule.objects.get(handler='StudentClassRegModule')
        ProgramModuleObj.getFromProgModule(self.program, pm)

        d = self.moduleobj.teachers()

        # Reject a class from self.teacher, approve a class from self.other_teacher1, make a class from self.other_teacher2 proposed
        cls1 = random.choice(self.teacher.getTaughtClasses())
        cls1.status = -1
        cls1.save()
        cls2 = random.choice(self.other_teacher1.getTaughtClasses())
        cls2.status = 1
        cls2.save()
        cls3 = random.choice(self.other_teacher2.getTaughtClasses())
        cls3.status = 0
        cls3.save()
        # Check them
        d = self.moduleobj.teachers()
        self.failUnless(self.teacher in d['class_rejected'])
        self.failUnless(self.other_teacher1 in d['class_approved'])
        self.failUnless(self.other_teacher2 in d['class_proposed'])
        # Undo the statuses
        cls1.status = 10
        cls1.save()
        cls2.status = 10
        cls2.save()
        cls3.status = 10
        cls3.save()

        # Schedule the classes randomly
        self.schedule_randomly()
        # Find an empty class
        cls = random.choice([
            cls for cls in self.program.classes() if not cls.isFull()
            and not cls.is_nearly_full(ClassSubject.get_capacity_factor())
        ])
        teacher = cls.get_teachers()[0]
        classes = list(teacher.getTaughtClasses())
        classes.remove(cls)
        for c in classes:
            c.removeTeacher(teacher)
        #print teacher.getTaughtClasses()
        d = self.moduleobj.teachers()
        # Check it
        self.failUnless(teacher not in d['class_full']
                        and teacher not in d['class_nearly_full'])

        # Mostly fill it
        self.add_student_profiles()
        for i in range(0, 4):
            cur_student = self.students[i]
            cls.preregister_student(cur_student)
        # Check it
        d = self.moduleobj.teachers()
        self.failUnless(teacher in d['class_nearly_full'])
        self.failUnless(teacher not in d['class_full'])

        # Fill it
        cls.get_sections()[0].preregister_student(self.students[4])
        # Check it
        d = self.moduleobj.teachers()
        self.failUnless(teacher in d['class_full'])
        self.failUnless(teacher in d['class_nearly_full'])

        # Make a program
        self.create_past_program()

        # Create a class for the teacher
        new_class, created = ClassSubject.objects.get_or_create(
            category=self.categories[0],
            grade_min=7,
            grade_max=12,
            parent_program=self.new_prog,
            class_size_max=30,
            class_info='Previous class!')
        new_class.makeTeacher(self.teacher)
        new_class.add_section(duration=50.0 / 60.0)
        new_class.accept()
        # Check taught_before
        d = self.moduleobj.teachers()
        self.failUnless(self.teacher in d['taught_before'])

        transaction.rollback()
Exemplo n.º 7
0
    def teachers(self, QObject=False):
        #   New approach: Pile the class datatree anchor IDs into the appropriate lists.

        Q_isteacher = Q(userbit__verb=GetNode('V/Flags/Registration/Teacher'))
        fields_to_defer = [
            x.name for x in ClassSubject._meta.fields
            if isinstance(x, models.TextField)
        ]
        Q_rejected_teacher = Q(
            userbit__qsc__classsubject__in=self.program.classes().defer(
                *fields_to_defer).filter(status__lt=0)) & Q_isteacher
        Q_approved_teacher = Q(
            userbit__qsc__classsubject__in=self.program.classes().defer(
                *fields_to_defer).filter(status__gt=0)) & Q_isteacher
        Q_proposed_teacher = Q(
            userbit__qsc__classsubject__in=self.program.classes().defer(
                *fields_to_defer).filter(status=0)) & Q_isteacher

        ## is_nearly_full() means at least one section is more than float(ClassSubject.get_capacity_factor()) full
        ## isFull() means that all *scheduled* sections are full
        ## Querying the full catalog is overkill here, but we do use a fair bit of it..., and hopefully it's
        ## better cached than other simpler queries that we might use.
        classes = ClassSubject.objects.catalog(self.program)
        capacity_factor = ClassSubject.get_capacity_factor()
        nearly_full_classes = [
            x.anchor for x in classes if x.is_nearly_full(capacity_factor)
        ]
        Q_nearly_full_teacher = Q(
            userbit__qsc__in=nearly_full_classes) & Q_isteacher
        full_classes = [x.anchor for x in classes if x.isFull()]
        Q_full_teacher = Q(userbit__qsc__in=full_classes) & Q_isteacher

        Q_taught_before = Q_isteacher & Q(
            userbit__qsc__classsubject__status=10,
            userbit__qsc__classsubject__parent_program__in=Program.objects.
            exclude(pk=self.program.pk))

        #   Add dynamic queries for checking for teachers with particular resource requests
        additional_qs = {}
        for item in self.get_resource_pairs():
            additional_qs[item[0]] = Q_isteacher & (
                Q_rejected_teacher | Q_approved_teacher
                | Q_proposed_teacher) & item[2]

        if QObject:
            result = {
                'class_approved': self.getQForUser(Q_approved_teacher),
                'class_proposed': self.getQForUser(Q_proposed_teacher),
                'class_rejected': self.getQForUser(Q_rejected_teacher),
                'class_nearly_full': self.getQForUser(Q_nearly_full_teacher),
                'class_full': self.getQForUser(Q_full_teacher),
                'taught_before': self.getQForUser(Q_taught_before),
            }
            for key in additional_qs:
                result[key] = self.getQForUser(additional_qs[key])
        else:
            result = {
                'class_approved':
                ESPUser.objects.filter(Q_approved_teacher).distinct(),
                'class_proposed':
                ESPUser.objects.filter(Q_proposed_teacher).distinct(),
                'class_rejected':
                ESPUser.objects.filter(Q_rejected_teacher).distinct(),
                'class_nearly_full':
                ESPUser.objects.filter(Q_nearly_full_teacher).distinct(),
                'class_full':
                ESPUser.objects.filter(Q_full_teacher).distinct(),
                'taught_before':
                ESPUser.objects.filter(Q_taught_before).distinct(),
            }
            for key in additional_qs:
                result[key] = ESPUser.objects.filter(
                    additional_qs[key]).distinct()

        return result
Exemplo n.º 8
0
        def setUp(self):
            """ Create a Program to work with """
            super(self, ProgramModuleTestCase).setUp()

            self._max_prog_modules_id = ProgramModule.objects.order_by(
                '-id')[0].id

            # We need all the Program Modules loaded up to do this properly
            from esp.program.modules.models import install as program_modules_install
            program_modules_install()

            self.module = moduleClass()
            self.programmodule = ProgramModule.objects.get(
                handler=self.module.module_properties_autopopulated()
                ['handler'])

            self.prog_anchor = GetNode("Q/Programs/TestProgram/%s" %
                                       Random().sample(string.letters, 12))
            self.prog_urlname = "%s/%s" % (self.prog_anchor.parent.name,
                                           self.prog_anchor.name)
            self.prog = Program()

            # Create a random anchor, so that we can keep creating new programs
            self.prog.anchor = self.prog_anchor
            self.prog.grade_min = 8
            self.prog.grade_max = 11
            self.prog.director_email = "root@localhost"
            self.prog.program_size_max = 1000

            self.prog.program_modules.add(self.programmodule)

            self.prog.save()

            # Now, we need some class categories to play with
            self.class_categories = [
                ClassCategories.objects.get_or_create(category='Academic',
                                                      symbol='A')[0],
                ClassCategories.objects.get_or_create(category='Comedic',
                                                      symbol='C')[0],
                ClassCategories.objects.get_or_create(
                    category='Extracurricular', symbol='E')[0]
            ]

            # Stick some classes in this program
            self.classes = [ClassSubject(), ClassSubject(), ClassSubject()]
            for klass in self.classes:
                klass.anchor = self.prog_anchor
                klass.parent_program = self.prog
                klass.category = self.class_categories[0]
                klass.grade_min = 7
                klass.grade_max = 12
                klass.class_size_min = 0
                klass.class_size_max = 100
                klass.duration = 2
                klass.save()

                # And do the sketchtastic thing to get the DataTree node ID
                klass.anchor = GetNode(
                    "%s/Classes/%s" %
                    (klass.anchor.get_uri(), klass.emailcode()))
                klass.anchor.friendly_name = "Sample Class"
                klass.anchor.save()
                klass.save()

                # And make a class section too, just for good measure.
                # After all, we're not much of a class if we don't have a section.
                section = ClassSection()
                section.anchor = GetNode("%s/Section1" %
                                         klass.anchor.get_uri())
                section.duration = 2
                section.parent_class = klass
                section.save()

            self.students = [ESPUser(), ESPUser(), ESPUser(), ESPUser()]
            for student in self.students:
                # Set up some students
                pass

            self.teachers = [ESPUser(), ESPUser(), ESPUser()]
            for teacher in self.teachers:
                # Set up some teachers
                pass
    def test_teachers(self):
        # Get the instance of StudentClassRegModule
        pm = ProgramModule.objects.get(handler='StudentClassRegModule')
        ProgramModuleObj.getFromProgModule(self.program, pm)

        d = self.moduleobj.teachers()

        # Reject a class from self.teacher, approve a class from self.other_teacher1, make a class from self.other_teacher2 proposed
        cls1 = random.choice(self.teacher.getTaughtClasses())
        cls1.status = -1
        cls1.save()
        cls2 = random.choice(self.other_teacher1.getTaughtClasses())
        cls2.status = 1
        cls2.save()
        cls3 = random.choice(self.other_teacher2.getTaughtClasses())
        cls3.status = 0
        cls3.save()
        # Check them
        d = self.moduleobj.teachers()
        self.assertTrue(self.teacher in d['class_rejected'])
        self.assertTrue(self.other_teacher1 in d['class_approved'])
        self.assertTrue(self.other_teacher2 in d['class_proposed'])
        # Undo the statuses
        cls1.status = 10
        cls1.save()
        cls2.status = 10
        cls2.save()
        cls3.status = 10
        cls3.save()

        # Schedule the classes randomly
        self.schedule_randomly()
        # Find an empty class
        cls = random.choice([cls for cls in self.program.classes() if not cls.isFull() and not cls.is_nearly_full(ClassSubject.get_capacity_factor())])
        teacher = cls.get_teachers()[0]
        classes = list(teacher.getTaughtClasses())
        classes.remove(cls)
        for c in classes:
            c.removeTeacher(teacher)
        d = self.moduleobj.teachers()
        # Check it
        self.assertTrue(teacher not in d['class_full'] and teacher not in d['class_nearly_full'])

        # Mostly fill it
        self.add_student_profiles()
        for i in range(0, 4):
            cur_student = self.students[i]
            cls.preregister_student(cur_student)
        # Check it
        d = self.moduleobj.teachers()
        self.assertTrue(teacher in d['class_nearly_full'])
        self.assertTrue(teacher not in d['class_full'])

        # Fill it
        cls.get_sections()[0].preregister_student(self.students[4])
        # Check it
        d = self.moduleobj.teachers()
        self.assertTrue(teacher in d['class_full'])
        self.assertTrue(teacher in d['class_nearly_full'])

        # Make a program
        self.create_past_program()

        # Create a class for the teacher
        new_class, created = ClassSubject.objects.get_or_create(category=self.categories[0], grade_min=7, grade_max=12, parent_program=self.new_prog, class_size_max=30, class_info='Previous class!')
        new_class.makeTeacher(self.teacher)
        new_class.add_section(duration=50.0/60.0)
        new_class.accept()
        # Check taught_before
        d = self.moduleobj.teachers()
        self.assertTrue(self.teacher in d['taught_before'])
Exemplo n.º 10
0
    def get_lunch_subject(self, day):
        """ Locate lunch subject with the appropriate day in the 'message for directors' field. """

        category = self.get_lunch_category()
        lunch_subjects = ClassSubject.objects.filter(
            parent_program__id=self.program.id,
            category=self.get_lunch_category(),
            message_for_directors=day.isoformat())
        lunch_subject = None
        example_timeslot = self.days[day]['lunch'][0]
        timeslot_length = (example_timeslot.end -
                           example_timeslot.start).seconds / 3600.0

        if lunch_subjects.count() == 0:
            #   If no lunch was found, create a new subject
            new_subject = ClassSubject()
            new_subject.grade_min = 7
            new_subject.grade_max = 12
            new_subject.parent_program = self.program
            new_subject.category = category
            new_subject.class_info = 'Enjoy a break for lunch with your friends!  Please register for at least one lunch period on each day of the program.'
            new_subject.class_size_min = 0
            new_subject.class_size_max = self.program.program_size_max
            new_subject.status = 10
            new_subject.duration = '%.4f' % timeslot_length
            new_subject.message_for_directors = day.isoformat()
            new_subject.anchor = self.program.classes_node()
            new_subject.save()
            nodestring = new_subject.category.symbol + str(new_subject.id)
            new_anchor = new_subject.anchor.tree_create([nodestring])
            new_anchor.friendly_name = 'Lunch Period'
            new_anchor.save()
            new_subject.anchor = new_anchor
            new_subject.save()
            lunch_subject = new_subject
            #   print 'Generated subject: %s' % lunch_subject
        else:
            #   Otherwise, return an existing lunch subject
            lunch_subject = lunch_subjects[0]
            #   print 'Selected subject: %s' % lunch_subject
        return lunch_subject
Exemplo n.º 11
0
    def satprep_classgen(self, request, tl, one, two, module, extra, prog):
        """ This view will generate the classes for all the users. """

        delete = False

        if not request.method == 'POST' and not request.POST.has_key(
                'newclass_create'):
            #   Show the form asking for a room number and capacity for each teacher.

            user_list = self.program.getLists()['teachers_satprepinfo']['list']
            reginfos = [
                module_ext.SATPrepTeacherModuleInfo.objects.get(program=prog,
                                                                user=u)
                for u in user_list
            ]
            reginfos.sort(key=lambda x: x.subject + x.section)
            user_list = [r.user for r in reginfos]

            context = {
                'timeslots': prog.getTimeSlots(),
                'teacher_data': zip([ESPUser(u) for u in user_list], reginfos)
            }

            return render_to_response(self.baseDir() + 'newclass_confirm.html',
                                      request, (prog, tl), context)

        #   Delete current classes if specified (currently turned off, not necessary)
        if delete:
            cur_classes = ClassSubject.objects.filter(
                parent_program=self.program)
            [cls.delete() for cls in cur_classes]

        data = request.POST

        #   Pull the timeslots from the multiselect field on the form.
        timeslots = []
        for ts_id in data.getlist('timeslot_ids'):
            ts = Event.objects.get(id=ts_id)
            timeslots.append(ts)

        #   Create classrooms based on the form input.
        for key in data:
            key_dir = key.split('_')
            if len(key_dir) == 2 and key_dir[0] == 'room' and len(
                    data[key]) > 0:
                #   Extract a room number and capacity from POST data.
                room_num = data.get(key)
                cap_key = 'capacity_' + key_dir[1]
                room_capacity = int(data.get(cap_key))
                reginfo = module_ext.SATPrepTeacherModuleInfo.objects.get(
                    id=int(key_dir[1]))
                user = reginfo.user

                #   Initialize a class subject.
                newclass = ClassSubject()
                newclass.parent_program = self.program
                newclass.class_info = '%s: Section %s (%s)' % (
                    reginfo.get_subject_display(), reginfo.section,
                    reginfo.get_section_display())
                newclass.grade_min = 9
                newclass.grade_max = 12

                newclass.class_size_min = 0
                newclass.class_size_max = room_capacity

                newclass.category = ClassCategories.objects.get(
                    category='SATPrep')
                newclass.anchor = self.program.classes_node()

                newclass.save()

                nodestring = 'SAT' + str(newclass.id)
                newclass.anchor = self.program.classes_node().tree_create(
                    [nodestring])
                newclass.anchor.friendly_name = 'SAT Prep %s - %s' % (
                    reginfo.get_subject_display(),
                    reginfo.get_section_display())
                newclass.anchor.save()
                newclass.anchor.tree_create(['TeacherEmail'])
                newclass.save()

                newclass.makeTeacher(user)
                newclass.accept()

                #   Create a section of the class for each timeslot.
                #   The sections are all held in the same room by default.  This can be changed
                #   in the scheduling module later.
                for ts in timeslots:
                    new_room, created = Resource.objects.get_or_create(
                        name=room_num,
                        res_type=ResourceType.get_or_create('Classroom'),
                        event=ts)
                    new_room.num_students = room_capacity
                    new_room.save()
                    sec = newclass.add_section(
                        duration=(ts.duration().seconds / 3600.0))
                    sec.meeting_times.add(ts)
                    sec.assign_room(new_room)
                    sec.status = 10
                    sec.save()

        #dummy_anchor.delete()
        return HttpResponseRedirect('/manage/%s/schedule_options' %
                                    self.program.getUrlBase())
Exemplo n.º 12
0
    def diagnostic_sections(self, request, tl, one, two, module, extra, prog):
        """ Step 2 of the diagnostic setup process. """

        #   Initialize some stuff
        empty_rooms = request.GET['empty_rooms'].split(',')

        timeslot_id = int(request.GET['timeslot'])
        timeslot = Event.objects.get(id=timeslot_id)

        rooms = list(
            prog.getClassrooms(timeslot=timeslot).exclude(
                name__in=empty_rooms))

        #   Get student list, sort alphabetically
        students = list(prog.students()['confirmed'])
        num_students = len(students)
        students.sort(key=lambda s: s.last_name)

        debug_str = ''
        total_size = 0
        accum_capacity = {}

        #   Count cumulative capacity of rooms encountered so far
        for r in rooms:
            total_size += r.num_students
            accum_capacity[r.id] = total_size - r.num_students

        #   Reverse the list so you know how much space is remaining
        rooms.reverse()
        for r in rooms:
            r.add_students = num_students * r.num_students / total_size

        #   Iterate over the rooms, adding students until the number of students remaining
        #   matches the proportion of space remaining
        sections = []
        students_remaining = num_students
        i = 0
        j = 0
        for r in rooms:
            new_section = {
                'timeslot': timeslot,
                'students': [],
                'room': r,
                'index': i
            }
            new_class = ClassSubject()
            new_class.parent_program = prog
            new_class.class_size_max = r.num_students
            new_class.duration = timeslot.duration().seconds / 3600.0
            new_class.grade_min = 9
            new_class.grade_max = 12
            new_class.anchor = DataTree.get_by_uri(
                prog.anchor.get_uri() + '/Classes/Diag' + str(i + 1),
                create=True)
            new_class.category = ClassCategories.objects.get(
                category='SATPrep')
            new_class.status = 10
            new_class.save()
            new_sec = new_class.add_default_section()

            new_sec.meeting_times.add(timeslot)
            new_sec.assignClassRoom(r)
            while students_remaining > (accum_capacity[r.id] * num_students /
                                        total_size):
                debug_str += 'i=%d %d/%d ac=%d\n' % (i, j, num_students,
                                                     accum_capacity[r.id])
                new_sec.preregister_student(students[j])
                new_section['students'].append(students[j])
                j += 1
                students_remaining -= 1
            if len(new_section['students']) > 0:
                new_section['fln'] = new_section['students'][0].last_name
                new_section['lln'] = new_section['students'][-1].last_name
                new_class.anchor.friendly_name = 'SAT Prep Diagnostic %d: %s - %s' % (
                    i + 1, new_section['fln'], new_section['lln'])
                new_class.anchor.save()
            i += 1
            sections.append(new_section)

        context = {'prog': prog, 'sections': sections}
        return render_to_response(self.baseDir() + 'diagnostic_sections.html',
                                  request, (prog, tl), context)