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
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
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
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()
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
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'])
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
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())
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)