Skip navigation
All Places > Canvas Developers > Blog > Author: Gerald Q. Maguire

Canvas Developers

15 Posts authored by: Gerald Q. Maguire

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 .


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.

Gerald Q. Maguire

Creating an Index

Posted by Gerald Q. Maguire Apr 14, 2020

To follow up on my earlier question in Generating an index and permitted attributes for <span> this blog post contains some more information about generating an index from the pages in a Canvas course. A full description, script, and source code can be found under "Making an index" at GitHub - gqmaguirejr/Canvas-tools: Some tools for use with the Canvas LMS. 


Basically the process is based on creating in a local directory a copy of all of the HTML pages in a Canvas course along with some metadata about the module items in the course. Once you have the files, you can find keywords and phrases from the HTML and then construct the index or in my case a number of different indexes. I have split the process of finding keywords and phrases into two parts, the first works on the HTML files to find the strings in the various tags and stores this into a JSON formatted file - and the second part is part of the program computes the indexes. In this second program I started by splitting the text into words with a simple regular expression and then switched to using the Python NLTK package - specifically, the functions nltk.sent_tokenize(string1) nltk.word_tokenize(string2).


The resulting page (computed from ~850 HTML files) can be seen at Test page 3: Chip sandbox 


With regard to <span>, I found it useful to use them in three ways:

1. To keep a set of words together as a logical "word":

<span>Adam Smith</span> <span>Autonomous system number</span>

2. To mark text that I did not want to index:

<span class="dont-index">20% error rate</span>

3. To mark text as a reference (that I do not want to index):

<span class="inline-ref">(see Smith, Figure 10, on page 99.)</span>

Overall, the process of generating an index was useful - as I found mis-spellings, inconsistent use of various terms and capitalization, random characters that seemed to have been typos or poor alternative img descriptions, ...). It also was a nice forcing function to rework some of the content.


However, it remains a work in progress. I know that there are a number of weaknesses, such as not being careful in the final index to language tag entries and there is a need to remove some additional words that probably should not be in the index. Also, this is not a general-purpose natural language processing program - it could make better use of the NLTK package and it is very English language centric (in that it assumes the default language of the course is English, it does not pass the actual language information to the tokenization function, and it only contains stop words in English).



I find the current system of emails of newly submitted assignments to be almost worthless, as I am in a number of courses where there are large numbers of students and most of them are irrelevant from my point of view as a teacher. In these courses, sections have been created to make it easy for a teacher to view the subset of students that is actually relevant to the teacher. However, since I have a large number of such courses (i.e., more than a dozen) and students are submitting material at their own pace through these courses, it is difficult to find the wheat among the chaff of notices about submissions for each of these courses.


This motivated the design of a program to get information about just the assignment submissions that I am interested in. Of course one can easily get a list of all the courses that a user is in, but how can you know what sections within these courses a user is interested in?  The answer is to ask the user to provide this information!


The result is two programs:


The first program creates a JSON formatted file with a course_info dictionary of the form:

