Commit 24fcab7f by David Ormsbee Committed by Clinton Blackburn

Very rough cut at generating a course outline after publishing.

parent b3cc2f57
...@@ -719,6 +719,8 @@ INSTALLED_APPS = ( ...@@ -719,6 +719,8 @@ INSTALLED_APPS = (
# Additional problem types # Additional problem types
'edx_jsme', # Molecular Structure 'edx_jsme', # Molecular Structure
'openedx.core.djangoapps.content.course_structures',
) )
......
...@@ -11,6 +11,7 @@ from django.conf import settings ...@@ -11,6 +11,7 @@ from django.conf import settings
if not settings.configured: if not settings.configured:
settings.configure() settings.configure()
from django.core.cache import get_cache, InvalidCacheBackendError from django.core.cache import get_cache, InvalidCacheBackendError
import django.dispatch
import django.utils import django.utils
import re import re
...@@ -39,6 +40,20 @@ except ImportError: ...@@ -39,6 +40,20 @@ except ImportError:
ASSET_IGNORE_REGEX = getattr(settings, "ASSET_IGNORE_REGEX", r"(^\._.*$)|(^\.DS_Store$)|(^.*~$)") ASSET_IGNORE_REGEX = getattr(settings, "ASSET_IGNORE_REGEX", r"(^\._.*$)|(^\.DS_Store$)|(^.*~$)")
class SignalHandler(object):
course_published = django.dispatch.Signal(providing_args=["course_key", "version"])
_mapping = {
"course_published": course_published
}
def __init__(self, modulestore_class):
self.modulestore_class = modulestore_class
def send(self, signal_name, **kwargs):
signal = self._mapping[signal_name]
signal.send_robust(sender=self.modulestore_class, **kwargs)
def load_function(path): def load_function(path):
""" """
...@@ -59,6 +74,7 @@ def create_modulestore_instance( ...@@ -59,6 +74,7 @@ def create_modulestore_instance(
i18n_service=None, i18n_service=None,
fs_service=None, fs_service=None,
user_service=None, user_service=None,
signal_handler=None,
): ):
""" """
This will return a new instance of a modulestore given an engine and options This will return a new instance of a modulestore given an engine and options
...@@ -104,6 +120,7 @@ def create_modulestore_instance( ...@@ -104,6 +120,7 @@ def create_modulestore_instance(
i18n_service=i18n_service or ModuleI18nService(), i18n_service=i18n_service or ModuleI18nService(),
fs_service=fs_service or xblock.reference.plugins.FSService(), fs_service=fs_service or xblock.reference.plugins.FSService(),
user_service=user_service or xb_user_service, user_service=user_service or xb_user_service,
signal_handler=signal_handler or SignalHandler(class_),
**_options **_options
) )
......
...@@ -108,6 +108,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -108,6 +108,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
fs_service=None, fs_service=None,
user_service=None, user_service=None,
create_modulestore_instance=None, create_modulestore_instance=None,
signal_handler=None,
**kwargs **kwargs
): ):
""" """
...@@ -142,6 +143,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -142,6 +143,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
for course_key, store_key in self.mappings.iteritems() for course_key, store_key in self.mappings.iteritems()
if store_key == key if store_key == key
] ]
store = create_modulestore_instance( store = create_modulestore_instance(
store_settings['ENGINE'], store_settings['ENGINE'],
self.contentstore, self.contentstore,
...@@ -150,6 +152,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -150,6 +152,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
i18n_service=i18n_service, i18n_service=i18n_service,
fs_service=fs_service, fs_service=fs_service,
user_service=user_service, user_service=user_service,
signal_handler=signal_handler,
) )
# replace all named pointers to the store into actual pointers # replace all named pointers to the store into actual pointers
for course_key, store_name in self.mappings.iteritems(): for course_key, store_name in self.mappings.iteritems():
......
...@@ -504,6 +504,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -504,6 +504,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
i18n_service=None, i18n_service=None,
fs_service=None, fs_service=None,
user_service=None, user_service=None,
signal_handler=None,
retry_wait_time=0.1, retry_wait_time=0.1,
**kwargs): **kwargs):
""" """
...@@ -560,6 +561,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -560,6 +561,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
self.user_service = user_service self.user_service = user_service
self._course_run_cache = {} self._course_run_cache = {}
self.signal_handler = signal_handler
def close_connections(self): def close_connections(self):
""" """
......
...@@ -608,7 +608,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase): ...@@ -608,7 +608,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
default_class=None, default_class=None,
error_tracker=null_error_tracker, error_tracker=null_error_tracker,
i18n_service=None, fs_service=None, user_service=None, i18n_service=None, fs_service=None, user_service=None,
services=None, **kwargs): services=None, signal_handler=None, **kwargs):
""" """
:param doc_store_config: must have a host, db, and collection entries. Other common entries: port, tz_aware. :param doc_store_config: must have a host, db, and collection entries. Other common entries: port, tz_aware.
""" """
...@@ -637,6 +637,8 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase): ...@@ -637,6 +637,8 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
if user_service is not None: if user_service is not None:
self.services["user"] = user_service self.services["user"] = user_service
self.signal_handler = signal_handler
def close_connections(self): def close_connections(self):
""" """
Closes any open connections to the underlying databases Closes any open connections to the underlying databases
......
...@@ -354,7 +354,11 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli ...@@ -354,7 +354,11 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli
# Now it's been published, add the object to the courseware search index so that it appears in search results # Now it's been published, add the object to the courseware search index so that it appears in search results
CoursewareSearchIndexer.add_to_search_index(self, location) CoursewareSearchIndexer.add_to_search_index(self, location)
return self.get_item(location.for_branch(ModuleStoreEnum.BranchName.published), **kwargs) published_location = location.for_branch(ModuleStoreEnum.BranchName.published)
if self.signal_handler:
self.signal_handler.send("course_published", course_key=published_location.course_key)
return self.get_item(published_location, **kwargs)
def unpublish(self, location, user_id, **kwargs): def unpublish(self, location, user_id, **kwargs):
""" """
......
...@@ -34,7 +34,8 @@ def create_modulestore_instance( ...@@ -34,7 +34,8 @@ def create_modulestore_instance(
options, options,
i18n_service=None, i18n_service=None,
fs_service=None, fs_service=None,
user_service=None user_service=None,
signal_handler=None,
): ):
""" """
This will return a new instance of a modulestore given an engine and options This will return a new instance of a modulestore given an engine and options
......
...@@ -294,7 +294,8 @@ class XMLModuleStore(ModuleStoreReadBase): ...@@ -294,7 +294,8 @@ class XMLModuleStore(ModuleStoreReadBase):
""" """
def __init__( def __init__(
self, data_dir, default_class=None, course_dirs=None, course_ids=None, self, data_dir, default_class=None, course_dirs=None, course_ids=None,
load_error_modules=True, i18n_service=None, fs_service=None, user_service=None, **kwargs load_error_modules=True, i18n_service=None, fs_service=None, user_service=None,
signal_handler=None, **kwargs
): ):
""" """
Initialize an XMLModuleStore from data_dir Initialize an XMLModuleStore from data_dir
......
...@@ -1620,6 +1620,8 @@ INSTALLED_APPS = ( ...@@ -1620,6 +1620,8 @@ INSTALLED_APPS = (
'survey', 'survey',
'lms.djangoapps.lms_xblock', 'lms.djangoapps.lms_xblock',
'openedx.core.djangoapps.content.course_structures',
) )
######################### MARKETING SITE ############################### ######################### MARKETING SITE ###############################
......
from ratelimitbackend import admin
from .models import CourseStructure
admin.site.register(CourseStructure)
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'CourseStructure'
db.create_table('course_structures_coursestructure', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('created', self.gf('model_utils.fields.AutoCreatedField')(default=datetime.datetime.now)),
('modified', self.gf('model_utils.fields.AutoLastModifiedField')(default=datetime.datetime.now)),
('course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255, db_index=True)),
('version', self.gf('django.db.models.fields.CharField')(max_length=255)),
('structure_json', self.gf('django.db.models.fields.TextField')()),
))
db.send_create_signal('course_structures', ['CourseStructure'])
def backwards(self, orm):
# Deleting model 'CourseStructure'
db.delete_table('course_structures_coursestructure')
models = {
'course_structures.coursestructure': {
'Meta': {'object_name': 'CourseStructure'},
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}),
'structure_json': ('django.db.models.fields.TextField', [], {}),
'version': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['course_structures']
\ No newline at end of file
import json
from django.db import models
from django.dispatch import receiver
from celery.task import task
from model_utils.models import TimeStampedModel
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.django import modulestore, SignalHandler
from xmodule_django.models import CourseKeyField
class CourseStructure(TimeStampedModel):
course_id = CourseKeyField(max_length=255, db_index=True)
version = models.CharField(max_length=255, blank=True, default="")
# Right now the only thing we do with the structure doc is store it and
# send it on request. If we need to store a more complex data model later,
# we can do so and build a migration. The only problem with a normalized
# data model for this is that it will likely involve hundreds of rows, and
# we'd have to be careful about caching.
structure_json = models.TextField()
# Index together:
# (course_id, version)
# (course_id, created)
def course_structure(course_key):
course = modulestore().get_course(course_key, depth=None)
blocks_stack = [course]
blocks_dict = {}
while blocks_stack:
curr_block = blocks_stack.pop()
children = curr_block.get_children() if curr_block.has_children else []
blocks_dict[unicode(curr_block.scope_ids.usage_id)] = {
"usage_key": unicode(curr_block.scope_ids.usage_id),
"block_type": curr_block.category,
"display_name": curr_block.display_name,
"graded": curr_block.graded,
"format": curr_block.format,
"children": [unicode(ch.scope_ids.usage_id) for ch in children]
}
blocks_stack.extend(children)
return {
"root": unicode(course.scope_ids.usage_id),
"blocks": blocks_dict
}
@receiver(SignalHandler.course_published)
def listen_for_course_publish(sender, course_key, **kwargs):
update_course_structure(course_key)
@task()
def update_course_structure(course_key):
structure = course_structure(course_key)
CourseStructure.objects.create(
course_id=unicode(course_key),
structure_json=json.dumps(structure),
version="",
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment