Canvas Campus Course Enrollments

chadscott
Community Contributor
2
1566

The Problem:

Campus administrators and district department directors wanted some method to automatically enroll students/staff into courses based on their campus or district department affiliation.

Librarians, counselors, assistant principals were all using Canvas courses to share resources, communicate with students, and to extend campus community beyond the building. District departments such as Dyslexia, Assistive Technology, etc. use Canvas to house departmental resources and to complete trainings. In both scenarios, keeping up with enrollments in these courses was not possible and limited the use and effectiveness of the courses.

 At first, courses for the library and counselor were made Institutional courses, but that required adding Redirect links to these courses to the navigation of all student courses for that campus, which could be removed by the teacher. Also, because there were no enrollments, these courses could not be used to make announcements or be used to communicate with students/parents through the Canvas Inbox or facilitate discussions/assignments/etc.

The Process:

Getting the Information:

I knew I could get information from our Student Information System (eSchool) that would tell me the campus and grade level of a student. I also found out that I could get the building, group, and job description for staff from our ERP system (Munis). I asked for a nightly file to be created for each as CSV files.

Setting Up the Courses:

The difficulty would be the manual setup and management of these campus/department-wide courses. Because these are manually created, I needed to do some clean-up and prepare them to be added to the process. I created a master list of courses, to keep track of the implementation process as well as documentation of what courses have auto-enrollments.

mainlist.png


Here are the following steps I did to prepare each course -

  1. Created additional sections If enrolling by grade level 
  2. Added an SIS ID to the section(s) - I suggest a naming convention
  3. Checked that the course is set to Default term and removed any end date
  4. Course Settings > Enabled “Show Recent Announcements” and disabled “Allows students to attach files to discussions”
  5. If the course had student enrollments already, I removed them.
  6. I moved the course to the campus sub-account
  7. I added the SIS ID for the section(s) to the Ruby script
  8. Marked complete once enrollments were verified after running the script

Currently, there are four types of courses -

  1. Campus course with all students enrolled in one section (Library course)
  2. Campus course with students enrolled in grade-level sections (Counselor course)
  3. Campus course with students enrolled in a single grade level section (Class of… course)
  4. Campus information/training course with all staff enrolled in one section (Campus/District staff courses)

The Script:

The script, after two refactors, contains the following parts -

  1. File input/output directory pathways
  2. Campus hash that contains all section SIS IDs keyed to each course type
    1. An array for all campus courses
    2. An array for all grade level courses (each grade is a separate key)
    3. A graduating ‘Class of’ string root - the year is generated and appended
    4. An array for all staff courses
  3. Grades lambdas that contains the grade codes and their corresponding key from the Campus hash
  4. Grad year lambdas that calculate the appropriate graduation year based on current month and year.
  5. Generate enrollments for each student in the student input file
    1. Variables fetch needed information from the lambdas and hashes and then if they aren’t nil, the enrollment is added to the output CSV file
  6. Determine professional staff status
    1. This determines if the staff is a teacher, administrator, or student support paraprofessional
  7. Generate enrollments for each staff in the staff input file
    1. If the staff member is a professional designation and their campus has a staff course, the enrollment is added to the output CSV file
  8. Post the enrollment file to Canvas
    1. I’m using Diffing mode to delete enrollments that don’t need to be in the course anymore. That way, if students change campuses, they only see the courses for their current campus.

 

Right now, I am still kicking off the script manually (via a shortcut on my desktop) every morning, but I intend to schedule this on our server like our normal SIS import process. When I do this, I will add code to monitor the import progress and download the results CSV to log enrollment changes.

I've shortened the campus hash to just a few examples, but my hash has 78 campuses at this point. 🙂

The Code Template:

 

 

 

require 'csv'

require 'uri'

require 'net/http'

# Print to console enrollment file is complete
puts 'Campus courses enrollments file generating...'

# input file from eSchool (id, grade, building)
stu_roster_csv =
  'K:\exampledir\StudentRoster.csv'
# input file from Munis (StaffID, CAMPUSID, Group, JobClass, Position)
staff_roster_csv =
  'K:\exampledir\StaffRoster.csv'
# output enrollment file (id, role, section_id, status)
enroll_csv = 'K:\exampledir\campus_course_enrollment.csv'

# clear enrollments file
CSV.open(enroll_csv, 'w') { |clr| clr << %w[user_id role section_id status] }

# This hash contains the section ids for each course type.
campus = {
  # CRHS
  '7': {
    campCours: ['CRHSLIB4321'],
    cour12: ['CRHSFL22'],
    gradCourse: 'CRHSCL'
  },
  # JHS
  '14': {
    campCours: ['JHSLIB4651'],
    cour9: ['JHSCOUNS9'],
    cour10: ['JHSCOUNS10'],
    cour11: ['JHSCOUNS11'],
    cour12: ['JHSCOUNS12'],
    gradCourse: 'JHSCL',
    staffCours: ['JHSSTAFF']
  },
  # BDJH
  '50': {
    campCours: ['BDJHLIB5124'],
    cour6: ['BDJHCOUNS6'],
    cour7: ['BDJHCOUNS7'],
    cour8: ['BDJHCOUNS8']
  },
  # MCJH
  '43': {
    cour6: ['MCJHCOUNS6'],
    cour7: ['MCJHCOUNS7'],
    cour8: ['MCJHCOUNS8']
  },
  # ACE
  '143': {
    courPK: ['ACELIBPK'],
    courKG: ['ACELIBK'],
    cour1: ['ACELIB1'],
    cour2: ['ACELIB2'],
    cour3: ['ACELIB3'],
    cour4: ['ACELIB4'],
    cour5: %w[ACELIB5 ACE5CHOIR],
    staffCours: ['ACELIBSTAFF']
  },
  # BCE
  '106': {
    courPK: ['BCELIBPK'],
    courKG: ['BCELIBK'],
    cour1: ['BCELIB1'],
    cour2: ['BCELIB2'],
    cour3: ['BCELIB3'],
    cour4: ['BCELIB4'],
    cour5: ['BCELIB5'],
    staffCours: ['BCELIBSTAFF']
  }
}

