Skip to content

Commit

Permalink
Merge pull request #166 from uw-it-aca/feature/data-abstraction
Browse files Browse the repository at this point in the history
Feature/data abstraction
  • Loading branch information
mikeseibel authored Jul 29, 2024
2 parents b48e931 + 4c98dc6 commit 070967a
Show file tree
Hide file tree
Showing 78 changed files with 2,296 additions and 1,397 deletions.
16 changes: 8 additions & 8 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ jobs:

steps:
- name: Checkout Repo
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.10'

Expand All @@ -82,18 +82,18 @@ jobs:
exclude_paths: 'migrations'

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3

- name: Cache Docker layers
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-$(echo ${{ hashFiles('Dockerfile') }} | head -c 16)
restore-keys: |
${{ runner.os }}-buildx-
- name: Build App Image
uses: docker/build-push-action@v3
uses: docker/build-push-action@v6
with:
target: app-container
tags: ${{ needs.context.outputs.image_tag }}
Expand All @@ -103,7 +103,7 @@ jobs:
cache-to: type=local,dest=/tmp/.buildx-cache

- name: Build Test Image
uses: docker/build-push-action@v3
uses: docker/build-push-action@v6
with:
target: app-test-container
tags: app-test-container
Expand Down Expand Up @@ -149,11 +149,11 @@ jobs:

steps:
- name: Checkout Repo
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Deployment Pipeline
if: >-
contains(fromJSON('["main", "develop"]'),
contains(fromJSON('["main", "develop"'),
needs.context.outputs.git_repo_branch)
uses: uw-it-aca/actions/cicd-deploy@main
with:
Expand Down
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ RUN /app/bin/pip install psycopg2
RUN . /app/bin/activate && pip install nodeenv && nodeenv -p && \
npm install -g npm && ./bin/npm install less -g

RUN . /app/bin/activate && python manage.py collectstatic --noinput &&\
python manage.py compress -f
RUN . /app/bin/activate && python manage.py compress -f && python manage.py collectstatic --noinput

FROM us-docker.pkg.dev/uwit-mci-axdd/containers/django-test-container:${DJANGO_CONTAINER_VERSION} as app-test-container

Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ services:
ENV: localdev
PORT: 8000
AUTH: SAML_MOCK BLTI_DEV
PANOPTO_API_APP_ID: UWNetid
LTI_DEVELOP_APP: scheduler/course
restart: always
container_name: app-scheduler
Expand Down
13 changes: 10 additions & 3 deletions docker/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

MIDDLEWARE += ['userservice.user.UserServiceMiddleware',]

COMPRESS_ENABLED = True
COMPRESS_ENABLED = False if os.getenv("ENV") == "localdev" else True
COMPRESS_OFFLINE = True
COMPRESS_ROOT = '/static/'

Expand Down Expand Up @@ -66,6 +66,12 @@
'scheduler.context_processors.localdev_mode',
]

SCHEDULER_TIMEZONE = "America/Los_Angeles"

USER_MODULE = 'scheduler.org.uw.user'
COURSES_MODULE = 'scheduler.org.uw.course'
RESERVATIONS_MODULE = 'scheduler.org.uw.reservations'

PANOPTO_ADMIN_GROUP = 'u_acadev_panopto_support'
RESTCLIENTS_ADMIN_GROUP = PANOPTO_ADMIN_GROUP
USERSERVICE_ADMIN_GROUP = PANOPTO_ADMIN_GROUP
Expand All @@ -89,12 +95,13 @@
'is_desktop': True,
}

PANOPTO_API_USER = os.getenv('PANOPTO_API_USER', '')
PANOPTO_API_APP_ID = os.getenv('PANOPTO_API_APP_ID', '')

if os.getenv("ENV", "localdev") == "localdev":
LTI_DEVELOP_APP = os.getenv("LTI_DEVELOP_APP", '')
DEBUG = True
else:
PANOPTO_API_USER = os.getenv('PANOPTO_API_USER')
PANOPTO_API_APP_ID = os.getenv('PANOPTO_API_APP_ID')
PANOPTO_API_TOKEN = os.getenv('PANOPTO_API_TOKEN')
PANOPTO_SERVER = os.getenv('PANOPTO_SERVER')
DEBUG = (os.getenv("ENV", "UNSET") == "dev")
Expand Down
5 changes: 2 additions & 3 deletions scheduler/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.conf import settings
from uw_saml.utils import is_member_of_group
import re
from scheduler.user import User


