chriscas
Community Coach
Community Coach

Hello Developers Group,

I am creating this blog as an area where we can share links to code repositories and projects with each other, as we discussed in the developers meetup yesterday.  If you have a project website, GitHub, etc that others might find useful, please share it as a reply here to this blog.

I will share my own GitHub repository to get us started.  At the time of posting (March 1, 2024), I know I need to clean up some things in there, but I'm willing to share now.

My repository is mostly comprised of small JavaScript customization tweaks to the Canvas web UI.  Some highlights include:

  • course_settings-customize_apps_text - Customize the text found on the external apps tab of course. settings.
  • dashboard-add_all_courses_link - Add an "All courses" link to the top of the dashboard.
  • syllabus-add_policy_information - Add some text to all course syllabus pages.
  • user_profile-disable_default_email_change - Prevent users from changing their default email address.

It also includes a couple python scripts:

  • update_lti_parameters - Modify existing LTI configurations via the Canvas API
  • course_navigation_tav_clone - For all courses in a given account, clones the position of one LTI tool to another.  This was mainly created to maintain course integrity for faculty when changing from an LTI 1.1 tool to an LTI 1.3 tool

I'm sure some of you have much larger projects than whet I have been able to create.  I'm interested to see what others have created and see some examples of how the those projects are done.

-Chris

more
3 1 126
agschmid
Community Contributor

Years ago I used the API to find which LTI tools were used in a course, then switched to CD1 and now I'm using CD2. 

I built a Tableau report with several filters, including:

  • Type of course: sis, non-sis or all
  • State of the course: published, unpublished or all
Read more...

more
3 0 218
BenjaminSelby
Community Participant

I created a blog post in 2020 explaining how to bulk load and sync Canvas Avatar images using Powershell.

https://community.canvaslms.com/t5/Canvas-Developers-Group/How-to-bulk-load-and-update-avatar-profil...

Since then, I've refined the code quite substantially. Here is a new version which is probably slightly more user friendly and easier to manage. 

Please note I've cobbled this together from code modules which I maintain in our environment. I've copy/pasted most of the function calls which would be in our Canvas.psm1 code module. I think the script should work fine, but there may be a few bugs due to the fact that the format presented here isn't exactly how we use it at our school. I'm just sharing it in a user-friendly format. 

