def test_working_hours_multidate(self): """ Test checking working hours when lesson starts in one day, and ends on another. This will be frequent situations, because our teachers are in different timezones. """ mixer.blend(WorkingHours, teacher=self.teacher, start='23:00', end='23:59', weekday=0) mixer.blend(WorkingHours, teacher=self.teacher, start='00:00', end='02:00', weekday=1) entry_besides_hours = TimelineEntry( teacher=self.teacher, start=self.tzdatetime(2032, 5, 3, 22, 0), # does not fit end=self.tzdatetime(2032, 5, 4, 0, 30) ) self.assertFalse(entry_besides_hours.is_fitting_working_hours()) entry_besides_hours.start = self.tzdatetime(2032, 5, 3, 22, 30) # does fit entry_besides_hours.end = self.tzdatetime(2016, 7, 26, 2, 30) # does not fit self.assertFalse(entry_besides_hours.is_fitting_working_hours()) entry_within_hours = TimelineEntry( teacher=self.teacher, start=self.tzdatetime(2032, 5, 3, 23, 30), end=self.tzdatetime(2032, 5, 4, 0, 30) ) self.assertTrue(entry_within_hours.is_fitting_working_hours())
def test_free_slots_for_lesson(self): """ Test for getting free time slots for a particular teacher with particular lesson """ other_teacher = create_teacher() master_class = mixer.blend(lessons.MasterClass, host=self.teacher) other_master_class = mixer.blend(lessons.MasterClass, host=other_teacher) entry = TimelineEntry(teacher=self.teacher, lesson=master_class, start=self.tzdatetime(2032, 5, 3, 14, 10), end=self.tzdatetime(2032, 5, 3, 14, 40)) entry.save() other_entry = TimelineEntry(teacher=other_teacher, lesson=other_master_class, start=self.tzdatetime(2032, 5, 3, 14, 10), end=self.tzdatetime(2032, 5, 3, 14, 40)) other_entry.save() slots = self.teacher.find_free_slots(self.tzdatetime(2032, 5, 3), lesson_id=master_class.pk) self.assertEquals(len(slots), 1) slots = self.teacher.find_free_slots(self.tzdatetime(2032, 5, 3), lesson_id=other_master_class.pk) self.assertEquals(len(slots), 0)
def test_working_hours(self): mixer.blend(WorkingHours, teacher=self.teacher, start='12:00', end='13:00', weekday=0) entry_besides_hours = TimelineEntry( teacher=self.teacher, start=self.tzdatetime(2032, 5, 3, 4, 0), end=self.tzdatetime(2032, 5, 3, 4, 30), ) self.assertFalse(entry_besides_hours.is_fitting_working_hours()) entry_within_hours = TimelineEntry( teacher=self.teacher, start=self.tzdatetime(2032, 5, 3, 12, 30), end=self.tzdatetime(2032, 5, 3, 13, 0), ) self.assertTrue(entry_within_hours.is_fitting_working_hours())
def test_working_hours_nonexistant(self): entry = TimelineEntry( teacher=self.teacher, start=self.tzdatetime(2032, 5, 3, 22, 0), # does not fit end=self.tzdatetime(2032, 5, 3, 22, 30), ) self.assertFalse(entry.is_fitting_working_hours()) # should not throw anything
def test_cant_save_due_to_overlap(self): overlapping_entry = TimelineEntry( teacher=self.teacher, lesson=self.lesson, start=self.tzdatetime(2016, 1, 3, 4, 0), end=self.tzdatetime(2016, 1, 3, 4, 30), ) with self.assertRaises(AutoScheduleExpcetion): # should conflict with self.big_entry overlapping_entry.clean()
def _create_entry(self, clean): entry = TimelineEntry( slots=1, lesson=self.lesson, teacher=self.host, start=self.tzdatetime(2032, 9, 13, 12, 0), ) self.assertFalse(entry.is_finished) return entry
def test_filter_by_lesson_type(self): first_master_class = mixer.blend(lessons.MasterClass, host=self.first_teacher) second_master_class = mixer.blend(lessons.MasterClass, host=self.second_teacher) entry = TimelineEntry( teacher=self.first_teacher, lesson=first_master_class, start=datetime(2032, 5, 6, 14, 10), end=datetime(2032, 5, 6, 14, 40), ) entry.save() entry = TimelineEntry( teacher=self.second_teacher, lesson=second_master_class, start=datetime(2032, 5, 6, 14, 15), end=datetime(2032, 5, 6, 14, 45), ) entry.save() master_class_type = ContentType.objects.get_for_model( first_master_class) response = self.c.get('/market/2032-05-06/type/%d/lessons.json' % master_class_type.pk) self.assertEquals(response.status_code, 200) records = json.loads(response.content.decode('utf-8')) self.assertEquals(len(records), 2) self.assertEquals(len(records[0]['slots']), 1) self.assertIsTime( records[0]['slots'][0]['server'] ) # assert that returned slots carry some time (we dont care about timezones here) self.assertIsTime(records[1]['slots'][0]['server']) self.assertEquals(records[0]['name'], first_master_class.name) self.assertEquals(records[1]['name'], second_master_class.name) self.assertEquals(records[0]['host'], self.first_teacher.user.crm.full_name) self.assertEquals(records[1]['host'], self.second_teacher.user.crm.full_name)
def test_no_validation_when_more_then_one_student_has_signed(self): """ There is no need to validate a timeline entry when it has students """ overlapping_entry = TimelineEntry( teacher=self.teacher, lesson=self.lesson, start=self.tzdatetime(2016, 1, 3, 4, 0), end=self.tzdatetime(2016, 1, 3, 4, 30), taken_slots=1, ) overlapping_entry.clean() # should not throw anything self.assertTrue(True)
def test_cant_save_due_to_not_fitting_working_hours(self): """ Create an entry that does not fit into teachers working hours """ entry = TimelineEntry( teacher=self.teacher, lesson=self.lesson, start=self.tzdatetime(2032, 5, 3, 13, 30), # monday end=self.tzdatetime(2032, 5, 3, 14, 0), allow_besides_working_hours=False ) with self.assertRaises(DoesNotFitWorkingHours, msg='Entry does not fit teachers working hours'): entry.clean()
def test_free_slots_for_lesson_type_validates_with_auto_schedule(self): master_class = mixer.blend(lessons.MasterClass, host=self.teacher) entry = TimelineEntry(teacher=self.teacher, lesson=master_class, start=self.tzdatetime(2032, 5, 3, 14, 10), end=self.tzdatetime(2032, 5, 3, 14, 40)) entry.save() lesson_type = ContentType.objects.get_for_model(master_class) with patch('timeline.models.Entry.clean') as clean: clean.side_effect = AutoScheduleExpcetion(message='testing') slots = self.teacher.find_free_slots(date=self.tzdatetime( 2032, 5, 3), lesson_type=lesson_type.pk) self.assertEqual(len(slots), 0)
def test_get_teachers_by_lesson(self): """ Find teachers for a particular lesson """ first_master_class = mixer.blend(lessons.MasterClass, host=self.teacher) first_entry = TimelineEntry(teacher=self.teacher, lesson=first_master_class, start=self.tzdatetime(2032, 5, 3, 14, 10), end=self.tzdatetime(2032, 5, 3, 14, 40)) first_entry.save() free_teachers = list( Teacher.objects.find_free(date=self.tzdatetime(2032, 5, 3), lesson_id=first_master_class.pk)) self.assertEquals(len(free_teachers), 1)
def test_cant_save_due_to_teacher_has_events(self): entry = TimelineEntry( teacher=self.teacher, lesson=self.lesson, start=self.tzdatetime(2016, 5, 3, 13, 30), end=self.tzdatetime(2016, 5, 3, 14, 00), ) mixer.blend( ExternalEvent, teacher=self.teacher, start=self.tzdatetime(2016, 5, 2, 00, 00), end=self.tzdatetime(2016, 5, 5, 23, 59), ) with self.assertRaises(AutoScheduleExpcetion): entry.clean()
def test_cant_save_due_to_teacher_absence(self): entry = TimelineEntry( teacher=self.teacher, lesson=self.lesson, start=self.tzdatetime(2016, 5, 3, 13, 30), end=self.tzdatetime(2016, 5, 3, 14, 00), ) vacation = Absence( type='vacation', teacher=self.teacher, start=self.tzdatetime(2016, 5, 2, 00, 00), end=self.tzdatetime(2016, 5, 5, 23, 59), ) vacation.save() with self.assertRaises(AutoScheduleExpcetion): entry.clean()
def test_two_teachers_for_single_slot(self): """ Check if find_free_slots returns only slots of selected teacher """ other_teacher = create_teacher() master_class = mixer.blend(lessons.MasterClass, host=other_teacher) entry = TimelineEntry(teacher=other_teacher, lesson=master_class, start=self.tzdatetime(2032, 5, 3, 14, 10), end=self.tzdatetime(2032, 5, 3, 14, 40)) entry.save() lesson_type = ContentType.objects.get_for_model(master_class) slots = self.teacher.find_free_slots(date=self.tzdatetime(2032, 5, 3), lesson_type=lesson_type.pk) self.assertEquals( len(slots), 0 ) # should not return anything — we are checking slots for self.teacher, not other_teacher
def test_free_slots_for_lesson_type(self): """ Test for getting free time slots for a certain lesson type. """ master_class = mixer.blend(lessons.MasterClass, host=self.teacher) entry = TimelineEntry(teacher=self.teacher, lesson=master_class, start=self.tzdatetime(2032, 5, 3, 14, 10), end=self.tzdatetime(2032, 5, 3, 14, 40)) entry.save() lesson_type = ContentType.objects.get_for_model(master_class) slots = self.teacher.find_free_slots(date=self.tzdatetime(2032, 5, 3), lesson_type=lesson_type.pk) self.assertEquals(len(slots), 1) slots = self.teacher.find_free_slots(date=self.tzdatetime(2032, 5, 5), lesson_type=lesson_type.pk) self.assertEquals( len(slots), 0) # there is no master classes, planned on 2032-05-05
def test_schedule_existsing_entry(self): """ Create a timeline entry, that class.__get_entry should return instead of creating a new one """ lesson = products.OrdinaryLesson.get_default() c = self._buy_a_lesson(lesson) date = self.tzdatetime(2016, 8, 17, 10, 1) entry = TimelineEntry( teacher=self.host, start=date, lesson=lesson ) entry.save() c.schedule( teacher=self.host, date=self.tzdatetime(2016, 8, 17, 10, 1), allow_besides_working_hours=True, ) c.save() self.assertEquals(c.timeline, entry)
def setUp(self): self.teacher = create_teacher() self.lesson = mixer.blend(lessons.MasterClass, host=self.teacher) self.entry = TimelineEntry( teacher=self.teacher, lesson=self.lesson, start=self.tzdatetime('Europe/Moscow', 2016, 1, 18, 14, 10), end=self.tzdatetime('Europe/Moscow', 2016, 1, 18, 14, 40), ) self.entry.save() mixer.blend(WorkingHours, teacher=self.teacher, weekday=0, start='13:00', end='15:00') self.absence = Absence( type='vacation', teacher=self.teacher, start=self.tzdatetime(2032, 5, 3, 0, 0), end=self.tzdatetime(2032, 5, 3, 23, 59), ) self.absence.save()
class TestRevertChanges(LoggedInTestCase, ModuleTestCase): def setUp(self): super(TestRevertChanges, self).setUp() self.master = ModuleTeaching( **{ "module": self.module, "teaching_lectures": 45, "teaching_tutorials": 3, "teaching_online": 45, "teaching_practical_workshops": 8, "teaching_supervised_time": 8, "teaching_fieldworks": 8, "teaching_external_visits": 8, "teaching_schedule_assessment": 8, "teaching_placement": 8, "archive_flag": False, "staging_flag": True, "current_flag": False, "version_number": 1, "copy_number": 3 }) self.master.save() self.prev = ModuleTeaching( **{ "module": self.module, "teaching_lectures": 8, "teaching_tutorials": 8, "teaching_online": 8, "teaching_practical_workshops": 8, "teaching_supervised_time": 8, "teaching_fieldworks": 8, "teaching_external_visits": 8, "teaching_schedule_assessment": 8, "teaching_placement": 8, "archive_flag": True, "staging_flag": False, "current_flag": False, "version_number": 2, "copy_number": 1 }) self.prev.save() self.current = ModuleTeaching( **{ "module": self.module, "teaching_lectures": 45, "teaching_tutorials": 84, "teaching_online": 8, "teaching_practical_workshops": 8, "teaching_supervised_time": 8, "teaching_fieldworks": 8, "teaching_external_visits": 8, "teaching_schedule_assessment": 8, "teaching_placement": 8, "archive_flag": True, "staging_flag": False, "current_flag": False, "version_number": 3, "copy_number": 2 }) self.current.save() self.parent_entry = TimelineEntry( **{ "title": "Changes to tracking form", "changes": "Changes to tracking form:\n\n* There are 1 changes to Module Teaching\n* There are 2 changes to Assessment test4\n* There are 1 changes to Software tet\n", "created": "2018-04-11T16:58:40.999Z", "last_modified": "2018-04-11T16:58:40.999Z", "module_code": self.module.module_code, "parent_entry": None, "status": "Draft", "entry_type": "Tracking-Form", "content_type": None, "object_id": None, "revert_object_id": "0", "changes_by": self.admin, "approved_by": None }) self.parent_entry.save() self.entry = TimelineEntry( **{ "title": "Module Teaching", "changes": "* teaching online: 8 -> 45\n", "created": "2018-04-11T16:58:41.019Z", "last_modified": "2018-04-11T16:58:41.019Z", "module_code": self.module.module_code, "parent_entry": self.parent_entry, "status": "Draft", "entry_type": "Tracking-Form", "content_object": self.current, "revert_object_id": self.prev.pk, "changes_by": None, "approved_by": None }) self.entry.save() def test_valid_revert_changes(self): """ Test that the changes are reverted for tracking form data """ # test the current staged changes master = ModuleTeaching.objects.filter(version_number=1).first() self.assertTrue(master.staging_flag) self.assertFalse(master.archive_flag) self.assertFalse(master.current_flag) self.assertEquals(master.copy_number, 3) result = revert_changes(self.parent_entry) self.assertTrue(result) # check timeline has been removed with self.assertRaises(TimelineEntry.DoesNotExist): TimelineEntry.objects.get(pk=self.parent_entry.pk) # check that it has been reverted master = ModuleTeaching.objects.filter(version_number=1).first() self.assertTrue(master.current_flag) self.assertFalse(master.archive_flag) self.assertFalse(master.staging_flag) self.assertEquals(master.copy_number, 2) def test_no_children_master(self): """ Test when there are no child for parent entry """ result = revert_changes(self.entry) self.assertFalse(result) def test_invalid_master_instance(self): """ Test when the parent entry is not a timeline entry type """ parents = [None, ModuleTeaching, self.user] for parent in parents: with self.assertRaises(ValueError): revert_changes(parent)
def setUp(self): super(TestRevertChanges, self).setUp() self.master = ModuleTeaching( **{ "module": self.module, "teaching_lectures": 45, "teaching_tutorials": 3, "teaching_online": 45, "teaching_practical_workshops": 8, "teaching_supervised_time": 8, "teaching_fieldworks": 8, "teaching_external_visits": 8, "teaching_schedule_assessment": 8, "teaching_placement": 8, "archive_flag": False, "staging_flag": True, "current_flag": False, "version_number": 1, "copy_number": 3 }) self.master.save() self.prev = ModuleTeaching( **{ "module": self.module, "teaching_lectures": 8, "teaching_tutorials": 8, "teaching_online": 8, "teaching_practical_workshops": 8, "teaching_supervised_time": 8, "teaching_fieldworks": 8, "teaching_external_visits": 8, "teaching_schedule_assessment": 8, "teaching_placement": 8, "archive_flag": True, "staging_flag": False, "current_flag": False, "version_number": 2, "copy_number": 1 }) self.prev.save() self.current = ModuleTeaching( **{ "module": self.module, "teaching_lectures": 45, "teaching_tutorials": 84, "teaching_online": 8, "teaching_practical_workshops": 8, "teaching_supervised_time": 8, "teaching_fieldworks": 8, "teaching_external_visits": 8, "teaching_schedule_assessment": 8, "teaching_placement": 8, "archive_flag": True, "staging_flag": False, "current_flag": False, "version_number": 3, "copy_number": 2 }) self.current.save() self.parent_entry = TimelineEntry( **{ "title": "Changes to tracking form", "changes": "Changes to tracking form:\n\n* There are 1 changes to Module Teaching\n* There are 2 changes to Assessment test4\n* There are 1 changes to Software tet\n", "created": "2018-04-11T16:58:40.999Z", "last_modified": "2018-04-11T16:58:40.999Z", "module_code": self.module.module_code, "parent_entry": None, "status": "Draft", "entry_type": "Tracking-Form", "content_type": None, "object_id": None, "revert_object_id": "0", "changes_by": self.admin, "approved_by": None }) self.parent_entry.save() self.entry = TimelineEntry( **{ "title": "Module Teaching", "changes": "* teaching online: 8 -> 45\n", "created": "2018-04-11T16:58:41.019Z", "last_modified": "2018-04-11T16:58:41.019Z", "module_code": self.module.module_code, "parent_entry": self.parent_entry, "status": "Draft", "entry_type": "Tracking-Form", "content_object": self.current, "revert_object_id": self.prev.pk, "changes_by": None, "approved_by": None }) self.entry.save()