Showing results for 
Show  only  | Search instead for 
Did you mean: 

Canvas Developers Group

New Member

“Computer Vision” sounds like something from the future, though computer vision algorithms are not as fantastic as they seem to be. Starting from 1960-ies and up to today, computer vision was developing along with AI ideas and culminated in today’s helping hand of business and marketing enhancement. Let’s discover why, in the era of tech, it becomes essential to get to know such technologies.


0 0 48
New Member

Hi Everyone,

Great to be part of this community, Im IT Support for Horizon Christian School, and would love to share an app i made for the school.

Not sure were to post this, so hopefully ok here.

I have created an application with a Settings file that will make this application universal across platforms.

Our host of example is the key you need to generate in your user settings

Click [+ New Access Token]. button

I have shared from my onedrive, feel free to try it out if you like.

Screenshot Attached

Horizon Canvas User Importer 

0 0 127
New Member

I want a test question type that I will call a mastery question. This kind of question will allow me to randomize the input numbers so that the answer can be calculated by Canvas but will be different every time for every student.

For example, ...
If I want to test that students know that the sum of the three angles in any triangle is 180 degrees then I would have Canvas randomize a variable 'A' (I could tell Canvas to generate a whole number between 20 and 50) and a variable 'B' (I could tell Canvas to generate a whole number between 50 and 90). Then the answer could be automatically calculated by Canvas as '180 - A - B'.

Students could answer this question only if they understood the principle but could test their knowledge using the same question an unlimited number of times. That is because the first time they are asked the question: "What is the third angle of a triangle if the first angle is 32 degrees [random number generated for 'A'] ad the second angle is 76 degrees [input random number generated for 'B']? The correct answer would be calculated by Canvas as 72 degrees (180-32-76 = 72). If the student did not enter the correct answer they would get feedback reminding them of what they need to know then they could get the same question again but with different numbers.

I want to use this for a math review test that should evaluate the students' readiness for moving forward in a course. This is essential for any math-rich course.

0 1 149
New Member

Unenroll user from a class via SIS UserID?


0 2 241

I'm looking to build an app that integrates on all canvas pages for the student role and ideally would like to use LTI to accomplish this, but as LTI placement doesn't allow for space on every page and doesn't allow for the space I'm looking to occupy (right sidebar) I am currently building this app out using the global javascript functionality. I do still need to authenticate the user on my server for server side functions and this is where LTI really would have helped. I'm curious if anyone has any experience combining these two approaches and if there might be a way  to authenticate a global js based app using the LTI 1.3 protocol (from the global js context - so would have to be an api call or something).

