Ejemplo n.º 1
0
def app_config():
    term_id = app.config['CURRENT_TERM_ID']
    return tolerant_jsonify({
        'canvasBaseUrl':
        app.config['CANVAS_BASE_URL'],
        'courseCaptureExplainedUrl':
        app.config['COURSE_CAPTURE_EXPLAINED_URL'],
        'courseCapturePoliciesUrl':
        app.config['COURSE_CAPTURE_POLICIES_URL'],
        'currentTermId':
        term_id,
        'currentTermName':
        term_name_for_sis_id(term_id),
        'devAuthEnabled':
        app.config['DEVELOPER_AUTH_ENABLED'],
        'diabloEnv':
        app.config['DIABLO_ENV'],
        'ebEnvironment':
        app.config['EB_ENVIRONMENT']
        if 'EB_ENVIRONMENT' in app.config else None,
        'emailTemplateTypes':
        EmailTemplate.get_template_type_options(),
        'publishTypeOptions':
        NAMES_PER_PUBLISH_TYPE,
        'roomCapabilityOptions':
        Room.get_room_capability_options(),
        'searchFilterOptions':
        get_search_filter_options(),
        'searchItemsPerPage':
        app.config['SEARCH_ITEMS_PER_PAGE'],
        'supportEmailAddress':
        app.config['EMAIL_DIABLO_SUPPORT'],
        'timezone':
        app.config['TIMEZONE'],
    })
Ejemplo n.º 2
0
def approve():
    term_id = app.config['CURRENT_TERM_ID']
    term_name = term_name_for_sis_id(term_id)

    params = request.get_json()
    publish_type = params.get('publishType')
    recording_type = params.get('recordingType')
    section_id = params.get('sectionId')

    course = SisSection.get_course(term_id, section_id) if section_id else None

    if not course or publish_type not in get_all_publish_types() or recording_type not in get_all_recording_types():
        raise BadRequestError('One or more required params are missing or invalid')

    if not current_user.is_admin and current_user.uid not in [i['uid'] for i in course['instructors']]:
        raise ForbiddenRequestError('Sorry, request unauthorized')

    if Approval.get_approval(approved_by_uid=current_user.uid, section_id=section_id, term_id=term_id):
        raise ForbiddenRequestError(f'You have already approved recording of {course["courseName"]}, {term_name}')

    meetings = course.get('meetings', {}).get('eligible', [])
    if len(meetings) != 1:
        raise BadRequestError('Unique eligible meeting pattern not found for course')
    meeting = meetings[0]

    location = meeting and meeting.get('location')
    room = Room.find_room(location=location)
    if not room:
        raise BadRequestError(f'{location} is not eligible for Course Capture.')

    previous_approvals = Approval.get_approvals_per_section_ids(section_ids=[section_id], term_id=term_id)
    approval = Approval.create(
        approved_by_uid=current_user.uid,
        approver_type_='admin' if current_user.is_admin else 'instructor',
        course_display_name=course['label'],
        publish_type_=publish_type,
        recording_type_=recording_type,
        room_id=room.id,
        section_id=section_id,
        term_id=term_id,
    )

    if previous_approvals:
        # Compare the current approval with preferences submitted in previous approval
        previous_approval = previous_approvals[-1]
        if (approval.publish_type, approval.recording_type) != (previous_approval.publish_type, previous_approval.recording_type):
            notify_instructors_of_changes(course, approval, previous_approvals)

    all_approvals = previous_approvals + [approval]
    if len(course['instructors']) > len(all_approvals):
        approval_uids = [a.approved_by_uid for a in all_approvals]
        pending_instructors = [i for i in course['instructors'] if i['uid'] not in approval_uids]
        last_approver = next((i for i in course['instructors'] if i['uid'] == approval.approved_by_uid), None)
        if last_approver:
            notify_instructor_waiting_for_approval(course, last_approver, pending_instructors)

    return tolerant_jsonify(_after_approval(course=SisSection.get_course(term_id, section_id)))
Ejemplo n.º 3
0
def _get_substitutions(
    course=None,
    instructor_name=None,
    pending_instructors=None,
    previous_publish_type_name=None,
    previous_recording_type_name=None,
    publish_type_name=None,
    recipient_name=None,
    recording_type_name=None,
):
    term_id = (course and course['termId']) or app.config['CURRENT_TERM_ID']
    return {
        'course.days':
        course and course['meetingDays'],
        'course.format':
        course and course['instructionFormat'],
        'course.name':
        course and course['courseName'],
        'course.room':
        course and course['meetingLocation'],
        'course.section':
        course and course['sectionNum'],
        'course.time.end':
        course and course['meetingEndTime'],
        'course.time.start':
        course and course['meetingStartTime'],
        'course.title':
        course and course['courseTitle'],
        'instructor.name':
        instructor_name,
        'instructors.pending':
        pending_instructors and [p['name'] for p in pending_instructors],
        'publish.type':
        publish_type_name,
        'publish.type.previous':
        previous_publish_type_name,
        'recording.type':
        recording_type_name,
        'recording.type.previous':
        previous_recording_type_name,
        'signup.url':
        course and _get_sign_up_url(term_id, course['sectionId']),
        'term.name':
        term_name_for_sis_id(term_id),
        'user.name':
        recipient_name,
    }
