by Pavel Simakov 2013-05-15
As of release 1.4, Course Builder allows integration of third-party extensions and modules. Using these capabilities, I quickly re-packaged Khan Academy Exercises as a Course Builder module. The module is reusable and is installed into Course Builder in seconds. A live demo is in my Course Builder Experimental Playground. The source code is on GitHub. In this post I describe step by step how this integration was developed. You will be able to do the same, if not better!
Step 1: Getting Started
Course Builder platform is modular and extensible. The easiest way to add your features is NOT to modify the existing source code, but to create a new module or custom component. My module name is
A module must be enabled in the
... import modules.khanex.khanex ... modules.khanex.khanex.register_module().enable() ...
A module registration function is a place where you connect your extensions to the rest of the Course Builder. I am adding the following:
... def register_module(): """Registers this module in the registry.""" # register custom tag tags.Registry.add_tag_binding('khanex', KhanExerciseTag) # register handlers zip_handler = ( '/khan-exercises', sites.make_zip_handler(ZIP_FILE)) render_handler = ( '/khan-exercises/khan-exercises/indirect/', KhanExerciseRenderer) # register module global custom_module custom_module = custom_modules.Module( 'Khan Academy Exercise', 'A set of pages for delivering Khan Academy Exercises via ' 'Course Builder.', , [render_handler, zip_handler]) return custom_module
All modules can be inspected by the administrator in the Deployment section of Course Builder instance. Our new module is here as well, highlighted in blue:
Step 2: Extending Lesson EditorCourse Builder uses XHTML to represent student facing content. Not only all existing HTML tags can be used to author the lesson, but you can create new tags! I will create a new tag
<h1>My lesson</h1> <p>My Exercise: <khanex name="adding_and_subtracting_negative_numbers"></khanex> </p>
Writing HTML by hand is so 1998... Instead, we simply tell Course Builder visual editor about the new tag properties. The editor will then let course author use the tag in either the WYSIWYG mode or the raw HTML mode as desired.
My tag will have only one property: a name of the specific exercise from the bundled Khan Academy Exercise library to load and show to a student. The tag properties are configured by constructing a tag schema object and overriding a method
def get_schema(self, unused_handler): """Make schema with a list of all exercises by inspecting a zip file.""" ... reg = schema_fields.FieldRegistry('Khan Exercises') reg.add_property( schema_fields.SchemaField( 'name', 'Exercises', 'select', optional=True, select_data=items, description=('The relative URL name of the exercise.'))) return reg
Now the tag can be managed automatically by the visual editor (as well as entered manually as XHTML) and will appear in the rich text edit toolbar. Like this:
Step 3: Extending Lesson Renderer
Next step is to write some code to convert our new
The rendering code goes into a
The runtime behind Khan Academy Exercises is undocumented and quite complex. I will not get into that fully here. The script to embed an exercise was written by me a while ago and has nothing specific to Course Builder. If you care to know, this is what happens behind the scene:
Step 4: Recording Student Attempts
When student interacts with an exercise course author may want to know all the attempts he makes: correct or incorrect. The Khan Academy Exercises package I use has the callback that sends student interaction data to a server of your choice. We now need to implement the server-side handler that receives this data.
... # record submission models.EventEntity.record( 'module-khanex.exercise-submit', self.get_user(), data) ...
You can see all captured events using Google App Engine datastore viewer. Here is what a typical event looks like:
Step 5: Recording Student Progress
A progress indicator is an empty, partially- or fully-filled circle, shown to a student across the Course. It shows the completion state of various components. Course Builder progress tracker manages this functionality. In the same handler above, after we figure out current unit and lesson, we update the progress indicator and record that student attempted a specific exercise:
... # update progress unit_id, lesson_id = self._get_unit_lesson_from(data) self.get_course().get_progress_tracker().put_activity_accessed( student, unit_id, lesson_id) ...
Now, answering a Khan Academy Exercise exercise will update the state of the progress indicator automatically as expected:
There are many interesting things one can do with data submission: record progress only if student gave a right answer, update student grade book, find related exercises, etc. I implemented the simplest.
Step 6: Enabling Dynamic Configuration Options
It’s common for a module to require global configuration parameters to be set by a system administrator. Course Builder supports dynamic configuration and provides user interface for managing configuration parameters automatically.
For illustration, let's define a new configuration property that holds a list of exercise names, allowed for use. The bundled Khan Academy Exercise library contains over 400 exercises, but we can now restrict these and only allow handful to be used by the course author. It takes just a couple of lines of code to define a new dynamic configuration property. Here is how I did it:
... from models.config import ConfigProperty ... WHITELISTED_EXERCISES = ConfigProperty( '_khanex_whitelisted', str, ( 'A white-listed exercises that can be show to students. If this list ' 'is empty, all exercises are available.'), default_value='', multiline=True) ...
Now that the property if defined it can be modified to a desired value at runtime using visual editor. Here is the new property (in blue) in the list of settings:
Click the "Override" button to bring up the editor and modify the value. Here is what the editor for the property looks like:
Note that the user interface was constructed automatically given a property definition. I did not have to do any work manually. Course Builder takes care of building user interface, storing property values in the datastore and propagating value updates to all front end application instances. The only thing left to do is to check the value before listing or serving specific exercise.
Step 7: Monitoring Performance in Real-Time
Modern web applications with large number of concurrent users must have built-in real-time monitoring. Course Builder provides this functionality out of the box and we can use it in a our new module. I create new >performance counter to count a number of times students submits an attempt to a server. It takes just couple lines of code to declare the counter and then update its value in the
... from models.counters import PerfCounter ... # declare ATTEMPT_COUNT = PerfCounter( 'gcb-khanex-attempt-count', 'A number of attempts made by all users on all exercises.') ... # update ATTEMPT_COUNT.inc() ...
The performance counter values are automatically aggregate across all front end application instances and displayed to the system administrator on the Metrics tab in real-time!
Step 8: Custom Analytics DashboardOne can also create a custom DurableJob, compute exercise attempts statistics and then display the results to the course author via a dashboard. I have not done it in my module, but you can see how it's done in the peer review module.
Step 9: Putting it All Together
We now are all done. In just 200 lines of code we fully integrated Khan Academy Exercises into Course Builder. Here is a LIVE DEMO! All that's left is to deploy the code to real Google App Engine instance. It takes just 1 minute to deploy!
The Final Word
Course Builder is modular and extensible. It is easy to create your own extension. It is easy to deploy and run at huge scale. Just focus on your subject matter, your creativity and your students.
Software Secret Weapons (TM) Copyright (C) 2004-2017 by Pavel Simakov
any ideas, thoughts, conclusions, recommendations or the source code presented on this site