Quite a bit of the information in my original blog post (https://community.canvaslms.com/t5/Canvas-Developers-Group/How-to-bulk-load-and-update-avatar-profil...) is probably still relevant, so it might pay to have a quick read of that before trying to integrate this script in your IT environment. 

My original version had a lot of processing concerned with checking whether or not a sync was required, and image lifetime checks etc. This one is simpler - it just does a complete sync of all user images, and over-writes any existing images it finds. So, you could run this once or twice a week and just clobber anything which has been uploaded in the past. 

Please note that this process DELETES each user's profile pictures folder every time it runs, then re-creates it when it uploads a new image. I found that this is the only reliable way of over-writing an image with the same name where the actual image might have changed. There might be a better way to do this, if I find one I'll modify this code to improve it. 

If anyone has questions, please feel free to ask. Or let me know how you get on with implementing this in your environment. 

 


$SCRIPT:canvasApiToken                  = "<YOUR_TOKEN_HERE>" 
$SCRIPT:canvasApiHeader                 = @{"Authorization"="Bearer " + $SCRIPT:CanvasApiToken}
$SCRIPT:canvasApiUrl                    = 'https://<YOUR_DOMAIN>.instructure.com:443/api/v1'
$SCRIPT:imageFolder                     = "<PATH_TO_IMAGE_FOLDER>"
$SCRIPT:imageFileExtension              = "jpg"
$SCRIPT:avatarUrlTemplate               = 'https://<YOUR_DOMAIN>.instructure.com.images/thumbnails/.+/.+'
$SCRIPT:avatarUrlPath                   = 'https://<YOUR_DOMAIN>.instructure.com/images/thumbnails/'
$SCRIPT:avatarDefaultUrl                = 'https://<YOUR_DOMAIN>.instructure.com/images/messages/avatar-50.png'
$SCRIPT:validAvatarUrlPattern           = "^https://<YOUR_DOMAIN>.instructure.com.images/thumbnails/\d+/{0}$"
$SCRIPT:canvasProfileFolderName         = 'profile pictures'
$SCRIPT:canvasProfileFolderParentPath   = 'my files'
$SCRIPT:canvasProfileFolderPath         = '{0}/{1}' -f $SCRIPT:canvasProfileFolderParentPath, $SCRIPT:canvasProfileFolderName
$SCRIPT:imageFileContentType            = 'image/jpeg'



###########################################################################
# FUNCTIONS
###########################################################################


Function Invoke-CanvasApiRequest (
    $uri,
    $method = 'GET',
    $contentType,
    $inFile,
    $body,
    $form
) {
    <#
    .PARAMETER $uri
        The api end point URI. Note that this should NOT include the URI base e.g. server name + version.
        In other words, it should look like this: '/accounts/1/roles/'
    #>
    if($uri[0] -NE '/') { $uri = '/{0}' -f $uri}
    $uri = '{0}{1}' -f $SCRIPT:canvasApiUrl, $uri

    $params = @{
        Uri             = $uri
        Method          = $method
        Headers         = $SCRIPT:canvasApiHeader
        FollowRelLink   = $true
    }

    if(-NOT [String]::IsNullOrWhiteSpace($contentType)) { $params.ContentType   = $contentType}
    if(-NOT [String]::IsNullOrWhiteSpace($body))        { $params.Body          = $body}
    if(-NOT [String]::IsNullOrWhiteSpace($form))        { $params.Form          = $form}
    if(-NOT [String]::IsNullOrWhiteSpace($inFile))      { $params.InFile        = $inFile}

    Write-Debug ('{0}: {1}' -f $method.toUpper(), $uri)
    Write-Debug ('PARAMS: {0}' -f [PsCustomObject]$params) 
    if($params.keys -CONTAINS 'form' -AND $params.Form -NE $null) {
        Write-Debug ('FORM: {0}' -f [PsCustomObject]$params) 
    }

    $response = Invoke-RestMethod @params

    <# Remove pagination. #>
    $response = $response | ForEach-Object {$_}
    return $response
}


Function Get-CanvasUser(
    $canvasId
) {
    $uri    = "/users/{0}" -f $canvasId
    $user   = Invoke-CanvasApiRequest -Uri $uri
    return $user
}


Function Get-UserInfo (
    [int] $userCanvasId
) {

    $user           = Get-CanvasUser -canvasId $userCanvasId
    $imageFileName  = "$($user.sis_user_id).$imageFileExtension"
    $imageFilePath  = "$imageFolder\$imageFileName"

    Add-Member `
        -InputObject        $user `
        -NotePropertyName   ImageFileName `
        -NotePropertyValue  $imageFileName `
        -Force

    Add-Member `
        -InputObject        $user `
        -NotePropertyName   ImageFolder `
        -NotePropertyValue  $imageFolder `
        -Force

    Add-Member `
        -InputObject        $user `
        -NotePropertyName   ImageFilePath `
        -NotePropertyValue  $imageFilePath `
        -Force

    <# The information for a user returned by the Canvas API includes the URL of that 
    user's avatar image. 
    If user's avatar has a valid URL, we extract file ID and UUID from it and attach them 
    to the user object which is returned. 
    Otherwise leave these fields empty. #>
    if ($user.avatar_url -match $SCRIPT:avatarUrlTemplate) {

        $avatarFileId     = $user.avatar_url.Replace($SCRIPT:avatarUrlPath, '').split('/')[0]
        $avatarFileUuid   = $user.avatar_url.Replace($SCRIPT:avatarUrlPath, '').split('/')[1]

    } else {

        $avatarFileId     = $null
        $avatarFileUuid   = $null

    }

    Add-Member `
        -InputObject        $user `
        -NotePropertyName   AvatarFileId `
        -NotePropertyValue  $avatarFileId `
        -Force

    Add-Member `
        -InputObject        $user `
        -NotePropertyName   AvatarFileUuid `
        -NotePropertyValue  $avatarFileUuid `
        -Force
    

    return $user
}


Function Get-CanvasUserFolders (
    [int]       $userCanvasId,
    [string]    $folderName,
    [string]    $parentFolderPath
) {
    <# 
    Be careful of case sensitivity, I have a feeling that Canvas API is 
    case sensitive with folder names etc.
    May return more than one folder, caller should check and handle as appropriate. 
    #>

    $uri            = "/users/$userCanvasId/folders?as_user_id=$userCanvasId"
    $canvasFolders  = Invoke-CanvasApiRequest -uri $uri

    if(-NOT [String]::IsNullOrWhiteSpace($folderName)) {
        if(-NOT [String]::IsNullOrWhiteSpace($parentFolderPath)) {
            # Strip off any trailing slashes if user has included them in path. 
            while($parentFolderPath[$parentFolderPath.Length - 1] -IN @('/', '\') ) {
                $parentFolderPath = $parentFolderPath.Substring(0, $parentFolderPath.Length - 1)
            }
            $folderPath      = "$parentFolderPath/$folderName"
            $canvasFolders   = $canvasFolders | Where-Object {$_.Full_Name -EQ $folderPath}
        } else {
            $canvasFolders   = $canvasFolders | Where-Object {$_.Name -EQ $folderName}
        }
    }
    
    Return $canvasFolders
}


Function Send-CanvasFileUpload (
    [int]       $userCanvasId,
    [string]    $inputFileName,
    [string]    $inputFolderPath,
    [string]    $toFolderPath,    
    [string]    $contentType,
    [boolean]   $checkQuota = $true
) {

    <#
    In order to upload a file to Canvas, we first notify the Canvas system of our intention. 
    We specify the file name and size etc. in a POST request. 
    We also specify the destination folder ID that we want to upload the file to.
    If a file upload is approved, the server responds with a URI that the image may be 
    set to. This URI is temporary and will time out after 30(?) minutes.

    .PARAMETER checkQuota
        Check the user's file quota before upload? 
    .PARAMETER inputFolderPath 
        Folder on the caller's machine to search for file with $fileName. 
    .PARAMETER contentType 
        MIME type of the file. 
    #>

    # Strip off any trailing slashes if user has included them in path. 
    while($inputFolderPath[$inputFolderPath.Length - 1] -IN @('/', '\') ) {
        $inputFolderPath = $inputFolderPath.Substring(0, $inputFolderPath.Length - 1)
    }

    $inputFilePath = "$inputFolderPath\$inputFileName"

    if((Test-Path -Path $inputFilePath) -EQ $false) {
        Throw "No file found at: $inputFilePath"
    }

    $fileSizeBytes = (Get-Item $inputFilePath).Length

    IF($checkQuota) {
        $uri = "/users/$userCanvasId/files/quota?as_user_id=$userCanvasId"
        $quotaResponse = Invoke-CanvasApiRequest -uri $uri
        $quotaRemaining = $quotaResponse.quota - $quotaResponse.quota_used

        if ($quotaRemaining -LT $fileSizeBytes) {
            [string] $errorMessage = "ERROR: User does not have sufficient file quota remaining to enable upload." `
                + ("`n`tQUOTA: {0,15:n0} bytes" -f $quotaResponse.quota) `
                + ("`n`tUSED:  {0,15:n0} bytes" -f $quotaResponse.quota_used) `
                + "`nUpload failed."
            Throw $errorMessage
        }
    }


    <# If CONTENT_TYPE is omitted it should be guessed when received by the Canvas API 
    based on file extension. #>
    $notifyForm = @{
        name                = $inputFileName
        size                = $fileSizeBytes 
        content_type        = $contentType
        parent_folder_path  = $toFolderPath
    }

    $uri            = "/users/$userCanvasId/files?as_user_id=$userCanvasId"
    $notifyResponse = Invoke-CanvasApiRequest `
        -method     'POST' `
        -URI        $uri `
        -form       $notifyForm


    if ($notifyResponse -EQ $null `
            -OR [String]::IsNullOrWhiteSpace($notifyResponse.upload_url)) {
        Throw "ERROR: Could not get desination URI for file upload. Cannot send file."
    }

    <# 
    We need to use CURL here to upload the file, rather than a POST via 
    Invoke-RestMethod, because I can't figure out how to set the content-type of 
    the form member named 'File' correctly (and no, the -ContentType parameter for 
    Invoke-RestMethod doesn't work, as it is ignored when posting a Form using 
    multipart/form-data).
    Might be able to get it to work using this method, haven't tried yet:
        https://get-powershellblog.blogspot.com/2017/09/multipartform-data-support-for-invoke.html
    #>

    $curlCommand        = "curl -X POST '$($notifyResponse.upload_url)' " `
                            + "-F filename='$inputFileName' " `
                            + "-F content-type='$contentType' " `
                            + "-F file=@'$inputfilePath' "
    Write-Debug "Running CURL command: $curlCommand"
    $curlResponse       = Invoke-Expression -Command $curlCommand
    $uploadResponse     = $curlResponse | Convertfrom-Json

    # If the file has uploaded successfully, the response object will contain information about the uploaded file. 
    if ($uploadResponse -EQ $null) {
        Throw "ERROR: File upload failed for an unknown reason."
    }

    return $uploadResponse
}


Function Get-CanvasUserFiles (
    [int]       $userCanvasId,
    [string]    $searchTerm,
    [boolean]   $folderInfo = $true
) {
    <# 
    Be careful of case sensitivity, I have a feeling that Canvas API is 
    case sensitive with some/all names/paths etc.

    This returns all user files. I don't think the API has a way to limit
    the response to a file query by folder or path, so I will leave filtering 
    by folder to the calling process. 

    .PARAMETER searchTerm
        Must be 2 or more characters. 
    .PARAMETER folderInfo
        Add extra information to each file about it's parent folder. 
        May take additional time so might slow down large queries. 
    #>

    $uri            = "/users/$userCanvasId/files"
    if(-NOT [String]::IsNullOrWhiteSpace($searchTerm)) {
        $uri        += "?search_term=$searchTerm"
    }

    $files          = Invoke-CanvasApiRequest -uri $uri

    if($folderInfo){
        foreach($file in $files) {
            $uri        = "/users/$userCanvasId/folders/$($file.folder_id)"
            $folder     = Invoke-CanvasApiRequest -uri $uri
            if($folder -NE $null) {

                Add-Member `
                    -InputObject        $file `
                    -NotePropertyName   'folder_name' `
                    -NotePropertyValue  $folder.Name `
                    -Force

                Add-Member `
                    -InputObject        $file `
                    -NotePropertyName   'folder_path' `
                    -NotePropertyValue  $folder.Full_Name `
                    -Force                
            }
        }
    }
    
    Return $files
}


Function Set-CanvasUserAvatar(
    [int]       $userCanvasId,
    [string]    $fileUuid
) {
    <#
    .PARAMETER fileUuid 
        The UUID of a file which has been returned via the 'files' endpoint in the 
        Canvas API. 
    #>

    [boolean] $avatarSetResult = $false

    <# Get a list of the avatar images currently available for the user in Canvas. 
    This list will contain some default images (e.g. when nothing else is available)
    as well as any images which (I think) need to be in the 'My Files\profile pictures' 
    folder to be available. #>
    $uri                = "/users/$userCanvasId/avatars?as_user_id=$userCanvasId"
    $availableAvatars   = Invoke-CanvasApiRequest -uri $uri

    $wantAvatar = $availableAvatars `
        | Where-Object { ($_ | Get-Member -Name 'uuid') -AND $_.uuid -EQ $fileUuid}

    if ($wantAvatar -EQ $null) {
        Throw "The requested file UUID ($fileUuid) was not found as an available avatar image for user $userCanvasId."
    } 

    Write-Debug "Assigning image file with TOKEN: $($wantAvatar.token) as user avatar..."

    <# I can't get this to work with the parameters in a form submission, but it seems 
    to work fine in the url so whatever. #>
    $uri                = "/users/$($userCanvasId)?user[avatar][token]=$($wantAvatar.token)"
    $setAvatarResponse  = Invoke-CanvasApiRequest -method 'PUT' -uri $uri

    if ($setAvatarResponse.avatar_url -MATCH ($SCRIPT:validAvatarUrlPattern -f $fileUuid) ) {
        Write-Debug "OK, avatar assigned successfully."
        $avatarSetResult = $true
    } else {
        Write-Debug "ERROR: There was a problem assigning the avatar for UUID: $fileUuid."
        Write-Debug "Current avatar URL: $($setAvatarResponse.avatar_url)."
    }


    return $avatarSetResult
}


Function Sync-CanvasAvatar (
    [string]    $userCanvasId
) {

    Write-Host  "Getting info for Canvas user: $userCanvasId"
    $user       = Get-UserInfo -userCanvasId $userCanvasId
    Write-Host  "CurrentAvatar: $($user.avatar_url)"
    
    <# I have observed some problems when over-writing an image with the same name.  
    Best method to set profile image is to just delete profile pictures folder then recreate. 
    We assume that the profile pictures folder does not need to contain anything more than 
    the current profile image. #>
    $profilePicturesFolder = Get-CanvasUserFolders `
            -userCanvasId       $user.id `
            -folderName         $SCRIPT:canvasProfileFolderName `
            -parentFolderPath   $SCRIPT:canvasProfileFolderParentPath
        | Sort-Object -Top 1
    
    if ($profilePicturesFolder -NE $null) {
        $uri = "/folders/$($profilePicturesFolder.id)?force=true&as_user_id=$($user.id)"
        $deleteResponse = Invoke-CanvasApiRequest -method 'DELETE' -uri $uri
        Start-Sleep -Seconds 5
    }
    
    
    
    Write-Host "Uploading profile image file..."
    $uploadResponse = Send-CanvasFileUpload `
        -userCanvasId       $user.id `
        -inputFileName      $user.imageFileName `
        -inputFolderPath    $user.imageFolder `
        -toFolderPath       $SCRIPT:canvasProfileFolderName `
        -contentType        $SCRIPT:imageFileContentType
    
    if($uploadResponse -EQ $null -OR $uploadResponse.upload_status -NE 'success') {
        Throw "Unable to upload file for user."
    }
    
    <# Not sure but Canvas appears to have a weird thing where if you upload a file 
    which is a duplicate of an existing one, the upload response shows 'success' and a 
    new file UUID, but the old file (and its UUID) are retained. So, we have to 
    get the avatar file now explicitly rather than using the UUID in the upload_response. #>
    
    $userFiles      = Get-CanvasUserFiles -userCanvasId $user.id -searchTerm $user.imageFileName
    $avatarImage    = $userFiles `
        | Where-Object {$_.folder_path -EQ $SCRIPT:canvasProfileFolderPath `
                -AND $_.filename -EQ $user.imageFileName} `
        | Sort-Object -Unique -Top 1
    
    if($avatarImage -EQ $null) {
        Throw "Could not find valid avatar image for user $($user.name) [$($user.sis_user_id)]."
    }
    
    
    Write-Host "Assigning user's avatar..."
    $assignmentResult = Set-CanvasUserAvatar `
        -userCanvasId   $user.id `
        -fileUuid       $avatarImage.uuid
    if( $assignmentResult -EQ $false ) {
        Write-Host "ERROR: Avatar image assignment failed."
        Return 
    }

}


###########################################################################
# MAIN
###########################################################################


<# Filter out users who we don't want to include in sync. 
Insert your own filtering term here. #>
$currentUsers = Invoke-CanvasApiRequest -Uri '/accounts/1/users' `
    | Where-Object { ($_ | Get-Member -Name 'login_id') `
        -AND $_.login_id -MATCH '<YOUR_PATTERN_HERE>'}

foreach($user in $currentUsers) {
    try {
        Sync-CanvasAvatar -userCanvasId $user.id 
    } catch {
        # Catch any errors so we can continue processing users. 
        $ex = $_
        Write-Error $ex
    }
}

 

 

more
1 0 100
knowbd
Community Member

I am working to have an AI LLM (ChatGPT 3.5 or better) generate QTI 2.1 XML that I can copy-paste into text files then ZIP and upload to Canvas to create Item Banks in New Quizzes.

I have been successful in getting ChatGPT to generate a set of test questions from a raw lecture transcript and put the questions into QTI 2.1 format.  For Canvas to upload the questions successfully, however, there must be additional files in the ZIP file.  I will be working to get ChatGPT to produce code for those as well.

Not needing to reinvent the wheel, has anyone already done the R&D on this?  Success with prompts? Failure with lessons learned?  

Any insights will be appreciated.

Regards,

Keith

more
4 0 259
jerry_nguyen
Community Contributor

Sit back, relax, and let n8n do (most of) the tedious admin tasks for you.

N8N is an open-source workflow automation tool that empowers you to harness the capabilities of Canvas backend systems, including Canvas API, Canvas Data, and Live Data, to streamline and reduce your workload.

Although I posted this article in the Developers Group, you don't need to know any programming to get started. The best thing about n8n is that we can all share our workflows with each other, and I'll update this blog post with more workflows.

 

Read more...

more
4 0 532
tin900
Community Member

Hello, Canvas Community!

I'm thrilled to announce the R package called "vvcanvas". This package provides a convenient interface to interact with the Canvas Learning Management System (LMS) API, enabling users to authenticate, retrieve course information, fetch specific details, and perform various operations within the Canvas LMS.

The package can be installed from CRAN directly using:

install.packages("vvcanvas")

Alternatively, the development version can be installed from GitHub

devtools::install_github("vusaverse/vvcanvas")

 

 

How to Contribute

vvcanvas is an open-source project, and contributions from the community are highly encouraged. If you encounter any bugs, have feature requests, or would like to contribute code improvements, you can open an issue or submit a pull request on the GitHub repository.

 

 

more
1 0 254
Code-with-Ski
Community Participant

Learn about some of the new features that I built and released in v3.0.0 of the Canvas LMS Mods (Basic) Chrome extension to provide course level reports primarily about course content.  The code for the extension is open-source if you would like to view and/or adapt the source code. Canvas LMS Mods (Basic) GitHub Repository

Learn more about the available features in the GitHub repository or on my past blog post: Creating a Chrome Extension to Use with Canvas LMS

Read more...

more
3 1 463
Code-with-Ski
Community Participant

Learn about some of the new features that I built and released in v3.0.0 of the Canvas LMS Mods (Basic) Chrome extension to enhance the experience of creating/editing rubrics.  The code for the extension is open-source if you would like to view and/or adapt the source code. Canvas LMS Mods (Basic) GitHub Repository

Learn more about the available features in the GitHub repository or on my past blog post: Creating a Chrome Extension to Use with Canvas LMS

Read more...

more
2 5 737
hhchiang
Community Member

Hi all, I've deployed an LTI 1.3 tool as a Discussion Topic Menu, and when I click the menu item in my course, I get back a json result, but I can't find the Discussion Topic ID in the json results.   

Does anyone else have experience working with LTI1.3 and Discussion Topic Menu placement ?

Thanks

more
0 0 615
Code-with-Ski
Community Participant

I recently updated my Canvas LMS Mods (Basic) Chrome extension to provide some new enhancements to the course search and admin flyout menu inspired by ideas and requests I have seen from other users in the community.

Read more...

more
15 38 4,968
elena18
Community Member

It's few months that I don't access the Canvas app installed on OVH VPS server. I tried now and it shows the directory of files, but not the login interface. Nothings has been changed since the last time I use it (december 2022)

Read more...

more
0 2 962
RobbieWatling
Community Novice

Hi all,

I would like to attach a zip file to all of my students submissions. I have been able to upload the file via the Cavnas API. However, I cannot seem to get the file to attach a student's submission. Acccording to https://canvas.instructure.com/doc/api/submission_comments.html I need to "PUT" the file id to the submission API. Can someone who knows rest API better assist me? The main issue is not really understanding how to use the "parameters" for a submission comment here https://canvas.instructure.com/doc/api/submissions.html#method.submissions_api.update 

 

I've attached a portion of my script below, but a linux command would be sufficient too.

 

REQUIREMENTS = """
certifi==2018.11.29
chardet==3.0.4
idna==2.8
python-magic==0.4.15
requests==2.21.0
urllib3==1.24.1
"""

# https://stackoverflow.com/a/44873382
def sha256sum(filename):
    h = hashlib.sha256()
    b = bytearray(128 * 1024)
    mv = memoryview(b)
    with open(filename, "rb", buffering=0) as f:
        for n in iter(lambda: f.readinto(mv), 0):
            h.update(mv[:n])
    return h.hexdigest()


# https://stackoverflow.com/a/16696317 with tweaks
def download_file(url, f):
    # NOTE the stream=True parameter below
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        for chunk in r.iter_content(chunk_size=8192):
            if chunk:  # filter out keep-alive new chunks
                f.write(chunk)
    # possibly unneeded.
    f.flush()


def add_custom_site_packages_directory(raise_if_failure=True):
    digest = hashlib.sha256(REQUIREMENTS.encode("utf8")).hexdigest()
    dep_root = os.path.join(gettempdir(), "pyallinone_{}".format(digest))
    os.makedirs(dep_root, exist_ok=True)

    for dirpath, dirnames, filenames in os.walk(dep_root):
        if dirpath.endswith(os.path.sep + "site-packages"):
            # that's our dir!
            sys.path.insert(0, os.path.abspath(dirpath))
            return dep_root

    if raise_if_failure:
        raise ValueError("could not find our site-packages dir")

    return dep_root


dep_root = add_custom_site_packages_directory(False)

deps_installed = False

while True:
    try:
        import requests
        import magic

        break
    except ImportError:
        if deps_installed:
            raise ValueError("Something was broken, could not install dependencies")
        try:
            from pip import main as pipmain
        except ImportError:
            from pip._internal import main as pipmain

        with NamedTemporaryFile() as req:
            req.write(REQUIREMENTS.encode("utf-8"))
            req.flush()
            pipmain(
                [
                    "install",
                    "--prefix",
                    dep_root,
                    "--upgrade",
                    "--no-cache-dir",
                    "--no-deps",
                    "-r",
                    req.name,
                ]
            )

        add_custom_site_packages_directory()
        deps_installed = True

print("Step 1...")
hashes = {}
file_ids = []
file_sizes = []
for fn in FILENAMES:
    print(
        "uploading {fn} for assignment {ASSIGNMENT_ID}".format(
            fn=fn, ASSIGNMENT_ID=ASSIGNMENT_ID
        )
    )
    with requests.post(
        CANVAS_API_BASE
        + "courses/{COURSE_ID}/assignments/{ASSIGNMENT_ID}/submissions/{SUBMISSION_ID}/comments/files".format(
            COURSE_ID=COURSE_ID, ASSIGNMENT_ID=ASSIGNMENT_ID, SUBMISSION_ID=SUBMISSION_ID
        ),
        json={
            "access_token": CANVAS_KEY,
            "name": fn,
            "size": os.stat(fn).st_size,
            "content_type": magic.from_file(fn, mime=True),
        },
    ) as r:
        r.raise_for_status()
        response = r.json()
    upload_url = response["upload_url"]
    upload_params = response["upload_params"]

    hashes[fn] = sha256sum(fn)
    with requests.post(
        upload_url, data=upload_params, files={"file": (fn, open(fn, "rb"))}
    ) as r2:
        r2.raise_for_status()
        upload_response = r2.json()

    file_ids.append(upload_response["id"])
    location = upload_response["location"]

print(file_ids)

 

more
0 1 695
SteveBarnes
Community Member

I need an access token to grab a list of line items during the LTI1.3 workflow. I am currently receiving an unsupported grant type error.

I have seen claims that Canvas does not support this flow to issue access tokens for machine to machine API use.

Link here: https://community.canvaslms.com/t5/Canvas-Developers-Group/grant-type-client-credentials/m-p/459904

more
0 0 840
RandyBennett
Community Member

I'm a teacher, and I've been playing with the api in a few other sheets that I've found. It's got me wondering if I could use a google sheet with a series of my gradebooks brought from other sheets, and have it update the grades in canvas.

Read more...

more
0 2 858
DeletedUser
Not applicable

Hi, I'm new

Read more...

more
0 1 651
SarahDaugherty
Community Member

I have a suggestion for a couple new features that I know i would use frequently. I believe other students would as well. It is efficient and time saving, also would help navigate us through what we have completed over our time in school, our past and current classes. Maybe the instructors would find it useful and a helpful added feature for them as well. 

 

Unsure of how Canvas is programed but I sure am hoping it can be tested, figured out and added into a feature update. 

Read more...

more
0 1 764
arlin_burton
Community Explorer

Hi Im a Canvas Admin, for a University

I am requesting a design feature for Canvas,

is it possible to add a points total option to appear on the Assignments modules for each category or the GradeBook total column?

I have instructors with 25 to 40 assignments and have to remind them to use a calculator to points for the assignments and Quizzes

the syllabus will read 100 points for an A but the Instructor will create multiple Quizzes and Assignments that have varying points.

see attachments

 

 

If you could just design the module to sum the total of the page points.

see attachment

 

arlin.burton

Read more...

more
0 1 1,052
WastaTariq
Community Member

Dear Canvas 

This is Wasta, I am using Canvas app which my school provides me to takes the video lecture but on my Ipad (IOS 15) generation 8 ,as I open the video there is no audio in the video , only when I start the video there is a sound but as soon as I forward it or rewind the video the audio disappears. I contacted the apple support system they said my device is working fine and everything is good to go from my side they asked me to contact the developer as this issue is specifically for canvas. 
Also I tried to take the video from safari, chrome, Firefox but I faced the same issue there as well no audio as soon as I forward or rewind the video. Please help me in fixing the problem I am so worried due to this.

Regards 

Wastatar30@gmail.com

more
0 0 843
WastaTariq
Community Member

Dear Canvas 

Dear Canvas

This is Wasta, I am using Canvas app which my school provides me to takes the video lecture but on my Ipad (IOS 15) generation 8 ,as I open the video there is no audio in the video , only when I start the video there is a sound but as soon as I forward it or rewind the video the audio disappears. I contacted the apple support system they said my device is working fine and everything is good to go from my side they asked me to contact the developer as this issue is specifically for canvas. 
Also I tried to take the video from safari, chrome, Firefox but I faced the same issue there as well no audio as soon as I forward or rewind the video 

Please help me as I am in stress due to this 

 

regards

wastatar30@gmail.com

Read more...

more
0 0 821
DominicDeSantis
Community Member

My team is trying to replicate in one of our views how Canvas presents TODOs in its own UI. The TODO items endpoint (https://canvas.instructure.com/doc/api/users.html#method.users.todo_items) appears to be the correct one to use, although it's unclear what parameters Canvas itself may be using to for its presentation. As such, we have some questions about Canvas and the API.

When Canvas presents the TODOs in its own UI, what rules/boundaries determine the list of items that are being presented:
1. Does it present the user's most recent n number of TODOs?
2. Does it present the user's TODOs for the current term?
3. Does it present all of the user's TODOs ever?
4. Something else?

Regarding the TODO items API (https://canvas.instructure.com/doc/api/users.html#method.users.todo_items), we're trying to understand some nuances of the pagination. Specifically, what is the difference between fetching the "current" vs "last" pages?

more
0 2 1,225
zapsarth
Community Member

I want to know how to use the webhook subscription api, including all the context types and such.

 

Read more...

more
0 0 1,064
zapsarth
Community Member

I need an api endpoint that returns a course image url.

Read more...

more
0 2 1,267
Code-with-Ski
Community Participant

Learn about the Chrome extension that I've been developing as a personal side project to use with Canvas LMS to add some new features and help improve workflows.  Most of the customizations so far are for the admin area of Canvas, but I am now working on adding some customizations that will add features in other areas of Canvas that could be useful to more users.

Link to Chrome Extension in the Chrome Web Store: Canvas LMS Mods (Basic)

Read more...

more
11 28 6,291
Alex1005
Community 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.

Read more...

more
0 0 980
BradHeffernan
Community 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 horizon-sa.instructure.com 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 

more
0 0 1,061
BetsyWilkinson
Community 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.

more
0 2 1,179
breilly1
Community Member

Unenroll user from a class via SIS UserID?

Read more...

more
0 2 1,315
JosephRousseau
Partner
Partner

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?

more
0 6 1,795
OliverKharasGU
Community Explorer

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,

more
0 1 1,269
RongenRobles
Community Member

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

Read more...

more
0 0 1,226