When we delete a course in Moodle, we lose the Panopto groups associated with it. This script copies the Panopto course group to a new, internal group with the same membership to ensure faculty and students retain access to videos in their course folders after course deletion. See my forum post for some details and advice from Panopto support. While the script could be adapted to other use cases, it is tightly coupled to this particular one. For instance, the script assumes:
- the root folder is a semester
- the course folders are the root's grandchildren
- Publisher groups aren't needed (thus not copied) because we don't use them
Copied groups have the name of the original group plus a postfix like "(internal abc123)" where abc123 is a hash, e.g. "ANIMA-2100-1-2023SP: Visual Storytelling (2023SP)::Creator (internal 123456)".
pipenv install
cp example.env .env
$EDITOR .env # fill in values
The APP_KEY
env var is the Application Key of the Identity Provider (IDP) we're using.
Find ID of the semester folder in Panopto (browse to the folder and look at the Manage tab in the web UI). Then run:
# you can also specify ROOT_FOLDER in .env
pipenv run python app.py term ROOT_FOLDER_ID
# often course folders will include Moodle groups from other semesters, use a filter to skip them
pipenv run python app.py term ROOT_FOLDER --filter "\(2021SP\)"
This iterates over all the grandchild course folders and copies their creator and access groups to internal groups. The LOGLEVEL
config/env var can be set to DEBUG
to see the objects returned by the Panopto SOAP API.
There is a Panopto Group Copy Test course category with a corresponding folder hierarchy in Panopto meant for testing this app. Sharing is configured to test issues like #3. If you search our Panopto groups for "pano-test-" it should return the test groups and you can delete all the internal ones to start over. For the --filter
flag, here's a good test that creates some groups and skips others:
python app.py term 64456041-2dd0-4c27-9d6c-b1020149a856 --filter "pano-test-1"
Users are only added to Panopto course folder groups once they access the course so the "one instructor many students" viewer group might not actually have all the students enrolled in the Moodle course.
There are some nuances to using the Panopto SOAP APIs. When looking at their public documentation, only the methods on the I... Interface
objects can be called. Other objects are structural information. Each interface requires a separate SOAP client; this script uses three clients.
Authentication would look different if we were using internal Panopto accounts (not external IDPs) with a password; it's easy to find out how in the Panopto SOAP examples. In general, when using an external account source like Moodle or our SSO, you must prefix the username with the IDP like sso.cca.edu\\ephetteplace
(two backslashes because it's the Python escape character).
Where a method accepts a list of IDs, like GetFoldersById
, we can pass either a single-entry list or a guid dict like { "guid": [id1, id2, ...]}
. If you pass a multi-entry list like GetFoldersById(auth=AuthInfo, folderIds=[id1, id2])
, Panopto will happily return results for only id1
with no warning.
Methods return either a list of objects or None
(not an empty list). So we cannot for user in GetUsersInGroup(): do_something(user)
because if there are no users in a group, it's a TypeError.
This is the chain of API calls the script makes:
- SessionManagement GetFoldersById
- Call
GetFoldersById
again with the child folder IDs to get grandchild folders (which are course folders, e.g. Moodle > 2020FA > ANIMA > Animation 101) - AccessManagement GetFolderAccessDetails to get a list of groups on the course folder
- UserManagement GetGroup to determine whether groups are external or not
- UserManagement GetUsersInGroup to get the members of the groups
- UserManagement CreateInternalGroup to create new internal groups
- AccessManagement GrantGroupAccessToFolder to give the new internal groups access to the course folders