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 render_exchanges(exchanges, show_subject=True, third_person=False, manager_student=None, show_student=True, show_finalized=False, show_cancelled=False, show_cancel_link=True): """Directive-like helper. Args: exchanges (List[Exchange]): A list of exchanges. show_subject (bool): Whether to show the subject preceding everything else. show_student (bool): Whether to show the student's name, if SPECIFIC_STUDENT. third_person (bool): Whether to output third person perspective noun forms. manager_student (Student): The student that would accept the exchange. If None, no accept links are generated. show_finalized (bool): Whether to display finalized exchanges. show_cancelled (bool): Whether to display cancelled exchanges. show_cancel_link (bool): Whether to display the cancel button, if available. """ filtered_exchanges = exchanges if not show_finalized: filtered_exchanges = [e for e in filtered_exchanges if not e.is_finalized()] if not show_cancelled: filtered_exchanges = [e for e in filtered_exchanges if not e.is_cancelled()] # sort exchanges sorted_exchanges = sorted(filtered_exchanges, key=_exchange_sorted_helper) view_models = [] vm = namedtuple('ExchangeViewModel', ['type', 'allocation_from', 'allocation_to', 'initiator_student', 'requested_finalizer_student', 'date_created', 'date_finalized', 'cancelled', 'subject', 'has_initiator_student', 'accept_link', 'cancel_link']) for ex in sorted_exchanges: subject = Activity.from_timetable_activity(ex.allocation_from.activityRealization.activity).subject view_models.append(vm( subject="{}".format(subject.name), type=ex.get_type().name, allocation_from="{} at {}".format(ex.allocation_from.day, ex.allocation_from.start), allocation_to="{} at {}".format(ex.allocation_to.day, ex.allocation_to.start), has_initiator_student=ex.initiator_student is not None, initiator_student="{} {}".format(ex.initiator_student.name.title(), ex.initiator_student.surname.title()) if ex.initiator_student is not None else None, requested_finalizer_student="{}".format(str(ex.requested_finalizer_student)), date_created="{}".format(str(ex.date_created)), date_finalized="{}".format(str(ex.date_finalized)), cancelled=bool(ex.date_cancelled), accept_link=reverse("accept_exchange", kwargs={ "timetable_slug": ex.allocation_from.timetable.slug, "exchange_id": ex.id }) if manager_student and is_exchange_acceptable(ex, manager_student) else None, cancel_link=reverse("cancel_exchange", kwargs={ "timetable_slug": ex.allocation_from.timetable.slug, "exchange_id": ex.id }) if manager_student and is_exchange_cancellable(ex, manager_student) else None )) return { "exchanges": view_models, "show_subject": show_subject, "show_student": show_student, "show_cancel_link": show_cancel_link, "source_word": "Their" if not third_person else "From", "destination_word": "for your" if not third_person else "to" }
def test_get_student_subject_other_allocations(self): labs = get_student_subject_other_allocations(self.timetable, self.students[0], self.subjects[0], activity_types=["LAB", "LV", "AV"]) lecs = get_student_subject_other_allocations(self.timetable, self.students[0], self.subjects[0], activity_types=["P"]) self.assertEqual(5, len(labs)) self.assertEqual(0, len(lecs)) self.assertEqual(self.subjects[0], Activity.from_timetable_activity(labs[0].activityRealization.activity).subject)
def _cancel_exchange_get_params(self, exchange: Exchange): """Cancel an exchange object and return its parameters for end-to-end testing.""" result = dict( timetable=self.timetable, source_person=exchange.initiator_student, requested_student=exchange.requested_finalizer_student, subject_transfer_to_map={Activity.from_timetable_activity( exchange.allocation_from.activityRealization.activity).subject.id: exchange.allocation_to}, force_allocation_from=exchange.allocation_from if exchange.initiator_student is None else None ) exchange.date_cancelled = datetime.utcnow() exchange.save() return result
def accept_exchange(request, timetable_slug, exchange_id): selected_timetable = get_object_or_404(Timetable, slug=timetable_slug) student = Student.from_user(request.user) exchange = get_object_or_404(Exchange, id=exchange_id) if exchange.is_finalized(): header = "Exchange already finalized." message = "This exchange has already been finalized." elif exchange.is_cancelled(): header = "Exchange cancelled." message = "This exchange is cancelled." else: subject = Activity.from_timetable_activity( exchange.allocation_from.activityRealization.activity).subject try: any_fulfilled = process_new_exchange_request( selected_timetable, student, exchange.initiator_student if exchange.get_type() == ExchangeType.SPECIFIC_STUDENT else None, {subject.id: exchange.allocation_from}) 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!" message = "Your request was fulfilled. 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 })
def test_get_available_exchanges(self): exchanges = get_available_exchanges(self.timetable, self.students[0]) self.assertEqual(len(exchanges), 10) for e in exchanges: self.assertIn(Activity.from_timetable_activity(e.allocation_from.activityRealization.activity).subject, self.subjects)
def sync_activities_with_fri_najave(self, timetable, semester, year, location, update_subject): """ Sinhronizira učitelje v najavah z učitelji v aktivnostih. Če je potrebno, ustvari nove aktivnosti. Starih aktivnosti, ki jih ni v najavah, ne pobriše. Ce dodamo kodo predmeta, se posodobijo aktivnosti samo pri tem predmetu. Ce je koda predmeta None, potem se posodobijo vse aktivnosti. """ logger.info("Entering syncActivitiesWithFRINajave") def activity_name(subject, lecture_type): name = "{0}({1})_{2}".format(subject.name, subject.code, lecture_type.short_name) logger.debug("Generated name {}".format(name)) return name def activity_short_name(subject, lecture_type): name = "{0}_{1}".format(subject.short_name, lecture_type.short_name) logger.debug("Generated sname {}".format(name)) return name def get_teacher_codes(cikel, lecture_type_id): logger.debug("Processing {}".format(cikel)) codes = set() mapping_urnik_studis = {1: [1, 7], 2: [3], 3: [2], 4: [8], 6: [4]} for izvajalec in cikel['izvajalci']: if izvajalec['tip_izvajanja']['id'] in mapping_urnik_studis[ lecture_type_id]: codes.add(izvajalec['delavec_sifra']) logger.debug("Got codes for {}: {}".format(lecture_type_id, codes)) return codes def subject_lecture_types(cikel): # id from studis to our system # Studis: # [{u'id': 1, 'title': {u'en': None, 'sl': 'Predavanja'}}, # {u'id': 2, 'title': {u'en': None, 'sl': 'Avditorne vaje'}}, # {u'id': 3, 'title': {u'en': None, 'sl': 'Laboratorijske vaje'}}, # {u'id': 4, 'title': {u'en': None, 'sl': 'Seminar'}}, # {u'id': 5, 'title': {u'en': None, 'sl': 'Poobla\u0161\u010denec'}}, # {u'id': 6, 'title': {u'en': None, 'sl': 'Koordinator'}}, # {u'id': 7, 'title': {u'en': None, 'sl': 'Nosilec'}}, # {u'id': 8, 'title': {u'en': None, 'sl': 'Laborant'}}] mapping_studis_urnik = { 1: 1, 2: 3, 3: 2, 4: 6, 5: None, 6: None, 7: 1, 8: 2 } lecture_types_ids = set( mapping_studis_urnik[t['tip_izvajanja']['id']] for t in cikel['izvajalci']) lecture_types_ids.discard(None) return lecture_types_ids def get_duration(lecture_type_id, izvajanje): """ Get subject duration. The duration of subject depends on subject and lecture type. It is read from Studis database. """ mapping_urnik_studis = { 1: 'st_ur_predavanj', 2: 'st_ur_lab_vaj', 3: 'st_ur_avd_vaj', 6: 'st_ur_seminarja', 8: 'st_ur_lab_vaj', } if izvajanje[mapping_urnik_studis[lecture_type_id]] is None: return None add_duration = 0 # Weird fix: sometimes hours are attributed to seminar if izvajanje[ 'st_ur_seminarja'] is not None and lecture_type_id not in [ 1, 6 ]: add_duration = izvajanje['st_ur_seminarja'] total_duration = add_duration + izvajanje[ mapping_urnik_studis[lecture_type_id]] return total_duration / 15 semester_id = semester['id'] studij = Studij(year) sifranti = Sifranti() najave = Najave(year) # Only process this semester izvajanja izvajanja = [ i for i in studij.get_izvajanja() if i["semester"] == semester_id ] izvajanja_subject_ids = defaultdict(list) for izvajanje in izvajanja: izvajanja_subject_ids[izvajanje['idpredmet']].append(izvajanje) for cikel in najave.get_predmeti_cikli(): subject_code = cikel['predmet_sifra'] # Skip subjects we should no update if update_subject is not None and update_subject != subject_code: continue found = False izvajanja = izvajanja_subject_ids[cikel['predmet_id']] for izvajanje in izvajanja: izvajanje_id = izvajanje["id"].split("-")[1].strip() cikel_izvajanje_id = str(cikel["izvajanje_id"]) if cikel_izvajanje_id == izvajanje_id: found = True if not found: logger.info( "No matching izvajanje for cikel {0}, skipping".format( cikel).encode("utf-8")) continue izvajanje = izvajanja[0] if izvajanje['izvaja_partnerska_institucija']: logger.info('This izvajanje is managed by other faculty, skip') continue logger.info("Looking for subject code {0}".format( subject_code).encode("utf-8")) try: subject = Subject.objects.get(code=subject_code) except Exception: logger.Exception( f"Error retrieving subject with code {subject_code}") continue logger.debug("Processing subject {}".format(subject)) lecture_types = subject_lecture_types(cikel) logger.debug("Got lecture types: {}".format(lecture_types)) for lecture_type_id in lecture_types: logger.debug("Processing lt {}".format(lecture_type_id)) lecture_type = LectureType.objects.get(pk=lecture_type_id) logger.debug("lt {}".format(lecture_type)) activities = subject.activities.filter( lecture_type=lecture_type, activityset=timetable.activityset) logger.debug("Got activities {}".format(activities)) teacher_codes = get_teacher_codes(cikel, lecture_type_id) logger.debug("Got teacher codes {}".format(teacher_codes)) duration = get_duration(lecture_type_id, izvajanje) logger.debug("Got duration {}".format(duration)) teachers = [] for code in teacher_codes: try: teacher = Teacher.objects.get(code=code) teachers.append(teacher) except ObjectDoesNotExist as e: logger.exception( "Teacher with code {0} on subject {1} does not exist" .format(code, subject.code)) if activities.count() == 0: logger.debug( "Activity of type {0} for {1} not found. Creating one." .format(lecture_type, subject)) if duration is None: logger.debug( 'Duration for type {0} is 0, skipping.'.format( lecture_type)) continue activity = Activity( subject=subject, lecture_type=lecture_type, activityset=timetable.activityset, duration=duration, name=activity_name(subject, lecture_type), short_name=activity_short_name(subject, lecture_type), type=lecture_type.short_name) activity.save() activity.locations.add(location) logger.debug("Created activity {0}.".format(activity)) activities = [activity] for activity in activities: activity.name = activity_name(subject, lecture_type) activity.short_name = activity_short_name( subject, lecture_type) print("Name, shortname") print(activity.name, activity.short_name) activity.save() activity.teachers.clear() activity.teachers.add(*teachers) logger.info("Exiting syncActivitiesWithFRINajave")
def import_unitime_activities(tt, solution): """Read all Unitime allocations for the given solution. Read all scheduling subparts for these allocations and create activities and corresponding realizations and alocations. """ day_mapping = { 64: "MON", 32: "TUE", 16: "WED", 8: "THU", 4: "FRI", 2: "SAT", 1: "SUN" } itype_type_mapping = {10: 'P', 30: 'LV', 20: 'AV'} db = Database() query = ( "SELECT c.uniqueid, slot, days, r.external_uid, " "di.external_uid, ss.uniqueid, ss.min_per_week, ss.itype " "FROM assignment AS a " "JOIN assigned_rooms AS ar ON (a.uniqueid = ar.assignment_id) " "JOIN assigned_instructors AS ai ON (a.uniqueid = ai.assignment_id) " "JOIN departmental_instructor AS di ON " "(di.uniqueid = ai.instructor_id) " "JOIN class_ AS c ON (c.uniqueid = a.class_id) " "JOIN room AS r ON (r.uniqueid = ar.room_id) " "JOIN scheduling_subpart AS ss ON (ss.uniqueid = c.subpart_id) " "WHERE solution_id={0}").format(solution) db.execute(query) allocations = db.fetch_all_rows() # Subparts represent allocations subpart_data = dict() subpart_ids = set() for _, _, _, _, teacher_id, subpart_id, mpw, itype in allocations: subpart_ids.add(subpart_id) if subpart_id not in subpart_data: subpart_data[subpart_id] = (itype, mpw, [teacher_id]) else: subpart_data[subpart_id][2].append(teacher_id) # For each subpart id we create one activity for subpart_id in subpart_ids: # First we have to determine the subject for the subpart query = """SELECT co.external_uid FROM scheduling_subpart AS ss JOIN instr_offering_config AS ioc ON (ss.config_id=ioc.uniqueid) JOIN course_offering AS co ON (co.instr_offr_id=ioc.instr_offr_id) WHERE ss.uniqueid={0}""".format(subpart_id) db.execute(query) assert db.rowcount == 1, "There should be exactly one subject per subpart" subject = Subject.objects.get(pk=db.fetch_next_row()[0]) itype, minutes, teacher_ids = subpart_data[subpart_id] teachers = [ Teacher.objects.get(pk=teacher_id) for teacher_id in teacher_ids ] type_short_name = itype_type_mapping[itype] lecture_type = LectureType.objects.get(short_name=type_short_name) activity = Activity( subject=subject, lecture_type=lecture_type, name=subject.name + "_" + type_short_name, short_name=subject.short_name, activityset=tt.activityset, type=type_short_name, duration=minutes / 60, ) activity.save() for teacher in teachers: activity.teachers.add(teacher) # Get all allocations and iterate through all class ids # Classes represent realizations activity_allocations = [a for a in allocations if a[5] == subpart_id] class_ids = set([a[0] for a in activity_allocations]) for class_id in class_ids: class_allocations = [ a for a in activity_allocations if a[0] == class_id ] class_teacher_ids = [a[4] for a in class_allocations] class_teachers = [ Teacher.objects.get(pk=teacher_id) for teacher_id in class_teacher_ids ] slot, day, room_id = class_allocations[1:1 + 3] day = day_mapping[day] hour = slot / 12 # minute = slot % 12 * 5 # V resnici se pri nama vedno zacne ob uri in ne 15 cez minute = 0 allocation_time = "{0:02d}:{1:02d}".format(hour, minute) room = Classroom.objects.get(id=room_id) realization = ActivityRealization(activity=activity) realization.save() realization.teachers.add(*class_teachers) Allocation(timetable=tt, activityRealization=realization, classroom=room, day=day, start=allocation_time).save()