def userservice_validate(username):
Expand All @@ -13,8 +13,7 @@ def userservice_validate(username):
if username != username.lower():
return "Usernames must be all lowercase"

re_personal_netid = re.compile(r'^[a-z][a-z0-9]{0,7}$', re.I)
if not re_personal_netid.match(username):
if not User().validate_login_id(username):
return ("Username not a valid netid (starts with a letter, "
"then 0-7 letters or numbers)")

Expand Down
63 changes: 63 additions & 0 deletions scheduler/course.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright 2024 UW-IT, University of Washington
# SPDX-License-Identifier: Apache-2.0

#
# Course reflects the attributes necessary to correlate a
# specific course offering's meeting schedule with
# Panopto recording sessions, the facilities scheduling system,
# and Canvas LMS.
#

from abc import ABC, abstractmethod
from scheduler.utils.loader import load_class_from_module_setting


COURSES_MODULE_SETTING = 'COURSES_MODULE'


class Course(object):
"""Indirection class used to load campus-specific Course class"""
def __new__(self, courseId):
return load_class_from_module_setting(
COURSES_MODULE_SETTING, 'Course', courseId)


class BaseCourse(ABC):
"""Base class definining methods necessary to associate a course
offering with the course meeting location and panopto folder and
scheduled recording sessions
"""

@abstractmethod
def reservation_uid(self):
"""ID used to reference the course offering in the reservation
system. For R25 this is would be the Alien UID unique for
the couse offering.
"""
pass

@abstractmethod
def panopto_course_external_id(self, start_datetime):
"""Opaque string unique to each course offering."""
pass

@abstractmethod
def panopto_course_folder(self, title):
"""Folder name and folder external_id that must match the values
set by Panopto's Recordings LTI
"""
pass

@abstractmethod
def panopto_course_session(self, start_datetime):
"""Recording session name and external_id unique to a course
offering meeting time.
"""
pass

@abstractmethod
def get_crosslisted_course(self):
"""If the Course is crosslisted (joined), return the Course
class corresponding to the course crosslisted are linked into
"""
return self
Empty file added scheduler/dao/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions scheduler/dao/canvas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright 2024 UW-IT, University of Washington
# SPDX-License-Identifier: Apache-2.0

from uw_canvas.courses import Courses as CanvasCourses


canvas_api = CanvasCourses()


def get_course_by_sis_id(sis_id):
return canvas_api.get_course_by_sis_id(sis_id)


def get_course_by_canvas_id(course_id):
if canvas_api.valid_course_id(course_id):
return canvas_api.get_course(course_id)

raise Exception("Invalid course id: {}".format(course_id))
Empty file.
29 changes: 29 additions & 0 deletions scheduler/dao/panopto/access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2024 UW-IT, University of Washington
# SPDX-License-Identifier: Apache-2.0

from panopto_client.access import AccessManagement


access_api = AccessManagement()


def get_session_access_details(session_id):
return access_api.getSessionAccessDetails(session_id)


def get_folder_access_details(folder_id):
return access_api.getFolderAccessDetails(folder_id)


def update_session_is_public(session_id, is_public):
return access_api.updateSessionIsPublic(session_id, is_public)


def grant_users_access_to_folder(folder_id, creator_ids, access_type):
return access_api.grantUsersAccessToFolder(
folder_id, creator_ids, access_type)


def revoke_users_access_from_folder(folder_id, creator_ids, access_type):
return access_api.revokeUsersAccessFromFolder(
folder_id, creator_ids, access_type)
49 changes: 49 additions & 0 deletions scheduler/dao/panopto/recorder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright 2024 UW-IT, University of Washington
# SPDX-License-Identifier: Apache-2.0

