def _finalized_exchange_pair_checks(self, exchange_left, exchange_right): """Perform common checks for the consistency of two completed exchanges.""" exchange_left.refresh_from_db() exchange_right.refresh_from_db() self.assertTrue(exchange_left.is_finalized()) self.assertTrue(exchange_right.is_finalized()) self.assertEqual(exchange_left.finalizer_exchange, exchange_right) self.assertEqual(exchange_right.finalized_exchange, exchange_left) self.assertIsNone(exchange_left.get_match()) self.assertIsNone(exchange_right.get_match()) # check if student allocations have been exchanged as requested # but only when we actually have student objects available (not for FREE_CHANGE, TEACHER_OFFER) if exchange_left.initiator_student is not None: self.assertEqual(exchange_right.allocation_from, get_current_student_subject_allocation(self.timetable, exchange_left.initiator_student, Activity.from_timetable_activity( exchange_right.allocation_from.activityRealization.activity).subject, activity_types=["LAB", "LV", "AV"])) if exchange_right.initiator_student is not None: self.assertEqual(exchange_left.allocation_from, get_current_student_subject_allocation(self.timetable, exchange_right.initiator_student, Activity.from_timetable_activity( exchange_left.allocation_from.activityRealization.activity).subject, activity_types=["LAB", "LV", "AV"]))
def test_get_current_student_subject_allocation(self): lab = get_current_student_subject_allocation(self.timetable, self.students[0], self.subjects[0], activity_types=["LAB", "LV", "AV"]) lec = get_current_student_subject_allocation(self.timetable, self.students[0], self.subjects[0], activity_types=["P"]) self.assertEqual(self.timetable, lab.timetable) self.assertEqual(self.timetable, lec.timetable) self.assertEqual(self.subjects[0], Activity.from_timetable_activity(lab.activityRealization.activity).subject) self.assertEqual(self.subjects[0], Activity.from_timetable_activity(lec.activityRealization.activity).subject)
def test_number_of_students_in_allocation(self): num = number_of_students_in_allocation(get_current_student_subject_allocation(self.timetable, self.students[0], self.subjects[0], ["LAB", "LV", "AV"])) # change this as needed self.assertEqual(20, num)
def test_get_allocation_student_group_exists(self): # quite a roundabout way, but reliable I guess self.assertIsNotNone(get_allocation_student_group(get_current_student_subject_allocation(self.timetable, self.students[0], self.subjects[0], ["LAB", "LV", "AV"]), self.students[0]))
def landing_student(request, timetable_slug): selected_timetable = get_object_or_404(Timetable, slug=timetable_slug) student = Student.from_user(request.user) student_exchanges = get_student_exchanges(selected_timetable, student) pending_exchanges = [ ex for ex in student_exchanges if not ex.is_finalized() ] completed_exchanges = [ex for ex in student_exchanges if ex.is_finalized()] student_subjects = get_student_subject_list(selected_timetable, student) available_subject_exchanges_allocations = [] for s in student_subjects: # don't show the subject if the student doesn't attend any lab cycles try: a = get_current_student_subject_allocation(selected_timetable, student, s, ["LAB", "LV", "AV"]) except: continue available_subject_exchanges_allocations.append( (s, get_subject_exchanges(selected_timetable, s), a)) return render( request, "exchange/student_main.html", { 'selected_timetable': selected_timetable.__dict__, 'user': request.user.__dict__, 'student': student, 'pending_exchanges': pending_exchanges, 'completed_exchanges': completed_exchanges, 'available_subject_exchanges_allocations': available_subject_exchanges_allocations, })
def setUpTestData(cls): """IMPORTANT: Do not delete any objects, because they are not automatically recovered.""" super().setUpTestData() lv = ["LAB", "LV", "AV"] cls.exchanges: List[Exchange] = [] # ensure datetimes are generated distinctly and logically def get_datetime_iterator(): base_datetime = datetime.utcnow() - timedelta(hours=1) iteration = 0 while True: yield base_datetime + timedelta(seconds=iteration) iteration += 1 datetime_iterator = get_datetime_iterator() # 0: initiator, by students[0], subjects[0], to students[1]'s allocation cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[0], cls.subjects[0], lv), allocation_to=get_current_student_subject_allocation(cls.timetable, cls.students[1], cls.subjects[0], lv), initiator_student=cls.students[0], requested_finalizer_student=None, date_created=next(datetime_iterator) )) # 1: finaliser, by students[1], subjects[0], counterpart to 0) cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[1], cls.subjects[0], lv), allocation_to=get_current_student_subject_allocation(cls.timetable, cls.students[0], cls.subjects[0], lv), initiator_student=cls.students[1], requested_finalizer_student=None, date_created=next(datetime_iterator) )) # 2: initiator, by students[0], subjects[1], has no match (when a correct destination allocation is selected) cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[0], cls.subjects[1], lv), allocation_to=cls._get_student_subject_allocation_inverse(cls.timetable, cls.students[0], cls.subjects[1], lv, skip=2), initiator_student=cls.students[0], requested_finalizer_student=None, date_created=next(datetime_iterator) )) # 3: initiator, by students[0], subjects[1], different destination lab cycle than 2) cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[0], cls.subjects[1], lv), allocation_to=cls._get_student_subject_allocation_inverse(cls.timetable, cls.students[0], cls.subjects[1], lv, skip=1), initiator_student=cls.students[0], requested_finalizer_student=None, date_created=next(datetime_iterator) )) # 4: finalizer, by teachers[1], subjects[1], matches above (teacher offer) cls.exchanges.append(Exchange.objects.create( allocation_from=cls._get_student_subject_allocation_inverse(cls.timetable, cls.students[0], cls.subjects[1], lv, skip=1), allocation_to=get_current_student_subject_allocation(cls.timetable, cls.students[0], cls.subjects[1], lv), initiator_student=None, requested_finalizer_student=cls.students[0], date_created=next(datetime_iterator) )) # 5: initiator, by students[2], subjects[1], same destination than 3), but chronologically after so lower prio cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[2], cls.subjects[1], lv), allocation_to=cls._get_student_subject_allocation_inverse(cls.timetable, cls.students[0], cls.subjects[1], lv, skip=1), initiator_student=cls.students[2], requested_finalizer_student=None, date_created=next(datetime_iterator) )) # 6: initiator, by students[2], subjects[1], specified students[3], chronologically after 2, 3, 4, 5 cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[2], cls.subjects[1], lv), allocation_to=get_current_student_subject_allocation(cls.timetable, cls.students[3], cls.subjects[1], lv), initiator_student=cls.students[2], requested_finalizer_student=cls.students[3], date_created=next(datetime_iterator) )) # 7: finalizer, by students[3], subjects[1], specified students[2], chronologically after 2, 3, 4, 5, 6 cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[3], cls.subjects[1], lv), allocation_to=get_current_student_subject_allocation(cls.timetable, cls.students[2], cls.subjects[1], lv), initiator_student=cls.students[3], requested_finalizer_student=cls.students[2], date_created=next(datetime_iterator) )) # 8: initiator, free change, subjects[0], same as 0), chronologically before (test priority of student-initiated # exchanges, which override free changes, should never be finalised cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[0], cls.subjects[0], lv), allocation_to=None, initiator_student=None, requested_finalizer_student=None, date_created=cls.exchanges[0].date_created - timedelta(days=1) )) # 9: initiator, free change, subjects[1], should match 10), does not create an inverse free change (no room) cls.exchanges.append(Exchange.objects.create( allocation_from=cls._get_student_subject_allocation_inverse(cls.timetable, cls.students[4], cls.subjects[1], lv, skip=0), allocation_to=None, initiator_student=None, requested_finalizer_student=None, date_created=next(datetime_iterator) )) # 10: finalizer, by students[4], subjects[1], matches 9) cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[4], cls.subjects[1], lv), allocation_to=cls._get_student_subject_allocation_inverse(cls.timetable, cls.students[4], cls.subjects[1], lv, skip=0), initiator_student=cls.students[4], requested_finalizer_student=None, date_created=next(datetime_iterator) )) # 11: initiator, free change, subjects[0], should match 12), from an exchange other than that of students[0] # for this subject (so we don't collide with above), should create an inverse free change cls.exchanges.append(Exchange.objects.create( allocation_from=cls._get_student_subject_allocation_inverse(cls.timetable, cls.students[0], cls.subjects[0], lv, skip=0), allocation_to=None, initiator_student=None, requested_finalizer_student=None, date_created=next(datetime_iterator) )) # 12: finalizer, by students[1], subjects[0], matches 11), to an exchange other than that of students[0] # for this subject (so we don't collide with above) cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[1], cls.subjects[0], lv), allocation_to=cls._get_student_subject_allocation_inverse(cls.timetable, cls.students[0], cls.subjects[0], lv, skip=0), initiator_student=cls.students[1], requested_finalizer_student=None, date_created=next(datetime_iterator) )) helper_student_13_14 = Student.objects.filter(groups__in=list( get_current_student_subject_allocation(cls.timetable, cls.students[3], cls.subjects[2], lv).groups.all())).exclude(id__in=[s.id for s in cls.students]).first() # 13: initiator, teacher offer, subjects[2], intentionally does not match 14), to a different student # but one that attends the same allocation as students[3] cls.exchanges.append(Exchange.objects.create( allocation_from=cls._get_student_subject_allocation_inverse(cls.timetable, helper_student_13_14, cls.subjects[2], lv), allocation_to=get_current_student_subject_allocation(cls.timetable, cls.students[3], cls.subjects[2], lv), initiator_student=None, requested_finalizer_student=helper_student_13_14, date_created=next(datetime_iterator) )) # 14: finalizer, by students[3], subjects[2], does not match 13) cls.exchanges.append(Exchange.objects.create( allocation_from=get_current_student_subject_allocation(cls.timetable, cls.students[3], cls.subjects[2], lv), allocation_to=cls._get_student_subject_allocation_inverse(cls.timetable, helper_student_13_14, cls.subjects[2], lv), initiator_student=cls.students[3], requested_finalizer_student=None, date_created=next(datetime_iterator) ))
def test_get_allocation_student_group_does_not_exist(self): self.assertIsNone(get_allocation_student_group(get_current_student_subject_allocation(self.timetable, self.students[0], self.subjects[0], ["LAB", "LV", "AV"]), self.students[4]))
def test_get_current_student_subject_allocation_null_subject(self): with self.assertRaises(ValueError): get_current_student_subject_allocation(self.timetable, self.students[0], None, ["LAB", "LV", "AV"])
def create_exchange_teacher(request, timetable_slug, subject_code): selected_timetable = get_object_or_404(Timetable, slug=timetable_slug) teacher = request.user.teacher subject = get_object_or_404(Subject, code=subject_code) if not teacher_teaches_subject(selected_timetable, teacher, subject): raise PermissionDenied activity_types = ["LAB", "LV", "AV"] student_selection_form = StudentSelectionForm(Student.objects, data=request.GET) if not student_selection_form.is_valid(): return HttpResponseBadRequest() selected_student = student_selection_form.cleaned_data["selected_student"] try: student_allocation = get_current_student_subject_allocation( selected_timetable, selected_student, subject, activity_types) available_allocations = get_student_subject_other_allocations( selected_timetable, selected_student, subject, activity_types) except Allocation.DoesNotExist: raise Http404( "The student does not attend the subject or no allocations have been found." ) exchange_creation_form = TeacherExchangeCreationForm( available_allocations, data=request.POST or None, initial={ "timetable": selected_timetable, "teacher": teacher, "student": selected_student, "current_student_allocation": student_allocation }) # first way into this is with a submitted form with a selected student if request.method == "GET": return render( request, "exchange/exchange_create.html", { "selected_timetable": selected_timetable, "form": exchange_creation_form }) else: if exchange_creation_form.is_valid(): any_fulfilled = process_new_exchange_request( exchange_creation_form.cleaned_data["timetable"], exchange_creation_form.cleaned_data["teacher"], exchange_creation_form.cleaned_data["student"], exchange_creation_form.get_subject_transfer_map(), # the teacher acts as if they attend the allocation the student wants force_allocation_from=exchange_creation_form. cleaned_data["requested_student_allocation"]) if any_fulfilled: header = "Exchange fulfilled immediately!" message = "The request was fulfilled immediately. Verify the new slot on the timetable.." else: header = "Request added to the queue." message = "The request could not be fulfilled immediately and was placed into the queue for it to " \ "be fulfilled in the future." return render( request, "exchange/exchange_create_result.html", { "selected_timetable": selected_timetable, "header": header, "message": message }) pass else: return render( request, "exchange/exchange_create.html", { "selected_timetable": selected_timetable, "form": exchange_creation_form })
def create_exchange_student(request, timetable_slug): selected_timetable = get_object_or_404(Timetable, slug=timetable_slug) student = Student.from_user(request.user) subjects = get_student_subject_list(selected_timetable, student) subject_available_allocation_map = {} subject_attending_allocation_map = {} for subject in subjects: activity_types = ["LAB", "LV", "AV"] try: student_allocation = get_current_student_subject_allocation( selected_timetable, student, subject, activity_types) allocations = get_student_subject_other_allocations( selected_timetable, student, subject, activity_types) subject_available_allocation_map[subject] = allocations subject_attending_allocation_map[subject] = student_allocation except Allocation.DoesNotExist: # don't show subjects the student doesn't have attend labs for pass if request.method == "POST": form = ExchangeCreationForm(subject_available_allocation_map, subject_attending_allocation_map, data=request.POST) if form.is_valid(): try: requested_student_string = form.get_requested_student() requested_student = None if requested_student_string: requested_student = parse_student_from_ambiguous_identifier( requested_student_string) any_fulfilled = process_new_exchange_request( selected_timetable, student, requested_student, form.get_subject_transfers(keep_empty=False)) except FormProcessingError as e: return render( request, "exchange/exchange_create_result.html", { "selected_timetable": selected_timetable, "header": e.header, "message": e.message }) if any_fulfilled: header = "Exchange fulfilled immediately!" message = "Your request was fulfilled immediately. Check your new slot on your timetable." else: header = "Request added to the queue." message = "Your request could not be fulfilled immediately and was placed into the queue for it to " \ "be fulfilled in the future. Check back at a later date!" return render( request, "exchange/exchange_create_result.html", { "selected_timetable": selected_timetable, "header": header, "message": message }) # otherwise fall through to rendering the same form, with the data filled out, as is tradition else: form = ExchangeCreationForm(subject_available_allocation_map, subject_attending_allocation_map) return render(request, "exchange/exchange_create.html", { "selected_timetable": selected_timetable, "form": form })