If not (maybe I'm approaching this the wrong way), I know I can call the canvas api using the context of the currently logged in user, is there an easy way I could maybe pass a token or something that my server could authenticate the logged in user?

0 6 344
New Member

Hi everyone, I hopefully have a simple question to ask here about this call.

I want to run a creation of quiz questions through a CSV file with this API and I have everything sorted bar how to write the say 4 possible answers to a multiple choice question. I'm using Postman to run the call.

If someone has an example that would be great too 😊

*FYI I have attached an image of a test question


Thanks again,

0 1 213
New Member

We need to embed our Hubspot chatflow to our Canvas Instructure.


0 0 279
New Member

Hi everyone this is what I currently have:




And my CSV contains the following two copies:


But my two copies are sitting as 'pre processing' and I am unsure as to what I'm doing wrong. Help would be greatly appreciated

0 4 350
New Member

Get grade book  of course with Canvas API


0 1 225
New Member

Has anyone had success renaming specific terminology both in the navigation and throughout the platform?


0 0 268
New Member

If you are thinking of taking your courses to the next level or are interested in other flexible ways to develop and manage content authoring, read on!

🎬 YouTube version.

Following on from our CanvasCon 2018 talk, this article describes how we embraced the web’s shift to a decentralised stack (APIs, no-code, Notion, Zapier...) and created our own authoring system that creates engaging and responsive pages rendered in Canvas.



10 0 642
New Member

گلاب با تقطیر گلبرگهای گل رز با بخار ایجاد می شود. گلاب معطر است و گاهی اوقات به عنوان یک عطر طبیعی ملایم به عنوان جایگزینی برای عطرهای پر از مواد شیمیایی استفاده می شود.

هزاران سال از گلاب استفاده شده است ، از جمله در قرون وسطی. تصور می شود که در ایران کنونی ریشه گرفته باشد.

به طور سنتی هم در محصولات زیبایی و هم در محصولات غذایی و آشامیدنی مورد استفاده قرار گرفته است.

این همچنین با بسیاری از مزایای بالقوه سلامتی ، از جمله موارد زیر همراه است


0 0 267
New Member

This is a general question about APIs. Imagine, we have an object (users, assignments, etc) in canvas with all parameters set especially (SIS ID, or integration ID). Now, I want to know, if I send an API(POST request) with exact parameters to canvas, what would happen when the object exists already in canvas?

The canvas ignore the POST request? or overwrite the object with the new request, or create a duplicate?

I am asking this question because I am developing a python code, reads the data from the database and sends POST requests to create an object in canvas, so I am worried if I send a request which the object is already into the system.


0 2 388
Community Contributor

Exporting Canvas Live Events to your SQL database


7 6 1,228
New Member


We are trying to set up Diffing mode to run our SIS files and are using the below CURL command.

/usr/bin/curl --tlsv1.2 -H 'Content-Type: application/octet-stream' -F diffing_data_set_identifier=DiffID -F attachment=@/home/batch/Canvas/ --output /dev/null --write-out '%{http_code}' --stderr /tmp/ -H "Authorization: Bearer TOKEN" 

Unfortunately the files are not updating anything in the system. Does anyone see anything wrong with our CURL command that may be causing this issue to occur?



0 1 358
New Member

When submitting a multi HTML Content Items request, the generated HTML is wrong.

See how item #2 and #3 are embedded inside item #1 instead of after it:


Seems like a regression as it used to work fine a few days ago.

Any idea what is causing this?

Best Regards,


0 0 417
Community Contributor

I've created a Ruby script to add events to teacher and student personal calendars in our institution. This will post grading periods, holidays, and testing dates to allow for better planning by users.


2 1 637
Community Champion

Some time ago I wrote a program to compute an index for a course by walking the course pages and identifying key terms in the page, collecting all of the figure and table captions, all of the text that has been tagged as being in a language other than English, etc. and

see the heading "Making an index" at and details of using it can be found at 

The result is a wikipage with entries of the form:

<li>sockets API
<li><a href="../modules/items/316319">Socket API</a></li>

Note that the anchor HREF is to a relative location the "../" gets replace by the browser with the prefix for the page ( ) yielding the full URL 

I had to use these relative HREFs to reduce the index for the course down to 3 pages due to the limited size of wikipages. This works perfectly well for the student in the course.

If I run the link validator from the page that says:

Course Link Validator

The course link validator searches course content for invalid or unreachable links and images.
The link validator reports: Found 14,610 broken links
Nearly all of the claimed broken links do in fact work.

1 0 445
Community Champion

Making use of sections, grading standards, custom data columns, and other features of Canvas to support a course with ~600 students, ~180 examiners, lots of other teachers/TAs, etc.

Making administrative assignments to hold useful information - assignments to store and manage information concerning students (but not containing student submissions) - an alternative to custom gradebook columns.


0 0 354
New Member

Hi! I'm Noah,
a student at the Karel de Grote Hogeschool in Antwerp who's studying NxTmedia Technology Web Development.

Since the COVID-19 virus changed the way of teaching and following lectures we've been trying to find a solution for the inactivity of students and for centralization of some information. Aftera long time of brainstorming we finnally found a "solution" that should make it possible for students an their teachers to get more attention for announcements that are being published on canvas.

In our school we use discord as a full time replacement of normal tutoring. since we had to do everything online to minimize the contact. Over a short period of time more and more students of were getting used to using discord as a teaching platform. Most of the students already knew Discord as a voice communication platform for gaming. Mostly they were online here instead of Canvas.

What we've created is a bot that makes it possible to create watchlists in text channels that announce new incoming announcements of the courses that are being watched.

This made it possible to reach our students much faster and created a much bigger chance for you that the students would read it.

we've just released the first version of our Discord bot and we would like some opinions! As honest as possible 😉

There is a more detailed explanation on our public GitHub repository for the ones who're interested! Also we've open-sourced the whole project just to support the community and help the ones who need it!

The link of our Open-source Discord Bot

8 8 5,077
Community Member

As far as we can see, there is no built-in method to add and update user avatar photos in Canvas. Many organisations probably allow students to add their own avatars, but it's arguably better to have school photos in Canvas because teachers find them more useful to help with identifying students etc. This blog post describes a method which I have implemented to add all student and staff photos to Canvas, and to maintain them so that they revert to the 'official' photo if they are changed. Full code is available on GitHub. 


4 9 2,561
Community Champion

returns { 'notification_preferences': [ list ] } rather than [ list ]



0 0 498
Community Member

A script to generate a Postman collection based on Canvas' swagger documentation.


23 5 2,110
Community Member

NEVER conclude courses!  We found this out the hard way.  It really creates more problems than it solves and concluding is pretty much the younger evil brother of the delete process.  You have been warned!


2 1 1,502

This blog post was authored by Xander Moffatt who is a Software Engineer on our Interoperability Team.

Safari blocks all 3rd-party cookies by default, which breaks LTI tools that rely on setting cookies when launched in an iframe. Instead, it exposes a new API for getting user-permitted access to set cookies from an iframe, which works though requires jumping through some fun hoops.


Safari has been working towards this step for a few years now, since the introduction of the Storage Access API and Intelligent Tracking Prevention. These features were introduced to limit cross-site tracking that users never agreed to, and to preserve their privacy. These limitations have also grown more strict over the years, with most third-party cookies already being blocked by the time of the newest release, 13.1.

Before this release, third-party cookies were allowed to be set once the domain has set a first-party cookie, which occurs when the tool is launched in the parent browser window instead of a child iframe. Canvas implemented this change to launch the tool in the parent window, let it set a cookie, and provide a redirect url so that the tool is launched again in an iframe, with the ability to set third-party cookies.

This release makes Safari the first mainstream browser to fully block third-party cookies by default, though Chrome aims to ship the same features by 2022. The Storage Access API should also be made standard, providing a known way for LTI tools to still be functional. Note that this behavior can be turned off, by disabling the Preferences > Privacy > Prevent cross-site tracking checkbox.

When this behavior is enabled, the current behavior of an LTI tool launch in Canvas is to get stuck in an infinite loop, since Storage Access hasn’t been granted and so the cookie can never be set.

Storage Access API

There are only two methods in the Storage Access API, but they are more complex than they look. document.hasStorageAccess asynchronously resolves to a boolean indicating whether the iframe already has access to set its own cookies. In practice, this is almost never true until a call to the next method, document.requestStorageAccess. This method also asynchronously resolves to a boolean indicating whether the iframe now has access. The hoops that require jumping through come with this method.

  • this method must be called upon a user gesture (like tap/click). this means the user must click a button and the listener for this button must directly call requestStorageAccess. any calls that aren’t inside a listener will immediately return false.
  • this method won’t return true for a domain in an iframe unless the user has interacted with the domain in a first-party context. This is to make sure that the user knows and trusts this domain. Interaction in this case means another user gesture like a tap or click.
  • this method will return true if the user has had storage access granted in the last 24 hours.
  • once this has been called from a user gesture, the user has interacted in a first-party context, and then the request is sent again from a third-party context, then Safari will prompt the user using a browser dialog box to allow storage access. Once the user clicks Allow, then this method will return true and the tool can finally set the cookies it needs to authenticate its user.


There are a couple of ways to approach this situation from a Canvas-launching-LTI tools standpoint, and both of them are on the tool side, as opposed to the Canvas side. Canvas continues its behavior of providing a redirect url when the tool requests a full window launch, but the tool has some decisions to make.

If your LTI tool can handle being stateless and not setting cookies (ie it doesn’t require logging in, or the login process is fast so can be done on every launch), do it. Move any non-login cookies to window.ENV or something, let the user login if needed, and just plan on that whole flow happening on every launch.

If your LTI tool requires storing state in cookies and keeping the user logged in, there is a slightly more complex process to work with an in-line Canvas launch. Note that the Storage Access API happens in Javascript, but most LTI tools want to set httpOnly cookies from the server for sensitive cookies like a login token, so once the tool has Storage Access, a final redirect back to the server to set cookies and render the UI will be needed.

  1. When the tool launches, use document.hasStorageAccess to check if the tool already has Storage Access. This will most likely never be true, but if it is, redirect to the tool server to set cookies and render the UI.
  2. Request Storage Access using a user button click that calls document.requestStorageAccess. If the user has granted Storage Access within the last 24 hours, this will be granted. If granted, redirect to the tool server to set cookies and render the UI.
  3. If the request fails, then it’s time to get user interaction in a first-party context. Send a postMessage to Canvas requesting a full window launch, providing the tool’s normal launch url.
  4. Once that custom postMessage has been sent, Canvas will launch the tool again, in a full window. Canvas will send a platform_redirect_url in the request parameters, which is how you can tell it’s a full window launch. Get user interaction by having them click a button, and on that click redirect to the url Canvas supplied.
  5. Canvas will redirect to that url, which means another tool launch in an iframe. The tool will go through steps 1 and 2 again, and this time Safari should prompt the user to grant access. Once that happens, the tool has Storage Access and should redirect to the tool server to set cookies and render the UI.

Efforts are being made to encapsulate this behavior in some sort of gem/module, but since it touches both server- and client-side code it might be hard.

Though this method requires anywhere from 1-3 user button clicks before the app loads, it does provide a non-hacky way of interacting with cookies in Safari.

note that these are snippets that don’t have all variables and dependencies added. They are just for reference!
  • checking for storage access
document.addEventListener("DOMContentLoaded", () => {
if (document.hasStorageAccess) {
.then((hasStorageAccess) => {
if (hasStorageAccess) {
.catch((err) => console.error(err));
} else {

<RequestStorageAccess />,
  • requesting storage access
const requestStorageAccess = () => {
.then(() => redirectToSetCookies())
.catch(() => requestFullWindowLaunch());

const buttonText = "Continue to LTI Tool";
const promptText =
"Safari requires your interaction with this tool inside Canvas to keep you logged in.\n" +
"A dialog may appear asking you to allow this tool to use cookies while browsing Canvas.\n" +
"For the best experience, click Allow.";

return (
  • requesting a full window launch
const requestFullWindowLaunch = () => {
messageType: "requestFullWindowLaunch",
  • interact with user in a first-party context
const SafariLaunch = () => {
const redirect = () => {
const buttonText = "Continue to LTI Tool";
const promptText =
"Safari requires your interaction with this tool outside of Canvas before continuing.";

return (

document.addEventListener("DOMContentLoaded", () => {
<SafariLaunch />,
  • handle different types of launches, including full window, Safari, and non-Safari
# Safari launch: Full-window launch, solely for first-party user interaction.
# Redirect to Canvas for inline relaunch.
if safari_redirect_required?
@platform_redirect_url = params[:platform_redirect_url]
return render('safari/full_window_launch')

if browser.safari?
# Safari launch: request Storage Access, then redirect to
# :relaunch_after_storage_access_request with pertinent cookie info
# If Storage Access request fails, request a full window launch instead.
@id_token = id_token
@state = state
return render('safari/request_storage_access')

# Non-Safari launch: set cookies and render app launch


7 2 4,253
Community Champion

After the fun yesterday of using the dashboard cards to make the entry of a course ID easier (Using the dashboard information via the API in programs ). I decided to do a similar thing for user IDs, so rather than have to enter a Canvas user_id for each program - why not be more flexible with a "person_id". The result is a program that can take any of several forms of user identification in and get a Canvas user-id for this user. It also shows how to use some of the SIS IDs at Object IDs, SIS IDs, and special IDs - Canvas LMS REST API Documentation.

    # check for numeric string, in which case this a Canvas user_id
if person_id.isdigit():
elif person_id.count('-') == 4: # a sis_integration_id
integration_id=person_id # since we have the ID, save it for later
elif person_id.find('@') > 1: # if an e-mail address/login ID
# assume it is a local university ID

# extract Canvas user-id from the user's info:
print("sortable name={}".format(info['sortable_name']))
print("Canvas user_id={}".format(user_id))

# try to get the user's integration_id via their profile
integration_id=user_profile.get('integration_id', None)
login_id=user_profile.get('login_id', None)
if login_id:


The routines user_info() and user_profile_info() just call the GET /api/v1/users/:id and GET /api/v1/users/:id/profile APIs respectively.

This has some relation to the question I ask in Searching for users and my several iterations of addressing the question.

Also, as noted in my reply to What is the use case for "integration_ids"? in order to get the user's integration_id, i.e., the sis_integration_id) - I had to use the GET /api/v1/courses/:course_id/enrollments API. I'm not completely sure why all of these different IDs for a user are not returned in the User structure returned by GET /api/v1/users/:id .

0 0 424
Community Champion

In most of my programs that use the Canvas API, I take in the course_id from the command line in numeric form (i.e., the string: 11). One of my colleagues said that he does not like to remember the course numbers but would rather use the course code or a nickname. So this motivated me to see if one could use the dashboard information for this.

The first thing I discovered was that it seems the dashboard API is not documented - or perhaps I just could not find it. So I watched a Canvas session in the browser and found the API is:

GET /api/v1/dashboard/dashboard_cards

So I made a test program to get all of my cards and make a spreadsheet of them, see at GitHub - gqmaguirejr/Canvas-tools: Some tools for use with the Canvas LMS. 

After looking at the cards and their information it was really easy to see how to use this information to make it so that you can convert the "course_id" that is actually a nickname, short name, or original name (or a prefix or substring of it).

def course_id_from_assetString(card):
global Verbose_Flag

if len(course_id) > 7:
if course_id.startswith('course_'):
course_id=course_id.replace('course_', "", 1)
if Verbose_Flag:
print("course_id_from_assetString:: course_id={}".format(course_id))
return course_id
print("Error missing assetString for card {}".format(card))
return None

# check if the course_id is all digits, matches course code, or matches a short_name
def process_course_id_from_commandLine(course_id):
if not course_id.isdigit():
for c in cards:
# look to see if the string is a course_code
if course_id == c['courseCode']:
# check for matched against shortName
if course_id == c['shortName']:
# look for the string at start of the shortName
if c['shortName'].startswith(course_id) > 0:
print("picked the course {} based on the starting match".format(c['shortName']))
# look for the substring in the shortName
if c['shortName'].find(course_id) > 0:
print("picked the course {} based on partial match".format(c['shortName']))

# check for matched against originalName
if course_id == c['originalName']:
# look for the string at start of the shortName
if c['originalName'].startswith(course_id) > 0:
print("picked the course {} based on the starting match".format(c['shortName']))
# look for the substring in the shortName
if c['originalName'].find(course_id) > 0:
print("picked the course {} based on partial match".format(c['shortName']))

print("processing course: {0} with course_id={1}".format(c['originalName'], course_id))
return course_id

Now, hopefully, there will be a happy user as in the main program to process the first argument of the command line as a course_id you simply say:

if not course_id:
print("Unable to recognize a course_id, course code, or short name for a course in {}".format(remainder[0]))

Of course, there are probably some gotchas - but it should work better than having to look up the numeric values.

1 1 653
Community Member

You will require the following variables set up accordingly. Values such as $USERID, $COURSEID and $ASSIGNMENTID can be obtained from the API or looking at link URLs in the Canvas web interface.


>> $token = '<TOKEN>'
>> $headers = @{"Authorization"="Bearer "+$token}
>> $userId = 123
>> $asUserId = 456
>> $courseId = 789
>> $assignmentId = 101112
>> $fileName = 'submission.bmp'
>> $filePath = 'c:\submission.bmp'
>> $fileContentType = 'image/bmp' 


Step 1 - Initiate the assignment submission file upload process.

Note that the AS_USER_ID parameter is attached here to the URI to enable masquerading (otherwise you cannot upload a file to another user's account.)


>> $response = Invoke-RestMethod `
   -URI   "https://<HOST_NAME>:443/api/v1/courses/$courseId/assignments/$assignmentId/submissions/$userId/files?as_user_id=$asUserId" `
   -headers $headers `
   -method POST 


We obtain an upload URI from the $RESPONSE object.

>> $uploadUri = $response.upload_url


This upload URI has a life span of 30 minutes, and cannot be used after timeout. The response content contains a list of parameters called UPLOAD_PARAMS which should be included in the POST submission body along with the file data when the file is subsequently uploaded. For our school, these parameters are FILENAME and CONTENT_TYPE.


Step 2 - Construct a hashmap which includes the file to be uploaded, along with the file parameters specified in the response above. This hashmap is passed to the Invoke-RestMethod powershell command which sends the file as part of a form submission.

>> $form = @{

   filename = $fileName
   content_type = $fileContentType
   file = Get-Item -Path $filePath

>> $response = Invoke-RestMethod `
   -URI $uploadUri `
   -Method POST `
   -Form $form

>> Write-Host "$($response.size) bytes uploaded."




Step 3 - Associate the uploaded file with an assignment submission. The $RESPONSE object returned by the previous API call conveniently contains the ID of the file which was just uploaded. We create a $BODY hashmap which is then submitted as POST parameters to associate the assignment submission with the uploaded file. 


Note the braces "[]" which must be included after the "[file_ids]" parameter name.

>> $body = @{


>> $response = invoke-restmethod `
   -uri "https://<HOSTNAME>:443/api/v1/courses/$courseId/assignments/$assignmentId/submissions" `
   -headers $headers `
   -method POST `
   -body $body `
   -ContentType "multipart/form-data"


If no errors occur (these can be handled with TRY/CATCH) then the submission process has completed successfully. 

The $RESPONSE object returned by the previous call does contain values which might also be tested to determine if the submission has completed successfully (e.g. workflow_state='submitted') but I haven't yet encountered a scenario where a submission would fail without throwing a catchable error. 

1 0 621
New Member

I've developed a tool I wanted to share here.  I teach multiple sections of a course with up to 72 students per section.  I typically merge all sections of my course into a single canvas site.  This works well for most things, but grouping 360 students into 60 teams manually is a nightmare.  The GUI for team management is an atrocious mess.

I've written a Google Apps Script in JavaScript that uses a Google Spreadsheet as the GUI.  With this tool, you can download your canvas roster, and then you can upload all teams automatically using another sheet.  

I wrote the tool to work with CATME team formation, specifically.  But, so long as the 'CATME Import' sheet contains the student email and team name, it should work fine for manual team formation.

This is my first stab at API coding, and so there are bound to be lots of errors and bugs.  For one, I don't have the OAuth2 worked out, so this version uses a temporary token that you get from your Canvas page.  

I've put a version of the code up on GitHub - GitHub - dagray3/canvas_api_scripts: collection of Google Apps Scripts in JavaScript for working wit... 

I don't know how to share the companion google sheet with others.  But, I think it's a good, rough beta that might be of use to someone.  Hit me up with a DM if you have questions or comments about the code.  Otherwise, be awesome.

2 2 869
Community Champion

In a Canvas course, you can quickly check the number of missing assignments for single students relatively quickly. You can also message groups of students missing specific assignments from the analytics page (or the gradebook). What you can't do is get a list of all students in a course and their missing assignments in a CSV for quick analysis.

In my never-ending exploration of the Canvas API, I've got a Python script that creates a missing assignments report for a course, broken down by section.


I have my own specific thoughts about using the "missing" flag to communicate with students about work. The bigger picture is that while we're distance learning, it's helpful to be able to get a birds-eye view of the entire course in terms of assignment submission. We also have enlisted building principals to help check in on progress and having this report available is helpful for their lookup purposes.

The Script

from canvasapi import Canvas # pip install canvasapi
import csv
import concurrent.futures
from functools import partial

KEY = '' # Your Canvas API key
URL = '' # Your Canvas API URL
COURSE = '' # Your course ID

canvas = Canvas(URL, KEY)
course = canvas.get_course(COURSE)
assignments = len(list(course.get_assignments()))
writer = csv.writer(open('report.csv', 'w'))

def main():
    sections = course.get_sections()

    writer.writerow(['Name', 'Building', 'Last Activity', 'Complete', 'Missing'])

    for section in sections:
        enrollments = section.get_enrollments(state="active", type="StudentEnrollment")
        # Play with the number of workers.
        with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
            data = []
            job = partial(process_user, section=section)

            results = [executor.submit(job, enrollment) for enrollment in enrollments]
            for f in concurrent.futures.as_completed(results):
                print(f'Processed {len(data)} in {len(list(enrollments))} at {section}')

def process_user(enrollment, section):
    missing = get_user_missing(section, enrollment.user['id'])
    return [ 
        len(missing), ', '.join(missing)

def get_user_missing(section, user_id):
    submissions = section.get_multiple_submissions(student_ids=[user_id], 
                                                   include=["assignment", "submission_history"], 

    missing_list = [item.assignment['name'] for item in submissions \
        if item.workflow_state == "unsubmitted" and item.excused is not True]

    return missing_list

if __name__ == "__main__":


How does it work?

The script uses UCF's canvasapi library to handle all of the endpoints. Make sure to pip install before you try to run the script. The Canvas object makes it easy to pass course and section references around for processing. Because each student has to be individually looked up, it uses multiple threads to speed up. There isn't much compute, just API calls and data wrangling, so multithreading worked better than multiprocessing.

For each section, the script calls for each students' submissions, looking for workflow_state="unsubmitted" specifically to handle filtering on the Canvas servers. From this filtered list, it creates a final list by checking the submission history and any excused flags. A list is then returned to the main worker and the section is written as a whole to keep the processes thread-safe.

When the script is finished, you'll have a CSV report on your filesystem (in the same directory as the script itself) that you can use.



Currently, missing assignments are joined as a single string in the final cell, so those could be broken out into individual columns. I found that the resulting sheet is nicer when the number of columns is consistent, but there could be some additional processing added to sort assignments by name to keep order similar.

Canvas is also implementing GraphQL endpoints so you can request specific bits of data. The REST endpoints are helpful, but you get a lot of data back. Cleaning up the number of bytes of return data will also help it run faster.

4 1 1,086