Very rough cut at generating a course outline after publishing.
This commit is contained in:
committed by
Clinton Blackburn
parent
b3cc2f576f
commit
24fcab7f24
@@ -719,6 +719,8 @@ INSTALLED_APPS = (
|
||||
|
||||
# Additional problem types
|
||||
'edx_jsme', # Molecular Structure
|
||||
|
||||
'openedx.core.djangoapps.content.course_structures',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from django.conf import settings
|
||||
if not settings.configured:
|
||||
settings.configure()
|
||||
from django.core.cache import get_cache, InvalidCacheBackendError
|
||||
import django.dispatch
|
||||
import django.utils
|
||||
|
||||
import re
|
||||
@@ -39,6 +40,20 @@ except ImportError:
|
||||
|
||||
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):
|
||||
"""
|
||||
@@ -59,6 +74,7 @@ def create_modulestore_instance(
|
||||
i18n_service=None,
|
||||
fs_service=None,
|
||||
user_service=None,
|
||||
signal_handler=None,
|
||||
):
|
||||
"""
|
||||
This will return a new instance of a modulestore given an engine and options
|
||||
@@ -104,6 +120,7 @@ def create_modulestore_instance(
|
||||
i18n_service=i18n_service or ModuleI18nService(),
|
||||
fs_service=fs_service or xblock.reference.plugins.FSService(),
|
||||
user_service=user_service or xb_user_service,
|
||||
signal_handler=signal_handler or SignalHandler(class_),
|
||||
**_options
|
||||
)
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
fs_service=None,
|
||||
user_service=None,
|
||||
create_modulestore_instance=None,
|
||||
signal_handler=None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
@@ -142,6 +143,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
for course_key, store_key in self.mappings.iteritems()
|
||||
if store_key == key
|
||||
]
|
||||
|
||||
store = create_modulestore_instance(
|
||||
store_settings['ENGINE'],
|
||||
self.contentstore,
|
||||
@@ -150,6 +152,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
i18n_service=i18n_service,
|
||||
fs_service=fs_service,
|
||||
user_service=user_service,
|
||||
signal_handler=signal_handler,
|
||||
)
|
||||
# replace all named pointers to the store into actual pointers
|
||||
for course_key, store_name in self.mappings.iteritems():
|
||||
|
||||
@@ -504,6 +504,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
|
||||
i18n_service=None,
|
||||
fs_service=None,
|
||||
user_service=None,
|
||||
signal_handler=None,
|
||||
retry_wait_time=0.1,
|
||||
**kwargs):
|
||||
"""
|
||||
@@ -560,6 +561,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
|
||||
self.user_service = user_service
|
||||
|
||||
self._course_run_cache = {}
|
||||
self.signal_handler = signal_handler
|
||||
|
||||
def close_connections(self):
|
||||
"""
|
||||
|
||||
@@ -608,7 +608,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
|
||||
default_class=None,
|
||||
error_tracker=null_error_tracker,
|
||||
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.
|
||||
"""
|
||||
@@ -637,6 +637,8 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
|
||||
if user_service is not None:
|
||||
self.services["user"] = user_service
|
||||
|
||||
self.signal_handler = signal_handler
|
||||
|
||||
def close_connections(self):
|
||||
"""
|
||||
Closes any open connections to the underlying databases
|
||||
|
||||
@@ -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
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -34,7 +34,8 @@ def create_modulestore_instance(
|
||||
options,
|
||||
i18n_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
|
||||
|
||||
@@ -294,7 +294,8 @@ class XMLModuleStore(ModuleStoreReadBase):
|
||||
"""
|
||||
def __init__(
|
||||
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
|
||||
|
||||
@@ -1620,6 +1620,8 @@ INSTALLED_APPS = (
|
||||
'survey',
|
||||
|
||||
'lms.djangoapps.lms_xblock',
|
||||
|
||||
'openedx.core.djangoapps.content.course_structures',
|
||||
)
|
||||
|
||||
######################### MARKETING SITE ###############################
|
||||
|
||||
0
openedx/core/djangoapps/content/__init__.py
Normal file
0
openedx/core/djangoapps/content/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from ratelimitbackend import admin
|
||||
|
||||
from .models import CourseStructure
|
||||
|
||||
admin.site.register(CourseStructure)
|
||||
@@ -0,0 +1,40 @@
|
||||
# -*- 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']
|
||||
62
openedx/core/djangoapps/content/course_structures/models.py
Normal file
62
openedx/core/djangoapps/content/course_structures/models.py
Normal file
@@ -0,0 +1,62 @@
|
||||
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="",
|
||||
)
|
||||
Reference in New Issue
Block a user