from scheduler.utils.validation import Validation
from panopto_client.remote_recorder import RemoteRecorderManagement
import re


recorder_api = RemoteRecorderManagement()


def list_recorders():
return recorder_api.listRecorders()


def get_recorder_details(recorder_id):
if re.match(r'^\d+$', recorder_id):
recorders = get_recorder_by_external_id(recorder_id)
else:
Validation().panopto_id(recorder_id)
recorders = get_recorder_by_id(recorder_id)

if not (recorders and hasattr(recorders, 'RemoteRecorder')):
return None

return recorders.RemoteRecorder


def get_recorder_by_id(recorder_id):
return recorder_api.getRemoteRecordersById(recorder_id)


def get_recorder_by_external_id(external_id):
return recorder_api.getRemoteRecordersByExternalId(external_id)


def update_recorder_external_id(recorder_id, external_id):
return recorder_api.updateRemoteRecorderExternalId(
recorder_id, external_id)


def schedule_recording(
name, folder_id, is_broadcast, start_time, end_time, recorder_id):
return recorder_api.scheduleRecording(
name, folder_id, is_broadcast, start_time, end_time, recorder_id)


def update_recording_time(session_id, start_time, end_time):
return recorder_api.updateRecordingTime(session_id, start_time, end_time)
82 changes: 82 additions & 0 deletions scheduler/dao/panopto/sessions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright 2024 UW-IT, University of Washington
# SPDX-License-Identifier: Apache-2.0

from panopto_client.session import SessionManagement
from panopto_client import PanoptoAPIException
from scheduler.utils import panopto_app_id
from scheduler.exceptions import PanoptoFolderDoesNotExist


session_api = SessionManagement()


def get_sessions_by_external_ids(external_ids):
sessions = session_api.getSessionsByExternalId(external_ids)
return sessions.Session if (sessions and 'Session' in sessions and
len(sessions.Session)) else None


def get_sessions_by_session_ids(session_ids):
sessions = session_api.getSessionsById(session_ids)
return sessions.Session if (sessions and 'Session' in sessions and
len(sessions.Session)) else None


def update_session_external_id(session_id, external_id):
return session_api.updateSessionExternalId(session_id, external_id)


def update_session_is_broadcast(session_id, is_broadcast):
return session_api.updateSessionIsBroadcast(session_id, is_broadcast)


def move_sessions(session_ids, folder_id):
return session_api.moveSessions(session_ids, folder_id)


def delete_sessions(session_ids):
return session_api.deleteSessions(session_ids)


def get_folders_by_id(folder_ids):
try:
return session_api.getFoldersById(folder_ids)
except PanoptoAPIException as ex:
if str(ex).find("was not found") > 0:
raise PanoptoFolderDoesNotExist(ex)
else:
raise ex


def get_folders_with_external_context_list(*args, **kwargs):
return session_api.getFoldersWithExternalContextList(*args, **kwargs)


def get_folders_list(*args, **kwargs):
return session_api.getFoldersList(*args, **kwargs)


def get_all_folders_with_external_context_by_external_id(
external_ids, providers=None):
if not providers:
providers = [panopto_app_id()]

return session_api.getAllFoldersWithExternalContextByExternalId(
external_ids, providers)


def get_all_folders_by_external_id(external_ids):
return session_api.getAllFoldersByExternalId(external_ids)


def add_folder(name, parent_id=None, is_public=False):
return session_api.addFolder(name, parent_id, is_public)


def provision_external_course(course_name, external_id):
return session_api.provisionExternalCourse(course_name, external_id)


def update_folder_external_id_with_provider(folder_id, external_id, provider):
return session_api.updateFolderExternalIdWithProvider(
folder_id, external_id, provider)
15 changes: 15 additions & 0 deletions scheduler/dao/panopto/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2024 UW-IT, University of Washington
# SPDX-License-Identifier: Apache-2.0

from panopto_client.user import UserManagement


user_api = UserManagement()


def get_users_from_guids(guids):
return user_api.getUsers(guids)


def get_user_by_key(key):
return user_api.getUserByKey(key)
Loading

0 comments on commit 070967a

Please sign in to comment.