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.
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.
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.
Here are the following steps I did to prepare each course -
Currently, there are four types of courses -
The script, after two refactors, contains the following parts -
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. 🙂
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
My current role focuses on maintaining Canvas for Katy ISD and developing new tools and trainings for users. Previously, I was a Classroom Technology Designer from 2016/2017 to 2019/2020 school years at several high schools and junior highs. I have six years of classroom teaching experience - I taught AP Environmental Science, Chemistry, and IPC at Morton Ranch HS and Miller Career & Technology Center. I graduated from Eastern Michigan University with a B.S. in Geology and minor in Environmental Science. Previous to my teaching career, I worked as an Environmental Geologist focused on groundwater and soil remediation projects. I have a passion for spreadsheets and love developing methods to increase efficiency and reduce investment in tedious tasks. I love learning - whether it's something technology related or something more traditional like making costumes and quilts.