def _scheduled_locations_per_section_id(all_scheduled): locations_per_section_id = {} rooms = Room.get_rooms([s.room_id for s in all_scheduled]) locations_per_room_id = dict((room.id, room.location) for room in rooms) for section_id, room_id in dict( (s.section_id, s.room_id) for s in all_scheduled).items(): locations_per_section_id[section_id] = locations_per_room_id[room_id] return locations_per_section_id
def _to_api_json(term_id, rows, include_rooms=True): rows = rows.fetchall() section_ids = list(set(int(row['section_id']) for row in rows)) courses_per_id = {} # Perform bulk queries and build data structures for feed generation. section_ids_opted_out = CoursePreference.get_section_ids_opted_out( term_id=term_id) invited_uids_by_section_id = {section_id: [] for section_id in section_ids} for invite in SentEmail.get_emails_of_type(section_ids=section_ids, template_type='invitation', term_id=term_id): if invite.recipient_uid not in invited_uids_by_section_id[ invite.section_id]: invited_uids_by_section_id[invite.section_id].append( invite.recipient_uid) approval_results = Approval.get_approvals_per_section_ids( section_ids=section_ids, term_id=term_id) scheduled_results = Scheduled.get_scheduled_per_section_ids( section_ids=section_ids, term_id=term_id) room_ids = set(row['room_id'] for row in rows) room_ids.update(a.room_id for a in approval_results) room_ids.update(s.room_id for s in scheduled_results) rooms = Room.get_rooms(list(room_ids)) rooms_by_id = {room.id: room for room in rooms} approvals_by_section_id = {section_id: [] for section_id in section_ids} for approval in approval_results: approvals_by_section_id[approval.section_id].append( approval.to_api_json(rooms_by_id=rooms_by_id)) scheduled_by_section_id = { s.section_id: s.to_api_json(rooms_by_id=rooms_by_id) for s in scheduled_results } cross_listings_per_section_id, instructors_per_section_id, canvas_sites_by_section_id = _get_cross_listed_courses( section_ids=section_ids, term_id=term_id, approvals=approvals_by_section_id, invited_uids=invited_uids_by_section_id, ) # Construct course objects. # If course has multiple instructors or multiple rooms then the section_id will be represented across multiple rows. # Multiple rooms are rare, but a course is sometimes associated with both an eligible and an ineligible room. We # order rooms in SQL by capability, NULLS LAST, and use scheduling data from the first row available. for row in rows: section_id = int(row['section_id']) if section_id in courses_per_id: course = courses_per_id[section_id] else: # Approvals and scheduled (JSON) approvals = approvals_by_section_id.get(section_id) scheduled = scheduled_by_section_id.get(section_id) # Instructors per cross-listings cross_listed_courses = cross_listings_per_section_id.get( section_id, []) instructors = instructors_per_section_id.get(section_id, []) # Construct course course = { 'allowedUnits': row['allowed_units'], 'approvals': approvals, 'canvasCourseSites': canvas_sites_by_section_id.get(section_id, []), 'courseName': row['course_name'], 'courseTitle': row['course_title'], 'crossListings': cross_listed_courses, 'deletedAt': safe_strftime(row['deleted_at'], '%Y-%m-%d'), 'hasOptedOut': section_id in section_ids_opted_out, 'instructionFormat': row['instruction_format'], 'instructors': instructors, 'invitees': invited_uids_by_section_id.get(section_id), 'isPrimary': row['is_primary'], 'label': _construct_course_label( course_name=row['course_name'], instruction_format=row['instruction_format'], section_num=row['section_num'], cross_listings=cross_listed_courses, ), 'meetings': { 'eligible': [], 'ineligible': [], }, 'nonstandardMeetingDates': False, 'sectionId': section_id, 'sectionNum': row['section_num'], 'scheduled': scheduled, 'termId': row['term_id'], } courses_per_id[section_id] = course # Note: Instructors associated with cross-listings are slurped up separately. instructor_uid = row['instructor_uid'] if instructor_uid and instructor_uid not in [ i['uid'] for i in course['instructors'] ]: course['instructors'].append( _to_instructor_json( row=row, approvals=course['approvals'], invited_uids=course['invitees'], ), ) meeting = _to_meeting_json(row) eligible_meetings = course['meetings']['eligible'] ineligible_meetings = course['meetings']['ineligible'] if not next((m for m in (eligible_meetings + ineligible_meetings) if meeting.items() <= m.items()), None): room = rooms_by_id.get( row['room_id']) if 'room_id' in row.keys() else None if room and room.capability: meeting['eligible'] = True meeting.update({ 'recordingEndDate': safe_strftime(get_recording_end_date(meeting), '%Y-%m-%d'), 'recordingStartDate': safe_strftime(get_recording_start_date(meeting), '%Y-%m-%d'), }) eligible_meetings.append(meeting) eligible_meetings.sort( key=lambda m: f"{m['startDate']} {m['startTime']}") if meeting['startDate'] != app.config[ 'CURRENT_TERM_BEGIN'] or meeting[ 'endDate'] != app.config['CURRENT_TERM_END']: course['nonstandardMeetingDates'] = True else: meeting['eligible'] = False ineligible_meetings.append(meeting) ineligible_meetings.sort( key=lambda m: f"{m['startDate']} {m['startTime']}") if include_rooms: meeting['room'] = room.to_api_json() if room else None # Next, construct the feed api_json = [] for section_id, course in courses_per_id.items(): _decorate_course(course) # Add course to the feed api_json.append(course) return api_json