Kolibri Documentation

Kolibri Documentation
Release 0.6.2
Learning Equality
Feb 02, 2018
Contents
1
User Guide
1
2
Developer Guide
3
Release Notes
127
4
Contributing
135
5
Credits
143
6
Kolibri
145
7
What is Kolibri?
147
8
How can I use it?
149
9
How can I contribute?
151
61
i
ii
CHAPTER
1
User Guide
1.1 Install Kolibri
Warning: Kolibri is not released yet, so these guides are Work in Progress !
To install Kolibri, check the system requirements first and then follow the procedure for the operating system on your
device.
1.1.1 System requirements
Operating systems
• Windows Vista, 7, 8.1, 10
• (planned) Linux: Any system with Python 3.4
• (planned) Mac OSX 10.9, 10.10 and 10.11
• (planned) Debian/Raspberry Pi packages: Wheezy or later
• (planned) Ubuntu packages: 14.04, 15.10, 16.04 - anything that’s not end-of-life.
Limited support
TODO
Supported Browsers
• IE10+, Microsoft Edge
• Firefox
• Chrome
1
Kolibri Documentation, Release 0.6.2
• (planned) Safari
• (planned) Epiphany on Raspberry Pi
• (planned) others on Android and iOS.
Known issues:
TODO
Video playback
Videos are MP4 encoded. On Ubuntu/Debian systems, install the Ubuntu restricted extras package.
Hardware requirements
TODO - REVIEW this whole section
Clients
Very old desktops and very low-power computers can be used as client devices to access Kolibri. For instance, some
deployments are known to use first-gen Raspberry Pi as desktop computers.
It is always a good idea to do a practical test, but when you want to deploy Kolibri, usually it’s not necessary to scale
your hardware. The main concern is that your system needs a video card and driver that can play the videos.
Servers
Kolibri hardware requirements as a server are next to nothing.
• 256 MB
• 500 MHz CPU
• Hard drive space depends on the size of the content channels you intend to import into Kolibri
If you have a center with less than 30 computers, a device as simple as a Raspberry Pi is known to work fine as a
server.
TODO - REVIEW with RPi package reqs if necessary
Note: In case you are deploying on Linux and want an efficient setup, use the kolibri-raspberry-pi package,
it doesn’t require a specific architecture, but it’s required to use if you deploy on a system with specs equivalent to or
smaller than Raspberry Pi.
1.1.2 Windows
To install or uninstall Kolibri on Windows, follow these steps.
2
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Install
1. Download the Kolibri :url-windows-installer:‘Windows installer <>‘.
2. Double-click the downloaded .exe file.
3. Select the language for the installation.
4. Follow the instructions in the installation wizard window.
5. Once the installation finishes, Kolibri will auto-start and open in the default browser on your computer.
6. Proceed with the initial_setup of your facility.
Warning: Windows firewall will prompt you to allow the Python process needed to run Kolibri. Click Allow
access to accept and proceed.
TODO - Update links for the installer.
Uninstall
1. Open the Windows Control Panel.
2. Select Programs and Features option.
3. Select Kolibri from the list of programs.
4. Click the button Uninstall/Change and follow the instructions.
Upgrade
To upgrade Kolibri, follow these steps.
1. Download the new version of Kolibri.
2. Double-click the downloaded .exe file.
3. Follow the instructions in the installation wizard window.
4. Once the installation of the upgrade is finished, Kolibri will auto-start and open in the default browser on your
computer.
5. Go explore the new and improved Kolibri features!
1.1.3 Linux
Warning: Linux installer is not yet available!
Install
1. Download the Kolibri :url-deb-installer:‘Linux installer <>‘.
2. Run the command:
1.1. Install Kolibri
3
Kolibri Documentation, Release 0.6.2
sudo dpkg -i kolibri-installer-filename.deb
1. Follow the instructions in the installation wizard window.
2. Once you have Kolibri installed on your system, proceed with the initial_setup of your facility.
TODO - Update links for the installer, and review the system service options.
Uninstall
• Open up Software on Ubuntu and locate the Kolibri. Press Remove.
OR
• Use apt-get remove <name of package>. You need to know the name of the package you installed,
most probably kolibri.
Upgrade
TODO - Review
To upgrade Kolibri, follow these steps.
1. Download the new version of Kolibri.
2. Start the installer.
3. Follow the instructions in the installation wizard window.
4. Once the installation of the upgrade is finished, Kolibri will auto-start and open in the default browser on your
computer.
5. Go explore the new and improved Kolibri features!
1.1.4 OSX
Warning: OSX installer is not yet available!
Install
1. Download the Kolibri :url-osx-installer:‘OSX installer <>‘.
2. Double-click the downloaded .pkg file.
3. Follow the instructions in the installation wizard window.
4. Once you have Kolibri installed on your system, proceed with the initial_setup of your facility.
Uninstall
TODO
4
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Upgrade
TODO - Review
To upgrade Kolibri, follow these steps.
1. Download the new version of Kolibri.
2. Start the installer.
3. Follow the instructions in the installation wizard window.
4. Once the installation of the upgrade is finished, Kolibri will auto-start and open in the default browser on your
computer.
5. Go explore the new and improved Kolibri features!
1.1.5 Android
Warning: Final Android installer is not yet available!
Install
1. Allow the installation of Kolibri on your Android device.
Warning: This beta version of Kolibri Android installer is not yet available on Play Store, and
in order to install it, you need to change a security setting (these steps may be slightly different
depending on the device model and Android version):
(a) Open your device’s Settings app.
(b) Under Personal, tap Security.
(c) Under Device administration, tap Unknown sources.
2. Download the Kolibri :url-android-installer:‘Android installer <>‘ (*.apk file).
3. Tap the downloaded .apk file.
4. Wait until the installation finishes.
5. Once you have Kolibri installed, tap Open and proceed with the initial_setup of your facility.
TODO - Update links for the installer.
Uninstall
TODO - Review
1. Open your device’s Settings app.
2. Under Apps, tap Kolibri.
3. Tap Uninstall button.
1.1. Install Kolibri
5
Kolibri Documentation, Release 0.6.2
Upgrade
TODO - Review
To upgrade Kolibri, follow these steps.
1. Uninstall the previous version.
2. Download the new version of Kolibri.
3. Tap the downloaded .apk file.
4. Wait until the installation finishes.
5. Tap Open and go explore the new and improved Kolibri features!
1.1.6 Advanced installation options
Warning: Advanced installation options are not yet available!
Generic installation (pip install)
Once Kolibri is released, you may install it as a standard package from PyPi using this command:
$ pip install kolibri --pre
Debian/Ubuntu: Subscribe to updates through a PPA
TODO - REVIEW this whole section once PPA is ready
We maintain a PPA on Launchpad and if you are connected to the internet, this will also give you automatic updates.
On Ubuntu, do this:
sudo
sudo
...
sudo
sudo
apt-get install software-properties-common python-software-properties
su -c 'echo "deb http://ppa.launchpad.net/learningequality/kolibri" >
apt-get update
apt-get install kolibri
Raspberry Pi
TODO - once RPi deb is ready
1.1.7 Initial Setup
To do the initial setup of after the installation, follow these steps.
Note: You need to do the initial setup only once, the first time you start Kolibri after the installation.
1. Select the default language for Kolibri.
6
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Fig. 1.2: Select the default Kolibri language.
Tip: You can change the user interface language later, from the user menu in the upper right corner.
1. Name your facility. A Facility is the location where you installed Kolibri, such as a school or a training center.
2. Create the Admin account. This Admin user will be a Super User able to manage all the device content, and
all the rest of the facility users and their permisions.
3. Choose a Facility setup. Click the Setup details for more information about user permissions for each setup
type.
1.1. Install Kolibri
7
Kolibri Documentation, Release 0.6.2
Fig. 1.3: Select the name for the facility where Kolibri is running.
Facility type
Admin managed
For schools and other formal learning contexts.
Self-managed
For parent-child learning, homeschooling
or suplementary individual learning.
Informal and personal use
For libraries, orphanages, correctional
facilities, youth centers, computer labs,
and other non-formal learning contexts.
Users
• Admins must create all user accounts.
• Users can sign in without password.
• Users cannot edit their account information.
• Guests can create their own accounts.
• Users can edit their account information.
• Guests can create their own accounts.
• Users can edit their account information.
1. Once you finish the initial setup, proceed to import some content, and create users (if you chose the Adminmanaged facility setup). Make sure to check how to configure other computers in the network to access Kolibri.
8
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Fig. 1.4: Select the username and password for the facility Super User.
1.1. Install Kolibri
9
Kolibri Documentation, Release 0.6.2
Fig. 1.5: Choose a Facility setup.
10
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Fig. 1.6: View the Facility setup details.
1.1. Install Kolibri
11
Kolibri Documentation, Release 0.6.2
1.2 Access Kolibri
1.2.1 Starting Kolibri on Windows
To start Kolibri on Windows, just double-click the desktop shortcut. You will see the notification message Kolibri is
starting, please wait. . . .
When you see the notification Kolibri is running. . . , Kolibri will open in the browser with the URL http://127.0.0.1:
8080.
Kolibri Taskbar Options
While it is running, Kolibri will display an icon in the Windows taskbar (usually at bottom right, near the clock), that
allows you to stop it and configure other settings.
Fig. 1.7: Kolibri taskbar options.
• Use the Load in browser option to open Kolibri in the browser.
• By default Kolibri will start running every time you start the computer where it is installed. Uncheck the Run
Kolibri at system startup option if you prefer to start it manually from the desktop shortcut.
• When installed, Kolibri will open in the browser everytime it is started. Uncheck the option Open browser
when Kolibri starts if you prefer to have it running in the background, and to open it manually in the browser
by typing the URL http://127.0.0.1:8080 in the address bar.
• Select Exit to stop Kolibri. You will be prompted to confirm the selection, after which Kolibri will stop. You
will have to close the browser (or the tab) manually.
Note: Remember to configure other computers in the network to access Kolibri content.
12
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
1.2.2 Starting Kolibri on Linux and OSX
Warning: Final Kolibri installer for Linux and OSX is not released yet, so these steps are Work in Progress!
TODO - REVIEW when the DEB installer is finished
Open the default browser at http://127.0.0.1:8080 displaying the Kolibri start page.
Note: Remember to configure other computers in the network to access Kolibri content.
1.2.3 Starting Kolibri on Android
Warning: Final Kolibri installer for Android is not released yet, so these steps are Work in Progress!
Tap the Kolibri icon on your device.
1.2.4 Accessing Kolibri from Other Devices in the Network
While Kolibri is up and running on the device where you installed it, other computers, tablets, even mobile phones in
the same Local Area Network (LAN) can access its learning contents.
• To access the content on the same device/computer where Kolibri is running, open the browser at the address
http://127.0.0.1:8080/.
• To access the content from other devices in the same network, you need to know the IP address of one where
where Kolibri is running. For example, if Kolibri is on a device/computer with IP address 192.168.0.104, you
can access it from others in the same network by opening the browser and typing the address http://192.168.0.
104:8080.
Note: Use the ipconfig command on Windows or ifconfig command on Linux/OSX to find out the IP address
of the device running the Kolibri.
TODO - IP of the Android device?
1.2.5 Change Language
To change language in which Kolibri user interface is displayed, follow these steps.
1. Open your user menu in the upper right corner.
2. Select the Change language option.
3. Choose the desired language.
4. Click Select to confirm.
1.2. Access Kolibri
13
Kolibri Documentation, Release 0.6.2
1.3 Manage Kolibri
1.3.1 Default User Roles
Kolibri users by default can be divided in 3 different roles with respective access to features.
Default user roles
Learners can:
• View content and have their progress tracked
Coaches can:
• View content and have their progress tracked
• View Coach dashboard and track progress of other users and usage stats for individual exercises
• Create/Edit/Delete Groups in Classes and add users to them
• Create/Edit/Delete Exams and assign them to users
Admins can:
• View content and have their progress tracked
• View Coach dashboard and track progress of other users and usage stats for individual exercises
• Create/Edit/Delete other Admins, Coaches, and Learners
• Create/Edit/Delete Groups in Classes and add users to them
• Create/Edit/Delete Classes and enroll users in them
• View/Edit Facility configuration settings
• Export Detail and Summary logs usage data
Kolibri Super Users
Kolibri Super users have all device permissions and are able to assign them to other users. Therefore Super users
can:
•
•
•
•
•
•
•
•
•
View content and have their progress tracked
View Coach dashboard and track progress of other users and usage stats for individual exercises
Create/Edit/Delete other Admins, Coaches, and Learners
Create/Edit/Delete Groups in Classes and add users to them
Create/Edit/Delete Classes and enroll users in them
View/Edit Facility configuration settings
Export Detail and Summary logs usage data
Import/Export Content channels
View/Edit Permissions of other users
Assign Additional Permissions
By default, only Super users can view the Device dashboard, import/export Content channels in Kolibri, and modify
Permissions for other users. However, depending on the needs of the institution, Super users can also grant these
permissions to other users.
14
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
1.3.2 Manage Device
You can manage content and permissions for the device where Kolibri is running from the Device dashboard.
Note: To manage device settings you must have the appropriate permissions.
Assign Permissions
You can assign additional permissions to Kolibri users which will provide them access to more features compared to
their user roles. To manage permissions for Kolibri users, use the Permission tab in the Device dashboard.
Permission to Manage Content
To grant permission to manage content channels in Kolibri to another user, follow these steps.
1. Click Edit permissions for the chosen user.
2. Under Device Permissions activate the option Can import and export content channels.
3. Click Save changes to apply and finish.
1.3. Manage Kolibri
15
Kolibri Documentation, Release 0.6.2
The users who have been granted the permissions to manage content channels will have a black key indicator in front
of their name, and will be able to see the Device dashboard with the Content tab.
Super User Permissions
To grant Super user permissions to another user, follow these steps.
1. Click Edit permissions for the chosen user.
2. Activate the option Make superuser.
3. Click Save changes to apply and finish.
The users who have been granted the Super user permissions will have a yellow key indicator in front of their name,
and will be able to see the Device dashboard with both the Content and Permissions tabs.
16
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
1.3.3 Manage Content
Note: To manage Kolibri content channels you must have the appropriate permissions.
Kolibri Content Channel is a collection of educational resources (video, audio or document files) prepared and organized by the content curator for their use in Kolibri. Each Kolibri Content Channel has its own Content Channel ID
in Kolibri Studio. In order to import channels in Kolibri, you need the channel ID from the content curator who
assembled it.
You can import and export Content Channels for Kolibri in the Content tab of the Device dashboard.
1.3. Manage Kolibri
17
Kolibri Documentation, Release 0.6.2
Import Content Channel to Kolibri
To import Content Channel to Kolibri, follow these steps.
1. Click Import button in My Channels pane.
2. Choose the source option (Internet or Local Drives).
Import Content Channel from the Internet
If the computer where Kolibri is running has an Internet connection with the sufficient bandwidth, follow these steps
to import content channels.
18
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
1. Choose option for Internet.
2. Enter Content ID for the desired channel from Kolibri Studio.
3. Click Import button, and confirm the import.
4. Wait for the content to be downloaded and click Close for the new channel to appear under the My Channels
heading.
Import Content Channel from a Local Drive
If the computer where Kolibri server is running does not have access to Internet or has insufficient bandwidth, you
have the option to receive content channels stored on an external drive (USB stick or hard disk). Follow these steps to
import content channels.
1.3. Manage Kolibri
19
Kolibri Documentation, Release 0.6.2
1. Insert the USB drive in your computer.
2. Choose option for Local Drives.
3. Kolibri will automatically detect the drive(s) with available content files.
4. Select the drive where the channel content is stored.
5. Click Import button.
6. Wait for the content to be imported and click Close for the new channel to appear under the My Channels
heading.
Note: If the local drive is not detected, try re-inserting the storage device (USB stick or external hard disk) and
pressing the button Refresh.
Tip: Workaround for import from local drive on older devices.
If Kolibri is installed on an older or a low-resource device, you can try the following procedure for
importing content channels for faster results.
1. Stop Kolibri.
2. Browse the local drive with the file explorer of your operating system.
3. Copy the content folder located inside the KOLIBRI_DATA folder on the local drive.
4. Paste the copied content folder inside the .kolibri folder on your hard disk. The location of
the .kolibri folder will depend on your operating system (see the table below).
5. Confirm the merge of the two folders.
20
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
6. Restart Kolibri, and the new channels should now be available.
Operating system
Windows
OSX
Linux
Location
C:/Users/<your_username>/.kolibri/
HD/Users/<your_username>/.kolibri/
/home/<your_username>/.kolibri/
Export from Kolibri to Local Drive
If you want to make available the content you have imported on your Kolibri server, to another computer where Kolibri
is installed, follow these steps to export your content channels.
Note: You must have an external drive (USB stick or hard disk) attached to your device.
1. Click Export button in My Channels pane.
2. Select the local drive where you wish to export Kolibri content.
3. Click Export button.
4. Once the export is finished, safely disconnect the drive according to the recommended procedure for your
operating system, and proceed to import channels on other devices.
This procedure makes a copy of the content folder located inside the .kolibri folder on your hard disk, and
places it the KOLIBRI_DATA folder on the selected local drive. This structure is recognized by the Import from
local drive command.
1.3. Manage Kolibri
21
Kolibri Documentation, Release 0.6.2
1.3.4 Manage Facility
You can edit facility configuration settings in Kolibri from the Configuration tab in your Facility dashboard.
1. Activate the options you want to make available for the users of your facility.
2. Click Save changes to apply and finish.
22
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Note: To manage Kolibri users you must sign-in as Super user or Admin.
1.3.5 Manage Users
You can search for, filter, add, and edit user accounts in Kolibri from the Users tab in your Facility dashboard.
Note: To manage Kolibri users you must sign-in as Super user or Admin.
Create a New User Account
To create a new user account, follow these steps.
1. Click Add New button.
2. Fill in the required information (name, username, password).
3. Select user profile (Admin, Coach or Learner).
4. Click Create Account to add the new user.
1.3. Manage Kolibri
23
Kolibri Documentation, Release 0.6.2
Select Users by Type
1. Click All Users selector to display user types.
2. Toggle between options to filter the user roster according to type, or leave it as All Users to display all.
24
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Edit User’s Account
To edit username or the full name account, follow these steps.
1. Click on the Edit button (pencil icon) next to the user’s name.
2. Edit Full name or Username in the Edit account info window.
3. Click Confirm to update the edited information, or Cancel to exit without saving.
1.3. Manage Kolibri
25
Kolibri Documentation, Release 0.6.2
Reset User’s Password
1. Click Reset password in the Edit account info window.
2. Enter the new password in both fields.
3. Click Save to confirm, or Back to exit without changing the password.
26
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Delete User’s Account
1. Click Delete user in the Edit account info window.
2. Click Yes to confirm, or No to exit without deleting the account.
1.3.6 Manage Classes
You can view, create and delete classes, as well as search, filter and enroll Kolibri users in them, using the Classes tab
in your Facility dashboard. Default view displays the list of all classes in your facility, with the number of enrolled
users for each class.
1.3. Manage Kolibri
27
Kolibri Documentation, Release 0.6.2
Note: To manage Kolibri users you must sign-in as Super user or Admin.
Add New Class
To add a new class, follow these steps.
1. Click Add new class button.
2. Fill in the class name.
3. Click Create to add the new class, or Cancel to exit.
Delete Class
To delete class, follow these steps.
1. Click Delete class button for the chosen class from the list.
2. Click Delete class in the confirmation window to proceed, or Cancel to exit without deleting the class.
28
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Note: Users enrolled in the class you are deleting will not be removed from the database.
Edit Class and Enroll Users
To edit a class select it from the default view in the Classes tab. In the following Class view you can change class
name, remove currently enrolled users from the class and enroll new ones.
Change Class Name
To edit class name, follow these steps.
1. Click on the Edit button (pencil icon) next to the class’ name.
2. Write the new name in the Class name field.
3. Click Update to confirm the edited information, or Cancel to exit without saving.
1.3. Manage Kolibri
29
Kolibri Documentation, Release 0.6.2
Enroll users to class
1. Click Enroll users button.
• List in this view contains all the users currently NOT enrolled for the selected class.
• You can search for a specific user by name.
2. Use checkboxes to select all the user in the list, or specific users you want to enroll to class. You can also use
the New user account button to create a new user AND enroll them at the same time.
3. Click Review & save button.
4. Click Yes, enroll users to confirm, or No, go back to exit without enrolling the selected users.
30
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Remove users from class
1. Click Remove button for the chosen user.
2. Click Remove from class to confirm, or Cancel to exit without removing the user.
Note: Users removed from the class will not be deleted from the database, and you can still access their accounts
from the Users tab in the Facility dashboard.
1.3.7 Manage Data
1.3. Manage Kolibri
31
Kolibri Documentation, Release 0.6.2
Note: To manage Kolibri users you must sign-in as Super user or Admin.
You can download Kolibri Detail and Summary logs usage data and export in the CSV format from the Data tab in
your Facility dashboard.
1.3.8 Get support
If you want to contact the Learning Equality Support team to report an issue, or share your experience about using
Kolibri, please register at our Community Forums.
Once you register on our forums, please read the the first two pinned topics (Welcome to LE’s Support Community and
How do I post to this forum? )
You can add a new topic with the + New Topic button on the right. Make sure to select the Kolibri category in the
Create a New Topic window so it’s easier to classify and respond to.
32
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
1.4 Coach
You can track progress of the Learner users, create and assign Exams to classes or learner groups from the Coach
dashboard. The default view of the Coach dashboard presents the list of Classes with number of learners enrolled to
each class.
Select a class from the list to access the progress-tracking features and create exams.
1.4. Coach
33
Kolibri Documentation, Release 0.6.2
1.4.1 Recent Activity View
This is the default view when you select a class from the Coach dashboard. It displays the list of channels and items
(exercises and resources - videos, reading material, etc.) accessed during the last 7 days by learners of the selected
class.
If the class learners have access to more then one channel, you will first see the list of channels which you can navigate
by topics and subtopics until you arrive to a specific item. In this view you can see the progress of each class learner
for that specific item.
1.4.2 Topic Activity View
Use this view to access the full report of activity progress for the selected class. You can navigate channels by topics
and subtopics until you see the progress of each class learner for one specific item.
34
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
1.4.3 Manage Groups
In case you need to further divide learners inside classes, for example to address the different progress needs or levels,
you can use the Groups feature. Create and delete groups, as well as assign learners to them from the Groups tab
in your Coach dashboard. Default view displays the list of all groups for the selected class, with the list of assigned
learners for each group.
1.4. Coach
35
Kolibri Documentation, Release 0.6.2
Note: To manage Kolibri users you must sign-in as Super user, Admin or Coach.
Create a New Group
To create a new learner group, follow these steps.
1. Click + New group button.
2. Give group a desired name.
3. Click Save to confirm, or Cancel to exit without creating a group.
Assign Learners to Group
Below existing groups there is a list with all learners currently NOT assigned to any groups.
36
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
1. Use checkboxes to select all the learners in the list, or specific ones you want to assign to the group.
2. Click Move learners button on the right side of the list.
3. Select the group to which you want to assign the selected learners in the confirmation window.
4. Click Move to proceed, or Cancel to exit without assigning.
Move learners between groups
1. Use checkboxes to select all the user in one group, or specific users you want to assign to another group.
2. Click Move learners button on the right side of the origin group.
3. Select the group to which you want to move the selected learners, or the Ungrouped option if you want to
remove them from the origin group without assigning to a new one.
4. Click Move to proceed, or Cancel to exit without moving.
1.4. Coach
37
Kolibri Documentation, Release 0.6.2
Rename Group
To rename group, follow these steps.
1. Click the down arrow icon on the right edge of the desired group from the list.
2. Select the Rename group from the drop-down menu.
3. Input the new name for the group in the confirmation window.
4. Click Save changes button to proceed, or Cancel to exit without renaming the group.
Delete Group
To delete a group, follow these steps.
1. Click the down arrow icon on the right edge of the desired group from the list.
2. Select the Delete group from the drop-down menu.
3. Click Delete group button in the confirmation window to proceed, or Cancel to exit without deleting the group.
Note: Learners currently assigned to group will become ungrouped.
1.4.4 Manage Exams
You can view, create and delete exams, as well as assign them to learners, using the Exams tab in your Coach
dashboard. Default view displays the list of all exams in a selected class, with a series of options to set the visibility,
(de)activate when required, and view report of students who took them.
38
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Note: To manage Exams in Kolibri classes and groups you must be logged-in as Coach or Admin.
Create New Exam
To create a new exam, follow these steps.
1. Click New exam button.
2. Select the content channel from which you wish to select questions for the exam.
3. Click Create exam to confirm, or Cancel to exit the confirmation window.
4. Fill in the field for exam title.
5. Fill in the field for number of questions you want exam to contain.
6. Navigate through the topic tree and select checkboxes of those exercises you want to include in the exam.
1.4. Coach
39
Kolibri Documentation, Release 0.6.2
As you keep adding the exercises you will see confirmation messages at the bottom.
7. Click Preview button to view the result in overlay window.
40
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
• Click Randomize questions button to present them in the different order from those in the topic
origin.
• Click the Close (X) button in the upper right corner to return to the exam home page.
8. Click Finish button to save the result.
Change Exam Visibility
Newly created exam will be visible to entire class. To change exam visibility, meaning to assign it only to one group
of learners instead of the whole class, follow these steps.
1. Click Change button under the Visible to column in the list of exams.
2. Select the group(s) of learners to whom you wish to assign the exam.
3. Click Update to confirm, or Cancel to exit the confirmation window.
1.4. Coach
41
Kolibri Documentation, Release 0.6.2
Activate/Deactivate Exam
Once you set the visibility of exam to the chosen group(s) of learners, you need to Activate it in order for it to appear
in the Learn view of the learners to whom you assigned it.
• Click Activate button under the Action column in the list of exams.
• When the exam period concludes, click the Deactivate button.
View Exam Report
To view the report on learners who have taken the exam, follow these steps.
1. Click down arrow near the Activate button for the desired exam from the list.
2. Select View report in the drop-down menu.
42
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
3. Click the name of the learner to view the detailed report with preview of results for each question.
4. Click each of the questions to preview it and understand better which question learners answered correctly in
1.4. Coach
43
Kolibri Documentation, Release 0.6.2
the exam and those they struggled with.
Delete Exam
To delete exam, follow these steps.
1. Click down arrow near the Activate button for the desired exam from the list.
2. Select Delete in the drop-down menu.
44
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
3. Click Delete button in the confirmation window to proceed, or Cancel to exit without deleting the exam.
Warning: All data from the exam you are deleting will be lost.
Rename Exam
To rename exam, follow these steps.
1. Click down arrow near the Activate button for the desired exam from the list.
2. Select Rename in the drop-down menu.
3. Change the exam title in the confirmation window.
4. Click Rename button to proceed, or Cancel to exit without renaming the exam.
1.5 Learner
1.5.1 Access Kolibri
Note:
• If you are using Kolibri in your school, education center or facility, your coach or administrator will provide the
instructions how to open the sign-in page, and username and password if necessary.
• If you are using Kolibri on your own, outside an education center or facility, follow the instructions how to
Access Kolibri according to your operating system.
To sign in to Kolibri and start learning follow these steps:
1. Type your username and password (may be optional).
2. Click the SIGN IN button.
Warning: If you start browsing Kolibri as a guest, you need either to select Sign in option from the Guest menu
in the upper right corner, or from the sidebar menu (left or bottom) to open the sign-in page.
Once you have logged in into Kolibri, you can see and edit your user data from the Profile option in the main menu
(below Learn).
To sign out from Kolibri you can either:
• Click the user icon in the upper right corner and select Sign out option.
OR
• Select Sign out option in the main menu.
1.5. Learner
45
Kolibri Documentation, Release 0.6.2
Fig. 1.8: Main sign-in page.
46
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Fig. 1.10: View and edit your user profile.
1.5. Learner
47
Kolibri Documentation, Release 0.6.2
1.5.2 Learn
Each time you login into Kolibri, the first thing you will see is the Learn page. Here you will find learning topics and
materials related to what you were doing the last time you used Kolibri, or those recommended by your teachers and
coaches (not visible if you are browsing as a guest).
Recommended
In the Recommended tab you can see various sections:
• Most popular section which displays the most frequently used materials in your school or facility.
• Next steps section which displays suggestions according to your previously visited activities.
• Resume section displays activities that you started but haven’t finished yet.
• Featured in. . . section.
You can browse through learning materials in all sections by using the arrow icons at the beginning and the end of the
section rows.
Fig. 1.11: Learn page gives you access to all Kolibri learning content and activities.
48
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Topics
Topics tab offers you the option to navigate through the complete set of learning topics and materials available in
Kolibri. Use it as you wish, or according to indications from your teachers and coaches.
In Kolibri Topic tab content from different sources is grouped in Channels. Depending on how your school and
teachers or coaches decided to organize the content, you may have one or more Channels available here. Follow the
indications by your teachers or coaches on how to use the content from each available Channel.
Fig. 1.12: Content in Kolibri is grouped in Channels.
Navigate Kolibri topics with breadcrumb links
When you are browsing a topic in Kolibri, the Breadcrumb links indicate previously visited, more general topics.
Current topic is at the last position, and you can click any of the previous links in the breadcrumb to go back to a
specific broader topic.
Search
If you are looking for a specific subject, topic, or term, use the Search feature:
1. Click the magnifying glass icon in the upper right corner.
2. Type the word or combination of words you are looking for in the search field.
3. Press Enter to display search results below the field.
1.5. Learner
49
Kolibri Documentation, Release 0.6.2
Fig. 1.13: Use breadcrumbs links to navigate back through Kolibri topics.
Content types
Exercises
Kolibri Exercises can require you to do different things: fill in a missing number, write a formula, choose one of the
available options, etc. Each correct answer gets you a checkmark, and majority of exercises require 5 correct answers
in a row to be completed. Some exercises can offer one or more hints, to help you solve the problem.
Independent of the required action (writing an answer yourself or choosing one of the options), these are the steps to
follow.
1. Read the question carefully.
2. Write the answer or choose one of the provided options.
3. When you are ready to submit, click the Check answer button.
• If the answer is correct and a checkmark appears, click the Next question button to proceed.
• If the answer is incorrect, click the Get a hint button, read the suggestions, and try to answer again.
4. Once you have achieved the required number of correct answers in a row, click the Next item button, to continue
learning with the rest of the material in that topic.
5. If you are unable to solve some questions, try reviewing the videos in the Recommended section below the
exercise, or seek help from your peers or teacher/coach.
50
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Fig. 1.14: Search page gives you option to search for a specific term in Kolibri.
1.5. Learner
51
Kolibri Documentation, Release 0.6.2
Fig. 1.15: Exercise page in Kolibri.
52
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Video and Audio Player options
To play videos and listen to audio files in Kolibri you have several available control buttons at the bottom of the player
screen. Move the cursor or tap on the player screen to make appear the control buttons while playing.
Kolibri video and audio player.
(controls at the bottom of the player)
• Play/Pause buttons
• Rewind/Fast forward buttons by +/- 10 seconds
• Time tracker indicator with progress bar
• Video duration indicator
• Volume scrollbar
1.5. Learner
53
Kolibri Documentation, Release 0.6.2
• Playback speed selector
• Fullscreen button
Use the Download content button below the player to download the video, audio and thumbnail files to your computer.
Some videos will provide multiple resolution options.
PDF Viewer options
Note: Options for viewing PDF files will depend on the browser and operating system you are using to view Kolibri.
• Use the Toggle Fullscreen button to open the PDF file in fullscreen view.
• Use the Esc button to close the fullscreen view and return.
Use the Download content button below the PDF viewer to download the PDF file to your computer.
Exams
If your teacher/coach scheduled an exam for you or your class, it will be available through the Exams tab.
1. Press the button Start when you are ready to start taking exam.
54
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
2. You can go on answering the questions in the order you prefer: move through questions with Previous and Next
question buttons, or click on the question number in the column on the left side.
3. Each time you fill in the answer field, or select one of the available options, the question will be marked by a
colored dot in the column on the left side.
4. You can review and correct your answers to all questions as many times you need before submitting.
5. Press the Back to exam list if you want to pause the exam and come back later.
6. Press the button Continue to resume when you are ready.
7. Press the button Submit exam when you are positive that you answered all the questions, and you want to finish
the exam.
8. You will see instant feedback with the result of your exam.
1.6 Frequently Asked Questions
1.6.1 Network Terminology
About IP addresses
• 0.0.0.0 = A special IP address on the server (your device running Kolibri and “serving” its content to others
in the local network), which actually means “all available IP addresses”. It’s a kind of alias. But accessing 0.0.
0.0 from another computer doesn’t make sense and doesn’t work. By default, Kolibri will serve on 0.0.0.0,
which essentially means all IP addresses that are available on the device will render Kolibri accessible.
1.6. Frequently Asked Questions
55
Kolibri Documentation, Release 0.6.2
56
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
• 127.0.0.1 = A device’s local IP address, meaning “myself”. Some people joke and say “There’s no place
like 127.0.0.1”, meaning “there’s no place like home” :) This can be used on the serving device itself to test that
Kolibri is running, in case you need a failsafe way of checking that Kolibri is in fact running and responsive.
• 192.x.y.z = Addresses starting with 192 are local network IP addresses. The same thing can be said about
10.x.y.z. The address that you wanna use to enter on the clients/tablets in order to contact the server will in
most cases start with 192 or 10.
• Port number: Kolibri runs on port 8080. When you access something on an IP address, you need a port. Ports
can be open or closed on the server, but they can also be regulated by firewall rules on the way. http:// <this is the protocol that the browser reads out from the “URL”, which is just some text that describes Kolibri.
• http://192.168.1.1:8080 means: “Connect to IP address 192.168.1.1 on port 8080 with the
HTTP protocol”. The browser will the continue to try to reach this address, but may fail for instance if Kolibri
isn’t running, or if a step along the way blocks access.
1.6.2 Troubleshoot Network Problems
1. Can you access Kolibri via http://127.0.0.1:8080?
2. Can you access anything from the external IP of the device running Kolibri FROM the device itself?
3. Can you ping the external IP address from another device on the network?
1.6.3 Working with Kolibri from the Command Line
Warning: In Windows you need to open cmd.exe Command prompt in the folder where Kolibri executable is
located: c:/Python27/Scripts.
If you see errors in the prompt/terminal output while running the commands below, ask for help at our Community
Forums, or file an issue on GitHub.
1.6. Frequently Asked Questions
57
Kolibri Documentation, Release 0.6.2
Start/Stop Kolibri
In case you need to troubleshoot potential problems while running Kolibri, you may try to start it manually from the
command line.
kolibri start --debug --foreground
kolibri stop
Import Content Channels from Internet
To import content channels from Internet, run these two commands in sequence. The first downloads the channel
database, and the second downloads the resources (videos, documents, etc.). Make sure not to include the angle
brackets “< >” in the command.
kolibri manage importchannel -- network <Channel ID>
kolibri manage importcontent -- network <Channel ID>
Import Content Channels from a Local Drive
To import content channels from the local drive, run these two commands in sequence. Local drive should have a
folder KOLIBRI_DATA at the root, with Kolibri content inside.
kolibri manage importchannel -- local <Channel ID> /path/to/local/drive
kolibri manage importcontent -- local <Channel ID> /path/to/local/drive
Export Content Channels
To export Kolibri content channels on a local drive in order to share it with another device, run these two commands
in sequence. The first exports the channel database, and the second exports the resources (videos, documents, etc.).
kolibri manage exportchannel -- <Channel ID> /path/to/local/drive/KOLIBRI_DATA
kolibri manage exportcontent -- <Channel ID> /mount/mydrive/KOLIBRI_DATA
The path should be to a folder named KOLIBRI_DATA at the root of the local drive, so it will get picked up later for
importing via the Web UI.
Create a New Super User
In case you need to create another Super user, either to address additional need of managing facility, or if you lost the
password for the old one, run the following command:
kolibri manage createsuperuser
You will be prompted to input the Username and Password and the new Super user user account will be created.
Change Language
kolibri language setdefault <langcode>
58
Chapter 1. User Guide
Kolibri Documentation, Release 0.6.2
Available languages in Kolibri
English
en
Spanish (Spain)
es-es
Spanish (Mexico)
es-mx
French
fr
Portuguese (Portugal) pt-pt
Portuguese (Brazil)
pt-br
Swahili (Tanzania)
sw-tz
1.6. Frequently Asked Questions
59
Kolibri Documentation, Release 0.6.2
60
Chapter 1. User Guide
CHAPTER
2
Developer Guide
2.1 Getting started
First of all, thank you for your interest in contributing to Kolibri! The project was founded by volunteers dedicated to
helping make educational materials more accessible to those in need, and every contribution makes a difference. The
instructions below should get you up and running the code in no time!
2.1.1 Setting up Kolibri for development
Most of the steps below require entering commands into your Terminal (Linux, Mac) or command prompt (cmd.exe
on Windows) that you will learn how to use and become more comfortable with.
Tip: In case you run into any problems during these steps, searching online is usually the fastest way out: whatever
error you are seeing, chances are good that somebody already had it in the past and posted a solution somewhere. . . ;)
Git & GitHub
1. Install and set-up Git on your computer. Try this tutorial if you need more practice with Git!
2. Sign up and configure your GitHub account if you don’t have one already.
3. Fork the main Kolibri repository. This will make it easier to submit pull requests. Read more details about
forking from GitHub.
Install Environment Dependencies
1. Install Python if you are on Windows, on Linux and OSX Python is preinstalled (recommended versions 2.7+
or 3.4+).
2. Install pip package installer.
61
Kolibri Documentation, Release 0.6.2
3. Install Node (version 6 is required).
4. Install Yarn according the instructions specific for your OS.
Note:
• On Ubuntu install Node.js via nvm to avoid build issues.
• On a Mac, you may want to consider using the Homebrew package manager.
Ready for the fun part in the Terminal? Here we go!
Checking out the code
1. Make sure you registered your SSH keys on GitHub.
2. Clone your Kolibri fork to your local computer. In the following commands replace $USERNAME with your
own GitHub username:
# using SSH
git clone git@github.com:$USERNAME/kolibri.git
# using HTTPS
git clone https://github.com/$USERNAME/kolibri.git
3. Enable syncing your local repository with upstream, which refers to the Kolibri source from where you cloned
your fork. That way you can keep it updated with the changes from the rest of Kolibri team contributors:
cd kolibri # Change into the newly cloned directory
git remote add upstream git@github.com:learningequality/kolibri.git
˓→the upstream
git fetch upstream # Check if there are changes upstream
git checkout develop
# Add
Warning: develop is the active development branch - do not target the master branch.
Virtual environment
It is best practice to use Python virtual environment to isolate the dependencies of your Python projects from each
other. This also allows you to avoid using sudo with pip, which is not recommended.
You can learn more about using virtualenv, or follow these basic instructions:
Initial setup, performed once:
$ sudo pip install virtualenv
$ mkdir ~/.venvs
˓→environments
$ virtualenv ~/.venvs/kolibri
# install virtualenv globally
# create a common directory for multiple virtual
# create a new virtualenv for Kolibri dependencies
Note: We create the virtualenv outside of the Kolibri project folder. You can choose another location than ~/.
venvs/kolibri if desired.
To activate the virtualenv in a standard Bash shell:
62
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
$ source ~/.venvs/kolibri/bin/activate
# activate the venv
Now, any commands run with pip will target your virtualenv rather than the global Python installation.
To deactivate the virtualenv, run the command below. Note, you’ll want to leave it activated for the remainder of
project setup!
$ deactivate
Tip:
• Users of Windows and other shells such as Fish should read the guide for instructions on activating.
• If you set the PIP_REQUIRE_VIRTUALENV environment variable to true, pip will only install packages
when a virtualenv is active. This can help prevent mistakes.
• Bash users might also consider using virtualenvwrapper, which simplifies the process somewhat.
Install Project Dependencies
Note: Make sure your virtualenv is active!
To install Kolibri project-specific dependencies make sure you’re in the kolibri directory and run:
# Python requirements
(kolibri)$ pip install -r requirements.txt
(kolibri)$ pip install -r requirements/dev.txt
# Kolibri Python package in 'editable' mode, so your installation points to
˓→your git checkout:
(kolibri)$ pip install -e .
# Javascript dependencies
(kolibri)$ yarn install
Tip:
• We’ve adopted this concatenated version with added cleanup: make clean && pip install -r
requirements.txt --upgrade && pip install -e . && yarn install.
• In case you get webpack compilation error with Node modules build failures, add the flag --force at the end,
to ensure binaries get installed.
2.1.2 Running Kolibri server
Development server
To start up the development server and build the client-side dependencies, use the following command:
(kolibri)$ kolibri --debug manage devserver --webpack
2.1. Getting started
63
Kolibri Documentation, Release 0.6.2
Wait for the build process to complete. This takes a while the first time, will complete faster as you make edits and the
assets are automatically re-built.
Now you should be able to access the server at http://127.0.0.1:8000/.
Tip: If you need to make the development server available through the LAN, you must leave out the --webpack
flag, and use the following command:
(kolibri)$ yarn run build
(kolibri)$ kolibri --debug manage devserver -- 0.0.0.0:8000
Now you can simply use your server’s IP from another device in the local network through the port 8000, for example
http://192.168.1.38:8000/.
Tip:
If get an error similar to Node Sass could not find a binding for your current
environment try running:
(kolibri)$ npm rebuild node-sass
More advanced examples of the devserver command:
# runs the dev server and rebuild client assets when files change
kolibri --debug manage devserver --webpack
# runs the dev server and re-run client-side tests when files changes
kolibri --debug manage devserver --karma
# runs all of the above
kolibri --debug manage devserver --webpack --karma
Running the Production Server
In production, content is served through CherryPy. Static assets must be pre-built:
yarn run build
kolibri start
Now you should be able to access the server at http://127.0.0.1:8080/.
2.1.3 Contributing code to Kolibri
• Once you’ve toyed around with things, read through the rest of the Developer Guide, especially topics in Architecture and Themes to understand more about the Kolibri structure.
• When you’re up to speed with that, you’re probably itching to make some contributions! Head over to the issues
page on GitHub and take a look at the current project priorities. Try filtering by milestone. If you find a bug in
your testing, please submit your own issue
• Once you’ve identified an issue and you’re ready to start hacking on a solution, get ready to Submit Pull Requests!
64
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Branching and Release Process
The develop branch is reserved for active development. When we get close to releasing a new stable version/release
of Kolibri, we generally fork the develop branch into a new branch (like release-0.1.x). If you’re working on
an issue tagged for example with the release-0.1.x milestone, then you should target changes to that branch.
Changes to those branches will later be pulled into develop again. If you’re not sure which branch to target, ask the
dev team!
Note: At a high level, we follow the ‘Gitflow’ model. Some helpful references:
• http://nvie.com/posts/a-successful-git-branching-model/
• https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow/
Submit Pull Requests
The most common situation is working off of develop branch so we’ll take it as an example:
$ git checkout upstream/develop
$ git checkout -b name-of-your-bugfix-or-feature
After making changes to the code, commit and push them to a branch on your fork:
$ git add -A # Add all changed and new files to the commit
$ git commit -m "Write here the commit message"
$ git push origin name-of-your-bugfix-or-feature
Go to Kolibri GitHub page, and if you are logged-in you will see the link to compare your branch and and create the
new pull request. Please fill in all the aplicable sections in the PR template and DELETE unecessary headings.
Another member of the team will review your code, and either ask for updates on your part or merge your PR to
Kolibri codebase. Until the PR is merged you can push new commits to your branch and add updates to it.
2.1.4 Additional Recommended Setup
If you’re planning on contributing code to the project, there are a few additional steps you should consider taking.
Editor Config
We have a project-level .editorconfig file to help you configure your text editor or IDE to use our internal conventions.
Check your editor to see if it supports EditorConfig out-of-the-box, or if a plugin is available.
Front-end Dev Tools
If you’re working with front-end Vue.js and use Google Chrome Dev Tools, you may find the Vue.js devtools helpful
DB Setup
You can initialize the server using:
2.1. Getting started
65
Kolibri Documentation, Release 0.6.2
kolibri manage migrate
Pre-Commit Install
We use pre-commit to help ensure consistent, clean code. The pip package should already be installed from a prior
setup step, but you need to install the git hooks using this command.
pre-commit install
2.1.5 Development
Linting
To improve build times, and facilitate rapid development, Javascript linting is turned off by default when you run
the dev server. However, all frontend assets that are bundled will be linted by our Travis CI builds. It is a good
idea, therefore, to test your linting before submitting code for PR. To run the devserver in this mode you can run the
following command.
kolibri --debug manage devserver --webpack --lint
Code Testing
Kolibri comes with a Python test suite based on py.test. To run tests in your current environment:
pytest
# alternatively, "make test" does the same
You can also use tox to setup a clean and disposable environment:
tox -e py3.4
# Runs tests with Python 3.4
To run Python tests for all environments, lint and documentation tests, use simply tox. This simulates what our CI
also does.
To run Python linting tests (pep8 and static code analysis), use tox -e lint or make lint.
Note that tox, by default, reuses its environment when it is run again. If you add anything to the requirements, you
will want to either delete the .tox directory, or run tox with the -r argument to recreate the environment.
We strive for 100% code coverage in Kolibri. When you open a Pull Request, code coverage (and your impact
on coverage) will be reported. To test code coverage locally, so that you can work to improve it, you can run the
following:
tox -e py3.4
coverage html
Then, open the generated ./htmlcov/index.html file in your browser.
Kolibri comes with a Javascript test suite based on mocha. To run all tests:
yarn test
This includes tests of the bundling functions that are used in creating front end assets. To do continuous unit testing
for code, and jshint running:
66
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
yarn run test-karma:watch
Alternatively, this can be run as a subprocess in the development server with the following flag:
kolibri --debug manage devserver --karma
You can also run tests through Django’s test management command, accessed through the kolibri command:
kolibri manage test
To run specific tests only, you can add --, followed by a label (consisting of the import path to the
test(s) you want to run, possibly ending in some subset of a filename, classname, and method name). For
example, the following will run only one test, named test_admin_can_delete_membership in the
MembershipPermissionsTestCase class in kolibri/auth/test/test_permissions.py:
kolibri manage test -- kolibri.auth.test.test_permissions.
˓→MembershipPermissionsTestCase.test_admin_can_delete_membership
Updating Documentation
First, install some additional dependencies related to building documentation output:
pip install -r requirements/docs.txt
pip install -r requirements/build.txt
To make changes to documentation, edit the rst files in the kolibri/docs directory and then run:
make docs
You can also run the auto-build for faster editing from the docs directory:
cd docs
sphinx-autobuild --port 8888 . _build
Manual Testing
All changes should be thoroughly tested and vetted before being merged in. Our primary considerations are:
• Performance
• Accessibility
• Compatibility
• Localization
• Consistency
For more information, see the next section on Manual Testing & QA.
2.1. Getting started
67
Kolibri Documentation, Release 0.6.2
2.2 Manual Testing & QA
2.2.1 Accessibility (A11y) Testing
Inclusive design benefits all users, and we strive to make Kolibri accessible for all. Testing for accessibility can be
challenging, but there are a few features you should check for before submitting your PR:
• Working keyboard navigation - everything that user can do with mouse or by touch must also work with the
keyboard alone.
• Sufficient color contrast between foreground text/elements and the background.
• Meaningful text alternative for all non-decorative images, or an empty ALT attribute in case of decorative ones.
• Meaningful labels on ALL form or button elements.
• Page has one main heading (H1) and consecutive lower heading levels.
Here are a few tools that we use in testing for accessibility:
• WAVE Evaluation Tool - Firefox Add-on and Chrome extension.
• tota11y accessibility visualization toolkit - bookmarklet for Firefox and Chrome.
• Accessibility Developer Tools - Chrome extension.
• aXe Accessibility Engine - Firefox Add-on and Chrome extension.
There is a much longer list on our Kolibri Accessibility Tools Wiki page if you want to go deeper, but these four should
be enough to help you avoid the most important accessibility pitfalls.
2.2.2 Cross-browser and OS Testing
It’s vital to ensure that our app works across a wide range of browsers and operating systems, particularly older
versions of Windows and Android that are common on old and cheap devices.
In particular, we want to ensure that Kolibri runs on major browsers that match any of the following criteria:
• within the last two versions
• IE 9+ on Windows XP and up
• has at least 1% of global usage stats
Here are some useful options, in order of simplicity:
BrowserStack
BrowserStack is an incredibly useful tool for cross-browser and OS testing. In particular, it’s easy to install plugin
which forwards localhost to a VM running on their servers, which in turn is displayed in your browser.
Amazon Workspaces
In some situations, simply having a browser is not enough. For example, a developer may need to test Windowsspecific backend or installer code from another OS. In many situations, a virtual machine is appropriate - however
these can be slow to download and run.
Amazon’s AWS Workspaces provides a faster alternative. They run Windows VMs in their cloud, and developers can
RDP in.
Local Virtual Machines
Workspaces is very useful, but it has limitations: only a small range of OSes are available, and connectivity and
provisioning are required.
68
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
An alternative is to run the guest operating system inside a virtual machine using e.g. VirtualBox. This also gives more
developer flexibility, including e.g. shared directories between the guest and host systems. This tutorial was written
for KA Lite, but much of it still applies to Kolibri.
Hardware
There are some situations where actual hardware is necessary to test the application. This is particularly true when
virtualization might prohibit or impede testing features, such as lower-level driver interactions.
2.2.3 Responsiveness to Varying Screen Sizes
We want to ensure that the app looks and behaves reasonably across a wide range of typical screen sizes, from small
tablets to large, HD monitors. It is highly recommended to constantly be testing functionality at a range of sizes.
Chrome and Firefox’s Developer Tools both have some excellent functionality to simulate arbitrary screen resolutions.
2.2.4 Slow Network Connection Speeds
It’s important to simulate end-users network conditions. This will help identify real-world performance issues that
may not be apparent on local development machines.
Chrome’s Developer Tools have functionality to simulate a variety of network connections, including Edge, 3G, and
even offline. An app can be loaded into multiple tabs, each with its own custom network connectivity profile. This
will not affect traffic to other tabs.
Within the Chrome Dev Tools, navigate to the Network panel. Select a connection from the drop-down to apply
network throttling and latency manipulation. When a Throttle is enabled the panel indicator will show a warning icon.
This is to remind you that throttling is enabled when you are in other panels.
For Kolibri, our target audience’s network condition can be mimicked by setting connectivity to Regular 3G (100ms,
750kb/s, 250 kb/s).
2.2.5 Performance Testing with Django Debug Panel
We have built in support for Django Debug Panel (a Chrome extension that allows tracking of AJAX requests to
Django).
To use this, ensure that you have development dependencies installed, and install the Django Debug Panel Chrome
Extension. You can then run the development or production servers with the following environment variable set:
DJANGO_SETTINGS_MODULE=kolibri.deployment.default.settings.debug_panel
This will activate the debug panel, and will display in the Dev tools panel of Chrome. This panel will track all page
loads and API requests. However, all data bootstrapping into the template will be disabled, as our data bootstrapping
prevents the page load request from being profiled, and also does not profile the bootstrapped API requests.
2.2.6 Generating User Data
For manual testing, it is sometimes helpful to have generated user data, particularly for Coach and Admin facing
functionality.
In order to do this, a management command is available:
kolibri manage generateuserdata
2.2. Manual Testing & QA
69
Kolibri Documentation, Release 0.6.2
This will generate user data for the each currently existing channel on the system. Use the –help flag for options.
2.3 Architecture
2.3.1 Distribution and installers
The Kolibri Package build pipeline looks like this:
Git master branch
|
|
/ \
/
\
Python dist, online dependencies \
`python setup.py bdist_wheel`
\
/
\
/
Python dist, bundled dependencies
Upload to PyPi
`python setup.py bdist_wheel --static`
Installable with
\
`pip install kolibri`
\
Upload to PyPi
Installable with
`pip install kolibri-static`
/
|
\
/
|
\
Windows
OSX
Debian
installer
installer
installer
Make targets
To build both the slim Kolibri and the one with bundled dependencies, simply run make dist. The .whl files will
now be available in dist/*whl and you can install them with pip install dist/filename.whl.
Automated CI tests
If you add [ setup ] to your commit message, our CI will automatically test that builds work.
Otherwise, changes to certain files like requirements/* and setup.py will automatically prompt test builds to
fire.
2.3.2 Project Conventions
TODO
Documentation
reStructuredText, docstrings, requirements for PRs to master. . .
70
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Git Workflow
stable master, develop, feature branches, tags, releases, hot fixes, internal vs external repos. . .
Python Code
PEP8, additional conventions and best practices. . .
Vue.js Components
Note that the top-level tags of Vue.js components are <template>, <script>, and <style>.
• Whitespace
– an indent is 2 spaces
– two blank lines between top-level tags
– one blank line of padding within a top-level tag
– one level of indent for the contents of all top-level tags
• Keep most child-components stateless. In practice, this means using props but not data.
• Avoid using Vue.js’ camelCase-to-kebab-case mapping. Instead, use square brackets and strings to reference
names.
• Use scoped styles where ever possible
• Name custom tags using kebab-case
• Components are placed in the vue directory. The root component file is called vue/index.vue, and is mounted on
a tag called <rootvue>.
• Components are defined either as a file with a .vue extension (my-component.vue) or as a directory with an
index.vue file (my-component/index.vue). Both forms can be used with require('my-component').
• Put child components inside the directory of a parent component if they are only used by the parent. Otherwise,
put shared child components in the vue director.
• Any user visisble interface text should be rendered translatable, see i18n for details.
JavaScript Code
• We use the AirBnB Javascript Style guide for client-side ES6 code in Vue components.
• use strict is automatically inserted.
• Use ES6 import/export statements, not CommonJS-style require and module.exports statements.
• For logging statements we use a thin wrapper around the log-level JS library, that prefixes the log statements
with information about the logging level and current file. To access the logger, simply include the following code
snippet:
import logger from 'kolibri.lib.logging';
const logging = logger.getLogger(__filename);
2.3. Architecture
71
Kolibri Documentation, Release 0.6.2
Stylus and CSS
• clear out unused styles
• avoid using classes as JS identifiers, and prefix with js- if necessary
HTML
attribute lists, semantic structure, accessibility. . .
2.3.3 Front-end Architecture
Components
We leverage Vue.js components as the primary building blocks for our UI. For general UI development work, this is
the most common tool a developer will use. It would be prudent to read through the Vue.js guide thoroughly.
Each component contains HTML with dynamic Vue.js directives, styling which is scoped to that component (written
using Stylus), and logic which is also scoped to that component (all code, including that in Vue components should
be written using Bublé compatible ES2015 JavaScript). Non-scoped styles can also be added, but these should be
carefully namespaced.
Components allow us to define new custom tags that encapsulate a piece of self-contained, re-usable UI functionality.
When composed together, they form a tree structure of parents and children. Each component has a well-defined
interface used by its parent component, made up of input properties, events and content slots. Components should
never reference their parent.
Read through Project Conventions for some important consistency tips on writing new components.
Layout of Frontend Code
Front-end code and assets are generally contained in one of two places: either in one of the plugin subdirectories
(under kolibri/plugins) or in kolibri/core, which contains code shared across all plugins as described below.
Within these directories, there should be an assets directory with src and test under it. Most assets will go in src, and
tests for the components will go in test.
For example:
kolibri/
core/
assets/
src/
core-base.vue
core-modal.vue
core-global.styl
core-theme.styl
font-NotoSans.css
test/
...
plugins/
learn
assets/
src/
vue/
index.vue
72
# core (shared) items
#
#
#
#
#
global base template, used by apps
example of another shared component
globally defined styles, indluded in head
style variable values
embedded font
# tests for core assets
# learn plugin
# root view
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
some-page.vue
another-page/
index.vue
child.vue
shared.vue
app.js
router.js
store.js
test/
app.js
management/
assets/
src/
vue/user-page.vue
vue/index.vue
app.js
test/
app.js
# top-level client-side page
# top-level client-side page
# child component used only by parent
# shared across this plugin
# instantiate learn app on client-side
# nested-view
# root view
# instantiate mgmt app on client-side
In the example above, the vue/another-page/index.vue file in learn can use other assets in the same directory (such as
child.vue), components in vue (such as shared.vue), and assets in core (such as variables in core-theme.styl). However
it cannot use files in other plugin directories (such as management).
Note: For many development scenarios, only files in these directories need to be touched.
There is also a lot of logic and configuration relevant to front-end code loading, parsing, testing, and linting. This
includes webpack, NPM, and integration with the plugin system. This is somewhat scattered, and includes logic
in frontend_build/. . . , package.json, kolibri/core/webpack/. . . , and other locations. Much of this functionality is described in other sections of the docs (such as asset_loading), but it can take some time to understand how it all hangs
together.
SVG Icons
SVGs can be inlined into Vue components using a special syntax:
<svg src="icon.svg"></svg>
Then, if there is a file called icon.svg in the same directory, that file will be inserted directly into the outputted
HTML. This allows aspects of the icon (e.g. fill) to be styled using CSS.
Attributes (such as vue directives like v-if and SVG attributes like viewbox) can also be added to the svg tag.
Single-page Apps
The Kolibri front-end is made of a few high-level “app” plugins, which are single-page JS applications (conventionally
app.js) with their own base URL and a single root Vue.js component. Examples of apps are ‘Learn’ and ‘User
Management’, as shown in the example above. Apps are independent of each other, and can only reference components
and styles from within themselves and from core.
Each app is implemented as a Kolibri plugin and is defined in a subdirectory of kolibri/plugins.
On the Server-side, the kolibri_plugin.py file describes most of the configuration for the single-page app. In
particular, this includes the base Django HTML template to return (with an empty <body>), the URL at which the
app is exposed, and the javascript entry file which is run on load.
2.3. Architecture
73
Kolibri Documentation, Release 0.6.2
On the client-side, the app creates a single KolibriModule object in the entry file (conventionally app.js) and
registers this with the core app, a global variable called kolibriGlobal. The Kolibri Module then mounts single
root component to the HTML returned by the server, which recursively contains all additional components, html and
logic.
Defining a New Kolibri Module
Note: This section is mostly relevant if you are creating a new app or plugin. If you are just creating new components,
you don’t need to do this.
A Kolibri Module is initially defined in Python by sub-classing the WebpackBundleHook class (in kolibri.
core.webpack.hooks). The hook defines the JS entry point (conventionally called app.js) where the
KolibriModule subclass is instantiated, and where events and callbacks on the module are registered. These
are defined in the events and once properties. Each defines key-value pairs of the name of an event, and the name
of the method on the KolibriModule object. When these events are triggered on the Kolibri core JavaScript app,
these callbacks will be called. (If the KolibriModule is registered for asynchronous loading, the Kolibri Module
will first be loaded, and then the callbacks called when it is ready. See asset_loading for more information.)
All apps should extend the KolibriModule class found in kolibri/core/assets/src/kolibri_module.js.
The ready method will be automatically executed once the Module is loaded and registered with the Kolibri Core
App. By convention, JavaScript is injected into the served HTML after the <rootvue> tag, meaning that this tag
should be available when the ready method is called, and the root component (conventionally in vue/index.vue) can
be mounted here.
Content Renderers
A special kind of Kolibri Module is dedicated to rendering particular content types. All content renderers should
extend the ContentRendererModule class found in kolibri/core/assets/src/content_renderer_module.js. In addition, rather than subclassing the WebpackBundleHook class, content renderers should be defined in the Python
code using the ContentRendererHook class defined in kolibri.content.hooks. In addition to the standard options for the WebpackBundleHook, the ContentRendererHook also accepts a json file defining the
content types that it renders:
.. automodule:: kolibri.content.hooks
members
noindex
The ContentRendererModule class has one required property getRendererComponent which should return a Vue component that wraps the content rendering code. This component will be passed defaultFile, files,
supplementaryFiles, and thumbnailFiles props, defining the files associated with the piece of content.
{
props: [
'defaultFile',
'files',
]
};
In order to log data about users viewing content, the component should emit startTracking, updateProgress,
and stopTracking events, using the Vue $emit method. startTracking and stopTracking are emitted
74
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
without any arguments, whereas updateProgress should be emitted with a single value between 0 and 1 representing the current proportion of progress on the content.
this.$emit('startTracking');
this.$emit('stopTracking');
this.$emit('updateProgress', 0.25);
For content that has assessment functionality two additional props will be passed: itemId and answerState.
itemId is a unique identifier for that content for a particular question in the assessment, answerState is passed
to prefill an answer (one that has been previously given on an exam, or for a coach to preview a learner’s given
answers). The answer renderer should also define a checkAnswer method in its component methods, this method
should return an object with the following keys: correct, answerState, and simpleAnswer - describing the
correctness, an object describing the answer that can be used to reconstruct it within the renderer, and a simple, human
readable answer. If no valid answer is given, null should be returned. In addition to the base content renderer events,
assessment items can also emit a hintTaken event to indicate that the user has taken a hint in the assessment, an
itemError event to indicate that there has been an error in rendering the requested question corresponding to the
itemId, and an interaction event that indicates a user has interacted with the assessment.
{
props: [
'defaultFile',
'files',
'itemId',
'answerState',
],
methods: {
checkAnswer() {
return {
correct: true,
answerState: {
answer: 81,
working: '3^2 = 3 * 3',
},
simpleAnswer: '81',
};
},
},
};
Shared Core Functionality
Kolibri provides a set of shared “core” functionality – including components, styles, and helper logic, and libraries –
which can be re-used across apps and plugins.
JS Libraries
The following libraries are available globally, in all module code:
• vue - the Vue.js object
• vuex - the Vuex object
• logging - our wrapper around the loglevel logging module
• core-base - a shared base Vue.js component (core-base.vue)
2.3. Architecture
75
Kolibri Documentation, Release 0.6.2
And many others.
The complete specification for commonly shared modules can be found in
kolibri/core/assets/src/core-app/apiSpec.js - this object defines which modules are imported into the core object. If the module in question has the ‘requireName’ attribute set on the core specification, then it can be used in code
with a standard CommonJS-style require statement - e.g.:
const vue = require('kolibri.lib.vue');
const coreBase = require('kolibri.coreVue.components.coreBase');
Adding additional globally-available objects is relatively straightforward due to the plugin and webpack build system.
To expose something on the core app, add a key to the object in apiSpec.js which maps to an object with the following
keys:
modulePath: {
module: require('module-name'),
}
This module would now be available for import anywhere with the following statement:
const MODULE = require('kolibri.modulePath');
For better organisation of the Core API specification, modules can also be attached at arbitrarily nested paths:
modulePath: {
nestedPath: {
module: require('module-name'),
}
}
This module would now be available for import anywhere with the following statement:
const MODULE = require('kolibri.modulePath.nestedPath');
For convenience (and to prevent accidental imports), 3rd party (NPM) modules installed in node_modules can be
required by their usual name also:
const vue = require('vue');
Bootstrapped Data
The kolibriGlobal object is also used to bootstrap data into the JS app, rather than making unnecessary API
requests.
For example, we currently embellish the kolibriGlobal object with a urls object. This is defined by Django
JS Reverse and exposes Django URLs on the client side. This will primarily be used for accessing API Urls for
synchronizing with the REST API. See the Django JS Reverse documentation for details on invoking the Url.
Styling
For shared styles, two mechanisms are provided:
• The core-theme.styl file provides values for some globally-relevant Stylus variables. These variables can be used
in any component’s <style> block by adding the line @require '~core-theme.styl'.
• The core-global.styl file is always inserted into the <head> after normalize.css and provides some basic styling
to global elements
76
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Additional Functionality
These methods are also publicly exposed methods of the core app:
kolibriGlobal.register_kolibri_module_async
˓→asynchronous loading.
kolibriGlobal.register_kolibri_module_sync
˓→has loaded.
kolibriGlobal.stopListening
˓→triggering.
kolibriGlobal.emit
// Register a Kolibri module for
// Register a Kolibri module once it
// Unbind an event/callback pair from
// Emit an event, with optional args.
Unit Testing
Unit testing is carried out using Mocha. All JavaScript code should have unit tests for all object methods and functions.
Tests are written in JavaScript, and placed in the ‘assets/test’ folder. An example test is shown below:
var assert = require('assert');
var SearchModel = require('../src/search/search_model.js');
describe('SearchModel', function() {
describe('default result', function() {
it('should be empty an empty array', function () {
var test_model = new SearchModel();
assert.deepEqual(test_model.get("result"), []);
});
});
});
Vue.js components can also be tested.
The management plugin contains an example
(kolibri/plugins/management/assets/test/management.js) where the component is bound to a temporary DOM
node, changes are made to the state, and assertions are made about the new component structure.
Adding Dependencies
Dependencies are tracked using yarn - see the docs here.
We distinguish development dependencies from runtime dependencies, and these should be installed as such using
yarn add --dev [dep] or yarn add [dep], respectively. Your new dependency should now be recorded in
package.json, and all of its dependencies should be recorded in yarn.lock.
Individual plugins can also have their own package.json and yarn.lock for their own dependencies. Running yarn
install will also install all the dependencies for each activated plugin (inside a node_modules folder inside the
plugin itself). These dependencies will only be available to that plugin at build time. Dependencies for individual
plugins should be added from within the root directory of that particular plugin.
To assist in tracking the source of bloat in our codebase, the command yarn run bundle-stats is available to
give a full readout of the size that uglified packages take up in the final Javascript code.
In addition, a plugin can have its own webpack.config.js for plugin specific webpack configuration (loaders, plugins,
etc.). These options will be merged with the base options using webpack-merge.
2.3. Architecture
77
Kolibri Documentation, Release 0.6.2
2.3.4 Git workflow
Note: This is a work in progress, reflecting some practices already adapted but yet to be discussed in the community.
We do not enforce a specific Git workflow at present. You can use tools like git-flow and git-extras to automate many
tasks.
Branch strategy
• master: always has the latest, stable and released code.
• develop: is our current development branch which all new features should target.
• releases/M.N.x tracks stable release series. All minor releases have a release tracker, so for instance 1.
0.x and 1.1.x both have separate branches.
• features/new-thing tracks collaborative works on new features.
Having separate release branches requires a lot of back-porting, so ALWAYS target the oldest release branch that you
want a bug fix introduced in, and then merging or cherry-picking must happen for all subsequent branches following
the review and merging of the oldest release branch.
2.3.5 i18n
As a platform intended for use around the world, Kolibri has a strong mandate for translation and internationalization.
As such, it has been designed with technologies to enable this built in.
Backend Translation
For any strings in Django, we are using the standard Django i18n machinery (gettext and associated functions) to
provide translations. See the Django i18n documentation for more information.
Frontend Translation
For any strings in the frontend, we are using Vue-Intl an in house port of React-intl.
Within Kolibri, messages are defined on the body of the Vue component:
- ``$trs``, an object of the form::
{
msgId: 'Message text',
}
- ``name``, we use the Vue component name to namespace the messages.
The name and all ‘‘msgId‘‘s should be in camelCase.
User visible strings should be rendered directly in the template with {{ $tr('msgId') }}. These strings are
collected during the build process, and bundled into exported JSON files. These files are then uploaded to Crowdin
for translation.
An example Vue component would then look like this:
78
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
<template>
<div>
<p>{{ $tr('exampleMessage') }}</p>
</div>
</template>
<script>
module.exports = {
name: 'example',
$trs: {
exampleMessage: 'This message is just an example',
},
};
</script>
<style lang="stylus" scoped></style>
In order to translate strings outside of the scope of Vue components, i.e. in Javascript source files, the name space and
messages object still need to be defined, as shown in this example:
import { createTranslator } from 'kolibri.utils.i18n';
const name = 'exampleTitles';
const messages = {
msgIdForThisMessage: 'This is a message',
};
const translator = createTranslator(name, messages);
console.log(translator.$tr('msgIdForThisMessage'));
In this way, messages are namespaced, and then available off the $tr method of the translator object returned from
the createTranslator function.
These messages will then be discovered for any registered plugins and loaded into the page if that language is set as
the Django language. All language setting for the Frontend is based off the current Django language for the request.
2.3.6 Plugins
The behavior of Kolibri can be extended using plugins. The following is a guide to developing plugins.
Enabling and disabling plugins
Non-core plugins can be enabled or disabled using the kolibri plugin command. See ../user/cli.
2.3. Architecture
79
Kolibri Documentation, Release 0.6.2
How plugins work
From a user’s perspective, plugins are enabled and disabled through the command line interface or through a UI. Users
can also configure a plugin’s behavior through the main Kolibri interface.
Note: We have not yet written a configuration API, for now just make sure configuration-related variables are kept in
a central location of your plugin.
It’s up to the plugin to provide configuration Form classes and register them.
We should aim for a configuration style in which data can be pre-seeded, dumped and exported easily.
From a developer’s perspective, plugins are Django applications listed in INSTALLED_APPS and are initialized once
when the server starts, mean at the load time of the django project, i.e. Kolibri.
Loading a plugin
In general, a plugin should never modify internals of Kolibri or other plugins without using the hooks API or normal
conventional Django scenarios.
Note: Each app in INSTALLED_APPS is searched for the special kolibri_plugin module.
Everything that a plugin does is expected to be defined through <myapp>/kolibri_plugin.py.
Kolibri Hooks API
What are hooks
Hooks are classes that define something that happens at one or more places where the hook is looked for and applied.
It means that you can “hook into a component” in Kolibri and have it do a predefined and parameterized thing. For
instance, Kolibri could ask all its plugins who wants to add something to the user settings panel, and its then up to the
plugins to inherit from that specific hook and feed back the parameters that the hook definition expects.
The consequences of a hook being applied can happen anywhere in Kolibri. Each hook is defined through a class
inheriting from KolibriHook. But how the inheritor of that class deals with plugins using it, is entirely up to each
specific implementation and can be applied in templates, views, middleware - basically everywhere!
That’s why you should consult the class definition and documentation of the hook you are adding plugin functionality
with.
We have two different types of hooks:
Abstract hooks Are definitions of hooks that are implemented by implementing hooks.
Registered hooks Are concrete hooks that inherit from abstract hooks, thus embodying the definitions of the abstract
hook into a specific case.
So what’s “a hook”? Simply referring to “a hook” is okay, it can be ambiguous on purpose. For instance, in the
example, we talk about “a navigation hook”. So we both mean the abstract definition of the navigation hook and
everything that is registered for the navigation.
80
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Where can I find hooks?
All Kolibri core applications and plugins alike should by convention define their abstract hooks inside <myapp>/
hooks.py. Thus, to see which hooks a Kolibri component exposes, you can refer to its hooks module.
Note: Defining abstract hooks in <myapp>/hooks.py isn’t mandatory, but loading a concrete hook in <myapp>/
kolibri_plugin.py is.
Warning: Do not define abstract and registered hooks in the same module. Or to put it in other words: Never put
registered hooks in <myapp>/hooks.py. The non-abstract hooks should not be loaded unintentionally in case
your application is not loaded but only used to import an abstract definition by an external component!
In which order are hooks used/applied?
This is entirely up to the registering class. By default, hooks are applied in the same order that the registered hook gets
registered! This most likely means the order in which kolibri_plugin is loaded => the order in which the app is
listed in INSTALLED_APPS
An example of a plugin using a hook
Note: The example shows a NavigationHook which is simplified for the sake of readability. The actual implementation in Kolibri will defer.
Example implementation
Here is an example of how to use a hook in myplugin.kolibri_plugin.py:
from django.db.models
# This is where the actual abstract hook is defined
from kolibri.core.hooks import NavigationHook
# By inheriting NavigationHook, we tell that we are going to want our
# plugin to be part of the hook's activities with the specified attributes.
# We only define one navigation item, but defining another one is as simple
# as adding another class definition.
class MyPluginNavigationItem(NavigationHook):
label = _("My Plugin")
url = reverse_lazy("kolibri:my_plugin:index")
And here is the definition of that hook in kolibri.core.hooks:
from kolibri.plugins.hooks import KolibriHook
class NavigationHook(KolibriHook):
2.3. Architecture
81
Kolibri Documentation, Release 0.6.2
"""
Extend this hook to define a new navigation item
"""
#: A string label for the menu item
label = "Untitled"
#: A string or lazy proxy for the url
url = "/"
@classmethod
def get_menu(cls):
menu = {}
for hook in self.registered_hooks:
menu[hook.label] = url
return menu
class Meta:
abstract = True
Usage of the hook
Inside our templates, we load a template tag from navigation_tags, and this template tag definition looks like this:
from kolibri.core.hooks import NavigationHook
@register.assignment_tag()
def kolibri_main_navigation():
for item in NavigationHook().get_menu():
yield item
{% load kolibri_tags %}
<ul>
{% for menu_item in kolibri_main_navigation %}
<li><a href="{{ menu_item.url }}">{{ menu_item.label }}</a></li>
{% endfor %}
</ul>
Warning: Do not load registered hook classes outside of a plugin’s kolibri_plugin. Either define them
there directly or import the modules that define them. Hook classes should all be seen at load time, and placing
that logic in kolibri_plugin guarantees that things are registered correctly.
Other stuff you can do with plugins
Plugins can implement Javascript code as a Kolibri module that can be used in the frontend as a plugin to the core
Kolibri Javascript code. Each of these Javascript plugins are defined in the kolibri_plugin.py file by subclassing the
KolibriFrontEndPluginBase class to define each frontend Kolibri module. This defines the base Javascript
file that defines the Kolibri module. In addition, this Plugin object within the app will automatically add these Kolibri
82
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
modules to an internal frontend asset registry for loading in the front end. For more information on developing frontend
code for Kolibri please see Front-end Architecture.
Plugins can be standalone Django apps in their own right, meaning they can define templates, models, new urls, and
views just like any other app. However the API for all of this hasn’t yet been determined. . . Coming soon!
Core plugin example
View the source to learn more!
class kolibri.core.kolibri_plugin.KolibriCore
The most minimal plugin possible. Because it’s in the core, it doesn’t define enable or disable. Those
methods should never be called for this plugin.
2.3.7 Release Process
Update the Change log (release notes)
Update the and changelog as necessary. Ideally, this has already been done from individual commits and pull requests,
but it’s good to check.
Create a release branch
Select a release series number and initial version number:
$ SERIES=0.1.x
$ VER=0.1.0a
A quick repetition:
0.1.x
| \
/
|
\
/
|
\
major minor
patch
/
The release branch $SERIES should already exist in the remote upstream, otherwise you should create and push this
branch firstly (major and minor releases have their own release branches):
$ git checkout -b $SERIES upstream/master
$ git push upstream $SERIES
Set the version in the release branch:
$ # edit VERSION in kolibri/__init__.py
$ git add kolibri/__init__.py
$ git commit -m "Bump version to $VER"
Set the version number in the develop branch if necessary.
Push your changes to Github:
$ git push origin releases/$SERIES
2.3. Architecture
83
Kolibri Documentation, Release 0.6.2
Check list before releasing
Before a stable release, make sure that:
• Migrations are squashed
• Dependencies are up to date
Tag the release
We always add git tags to a commit that makes it to a final or pre release. A tag is prefixed v and follows the Semver
convention, for instance v1.2.3-alpha.1.
Warning: Always add tags in release branches. Otherwise, the tag chronology will break. Do not add tags in
feature branches or in the master branch. You can add tags for pre-releases in develop, provided that it is tacking
a series that doesn’t yet have a release branch.
Tag naming conventions
Tags are named like this:
• releases/stable/x.y.z
• releases/alpha/x.y.z-alpha.n
• releases/beta/x.y.z-beta.n
• releases/rc/x.y.z-rc.n
How to tag
Select a series to release from and version number:
$ SERIES=A.B.x
$ VER=releases/stable/A.B.C
$ NEXTVER=releases/alpha/A.B.C-alpha.0
Bump version immediately prior to release and tag the commit, signing your tag:
$
$
$
$
$
git checkout releases/$SERIES
# edit VERSION in kolibri/__init__.py
git add kolibri/__init__.py
git commit -m "Bump version to $VER"
git tag -s $VER
If it’s a stable release, remember to bump version number on release branch for subsequent releases to be the correct
development version. For instance, if you released 1.2.3, you should change the version tuple to be (1, 2, 3,
'alpha', 0). You should also add a new section to the change log:
$
$
$
$
$
# edit VERSION in kolibri/__init__.py
# edit CHANGELOG.rst
git add CHANGELOG.rst kolibri/__init__.py
git commit -m "Switch to track development versions $NEXTVER"
git tag -s $NEXTVER # If 'alpha.0' not in $NEXTVER
84
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Merge to master if this is a stable release in the latest release series:
$ git checkout master
$ git merge v$VER
Push your changes to Github (don’t forget to push the new tag):
$ git push
$ git push upstream --tags
Release to PyPI
Select the version number and checkout the exact git tag:
$ VER=0.1.0
$ git checkout v$VER
Release with PyPI using the make command:
$ make release
Declare victory.
2.3.8 Tech Stack
Kolibri is a web application built primarily using Python on the server-side and JavaScript on the client-side.
We use many run-time, development, and build-related technologies and tools, as outlined below.
Server
The server is a Django 1.9 application, and contains only pure-Python (2.7+) libraries dependencies at run-time.
The server is responsible for:
• Interfacing with the database (PostgreSQL) containing user, content, and language pack data
• Authentication and permission middleware
• Routing and handling of API calls, using the Django REST Framework
• Basic top-level URL routing between high-level sections of the application
• Serving basic HTML wrappers for the UI with data bootstrapped into the page
• Serving additional client assets such as fonts and images
TODO - how does Morango fit into this picture? Logging?
Client
The front-end user interface is built using HTML, the Stylus CSS-preprocessing language, and the ES2015 preset
features of ES6 JavaScript.
The frontend targets IE9 and up, with an emphasis on tablet-size screens. We strive to use accessible, semantic HTML
with support for screen readers, keyboard interaction, and right-to-left language support.
The client is responsible for:
2.3. Architecture
85
Kolibri Documentation, Release 0.6.2
• Compositing and rendering the UI using Vue.js components to build nested views
• Managing client-side state using Vuex
• Interacting with the server through HTTP requests
Additionally, loglevel is used for logging and normalize.css is included for browser style normalization.
Internationalization
We leverage the ICU Message syntax for formatting all user-facing text.
On the client-side, these strings are rendered using Format.js and integrated with Vue.js using vue-intl.
TODO: server-side, message extraction, translation
Developer Docs
Documentation for Kolibri developers are formatted using reStructuredText and the output is generated using Sphinx.
Most of the content is in the /docs directory, but some content is also extracted from Python source code and from
files in the root directory. We use Read the Docs to host a public version of our documentation.
Additionally, information about the design and implementation of Kolibri might be found on Google Drive, Trello,
Slack, InVision, mailing lists, office whiteboards, and lurking in the fragmented collective consciousness of our contributors.
Build Infrastructure
Client-side Resources
We use a combination of both Node.js and Python scripts to transform our source code as-written to the code that is
run in a browser. This process involves webpack, plus a number of both custom and third-party extensions.
Preparation of client-side resources involves:
• ES6 to ES5
• Transforming Vue.js component files (*.vue) into JS and CSS
• Stylus to CSS
• Auto-prefixing CSS
• Bundling multiple JS dependencies into single files
• Minifying and compressing code
• Bundle resources such as fonts and images
• Generating source maps
• Providing mechanisms for decoupled “Kolibri plugins” to interact with each other and asynchronously load
dependencies
• Linting to enforce code styles
86
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Server Setup
The standard Django manage.py commands are used under-the-hood for database migration and user set-up.
TODO: is this accurate?
Installers and Packages
TODO: introduce stack (sdist, PyPi, Debian, Windows, etc)
Continuous Integration
TODO: introduce stack (GitHub, CodeCov, Travis, commit hooks)
Tests and Linting
We use a number of mechanisms to help encourage code quality and consistency. These checks enforce a subset of
our Project Conventions.
• pre-commit is run locally on git commit and enforces some Python conventions
• We use EditorConfig to help developers set their editor preferences
• flake8 is also used to enforce Python conventions
• tox is used to run our test suites under a range of Python and Node environment versions
• sphinx-build -b linkcheck checks the validity of documentation links
• pytest runs our Python unit tests. We also leverage the Django test framework.
• In addition to building client assets, webpack runs linters on client-side code: ESLint for ES6 JavaScript, Stylint
for Stylus, and HTMLHint for HTML and Vue.js components.
• Client-side code is tested using a stack of tools including Karma, Mocha, PhantomJS, Sinon, and rewire. TODO:
Explain what each of these do
• codecov reports on the test coverage for Python and Node.js code. TODO - also client-side?
Helper Scripts
TODO: introduce stack (kolibri command, setup.py, makefiles, yarn commands, sphinx auto-build, etc)
2.3.9 Tests
Running the test suite
A prerequisite for testing is to have the test environment installed. As a developer, simply fetch the “dev” requirements:
$ pip install -r requirements/dev.txt
Running all the tests in your local environment:
2.3. Architecture
87
Kolibri Documentation, Release 0.6.2
$ py.test
Running tests with a specific Python version:
$ tox -e py2.7 # or. . . $ tox -e py3.4 # or. . . $ tox -e py3.5
To run a subset of tests:
$ py.test test/test_kolibri.py
$ # ...or with a tox environment
$ tox -e 3.5 test/test_kolibri.py
JS unit tests
Note: TODO! This will be written by one of the JS devs :)
Testing philosophy
Warning: This section an unfinished draft. We should carefully import stuff from our Dev Bible.
We want to achieve a >90% test coverage! To do that, it’s best to do TDD, meaning to write tests that express what
you want to achieve or fix and then implement that feature or fix the bug.
At all costs? No, definitely not. We don’t want too many slow integration tests that depend on virtual browsers
(Selenium) and the like.
Unreliable tests? Are not welcome at all.
Writing tests
Kolibri has a test discoverer (py.test) which automatically detects Python modules with test cases.
Inside the module that your tests target, place a package test and files called test_my_module.py:
mymodule/
__init__.py
test/
__init__.py
test_feature.py
2.3.10 Version numbers
We follow semantic versioning 2.0.0 according to semver.org but for Python distributions and in the internal string
representation in Python, you will find a PEP-440 flavor.
• 1.1.0 (Semver) = 1.1.0 (PEP-440).
• 1.0.0-alpha.1 (Semver) = 1.0.0a1 (PEP-440).
Here’s how version numbers are generated:
88
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
• kolibri.__version__ is automatically set, runtime environments use it to decide the version of Kolibri
as a string. This is especially something that PyPi and setuptools use.
• kolibri.VERSION is a tuple containing version information, it’s set in kolibri/__init__.py is automatically suffixed in pre-releases by a number of rules defined below. For a final release (not a pre-release), it
will be used exactly as it appears.
• kolibri/VERSION is a file containing the exact version of Kolibri for a distributed environment (pre-releases
only!)
• git describe --tags is a command run to fetch tag information from a git checkout with the Kolibri
code. The information is used to validate the major components of kolibri.VERSION and to suffix the
final version of prereleases. This information is stored permanently in kolibri/VERSION before shipping a
pre-release by calling make writeversion during make dist etc.
Confused? Here’s a table:
Release
type
Final
dev
release
(alpha0)
alpha1+
kolibri.VERSION
kolibri/
Git data
VERSION
Examples
Canonical, only information used
(1, 2, 3, ‘alpha’, 0), 0th alpha = a dev release! Never
used as a canonical
N/A
Fallback
0.1.0, 0.2.2, 0.2.post1
0.4.0.dev020170605181124f1234567
(1, 2, 3, ‘alpha’, 1)
Fallback
beta1+
(1, 2, 3, ‘alpha’, 1)
Fallback
rc1+
(release
candidate)
beta0,
rc0,
post0,
x.y.0
(1, 2, 3, ‘alpha’, 1)
Fallback
Not recommended, but if you use it, your release
transforms into a X.Y.0b0.dev{suffix} release, which
in most cases should be assigned to the preceding release type.
Fallback
N/A
timestamp
of
latest
commit +
hash
git
describe
--tags
Clean head: 1.2.3a1,
Changes since tag:
1.2.3a1.dev123f1234567
git
Clean head: 1.2.3b1,
describe Changes since tag:
--tags
1.2.3b1.dev123f1234567
git
Clean head: 1.2.3rc1,
describe Changes since tag:
--tags
1.2.3rc1.dev123f1234567
timestamp
0.4.0b0.dev020170605181124of
latest f1234567
commit +
hash
Fallback: kolibri/VERSION is auto-generated with make writeversion during the build process. The file
is read as a fallback when there’s no git data available in a pre-release (which is the case in an installed environment).
Release order example 1.2.3 release:
• VERSION = (1, 2, 3, 'alpha', 0) throughout the development phase, this results in a lot of 1.2.
3.dev0YYYYMMDDHHMMSS-1234abcd with no need for git tags.
• VERSION = (1, 2, 3, 'alpha', 1) for the first alpha release. When it’s tagged and released,
Warning: Do not import anything from the rest of Kolibri in this module, it’s crucial that it can be loaded without
the settings/configuration/django stack.
2.3. Architecture
89
Kolibri Documentation, Release 0.6.2
Do not import this file in other package’s __init__, because installation with setup.py should not depend on other
packages. In case you were to have a package foo that depended on kolibri, and kolibri is installed as a dependency
while foo is installing, then foo won’t be able to access kolibri before after setuptools has completed installation of
everything.
How to tag releases
Note: Current practice is to tag releases after bumping kolibri.VERSION. You are allowed to have a newer version in kolibri.VERSION, but you are not allowed to add the tag before actually bumping kolibri.VERSION.
Always use -s to sign your tags. Remember to push tags to remote upstream with git push <upstream>
--tags
1.2.3.dev{auto-suffix} - This is normally NOT tagged, but you may:
tag -s v1.2.3-alpha0
1.2.3a1{auto-suffix}:
tag -s v1.2.3-alpha1
1.2.3b1{auto-suffix}:
tag -s v1.2.3-beta1
1.2.3rc1 - a release candidate:
tag -s v1.2.3-final
1.2.3 - a final release:
tag -s v1.2.3
1.2.3 - a final release fix (in case something went wrong with an attempt to release a final version v1.2.3, should not
contain code changes apart from bumping kolibri.VERSION):
tag -s v1.2.3-post1
2.4 Themes
2.4.1 Content
This is a core module found in kolibri/Content.
Concepts and Definitions
ContentNode
High level abstraction for prepresenting different content kinds, such as Topic, Video, Audio, Exercise, Document,
and can be easily extended to support new content kinds. With multiple ContentNode objects, it supports grouping,
arranging them in tree structure, and symmetric and asymmetric relationship between two ContentNode objects.
90
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
File
A Django model that is used to store details about the source file, such as what language it supports, how big is the
size, which format the file is and where to find the source file.
ContentDB Diagram
**PK = Primary Key **FK = Foreign Key **M2M = ManyToManyField
2.4. Themes
91
Kolibri Documentation, Release 0.6.2
ContentTag
This model is used to establish a filtering system for all ContentNode objects.
ChannelMetadata
A Django model in each content database that stores the database readable names, description and author for each
channel.
ChannelMetadataCache
This class stores the channel metadata cached/denormed into the default database.
Implementation Details and Workflows
To achieve using separate databases for each channel and being able to switch channels dynamically, the following
data structure and utility functions have been implemented.
ContentDBRoutingMiddleware
This middleware will be applied to every request, and will dynamically select a database based on the channel_id. If a
channel ID was included in the URL, it will ensure the appropriate content DB is used for the duration of the request.
(Note: set_active_content_database is thread-local, so this shouldn’t interfere with other parallel requests.)
For example, this is how the client side dynamically requests data from a specific channel:
>>> localhost:8000/api/content/<channel_1_id>/contentnode
this will respond with all the contentnode data stored in database <channel_1_id>.sqlite3
>>> localhost:8000/api/content/<channel_2_id>/contentnode
this will respond with all the contentnode data stored in database <channel_2_id>.sqlite3
get_active_content_database
A utility function to retrieve the temporary thread-local variable that using_content_database sets
set_active_content_database
A utility function to set the temporary thread-local variable
using_content_database
A decorator and context manager to do queries on a specific content DB.
Usage as a context manager:
92
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
from models import ContentNode
with using_content_database("nalanda"):
objects = ContentNode.objects.all()
return objects.count()
Usage as a decorator:
from models import ContentNode
@using_content_database('nalanda')
def delete_all_the_nalanda_content():
ContentNode.objects.all().delete()
ContentDBRouter
A router that decides what content database to read from based on a thread-local variable.
ContentNode
ContentNode is implemented as a Django model that inherits from two abstract classes, MPTTModel and ContentDatabaseModel. django-mptt’s MPTTModel, which allows for efficient traversal and querying of the ContentNode
tree. ContentDatabaseModel is used as a marker so that the content_db_router knows to query against the
content database only if the model inherits from ContentDatabaseModel.
The tree structure is established by the parent field that is a foreign key pointing to another ContentNode object. You can also create a symmetric relationship using the related field, or an asymmetric field using the
is_prerequisite field.
File
The File model also inherits from ContentDatabaseModel.
To find where the source file is located, the class method get_url uses the checksum field and settings.
CONTENT_STORAGE_DIR to calculate the file path. Every source file is named based on its MD5 hash value
(this value is also stored in the checksum field) and stored in a namespaced folder under the directory specified
in settings.CONTENT_STORAGE_DIR. Because it’s likely to have thousands of content files, and some filesystems cannot handle a flat folder with a large number of files very well, we create namespaced subfolders to improve
the performance. So the eventual file path would look something like:
/home/user/.kolibri/content/storage/9/8/9808fa7c560b9801acccf0f6cf74c3ea.
mp4
As you can see, it is fine to store your content files outside of the kolibri project folder as long as you set the
settings.CONTENT_STORAGE_DIR accordingly.
The front-end will then use the extension field to decide which content player should be used. When the
supplementary field’s value is True, that means this File object isn’t necessary and can display the content
without it. For example, we will mark caption (subtitle) file as supplementary.
2.4. Themes
93
Kolibri Documentation, Release 0.6.2
Content Constants
A Python module that stores constants for the kind field in ContentNode model and the preset field and
extension field in File model.
Workflows
There are two workflows we currently designed to handle content UI rendering and content playback rendering
• Content UI Rendering
1. Start with a ContentNode object.
2. Get the associated File object that has the thumbnail field being True.
3. Get the thumbnail image by calling this File’s get_url method.
4. Determine the template using the kind field of this ContentNode object.
5. Renders the template with the thumbnail image.
• Content Playback Rendering
1. Start with a ContentNode object.
2. Retrieve a queryset of associated File objects that are filtered by the preset.
3. Use the thumbnail field as a filter on this queryset to get the File object and call this File object’s get_url
method to get the source file (the thumbnail image)
4. Use the supplementary field as a filter on this queryset to get the “supplementary” File objects, such as
caption (subtitle), and call these File objects’ get_url method to get the source files.
5. Use the supplementary field as a filter on this queryset to get the essential File object. Call its get_url
method to get the source file and use its extension field to choose the content player.
6. Play the content.
API Methods
class kolibri.content.api.OptionalPageNumberPagination
Pagination class that allows for page number-style pagination, when requested. To activate, the page_size argument must be set. For example, to request the first 20 records: ?page_size=20&page=1
API endpoints
request specific content:
>>> localhost:8000/api/content/<channel_id>/contentnode/<content_id>
search content:
>>> localhost:8000/api/content/<channel_id>/contentnode/?search=<search words>
request specific content with specified fields:
>>> localhost:8000/api/content/<channel_id>/contentnode/<content_id>/?fields=pk,title,
˓→kind
94
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
request paginated contents
>>> localhost:8000/api/content/<channel_id>/contentnode/?page=6&page_size=10
request combines different usages
>>> localhost:8000/api/content/<channel_id>/contentnode/?fields=pk,title,kind,
˓→instance_id,description,files&page=6&page_size=10&search=wh
Models
This is one of the Kolibri core components, the abstract layer of all contents. To access it, please use the public APIs
in api.py
The ONLY public object is ContentNode
class kolibri.content.models.AssessmentMetaData(*args, **kwargs)
A model to describe additional metadata that characterizes assessment behaviour in Kolibri. This model contains
additional fields that are only revelant to content nodes that probe a user’s state of knowledge and allow them to
practice to Mastery. ContentNodes with this metadata may also be able to be used within quizzes and exams.
Parameters
• id (UUIDField) – Id
• contentnode_id (ForeignKey to ~) – Contentnode
• assessment_item_ids (JSONField) – Assessment item ids
• number_of_assessments (IntegerField) – Number of assessments
• mastery_model (JSONField) – Mastery model
• randomize (BooleanField) – Randomize
• is_manipulable (BooleanField) – Is manipulable
class kolibri.content.models.ChannelMetadata(*args, **kwargs)
Holds metadata about all existing content databases that exist locally.
Parameters
• id (UUIDField) – Id
• name (CharField) – Name
• description (CharField) – Description
• author (CharField) – Author
• version (IntegerField) – Version
• thumbnail (TextField) – Thumbnail
• last_updated (DateTimeTzField) – Last updated
• min_schema_version (CharField) – Min schema version
• root_id (ForeignKey to ~) – Root
class kolibri.content.models.ContentNode(*args, **kwargs)
The top layer of the contentDB schema, defines the most common properties that are shared across all different
contents. Things it can represent are, for example, video, exercise, audio or document. . .
Parameters
2.4. Themes
95
Kolibri Documentation, Release 0.6.2
• id (UUIDField) – Id
• parent_id (TreeForeignKey to ~) – Parent
• license_name (CharField) – License name
• license_description (CharField) – License description
• title (CharField) – Title
• content_id (UUIDField) – Content id
• channel_id (UUIDField) – Channel id
• description (CharField) – Description
• sort_order (FloatField) – Sort order
• license_owner (CharField) – License owner
• author (CharField) – Author
• kind (CharField) – Kind
• available (BooleanField) – Available
• stemmed_metaphone (CharField) – Stemmed metaphone
• lang_id (ForeignKey to ~) – Lang
• lft (PositiveIntegerField) – Lft
• rght (PositiveIntegerField) – Rght
• tree_id (PositiveIntegerField) – Tree id
• level (PositiveIntegerField) – Level
• has_prerequisite (ManyToManyField) – Has prerequisite
• related (ManyToManyField) – Related
• tags (ManyToManyField) – Tags
get_descendant_content_ids()
Retrieve a queryset of content_ids for non-topic content nodes that are descendants of this node.
class kolibri.content.models.ContentTag(id, tag_name)
Parameters
• id (UUIDField) – Id
• tag_name (CharField) – Tag name
class kolibri.content.models.File(*args, **kwargs)
The second to bottom layer of the contentDB schema, defines the basic building brick for content. Things it can
represent are, for example, mp4, avi, mov, html, css, jpeg, pdf, mp3. . .
Parameters
• id (UUIDField) – Id
• local_file_id (ForeignKey to ~) – Local file
• available (BooleanField) – Available
• contentnode_id (ForeignKey to ~) – Contentnode
• preset (CharField) – Preset
96
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
• lang_id (ForeignKey to ~) – Lang
• supplementary (BooleanField) – Supplementary
• thumbnail (BooleanField) – Thumbnail
• priority (IntegerField) – Priority
get_download_filename()
Return a valid filename to be downloaded as.
get_download_url()
Return the download url.
get_preset()
Return the preset.
class kolibri.content.models.Language(id,
lang_code,
lang_direction)
lang_subcode,
lang_name,
Parameters
• id (CharField) – Id
• lang_code (CharField) – Lang code
• lang_subcode (CharField) – Lang subcode
• lang_name (CharField) – Lang name
• lang_direction (CharField) – Lang direction
class kolibri.content.models.LocalFile(*args, **kwargs)
The bottom layer of the contentDB schema, defines the local state of files on the device storage.
Parameters
• id (CharField) – Id
• extension (CharField) – Extension
• available (BooleanField) – Available
• file_size (IntegerField) – File size
get_storage_url()
Return a url for the client side to retrieve the content file. The same url will also be exposed by the file
serializer.
class kolibri.content.models.UUIDField(*args, **kwargs)
Adaptation of Django’s UUIDField, but with 32-char hex representation as Python representation rather than a
UUID instance.
2.4.2 Server-Client Communication
Server API
The Kolibri server represents data as Django Models. These models are defined in models.py files, which can be
found in the folders of the different Django apps/plugins.
In Django, Model data are usually exposed to users through webpages that are generated by the Django server. To
make the data available to the Kolibri client, which is a single-page app, the Models are exposed as JSON data through
a REST API provided by the Django REST Framework (DRF).
2.4. Themes
97
Kolibri Documentation, Release 0.6.2
In the api.py files, Django REST framework ViewSets are defined which describe how the data is made available
through the REST API. Each ViewSet also requires a defined Serializer, which describes the way in which the data
from the Django model is serialized into JSON and returned through the REST API. Additionally, optional filters can
be applied to the ViewSet which will allow queries to filter by particular features of the data (for example by a field)
or by more complex constraints, such as which group the user associated with the data belongs to. Permissions can be
applied to a ViewSet, allowing the API to implicitly restrict the data that is returned, based on the currently logged in
user.
Finally, in the api_urls.py file, the ViewSets are given a name (through the base_name keyword argument),
which sets a particular URL namespace, which is then registered and exposed when the Django server runs. Sometimes, a more complex URL scheme is used, as in the content core app, where every query is required to be prefixed
by a channel id (hence the <channel_id> placeholder in that route’s regex pattern)
Listing 2.1: api_urls.py
router = routers.SimpleRouter()
router.register('content', ChannelMetadataCacheViewSet, base_name="channel")
content_router = routers.SimpleRouter()
content_router.register(r'contentnode', ContentNodeViewset, base_name='contentnode')
content_router.register(r'file', FileViewset, base_name='file')
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^content/(?P<channel_id>[^/.]+)/', include(content_router.urls)),
]
Client Resource Layer
To access this REST API in the frontend Javascript code, an abstraction layer has been written to reduce the complexity
of inferring URLs, caching resources, and saving data back to the server.
Resources
In order to access a particular REST API endpoint, a Javascript Resource has to be defined, an example is shown here
Listing 2.2: channel.js
const Resource = require('kolibri.lib.apiResource').Resource;
class ChannelResource extends Resource {
static resourceName() {
return 'channel';
}
}
module.exports = ChannelResource;
Here, the resourceName static method must return 'channel' in order to match the base_name assigned to
the /content endpoint in api_urls.py.
However, in the case of a more complex endpoint, where arguments are required to form the URL itself (such as in the
contentnode endpoints above) - we can add additional required arguments with the resourceIdentifiers
static method return value
98
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Listing 2.3: contentNode.js
const Resource = require('kolibri.lib.apiResource').Resource;
class ContentNodeResource extends Resource {
static resourceName() {
return 'contentnode';
}
static idKey() {
return 'pk';
}
static resourceIdentifiers() {
// because ContentNode resources are accessed via
// /api/content/<channel_id>/contentnode/<pk>
return [
'channel_id',
];
}
}
module.exports = ContentNodeResource;
If this resource is part of the core app, it can be added to a global registry of resources inside kolibri/core/
assets/src/api-resources/index.js. Otherwise, it can be instantiated as needed, such as in the coach
reports module
const ContentSummaryResourceConstructor = require('./apiResources/contentSummary');
const ContentSummaryResource = new ContentSummaryResourceConstructor(coreApp);
First the constructor is imported from the require file, and then an instance is created - with a reference to the Kolibri
core app module passed as the only argument.
Models
The instantiated Resource can then be queried for client side representations of particular information. For a representation of a single server side Django model, we can request a Model from the Resource, using getModel
// corresponds to resource address /api/content/<channelId>/contentnode/<id>
const contentModel = ContentNodeResource.getModel(id, { channel_id: channelId });
The first argument is the database id (primary key) for the model, while the second argument defines any additional
required resourceIdentifiers that we need to build up the URL.
We now have a reference for a representation of the data on the server. To ensure that it has data from the server, we
can call .fetch on it which will resolve to an object representing the data
contentModel.fetch().then((data) => {
logging.info('This is the model data: ', data);
});
The fetch method returns a Promise which resolves when the data has been successfully retrieved. This may have
been due to a round trip call to the REST API, or, if the data has already been previously returned, then it will skip the
call to the REST API and return a cached copy of the data.
If you want to pass additional GET parameters to the REST API (to only return a limited set of fields, for example),
then you can pass GET parameters in the first argument
2.4. Themes
99
Kolibri Documentation, Release 0.6.2
contentModel.fetch({ title: true }).then((data) => {
logging.info('This is the model data: ', data);
});
If it is important to get data that has not been cached, you can call the fetch method with a force parameter
contentModel.fetch({}, true).then((data) => {
logging.info('This is definitely the most up to date model data: ', data);
});
Collections
For particular views on a data table (which could range from ‘show me everything’ to ‘show me all content nodes
with titles starting with “p”’) - Collections are used. Collections are a cached view onto the data table, which are
populated by Models - so if a Model that has previously been fetched from the server by a Collection is requested from
getModel, it is already cachced.
The first argument defines any additional required resourceIdentifiers that we need to build up the URL,
while the second argument defines the GET parameters that are used to define the filters to be applied to the data and
hence the subset of the data that the Collection represents.
We now have a reference for a representation of this data on the server. To ensure that it has data from the server, we
can call fetch on it, this will resolve to an array of the returned data objects
contentCollection.fetch().then((dataArray) => {
logging.info('This is the model data: ', dataArray);
});
The fetch method returns a Promise which resolves when the data has been successfully retrieved. This may have
been due to a round trip call to the REST API, or, if the data has already been previously returned, then it will skip the
call to the REST API and return a cached copy of the data.
If you want to pass additional GET parameters to the REST API (to only return a limited set of fields, for example),
then you can pass GET parameters in the first argument
// GET /api/content/<channelId>/contentnode/?popular=1&title=true
contentCollection.fetch({ title: true }).then((dataArray) => {
logging.info('This is the model data: ', dataArray);
});
If it is important to get data that has not been cached, you can call the fetch method with a force parameter
contentCollection.fetch({}, true).then((dataArray) => {
logging.info('This is the model data: ', dataArray);
});
Data Flow Diagram
2.4.3 Users, Authentication, and Permissions
This is a core module found in kolibri/auth.
100
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Concepts and Definitions
Facility
All user data (accounts, logs, ratings, etc) in Kolibri are associated with a particular “Facility”. A Facility is a grouping
of users who are physically co-located, and who generally access Kolibri from the same server on a local network, for
example in a school, library, or community center. Collectively, all the data associated with a particular Facility are
referred to as a “Facility Dataset”.
Users
There are two kinds of users: FacilityUser and DeviceOwner. A FacilityUser is associated with a particular Facility, and the user’s account and data may be synchronized across multiple devices. A DeviceOwner
account is not associated with a particular Facility, but is specific to one device, and is never synchronized across
multiple devices. A DeviceOwner is like a superuser, and has permissions to modify any data on her own device, whereas a FacilityUser only has permissions for some subset of data from their own Facility Dataset (as
determined in part by the roles they possess; see below).
Collections
Collections are hierarchical groups of users, used for grouping users and making decisions about permissions. Users
can have roles for one or more Collections, by way of obtaining Roles associated with those Collections. Collections
can belong to other Collections, and user membership in a collection is conferred through Membership. Collections
are subdivided into several pre-defined levels: Facility, Classroom, and LearnerGroup, as illustrated here:
In this illustration, Facility X contains two Classrooms, Class A and Class B. Class A contains two LearnerGroups,
Group Q and Group R.
Membership
A FacilityUser (but not a DeviceOwner) can be marked as a member of a Collection through a
Membership object. Being a member of a Collection also means being a member of all the Collections above
that Collection in the hierarchy. Thus, in the illustration below, Alice is directly associated with Group Q through a
Membership object, which makes her a member of Group Q. As Group Q is contained within Class A, which is
contained within Facility X, she is also implicitly a member of both those collections.
Note also that a FacilityUser is always implicitly a member of the Facility with which it is associated, even
if it does not have any Membership objects.
Roles
Another way in which a FacilityUser can be associated with a particular Collection is through a Role
object, which grants the user a role with respect to the Collection and all the collections below it. A Role object
also stores the “kind” of the role (currently, one of “admin” or “coach”), which affects what permissions the user gains
through the Role.
To illustrate, consider the example in the following figure:
2.4. Themes
101
Kolibri Documentation, Release 0.6.2
The figure shows a Role object linking Bob with Class A, and the Role is marked with kind “coach”, which we can
informally read as “Bob is a coach for Class A”. We consider user roles to be “downward-transitive” (meaning if you
have a role for a collection, you also have that role for descendents of that collection). Thus, in our example, we can
say that “Bob is also a coach for Group Q”. Furthermore, as Alice is a member of Group Q, we can say that “Bob is a
coach for Alice”.
Role-Based Permissions
As a lot of Facility Data in Kolibri is associated with a particular FacilityUser, for many objects we can concisely
define a requesting user’s permissions in terms of his or her roles for the object’s associated User. For example, if a
ContentLog represents a particular FacilityUser’s interaction with a piece of content, we might decide that
another FacilityUser can view the ContentLog if she is a coach (has the coach role) for the user. In our
scenario above, this would mean that Bob would have read permissions for a ContentLog for which “user=Alice”,
by virtue of having the coach role for Alice.
Some data may not be related to a particular user, but rather with a Collection (e.g. the Collection object
itself, settings for a Collection, or content assignments for a Collection). Permissions for these objects can be
defined in terms of the role the requesting User has with respect to the object’s associated Collection. So, for example,
we might allow Bob to assign content to Class A on the basis of him having the “coach” role for Class A.
Permission Levels
As we are constructing a RESTful API for accessing data within Kolibri, the core actions for which we need to define
permissions are the CRUD operations (Create, Read, Update, Delete). As Create, Update, and Delete permissions
often go hand in hand, we can collectively refer to them as “Write Permissions”.
Implementation Details
Collections
A Collection is implemented as a Django model that inherits from django-mptt’s MPTTModel, which allows for efficient traversal and querying of the collection hierarchy. For convenience, the specific types of collections – Facility, Classroom, and LearnerGroup – are implemented as _proxy models of the main
Collection model. There is a kind field on Collection that allows us to distinguish between these types,
and the ModelManager for the proxy models returns only instances of the matching kind.
From a Collection instance, you can traverse upwards in the tree with the parent field, and downwards via the
children field (which is a reverse RelatedManager for the parent field):
>>> my_classroom.parent
<Collection: "Facility X" (facility)>
>>> my_facility.children.all()
[<Collection: "Class A" (classroom)>, <Collection: "Class B" (classroom)>]
Note that the above methods (which are provided by MPTTModel) return generic Collection instances, rather
than specific proxy model instances. To retrieve parents and children as appropriate proxy models, use the helper
methods provided on the proxy models, e.g.:
102
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
>>> my_classroom.get_facility()
<Facility: Facility X>
>>> my_facility.get_classrooms()
[<Classroom: Class A>, <Classroom: Class B>]
Facility and FacilityDataset
The Facility model (a proxy model for Collection, as described above) is special in that it has no parent; it
is the root of a tree. A Facility model instance, and all other Facility Data associated with the Facility and its
FacilityUsers, inherits from AbstractFacilityDataModel, which has a dataset field that foreign keys
onto a common FacilityDataset instance. This makes it easy to check, for purposes of permissions or filtering
data for synchronization, which instances are part of a particular Facility Dataset. The dataset field is automatically
set during the save method, by calling the infer_dataset method, which must be overridden in every subclass
of AbstractFacilityDataModel to return the dataset to associate with that instance.
Efficient Hierarchy Calculations
In order to make decisions about whether a user has a certain permission for an object, we need an efficient way
to retrieve the set of roles the user has in relation to that object. This involves traversing the Role table, Collection
hierarchy, and possibly the Membership table, but we can delegate most of the work to the database engine (and
leverage efficient hierarchy lookups afforded by MPTT). The following algorithms and explanations will refer to the
naming in the following diagram:
In pseudocode, the query for “What Roles does Source User have in relation to Target User?” would be implemented
in the following way:
Fetch all Roles with:
User: Source User
Collection: Ancestor Collection
For which there is a Membership with:
User: Target User
Collection: Descendant Collection
And where:
Ancestor Collection is an ancestor of (or equal to) Descendant Collection
At the database level, this can be written in the following way, as a single multi-table SQL query:
SELECT DISTINCT
source_role.kind
FROM
collection_table AS ancestor_coll,
collection_table AS descendant_coll,
role_table,
membership_table
WHERE
role_table.user_id = {source_user_id} AND
role_table.collection_id = ancestor_coll.id AND
membership_table.user_id = {target_user_id}
membership_table.collection_id = descendant_coll.id AND
descendant_coll.lft BETWEEN ancestor_coll.lft AND ancestor_coll.rght AND
descendant_coll.tree_id = ancestor_coll.tree_id;
2.4. Themes
103
Kolibri Documentation, Release 0.6.2
Similarly, performing a queryset filter like “give me all ContentLogs associated with FacilityUsers for which
Source User has an admin role” can be written as:
SELECT
contentlog_table.*
FROM
contentlog_table
WHERE EXISTS
(SELECT
*
FROM
collection_table AS ancestor_coll,
collection_table AS descendant_coll,
role_table,
membership_table
WHERE
role_table.user_id = {source_user_id} AND
role_table.collection_id = ancestor_coll.id AND
membership_table.user_id = contentlog_table.user_id
membership_table.collection_id = descendant_coll.id AND
descendant_coll.lft BETWEEN ancestor_coll.lft AND ancestor_coll.rght AND
descendant_coll.tree_id = ancestor_coll.tree_id
)
Note the membership_table.user_id = contentlog_table.user_id condition, which links the rolemembership-collection hierarchy subquery into the main query. We refer to this condition as the “anchor”.
To facilitate making queries that leverage the role-membership-collection hierarchy, without needing to write custom
SQL each time, we have implemented a HierarchyRelationsFilter helper class. The class is instantiated by
passing in a queryset, and then exposes a filter_by_hierarchy method that allows various parts of the rolemembership-collection hierarchy to be constrained, and anchored back into the queryset’s main table. It then returns
a filtered queryset (with appropriate conditions applied) upon which further filters or other queryset operations can be
applied.
The signature for filter_by_hierarchy is:
def filter_by_hierarchy(self,
source_user=None,
role_kind=None,
ancestor_collection=None,
descendant_collection=None,
target_user=None):
With the exception of role_kind (which is either a string or list of strings, of role kinds), these parameters accept
either:
• A model instance (either a FacilityUser or a Collection subclass, as appropriate) or its ID
• An F expression that anchors some part of the hierarchy back into the base queryset model (the simplest usage
is just to put the name of a field from the base model in the F function, but you can also indirectly reference
fields of related models, e.g. F("collection__parent"))
For example, the ContentLog query described above (“give me all ContentLogs associated with
FacilityUsers for which Source User has an admin role”) can be implemented as:
contentlogs = HierarchyRelationsFilter(ContentLog.objects.all()).filter_by_hierarchy(
source_user=my_source_user, # specify the specific user to be the source user
role_kind=role_kinds.ADMIN, # make sure the Role is an admin role
104
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
˓→
target_user=F("user"),
ContentLog model
# anchor the target user to the "user" field of the
)
Managing Roles and Memberships
User and Collection models have various helper methods for retrieving and modifying roles and memberships:
• To get all the members of a collection (including those of its descendant collections), use Collection.
get_members().
• To add or remove roles/memberships, use the add_role, remove_role, add_member, and
remove_member methods of Collection (or the additional convenience methods, such as add_admin,
that exist on the proxy models).
• To check whether a user is a member of a Collection, use KolibriAbstractBaseUser.
is_member_of (for DeviceOwner, this always returns False)
• To check whether a user has a particular kind of role for a collection or another
user,
use the has_role_for_collection and has_role_for_user methods of
KolibriAbstractBaseUser.
• To list all role kinds a user has for a collection or another user, use the get_roles_for_collection and
get_roles_for_user methods of KolibriAbstractBaseUser.
Encoding Permission Rules
We need to associate a particular set of rules with each model, to specify the permissions that users should have
in relation to instances of that model. While not all models have the same rules, there are some broad categories
of models that do share the same rules (e.g. ContentInteractionLog, ContentSummaryLog, and UserSessionLog –
collectively, “User Log Data”). Hence, it is useful to encapsulate a permissions “class” that can be reused across
models, and extended (through inheritance) if slightly different behavior is needed. These classes of permissions are
defined as Python classes that inherit from kolibri.auth.permissions.base.BasePermissions, which defines the following
overridable methods:
• The following four Boolean (True/False) permission checks, corresponding to the “CRUD” operations: - user_can_create_object - user_can_read_object - user_can_update_object user_can_delete_object
• The queryset-filtering readable_by_user_filter method, which takes in a queryset and returns a queryset filtered down to just objects that should be readable by the user.
Associating Permissions with Models
A model is associated with a particular permissions class through a “permissions” attribute defined on the top level
of the model class, referencing an instance of a Permissions class (a class that subclasses BasePermissions).
For example, to specify that a model ContentSummaryLog should draw its permissions rules from the
UserLogPermissions class, modify the model definition as follows:
class ContentSummaryLog(models.Model):
permissions = UserLogPermissions()
<remainder of model definition>
2.4. Themes
105
Kolibri Documentation, Release 0.6.2
Specifying Role-Based Permissions
Defining a custom Permissions class and overriding its methods allows for arbitrary logic to be used in defining the
rules governing the permissions, but many cases can be covered by more constrained rule specifications. In particular,
the rules for many models can be specified in terms of the role- based permissions system described above. A builtin subclass of BasePermissions, called RoleBasedPermissions, makes this easy. Creating an instance of
RoleBasedPermissions involves passing in the following parameters:
• Tuples of role kinds that should be granted each of the CRUD permissions, encoded in the following parameters:
can_be_created_by, can_be_read_by, can_be_updated_by, can_be_deleted_by.
• The target_field parameter that determines the “target” object for the role-checking; this should be the
name of a field on the model that foreign keys either onto a FacilityUser or a Collection. If the model
we’re checking permissions for is itself the target, then target_field may be ".".
An example, showing that read permissions should be granted to a coach or admin for the user referred to by the
model’s “user” field. Similarly, write permissions should only be available to an admin for the user:
class UserLog(models.Model):
permissions = RoleBasedPermissions(
target_field="user",
can_be_created_by=(role_kinds.ADMIN,),
can_be_read_by=(role_kinds.COACH, role_kinds.ADMIN),
can_be_updated_by=(role_kinds.ADMIN,),
can_be_deleted_by=(role_kinds.ADMIN,),
)
<remainder of model definition>
Built-in Permissions Classes
Some common rules are encapsulated by the permissions classes in kolibri.auth.permissions.general.
These include:
• IsOwn: only allows access to the object if the object belongs to the requesting user (in other words, if the object
has a specific field, field_name, that foreign keys onto the user)
• IsFromSameFacility: only allows access to object if user is associated with the same facility as the object
• IsSelf: only allows access to the object if the object is the user
A general pattern with these provided classes is to allow an argument called read_only, which means that rather
than allowing both write (create, update, delete) and read permissions, they will only grant read permission. So, for example, IsFromSameFacility(read_only=True) will allow any user from the same facility to read the model,
but not to write to it, whereas IsFromSameFacility(read_only=False) or IsFromSameFacility()
would allow both.
Combining Permissions Classes
In many cases, it may be necessary to combine multiple permission classes together to define the ruleset
that you want. This can be done using the Boolean operators | (OR) and & (AND). So, for example,
IsOwn(field_name="user") | IsSelf() would allow access to the model if either the model has a foreign key named “user” that points to the user, or the model is itself the user model. Combining two permission classes
with &, on the other hand, means both classes must return True for a permission to be granted. Note that permissions
106
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
classes combined in this way still support the readable_by_user_filter method, returning a queryset that is
either the union (for |) or intersection (&) of the querysets that were returned by each of the permissions classes.
Checking Permissions
Checking whether a user has permission to perform a CRUD operation on an object involves calling the appropriate
methods on the KolibriAbstractBaseUser (FacilityUser or DeviceOwner) instance. For instance, to
check whether request.user has delete permission for ContentSummaryLog instance log_obj, you could do:
if request.user.can_delete(log_obj):
log_obj.delete()
Checking whether a user can create an object is slightly different, as you may not yet have an instance of the model.
Instead, pass in the model class and a dict of the data that you want to create it with:
data = {"user": request.user, "content_id": "qq123"}
if request.user.can_create(ContentSummaryLog, data):
ContentSummaryLog.objects.create(**data)
To efficiently filter a queryset so that it only includes records that the user should have permission to read (to make
sure you’re not sending them data they shouldn’t be able to access), use the filter_readable method:
all_results = ContentSummaryLog.objects.filter(content_id="qq123")
permitted_results = request.user.filter_readable(all_results)
Note that for the DeviceOwner model, these methods will simply return True (or unfiltered querysets), as device
owners are considered superusers. For the FacilityUser model, they defer to the permissions encoded in the
permission object on the model class.
Using Kolibri Permissions with Django REST Framework
There are two classes that make it simple to leverage the permissions system described above within a Django REST
Framework ViewSet, to restrict permissions appropriately on API endpoints, based on the currently logged-in user.
KolibriAuthPermissions is a subclass of rest_framework.permissions.BasePermission that
defers to our KolibriAbstractBaseUser permissions interface methods for determining which object-level
permissions to grant to the current user:
• Permissions for ‘POST’ are based on request.user.can_create
• Permissions for ‘GET’, ‘OPTIONS’ and ‘HEAD’ are based on request.user.can_read (Note that
adding KolibriAuthPermissions only checks object-level permissions, and does not filter queries made
against a list view; see KolibriAuthPermissionsFilter below)
• Permissions for ‘PUT’ and ‘PATCH’ are based on request.user.can_update
• Permissions for ‘DELETE’ are based on request.user.can_delete
KolibriAuthPermissions is a subclass of rest_framework.filters.BaseFilterBackend that filters list views to include only records for which the current user has read permissions. This only applies to ‘GET’
requests.
For example, to use the Kolibri permissions system to restrict permissions for an API endpoint providing access to a
ContentLog model, you would do the following:
2.4. Themes
107
Kolibri Documentation, Release 0.6.2
from kolibri.auth.api import KolibriAuthPermissions, KolibriAuthPermissionsFilter
class FacilityViewSet(viewsets.ModelViewSet):
permission_classes = (KolibriAuthPermissions,)
filter_backends = (KolibriAuthPermissionsFilter,)
queryset = ContentLog.objects.all()
serializer_class = ContentLogSerializer
Models
We have four main abstractions: Users, Collections, Memberships, and Roles.
Users represent people, like students in a school, teachers for a classroom, or volunteers setting up informal installations. A FacilityUser belongs to a particular facility, and has permissions only with respect to other data that
is associated with that facility. FacilityUser accounts (like other facility data) may be synced across multiple
devices.
Collections form a hierarchy, with Collections able to belong to other Collections. Collections are subdivided into
several pre-defined levels (Facility > Classroom > LearnerGroup).
A FacilityUser (but not a DeviceOwner) can be marked as a member of a Collection through a
Membership object. Being a member of a Collection also means being a member of all the Collections above
that Collection in the hierarchy.
Another way in which a FacilityUser can be associated with a particular Collection is through a Role
object, which grants the user a role with respect to the Collection and all the collections below it. A Role object
also stores the “kind” of the role (currently, one of “admin” or “coach”), which affects what permissions the user gains
through the Role.
class kolibri.auth.models.AbstractFacilityDataModel(*args, **kwargs)
Base model for Kolibri “Facility Data”, which is data that is specific to a particular Facility, such as
FacilityUsers, Collections, and other data associated with those users and collections.
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
ensure_dataset(*args, **kwargs)
If no dataset has yet been specified, try to infer it. If a dataset has already been specified, to prevent inconsistencies, make sure it matches the inferred dataset, otherwise raise a KolibriValidationError. If
we have no dataset and it can’t be inferred, we raise a KolibriValidationError exception as well.
infer_dataset(*args, **kwargs)
This method is used by ensure_dataset to “infer” which dataset should be associated with this instance. It
should be overridden in any subclass of AbstractFacilityDataModel, to define a model-specific
inference.
class kolibri.auth.models.Classroom(id,
_morango_dirty_bit,
_morango_partition, dataset,
lft, rght, tree_id, level)
_morango_source_id,
name, parent, kind,
Parameters
108
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• name (CharField) – Name
• parent_id (TreeForeignKey to ~) – Parent
• kind (CharField) – Kind
• lft (PositiveIntegerField) – Lft
• rght (PositiveIntegerField) – Rght
• tree_id (PositiveIntegerField) – Tree id
• level (PositiveIntegerField) – Level
get_facility()
Gets the Classroom’s parent Facility.
Returns A Facility instance.
get_learner_groups()
Returns a QuerySet of LearnerGroups associated with this Classroom.
Returns A LearnerGroup QuerySet.
class kolibri.auth.models.Collection(*args, **kwargs)
Collections are hierarchical groups of FacilityUsers, used for grouping users and making decisions
about permissions. FacilityUsers can have roles for one or more Collections, by way of obtaining
Roles associated with those Collections. Collections can belong to other Collections, and user
membership in a Collection is conferred through Memberships. Collections are subdivided into
several pre-defined levels.
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• name (CharField) – Name
• parent_id (TreeForeignKey to ~) – Parent
• kind (CharField) – Kind
• lft (PositiveIntegerField) – Lft
• rght (PositiveIntegerField) – Rght
• tree_id (PositiveIntegerField) – Tree id
• level (PositiveIntegerField) – Level
2.4. Themes
109
Kolibri Documentation, Release 0.6.2
add_member(user)
Create a Membership associating the provided user with this Collection. If the Membership
object already exists, just return that, without changing anything.
Parameters user – The FacilityUser to add to this Collection.
Returns The Membership object (possibly new) that associates the user with the
Collection.
add_role(user, role_kind)
Create a Role associating the provided user with this collection, with the specified kind of role. If the
Role object already exists, just return that, without changing anything.
Parameters
• user – The FacilityUser to associate with this Collection.
• role_kind – The kind of role to give the user with respect to this Collection.
Returns The Role object (possibly new) that associates the user with the Collection.
remove_member(user)
Remove any Membership objects associating the provided user with this Collection.
Parameters user – The FacilityUser to remove from this Collection.
Returns True if a Membership was removed, False if there was no matching
Membership to remove.
remove_role(user, role_kind)
Remove any Role objects associating the provided user with this Collection, with the specified kind
of role.
Parameters
• user – The FacilityUser to dissociate from this Collection (for the specific role
kind).
• role_kind – The kind of role to remove from the user with respect to this
Collection.
class kolibri.auth.models.Facility(id,
_morango_dirty_bit,
_morango_source_id,
_morango_partition, dataset, name, parent, kind, lft,
rght, tree_id, level)
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• name (CharField) – Name
• parent_id (TreeForeignKey to ~) – Parent
• kind (CharField) – Kind
• lft (PositiveIntegerField) – Lft
• rght (PositiveIntegerField) – Rght
• tree_id (PositiveIntegerField) – Tree id
110
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
• level (PositiveIntegerField) – Level
get_classrooms()
Returns a QuerySet of Classrooms under this Facility.
Returns A Classroom QuerySet.
class kolibri.auth.models.FacilityDataset(*args, **kwargs)
FacilityDataset stores high-level metadata and settings for a particular Facility. It is also the
model that all models storing facility data (data that is associated with a particular facility, and that inherits from AbstractFacilityDataModel) foreign key onto, to indicate that they belong to this particular
Facility.
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• description (TextField) – Description
• location (CharField) – Location
• preset (CharField) – Preset
• learner_can_edit_username (BooleanField) – Learner can edit username
• learner_can_edit_name (BooleanField) – Learner can edit name
• learner_can_edit_password (BooleanField) – Learner can edit password
• learner_can_sign_up (BooleanField) – Learner can sign up
• learner_can_delete_account (BooleanField) – Learner can delete account
• learner_can_login_with_no_password (BooleanField) – Learner can login
with no password
class kolibri.auth.models.FacilityUser(*args, **kwargs)
FacilityUser is the fundamental object of the auth app. These users represent the main users, and can be
associated with a hierarchy of Collections through Memberships and Roles, which then serve to help
determine permissions.
Parameters
• password (CharField) – Password
• last_login (DateTimeField) – Last login
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• username (CharField) – Required. 30 characters or fewer. Letters and digits only
• full_name (CharField) – Full name
• date_joined (DateTimeTzField) – Date joined
2.4. Themes
111
Kolibri Documentation, Release 0.6.2
• facility_id (ForeignKey to ~) – Facility
class kolibri.auth.models.KolibriAbstractBaseUser(*args, **kwargs)
Our custom user type, derived from AbstractBaseUser as described in the Django docs. Draws liberally
from django.contrib.auth.AbstractUser, except we exclude some fields we don’t care about, like
email.
This model is an abstract model, and is inherited by FacilityUser.
Parameters
• password (CharField) – Password
• last_login (DateTimeField) – Last login
• username (CharField) – Required. 30 characters or fewer. Letters and digits only
• full_name (CharField) – Full name
• date_joined (DateTimeTzField) – Date joined
can_create(Model, data)
Checks whether this user (self) has permission to create an instance of Model with the specified attributes
(data).
This method defers to the can_create_instance method, and in most cases should not itself be
overridden.
Parameters
• Model – A subclass of django.db.models.Model
• data – A dict of data to be used in creating an instance of the Model
Returns True if this user should have permission to create an instance of Model with the specified data, else False.
Return type bool
can_create_instance(obj)
Checks whether this user (self) has permission to create a particular model instance (obj).
This method should be overridden by classes that inherit from KolibriAbstractBaseUser.
In general, unless an instance has already been initialized, this method should not be called directly;
instead, it should be preferred to call can_create.
Parameters obj – An (unsaved) instance of a Django model, to check permissions for.
Returns True if this user should have permission to create the object, otherwise False.
Return type bool
can_delete(obj)
Checks whether this user (self) has permission to delete a particular model instance (obj).
This method should be overridden by classes that inherit from KolibriAbstractBaseUser.
Parameters obj – An instance of a Django model, to check permissions for.
Returns True if this user should have permission to delete the object, otherwise False.
Return type bool
can_read(obj)
Checks whether this user (self) has permission to read a particular model instance (obj).
This method should be overridden by classes that inherit from KolibriAbstractBaseUser.
112
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Parameters obj – An instance of a Django model, to check permissions for.
Returns True if this user should have permission to read the object, otherwise False.
Return type bool
can_update(obj)
Checks whether this user (self) has permission to update a particular model instance (obj).
This method should be overridden by classes that inherit from KolibriAbstractBaseUser.
Parameters obj – An instance of a Django model, to check permissions for.
Returns True if this user should have permission to update the object, otherwise False.
Return type bool
filter_readable(queryset)
Filters a queryset down to only the elements that this user should have permission to read.
Parameters queryset – A QuerySet instance that the filtering should be applied to.
Returns Filtered QuerySet including only elements that are readable by this user.
get_roles_for(obj)
Helper function that defers to get_roles_for_user or get_roles_for_collection based on
the type of object passed in.
get_roles_for_collection(coll)
Determine all the roles this user has in relation to the specified Collection, and return a set containing
the kinds of roles.
Parameters coll – The target Collection for which this user has the roles.
Returns The kinds of roles this user has with respect to the specified Collection.
Return type set of kolibri.auth.constants.role_kinds.* strings
get_roles_for_user(user)
Determine all the roles this user has in relation to the target user, and return a set containing the kinds of
roles.
Parameters user – The target user for which this user has the roles.
Returns The kinds of roles this user has with respect to the target user.
Return type set of kolibri.auth.constants.role_kinds.* strings
has_role_for(kinds, obj)
Helper function that defers to has_role_for_user or has_role_for_collection based on the
type of object passed in.
has_role_for_collection(kinds, coll)
Determine whether this user has (at least one of) the specified role kind(s) in relation to the specified
Collection.
Parameters
• kinds (string from kolibri.auth.constants.role_kinds.*) – The
kind (or kinds) of role to check for, as a string or iterable.
• coll – The target Collection for which this user has the roles.
Returns True if this user has the specified role kind with respect to the target Collection,
otherwise False.
Return type bool
2.4. Themes
113
Kolibri Documentation, Release 0.6.2
has_role_for_user(kinds, user)
Determine whether this user has (at least one of) the specified role kind(s) in relation to the specified user.
Parameters
• user – The user that is the target of the role (for which this user has the roles).
• kinds (string from kolibri.auth.constants.role_kinds.*) – The kind (or
kinds) of role to check for, as a string or iterable.
Returns True if this user has the specified role kind with respect to the target user, otherwise
False.
Return type bool
is_member_of(coll)
Determine whether this user is a member of the specified Collection.
Parameters coll – The Collection for which we are checking this user’s membership.
Returns True if this user is a member of the specified Collection, otherwise False.
Return type bool
class kolibri.auth.models.KolibriAnonymousUser
Custom anonymous user that also exposes the same interface as KolibriAbstractBaseUser, for consistency.
Parameters
• password (CharField) – Password
• last_login (DateTimeField) – Last login
• username (CharField) – Required. 30 characters or fewer. Letters and digits only
• full_name (CharField) – Full name
• date_joined (DateTimeTzField) – Date joined
class kolibri.auth.models.LearnerGroup(id,
_morango_dirty_bit,
_morango_source_id,
_morango_partition, dataset, name, parent, kind, lft,
rght, tree_id, level)
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• name (CharField) – Name
• parent_id (TreeForeignKey to ~) – Parent
• kind (CharField) – Kind
• lft (PositiveIntegerField) – Lft
• rght (PositiveIntegerField) – Rght
• tree_id (PositiveIntegerField) – Tree id
• level (PositiveIntegerField) – Level
114
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
get_classroom()
Gets the LearnerGroup’s parent Classroom.
Returns A Classroom instance.
class kolibri.auth.models.Membership(*args, **kwargs)
A FacilityUser can be marked as a member of a Collection through a Membership object. Being a
member of a Collection also means being a member of all the Collections above that Collection
in the tree (i.e. if you are a member of a LearnerGroup, you are also a member of the Classroom that
contains that LearnerGroup, and of the Facility that contains that Classroom).
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• user_id (ForeignKey to ~) – User
• collection_id (TreeForeignKey to ~) – Collection
class kolibri.auth.models.Role(*args, **kwargs)
A FacilityUser can have a role for a particular Collection through a Role object, which also stores
the “kind” of the Role (currently, one of “admin” or “coach”). Having a role for a Collection also implies
having that role for all sub-collections of that Collection (i.e. all the Collections below it in the tree).
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• user_id (ForeignKey to ~) – User
• collection_id (TreeForeignKey to ~) – Collection
• kind (CharField) – Kind
2.4.4 User Management
For now, this is a high-level spec that identifies the major components of a work-in-progress part of Kolibri. It is a
mixture of a descriptive specification for an app, as well as how it interacts with the kolibri.auth back-end layer below
it. Eventually, it could serve as a user manual.
The User Management allows a user with sufficient permissions to do a number of things related to managing accounts
and roles. It’s divided into two distinct sections
Learner Management
Learner Management provides an interface for:
2.4. Themes
115
Kolibri Documentation, Release 0.6.2
1. Viewing Classrooms and Learner Groups and a list of the learners they contain.
2. Creating new Classrooms.
3. Creating new Learner Groups.
4. Creating new user accounts and assigning them to Classrooms and Learner Groups.
5. Assigning existing accounts individually or in batches to Classrooms and Learner Groups.
6. Editing a learner’s details, including resetting their password.
The main interface of the Learner Management app is currently described in the mailing list thread “Learner Management app in Kolibri”. We assume the session user (the user who is visiting Learner Management) has write permissions
for any object represented in the Learner Management interface. For example, only classrooms and learner groups for
which the user has write permissions will be displayed in the Classroom and Group Selectors. In practice this could
mean that when the page loads a list of classrooms for which the session user is either a coach or admin will be fetched.
At the time of this writing, the only source to determine which users enjoy which permissions is the kolibri.auth
test suite.
Note: Roadmap: Moving forward, we are making digital prototypes for Learner Management. The aim is to get
quality feedback from likely users to inform the design. IMO it is premature to consider a design as stable prior to such
feedback. Role Management should be given a similar treatment – quickly create digital prototypes and get quality
feedback.
Learner Management has several conceptual parts. These may not reflect how they’re divided as Vue components,
so I try to reference the current implementation below. The application corresponds to management/assets/
app-root.vue and has several subcomponents.
Learner Roster
Displays a list of learners determined by the current selectors and filters. Will update automatically based on user
interaction with the selectors and filters. Each item in this list corresponds to a learner and has:
• A checkbox for bulk-selecting learners. Selecting multiple learners enables some actions described below.
• A last name, first name clickable link. Doing so summons a detail view modal for the learner.
• A manage button which summons a class and group management modal for that student.
Note: The roster described here corresponds to user-page.vue.
The detail view modal displays learner account data and provides a mechanism to reset a learner’s password.
The class and group management modal displays a list classrooms to which the student belongs. Each classroom has
a dropdown menu for assinging that learner to a specific group within that classroom. Additionally each classroom
has a checkbox for bulk-selection. Bulk-selecting permits the session user to remove the learner from the selected
clasrooms. Clicking “add” reveals classrooms to which the user doesn’t already belong. The learner may be added
to those classrooms by selecting them with the corresponding checkboxes, and simultaneously select a group through
the associated group dropdown.
Note: UI simplification: There are a number of simplifying assumptions made here. For one, kolibri.auth permits
a learner to belong to multiple groups within a classroom. Here we only allow a learner to belong to one group per
classroom in order to simplify the UI.
116
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Note: UI simplification: Secondly, kolibri.auth has no notion of being “ungrouped”. The kolibri.auth module
defines a Membership model that associates users to Learner Group and Classroom models. For the purposes of this
app, when a learner is assigned to a group, then a Membership object to the underlying Learner Group object is created.
Membership in a Learner Group implies the user is a member of the containing Classroom as well. When a learner is
assigned to the “Ungrouped” group of a Classroom, it correponds to creating a Membership object associated with the
Classroom. In all cases re-assigning a user to a different group should both destroy the existing Membership object
and create a new one.
Warning: Roadmap: I consider the detail view and class and group management modals to be somewhat
unsettled prior to getting quality user feedback.
Selectors and filters
The UI allows the list of learner in the roster to be filtered. This includes:
• A classroom selector. This is populated by a list of classrooms for which the session user has write permissions,
and the special option “All classrooms”. The list of learners is filtered to only show learners who are members
of the selected classroom, or all learners if “All classrooms” is selected.
• A group selector. This is disabled if “All classrooms” is selected. Otherwise it is populated with the list of
Learner Groups in the classroom with the special option “All groups”. This filters the list of learner analogously
to the classroom selector.
• Potentially other filters, for example listing learners in alphabetical or reverse-alphabetical order.
Miscellaneous widgets
Next to the classroom and group selectors are “add” and “remove” buttons. Clicking “add” summons a modal form
for creating a new classroom and a new learner group within the currently selected classroom, respectively. The
“add” button for groups is disabled if “All classrooms” is selected. Clicking “remove” deletes the currently selected
classroom or learner group, respectively. The corresponding “delete” button is disabled if “All classrooms” or “All
groups” is selected.
Space is reserved next to the roster for an information panel to display elaborating information based on the current
selection. Right now it includes only the total # of students which match the criteria determined by the selectors and
filters.
Facility Management
Facility Management (previously referred to as Role Management) will provide an interface for managing user Roles
in a Facility. Users may multiply possess Coach and Admin roles for a Facility or Classrooms within a Facility.
Kolibri user data is fundamentally divided into Facilities – a user who belongs to one Facility can never see or interact
with user account data from another Facility. However Kolibri provides another user type, called a Device Owner.
Device Owners differ from Facility Users in the following ways:
• Device Owners are not syncable from device to device – this account type belongs to one physical machine only.
In contrast, Facility Users account and their associated data are syncable.
• Device Owners enjoy every permission. They can be considered Admins for every Facility on the device.
2.4. Themes
117
Kolibri Documentation, Release 0.6.2
• Device Owners may see and edit all Facilities on their Device, including choosing which Facility data sets are
present on a physical device.
The purview of the Facility Management app is to allow users to give and revoke the Coach and Admin roles for the
Facility they belong to and the various Classrooms in that Facility. Moreover the functionality of the app is slightly
differnt if the session user is a device owner:
• If the session user is a Device Owner, the user may select which Facility to manage. Facility Users may only
manage their own Facility.
• A Device Owner may edit or delete a Facility. Editing a Facility can change it’s details like name, description,
etc. Deleting a Facility does not destroy it – it is just removed from that device, so that Facility Users tied to that
Facility may no longer log in.
Note: Roadmap: Jessica has begun designing this. See the invision prototype.
2.4.5 Front-end Asset Loading
Asset pipelining is done using Webpack - this allows the use of require to import modules - as such all written code
should be highly modular, individual files should be responsible for exporting a single function or object.
There are two distinct entities that control this behaviour - a Kolibri Hook on the Python side, which manages the
registration of the frontend code within Django (and also facilitates building of that code into compiled assets with
Webpack) and a Kolibri Module (a subclass of KolibriModule) on the JavaScript side (see frontend).
Kolibri has a system for synchronously and asynchronously loading these bundled JavaScript modules which is mediated by a small core JavaScript app, kolibriGlobal. Kolibri Modules define to which events they subscribe,
and asynchronously registered Kolibri Modules are loaded by kolibriGlobal only when those events are triggered. For example if the Video Viewer’s Kolibri Module subscribes to the content_loaded:video event, then when
that event is triggered on kolibriGlobal it will asynchronously load the Video Viewer module and re-trigger the
content_loaded:video event on the object the module returns.
Synchronous and asynchronous loading is defined by the template tag used to import the JavaScript for the Kolibri
Module into the Django template. Synchronous loading merely inserts the JavaScript and CSS for the Kolibri Module
directly into the Django template, meaning it is executed at page load.
This can be achieved in two ways using tags defined in kolibri/core/webpack/templatetags/webpack_tags.py.
The first way is simply by using the webpack_asset template tag.
The second way is if a Kolibri Module needs to load in the template defined by another plugin or a core part of Kolibri,
a template tag and hook can be defined to register that Kolibri Module’s assets to be loaded on that page. An example
of this is found in the base.html template using the webpack_base_assets tag.
This relies on the following function to collect all registered Kolibri Modules and load them synchronously:
kolibri.core.webpack.utils.webpack_asset_render
Asynchronous loading can also, analogously, be done in two ways. Asynchronous loading registers a Kolibri Module
against kolibriGlobal on the frontend at page load, but does not load, or execute any of the code until the events
that the Kolibri Module specifies are triggered. When these are triggered, the kolibriGlobal will load the Kolibri
Module and pass on any callbacks once it has initialized. Asynchronous loading can be done either explicitly with a
template tag that directly imports a single Kolibri Module using webpack_base_async_assets.
2.4.6 User Logs
This is a core module found in kolibri/logger.
118
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Concepts and Definitions
Content Interaction Log
This Model provides a record of an interaction with a content item. As such, it should encode the channel that the
content was in, and the id of the content. Further, it may be required to encode arbitrary data in a JSON blob that is
specific to the particular content type.
As a typical use case, a ContentInteractionLog object might be used to record an interaction with one instance of an
exercise (i.e. one question, but possibly multiple attempts within the same session), or a single session of viewing a
video.
Finally, these Logs will use MorangoDB to synchronize their data across devices.
Content Summary Log
This Model provides a summary of all interactions of a user with a content item. As such, it should encode the channel
that the content was in, and the id of the content. Further, it may be required to encode arbitrary data in a JSON blob
that is specific to the particular content type.
As a typical use case, a ContentSummaryLog object might be used to provide summary data about the state of completion of a particular exercise, video, or other content.
When a new InteractionLog is saved, the associated SummaryLog is updated at the same time. This means that the
SummaryLog acts as an aggregation layer for the current state of progress for a particular piece of content.
To implement this, a content viewer app would define the aggregation function that summarizes interaction logs into
the summary log. While this could happen in the frontend, it would probably be more efficient for this to happen on
the server side.
Finally, these Logs will use MorangoDB to synchronize their data across devices - in the case where two summary
logs from different devices conflict, then the aggregation logic would be applied across all interaction logs to create a
consolidated summary log.
Content Rating Log
This Model provides a record of user feedback on content.
As a typical use case, a ContentRatingLog object might be used to record user feedback data about any content.
Finally, these Logs will use MorangoDB to synchronize their data across devices.
User Session Log
This Model provides a record of an user session in Kolibri. As such, it should encode the channels interacted with, the
length of time engaged, and the pages visited.
As a typical use case, a UserSessionLog object might be used to record which pages a user visits, and how long the
user is logged on for.
Finally, these Logs will use MorangoDB to synchronize their data across devices.
2.4. Themes
119
Kolibri Documentation, Release 0.6.2
Implementation Details
Permissions
See Encoding Permission Rules.
Models
This app is intended to provide the core functionality for tracking user engagement with content and Kolibri in general.
As such, it is intended to store details of user interactions with content, a summary of those interactions, interactions
with the software in general, as well as user feedback on the content and the software.
class kolibri.logger.models.AttemptLog(*args, **kwargs)
This model provides a summary of a user’s engagement within a particular interaction with an item/question in
an assessment
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• item (CharField) – Item
• start_timestamp (DateTimeTzField) – Start timestamp
• end_timestamp (DateTimeTzField) – End timestamp
• completion_timestamp (DateTimeTzField) – Completion timestamp
• time_spent (FloatField) – (in seconds)
• complete (BooleanField) – Complete
• correct (FloatField) – Correct
• hinted (BooleanField) – Hinted
• answer (JSONField) – Answer
• simple_answer (CharField) – Simple answer
• interaction_history (JSONField) – Interaction history
• user_id (ForeignKey to ~) – User
• masterylog_id (ForeignKey to ~) – Masterylog
• sessionlog_id (ForeignKey to ~) – Sessionlog
class kolibri.logger.models.BaseAttemptLog(*args, **kwargs)
This is an abstract model that provides a summary of a user’s engagement within a particular interaction with
an item/question in an assessment
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
120
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• item (CharField) – Item
• start_timestamp (DateTimeTzField) – Start timestamp
• end_timestamp (DateTimeTzField) – End timestamp
• completion_timestamp (DateTimeTzField) – Completion timestamp
• time_spent (FloatField) – (in seconds)
• complete (BooleanField) – Complete
• correct (FloatField) – Correct
• hinted (BooleanField) – Hinted
• answer (JSONField) – Answer
• simple_answer (CharField) – Simple answer
• interaction_history (JSONField) – Interaction history
• user_id (ForeignKey to ~) – User
class kolibri.logger.models.ContentSessionLog(*args, **kwargs)
This model provides a record of interactions with a content item within a single visit to that content page.
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• user_id (ForeignKey to ~) – User
• content_id (UUIDField) – Content id
• channel_id (UUIDField) – Channel id
• start_timestamp (DateTimeTzField) – Start timestamp
• end_timestamp (DateTimeTzField) – End timestamp
• time_spent (FloatField) – (in seconds)
• progress (FloatField) – Progress
• kind (CharField) – Kind
• extra_fields (JSONField) – Extra fields
class kolibri.logger.models.ContentSummaryLog(*args, **kwargs)
This model provides a summary of all interactions a user has had with a content item.
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
2.4. Themes
121
Kolibri Documentation, Release 0.6.2
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• user_id (ForeignKey to ~) – User
• content_id (UUIDField) – Content id
• channel_id (UUIDField) – Channel id
• start_timestamp (DateTimeTzField) – Start timestamp
• end_timestamp (DateTimeTzField) – End timestamp
• completion_timestamp (DateTimeTzField) – Completion timestamp
• time_spent (FloatField) – (in seconds)
• progress (FloatField) – Progress
• kind (CharField) – Kind
• extra_fields (JSONField) – Extra fields
class kolibri.logger.models.ExamAttemptLog(*args, **kwargs)
This model provides a summary of a user’s engagement within a particular interaction with an item/question in
an exam
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• item (CharField) – Item
• start_timestamp (DateTimeTzField) – Start timestamp
• end_timestamp (DateTimeTzField) – End timestamp
• completion_timestamp (DateTimeTzField) – Completion timestamp
• time_spent (FloatField) – (in seconds)
• complete (BooleanField) – Complete
• correct (FloatField) – Correct
• hinted (BooleanField) – Hinted
• answer (JSONField) – Answer
• simple_answer (CharField) – Simple answer
• interaction_history (JSONField) – Interaction history
• user_id (ForeignKey to ~) – User
• examlog_id (ForeignKey to ~) – Examlog
• content_id (UUIDField) – Content id
• channel_id (UUIDField) – Channel id
122
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
class kolibri.logger.models.ExamLog(*args, **kwargs)
This model provides a summary of a user’s interaction with a particular exam, and serves as an aggregation
point for individual attempts on that exam.
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• exam_id (ForeignKey to ~) – Exam
• user_id (ForeignKey to ~) – User
• closed (BooleanField) – Closed
• completion_timestamp (DateTimeTzField) – Completion timestamp
class kolibri.logger.models.MasteryLog(*args, **kwargs)
This model provides a summary of a user’s engagement with an assessment within a mastery level
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
• user_id (ForeignKey to ~) – User
• summarylog_id (ForeignKey to ~) – Summarylog
• mastery_criterion (JSONField) – Mastery criterion
• start_timestamp (DateTimeTzField) – Start timestamp
• end_timestamp (DateTimeTzField) – End timestamp
• completion_timestamp (DateTimeTzField) – Completion timestamp
• mastery_level (IntegerField) – Mastery level
• complete (BooleanField) – Complete
class kolibri.logger.models.UserSessionLog(*args, **kwargs)
This model provides a record of a user session in Kolibri.
Parameters
• id (UUIDField) – Id
• _morango_dirty_bit (BooleanField) – morango dirty bit
• _morango_source_id (CharField) – morango source id
• _morango_partition (CharField) – morango partition
• dataset_id (ForeignKey to ~) – Dataset
2.4. Themes
123
Kolibri Documentation, Release 0.6.2
• user_id (ForeignKey to ~) – User
• channels (TextField) – Channels
• start_timestamp (DateTimeTzField) – Start timestamp
• last_interaction_timestamp (DateTimeTzField) – Last interaction timestamp
• pages (TextField) – Pages
classmethod update_log(user)
Update the current UserSessionLog for a particular user.
2.5 References
2.5.1 Upgrading
Warning: These instructions are under development
Upgrade paths
Kolibri can be automatically upgraded forwards. For instance, you can upgrade from 0.1->0.2 and 0.1->0.7.
We test all upgrade paths, but we also caution that the more versions that you skip, the higher the risks will be that
something isn’t working as expected.
That’s why we also support Downgrading.
Every time Kolibri is upgraded, it will automatically migrate your database and create a backup before doing so.
Note: Always upgrade as often as possible. If you are responsible for deployments at different sites, you should
consider a strategy for keeping software and contents updated.
Downgrading
To downgrade you need to do two steps:
1. If you have been using the latest version and want to store data, make sure to create a backup before continuing:
kalite manage dbbackup
2. Install the older version on top of the new version using the same installation type.
3. Restore the latest Database backup.
When you upgrade Kolibri, the database is changed to match the latest version of Kolibri, however these changes
cannot be unmade. That’s why you need to restore the database from a backup.
Database backup
While upgrading, Kolibri will automatically generate a backup of the database before making any changes. This
guarantees that in case the upgrade causes problems, you can downgrade and restore the backup.
124
Chapter 2. Developer Guide
Kolibri Documentation, Release 0.6.2
Backups
Kolibri stores database backups in ~/.kolibri/backups. The dump files created contain SQL statements to be
run by SQLite3. You can re-instate a dump by using the special dbrestore command.
Restoring from backup
Warning: Restoring from backup will overwrite the current database, so store a backup in case you have data you
want to preserve!
To restore from the latest available backup, run the following from command line:
$ kolibri manage dbrestore --latest
To restore from a specific backup file:
$ kolibri manage dbrestore /path/to/db-backup.dump
2.5.2 Files and Directories
.cache/. . . Testing-related, and ignored by git. TODO - what does it contain?
.eggs/. . . Packaging-related, and ignored by git. TODO - what does it contain?
.github/. . . These are files used by GitHub to generate templates for things like new pull requests and issues.
.tox/. . . Tox is a tool for testing software in a range of environments - for example using different versions of Python
and Node.
This directory is ignored by git.
TODO - what does it contain?
dist-packages-cache Packaging-related, and ignored by git. TODO - what does it contain?
dist-packages-temp Packaging-related, and ignored by git. TODO - what does it contain?
docs/. . . reStructuredText-based documentation, along with Sphinx-based build code
frontend_build/. . . Code for integrating Kolibri’s plugin system with webpack instrumentation for bundling clientside dependencies.
karma_config/. . . Configuration for Karma, our client-side unit test framework
kolibri/. . . main code-base, a Django application
requirements/. . . Python dependency files for PIP
test/. . . helper files for running tests in Travic CI TODO - is this correct?
.editorconfig general editor configuration file
.eslintrc.js configuration file for ESLint, our client-side javascript linter
.gitignore standard .gitignore file
.htmlhintrc configuration for our HTML linter, HTMLHint
.pre-commit-config.yaml configuration for our pre-commit hooks
2.5. References
125
Kolibri Documentation, Release 0.6.2
.stylintrc configuration for our Stylus linter, Stylint
.travis.yml configuration for Travis
AUTHORS.rst, CHANGELOG.rst, CONTRIBUTING.rst reStructuredText-formatted files. Also imported by the
generated /docs
LICENSE plain-text license files
Makefile wrapper for some scripts, including building packages and docs
MANIFEST.in list of non-python files to include in the Python package
package.json javascript dependencies, helper scripts, and configuration
pytest.ini configuration file for pytest
pytest_runner-2.7.1-py2.7.egg ?
README.rst reStructuredText-formatted file readme
requirements.txt Python PIP dependency requirements, simply redirects to requirements/base.txt
setup.cfg ?
setup.py configuration for Python package related to setuptools
tox.ini configuration for our Tox test environments
2.5.3 Glossary
Words with special meanings in the Kolibri ecosystem.
App A Kolibri ‘app’ is a special sort of plugin which provides a top-level URL and a self-contained single-page
javascript application. Each app attaches a single root view component to the HTML returned by a skeleton
Django template.
Examples of apps are the Learn, Admin, and Coach Reports apps.
Bundle ‘Bundle’ is a webpack term, referring to a collection of client-side assets. See Front-end Asset Loading for
more info.
Component A ‘view component’ is a composable UI element on the client-side, defined using Vue.js components.
Components are defined using using HTML, other components, styling, internationalized text, internal logic,
and – if necessary – internal state. Every component has an interface defined by its input parameters, events,
and slots that can take arbitrary HTML to render in itself.
See frontend for more info.
Hook A ‘hook’ is the server-side mechanism by which plugins interact with each other and with the core app. Hooks
allow behaviors and interactions to be defined abstractly by Kolibri core and then implemented concretely by
plugins.
See plugins for more info.
Plugin Kolibri ‘plugins’ define both functionality client- and server-side functionality. They can be enabled and
disabled on a per-installation basis. Plugins are decoupled from each other, but are dependent on the core
Kolibri application.
In theory, any plugin can be disabled and the application should still function without error, albeit limited
functionality.
Examples of plugins include the Learn application, and a content renderer for vector video.
See plugins for more info.
126
Chapter 2. Developer Guide
CHAPTER
3
Release Notes
Changes are ordered reverse-chronologically.
3.1 0.6.2
• Consistent ordering of channels in learner views
3.2 0.6.1
• Many mobile-friendly updates across the app
• Update French, Portuguese, and Swahili translations
• Upgraded Windows installer
3.3 0.6.0
• Cross-channel searching and browsing
• Improved device onboarding experience
• Improved device permissions experience (deprecated ‘device owner’, added ‘superuser’ flag and import permission)
• Various channel import/export experience and stability improvements
• Responsive login page
• Dynamic language switching
• Work on integrated living style guide
• Added beta support for right-to-left languages
127
Kolibri Documentation, Release 0.6.2
• Improved handling of locale codes
• Added support for frontend translation outside of Vue components
• Added an open-source ‘code of conduct’ for contributors
• By default run PEX file in foreground on MacOS
• Crypto optimizations from C extensions
• Deprecated support for HTML in translation strings
• Hide thumbnails from content ‘download’ button
• Automatic database backup during upgrades. #2365
• . . . and many other updates and fixes
3.4 0.5.3
• Release timeout bug fix from 0.4.8
3.5 0.5.2
• Release bug fix from 0.4.7
3.6 0.5.1
• Python dependencies: Only bundle, do not install dependencies in system env #2299
• Beta Android support
• Fix ‘importchannel’ command #2082
• Small translation improvements for Spanish, French, Hindi, and Swahili
3.7 0.5.0
• Update all user logging related timestamps to a custom datetime field that includes timezone info
• Added daemon mode (system service) to run kolibri start in background (default!) #1548
• Implemented kolibri stop and kolibri status #1548
• Newly imported channels are given a ‘last_updated’ timestamp
• Add progress annotation for topics, lazily loaded to increase page load performance
• Add API endpoint for getting number and total size of files in a channel
• Migrate all JS linting to prettier rather than eslint
• Merge audio_mp3_render and video_mp4_render plugins into one single media_player plugin
• KOLIBRI_LISTEN_PORT environment variable for specifying a default for the –port option #1724
128
Chapter 3. Release Notes
Kolibri Documentation, Release 0.6.2
3.8 0.4.9
• User experience improvements for session timeout
3.9 0.4.8
• Prevent session timeout if user is still active
• Fix exam completion timestamp bug
• Prevent exercise attempt logging crosstalk bug
• Update Hindi translations
3.10 0.4.7
• Fix bug that made updating existing Django models from the frontend impossible
3.11 0.4.6
• Fix various exam and progress tracking issues
• Add automatic sign-out when browser is closed
• Fix search issue
• Learner UI updates
• Updated Hindi translations
3.12 0.4.5
• Frontend and backend changes to increase performance of the Kolibri application under heavy load
• Fix bug in frontend simplified login code
3.13 0.4.4
• Fix for Python 3 compatibility in Whl, Windows and Pex builds #1797
• Adds Mexican Spanish as an interface language
• Upgrades django-q for bug fixes
3.14 0.4.3
• Speed improvements for content recommendation #1798
3.8. 0.4.9
129
Kolibri Documentation, Release 0.6.2
3.15 0.4.2
• Fixes for morango database migrations
3.16 0.4.1
• Makes usernames for login case insensitive #1733
• Fixes various issues with exercise rendering #1757
• Removes wrong CLI usage instructions #1742
3.17 0.4.0
• Class and group management
• Learner reports #1464
• Performance optimizations #1499
• Anonymous exercises fixed #1466
• Integrated Morango, to prep for data syncing (will require fresh database)
• Adds Simplified Login support as a configurable facility flag
3.18 0.3.3
• Turns video captions on by default
3.19 0.3.2
• Updated translations for Portuguese and Kiswahili in exercises.
• Updated Spanish translations
3.20 0.3.2
• Portuguese and Kaswihili updates
• Windows fixes (mimetypes and modified time)
• VF sidebar translations
130
Chapter 3. Release Notes
Kolibri Documentation, Release 0.6.2
3.21 0.3.0
• Add support for nested URL structures in API Resource layer
• Add Spanish and Swahili translations
• Improve pipeline for translating plugins
• Add search back in
• Content Renderers use explicit new API rather than event-based loading
3.22 0.2.0
• Add authentication for tasks API
• Temporarily remove ‘search’ functionality
• Rename ‘Learn/Explore’ to ‘Recommended/Topics’
• Add JS-based ‘responsive mixin’ as alternative to media queries
• Replace jeet grids with pure.css grids
• Begin using some keen-ui components
• Update primary layout and navigation
• New log-in page
• User sign-up and profile-editing functionality
• Versioning based on git tags
• Client heartbeat for usage tracking
• Allow plugins to override core components
• Wrap all user-facing strings for I18N
• Log filtering based on users and collections
• Improved docs
• Pin dependencies with Yarn
• ES2015 transpilation now Bublé instead of Babel
• Webpack build process compatible with plugins outside the kolibri directory
• Vue2 refactor
• HTML5 app renderer
3.23 0.1.1
• SVG inlining
• Exercise completion visualization
• Perseus exercise renderer
• Coach reports
3.21. 0.3.0
131
Kolibri Documentation, Release 0.6.2
3.24 0.1.0 - MVP
• Improved documentation
• Conditional (cancelable) JS promises
• Asset bundling performance improvements
• Endpoint indexing into zip files
• Case-insensitive usernames
• Make plugins more self-contained
• Client-side router bug fixes
• Resource layer smart cache busting
• Loading ‘spinner’
• Make modals accessible
• Fuzzy searching
• Usage data export
• Drive enumeration
• Content interaction logging
• I18N string extraction
• Channel switching bug fixes
• Modal popups
• A11Y updates
• Tab focus highlights
• Learn app styling changes
• User management UI
• Task management
• Content import/export
• Session state and login widget
• Channel switching
• Setup wizard plugin
• Documentation updates
• Content downloading
3.25 0.0.1 - MMVP
• Page titles
• Javascript logging module
• Responsiveness updates
• A11Y updates
132
Chapter 3. Release Notes
Kolibri Documentation, Release 0.6.2
• Cherrypy server
• Vuex integration
• Stylus/Jeet-based grids
• Support for multiple content DBs
• API resource retrieval and caching
• Content recommendation endpoints
• Client-side routing
• Content search
• Video, Document, and MP3 content renderers
• Initial VueIntl integration
• User management API
• Vue.js integration
• Learn app and content browsing
• Content endpoints
• Automatic inclusion of requirements in a static build
• Django JS Reverse with urls representation in kolibriGlobal object
• Python plugin API with hooks
• Webpack build pipeline, including linting
• Authentication, authorization, permissions
• Users, Collections, and Roles
3.25. 0.0.1 - MMVP
133
Kolibri Documentation, Release 0.6.2
134
Chapter 3. Release Notes
CHAPTER
4
Contributing
4.1 Contributing
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
You can contribute in many ways:
4.1.1 Types of Contributions
Report Bugs
Report bugs at https://github.com/learningequality/kolibri/issues.
If you are reporting a bug, please include:
• Your operating system name and version.
• Any details about your local setup that might be helpful in troubleshooting.
• Detailed steps to reproduce the bug.
Fix Bugs
Look through the GitHub issues for bugs. Anything tagged with “bug” is open to whoever wants to implement it.
Implement Features
Look through the GitHub issues for features. Anything tagged with “feature” is open to whoever wants to implement
it.
135
Kolibri Documentation, Release 0.6.2
Write Documentation
Kolibri could always use more documentation, whether as part of the official Kolibri docs, in docstrings, or even on
the web in blog posts, articles, and such.
Submit Feedback
The best way to send feedback is to file an issue at https://github.com/learningequality/kolibri/issues.
If you are proposing a feature:
• Explain in detail how it would work.
• Keep the scope as narrow as possible, to make it easier to implement.
• Remember that this is a volunteer-driven project, and that contributions are welcome :)
4.1.2 Get Started!
Ready to contribute? Here’s how to set up kolibri for local development.
1. Fork the kolibri repo on GitHub.
2. Clone your fork locally:
$ git clone git@github.com:your_name_here/kolibri.git
3. Create a branch for local development:
$ git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
4. When you’re done making changes, check that your changes pass style and unit tests, including testing other
Python versions with tox, and testing any Javascript changes with npm:
$ tox
$ npm test
To get tox, just pip install it. To get node (and hence npm) install it from https://nodejs.org.
5. Commit your changes and push your branch to GitHub:
$ git add .
$ git commit -m "Your detailed description of your changes."
$ git push origin name-of-your-bugfix-or-feature
6. Submit a pull request through the GitHub website.
4.1.3 Pull Request Guidelines
Before you submit a pull request, check that it meets these guidelines:
1. For your pull request description on Github, consider using our template.
2. Remember to add yourself to AUTHORS.rst and fill in CHANGELOG.rst with your feature or bug fix.
3. The pull request should include tests.
136
Chapter 4. Contributing
Kolibri Documentation, Release 0.6.2
4. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function
with a docstring, and add the feature to the list in README.rst.
5. The pull request should work for Python 2.7, 3.4, and 3.5, 3.6, and for PyPy. PRs will be automatically tested,
we recommend running the tox command locally before submitting your PR for review.
4.1.4 Tips
To run a subset of tests:
$ py.test test/test_kolibri.py
4.2 Code of conduct
4.2.1 Code of Conduct
1. Purpose
A primary goal of Kolibri and KA Lite is to be inclusive to the largest number of contributors, with the most varied and
diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment
for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).
This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.
We invite all those who participate in Kolibri or KA Lite to help us create safe and positive experiences for everyone.
2. Open Source Citizenship
A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to
recognize and strengthen the relationships between our actions and their effects on our community.
Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of
inequality and abuses of power that exist in society.
If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all
participants to contribute to the fullest extent, we also want to know!
3. Expected Behavior
The following behaviors are expected and requested of all community members:
• Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this
community.
• Exercise consideration and respect in your speech and actions.
• Attempt collaboration before conflict.
• Refrain from demeaning, discriminatory, or harassing behavior and speech.
• Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
4.2. Code of conduct
137
Kolibri Documentation, Release 0.6.2
• Remember that community event venues may be shared with members of the public; please be respectful to all
patrons of these locations.
4. Unacceptable Behavior
The following behaviors are considered harassment and are unacceptable within our community:
• Violence, threats of violence or violent language directed against another person.
• Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
• Posting or displaying sexually explicit or violent material.
• Posting or threatening to post other people’s personally identifying information (“doxing”).
• Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
• Inappropriate photography or recording.
• Inappropriate physical contact. You should have someone’s consent before touching them.
• Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping,
and unwelcomed sexual advances.
• Deliberate intimidation, stalking or following (online or in person).
• Advocating for, or encouraging, any of the above behavior.
• Sustained disruption of community events, including talks and presentations.
5. Consequences of Unacceptable Behavior
Unacceptable behavior from any community member, including sponsors and those with decision-making authority,
will not be tolerated.
Anyone asked to stop unacceptable behavior is expected to comply immediately.
If a community member engages in unacceptable behavior, the community organizers may take any action they deem
appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and
without refund in the case of a paid event).
6. Reporting Guidelines
If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer
as soon as possible. codeofconduct@learningequality.org.
Reporting Guidelines
Additionally, community organizers are available to help community members engage with local law enforcement or
to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers
will also provide escorts as desired by the person experiencing distress.
7. Addressing Grievances
If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Learning
Equality with a concise description of your grievance. Your grievance will be handled in accordance with our existing
governing policies.
Enforcement Manual
138
Chapter 4. Contributing
Kolibri Documentation, Release 0.6.2
8. Scope
We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this
Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business.
This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of
community activities when such behavior has the potential to adversely affect the safety and well-being of community
members.
9. Contact info
The Code of Conduct team consists of:
• Benjamin Bach (benjamin@learningequality.org)
• Radina Matic (radina@learningequality.org)
• Richard Tibbles (richard@learningequality.org)
Please write: codeofconduct@learningequality.org
10. License and attribution
This Code of Conduct is distributed under a Creative Commons Attribution-ShareAlike license.
Portions of text derived from the Django Code of Conduct and the Geek Feminism Anti-Harassment Policy.
Retrieved on November 22, 2016 from http://citizencodeofconduct.org/
4.2.2 Reporting Guidelines
If you believe someone is violating the code of conduct we ask that you report it to the Learning Equality by emailing
codeofconduct@learningequality.org. All reports will be kept confidential. In some cases we may determine that
a public statement will need to be made. If that’s the case, the identities of all victims and reporters will remain
confidential unless those individuals instruct us otherwise.
If you believe anyone is in physical danger, please notify appropriate law enforcement first. If you are unsure what
law enforcement agency is appropriate, please include this in your report and we will attempt to notify them.
If you are unsure whether the incident is a violation, or whether the space where it happened is covered by this Code
of Conduct, we encourage you to still report it. We would much rather have a few extra reports where we decide to
take no action, rather than miss a report of an actual violation. We do not look negatively on you if we find the incident
is not a violation. And knowing about incidents that are not violations, or happen outside our spaces, can also help us
to improve the Code of Conduct or the processes surrounding it.
In your report please include:
1. Your contact info (so we can get in touch with you if we need to follow up)
2. Names (real, nicknames, or pseudonyms) of any individuals involved. If there were other witnesses besides you,
please try to include them as well.
3. When and where the incident occurred. Please be as specific as possible.
4. Your account of what occurred. If there is a publicly available record (e.g. a mailing list archive or a public
Slack logger) please include a link.
5. Any extra context you believe existed for the incident.
4.2. Code of conduct
139
Kolibri Documentation, Release 0.6.2
6. If you believe this incident is ongoing.
7. Any other information you believe we should have.
What happens after you file a report?
You will receive an email from the Code of Conduct committee acknowledging receipt within 48 hours (we aim to be
quicker than that).
The committee will immediately meet to review the incident and determine:
1. What happened.
2. Whether this event constitutes a code of conduct violation.
3. Who the bad actor was.
4. Whether this is an ongoing situation, or if there is a threat to anyone’s physical safety.
If this is determined to be an ongoing incident or a threat to physical safety, the committee’s immediate priority will
be to protect everyone involved. This means we may delay an “official” response until we believe that the situation
has ended and that everyone is physically safe.
Once the committee has a complete account of the events they will make a decision as to how to response. Responses
may include:
• Nothing (if we determine no violation occurred).
• A private reprimand from the committee to the individual(s) involved.
• A public reprimand.
• An imposed vacation (i.e. asking someone to “take a week off” from a mailing list or Slack).
• A permanent or temporary ban from some or all communication spaces (mailing lists, Slack, etc.)
• A request for a public or private apology.
We’ll respond within one week to the person who filed the report with either a resolution or an explanation of why the
situation is not yet resolved.
Once we’ve determined our final action, we’ll contact the original reporter to let them know what action (if any) we’ll
be taking. We’ll take into account feedback from the reporter on the appropriateness of our response, but we don’t
guarantee we’ll act on it.
Attribution
This Code of Conduct is distributed under a Creative Commons Attribution-ShareAlike license.
Text derived from the Django Code of Conduct
4.2.3 Enforcement Manual
This is the enforcement manual followed by Learning Equality’s Code of Conduct Committee. It’s used when we
respond to an issue to make sure we’re consistent and fair. It should be considered an internal document, but we’re
publishing it publicly in the interests of transparency.
140
Chapter 4. Contributing
Kolibri Documentation, Release 0.6.2
The Code of Conduct Committee
All responses to reports of conduct violations will be managed by a Code of Conduct Committee (“the committee”).
Learning Equality’s (LE’s) core team (“the core”) will establish this committee, comprised of at least three members.
How the committee will respond to reports
When a report is sent to the committee, a member will reply with a receipt to confirm that a process of reading your
report has started.
See the reporting guidelines for details of what reports should contain. If a report doesn’t contain enough information,
the committee will obtain all relevant data before acting. The committee is empowered to act on the LE’s behalf in
contacting any individuals involved to get a more complete account of events.
The committee will then review the incident and determine, to the best of their ability:
• what happened
• whether this event constitutes a code of conduct violation
• who, if anyone, was the bad actor
• whether this is an ongoing situation, and there is a threat to anyone’s physical safety
This information will be collected in writing, and whenever possible the committee’s deliberations will be recorded
and retained (i.e. Slack transcripts, email discussions, recorded voice conversations, etc).
The committee should aim to have a resolution agreed upon within one week. In the event that a resolution can’t
be determined in that time, the committee will respond to the reporter(s) with an update and projected timeline for
resolution.
Acting Unilaterally
If the act is ongoing or involves a threat to anyone’s safety (e.g. threats of violence), any committee member may
act immediately (before reaching consensus) to end the situation. In ongoing situations, any member may at their
discretion employ any of the tools available to the committee, including bans and blocks.
If the incident involves physical danger, any member of the committee may – and should – act unilaterally to protect
safety. This can include contacting law enforcement (or other local personnel) and speaking on behalf of Learning
Equality.
In situations where an individual committee member acts unilaterally, they must report their actions to the committee
for review within 24 hours.
Resolutions
The committee must agree on a resolution by consensus. If the committee cannot reach consensus and deadlocks for
over a week, the committee will turn the matter over to the board for resolution.
Possible responses may include:
• Taking no further action (if we determine no violation occurred).
• A private reprimand from the committee to the individual(s) involved. In this case, the committee will deliver
that reprimand to the individual(s) over email, cc’ing the committee.
4.2. Code of conduct
141
Kolibri Documentation, Release 0.6.2
• A public reprimand. In this case, the committee will deliver that reprimand in the same venue that the violation
occurred (i.e. in Slack for an Slack violation; email for an email violation, etc.). The committee may choose to
publish this message elsewhere for posterity.
• An imposed vacation (i.e. asking someone to “take a week off” from a mailing list or Slack). The committee
will communicate this “vacation” to the individual(s). They’ll be asked to take this vacation voluntarily, but if
they don’t agree then a temporary ban may be imposed to enforce this vacation.
• A permanent or temporary ban from some or all Learning Equality spaces (mailing lists, Slack, etc.). The
committee will maintain records of all such bans so that they may be reviewed in the future, extended to new
Learning Equality fora, or otherwise maintained.
• A request for a public or private apology. The committee may, if it chooses, attach “strings” to this request: for
example, the committee may ask a violator to apologize in order to retain his or her membership on a mailing
list.
Once a resolution is agreed upon, but before it is enacted, the committee will contact the original reporter and any
other affected parties and explain the proposed resolution. The committee will ask if this resolution is acceptable, and
must note feedback for the record. However, the committee is not required to act on this feedback.
Finally, the committee will make a report for the core team.
The committee will never publicly discuss the issue; all public statements will be made by the core team.
Conflicts of Interest
In the event of any conflict of interest a committee member must immediately notify the other members, and recuse
themselves if necessary.
4.2.4 Attribution
Reporting Guidelines and Enforcement Manual are both distributed under a Creative Commons Attribution-ShareAlike
license.
Reporting Guidelines and Enforcement Manual are both derived from the Django’ Reporting Guidelines and Django’
Enforcement Manual
Changes made to the original doc: Instead of involving a board as DSF has, the core team at Learning Equality is
considered. Instead of IRC, we refer to Slack. The Code of Conduct Committee does not have a single chair but acts
as a group to make conflicts of interest easier, and to avoid problems in case of absence of the chair person. Instead
of interchanging “working group” and “committee” notation, we replaced all occurrences of “working group” and
“group” with “committee”.
142
Chapter 4. Contributing
CHAPTER
5
Credits
5.1 Development Lead and Copyright Holder
• Learning Equality – info@learningequality.org
5.2 Community
Please feel free to add your name on this list if you do a PR!
• Benjamin Bach (benjaoming)
• Michael Gallaspy (MCGallaspy)
• Richard Tibbles (rtibbles)
• Jamie Alexandre (jamalex)
• David Cañas (dxcanas)
• Eli Dai (66eli77)
• Devon Rueckner (indirectlylit)
• Rafael Aguayo (ralphiee22)
• Christian Memije (christianmemije)
• Radina Matic (radinamatic)
• Yixuan Liu (yil039)
143
Kolibri Documentation, Release 0.6.2
144
Chapter 5. Credits
CHAPTER
6
Kolibri
145
Kolibri Documentation, Release 0.6.2
146
Chapter 6. Kolibri
CHAPTER
7
What is Kolibri?
Kolibri is a Learning Management System / Learning App designed to run on low-power devices, targeting the needs
of learners and teachers in contexts with limited infrastructure. A user can install Kolibri and serve the app on a
local network, without an internet connection. Kolibri installations can be linked to one another, so that user data and
content can be shared. Users can create content for Kolibri and share it when there is network access to another Kolibri
installation or the internet.
At its core, Kolibri is about serving educational content. A typical user (called a Learner) will log in to Kolibri to
consume educational content (videos, documents, other multimedia) and test their understanding of the content by
completing exercises and quizzes, with immediate feedback. A user’s activity will be tracked to offer individualized
insight (like “next lesson” recommendations), and to allow user data to be synced across different installations – thus
a Kolibri learner can use his or her credentials on any linked Kolibri installation, for instance on different devices at a
school.
See https://learningequality.org/kolibri/ for more info.
147
Kolibri Documentation, Release 0.6.2
148
Chapter 7. What is Kolibri?
CHAPTER
8
How can I use it?
Kolibri is under active development and is not yet ready to be used in real deployments.
We expect a public beta to be available during second half of 2017.
In the meantime, take a look at KA-Lite, Kolibri’s predecessor which is already deployed around the world.
149
Kolibri Documentation, Release 0.6.2
150
Chapter 8. How can I use it?
CHAPTER
9
How can I contribute?
Warning: We welcome new contributors but since *Kolibri* is still in development, the APIs are subject to
change, and upgrade paths are not guaranteed for all releases. We do not recommend deploying Kolibri before the
first public betas are available.
Please start by:
• Reading our Developer Documentation available online, and in the docs/ directory.
• Get support in our Support Community.
• Sign up to receive developer announcements: Google groups.
• or via IRC: #kolibri on Freenode.
151
Kolibri Documentation, Release 0.6.2
152
Chapter 9. How can I contribute?
Index
A
U
AssessmentMetaData (class in kolibri.content.models), UUIDField (class in kolibri.content.models), 97
95
C
ChannelMetadata (class in kolibri.content.models), 95
ContentNode (class in kolibri.content.models), 95
ContentTag (class in kolibri.content.models), 96
F
File (class in kolibri.content.models), 96
G
get_descendant_content_ids()
(kolibri.content.models.ContentNode method),
96
get_download_filename()
(kolibri.content.models.File
method), 97
get_download_url()
(kolibri.content.models.File
method), 97
get_preset() (kolibri.content.models.File method), 97
get_storage_url()
(kolibri.content.models.LocalFile
method), 97
K
kolibri.content.api (module), 94
kolibri.content.models (module), 95
kolibri.plugins.hooks (module), 80
kolibri.plugins.registry (module), 79
kolibri.utils.version (module), 88
L
Language (class in kolibri.content.models), 97
LocalFile (class in kolibri.content.models), 97
O
OptionalPageNumberPagination
kolibri.content.api), 94
(class
in
153
Download PDF

advertising