Esempio n. 1
0
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,
    }
Esempio n. 2
0
 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,
     }
Esempio n. 3
0
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
Esempio n. 4
0
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']),
    }
Esempio n. 5
0
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
Esempio n. 6
0
 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,
     }
Esempio n. 7
0
    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)
Esempio n. 8
0
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}
        """)
Esempio n. 9
0
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