Ejemplo n.º 4
0
def get_template_substitutions(
        course,
        recipient_name,
        pending_instructors=None,
        previous_publish_type_name=None,
        previous_recording_type_name=None,
        publish_type_name=None,
        recording_type_name=None,
):
    term_id = (course and course['termId']) or app.config['CURRENT_TERM_ID']

    def _join_names(_dict):
        if not _dict:
            return None
        return ', '.join(i['name'] for i in _dict if i['name'])

    if course:
        meetings = course.get('meetings', {}).get('eligible', [])
        meetings = meetings or course.get('meetings', {}).get('ineligible', [])
        meeting = meetings and meetings[0]
        days = meeting and get_names_of_days(meeting['daysFormatted'])
    else:
        days = None
        meeting = None

    return {
        'course.date.end': meeting and meeting['endDate'],
        'course.date.start': meeting and meeting['startDate'],
        'course.days': days and readable_join(days),
        'course.format': course and course['instructionFormat'],
        'course.name': course and course['courseName'],
        'course.room': meeting and meeting['location'],
        'course.section': course and course['sectionNum'],
        'course.time.end': meeting and meeting['endTimeFormatted'],
        'course.time.start': meeting and meeting['startTimeFormatted'],
        'course.title': course and course['courseTitle'],
        'instructors.all': course and _join_names(course.get('instructors')),
        'instructors.pending': _join_names(pending_instructors),
        'instructors.previous': course and course.get('scheduled') and _join_names(course['scheduled'].get('instructors')),
        'publish.type': publish_type_name,
        'publish.type.previous': previous_publish_type_name,
        'recipient.name': recipient_name,
        'recording.type': recording_type_name,
        'recording.type.previous': previous_recording_type_name,
        'signup.url': course and get_sign_up_url(term_id, course['sectionId']),
        'term.name': term_name_for_sis_id(term_id),
    }
Ejemplo n.º 5
0
 def test_series_description(self):
     """Series description for cross-listed course."""
     cross_listed_section_id = 50012
     term_id = app.config['CURRENT_TERM_ID']
     course = SisSection.get_course(
         section_id=cross_listed_section_id,
         term_id=term_id,
     )
     description = get_series_description(
         course_label=course['label'],
         instructors=course['instructors'],
         term_name=term_name_for_sis_id(term_id),
     )
     assert 'MATH C51, LEC 001 | STAT C151, COL 001' in description
     assert 'Fall 2021' in description
     assert 'Rudolf Schündler and Arthur Storch' in description
     assert '2021' in description
Ejemplo n.º 6
0
def app_config():
    def _to_api_key(key):
        chunks = key.split('_')
        return f"{chunks[0].lower()}{''.join(chunk.title() for chunk in chunks[1:])}"
    return tolerant_jsonify(
        {
            **dict((_to_api_key(key), app.config[key]) for key in PUBLIC_CONFIGS),
            **{
                'currentTermName': term_name_for_sis_id(app.config['CURRENT_TERM_ID']),
                'ebEnvironment': get_eb_environment(),
                'emailTemplateTypes': EmailTemplate.get_template_type_options(),
                'publishTypeOptions': NAMES_PER_PUBLISH_TYPE,
                'roomCapabilityOptions': Room.get_room_capability_options(),
                'searchFilterOptions': get_search_filter_options(),
            },
        },
    )
Ejemplo n.º 7
0
def approve():
    term_id = app.config['CURRENT_TERM_ID']
    term_name = term_name_for_sis_id(term_id)

    params = request.get_json()
    publish_type = params.get('publishType')
    recording_type = params.get('recordingType')
    section_id = params.get('sectionId')
    course = SisSection.get_course(term_id, section_id) if section_id else None

    if not course or publish_type not in get_all_publish_types() or recording_type not in get_all_recording_types():
        raise BadRequestError('One or more required params are missing or invalid')

    if not current_user.is_admin and current_user.uid not in [i['uid'] for i in course['instructors']]:
        raise ForbiddenRequestError('Sorry, request unauthorized')

    if Approval.get_approval(approved_by_uid=current_user.uid, section_id=section_id, term_id=term_id):
        raise ForbiddenRequestError(f'You have already approved recording of {course["courseName"]}, {term_name}')

    location = course['meetingLocation']
    room = Room.find_room(location=location)
    if not room:
        raise BadRequestError(f'{location} is not eligible for Course Capture.')

    previous_approvals = Approval.get_approvals_per_section_ids(section_ids=[section_id], term_id=term_id)
    approval = Approval.create(
        approved_by_uid=current_user.uid,
        approver_type_='admin' if current_user.is_admin else 'instructor',
        cross_listed_section_ids=[c['sectionId'] for c in course['crossListings']],
        publish_type_=publish_type,
        recording_type_=recording_type,
        room_id=room.id,
        section_id=section_id,
        term_id=term_id,
    )
    _notify_instructors_of_approval(
        approval=approval,
        course=course,
        previous_approvals=previous_approvals,
    )
    return tolerant_jsonify(SisSection.get_course(term_id, section_id))
Ejemplo n.º 8
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)
Ejemplo n.º 9
0
 def test_term_name_for_sis_id(self):
     assert term_name_for_sis_id('2208') == 'Fall 2020'
     assert term_name_for_sis_id('2212') == 'Spring 2021'