# This hash contains which course lists from above to reference for each grade level.
grades = {
  'EE' => lambda { 'courEE'.to_sym },
  'PK' => lambda { 'courPK'.to_sym },
  'KG' => lambda { 'courKG'.to_sym },
  '01' => lambda { 'cour1'.to_sym },
  '02' => lambda { 'cour2'.to_sym },
  '03' => lambda { 'cour3'.to_sym },
  '04' => lambda { 'cour4'.to_sym },
  '05' => lambda { 'cour5'.to_sym },
  '06' => lambda { 'cour6'.to_sym },
  '07' => lambda { 'cour7'.to_sym },
  '08' => lambda { 'cour8'.to_sym },
  '09' => lambda { 'cour9'.to_sym },
  '10' => lambda { 'cour10'.to_sym },
  '11' => lambda { 'cour11'.to_sym },
  '12' => lambda { 'cour12'.to_sym }
}

def grad_year(month, year)
  {
    '09' => lambda { month < 8 ? year + 3 : year + 4 },
    '10' => lambda { month < 8 ? year + 2 : year + 3 },
    '11' => lambda { month < 8 ? year + 1 : year + 2 },
    '12' => lambda { month < 8 ? year : year + 1 }
  }
end

role = 'student'
status = 'active'

t = Time.now
month = t.month
year = t.year

# Create student enrollments
# Get each student record, set id, grade, and building to variables strip removes any whitespaces from string
CSV.foreach(stu_roster_csv, headers: true) do |row|
  student_id = row['STUDENT_ID']
  grade = row['GRADE']
  building = row['BUILDING']

  # Determine grade level, set hash key to variable for grade level sections
  curr_level = grades.fetch(grade).call
  campus_courses =
    if campus.fetch(building.to_sym, nil).nil? == false
      campus.fetch(building.to_sym).fetch(:campCours, nil)
    end

  grade_level_courses =
    if campus.fetch(building.to_sym, nil).nil? == false
      campus.fetch(building.to_sym).fetch(curr_level, nil)
    end

  class_course_yr =
    if grad_year(month, year).fetch(grade, nil).nil? == false
      grad_year(month, year).fetch(grade, nil).call
    end

  class_course_str =
    if campus.fetch(building.to_sym, nil).nil? == false
      campus.fetch(building.to_sym).fetch(:gradCourse, nil)
    end

  if class_course_str.nil? == false
    class_course = "#{class_course_str}#{class_course_yr}"
  end

  # Create enrollment entries for all campus courses
  if campus_courses.nil? == false
    campus_courses.each do |course|
      CSV.open(enroll_csv, 'a') do |csv|
        csv << [student_id, role, course, status]
      end
    end
  end

  # Create enrollment entries for all grade level courses
  if grade_level_courses.nil? == false
    grade_level_courses.each do |course|
      CSV.open(enroll_csv, 'a') do |csv|
        csv << [student_id, role, course, status]
      end
    end
  end

  # Create enrollment entries for all graduating class courses - HS only
  if class_course.nil? == false
    CSV.open(enroll_csv, 'a') do |csv|
      csv << [student_id, role, class_course, status]
    end
  end
end

# Create staff enrollments
# Determines professional staff status
def professional_staff(group, job_class, position)
  if (group.include? 'TEACHERS') || (group.include? 'ADMINISTRATOR') ||
       (job_class.include? 'CLASSROOM') || (position.include? 'INSTRUCTIONAL')
    true
  end
end

CSV.foreach(staff_roster_csv, headers: true) do |row|
  user_id = row['StaffID'].to_s
  staff_building = row['CampusID'].to_i.to_s
  group = row['Group'].to_s
  job_class = row['JobClass'].to_s
  position = row['Position'].to_s
  staff_courses =
    if campus.fetch(staff_building.to_sym, nil).nil? == false
      campus.fetch(staff_building.to_sym).fetch(:staffCours, nil)
    end

  # Create enrollment entries for all campus courses
  if (staff_courses.nil? == false) &&
       professional_staff(group, job_class, position)
    staff_courses.each do |course|
      CSV.open(enroll_csv, 'a') { |csv| csv << [user_id, role, course, status] }
    end
  end
end

# This posts the enrollment file to Canvas
url = URI('https://YOURCANVASURL/api/v1/accounts/1/sis_imports')

https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true

request = Net::HTTP::Post.new(url)
request['Authorization'] =
  'Bearer ADD YOUR API ACCESS TOKEN HERE'
form_data = [
  [
    'attachment',
    File.open(
      'K:\EXAMPLEDIR\campus_course_enrollment.csv'
    )
  ],
  %w[diffing_data_set_identifier campus_courses:enrollmentsFULL],
  %w[diffing_drop_status deleted],
  %w[override_sis_stickiness true],
  %w[clear_sis_stickiness true]
]
request.set_form form_data, 'multipart/form-data'
response = https.request(request)
puts response.read_body

# Print to console enrollment file is complete
puts 'Campus courses enrollments file created and posted successfully!'
# This opens a webpage to your SIS Imports
system('start https://YOURCANVASURL/accounts/1/sis_import')

 

 

I hope this may help some that need a place to start or a template to work from. If you aren’t comfortable with Ruby, this could be easily translated into Python, etc.

If you have any questions or if something isn't clear, please feel free to DM or comment below.

Cheers,

Chad Scott

2 Comments