Duplicate Course Enrollments with Python

bbennett2
Community Champion
4
2127

While schools are closed, we've moved much of our long term staff development material into Canvas. We have one long-running course with all staff split into site-based sections that has worked as a model for others. We needed a way to essentially duplicate the template course enrollments into new training courses.

 

Ignorance is bliss (sometimes) and I didn't know of a good way to make this happen. I looked at some of the provisioning reports, but I couldn't select a single course to run a report on. So, I reached for Python and the UCF Open canvasapi library to make it happen.

 

At the end of this process, I ended with a brand new course, populated with teachers enrolled in their specific sections. I was also able to disable the new registration email and set their course status to active by default.

 

 

from config import PROD_KEY, PROD_URL
from canvasapi import Canvas # pip install canvasapi

# Define your course IDs. Be careful!
template_course_id = ''
new_course_id = ''

canvas = Canvas(PROD_URL, PROD_KEY)

template_course = canvas.get_course(template_course_id)
new_course = canvas.get_course(new_course_id)

# Open the template course section by section
template_sections = template_course.get_sections()

# Get any sections that may already exist in the new course
new_sections = [section.name for section in new_course.get_sections()]

# This whole loop could be improved a little.
for section in template_sections:
    # Get all the section enrollments
    enrollments = section.get_enrollments()

    # If it's a brand new course, this should always be false
    if not section.name in new_sections:
        print(f'Creating section {section.name}')
        new_sections.append(section.name)
        course_section = {
            "name": section.name,
        }
        new_section = new_course.create_course_section(course_section=course_section)
        
        count = 0 # start counting enrollments for quick quality checks
        
        for enrollment in enrollments:
            student = enrollment.user['id']
            print(f'Enrolling {enrollment.user["name"]}')
            count += 1
            args = {
                "course_section_id": new_section.id,
                "notify": False,
                "enrollment_state": "active"
            }
            try:
                new_course.enroll_user(student, "StudentEnrollment", enrollment=args)
            except Exception as e:
                print(e)
        print(f'Enrolled {count} users in {new_section.name}')‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

 

It's definitely brute force, but it saved us from having to copy and paste nearly 1,300 users into the new course by hand from a spreadsheet.

 

Why force enroll at all?

I think this highlights one of the barriers for really taking Canvas to the next level for staff support. There is no good way to enroll non-student users in courses for required development. In our case, it's to fulfill a required training for staff and using Canvas makes sense as a lot is done through application and reflection.

 

The public course index in Canvas could be used, but without a great way to expose the course to instructional staff only (I know we could use some JavaScript and edit the template, but that's just another thing to manage) it could lead to students joining courses either by accident or maliciously.

 

We've also toyed around with making a custom self-signup process on an internal website where staff are forwarded directly to the enroll page, but it's another system to manage and another site for teachers to use. The most hands-off approach for all involved is to do something like this in the background as needed to get people where they need to be effectively and efficiently.

Tags (1)
4 Comments
raj_veeravalli
Community Participant

Brian,

Thanks for sharing your script. We do similar things with canvasapi to copy entire courses using the migration API and enroll students and staff to courses that run every semester. Nice to see that I'm not alone on this path Smiley Happy

We use the limit_privileges_to_course_section parameter to restrict student users to individual sections and allow staff unrestricted access at the course level.

The user role is available when you request course or section enrollments as part of the enrollment object. Hence, you should be able to mirror both StudentEnrollment and TeacherEnrollment or other role types, from a parent to a child course if you like without specifying that statically in your code.

The only way I can think of speeding this up is if you have the enrollment data on file, such as a CSV via the provisioning report or SIS export. I find that the use of REST API to consume data is a bit slow, even with python's multithreading. Actioning from an export file is almost error-free when computing deltas and minimizes the risk of network errors while providing a convenient audit log.

We have started investigating Graph QL endpoints for some of these large jobs with sgqlc package and use a mix of canvasapi package(REST API) at places, but it's still early stages.

bbennett2
Community Champion

Thanks for the tips on those flags! I don't envision using this too much, but it'll be nice to speed it up a little bit.

I have pulled the provisioning CSV to do some student account manipulation, but I didn't do it for this instance because they wanted a literal carbon copy of the first course, so pulling live made a little more sense. It's nice because I can run the script on a second monitor and just watch for errors in the terminal while it chews through the list.

ericdano
Community Member

Do you perhaps  have a non-mangled version of this? I'm currently writing an enrollment program and this would be good to look at.

 

Thanks!

bbennett2
Community Champion

@ericdano I'm not sure why the formatting was lost...probably had to do with the changeover to the new forum this summer. I just updated the syntax highlighting and it's easier to read now.