On Github regisb / openedx-conference-2016
Régis Behmo (@regisb) Open edX Conference, June 14 2016 | Stanford, CAfun-mooc.com
https://www.fun-mooc.fr 350 courses, 700 000 users
# LMS + CMS /edx/app/edxapp/edx-platform # Forum service (Ruby code) /edx/app/forum/cs_comments_service # Programs-based product lines such as edX's XSeries offering. /edx/app/programs/programs # Payment services (seldom installed) /edx/app/ecommerce/ecommerce /edx/app/ecomworker/ecomworker # Theme customization (optional) /edx/app/edxapp/themes # Python virtual environment for edx-platform dependencies /edx/app/edxapp/venvs/edxapp ...
How many lines of code in edx-platform? (1 Dj = 1 Django)
How many lines of code in edx-platform? (1 Dj = 1 Django)
cloc --exclude-dir=<vendor-folders> edx-platform
------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- Python 1791 64982 98146 244140 # 57.1% Javascript 718 13745 10806 79920 # 18.7% SASS 300 10054 3769 43727 # 10.2% HTML 493 4716 30435 27928 # 6.5% CoffeeScript 118 2660 1459 14558 # 3.4% CSS 12 510 461 6675 # 1.5% SQL 2 8 9 4137 XML 286 172 33 3451 YAML 40 270 367 1827 Bourne Shell 13 219 222 700 make 2 31 6 143 ActionScript 1 21 23 74 XSD 1 8 0 41 ------------------------------------------------------------------------------- SUM: 3777 97396 145736 427321 -------------------------------------------------------------------------------
# LMS + CMS /edx/app/edxapp/edx-platform # 427321 # Forum service (Ruby code) /edx/app/forum/cs_comments_service # 5399 # Programs-based product lines such as edX's XSeries offering. /edx/app/programs/programs # 4906 # Payment services (seldom installed) /edx/app/ecommerce/ecommerce # 25670 /edx/app/ecomworker/ecomworker # 643 # Theme customization (optional) /edx/app/edxapp/themes # Python virtual environment /edx/app/edxapp/venvs/edxapp
/edx/app/edxapp/venvs/edxapp ora2 # 31245 edx-search # 3321 opaque-keys # 3089 recommender-xblock # 3001 xblock-poll # 2194 edx-submissions # 2193 edx-milestones # 1953 event-tracking # 1777 edx-sga # 1567 edx-reverification-block # 1418 xblock-google-drive # 1261 edx-user-state-client # 1083 ccx-keys # 748 rate-xblock # 598 acid-xblock # 750 edx-jsme # 607 done-xblock # 534
$ curl -L https://raw.github.com/edx/configuration/.../Vagrantfile > Vagrantfile $ OPENEDX_RELEASE="named-release/dogwood.3" vagrant up && vagrant ssh $ sudo su edxapp $ paver devstack lms ... Starting development server at http://0.0.0.0:8000/
lms/urls.py:
url( r'^courses/{}/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$'.format( settings.COURSE_ID_PATTERN, ), 'courseware.views.index', name='courseware_section', )
lms/djangoapps/courseware/views.py:
def index(request, course_id, chapter, section): ... course = get_course_with_access(..., course_key, ...) section_module = get_module_for_descriptor( user, request, section_descriptor, field_data_cache, course_key, position, course=course )
pprint(course.__class__.__mro__) # class and all base classes of 'course' (<class 'xblock.internal.CourseDescriptorWithMixins'>, <class 'xmodule.course_module.CourseDescriptor'>, <class 'xmodule.course_module.CourseFields'>, <class 'xmodule.seq_module.SequenceDescriptor'>, <class 'xmodule.seq_module.SequenceFields'>, <class 'xmodule.seq_module.ProctoringFields'>, <class 'xmodule.mako_module.MakoModuleDescriptor'>, <class 'xmodule.mako_module.MakoTemplateBlockBase'>, <class 'xmodule.xml_module.XmlDescriptor'>, <class 'xmodule.xml_module.XmlParserMixin'>, <class 'xmodule.x_module.XModuleDescriptor'>, <class 'xmodule.x_module.HTMLSnippet'>, <class 'xmodule.x_module.ResourceTemplates'>, <class 'lms.djangoapps.lms_xblock.mixin.LmsBlockMixin'>, <class 'xmodule.modulestore.inheritance.InheritanceMixin'>, <class 'xmodule.x_module.XModuleMixin'>, <class 'xmodule.x_module.XModuleFields'>, <class 'xblock.core.XBlock'>, <class 'xblock.mixins.XmlSerializationMixin'>, <class 'xblock.mixins.HierarchyMixin'>, <class 'xmodule.mixin.LicenseMixin'>, <class 'xmodule.modulestore.edit_info.EditInfoMixin'>, <class 'xblock.XBlockMixin'>, <class 'xblock.core.XBlockMixin'>, <class 'xblock.mixins.ScopedStorageMixin'>, <class 'xblock.mixins.RuntimeServicesMixin'>, <class 'xblock.mixins.HandlersMixin'>, <class 'xblock.mixins.IndexInfoMixin'>, <class 'xblock.mixins.ViewsMixin'>, <class 'xblock.core.SharedBlockBase'>, <class 'xblock.plugin.Plugin'>, <type 'object'>)
http://.../courses/...edX+DemoX+Demo_Course/courseware/interactive_demonstrations/19a30.../
courseware.views.index
course = get_course_with_access(...)
xblock.internal.CourseDescriptorWithMixins
?
class Shape(object): def __init__(self): self.edges = [] def perimeter(self): return sum([e.size for e in self.edges]) class Square(Shape): def __init__(self): self.edges = make_square() class Triangle(Shape): def __init__(self): self.edges = make_triangle() class ColorMixin(object): def colorize(self, color): for edge in self.edges: edge.color = color class ColoredSquare(Square, ColorMixin): pass
pprint(course.__class__.__mro__) # class and all base classes of 'course' (<class 'xblock.internal.CourseDescriptorWithMixins'>, <class 'xmodule.course_module.CourseDescriptor'>, <class 'xmodule.course_module.CourseFields'>, <class 'xmodule.seq_module.SequenceDescriptor'>, <class 'xmodule.seq_module.SequenceFields'>, <class 'xmodule.seq_module.ProctoringFields'>, <class 'xmodule.mako_module.MakoModuleDescriptor'>, <class 'xmodule.mako_module.MakoTemplateBlockBase'>, <class 'xmodule.xml_module.XmlDescriptor'>, <class 'xmodule.xml_module.XmlParserMixin'>, <class 'xmodule.x_module.XModuleDescriptor'>, <class 'xmodule.x_module.HTMLSnippet'>, <class 'xmodule.x_module.ResourceTemplates'>, <class 'lms.djangoapps.lms_xblock.mixin.LmsBlockMixin'>, <class 'xmodule.modulestore.inheritance.InheritanceMixin'>, <class 'xmodule.x_module.XModuleMixin'>, <class 'xmodule.x_module.XModuleFields'>, <class 'xblock.core.XBlock'>, <class 'xblock.mixins.XmlSerializationMixin'>, <class 'xblock.mixins.HierarchyMixin'>, <class 'xmodule.mixin.LicenseMixin'>, <class 'xmodule.modulestore.edit_info.EditInfoMixin'>, <class 'xblock.XBlockMixin'>, <class 'xblock.core.XBlockMixin'>, <class 'xblock.mixins.ScopedStorageMixin'>, <class 'xblock.mixins.RuntimeServicesMixin'>, <class 'xblock.mixins.HandlersMixin'>, <class 'xblock.mixins.IndexInfoMixin'>, <class 'xblock.mixins.ViewsMixin'>, <class 'xblock.core.SharedBlockBase'>, <class 'xblock.plugin.Plugin'>, <type 'object'>)
xblock/runtime.py:
class Mixologist(object): def __init__(self, mixins): self._mixins = tuple(mixins) def mix(self, cls): ... return type( base_class.__name__ + 'WithMixins', # class name (base_class, ) + self._mixins, # class bases {'unmixed_class': base_class} # class attributes )
xblock/runtime.py:
class Mixologist(object): def __init__(self, mixins): self._mixins = tuple(mixins) def mix(self, cls): ... return type( base_class.__name__ + 'WithMixins', # class name (base_class, ) + self._mixins, # class bases {'unmixed_class': base_class} # class attributes )
common/lib/xmodule/xmodule/modulestore/__init__.py:
mixologist = Mixologist(settings.XBLOCK_MIXINS)
lms/envs/common.py cms/envs/common.py
# These are the Mixins that should be added to every XBlock. # This should be moved into an XBlock Runtime/Application object # once the responsibility of XBlock creation is moved out of modulestore
XBLOCK_MIXINS = ( LmsBlockMixin, InheritanceMixin, XModuleMixin, EditInfoMixin, AuthoringMixin, # (In the CMS only) )
# These are the Mixins that should be added to every XBlock. # This should be moved into an XBlock Runtime/Application object # once the responsibility of XBlock creation is moved out of modulestoreWhat is an "XBlock"? What is an "XBlock Runtime/Application"? What is a "modulestore"?
General explanation of Open edX and XBlocks (2013) (2'24): https://www.youtube.com/watch?v=dTS-nsf7d3Q
"XBlocks all the way down" -- Ned Batchelder, Appsembler webinar (15'): http://www.appsembler.com/blog/open-edx-xblocks-webinar/
Examples: course, poll, peer assessed exams,jsme (molecule editor)...
The xblock directory: http://xblocks.com http://xblocks.org/
"XBlocks all the way down" -- Ned Batchelder
"XBlocks all the way down" -- Ned Batchelder
pprint(course.__class__.__mro__) # class and all base classes of 'course' (<class 'xblock.internal.CourseDescriptorWithMixins'>, <class 'xmodule.course_module.CourseDescriptor'>, <class 'xmodule.course_module.CourseFields'>, <class 'xmodule.seq_module.SequenceDescriptor'>, <class 'xmodule.seq_module.SequenceFields'>, <class 'xmodule.seq_module.ProctoringFields'>, <class 'xmodule.mako_module.MakoModuleDescriptor'>, <class 'xmodule.mako_module.MakoTemplateBlockBase'>, <class 'xmodule.xml_module.XmlDescriptor'>, <class 'xmodule.xml_module.XmlParserMixin'>, <class 'xmodule.x_module.XModuleDescriptor'>, <class 'xmodule.x_module.HTMLSnippet'>, <class 'xmodule.x_module.ResourceTemplates'>, <class 'lms.djangoapps.lms_xblock.mixin.LmsBlockMixin'>, <class 'xmodule.modulestore.inheritance.InheritanceMixin'>, <class 'xmodule.x_module.XModuleMixin'>, <class 'xmodule.x_module.XModuleFields'>, <class 'xblock.core.XBlock'>, <class 'xblock.mixins.XmlSerializationMixin'>, <class 'xblock.mixins.HierarchyMixin'>, <class 'xmodule.mixin.LicenseMixin'>, <class 'xmodule.modulestore.edit_info.EditInfoMixin'>, <class 'xblock.XBlockMixin'>, <class 'xblock.core.XBlockMixin'>, <class 'xblock.mixins.ScopedStorageMixin'>, <class 'xblock.mixins.RuntimeServicesMixin'>, <class 'xblock.mixins.HandlersMixin'>, <class 'xblock.mixins.IndexInfoMixin'>, <class 'xblock.mixins.ViewsMixin'>, <class 'xblock.core.SharedBlockBase'>, <class 'xblock.plugin.Plugin'>, <type 'object'>)
XBlock-poll: "A user-friendly way to query students."https://github.com/open-craft/xblock-poll
poll/poll.py:
@XBlock.wants('settings')class PollBlock(XBlock): answers = List(scope=Scope.settings, help="The answer options on this poll.") choice = String(scope=Scope.user_state, help="The student's answer") ... def studio_view(self): ... return xblock.fragment.Fragment(some_html_code) def img_alt_mandatory(self): """ Determine whether alt attributes for images are configured to be mandatory. """ settings_service = self.runtime.service(self, "settings") if not settings_service: return True xblock_settings = settings_service.get_settings_bucket(self) return xblock_settings.get('IMG_ALT_MANDATORY', True)
Runtime responsibilities:
1. Instantiate xblocks
class XBlock(...): def __init__(self, runtime, ...): ...
class Runtime(object): def construct_xblock_from_class(self, cls, scope_ids, field_data=None, *args, **kwargs): return self.mixologist.mix(cls)(runtime=self, scope_ids=scope_ids, field_data=field_data, *args, **kwargs)
2. Provide service to xblocks
settings_service = self.runtime.service(self, "settings")
xblock/core.py:
class XBlock(..., ScopedStorageMixin, ...): ...
xblock/mixins.py:
@RuntimeServicesMixin.needs('field-data') class ScopedStorageMixin(...): @property def _field_data(self): return self.runtime.service(self, 'field-data') def force_save_fields(self, field_names): ... self._field_data.set_many(self, fields_to_save_json)
xblock/fields.py:
class Field(...): def __get__(self, xblock, ...): field_data = xblock._field_data if field_data.has(xblock, self.name): return value = self.from_json(field_data.get(xblock, self.name)) else: ...
xblock/field_data.py:
class FieldData(object): @abstractmethod def get(self, block, name): raise NotImplementedError @abstractmethod def set(self, block, name, value): raise NotImplementedError @abstractmethod def delete(self, block, name): raise NotImplementedError @abstractmethod def has(self, block, name): try: self.get(block, name) return True except KeyError: return False def set_many(self, block, update_dict): for key, value in update_dict.items(): self.set(block, key, value)
Course components
are actually
StuffWithMixins
are also
XBlocks
instantiated by
a runtime
with a
'field-data' service
that stores data in
where?
"The scope of an xblock field defines the set of xblock instances over which the field takes the same value."
lms/djangoapps/lms_xblock/field_data.py: class LmsFieldData(SplitFieldData): def __init__(self, authored_data, student_data): # See also CmsFieldData in cms/lib/xblock/field_data.py ... super(LmsFieldData, self).__init__({ # one block, all users Scope.content: authored_data, # all courses Scope.settings: authored_data, # one course Scope.user_state_summary: student_data, # aggregated user data # one user Scope.user_state: student_data, # one block, one course Scope.user_info: student_data, # all blocks Scope.preferences: student_data, # all blocks from same type # XBlock-specific properties Scope.parent: authored_data, Scope.children: authored_data, })
class PollBlock(XBlock): answers = List(scope=Scope.settings, help="The answer options on this poll.") choice = String(scope=Scope.user_state, help="The student's answer") ...
lms/djangoapps/courseware/module_render.py:
def get_module_for_descriptor(user, request, descriptor, ..., course_key, ...): return get_module_system_for_user( ... student_data=KvsFieldData(DjangoKeyValueStore(...)) ... )
lms/envs/common.py:
MODULESTORE = { 'default': { 'ENGINE': 'xmodule.modulestore.mixed.MixedModuleStore', 'OPTIONS': { 'stores': [ { 'NAME': 'split', 'ENGINE': 'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore', ... }, { 'NAME': 'draft', 'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore', ... }, { 'NAME': 'xml', 'ENGINE': 'xmodule.modulestore.xml.XMLModuleStore', ... } ] } } }
# These are the Mixins that should be added to every XBlock. # This should be moved into an XBlock Runtime/Application object # once the responsibility of XBlock creation is moved out of modulestoreWhat is an "XBlock"? What is an "XBlock Runtime/Application"? What is a "modulestore"?
Introduction of XBlocks:
commit 789ac3fc875aa26380fc7f0865dc5c89a7359473 Author: Calen Pennington <calen.pennington@gmail.com> Date: Fri Jan 4 16:19:58 2013 -0500 Use the XBlock library as the base for XModule, so that we can incrementally rely on more and more of the XBlock api
Introduction of XBlocks:
# bisect from latest release to first commit $ git bisect start named-release/dogwood.3 cc1de22e2 $ git bisect run ./bisect.sh $ cat ./bisect.sh #! /bin/bash if [ "$(git grep -i xblock | wc -l)" -le "10" ]; then exit 0 else exit 1 fi
XModuleDescriptor instantiation:
ModuleSystem ------------- | | XModuleDescriptor Modulestore ------------- | (author data)
Bind XModuleDescriptor to user:
ModuleSystem ------------- | | Modulestore ------------- | (authored data) | | XModule Modulestore ------------- | (student data) | | Student ------------- |
XBlock instantiation:
Runtime --------------- | | Field data --------------- | (authored data) | | XBlock Field data --------------- | (student data) | | Student --------------- |
XBlock instantiation:
Runtime --------------- | | XBlock (partially working) Field data --------------- | (authored data)
Bind XBlock to user:
Runtime --------------- | | Field data --------------- | (authored data) | | XBlock Field data --------------- | (student data) | | Student --------------- |
lms/djangoapps/lms_xblock/runtime.py:
class LmsModuleSystem(ModuleSystem): ...
cms/djangoapps/contentstore/views/preview.py:
class PreviewModuleSystem(ModuleSystem): ...
common/lib/xmodule/xmodule/x_module.py:
class ModuleSystem(..., xblock.runtime.Runtime): ...
lms/djangoapps/courseware/module_render.py:
system = LmsModuleSystem( ..., services={ 'i18n': ModuleI18nService(), 'fs': FSService(), 'field-data': ..., }, ... )
lms/djangoapps/lms_xblock/runtime.py:
class LmsModuleSystem(ModuleSystem): # settings.LMS_RUNTIME_SERVICES = { # "myservice": callable, # ... # } def __init__(self, ..., services, ...): for service_name, callable in settings.LMS_RUNTIME_SERVICES.items(): if service_name not in services: services[service_name] = callable() ...
Régis Behmoregis@fun-mooc.fr
Richard Mochrichard@fun-mooc.fr
Sylvain Toésylvain@fun-mooc.fr
Slides available at https://github.com/regisb/openedx-conference-2016