def _create_meeting(days, end_date, end_time, start_date, start_time): return { 'days': days, 'daysFormatted': format_days(days), 'endDate': end_date, 'endTime': end_time, 'startDate': start_date, 'startTime': start_time, }
def to_api_json(self, rooms_by_id=None): room_feed = None if self.room_id: if rooms_by_id: room_feed = rooms_by_id.get(self.room_id, None).to_api_json() else: room_feed = Room.get_room(self.room_id).to_api_json() formatted_days = format_days(self.meeting_days) return { 'id': self.id, 'alerts': self.alerts or [], 'createdAt': to_isoformat(self.created_at), 'instructorUids': self.instructor_uids, 'kalturaScheduleId': self.kaltura_schedule_id, 'meetingDays': formatted_days, 'meetingDaysNames': get_names_of_days(formatted_days), 'meetingEndDate': datetime.strftime(self.meeting_end_date, '%Y-%m-%d'), 'meetingEndTime': self.meeting_end_time, 'meetingEndTimeFormatted': format_time(self.meeting_end_time), 'meetingStartDate': datetime.strftime(self.meeting_start_date, '%Y-%m-%d'), 'meetingStartTime': self.meeting_start_time, 'meetingStartTimeFormatted': format_time(self.meeting_start_time), 'publishType': self.publish_type, 'publishTypeName': NAMES_PER_PUBLISH_TYPE[self.publish_type], 'recordingType': self.recording_type, 'recordingTypeName': NAMES_PER_RECORDING_TYPE[self.recording_type], 'room': room_feed, 'sectionId': self.section_id, 'termId': self.term_id, }
def get_recording_end_date(meeting): term_end = datetime.strptime(app.config['CURRENT_TERM_RECORDINGS_END'], '%Y-%m-%d') actual_end = datetime.strptime(meeting['endDate'].split()[0], '%Y-%m-%d') end_date = actual_end if actual_end < term_end else term_end # Determine first course meeting BEFORE end_date. last_recording = None meeting_day_indices = [DAYS.index(day) for day in format_days(meeting['days'])] for index in range(7): # Monday is 0 and Sunday is 6 day_index = (end_date.weekday() - index) % 7 if day_index in meeting_day_indices: last_day = end_date - timedelta(days=index) last_recording = datetime(last_day.year, last_day.month, last_day.day) break return last_recording
def _to_meeting_json(row): end_date = row['meeting_end_date'] start_date = row['meeting_start_date'] formatted_days = format_days(row['meeting_days']) return { 'days': row['meeting_days'], 'daysFormatted': formatted_days, 'daysNames': get_names_of_days(formatted_days), 'endDate': safe_strftime(end_date, '%Y-%m-%d'), 'endTime': row['meeting_end_time'], 'endTimeFormatted': format_time(row['meeting_end_time']), 'location': row['meeting_location'], 'startDate': safe_strftime(start_date, '%Y-%m-%d'), 'startTime': row['meeting_start_time'], 'startTimeFormatted': format_time(row['meeting_start_time']), }
def get_recording_start_date(meeting, return_today_if_past_start=False): term_begin = datetime.strptime(app.config['CURRENT_TERM_RECORDINGS_BEGIN'], '%Y-%m-%d') actual_start = datetime.strptime(meeting['startDate'].split()[0], '%Y-%m-%d') start_date = actual_start if actual_start > term_begin else term_begin today = datetime.today() start_date = today if start_date < today and return_today_if_past_start else start_date # Determine first course meeting AFTER start_date. first_recording = None meeting_day_indices = [DAYS.index(day) for day in format_days(meeting['days'])] for index in range(7): # Monday is 0 and Sunday is 6 day_index = (start_date.weekday() + index) % 7 if day_index in meeting_day_indices: first_day = start_date + timedelta(days=index) first_recording = datetime(first_day.year, first_day.month, first_day.day) break return first_recording
def to_api_json(self): return { 'createdAt': to_isoformat(self.created_at), 'crossListedSectionIds': self.cross_listed_section_ids, 'instructorUids': self.instructor_uids, 'meetingDays': format_days(self.meeting_days), 'meetingEndTime': format_time(self.meeting_end_time), 'meetingStartTime': format_time(self.meeting_start_time), 'publishType': self.publish_type, 'publishTypeName': NAMES_PER_PUBLISH_TYPE[self.publish_type], 'recordingType': self.recording_type, 'recordingTypeName': NAMES_PER_RECORDING_TYPE[self.recording_type], 'room': Room.get_room(self.room_id).to_api_json() if self.room_id else None, 'sectionId': self.section_id, 'termId': self.term_id, }
def _schedule_recurring_events_in_kaltura( self, category_ids, course_label, instructors, meeting, publish_type, recording_type, room, term_id, ): # Recording starts X minutes before/after official start; it ends Y minutes before/after official end time. days = format_days(meeting['days']) start_time = _adjust_time(meeting['startTime'], app.config['KALTURA_RECORDING_OFFSET_START']) end_time = _adjust_time(meeting['endTime'], app.config['KALTURA_RECORDING_OFFSET_END']) app.logger.info(f""" Prepare to schedule recordings for {course_label}: Room: {room.location} Instructor UIDs: {[instructor['uid'] for instructor in instructors]} Schedule: {days}, {start_time} to {end_time} Recording: {recording_type}; {publish_type} """) term_name = term_name_for_sis_id(term_id) recording_start_date = get_recording_start_date( meeting, return_today_if_past_start=True) recording_end_date = get_recording_end_date(meeting) summary = f'{course_label} ({term_name})' app.logger.info(f""" {course_label} ({term_name}) meets in {room.location}, between {start_time.strftime('%H:%M')} and {end_time.strftime('%H:%M')}, on {days}. Recordings of type {recording_type} will be published to {publish_type}. """) first_day_start = get_first_matching_datetime_of_term( meeting_days=days, start_date=recording_start_date, time_hours=start_time.hour, time_minutes=start_time.minute, ) first_day_end = get_first_matching_datetime_of_term( meeting_days=days, start_date=recording_start_date, time_hours=end_time.hour, time_minutes=end_time.minute, ) description = get_series_description(course_label, instructors, term_name) base_entry = self._create_kaltura_base_entry( description=description, instructors=instructors, name=f'{summary} in {room.location}', ) for category_id in category_ids or []: self.add_to_kaltura_category(category_id=category_id, entry_id=base_entry.id) until = datetime.combine( recording_end_date, time(end_time.hour, end_time.minute), tzinfo=default_timezone(), ) recurring_event = KalturaRecordScheduleEvent( # https://developer.kaltura.com/api-docs/General_Objects/Objects/KalturaScheduleEvent classificationType=KalturaScheduleEventClassificationType. PUBLIC_EVENT, comment=f'{summary} in {room.location}', contact=','.join(instructor['uid'] for instructor in instructors), description=description, duration=(end_time - start_time).seconds, endDate=first_day_end.timestamp(), organizer=app.config['KALTURA_EVENT_ORGANIZER'], ownerId=app.config['KALTURA_KMS_OWNER_ID'], partnerId=self.kaltura_partner_id, recurrence=KalturaScheduleEventRecurrence( # https://developer.kaltura.com/api-docs/General_Objects/Objects/KalturaScheduleEventRecurrence byDay=','.join(days), frequency=KalturaScheduleEventRecurrenceFrequency.WEEKLY, # 'interval' is not documented. When scheduling manually, the value was 1 in each individual event. interval=1, name=summary, timeZone='US/Pacific', until=until.timestamp(), weekStartDay=days[0], ), recurrenceType=KalturaScheduleEventRecurrenceType.RECURRING, startDate=first_day_start.timestamp(), status=KalturaScheduleEventStatus.ACTIVE, summary=summary, tags=CREATED_BY_DIABLO_TAG, templateEntryId=base_entry.id, ) return self.kaltura_client.schedule.scheduleEvent.add(recurring_event)
def _schedule_recordings(all_approvals, course): term_id = course['termId'] section_id = int(course['sectionId']) all_approvals.sort(key=lambda a: a.created_at.isoformat()) approval = all_approvals[-1] room = Room.get_room(approval.room_id) meeting_days, meeting_start_time, meeting_end_time = SisSection.get_meeting_times( term_id=term_id, section_id=section_id, ) time_format = '%H:%M' # Recording starts X minutes before/after official start; it ends Y minutes before/after official end time. recording_offset_start = app.config['KALTURA_RECORDING_OFFSET_START'] recording_offset_end = app.config['KALTURA_RECORDING_OFFSET_END'] adjusted_start_time = datetime.strptime( meeting_start_time, time_format) + timedelta(minutes=recording_offset_start) adjusted_end_time = datetime.strptime( meeting_end_time, time_format) + timedelta(minutes=recording_offset_end) days = format_days(meeting_days) instructor_uids = [ instructor['uid'] for instructor in course['instructors'] ] app.logger.info(f""" Prepare to schedule recordings for {course["label"]}: Room: {room.location} Instructor UIDs: {instructor_uids} Schedule: {days}, {adjusted_start_time} to {adjusted_end_time} Recording: {approval.recording_type}; {approval.publish_type} """) if room.kaltura_resource_id: Kaltura().schedule_recording( course_label=course['label'], instructor_uids=instructor_uids, days=days, start_time=adjusted_start_time, end_time=adjusted_end_time, publish_type=approval.publish_type, recording_type=approval.recording_type, room=room, ) scheduled = Scheduled.create( cross_listed_section_ids=approval.cross_listed_section_ids, instructor_uids=SisSection.get_instructor_uids( term_id=term_id, section_id=section_id), meeting_days=meeting_days, meeting_start_time=meeting_start_time, meeting_end_time=meeting_end_time, publish_type_=approval.publish_type, recording_type_=approval.recording_type, room_id=approval.room_id, section_id=section_id, term_id=term_id, ) notify_instructors_recordings_scheduled(course=course, scheduled=scheduled) uids = [approval.approved_by_uid for approval in all_approvals] app.logger.info( f'Recordings scheduled for course {section_id} per approvals: {", ".join(uids)}' ) else: app.logger.error(f""" FAILED to schedule recordings because room has no 'kaltura_resource_id'. Course: {course} Room: {room} Latest approval: {approval} """)
def _to_api_json(term_id, rows, include_rooms=True): courses_per_id = {} instructors_per_section_id = {} section_ids_opted_out = CoursePreference.get_section_ids_opted_out( term_id=term_id) # If course has multiple instructors then the section_id will be represented across multiple rows. for row in rows: approvals = [] section_id = int(row['section_id']) if section_id not in courses_per_id: # Construct new course instructors_per_section_id[section_id] = [] has_opted_out = section_id in section_ids_opted_out cross_listings = _get_cross_listed_courses(section_id=section_id, term_id=term_id) approvals, scheduled = _get_approvals_and_scheduled( section_ids=[section_id] + [c['sectionId'] for c in cross_listings], term_id=term_id, ) course_name = row['course_name'] instruction_format = row['instruction_format'] section_num = row['section_num'] course = { 'allowedUnits': row['allowed_units'], 'canvasCourseSites': _canvas_course_sites(term_id, section_id), 'courseName': course_name, 'courseTitle': row['course_title'], 'crossListings': cross_listings, 'hasOptedOut': has_opted_out, 'instructionFormat': instruction_format, 'instructors': [], 'isPrimary': row['is_primary'], 'label': f'{course_name}, {instruction_format} {section_num}', 'meetingDays': format_days(row['meeting_days']), 'meetingEndDate': row['meeting_end_date'], 'meetingEndTime': format_time(row['meeting_end_time']), 'meetingLocation': row['meeting_location'], 'meetingStartDate': row['meeting_start_date'], 'meetingStartTime': format_time(row['meeting_start_time']), 'sectionId': section_id, 'sectionNum': section_num, 'termId': row['term_id'], 'approvals': approvals, 'scheduled': scheduled, } invites = SentEmail.get_emails_of_type( section_id=section_id, template_type='invitation', term_id=term_id, ) course['invitees'] = [] for invite in invites: course['invitees'].extend(invite.recipient_uids) if scheduled: course['status'] = 'Scheduled' elif approvals: course['status'] = 'Partially Approved' else: course['status'] = 'Invited' if invites else 'Not Invited' if include_rooms: room = Room.get_room( row['room_id']).to_api_json() if 'room_id' in row else None course['room'] = room courses_per_id[section_id] = course # Build upon course object with one instructor per row. instructor_uid = row['instructor_uid'] if instructor_uid not in [ i['uid'] for i in instructors_per_section_id[section_id] ]: instructors_per_section_id[section_id].append({ 'approval': next((a for a in approvals if a['approvedBy']['uid'] == instructor_uid), False), 'deptCode': row['instructor_dept_code'], 'email': row['instructor_email'], 'name': row['instructor_name'], 'roleCode': row['instructor_role_code'], 'uid': instructor_uid, 'wasSentInvite': instructor_uid in courses_per_id[section_id]['invitees'], }) api_json = [] for section_id, course in courses_per_id.items(): room_id = course.get('room', {}).get('id') def _add_and_verify_room(approval_or_scheduled): action_room_id = approval_or_scheduled.get('room', {}).get('id') is_obsolete_room = not room_id or room_id != action_room_id approval_or_scheduled['hasObsoleteRoom'] = is_obsolete_room course['instructors'] = instructors_per_section_id[section_id] course['hasNecessaryApprovals'] = _has_necessary_approvals(course) scheduled = course['scheduled'] # Check for course changes w.r.t. room, meeting times, and instructors. if scheduled: def _meeting(obj): return f'{obj["meetingDays"]}-{obj["meetingStartTime"]}-{obj["meetingEndTime"]}' instructor_uids = set( [instructor['uid'] for instructor in course['instructors']]) scheduled['hasObsoleteInstructors'] = instructor_uids != set( scheduled['instructorUids']) scheduled['hasObsoleteMeetingTimes'] = _meeting( course) != _meeting(scheduled) _add_and_verify_room(scheduled) for approval in course['approvals']: _add_and_verify_room(approval) # Add course to the feed api_json.append(course) return api_json