{"courses_to_ignore": dict_of_courses_to_ignore,

"courses_without_specific_sections": dict_of_courses_without_specific_sections,

"courses_with_sections": dict_of_courses_with_sections



courses_to_ignore are courses that the user wants to ignore
courses_without_specific_sections are courses where the user is responsible for all the students in the course
courses_with_sections are courses where the user has a specific section - the specific section's name may be the user's name (in Canvas) or some other unique string (such as "Chip's section"). Because the name of the relevant section can be arbitrary, this file is necessary to know which section belongs to a given user.


The second program reads the information from the JSON file and then prunes out the courses_to_ignore from the list of a user's courses and then uses the information from courses_without_specific_sections and courses_with_sections to iterate through the courses and looks for ungraded assignments and then for each of the relevant students (in a course or section) looks for an ungraded assignment. Currently, the program just outputs information about these assignments.


To set up the JSON file is easy, you simply run the first program and then move entries from the courses_with_sections dict to one of the other dicts (removing unnecessary or irrelevant sections as you go). You can fun the first program in update mode (with the -U flag) to add more courses - it remembers the courses you have set to be ignored and the ones you have responsibility for all the students.


The programs can be found at GitHub - gqmaguirejr/Canvas-tools: Some tools for use with the Canvas LMS. 


Of course, I discovered an assignment that had been submitted that I had not seen, so on to grading it!

For some time I have been running a local Canvas instance for development activities. This has enabled me to both peek under the covers and give a VM with a complete Canvas instance and programs that I have developed to students.


During the summer I noticed that after updating the code using the github Canvas sources that I had a flashing dashboard that would never render a static dashboard and that when I went to the assignments page I could not see the list of assignments.

When using the inspector in the browser I could see the results of the query return the JSON for the assignments in the course. However, nothing appeared.

After some looking at the page for assignments, I found that the class where I expected to see the assignments there was a div that included "hide-content-while-scripts-not-loaded" and then searching in the source code (using find) I found the following:

find . -type f -exec grep hide-content-while-scripts-not-loaded {} \; -print   @body_classes << 'hide-content-while-scripts-not-loaded' ./app/views/assignments/new_index.html.erb       @body_classes << 'hide-content-while-scripts-not-loaded' ./app/views/courses/show.html.erb   @body_classes << 'hide-content-while-scripts-not-loaded right-side-optional' ./app/views/announcements/index.html.erb   @body_classes << 'hide-content-while-scripts-not-loaded' ./app/views/discussion_topics/index.html.erb   @body_classes << "full-width no-page-block hide-content-while-scripts-not-loaded" ./app/views/calendars/show.html.erb

So this hiding of contents occurs in a number of places, but I could not find the CSS.
After a bit of searching, I found at

// This hides stuff till the javascript has done it's stuff .hide-content-while-scripts-not-loaded   #content, #right-side-wrapper     +single-transition(opacity, 0.3s)     +opacity(1) .scripts-not-loaded   #content, #right-side-wrapper     +opacity(0)

The above means that the results are purposely hidden until some javascript has been loaded.

Additionally, using the inspector in the brower I saw the following when trying to display the page for assignments for a course:

assignment_index.js:14 Uncaught (in promise) Error: Cannot find module '@instructure/js-utils'     at webpackMissingModule (assignment_index.js:14)     at eval (assignment_index.js:14)     at Module.sMe2 (assignment_index-c-9c2eac0849.js:1941)     at __webpack_require__ (main-e-a68344b004.js:64)

Going to the docker container where the webpack is built I did a yarn run webpack. In this I found:

ERROR in ./app/jsx/bundles/dashboard_card.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/app/jsx/bundles'  @ ./app/jsx/bundles/dashboard_card.js 22:0-65 40:33-39 40:40-56  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js  ERROR in ./app/jsx/bundles/assignment_index.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/app/jsx/bundles'  @ ./app/jsx/bundles/assignment_index.js 29:0-57 91:0-16  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js  ERROR in ./app/jsx/dashboard/DashboardHeader.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/app/jsx/dashboard'  @ ./app/jsx/dashboard/DashboardHeader.js 37:0-65 283:27-33 283:34-50  @ ./app/jsx/bundles/dashboard.js  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js  ERROR in ./app/jsx/discussions/apiClient.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/app/jsx/discussions'  @ ./app/jsx/discussions/apiClient.js 19:0-66 28:9-16 28:17-33  @ ./app/jsx/discussions/actions.js  @ ./app/jsx/discussions/components/DiscussionsIndex.js  @ ./app/jsx/discussions/index.js  @ ./app/jsx/bundles/discussion_topics_index_v2.js  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js

The above means that the js-utils are not found, despite the fact that this is a package as one can see from the output of the command "ls packages":

babel-preset-pretranslated-format-message canvas-planner canvas-rce canvas-supported-browsers jest-moxios-utils js-utils k5uploader old-copy-of-react-14-that-is-just-here-so-if-analytics-is-checked-out-it-doesnt-change-yarn.lock

Similar to the solution in posting (Links to an external site.) 

The solution is to add in the docker-compose.override.yml file the following to the services -> jobs -> volumes key :
- js-utils:/usr/src/app/packages/js-utils

and then to the volumes key father down the file add this::
js-utils: {}

This fixes the problems with dashboard and assignments!

I also notice that another module ('canvas-planner') that is in packages also has problems during the yarn run webpack:

ERROR in ./packages/canvas-planner/lib/actions/index.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/packages/canvas-planner/lib/actions'  @ ./packages/canvas-planner/lib/actions/index.js 22:0-66 101:18-25 101:26-42  @ ./packages/canvas-planner/lib/index.js  @ ./app/jsx/dashboard/DashboardHeader.js  @ ./app/jsx/bundles/dashboard.js  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js  ERROR in ./packages/canvas-planner/lib/actions/loading-actions.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/packages/canvas-planner/lib/actions'  @ ./packages/canvas-planner/lib/actions/loading-actions.js 24:0-66 82:18-25 82:26-42 158:16-23 158:24-40  @ ./packages/canvas-planner/lib/actions/index.js  @ ./packages/canvas-planner/lib/index.js  @ ./app/jsx/dashboard/DashboardHeader.js  @ ./app/jsx/bundles/dashboard.js  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js

My hypothesis is that a similar approach can be used to solve this problem. However, since the output of the yarn run webpack also show the following (edited to reduce the mass of output)

ERROR in ./packages/canvas-planner/lib/actions/loading-actions.js Module not found: Error: Can't resolve '@instructure/js-utils' in '/usr/src/app/packages/canvas-planner/lib/actions'  @ ./packages/canvas-planner/lib/actions/loading-actions.js 24:0-66 82:18-25 82:26-42 158:16-23 158:24-40  @ ./packages/canvas-planner/lib/actions/index.js  @ ./packages/canvas-planner/lib/index.js  @ ./app/jsx/dashboard/DashboardHeader.js  @ ./app/jsx/bundles/dashboard.js  @ ./node_modules/bundles-generated.js  @ ./app/jsx/main.js  ERROR in ./app/coffeescripts/media_comments/js_uploader.js Module not found: Error: Can't resolve '@instructure/k5uploader' in '/usr/src/app/app/coffeescripts/media_comments'  @ ./app/coffeescripts/media_comments/js_uploader.js 21:0-49 106:26-36 123:26-36  @ ./public/javascripts/media_comments.js  @ ./app/jsx/runOnEveryPageButDontBlockAnythingElse.js  @ ./app/jsx/main.js  ERROR in ./packages/canvas-rce/lib/bridge/Bridge.js Module not found: Error: Can't resolve '@instructure/k5uploader' in '/usr/src/app/packages/canvas-rce/lib/bridge'  @ ./packages/canvas-rce/lib/bridge/Bridge.js 21:0-49 69:38-48 ...  ERROR in ./packages/canvas-rce/lib/rce/ResizeHandle.js Module not found: Error: Can't resolve 'react-draggable' in '/usr/src/app/packages/canvas-rce/lib/rce'  @ ./packages/canvas-rce/lib/rce/ResizeHandle.js 22:0-48 65:27-40 ...    ModuleDependencyWarning: "export 'passthroughProps' was not found in '@instructure/ui-react-utils' ... ,   ModuleDependencyWarning: "export 'passthroughProps' was not found in '@instructure/ui-react-utils' ...  ]  98% after emitting SizeLimitsPlugin[ ModuleDependencyWarning: "export 'addInputModeListener' was not found in '@instructure/ui-dom-utils' ...,   ModuleDependencyWarning: "export 'passthroughProps' was not found in '@instructure/ui-react-utils' ...,   ModuleDependencyWarning: "export 'passthroughProps' was not found in '@instructure/ui-react-utils' ...

It makes me curious as to why all of these missing files include the path "@instructure". Is there some error in the configuration that leads to the packages not being found (despite the fact that doing a "yarn list" showed that "@instructure/js-utils" was installed)?.


I should note that I am a novice with respect to Javascript - so some of the problems might be operator error, but the Canvas source code was freshly installed via the quick start update script.

A couple of days ago I decided to re-examine an issue that has annoyed me several times, the lack of a Question Bank API. The process began with some postings to followup the question raised by Jared Chapman in


This lead to some Tampermoney scripts (such as that enabled me to add question banks. This success lead to a desire for further automation and this lead me to investigate the combination of Puppeteer ( and Node to run Javascripts to create a question bank- the first result was Using Puppeteer to insert new question bank: Chip sandbox. After seeing what Puppeteer could do I expanded upon the program to have a program that would not only let me add question banks but would also output a JSON file of the resulting set of question banks (which of course could then be used with the Canvas API to insert questions into question banks!). The resulting program is documented at


Some obvious programs that could be derived from this last script would be:

  • to read a JSON file or CSV file that contains a list of question banks that one wants to create along with the course_id to create them in
  • a program to simply output the JSON for the existing question banks in a course

Of course, the real solution is to add a Question bank API. Meanwhile, quite a lot can be done despite the lack of such an API.


Once again I wish to thank James Jones and the many others who have provided examples of JavaScript and Puppeteer scripts. I still do not know how to use Puppeteer (well) and lack confidence in navigating DOM structures (especially of pages that have elements that lack IDs or that dynamically modify the Javascript on a page).

New FERPA requirements for cross-listed courses! and others have commented on the problems of cross listing. However, the ability to do cross-listing is controlled by the same permission as being able to create/edit/delete sections. I find this to be odd. I think that the ability to cross-list should not be tied to the ability to utilize sections within a course.


As the app/controllers/sections_controller.rb has the following code (with the relevant line highlighted):

  # @API Cross-list a Section
  # Move the Section to another course.  The new course may be in a different account (department),
  # but must belong to the same root account (institution).
  # @returns Section
  def crosslist
    @new_course = api_find(@section.root_account.all_courses.not_deleted, params[:new_course_id])
    if authorized_action(@section, @current_user, :update) && authorized_action(@new_course, @current_user, :manage)   
      @section.crosslist_to_course(@new_course, updating_user: @current_user)
      respond_to do |format|
        flash[:notice] = t('section_crosslisted', "Section successfully cross-listed!")
        format.html { redirect_to named_context_url(@new_course, :context_section_url, }
        format.json { render :json => (api_request? ? section_json(@section, @current_user, session, []) : @section) }

The check for authorized actions means that:

  • Unless the current user has the ability to update sections, the cross listing will not occur.
  • Unless the current user can manage the target course otherwise, the cross-listing will not occur.

Unfortunately, cross-listing provides a sneak path to add students to a course (as a teacher without administrative rights, but with the ability to create sections does a cross-listing - the students will be added to the target course - despite the fact that teacher does not have rights to add students to the course).


Moreover, if the current user can manually add students to the target course, then they can always add each of the students from a section to the target course. This means that there needs to be a check in the above code on whether the current user can enroll students in the target course. 



Therefore, the second test should be something similar to:

authorized_action(@new_course, @current_user, :manage)  && authorized_action(@new_course, @current_user, :manage_students) 


Where (based on looking at ./app/models/role_override.rb and spec/apis/v1/enrollments_api_spec.rb) :manage_students would enable the creation of student enrollments. Thus unless the user is allowed to enroll students in the target course, the cross-listing would not occur.


If the permission to add students ( permission: Users - add / remove students in courses) to imported courses (i.e., courses that are automatically created by SIS  imports) is disabled for the "Teacher" role, there should not be a problem in allowing teachers to create/edit/delete sections - while still meeting FERPA and other similar regulations (as there would not be any ability to cross-list a section worth of students and each section would only be within a single course). In fact, the use of sections could be used to reduce the visibility and interactions of students (and even teachers) to within their section, thus advancing student's privacy.

During 2019 I have been trying to use Canvas to help support the degree project process (for students, faculty, and administrators). One of the latest parts of this effort has been to look at some of the administrative decisions and actions that occur at the start of the process. A document about this can be found at (a PDF is attached). The code can be found in SinatraTest21.rb at This code makes use of user custom data in conjunction with a dynamic survey (realized via an external LTI tool) and the administrative decision and action part of the process utilizes custom columns in the gradebook and automatically creating sections and adding a given student to the relevant section.


The Ruby code in LTI tool uses a token to access the Canvas API from the LTI tool to put values into the custom columns in the gradebook - this is probably not the best approach, but worked for the purpose of this prototype.

Two commonly used grading schemes in Sweden are A-F (i.e., A, B, C, D, E, and F) and P/F (i.e., Pass and Fail) together with Fx (an incomplete - this is not a final grade). To install these grading schemes into a Canvas course or account I wrote a simple python program: (available at


Note that the numeric values associated with the letter grades do not matter, as it is only the letter grade that will be recorded in the central grade register (called Ladok).


While we have been using Canvas as an LMS for several years, we still did not have such a grading scale available at the account level, so I made a program to make it easy to insert these schemes for a course.

In conjunction with experiments concerning using an LTI interface to realize a dynamic quiz, I built a version of Canvas that could run in a Virtual Machine (specifically an image that could be run by VirtualBox).

Advantages of this approach include:

  • easy to try things without any risk to the real Canvas infrastructures
  • easy to give an OVA image of the VM to students

The image was built using an ubuntu version of linux by following the Quick Start instructions at

Details of the operation of the different containers that comprise this system will be described in a forthcoming Bachelor's thesis.

I should note that when I do the docker-compose to bring up the Canvas instance, it takes a very long time.

In order to do some experiments with the dynamic quiz, I created some fake users and enrolled them in a course. Additionally, since one of the things that I would like the dynamic quiz to exploit is knowledge of what program of study these users are in I augments the user's custom data with details of what program they are in.

The initial version of the program ( can be found at .

The result is a course with a set of fake users as can be seen in the list of user's in this course:

List of users in the course

An example of fake program data is:

custom data for user Ann FakeStudent is {'data': {'programs': [{'code': 'CINTE', 'name': 'Degree Programme in Information and Communication Technology', 'start': 2016}]}}


Some further details about the above can be found at Creating fake users and enrolling them into a course: Chip sandbox 

When faced with having to assign peer reviewing for multiple assignments, I realized that I needed a tool to copy peer reviewing assignments from one assignment to another. Then in a course with multiple assignments and multiple instructors (not all of whom were synchronized in making their peer reviewing assignment), I modified this program to allow the copying on a section basis (i.e., one can specify that only the peer reviewing assignment for a user in a given section is to be copied).

The results and the programs to do the above are at Copying peer reviewing assignments: Chip sandbox 

The current quiz API seems to be missing functionality to insert HTML answers, as one can only set an answer_text and not an answer_html.  This is despite the fact that the actual answers support an html element, as can be seen below:

"answers": [ { "id": 3603, "text": "answer1", "html": "", "comments": "", "comments_html": "", "weight": 0 }, { "id": 3070, "text": "answer2", "html": "", "comments": "", "comments_html": "", "weight": 100 }, ... ]


It is possible via the RCE GUI to cut the text and then switch to HTML mode and past it in. As shown in the example below:

[{'text': '', 'id': 2400, 'html': '<pre>sum(5,10)</pre>', 'weight': 0.0, 'comments_html': '', 'comments': ''}, {'text': '', 'id': 7243, 'html': '<pre>from math import *<br>sum(5,10,0)</pre>', 'weight': 0.0, 'comments_html': '', 'comments': ''}, {'text': '', 'id': 7766, 'html': '<pre>from math import<br>sum(5,10)</pre>', 'weight': 0.0, 'comments_html': '', 'comments': ''}, {'text': '', 'id': 5120, 'html': '<pre>from math import *<br>sum(5,10)</pre>', 'weight': 100.0, 'comments_html': '', 'comments': ''}]


I think that it should always be possible to set values via the API as opposed to having to use the GUI.  While it might take only a few minutes to do this for a single question, when there are large numbers of questions - this is when it should be possible to apply power tools.


[I am aware that there will be a new quiz engine, but as I do not have it - I have to work with what I have.]

I am a big fan of sections and have found that they are useful in a number of contexts. However, I really hate to use point and click user interfaces, so I decided it was time to make some programs to do the work for me.

This lead to a series of programs:

1. to list the existing section information (produces a spreadsheet with two pages: Sections and Students)

2. to create new sections by name

3. to remove sections that I no longer want

4. the real goal, it takes a course_id, spreadsheet file (such as an augmented version of the one produced by the first program), and a column name in this spreadsheet and adds then each student to the named section.


See Sections in courses: Chip sandbox 


An obvious extension is to randomly assign students to a set of N sections (with balancing over the set of sections). For example, this could be used to form sections for the purpose of assigning each section a different version of a quiz.

I just came from a workshop on "Language and Accessibility" (as a follow-upp to a seminar that we had a month ago on the topic - the slides from the seminar are available (in Swedish) from ). One of the issues that came up is providing users with dyslexia with material presented in the Dyslexie font ( Dyslexie Font: The dyslexia font which eases the reading  ). Has anyone looked at being able to set a particular CSS file to be used for the user's Canvas UI based upon a user's profile? I imagine this to be an extension of the fact that the user can choose their UI language (for example, as commonly used here English or Swedish, based upon their user settings (as stored in the user's 'locale' attribute' in the format described in RFC 5646)). One approach would be to encode this desire for a special font in a subtag for the language and another would be to actually add a custom CSS attribute to the user object. Has anyone done anything like this?


Having an attribute that specifies a custom CSS file would probably be cleaner but would require a new field in the object. An interesting side benefit of the having both the ability to specify a custom CSS and font would be that one could have multiple language pages (ala Using tabs for content in three languages: Chip sandbox ) where the user's choice of language would the automatically the selected visible alternative.


Note that in the above when referring to a "custom CSS file" I am thinking in terms of choosing one from a set of files for the site, as opposed to having each user able to upload their own CSS file. 

I have been encouraged to make the contents of my sandbox more visible, it can be found at  One of my goals has been to be able to use software to interact with Canvas, rather than being limited to cut and paste with RCE. Another of my goals has been to use programs to move content out of various systems that we are currently using and into Canvas. Another goal has been to move content in the other direction to other external systems.

My sandbox contains a lot of examples of things that I have been experimenting with, including computing readability statistics for the page of a course, adding citations to pages (via Zotero) using an RCE buttom to call on an LTI tool provider [a work in progress], and today's exporting of a spreadsheet of all assignments for a course (using Python's Pandas). I'm eager for feedback and opinions on this material.