Merge pull request #2918 from edx/dhm/meld_pretty_factory
Dhm/meld pretty factory
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import unittest
|
||||
from django.conf import settings
|
||||
|
||||
from xmodule import templates
|
||||
from xmodule.modulestore.tests import persistent_factories
|
||||
@@ -10,8 +9,6 @@ from xmodule.capa_module import CapaDescriptor
|
||||
from xmodule.modulestore.locator import CourseLocator, BlockUsageLocator, LocalId
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError, DuplicateCourseError
|
||||
from xmodule.html_module import HtmlDescriptor
|
||||
from xmodule.modulestore import inheritance
|
||||
from xblock.core import XBlock
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
|
||||
@@ -57,14 +54,14 @@ class TemplateTests(unittest.TestCase):
|
||||
|
||||
def test_factories(self):
|
||||
test_course = persistent_factories.PersistentCourseFactory.create(
|
||||
course_id='testx.tempcourse', org='testx', prettyid='tempcourse',
|
||||
course_id='testx.tempcourse', org='testx',
|
||||
display_name='fun test course', user_id='testbot'
|
||||
)
|
||||
self.assertIsInstance(test_course, CourseDescriptor)
|
||||
self.assertEqual(test_course.display_name, 'fun test course')
|
||||
index_info = modulestore('split').get_course_index_info(test_course.location)
|
||||
self.assertEqual(index_info['org'], 'testx')
|
||||
self.assertEqual(index_info['prettyid'], 'tempcourse')
|
||||
self.assertEqual(index_info['_id'], 'testx.tempcourse')
|
||||
|
||||
test_chapter = persistent_factories.ItemFactory.create(display_name='chapter 1',
|
||||
parent_location=test_course.location)
|
||||
@@ -75,31 +72,31 @@ class TemplateTests(unittest.TestCase):
|
||||
|
||||
with self.assertRaises(DuplicateCourseError):
|
||||
persistent_factories.PersistentCourseFactory.create(
|
||||
course_id='testx.tempcourse', org='testx', prettyid='tempcourse',
|
||||
course_id='testx.tempcourse', org='testx',
|
||||
display_name='fun test course', user_id='testbot'
|
||||
)
|
||||
|
||||
def test_temporary_xblocks(self):
|
||||
"""
|
||||
Test using load_from_json to create non persisted xblocks
|
||||
Test create_xblock to create non persisted xblocks
|
||||
"""
|
||||
test_course = persistent_factories.PersistentCourseFactory.create(
|
||||
course_id='testx.tempcourse', org='testx', prettyid='tempcourse',
|
||||
course_id='testx.tempcourse', org='testx',
|
||||
display_name='fun test course', user_id='testbot'
|
||||
)
|
||||
|
||||
test_chapter = self.load_from_json({'category': 'chapter',
|
||||
'fields': {'display_name': 'chapter n'}},
|
||||
test_course.system, parent_xblock=test_course)
|
||||
test_chapter = modulestore('split').create_xblock(
|
||||
test_course.system, 'chapter', {'display_name': 'chapter n'}, parent_xblock=test_course
|
||||
)
|
||||
self.assertIsInstance(test_chapter, SequenceDescriptor)
|
||||
self.assertEqual(test_chapter.display_name, 'chapter n')
|
||||
self.assertIn(test_chapter, test_course.get_children())
|
||||
|
||||
# test w/ a definition (e.g., a problem)
|
||||
test_def_content = '<problem>boo</problem>'
|
||||
test_problem = self.load_from_json({'category': 'problem',
|
||||
'fields': {'data': test_def_content}},
|
||||
test_course.system, parent_xblock=test_chapter)
|
||||
test_problem = modulestore('split').create_xblock(
|
||||
test_course.system, 'problem', {'data': test_def_content}, parent_xblock=test_chapter
|
||||
)
|
||||
self.assertIsInstance(test_problem, CapaDescriptor)
|
||||
self.assertEqual(test_problem.data, test_def_content)
|
||||
self.assertIn(test_problem, test_chapter.get_children())
|
||||
@@ -111,20 +108,22 @@ class TemplateTests(unittest.TestCase):
|
||||
try saving temporary xblocks
|
||||
"""
|
||||
test_course = persistent_factories.PersistentCourseFactory.create(
|
||||
course_id='testx.tempcourse', org='testx', prettyid='tempcourse',
|
||||
display_name='fun test course', user_id='testbot')
|
||||
test_chapter = self.load_from_json({'category': 'chapter',
|
||||
'fields': {'display_name': 'chapter n'}},
|
||||
test_course.system, parent_xblock=test_course)
|
||||
course_id='testx.tempcourse', org='testx',
|
||||
display_name='fun test course', user_id='testbot'
|
||||
)
|
||||
test_chapter = modulestore('split').create_xblock(
|
||||
test_course.system, 'chapter', {'display_name': 'chapter n'}, parent_xblock=test_course
|
||||
)
|
||||
self.assertEqual(test_chapter.display_name, 'chapter n')
|
||||
test_def_content = '<problem>boo</problem>'
|
||||
# create child
|
||||
new_block = self.load_from_json({
|
||||
'category': 'problem',
|
||||
'fields': {
|
||||
new_block = modulestore('split').create_xblock(
|
||||
test_course.system,
|
||||
'problem',
|
||||
fields={
|
||||
'data': test_def_content,
|
||||
'display_name': 'problem'
|
||||
}},
|
||||
test_course.system,
|
||||
},
|
||||
parent_xblock=test_chapter
|
||||
)
|
||||
self.assertIsNotNone(new_block.definition_locator)
|
||||
@@ -149,7 +148,6 @@ class TemplateTests(unittest.TestCase):
|
||||
def test_delete_course(self):
|
||||
test_course = persistent_factories.PersistentCourseFactory.create(
|
||||
course_id='edu.harvard.history.doomed', org='testx',
|
||||
prettyid='edu.harvard.history.doomed',
|
||||
display_name='doomed test course',
|
||||
user_id='testbot')
|
||||
persistent_factories.ItemFactory.create(display_name='chapter 1',
|
||||
@@ -173,9 +171,9 @@ class TemplateTests(unittest.TestCase):
|
||||
"""
|
||||
test_course = persistent_factories.PersistentCourseFactory.create(
|
||||
course_id='edu.harvard.history.hist101', org='testx',
|
||||
prettyid='edu.harvard.history.hist101',
|
||||
display_name='history test course',
|
||||
user_id='testbot')
|
||||
user_id='testbot'
|
||||
)
|
||||
chapter = persistent_factories.ItemFactory.create(display_name='chapter 1',
|
||||
parent_location=test_course.location, user_id='testbot')
|
||||
sub = persistent_factories.ItemFactory.create(display_name='subsection 1',
|
||||
@@ -242,44 +240,3 @@ class TemplateTests(unittest.TestCase):
|
||||
mapper = loc_mapper()
|
||||
self.assertEqual(modulestore('split').loc_mapper, mapper)
|
||||
|
||||
# ================================= JSON PARSING ===========================
|
||||
# These are example methods for creating xmodules in memory w/o persisting them.
|
||||
# They were in x_module but since xblock is not planning to support them but will
|
||||
# allow apps to use this type of thing, I put it here.
|
||||
@staticmethod
|
||||
def load_from_json(json_data, system, default_class=None, parent_xblock=None):
|
||||
"""
|
||||
This method instantiates the correct subclass of XModuleDescriptor based
|
||||
on the contents of json_data. It does not persist it and can create one which
|
||||
has no usage id.
|
||||
|
||||
parent_xblock is used to compute inherited metadata as well as to append the new xblock.
|
||||
|
||||
json_data:
|
||||
- 'location' : must have this field
|
||||
- 'category': the xmodule category (required or location must be a Location)
|
||||
- 'metadata': a dict of locally set metadata (not inherited)
|
||||
- 'children': a list of children's usage_ids w/in this course
|
||||
- 'definition':
|
||||
- '_id' (optional): the usage_id of this. Will generate one if not given one.
|
||||
"""
|
||||
class_ = XBlock.load_class(
|
||||
json_data.get('category', json_data.get('location', {}).get('category')),
|
||||
default_class,
|
||||
select=settings.XBLOCK_SELECT_FUNCTION
|
||||
)
|
||||
usage_id = json_data.get('_id', None)
|
||||
if not '_inherited_settings' in json_data and parent_xblock is not None:
|
||||
json_data['_inherited_settings'] = parent_xblock.xblock_kvs.inherited_settings.copy()
|
||||
json_fields = json_data.get('fields', {})
|
||||
for field_name in inheritance.InheritanceMixin.fields:
|
||||
if field_name in json_fields:
|
||||
json_data['_inherited_settings'][field_name] = json_fields[field_name]
|
||||
|
||||
new_block = system.xblock_from_json(class_, usage_id, json_data)
|
||||
if parent_xblock is not None:
|
||||
parent_xblock.children.append(new_block.scope_ids.usage_id)
|
||||
# decache pending children field settings
|
||||
parent_xblock.save()
|
||||
return new_block
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ def course_handler(request, tag=None, package_id=None, branch=None, version_guid
|
||||
index entry.
|
||||
PUT
|
||||
json: update this course (index entry not xblock) such as repointing head, changing display name, org,
|
||||
package_id, prettyid. Return same json as above.
|
||||
package_id. Return same json as above.
|
||||
DELETE
|
||||
json: delete this branch from this course (leaving off /branch/draft would imply delete the course)
|
||||
"""
|
||||
|
||||
@@ -22,12 +22,14 @@ log = logging.getLogger(__name__)
|
||||
|
||||
class LocalId(object):
|
||||
"""
|
||||
Class for local ids for non-persisted xblocks
|
||||
|
||||
Should be hashable and distinguishable, but nothing else
|
||||
Class for local ids for non-persisted xblocks (which can have hardcoded block_ids if necessary)
|
||||
"""
|
||||
def __init__(self, block_id=None):
|
||||
self.block_id = block_id
|
||||
super(LocalId, self).__init__()
|
||||
|
||||
def __str__(self):
|
||||
return "localid_{}".format(id(self))
|
||||
return "localid_{}".format(self.block_id or id(self))
|
||||
|
||||
|
||||
class Locator(object):
|
||||
@@ -358,8 +360,7 @@ class CourseLocator(Locator):
|
||||
Generate a discussion group id based on course
|
||||
|
||||
To make compatible with old Location object functionality. I don't believe this behavior fits at this
|
||||
place, but I have no way to override. If this is really needed, it should probably use the pretty_id to seed
|
||||
the name although that's mutable. We should also clearly define the purpose and restrictions of this
|
||||
place, but I have no way to override. We should clearly define the purpose and restrictions of this
|
||||
(e.g., I'm assuming periods are fine).
|
||||
"""
|
||||
return self.package_id
|
||||
|
||||
@@ -282,7 +282,6 @@ class MixedModuleStore(ModuleStoreWriteBase):
|
||||
:param fields: a dict of xblock field name - value pairs for the course module.
|
||||
:param metadata: the old way of setting fields by knowing which ones are scope.settings v scope.content
|
||||
:param definition_data: the complement to metadata which is also a subset of fields
|
||||
:param pretty_id: a field split.create_course uses and may quit using
|
||||
:returns: course xblock
|
||||
"""
|
||||
store = self.modulestores[store_name]
|
||||
@@ -297,11 +296,10 @@ class MixedModuleStore(ModuleStoreWriteBase):
|
||||
org = None
|
||||
|
||||
org = kwargs.pop('org', org)
|
||||
pretty_id = kwargs.pop('pretty_id', course_id)
|
||||
fields = kwargs.pop('fields', {})
|
||||
fields.update(kwargs.pop('metadata', {}))
|
||||
fields.update(kwargs.pop('definition_data', {}))
|
||||
course = store.create_course(course_id, org, pretty_id, user_id, fields=fields, **kwargs)
|
||||
course = store.create_course(course_id, org, user_id, fields=fields, **kwargs)
|
||||
else: # assume mongo
|
||||
course = store.create_course(course_id, **kwargs)
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class SplitMigrator(object):
|
||||
original_course = self.direct_modulestore.get_item(course_location)
|
||||
new_course_root_locator = self.loc_mapper.translate_location(old_course_id, course_location)
|
||||
new_course = self.split_modulestore.create_course(
|
||||
new_package_id, course_location.org, original_course.display_name,
|
||||
new_package_id, course_location.org,
|
||||
user.id,
|
||||
fields=self._get_json_fields_translate_children(original_course, old_course_id, True),
|
||||
root_block_id=new_course_root_locator.block_id,
|
||||
|
||||
@@ -5,7 +5,6 @@ Representation:
|
||||
* course_index: a dictionary:
|
||||
** '_id': package_id (e.g., myu.mydept.mycourse.myrun),
|
||||
** 'org': the org's id. Only used for searching not identity,
|
||||
** 'prettyid': a vague to-be-determined field probably more useful to storing searchable tags,
|
||||
** 'edited_by': user_id of user who created the original entry,
|
||||
** 'edited_on': the datetime of the original creation,
|
||||
** 'versions': versions_dict: {branch_id: structure_id, ...}
|
||||
@@ -99,6 +98,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
A Mongodb backed ModuleStore supporting versions, inheritance,
|
||||
and sharing.
|
||||
"""
|
||||
|
||||
SCHEMA_VERSION = 1
|
||||
reference_type = Locator
|
||||
def __init__(self, doc_store_config, fs_root, render_template,
|
||||
default_class=None,
|
||||
@@ -468,7 +469,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
heads. This function is primarily for test verification but may serve some
|
||||
more general purpose.
|
||||
:param course_locator: must have a package_id set
|
||||
:return {'org': , 'prettyid': ,
|
||||
:return {'org': string,
|
||||
versions: {'draft': the head draft version id,
|
||||
'published': the head published version id if any,
|
||||
},
|
||||
@@ -618,7 +619,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
"edited_on": datetime.datetime.now(UTC),
|
||||
"previous_version": None,
|
||||
"original_version": new_id,
|
||||
}
|
||||
},
|
||||
'schema_version': self.SCHEMA_VERSION,
|
||||
}
|
||||
self.db_connection.insert_definition(document)
|
||||
definition_locator = DefinitionLocator(new_id)
|
||||
@@ -654,6 +656,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
old_definition['edit_info']['edited_on'] = datetime.datetime.now(UTC)
|
||||
# previous version id
|
||||
old_definition['edit_info']['previous_version'] = definition_locator.definition_id
|
||||
old_definition['schema_version'] = self.SCHEMA_VERSION
|
||||
self.db_connection.insert_definition(old_definition)
|
||||
return DefinitionLocator(old_definition['_id']), True
|
||||
else:
|
||||
@@ -811,7 +814,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
return self.get_item(item_loc)
|
||||
|
||||
def create_course(
|
||||
self, course_id, org, prettyid, user_id, fields=None,
|
||||
self, course_id, org, user_id, fields=None,
|
||||
master_branch='draft', versions_dict=None, root_category='course',
|
||||
root_block_id='course'
|
||||
):
|
||||
@@ -870,7 +873,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
'edited_on': datetime.datetime.now(UTC),
|
||||
'previous_version': None,
|
||||
'original_version': definition_id,
|
||||
}
|
||||
},
|
||||
'schema_version': self.SCHEMA_VERSION,
|
||||
}
|
||||
self.db_connection.insert_definition(definition_entry)
|
||||
|
||||
@@ -904,6 +908,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
definition['edit_info']['edited_by'] = user_id
|
||||
definition['edit_info']['edited_on'] = datetime.datetime.now(UTC)
|
||||
definition['_id'] = ObjectId()
|
||||
definition['schema_version'] = self.SCHEMA_VERSION
|
||||
self.db_connection.insert_definition(definition)
|
||||
root_block['definition'] = definition['_id']
|
||||
root_block['edit_info']['edited_on'] = datetime.datetime.now(UTC)
|
||||
@@ -917,10 +922,11 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
index_entry = {
|
||||
'_id': course_id,
|
||||
'org': org,
|
||||
'prettyid': prettyid,
|
||||
'edited_by': user_id,
|
||||
'edited_on': datetime.datetime.now(UTC),
|
||||
'versions': versions_dict}
|
||||
'versions': versions_dict,
|
||||
'schema_version': self.SCHEMA_VERSION,
|
||||
}
|
||||
self.db_connection.insert_course_index(index_entry)
|
||||
return self.get_course(CourseLocator(package_id=course_id, branch=master_branch))
|
||||
|
||||
@@ -947,7 +953,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
# check children
|
||||
original_entry = self._get_block_from_structure(original_structure, descriptor.location.block_id)
|
||||
is_updated = is_updated or (
|
||||
descriptor.has_children and original_entry['fields']['children'] != descriptor.children
|
||||
descriptor.has_children and original_entry['fields'].get('children', []) != descriptor.children
|
||||
)
|
||||
# check metadata
|
||||
if not is_updated:
|
||||
@@ -986,6 +992,40 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
# nothing changed, just return the one sent in
|
||||
return descriptor
|
||||
|
||||
def create_xblock(self, runtime, category, fields=None, block_id=None, definition_id=None, parent_xblock=None):
|
||||
"""
|
||||
This method instantiates the correct subclass of XModuleDescriptor based
|
||||
on the contents of json_data. It does not persist it and can create one which
|
||||
has no usage id.
|
||||
|
||||
parent_xblock is used to compute inherited metadata as well as to append the new xblock.
|
||||
|
||||
json_data:
|
||||
- 'category': the xmodule category
|
||||
- 'fields': a dict of locally set fields (not inherited) in json format not pythonic typed format!
|
||||
- 'definition': the object id of the existing definition
|
||||
"""
|
||||
xblock_class = runtime.load_block_type(category)
|
||||
json_data = {
|
||||
'category': category,
|
||||
'fields': fields or {},
|
||||
}
|
||||
if definition_id is not None:
|
||||
json_data['definition'] = definition_id
|
||||
if parent_xblock is not None:
|
||||
json_data['_inherited_settings'] = parent_xblock.xblock_kvs.inherited_settings.copy()
|
||||
if fields is not None:
|
||||
for field_name in inheritance.InheritanceMixin.fields:
|
||||
if field_name in fields:
|
||||
json_data['_inherited_settings'][field_name] = fields[field_name]
|
||||
|
||||
new_block = runtime.xblock_from_json(xblock_class, block_id, json_data)
|
||||
if parent_xblock is not None:
|
||||
parent_xblock.children.append(new_block.scope_ids.usage_id)
|
||||
# decache pending children field settings
|
||||
parent_xblock.save()
|
||||
return new_block
|
||||
|
||||
def persist_xblock_dag(self, xblock, user_id, force=False):
|
||||
"""
|
||||
create or update the xblock and all of its children. The xblock's location must specify a course.
|
||||
@@ -1044,8 +1084,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
# generate an id
|
||||
is_new = True
|
||||
is_updated = True
|
||||
block_id = self._generate_block_id(structure_blocks, xblock.category)
|
||||
encoded_block_id = block_id
|
||||
block_id = getattr(xblock.scope_ids.usage_id.block_id, 'block_id', None)
|
||||
if block_id is None:
|
||||
block_id = self._generate_block_id(structure_blocks, xblock.category)
|
||||
encoded_block_id = LocMapperStore.encode_key_for_mongo(block_id)
|
||||
xblock.scope_ids.usage_id.block_id = block_id
|
||||
else:
|
||||
is_new = False
|
||||
@@ -1448,6 +1490,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
new_structure['previous_version'] = structure['_id']
|
||||
new_structure['edited_by'] = user_id
|
||||
new_structure['edited_on'] = datetime.datetime.now(UTC)
|
||||
new_structure['schema_version'] = self.SCHEMA_VERSION
|
||||
return new_structure
|
||||
|
||||
def _find_local_root(self, element_to_find, possibility, tree):
|
||||
@@ -1508,7 +1551,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
'original_version': new_id,
|
||||
'edited_by': user_id,
|
||||
'edited_on': datetime.datetime.now(UTC),
|
||||
'blocks': blocks
|
||||
'blocks': blocks,
|
||||
'schema_version': self.SCHEMA_VERSION,
|
||||
}
|
||||
|
||||
def _get_parents_from_structure(self, block_id, structure):
|
||||
|
||||
@@ -24,7 +24,6 @@ class PersistentCourseFactory(SplitFactory):
|
||||
keywords: any xblock field plus (note, the below are filtered out; so, if they
|
||||
become legitimate xblock fields, they won't be settable via this factory)
|
||||
* org: defaults to textX
|
||||
* prettyid: defaults to 999
|
||||
* master_branch: (optional) defaults to 'draft'
|
||||
* user_id: (optional) defaults to 'test_user'
|
||||
* display_name (xblock field): will default to 'Robot Super Course' unless provided
|
||||
@@ -33,14 +32,14 @@ class PersistentCourseFactory(SplitFactory):
|
||||
|
||||
# pylint: disable=W0613
|
||||
@classmethod
|
||||
def _create(cls, target_class, course_id='testX.999', org='testX', prettyid='999', user_id='test_user',
|
||||
def _create(cls, target_class, course_id='testX.999', org='testX', user_id='test_user',
|
||||
master_branch='draft', **kwargs):
|
||||
|
||||
modulestore = kwargs.pop('modulestore')
|
||||
root_block_id = kwargs.pop('root_block_id', 'course')
|
||||
# Write the data to the mongo datastore
|
||||
new_course = modulestore.create_course(
|
||||
course_id, org, prettyid, user_id, fields=kwargs,
|
||||
course_id, org, user_id, fields=kwargs,
|
||||
master_branch=master_branch, root_block_id=root_block_id
|
||||
)
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ class TestOrphan(unittest.TestCase):
|
||||
fields.update(data)
|
||||
# split requires the course to be created separately from creating items
|
||||
self.split_mongo.create_course(
|
||||
self.split_package_id, 'test_org', 'my course', self.userid, fields=fields, root_block_id='runid'
|
||||
self.split_package_id, 'test_org', self.userid, fields=fields, root_block_id='runid'
|
||||
)
|
||||
self.course_location = Location('i4x', 'test_org', 'test_course', 'course', 'runid')
|
||||
self.old_mongo.create_and_save_xmodule(self.course_location, data, metadata)
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
'''
|
||||
Created on Mar 25, 2013
|
||||
|
||||
@author: dmitchell
|
||||
'''
|
||||
"""
|
||||
Test split modulestore w/o using any django stuff.
|
||||
"""
|
||||
import datetime
|
||||
import subprocess
|
||||
import unittest
|
||||
import uuid
|
||||
from importlib import import_module
|
||||
|
||||
from xblock.fields import Scope
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.modulestore.exceptions import InsufficientSpecificationError, ItemNotFoundError, VersionConflictError, \
|
||||
DuplicateItemError, DuplicateCourseError
|
||||
from xmodule.modulestore.locator import CourseLocator, BlockUsageLocator, VersionTree, DefinitionLocator
|
||||
from xmodule.modulestore.inheritance import InheritanceMixin
|
||||
from xmodule.x_module import XModuleMixin
|
||||
from pytz import UTC
|
||||
from path import path
|
||||
import re
|
||||
import random
|
||||
|
||||
from xblock.fields import Scope
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.modulestore.exceptions import (InsufficientSpecificationError, ItemNotFoundError, VersionConflictError,
|
||||
DuplicateItemError, DuplicateCourseError)
|
||||
from xmodule.modulestore.locator import CourseLocator, BlockUsageLocator, VersionTree, LocalId
|
||||
from xmodule.modulestore.inheritance import InheritanceMixin
|
||||
from xmodule.x_module import XModuleMixin
|
||||
from xmodule.fields import Date, Timedelta
|
||||
from bson.objectid import ObjectId
|
||||
from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
|
||||
|
||||
|
||||
class SplitModuleTest(unittest.TestCase):
|
||||
'''
|
||||
@@ -52,52 +51,442 @@ class SplitModuleTest(unittest.TestCase):
|
||||
|
||||
modulestore = None
|
||||
|
||||
# These version_guids correspond to values hard-coded in fixture files
|
||||
# used for these tests. The files live in mitx/fixtures/splitmongo_json/*
|
||||
|
||||
GUID_D0 = "1d00000000000000dddd0000" # v12345d
|
||||
GUID_D1 = "1d00000000000000dddd1111" # v12345d1
|
||||
GUID_D2 = "1d00000000000000dddd2222" # v23456d
|
||||
GUID_D3 = "1d00000000000000dddd3333" # v12345d0
|
||||
GUID_D4 = "1d00000000000000dddd4444" # v23456d0
|
||||
GUID_D5 = "1d00000000000000dddd5555" # v345679d
|
||||
GUID_P = "1d00000000000000eeee0000" # v23456p
|
||||
|
||||
_date_field = Date()
|
||||
_time_delta_field = Timedelta()
|
||||
COURSE_CONTENT = {
|
||||
"testx.GreekHero": {
|
||||
"org": "testx",
|
||||
"root_block_id": "head12345",
|
||||
"user_id": "test@edx.org",
|
||||
"fields": {
|
||||
"tabs": [
|
||||
{
|
||||
"type": "courseware"
|
||||
},
|
||||
{
|
||||
"type": "course_info",
|
||||
"name": "Course Info"
|
||||
},
|
||||
{
|
||||
"type": "discussion",
|
||||
"name": "Discussion"
|
||||
},
|
||||
{
|
||||
"type": "wiki",
|
||||
"name": "Wiki"
|
||||
}
|
||||
],
|
||||
"start": _date_field.from_json("2013-02-14T05:00"),
|
||||
"display_name": "The Ancient Greek Hero",
|
||||
"grading_policy": {
|
||||
"GRADER": [
|
||||
{
|
||||
"min_count": 5,
|
||||
"weight": 0.15,
|
||||
"type": "Homework",
|
||||
"drop_count": 1,
|
||||
"short_label": "HWa"
|
||||
},
|
||||
{
|
||||
"short_label": "",
|
||||
"min_count": 2,
|
||||
"type": "Lab",
|
||||
"drop_count": 0,
|
||||
"weight": 0.15
|
||||
},
|
||||
{
|
||||
"short_label": "Midterm",
|
||||
"min_count": 1,
|
||||
"type": "Midterm Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.3
|
||||
},
|
||||
{
|
||||
"short_label": "Final",
|
||||
"min_count": 1,
|
||||
"type": "Final Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.4
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS": {
|
||||
"Pass": 0.75
|
||||
},
|
||||
},
|
||||
},
|
||||
"revisions": [{
|
||||
"user_id": "testassist@edx.org",
|
||||
"update": {
|
||||
"head12345": {
|
||||
"end": _date_field.from_json("2013-04-13T04:30"),
|
||||
"tabs": [
|
||||
{
|
||||
"type": "courseware"
|
||||
},
|
||||
{
|
||||
"type": "course_info",
|
||||
"name": "Course Info"
|
||||
},
|
||||
{
|
||||
"type": "discussion",
|
||||
"name": "Discussion"
|
||||
},
|
||||
{
|
||||
"type": "wiki",
|
||||
"name": "Wiki"
|
||||
},
|
||||
{
|
||||
"type": "static_tab",
|
||||
"name": "Syllabus",
|
||||
"url_slug": "01356a17b5924b17a04b7fc2426a3798"
|
||||
},
|
||||
{
|
||||
"type": "static_tab",
|
||||
"name": "Advice for Students",
|
||||
"url_slug": "57e9991c0d794ff58f7defae3e042e39"
|
||||
}
|
||||
],
|
||||
"graceperiod": _time_delta_field.from_json("2 hours 0 minutes 0 seconds"),
|
||||
"grading_policy": {
|
||||
"GRADER": [
|
||||
{
|
||||
"min_count": 5,
|
||||
"weight": 0.15,
|
||||
"type": "Homework",
|
||||
"drop_count": 1,
|
||||
"short_label": "HWa"
|
||||
},
|
||||
{
|
||||
"short_label": "",
|
||||
"min_count": 12,
|
||||
"type": "Lab",
|
||||
"drop_count": 2,
|
||||
"weight": 0.15
|
||||
},
|
||||
{
|
||||
"short_label": "Midterm",
|
||||
"min_count": 1,
|
||||
"type": "Midterm Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.3
|
||||
},
|
||||
{
|
||||
"short_label": "Final",
|
||||
"min_count": 1,
|
||||
"type": "Final Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.4
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS": {
|
||||
"Pass": 0.55
|
||||
}
|
||||
},
|
||||
}}
|
||||
},
|
||||
{"user_id": "testassist@edx.org",
|
||||
"update":
|
||||
{"head12345": {
|
||||
"end": _date_field.from_json("2013-06-13T04:30"),
|
||||
"grading_policy": {
|
||||
"GRADER": [
|
||||
{
|
||||
"min_count": 4,
|
||||
"weight": 0.15,
|
||||
"type": "Homework",
|
||||
"drop_count": 2,
|
||||
"short_label": "HWa"
|
||||
},
|
||||
{
|
||||
"short_label": "",
|
||||
"min_count": 12,
|
||||
"type": "Lab",
|
||||
"drop_count": 2,
|
||||
"weight": 0.15
|
||||
},
|
||||
{
|
||||
"short_label": "Midterm",
|
||||
"min_count": 1,
|
||||
"type": "Midterm Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.3
|
||||
},
|
||||
{
|
||||
"short_label": "Final",
|
||||
"min_count": 1,
|
||||
"type": "Final Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.4
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS": {
|
||||
"Pass": 0.45
|
||||
}
|
||||
},
|
||||
"enrollment_start": _date_field.from_json("2013-01-01T05:00"),
|
||||
"enrollment_end": _date_field.from_json("2013-03-02T05:00"),
|
||||
"advertised_start": "Fall 2013",
|
||||
}},
|
||||
"create": [
|
||||
{
|
||||
"id": "chapter1",
|
||||
"parent": "head12345",
|
||||
"category": "chapter",
|
||||
"fields": {
|
||||
"display_name": "Hercules"
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "chapter2",
|
||||
"parent": "head12345",
|
||||
"category": "chapter",
|
||||
"fields": {
|
||||
"display_name": "Hera heckles Hercules"
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "chapter3",
|
||||
"parent": "head12345",
|
||||
"category": "chapter",
|
||||
"fields": {
|
||||
"display_name": "Hera cuckolds Zeus"
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "problem1",
|
||||
"parent": "chapter3",
|
||||
"category": "problem",
|
||||
"fields": {
|
||||
"display_name": "Problem 3.1",
|
||||
"graceperiod": "4 hours 0 minutes 0 seconds"
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "problem3_2",
|
||||
"parent": "chapter3",
|
||||
"category": "problem",
|
||||
"fields": {
|
||||
"display_name": "Problem 3.2"
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
"testx.wonderful": {
|
||||
"org": "testx",
|
||||
"root_block_id": "head23456",
|
||||
"user_id": "test@edx.org",
|
||||
"fields": {
|
||||
"tabs": [
|
||||
{
|
||||
"type": "courseware"
|
||||
},
|
||||
{
|
||||
"type": "course_info",
|
||||
"name": "Course Info"
|
||||
},
|
||||
{
|
||||
"type": "discussion",
|
||||
"name": "Discussion"
|
||||
},
|
||||
{
|
||||
"type": "wiki",
|
||||
"name": "Wiki"
|
||||
}
|
||||
],
|
||||
"start": _date_field.from_json("2013-02-14T05:00"),
|
||||
"display_name": "A wonderful course",
|
||||
"grading_policy": {
|
||||
"GRADER": [
|
||||
{
|
||||
"min_count": 14,
|
||||
"weight": 0.25,
|
||||
"type": "Homework",
|
||||
"drop_count": 1,
|
||||
"short_label": "HWa"
|
||||
},
|
||||
{
|
||||
"short_label": "",
|
||||
"min_count": 12,
|
||||
"type": "Lab",
|
||||
"drop_count": 2,
|
||||
"weight": 0.25
|
||||
},
|
||||
{
|
||||
"short_label": "Midterm",
|
||||
"min_count": 1,
|
||||
"type": "Midterm Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.2
|
||||
},
|
||||
{
|
||||
"short_label": "Final",
|
||||
"min_count": 1,
|
||||
"type": "Final Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.3
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS": {
|
||||
"Pass": 0.95
|
||||
}
|
||||
},
|
||||
},
|
||||
"revisions": [{
|
||||
"user_id": "test@edx.org",
|
||||
"update": {
|
||||
"head23456": {
|
||||
"display_name": "The most wonderful course",
|
||||
"grading_policy": {
|
||||
"GRADER": [
|
||||
{
|
||||
"min_count": 14,
|
||||
"weight": 0.25,
|
||||
"type": "Homework",
|
||||
"drop_count": 1,
|
||||
"short_label": "HWa"
|
||||
},
|
||||
{
|
||||
"short_label": "",
|
||||
"min_count": 12,
|
||||
"type": "Lab",
|
||||
"drop_count": 2,
|
||||
"weight": 0.25
|
||||
},
|
||||
{
|
||||
"short_label": "Midterm",
|
||||
"min_count": 1,
|
||||
"type": "Midterm Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.2
|
||||
},
|
||||
{
|
||||
"short_label": "Final",
|
||||
"min_count": 1,
|
||||
"type": "Final Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.3
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS": {
|
||||
"Pass": 0.45
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"guestx.contender": {
|
||||
"org": "guestx",
|
||||
"root_block_id": "head345679",
|
||||
"user_id": "test@guestx.edu",
|
||||
"fields": {
|
||||
"tabs": [
|
||||
{
|
||||
"type": "courseware"
|
||||
},
|
||||
{
|
||||
"type": "course_info",
|
||||
"name": "Course Info"
|
||||
},
|
||||
{
|
||||
"type": "discussion",
|
||||
"name": "Discussion"
|
||||
},
|
||||
{
|
||||
"type": "wiki",
|
||||
"name": "Wiki"
|
||||
}
|
||||
],
|
||||
"start": _date_field.from_json("2013-03-14T05:00"),
|
||||
"display_name": "Yet another contender",
|
||||
"grading_policy": {
|
||||
"GRADER": [
|
||||
{
|
||||
"min_count": 4,
|
||||
"weight": 0.25,
|
||||
"type": "Homework",
|
||||
"drop_count": 0,
|
||||
"short_label": "HW"
|
||||
},
|
||||
{
|
||||
"short_label": "Midterm",
|
||||
"min_count": 1,
|
||||
"type": "Midterm Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.4
|
||||
},
|
||||
{
|
||||
"short_label": "Final",
|
||||
"min_count": 1,
|
||||
"type": "Final Exam",
|
||||
"drop_count": 0,
|
||||
"weight": 0.35
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS": {
|
||||
"Pass": 0.25
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
@staticmethod
|
||||
def bootstrapDB():
|
||||
'''
|
||||
Loads the initial data into the db ensuring the collection name is
|
||||
unique.
|
||||
Sets up the initial data into the db
|
||||
'''
|
||||
collection_prefix = SplitModuleTest.MODULESTORE['DOC_STORE_CONFIG']['collection'] + '.'
|
||||
dbname = SplitModuleTest.MODULESTORE['DOC_STORE_CONFIG']['db']
|
||||
processes = [
|
||||
subprocess.Popen([
|
||||
'mongoimport', '-d', dbname, '-c',
|
||||
collection_prefix + collection, '--jsonArray',
|
||||
'--file',
|
||||
SplitModuleTest.COMMON_ROOT + '/test/data/splitmongo_json/' + collection + '.json'
|
||||
],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
split_store = modulestore()
|
||||
for course_id, course_spec in SplitModuleTest.COURSE_CONTENT.iteritems():
|
||||
course = split_store.create_course(
|
||||
course_id, course_spec['org'], course_spec['user_id'],
|
||||
fields=course_spec['fields'],
|
||||
root_block_id=course_spec['root_block_id']
|
||||
)
|
||||
for collection in ('active_versions', 'structures', 'definitions')]
|
||||
for p in processes:
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
print "Couldn't run mongoimport:"
|
||||
print stdout
|
||||
print stderr
|
||||
raise Exception("DB did not init correctly")
|
||||
for revision in course_spec.get('revisions', []):
|
||||
for block_id, fields in revision.get('update', {}).iteritems():
|
||||
# cheat since course is most frequent
|
||||
if course.location.block_id == block_id:
|
||||
block = course
|
||||
else:
|
||||
block_usage = BlockUsageLocator.make_relative(course.location, block_id)
|
||||
block = split_store.get_instance(course.location.package_id, block_usage)
|
||||
for key, value in fields.iteritems():
|
||||
setattr(block, key, value)
|
||||
# create new blocks into dag: parent must already exist; thus, order is important
|
||||
new_ele_dict = {}
|
||||
for spec in revision.get('create', []):
|
||||
if spec['parent'] in new_ele_dict:
|
||||
parent = new_ele_dict.get(spec['parent'])
|
||||
elif spec['parent'] == course.location.block_id:
|
||||
parent = course
|
||||
else:
|
||||
block_usage = BlockUsageLocator.make_relative(course.location, spec['parent'])
|
||||
parent = split_store.get_instance(course.location.package_id, block_usage)
|
||||
block_id = LocalId(spec['id'])
|
||||
child = split_store.create_xblock(
|
||||
course.runtime, spec['category'], spec['fields'], block_id, parent_xblock=parent
|
||||
)
|
||||
new_ele_dict[spec['id']] = child
|
||||
course = split_store.persist_xblock_dag(course, revision['user_id'])
|
||||
# publish "testx.wonderful"
|
||||
to_publish = BlockUsageLocator(package_id="testx.wonderful", branch="draft", block_id="head23456")
|
||||
destination = CourseLocator(package_id="testx.wonderful", branch="published")
|
||||
split_store.xblock_publish("test@edx.org", to_publish, destination, [to_publish.block_id], None)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
def tearDown(self):
|
||||
"""
|
||||
Clear persistence between each test.
|
||||
"""
|
||||
collection_prefix = SplitModuleTest.MODULESTORE['DOC_STORE_CONFIG']['collection'] + '.'
|
||||
if SplitModuleTest.modulestore:
|
||||
for collection in ('active_versions', 'structures', 'definitions'):
|
||||
modulestore().db.drop_collection(collection_prefix + collection)
|
||||
# drop the modulestore to force re init
|
||||
SplitModuleTest.modulestore = None
|
||||
super(SplitModuleTest, self).tearDown()
|
||||
|
||||
def findByIdInResult(self, collection, _id):
|
||||
"""
|
||||
@@ -120,11 +509,7 @@ class SplitModuleCourseTests(SplitModuleTest):
|
||||
self.assertEqual(len(courses), 3, "Wrong number of courses")
|
||||
# check metadata -- NOTE no promised order
|
||||
course = self.findByIdInResult(courses, "head12345")
|
||||
self.assertEqual(course.location.package_id, "GreekHero")
|
||||
self.assertEqual(
|
||||
str(course.location.version_guid), self.GUID_D0,
|
||||
"course version mismatch"
|
||||
)
|
||||
self.assertEqual(course.location.package_id, "testx.GreekHero")
|
||||
self.assertEqual(course.category, 'course', 'wrong category')
|
||||
self.assertEqual(len(course.tabs), 6, "wrong number of tabs")
|
||||
self.assertEqual(
|
||||
@@ -135,13 +520,9 @@ class SplitModuleCourseTests(SplitModuleTest):
|
||||
course.advertised_start, "Fall 2013",
|
||||
"advertised_start"
|
||||
)
|
||||
self.assertEqual(
|
||||
len(course.children), 3,
|
||||
"children")
|
||||
self.assertEqual(str(course.definition_locator.definition_id), "ad00000000000000dddd0000")
|
||||
self.assertEqual(len(course.children), 3, "children")
|
||||
# check dates and graders--forces loading of descriptor
|
||||
self.assertEqual(course.edited_by, "testassist@edx.org")
|
||||
self.assertEqual(str(course.previous_version), self.GUID_D1)
|
||||
self.assertDictEqual(course.grade_cutoffs, {"Pass": 0.45})
|
||||
|
||||
def test_branch_requests(self):
|
||||
@@ -151,9 +532,7 @@ class SplitModuleCourseTests(SplitModuleTest):
|
||||
self.assertEqual(len(courses_published), 1, len(courses_published))
|
||||
course = self.findByIdInResult(courses_published, "head23456")
|
||||
self.assertIsNotNone(course, "published courses")
|
||||
self.assertEqual(course.location.package_id, "wonderful")
|
||||
self.assertEqual(str(course.location.version_guid), self.GUID_P,
|
||||
course.location.version_guid)
|
||||
self.assertEqual(course.location.package_id, "testx.wonderful")
|
||||
self.assertEqual(course.category, 'course', 'wrong category')
|
||||
self.assertEqual(len(course.tabs), 4, "wrong number of tabs")
|
||||
self.assertEqual(course.display_name, "The most wonderful course",
|
||||
@@ -171,40 +550,31 @@ class SplitModuleCourseTests(SplitModuleTest):
|
||||
self.assertIsNotNone(self.findByIdInResult(courses, "head12345"))
|
||||
self.assertIsNotNone(self.findByIdInResult(courses, "head23456"))
|
||||
|
||||
courses = modulestore().get_courses(
|
||||
branch='draft',
|
||||
qualifiers={'edited_on': {"$lt": datetime.datetime(2013, 3, 28, 15)}})
|
||||
self.assertEqual(len(courses), 2)
|
||||
|
||||
courses = modulestore().get_courses(
|
||||
branch='draft',
|
||||
qualifiers={'org': 'testx', "prettyid": "test_course"})
|
||||
self.assertEqual(len(courses), 1)
|
||||
self.assertIsNotNone(self.findByIdInResult(courses, "head12345"))
|
||||
|
||||
def test_get_course(self):
|
||||
'''
|
||||
Test the various calling forms for get_course
|
||||
'''
|
||||
locator = CourseLocator(version_guid=self.GUID_D1)
|
||||
locator = CourseLocator(package_id="testx.GreekHero", branch="draft")
|
||||
head_course = modulestore().get_course(locator)
|
||||
self.assertNotEqual(head_course.location.version_guid, head_course.previous_version)
|
||||
locator = CourseLocator(version_guid=head_course.previous_version)
|
||||
course = modulestore().get_course(locator)
|
||||
self.assertIsNone(course.location.package_id)
|
||||
self.assertEqual(str(course.location.version_guid), self.GUID_D1)
|
||||
self.assertEqual(course.location.version_guid, head_course.previous_version)
|
||||
self.assertEqual(course.category, 'course')
|
||||
self.assertEqual(len(course.tabs), 6)
|
||||
self.assertEqual(course.display_name, "The Ancient Greek Hero")
|
||||
self.assertEqual(course.graceperiod, datetime.timedelta(hours=2))
|
||||
self.assertIsNone(course.advertised_start)
|
||||
self.assertEqual(len(course.children), 0)
|
||||
self.assertEqual(str(course.definition_locator.definition_id), "ad00000000000000dddd0001")
|
||||
self.assertNotEqual(course.definition_locator.definition_id, head_course.definition_locator.definition_id)
|
||||
# check dates and graders--forces loading of descriptor
|
||||
self.assertEqual(course.edited_by, "testassist@edx.org")
|
||||
self.assertDictEqual(course.grade_cutoffs, {"Pass": 0.55})
|
||||
|
||||
locator = CourseLocator(package_id='GreekHero', branch='draft')
|
||||
locator = CourseLocator(package_id='testx.GreekHero', branch='draft')
|
||||
course = modulestore().get_course(locator)
|
||||
self.assertEqual(course.location.package_id, "GreekHero")
|
||||
self.assertEqual(str(course.location.version_guid), self.GUID_D0)
|
||||
self.assertEqual(course.location.package_id, "testx.GreekHero")
|
||||
self.assertEqual(course.category, 'course')
|
||||
self.assertEqual(len(course.tabs), 6)
|
||||
self.assertEqual(course.display_name, "The Ancient Greek Hero")
|
||||
@@ -214,14 +584,13 @@ class SplitModuleCourseTests(SplitModuleTest):
|
||||
self.assertEqual(course.edited_by, "testassist@edx.org")
|
||||
self.assertDictEqual(course.grade_cutoffs, {"Pass": 0.45})
|
||||
|
||||
locator = CourseLocator(package_id='wonderful', branch='published')
|
||||
locator = CourseLocator(package_id='testx.wonderful', branch='published')
|
||||
course = modulestore().get_course(locator)
|
||||
self.assertEqual(course.location.package_id, "wonderful")
|
||||
self.assertEqual(str(course.location.version_guid), self.GUID_P)
|
||||
published_version = course.location.version_guid
|
||||
|
||||
locator = CourseLocator(package_id='wonderful', branch='draft')
|
||||
locator = CourseLocator(package_id='testx.wonderful', branch='draft')
|
||||
course = modulestore().get_course(locator)
|
||||
self.assertEqual(str(course.location.version_guid), self.GUID_D2)
|
||||
self.assertNotEqual(course.location.version_guid, published_version)
|
||||
|
||||
def test_get_course_negative(self):
|
||||
# Now negative testing
|
||||
@@ -231,13 +600,13 @@ class SplitModuleCourseTests(SplitModuleTest):
|
||||
modulestore().get_course, CourseLocator(package_id='nosuchthing', branch='draft'))
|
||||
self.assertRaises(ItemNotFoundError,
|
||||
modulestore().get_course,
|
||||
CourseLocator(package_id='GreekHero', branch='published'))
|
||||
CourseLocator(package_id='testx.GreekHero', branch='published'))
|
||||
|
||||
def test_cache(self):
|
||||
"""
|
||||
Test that the mechanics of caching work.
|
||||
"""
|
||||
locator = CourseLocator(version_guid=self.GUID_D0)
|
||||
locator = CourseLocator(package_id='testx.GreekHero', branch='draft')
|
||||
course = modulestore().get_course(locator)
|
||||
block_map = modulestore().cache_items(course.system, course.children, depth=3)
|
||||
self.assertIn('chapter1', block_map)
|
||||
@@ -247,22 +616,32 @@ class SplitModuleCourseTests(SplitModuleTest):
|
||||
"""
|
||||
get_course_successors(course_locator, version_history_depth=1)
|
||||
"""
|
||||
locator = CourseLocator(version_guid=self.GUID_D3)
|
||||
locator = CourseLocator(package_id='testx.GreekHero', branch='draft')
|
||||
course = modulestore().get_course(locator)
|
||||
versions = [course.location.version_guid, course.previous_version]
|
||||
locator = CourseLocator(version_guid=course.previous_version)
|
||||
course = modulestore().get_course(locator)
|
||||
versions.append(course.previous_version)
|
||||
|
||||
locator = CourseLocator(version_guid=course.previous_version)
|
||||
result = modulestore().get_course_successors(locator)
|
||||
self.assertIsInstance(result, VersionTree)
|
||||
self.assertIsNone(result.locator.package_id)
|
||||
self.assertEqual(str(result.locator.version_guid), self.GUID_D3)
|
||||
self.assertEqual(result.locator.version_guid, versions[-1])
|
||||
self.assertEqual(len(result.children), 1)
|
||||
self.assertEqual(str(result.children[0].locator.version_guid), self.GUID_D1)
|
||||
self.assertEqual(result.children[0].locator.version_guid, versions[-2])
|
||||
self.assertEqual(len(result.children[0].children), 0, "descended more than one level")
|
||||
|
||||
result = modulestore().get_course_successors(locator, version_history_depth=2)
|
||||
self.assertEqual(len(result.children), 1)
|
||||
self.assertEqual(str(result.children[0].locator.version_guid), self.GUID_D1)
|
||||
self.assertEqual(result.children[0].locator.version_guid, versions[-2])
|
||||
self.assertEqual(len(result.children[0].children), 1)
|
||||
|
||||
result = modulestore().get_course_successors(locator, version_history_depth=99)
|
||||
self.assertEqual(len(result.children), 1)
|
||||
self.assertEqual(str(result.children[0].locator.version_guid), self.GUID_D1)
|
||||
self.assertEqual(result.children[0].locator.version_guid, versions[-2])
|
||||
self.assertEqual(len(result.children[0].children), 1)
|
||||
self.assertEqual(result.children[0].children[0].locator.version_guid, versions[0])
|
||||
|
||||
class SplitModuleItemTests(SplitModuleTest):
|
||||
'''
|
||||
@@ -273,97 +652,98 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
'''
|
||||
has_item(BlockUsageLocator)
|
||||
'''
|
||||
package_id = 'GreekHero'
|
||||
package_id = 'testx.GreekHero'
|
||||
locator = CourseLocator(package_id=package_id, branch='draft')
|
||||
course = modulestore().get_course(locator)
|
||||
previous_version = course.previous_version
|
||||
# positive tests of various forms
|
||||
locator = BlockUsageLocator(version_guid=self.GUID_D1, block_id='head12345')
|
||||
self.assertTrue(modulestore().has_item(package_id, locator),
|
||||
"couldn't find in %s" % self.GUID_D1)
|
||||
locator = BlockUsageLocator(version_guid=previous_version, block_id='head12345')
|
||||
self.assertTrue(
|
||||
modulestore().has_item(package_id, locator), "couldn't find in %s" % previous_version
|
||||
)
|
||||
|
||||
locator = BlockUsageLocator(package_id='GreekHero', block_id='head12345', branch='draft')
|
||||
locator = BlockUsageLocator(package_id='testx.GreekHero', block_id='head12345', branch='draft')
|
||||
self.assertTrue(
|
||||
modulestore().has_item(locator.package_id, locator),
|
||||
"couldn't find in 12345"
|
||||
)
|
||||
self.assertTrue(
|
||||
modulestore().has_item(locator.package_id, BlockUsageLocator(
|
||||
package_id=locator.package_id,
|
||||
branch='draft',
|
||||
block_id=locator.block_id
|
||||
)),
|
||||
"couldn't find in draft 12345"
|
||||
)
|
||||
self.assertFalse(
|
||||
modulestore().has_item(locator.package_id, BlockUsageLocator(
|
||||
package_id=locator.package_id,
|
||||
branch='published',
|
||||
block_id=locator.block_id)),
|
||||
"found in published 12345"
|
||||
)
|
||||
locator.branch = 'draft'
|
||||
self.assertTrue(
|
||||
modulestore().has_item(locator.package_id, locator),
|
||||
"not found in draft 12345"
|
||||
"found in published head"
|
||||
)
|
||||
|
||||
# not a course obj
|
||||
locator = BlockUsageLocator(package_id='GreekHero', block_id='chapter1', branch='draft')
|
||||
locator = BlockUsageLocator(package_id='testx.GreekHero', block_id='chapter1', branch='draft')
|
||||
self.assertTrue(
|
||||
modulestore().has_item(locator.package_id, locator),
|
||||
"couldn't find chapter1"
|
||||
)
|
||||
|
||||
# in published course
|
||||
locator = BlockUsageLocator(package_id="wonderful", block_id="head23456", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.wonderful", block_id="head23456", branch='draft')
|
||||
self.assertTrue(
|
||||
modulestore().has_item(
|
||||
locator.package_id,
|
||||
BlockUsageLocator(package_id=locator.package_id, block_id=locator.block_id, branch='published')
|
||||
), "couldn't find in 23456"
|
||||
)
|
||||
)
|
||||
locator.branch = 'published'
|
||||
self.assertTrue(modulestore().has_item(package_id, locator), "couldn't find in 23456")
|
||||
self.assertTrue(modulestore().has_item(package_id, locator), "couldn't find in published")
|
||||
|
||||
def test_negative_has_item(self):
|
||||
# negative tests--not found
|
||||
# no such course or block
|
||||
package_id = 'GreekHero'
|
||||
package_id = 'testx.GreekHero'
|
||||
locator = BlockUsageLocator(package_id="doesnotexist", block_id="head23456", branch='draft')
|
||||
self.assertFalse(modulestore().has_item(package_id, locator))
|
||||
locator = BlockUsageLocator(package_id="wonderful", block_id="doesnotexist", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.wonderful", block_id="doesnotexist", branch='draft')
|
||||
self.assertFalse(modulestore().has_item(package_id, locator))
|
||||
|
||||
# negative tests--insufficient specification
|
||||
self.assertRaises(InsufficientSpecificationError, BlockUsageLocator)
|
||||
self.assertRaises(InsufficientSpecificationError,
|
||||
modulestore().has_item, None, BlockUsageLocator(version_guid=self.GUID_D1))
|
||||
self.assertRaises(InsufficientSpecificationError,
|
||||
modulestore().has_item, None, BlockUsageLocator(package_id='GreekHero'))
|
||||
|
||||
locator = CourseLocator(package_id=package_id, branch='draft')
|
||||
course = modulestore().get_course(locator)
|
||||
previous_version = course.previous_version
|
||||
|
||||
with self.assertRaises(InsufficientSpecificationError):
|
||||
modulestore().has_item(None, BlockUsageLocator(version_guid=previous_version))
|
||||
with self.assertRaises(InsufficientSpecificationError):
|
||||
modulestore().has_item(None, BlockUsageLocator(package_id='testx.GreekHero'))
|
||||
|
||||
def test_get_item(self):
|
||||
'''
|
||||
get_item(blocklocator)
|
||||
'''
|
||||
locator = CourseLocator(package_id="testx.GreekHero", branch='draft')
|
||||
course = modulestore().get_course(locator)
|
||||
previous_version = course.previous_version
|
||||
|
||||
# positive tests of various forms
|
||||
locator = BlockUsageLocator(version_guid=self.GUID_D1, block_id='head12345')
|
||||
locator = BlockUsageLocator(version_guid=previous_version, block_id='head12345')
|
||||
block = modulestore().get_item(locator)
|
||||
self.assertIsInstance(block, CourseDescriptor)
|
||||
# get_instance just redirects to get_item, ignores package_id
|
||||
self.assertIsInstance(modulestore().get_instance("package_id", locator), CourseDescriptor)
|
||||
|
||||
def verify_greek_hero(block):
|
||||
self.assertEqual(block.location.package_id, "GreekHero")
|
||||
"""
|
||||
Check contents of block
|
||||
"""
|
||||
self.assertEqual(block.location.package_id, "testx.GreekHero")
|
||||
self.assertEqual(len(block.tabs), 6, "wrong number of tabs")
|
||||
self.assertEqual(block.display_name, "The Ancient Greek Hero")
|
||||
self.assertEqual(block.advertised_start, "Fall 2013")
|
||||
self.assertEqual(len(block.children), 3)
|
||||
self.assertEqual(str(block.definition_locator.definition_id), "ad00000000000000dddd0000")
|
||||
# check dates and graders--forces loading of descriptor
|
||||
self.assertEqual(block.edited_by, "testassist@edx.org")
|
||||
self.assertDictEqual(
|
||||
block.grade_cutoffs, {"Pass": 0.45},
|
||||
)
|
||||
|
||||
locator = BlockUsageLocator(package_id='GreekHero', block_id='head12345', branch='draft')
|
||||
locator = BlockUsageLocator(package_id='testx.GreekHero', block_id='head12345', branch='draft')
|
||||
verify_greek_hero(modulestore().get_item(locator))
|
||||
# get_instance just redirects to get_item, ignores package_id
|
||||
verify_greek_hero(modulestore().get_instance("package_id", locator))
|
||||
@@ -382,16 +762,15 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
|
||||
def test_get_non_root(self):
|
||||
# not a course obj
|
||||
locator = BlockUsageLocator(package_id='GreekHero', block_id='chapter1', branch='draft')
|
||||
locator = BlockUsageLocator(package_id='testx.GreekHero', block_id='chapter1', branch='draft')
|
||||
block = modulestore().get_item(locator)
|
||||
self.assertEqual(block.location.package_id, "GreekHero")
|
||||
self.assertEqual(block.location.package_id, "testx.GreekHero")
|
||||
self.assertEqual(block.category, 'chapter')
|
||||
self.assertEqual(str(block.definition_locator.definition_id), "cd00000000000000dddd0020")
|
||||
self.assertEqual(block.display_name, "Hercules")
|
||||
self.assertEqual(block.edited_by, "testassist@edx.org")
|
||||
|
||||
# in published course
|
||||
locator = BlockUsageLocator(package_id="wonderful", block_id="head23456", branch='published')
|
||||
locator = BlockUsageLocator(package_id="testx.wonderful", block_id="head23456", branch='published')
|
||||
self.assertIsInstance(
|
||||
modulestore().get_item(locator),
|
||||
CourseDescriptor
|
||||
@@ -402,15 +781,15 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
locator = BlockUsageLocator(package_id="doesnotexist", block_id="head23456", branch='draft')
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().get_item(locator)
|
||||
locator = BlockUsageLocator(package_id="wonderful", block_id="doesnotexist", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.wonderful", block_id="doesnotexist", branch='draft')
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().get_item(locator)
|
||||
|
||||
# negative tests--insufficient specification
|
||||
with self.assertRaises(InsufficientSpecificationError):
|
||||
modulestore().get_item(BlockUsageLocator(version_guid=self.GUID_D1))
|
||||
modulestore().get_item(BlockUsageLocator(version_guid=ObjectId()))
|
||||
with self.assertRaises(InsufficientSpecificationError):
|
||||
modulestore().get_item(BlockUsageLocator(package_id='GreekHero', branch='draft'))
|
||||
modulestore().get_item(BlockUsageLocator(package_id='testx.GreekHero', branch='draft'))
|
||||
|
||||
# pylint: disable=W0212
|
||||
def test_matching(self):
|
||||
@@ -443,7 +822,7 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
'''
|
||||
get_items(locator, qualifiers, [branch])
|
||||
'''
|
||||
locator = CourseLocator(version_guid=self.GUID_D0)
|
||||
locator = CourseLocator(package_id="testx.GreekHero", branch='draft')
|
||||
# get all modules
|
||||
matches = modulestore().get_items(locator)
|
||||
self.assertEqual(len(matches), 6)
|
||||
@@ -471,11 +850,11 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
'''
|
||||
get_parent_locations(locator, [block_id], [branch]): [BlockUsageLocator]
|
||||
'''
|
||||
locator = BlockUsageLocator(package_id="GreekHero", branch='draft', block_id='chapter1')
|
||||
locator = BlockUsageLocator(package_id="testx.GreekHero", branch='draft', block_id='chapter1')
|
||||
parents = modulestore().get_parent_locations(locator)
|
||||
self.assertEqual(len(parents), 1)
|
||||
self.assertEqual(parents[0].block_id, 'head12345')
|
||||
self.assertEqual(parents[0].package_id, "GreekHero")
|
||||
self.assertEqual(parents[0].package_id, "testx.GreekHero")
|
||||
locator.block_id = 'chapter2'
|
||||
parents = modulestore().get_parent_locations(locator)
|
||||
self.assertEqual(len(parents), 1)
|
||||
@@ -488,7 +867,7 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
"""
|
||||
Test the existing get_children method on xdescriptors
|
||||
"""
|
||||
locator = BlockUsageLocator(package_id="GreekHero", block_id="head12345", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.GreekHero", block_id="head12345", branch='draft')
|
||||
block = modulestore().get_item(locator)
|
||||
children = block.get_children()
|
||||
expected_ids = [
|
||||
@@ -530,9 +909,9 @@ class TestItemCrud(SplitModuleTest):
|
||||
create_item(course_or_parent_locator, category, user, definition_locator=None, fields): new_desciptor
|
||||
"""
|
||||
# grab link to course to ensure new versioning works
|
||||
locator = CourseLocator(package_id="GreekHero", branch='draft')
|
||||
locator = CourseLocator(package_id="testx.GreekHero", branch='draft')
|
||||
premod_course = modulestore().get_course(locator)
|
||||
premod_time = datetime.datetime.now(UTC) - datetime.timedelta(seconds=1)
|
||||
premod_history = modulestore().get_course_history_info(premod_course.location)
|
||||
# add minimal one w/o a parent
|
||||
category = 'sequential'
|
||||
new_module = modulestore().create_item(
|
||||
@@ -540,7 +919,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
fields={'display_name': 'new sequential'}
|
||||
)
|
||||
# check that course version changed and course's previous is the other one
|
||||
self.assertEqual(new_module.location.package_id, "GreekHero")
|
||||
self.assertEqual(new_module.location.package_id, "testx.GreekHero")
|
||||
self.assertNotEqual(new_module.location.version_guid, premod_course.location.version_guid)
|
||||
self.assertIsNone(locator.version_guid, "Version inadvertently filled in")
|
||||
current_course = modulestore().get_course(locator)
|
||||
@@ -548,10 +927,8 @@ class TestItemCrud(SplitModuleTest):
|
||||
|
||||
history_info = modulestore().get_course_history_info(current_course.location)
|
||||
self.assertEqual(history_info['previous_version'], premod_course.location.version_guid)
|
||||
self.assertEqual(str(history_info['original_version']), self.GUID_D3)
|
||||
self.assertEqual(history_info['original_version'], premod_history['original_version'])
|
||||
self.assertEqual(history_info['edited_by'], "user123")
|
||||
self.assertGreaterEqual(history_info['edited_on'], premod_time)
|
||||
self.assertLessEqual(history_info['edited_on'], datetime.datetime.now(UTC))
|
||||
# check block's info: category, definition_locator, and display_name
|
||||
self.assertEqual(new_module.category, 'sequential')
|
||||
self.assertIsNotNone(new_module.definition_locator)
|
||||
@@ -567,19 +944,22 @@ class TestItemCrud(SplitModuleTest):
|
||||
"""
|
||||
Test create_item w/ specifying the parent of the new item
|
||||
"""
|
||||
locator = BlockUsageLocator(package_id="wonderful", block_id="head23456", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.GreekHero", branch='draft', block_id='chapter2')
|
||||
original = modulestore().get_item(locator)
|
||||
|
||||
locator = BlockUsageLocator(package_id="testx.wonderful", block_id="head23456", branch='draft')
|
||||
premod_course = modulestore().get_course(locator)
|
||||
category = 'chapter'
|
||||
new_module = modulestore().create_item(
|
||||
locator, category, 'user123',
|
||||
fields={'display_name': 'new chapter'},
|
||||
definition_locator=DefinitionLocator("cd00000000000000dddd0022")
|
||||
definition_locator=original.definition_locator
|
||||
)
|
||||
# check that course version changed and course's previous is the other one
|
||||
self.assertNotEqual(new_module.location.version_guid, premod_course.location.version_guid)
|
||||
parent = modulestore().get_item(locator)
|
||||
self.assertIn(new_module.location.block_id, parent.children)
|
||||
self.assertEqual(str(new_module.definition_locator.definition_id), "cd00000000000000dddd0022")
|
||||
self.assertEqual(new_module.definition_locator.definition_id, original.definition_locator.definition_id)
|
||||
|
||||
def test_unique_naming(self):
|
||||
"""
|
||||
@@ -587,9 +967,11 @@ class TestItemCrud(SplitModuleTest):
|
||||
a definition id and new def data that it branches the definition in the db.
|
||||
Actually, this tries to test all create_item features not tested above.
|
||||
"""
|
||||
locator = BlockUsageLocator(package_id="contender", block_id="head345679", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.GreekHero", branch='draft', block_id='problem1')
|
||||
original = modulestore().get_item(locator)
|
||||
|
||||
locator = BlockUsageLocator(package_id="guestx.contender", block_id="head345679", branch='draft')
|
||||
category = 'problem'
|
||||
premod_time = datetime.datetime.now(UTC) - datetime.timedelta(seconds=1)
|
||||
new_payload = "<problem>empty</problem>"
|
||||
new_module = modulestore().create_item(
|
||||
locator, category, 'anotheruser',
|
||||
@@ -599,7 +981,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
another_module = modulestore().create_item(
|
||||
locator, category, 'anotheruser',
|
||||
fields={'display_name': 'problem 2', 'data': another_payload},
|
||||
definition_locator=DefinitionLocator("0d00000040000000dddd0031"),
|
||||
definition_locator=original.definition_locator,
|
||||
)
|
||||
# check that course version changed and course's previous is the other one
|
||||
parent = modulestore().get_item(locator)
|
||||
@@ -613,17 +995,15 @@ class TestItemCrud(SplitModuleTest):
|
||||
self.assertIsNone(new_history['previous_version'])
|
||||
self.assertEqual(new_history['original_version'], new_module.definition_locator.definition_id)
|
||||
self.assertEqual(new_history['edited_by'], "anotheruser")
|
||||
self.assertLessEqual(new_history['edited_on'], datetime.datetime.now(UTC))
|
||||
self.assertGreaterEqual(new_history['edited_on'], premod_time)
|
||||
another_history = modulestore().get_definition_history_info(another_module.definition_locator)
|
||||
self.assertEqual(str(another_history['previous_version']), '0d00000040000000dddd0031')
|
||||
self.assertEqual(another_history['previous_version'], original.definition_locator.definition_id)
|
||||
|
||||
def test_encoded_naming(self):
|
||||
"""
|
||||
Check that using odd characters in block id don't break ability to add and retrieve block.
|
||||
"""
|
||||
parent_locator = BlockUsageLocator(package_id="contender", block_id="head345679", branch='draft')
|
||||
chapter_locator = BlockUsageLocator(package_id="contender", block_id="foo.bar_-~:0", branch='draft')
|
||||
parent_locator = BlockUsageLocator(package_id="guestx.contender", block_id="head345679", branch='draft')
|
||||
chapter_locator = BlockUsageLocator(package_id="guestx.contender", block_id="foo.bar_-~:0", branch='draft')
|
||||
modulestore().create_item(
|
||||
parent_locator, 'chapter', 'anotheruser',
|
||||
block_id=chapter_locator.block_id,
|
||||
@@ -634,7 +1014,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
self.assertEqual(new_module.location.block_id, "foo.bar_-~:0") # hardcode to ensure BUL init didn't change
|
||||
# now try making that a parent of something
|
||||
new_payload = "<problem>empty</problem>"
|
||||
problem_locator = BlockUsageLocator(package_id="contender", block_id="prob.bar_-~:99a", branch='draft')
|
||||
problem_locator = BlockUsageLocator(package_id="guestx.contender", block_id="prob.bar_-~:99a", branch='draft')
|
||||
modulestore().create_item(
|
||||
chapter_locator, 'problem', 'anotheruser',
|
||||
block_id=problem_locator.block_id,
|
||||
@@ -652,7 +1032,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
"""
|
||||
# start transaction w/ simple creation
|
||||
user = random.getrandbits(32)
|
||||
new_course = modulestore().create_course('test_org.test_transaction', 'test_org', 'test_transaction', user)
|
||||
new_course = modulestore().create_course('test_org.test_transaction', 'test_org', user)
|
||||
new_course_locator = new_course.location.as_course_locator()
|
||||
index_history_info = modulestore().get_course_history_info(new_course.location)
|
||||
course_block_prev_version = new_course.previous_version
|
||||
@@ -735,13 +1115,12 @@ class TestItemCrud(SplitModuleTest):
|
||||
"""
|
||||
test updating an items metadata ensuring the definition doesn't version but the course does if it should
|
||||
"""
|
||||
locator = BlockUsageLocator(package_id="GreekHero", block_id="problem3_2", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.GreekHero", block_id="problem3_2", branch='draft')
|
||||
problem = modulestore().get_item(locator)
|
||||
pre_def_id = problem.definition_locator.definition_id
|
||||
pre_version_guid = problem.location.version_guid
|
||||
self.assertIsNotNone(pre_def_id)
|
||||
self.assertIsNotNone(pre_version_guid)
|
||||
premod_time = datetime.datetime.now(UTC) - datetime.timedelta(seconds=1)
|
||||
self.assertNotEqual(problem.max_attempts, 4, "Invalidates rest of test")
|
||||
|
||||
problem.max_attempts = 4
|
||||
@@ -764,16 +1143,13 @@ class TestItemCrud(SplitModuleTest):
|
||||
|
||||
history_info = modulestore().get_course_history_info(current_course.location)
|
||||
self.assertEqual(history_info['previous_version'], pre_version_guid)
|
||||
self.assertEqual(str(history_info['original_version']), self.GUID_D3)
|
||||
self.assertEqual(history_info['edited_by'], "**replace_user**")
|
||||
self.assertGreaterEqual(history_info['edited_on'], premod_time)
|
||||
self.assertLessEqual(history_info['edited_on'], datetime.datetime.now(UTC))
|
||||
|
||||
def test_update_children(self):
|
||||
"""
|
||||
test updating an item's children ensuring the definition doesn't version but the course does if it should
|
||||
"""
|
||||
locator = BlockUsageLocator(package_id="GreekHero", block_id="chapter3", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.GreekHero", block_id="chapter3", branch='draft')
|
||||
block = modulestore().get_item(locator)
|
||||
pre_def_id = block.definition_locator.definition_id
|
||||
pre_version_guid = block.location.version_guid
|
||||
@@ -799,7 +1175,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
"""
|
||||
test updating an item's definition: ensure it gets versioned as well as the course getting versioned
|
||||
"""
|
||||
locator = BlockUsageLocator(package_id="GreekHero", block_id="head12345", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.GreekHero", block_id="head12345", branch='draft')
|
||||
block = modulestore().get_item(locator)
|
||||
pre_def_id = block.definition_locator.definition_id
|
||||
pre_version_guid = block.location.version_guid
|
||||
@@ -816,8 +1192,10 @@ class TestItemCrud(SplitModuleTest):
|
||||
"""
|
||||
Test updating metadata, children, and definition in a single call ensuring all the versioning occurs
|
||||
"""
|
||||
locator = BlockUsageLocator(package_id="testx.GreekHero", branch='draft', block_id='problem1')
|
||||
original = modulestore().get_item(locator)
|
||||
# first add 2 children to the course for the update to manipulate
|
||||
locator = BlockUsageLocator(package_id="contender", block_id="head345679", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="guestx.contender", block_id="head345679", branch='draft')
|
||||
category = 'problem'
|
||||
new_payload = "<problem>empty</problem>"
|
||||
modulestore().create_item(
|
||||
@@ -828,7 +1206,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
modulestore().create_item(
|
||||
locator, category, 'test_update_manifold',
|
||||
fields={'display_name': 'problem 2', 'data': another_payload},
|
||||
definition_locator=DefinitionLocator("0d00000040000000dddd0031"),
|
||||
definition_locator=original.definition_locator,
|
||||
)
|
||||
# pylint: disable=W0212
|
||||
modulestore()._clear_cache()
|
||||
@@ -884,6 +1262,9 @@ class TestItemCrud(SplitModuleTest):
|
||||
# check subtree
|
||||
|
||||
def check_subtree(node):
|
||||
"""
|
||||
Check contents of subtree recursively
|
||||
"""
|
||||
if node:
|
||||
node_loc = node.location
|
||||
self.assertFalse(modulestore().has_item(reusable_location.package_id,
|
||||
@@ -901,7 +1282,10 @@ class TestItemCrud(SplitModuleTest):
|
||||
check_subtree(nodes[0])
|
||||
|
||||
def create_course_for_deletion(self):
|
||||
course = modulestore().create_course('nihilx.deletion', 'nihilx', 'deletion', 'deleting_user')
|
||||
"""
|
||||
Create a course we can delete
|
||||
"""
|
||||
course = modulestore().create_course('nihilx.deletion', 'nihilx', 'deleting_user')
|
||||
root = BlockUsageLocator(
|
||||
package_id=course.location.package_id,
|
||||
block_id=course.location.block_id,
|
||||
@@ -911,6 +1295,9 @@ class TestItemCrud(SplitModuleTest):
|
||||
return modulestore().get_item(root)
|
||||
|
||||
def create_subtree_for_deletion(self, parent, category_queue):
|
||||
"""
|
||||
Create a subtree in the tb deleted course
|
||||
"""
|
||||
if not category_queue:
|
||||
return
|
||||
node = modulestore().create_item(parent, category_queue[0], 'deleting_user')
|
||||
@@ -921,29 +1308,23 @@ class TestItemCrud(SplitModuleTest):
|
||||
|
||||
class TestCourseCreation(SplitModuleTest):
|
||||
"""
|
||||
Test create_course, duh :-)
|
||||
Test create_course
|
||||
"""
|
||||
def test_simple_creation(self):
|
||||
"""
|
||||
The simplest case but probing all expected results from it.
|
||||
"""
|
||||
# Oddly getting differences of 200nsec
|
||||
pre_time = datetime.datetime.now(UTC) - datetime.timedelta(milliseconds=1)
|
||||
new_course = modulestore().create_course('test_org.test_course', 'test_org', 'test_course', 'create_user')
|
||||
new_course = modulestore().create_course('test_org.test_course', 'test_org', 'create_user')
|
||||
new_locator = new_course.location
|
||||
# check index entry
|
||||
index_info = modulestore().get_course_index_info(new_locator)
|
||||
self.assertEqual(index_info['org'], 'test_org')
|
||||
self.assertEqual(index_info['prettyid'], 'test_course')
|
||||
self.assertGreaterEqual(index_info["edited_on"], pre_time)
|
||||
self.assertLessEqual(index_info["edited_on"], datetime.datetime.now(UTC))
|
||||
self.assertEqual(index_info['edited_by'], 'create_user')
|
||||
# check structure info
|
||||
structure_info = modulestore().get_course_history_info(new_locator)
|
||||
self.assertEqual(structure_info['original_version'], index_info['versions']['draft'])
|
||||
self.assertIsNone(structure_info['previous_version'])
|
||||
self.assertGreaterEqual(structure_info["edited_on"], pre_time)
|
||||
self.assertLessEqual(structure_info["edited_on"], datetime.datetime.now(UTC))
|
||||
self.assertEqual(structure_info['edited_by'], 'create_user')
|
||||
# check the returned course object
|
||||
self.assertIsInstance(new_course, CourseDescriptor)
|
||||
@@ -959,28 +1340,23 @@ class TestCourseCreation(SplitModuleTest):
|
||||
"""
|
||||
Test making a course which points to an existing draft and published but not making any changes to either.
|
||||
"""
|
||||
pre_time = datetime.datetime.now(UTC)
|
||||
original_locator = CourseLocator(package_id="wonderful", branch='draft')
|
||||
original_locator = CourseLocator(package_id="testx.wonderful", branch='draft')
|
||||
original_index = modulestore().get_course_index_info(original_locator)
|
||||
new_draft = modulestore().create_course(
|
||||
'best', 'leech', 'best_course', 'leech_master',
|
||||
'best', 'leech', 'leech_master',
|
||||
versions_dict=original_index['versions'])
|
||||
new_draft_locator = new_draft.location
|
||||
self.assertRegexpMatches(new_draft_locator.package_id, 'best')
|
||||
# the edited_by and other meta fields on the new course will be the original author not this one
|
||||
self.assertEqual(new_draft.edited_by, 'test@edx.org')
|
||||
self.assertLess(new_draft.edited_on, pre_time)
|
||||
self.assertEqual(new_draft_locator.version_guid, original_index['versions']['draft'])
|
||||
# however the edited_by and other meta fields on course_index will be this one
|
||||
new_index = modulestore().get_course_index_info(new_draft_locator)
|
||||
self.assertGreaterEqual(new_index["edited_on"], pre_time)
|
||||
self.assertLessEqual(new_index["edited_on"], datetime.datetime.now(UTC))
|
||||
self.assertEqual(new_index['edited_by'], 'leech_master')
|
||||
|
||||
new_published_locator = CourseLocator(package_id=new_draft_locator.package_id, branch='published')
|
||||
new_published = modulestore().get_course(new_published_locator)
|
||||
self.assertEqual(new_published.edited_by, 'test@edx.org')
|
||||
self.assertLess(new_published.edited_on, pre_time)
|
||||
self.assertEqual(new_published.location.version_guid, original_index['versions']['published'])
|
||||
|
||||
# changing this course will not change the original course
|
||||
@@ -994,12 +1370,9 @@ class TestCourseCreation(SplitModuleTest):
|
||||
self.assertNotEqual(new_index['versions']['draft'], original_index['versions']['draft'])
|
||||
new_draft = modulestore().get_course(new_draft_locator)
|
||||
self.assertEqual(new_item.edited_by, 'leech_master')
|
||||
self.assertGreaterEqual(new_item.edited_on, pre_time)
|
||||
self.assertNotEqual(new_item.location.version_guid, original_index['versions']['draft'])
|
||||
self.assertNotEqual(new_draft.location.version_guid, original_index['versions']['draft'])
|
||||
structure_info = modulestore().get_course_history_info(new_draft_locator)
|
||||
self.assertGreaterEqual(structure_info["edited_on"], pre_time)
|
||||
self.assertLessEqual(structure_info["edited_on"], datetime.datetime.now(UTC))
|
||||
self.assertEqual(structure_info['edited_by'], 'leech_master')
|
||||
|
||||
original_course = modulestore().get_course(original_locator)
|
||||
@@ -1015,8 +1388,7 @@ class TestCourseCreation(SplitModuleTest):
|
||||
"""
|
||||
Create a new course which overrides metadata and course_data
|
||||
"""
|
||||
pre_time = datetime.datetime.now(UTC)
|
||||
original_locator = CourseLocator(package_id="contender", branch='draft')
|
||||
original_locator = CourseLocator(package_id="guestx.contender", branch='draft')
|
||||
original = modulestore().get_course(original_locator)
|
||||
original_index = modulestore().get_course_index_info(original_locator)
|
||||
fields = {}
|
||||
@@ -1028,7 +1400,7 @@ class TestCourseCreation(SplitModuleTest):
|
||||
fields['grading_policy']['GRADE_CUTOFFS'] = {'A': .9, 'B': .8, 'C': .65}
|
||||
fields['display_name'] = 'Derivative'
|
||||
new_draft = modulestore().create_course(
|
||||
'counter', 'leech', 'derivative', 'leech_master',
|
||||
'counter', 'leech', 'leech_master',
|
||||
versions_dict={'draft': original_index['versions']['draft']},
|
||||
fields=fields
|
||||
)
|
||||
@@ -1036,12 +1408,9 @@ class TestCourseCreation(SplitModuleTest):
|
||||
self.assertRegexpMatches(new_draft_locator.package_id, 'counter')
|
||||
# the edited_by and other meta fields on the new course will be the original author not this one
|
||||
self.assertEqual(new_draft.edited_by, 'leech_master')
|
||||
self.assertGreaterEqual(new_draft.edited_on, pre_time)
|
||||
self.assertNotEqual(new_draft_locator.version_guid, original_index['versions']['draft'])
|
||||
# however the edited_by and other meta fields on course_index will be this one
|
||||
new_index = modulestore().get_course_index_info(new_draft_locator)
|
||||
self.assertGreaterEqual(new_index["edited_on"], pre_time)
|
||||
self.assertLessEqual(new_index["edited_on"], datetime.datetime.now(UTC))
|
||||
self.assertEqual(new_index['edited_by'], 'leech_master')
|
||||
self.assertEqual(new_draft.display_name, fields['display_name'])
|
||||
self.assertDictEqual(
|
||||
@@ -1053,7 +1422,7 @@ class TestCourseCreation(SplitModuleTest):
|
||||
"""
|
||||
Test changing the org, pretty id, etc of a course. Test that it doesn't allow changing the id, etc.
|
||||
"""
|
||||
locator = CourseLocator(package_id="GreekHero", branch='draft')
|
||||
locator = CourseLocator(package_id="testx.GreekHero", branch='draft')
|
||||
course_info = modulestore().get_course_index_info(locator)
|
||||
course_info['org'] = 'funkyU'
|
||||
modulestore().update_course_index(course_info)
|
||||
@@ -1061,24 +1430,23 @@ class TestCourseCreation(SplitModuleTest):
|
||||
self.assertEqual(course_info['org'], 'funkyU')
|
||||
|
||||
course_info['org'] = 'moreFunky'
|
||||
course_info['prettyid'] = 'Ancient Greek Demagods'
|
||||
modulestore().update_course_index(course_info)
|
||||
course_info = modulestore().get_course_index_info(locator)
|
||||
self.assertEqual(course_info['org'], 'moreFunky')
|
||||
self.assertEqual(course_info['prettyid'], 'Ancient Greek Demagods')
|
||||
|
||||
# an allowed but not necessarily recommended way to revert the draft version
|
||||
head_course = modulestore().get_course(locator)
|
||||
versions = course_info['versions']
|
||||
versions['draft'] = self.GUID_D1
|
||||
versions['draft'] = head_course.previous_version
|
||||
modulestore().update_course_index(course_info)
|
||||
course = modulestore().get_course(locator)
|
||||
self.assertEqual(str(course.location.version_guid), self.GUID_D1)
|
||||
self.assertEqual(course.location.version_guid, versions['draft'])
|
||||
|
||||
# an allowed but not recommended way to publish a course
|
||||
versions['published'] = self.GUID_D1
|
||||
versions['published'] = versions['draft']
|
||||
modulestore().update_course_index(course_info)
|
||||
course = modulestore().get_course(CourseLocator(package_id=locator.package_id, branch="published"))
|
||||
self.assertEqual(str(course.location.version_guid), self.GUID_D1)
|
||||
self.assertEqual(course.location.version_guid, versions['draft'])
|
||||
|
||||
def test_create_with_root(self):
|
||||
"""
|
||||
@@ -1086,7 +1454,7 @@ class TestCourseCreation(SplitModuleTest):
|
||||
"""
|
||||
user = random.getrandbits(32)
|
||||
new_course = modulestore().create_course(
|
||||
'test_org.test_transaction', 'test_org', 'test_transaction', user,
|
||||
'test_org.test_transaction', 'test_org', user,
|
||||
root_block_id='top', root_category='chapter'
|
||||
)
|
||||
self.assertEqual(new_course.location.block_id, 'top')
|
||||
@@ -1120,11 +1488,11 @@ class TestInheritance(SplitModuleTest):
|
||||
"""
|
||||
# Note, not testing value where defined (course) b/c there's no
|
||||
# defined accessor for it on CourseDescriptor.
|
||||
locator = BlockUsageLocator(package_id="GreekHero", block_id="problem3_2", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.GreekHero", block_id="problem3_2", branch='draft')
|
||||
node = modulestore().get_item(locator)
|
||||
# inherited
|
||||
self.assertEqual(node.graceperiod, datetime.timedelta(hours=2))
|
||||
locator = BlockUsageLocator(package_id="GreekHero", block_id="problem1", branch='draft')
|
||||
locator = BlockUsageLocator(package_id="testx.GreekHero", block_id="problem1", branch='draft')
|
||||
node = modulestore().get_item(locator)
|
||||
# overridden
|
||||
self.assertEqual(node.graceperiod, datetime.timedelta(hours=4))
|
||||
@@ -1139,14 +1507,14 @@ class TestPublish(SplitModuleTest):
|
||||
self.user = random.getrandbits(32)
|
||||
|
||||
def tearDown(self):
|
||||
SplitModuleTest.tearDownClass()
|
||||
SplitModuleTest.tearDown(self)
|
||||
|
||||
def test_publish_safe(self):
|
||||
"""
|
||||
Test the standard patterns: publish to new branch, revise and publish
|
||||
"""
|
||||
source_course = CourseLocator(package_id="GreekHero", branch='draft')
|
||||
dest_course = CourseLocator(package_id="GreekHero", branch="published")
|
||||
source_course = CourseLocator(package_id="testx.GreekHero", branch='draft')
|
||||
dest_course = CourseLocator(package_id="testx.GreekHero", branch="published")
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, ["head12345"], ["chapter2", "chapter3"])
|
||||
expected = ["head12345", "chapter1"]
|
||||
self._check_course(
|
||||
@@ -1154,19 +1522,19 @@ class TestPublish(SplitModuleTest):
|
||||
)
|
||||
# add a child under chapter1
|
||||
new_module = modulestore().create_item(
|
||||
self._usage(source_course, "chapter1"), "sequential", self.user,
|
||||
BlockUsageLocator.make_relative(source_course, "chapter1"), "sequential", self.user,
|
||||
fields={'display_name': 'new sequential'},
|
||||
)
|
||||
# remove chapter1 from expected b/c its pub'd version != the source anymore since source changed
|
||||
expected.remove("chapter1")
|
||||
# check that it's not in published course
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().get_item(self._usage(dest_course, new_module.location.block_id))
|
||||
modulestore().get_item(BlockUsageLocator.make_relative(dest_course, new_module.location.block_id))
|
||||
# publish it
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None)
|
||||
expected.append(new_module.location.block_id)
|
||||
# check that it is in the published course and that its parent is the chapter
|
||||
pub_module = modulestore().get_item(self._usage(dest_course, new_module.location.block_id))
|
||||
pub_module = modulestore().get_item(BlockUsageLocator.make_relative(dest_course, new_module.location.block_id))
|
||||
self.assertEqual(
|
||||
modulestore().get_parent_locations(pub_module.location)[0].block_id, "chapter1"
|
||||
)
|
||||
@@ -1178,7 +1546,7 @@ class TestPublish(SplitModuleTest):
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None)
|
||||
expected.append(new_module.location.block_id)
|
||||
# check that it is in the published course (no error means it worked)
|
||||
pub_module = modulestore().get_item(self._usage(dest_course, new_module.location.block_id))
|
||||
pub_module = modulestore().get_item(BlockUsageLocator.make_relative(dest_course, new_module.location.block_id))
|
||||
self._check_course(
|
||||
source_course, dest_course, expected, ["chapter2", "chapter3", "problem1", "problem3_2"]
|
||||
)
|
||||
@@ -1187,13 +1555,13 @@ class TestPublish(SplitModuleTest):
|
||||
"""
|
||||
Test the exceptions which preclude successful publication
|
||||
"""
|
||||
source_course = CourseLocator(package_id="GreekHero", branch='draft')
|
||||
source_course = CourseLocator(package_id="testx.GreekHero", branch='draft')
|
||||
# destination does not exist
|
||||
destination_course = CourseLocator(package_id="Unknown", branch="published")
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().xblock_publish(self.user, source_course, destination_course, ["chapter3"], None)
|
||||
# publishing into a new branch w/o publishing the root
|
||||
destination_course = CourseLocator(package_id="GreekHero", branch="published")
|
||||
destination_course = CourseLocator(package_id="testx.GreekHero", branch="published")
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().xblock_publish(self.user, source_course, destination_course, ["chapter3"], None)
|
||||
# publishing a subdag w/o the parent already in course
|
||||
@@ -1205,17 +1573,17 @@ class TestPublish(SplitModuleTest):
|
||||
"""
|
||||
Test publishing moves and deletes.
|
||||
"""
|
||||
source_course = CourseLocator(package_id="GreekHero", branch='draft')
|
||||
dest_course = CourseLocator(package_id="GreekHero", branch="published")
|
||||
source_course = CourseLocator(package_id="testx.GreekHero", branch='draft')
|
||||
dest_course = CourseLocator(package_id="testx.GreekHero", branch="published")
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, ["head12345"], ["chapter2"])
|
||||
expected = ["head12345", "chapter1", "chapter3", "problem1", "problem3_2"]
|
||||
self._check_course(source_course, dest_course, expected, ["chapter2"])
|
||||
# now move problem1 and delete problem3_2
|
||||
chapter1 = modulestore().get_item(self._usage(source_course, "chapter1"))
|
||||
chapter3 = modulestore().get_item(self._usage(source_course, "chapter3"))
|
||||
chapter1 = modulestore().get_item(BlockUsageLocator.make_relative(source_course, "chapter1"))
|
||||
chapter3 = modulestore().get_item(BlockUsageLocator.make_relative(source_course, "chapter3"))
|
||||
chapter1.children.append("problem1")
|
||||
chapter3.children.remove("problem1")
|
||||
modulestore().delete_item(self._usage(source_course, "problem3_2"), self.user)
|
||||
modulestore().delete_item(BlockUsageLocator.make_relative(source_course, "problem3_2"), self.user)
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, ["head12345"], ["chapter2"])
|
||||
expected = ["head12345", "chapter1", "chapter3", "problem1"]
|
||||
self._check_course(source_course, dest_course, expected, ["chapter2", "problem3_2"])
|
||||
@@ -1224,13 +1592,18 @@ class TestPublish(SplitModuleTest):
|
||||
"""
|
||||
Check that the course has the expected blocks and does not have the unexpected blocks
|
||||
"""
|
||||
history_info = modulestore().get_course_history_info(dest_course_loc)
|
||||
self.assertEqual(history_info['edited_by'], self.user)
|
||||
for expected in expected_blocks:
|
||||
source = modulestore().get_item(self._usage(source_course_loc, expected))
|
||||
pub_copy = modulestore().get_item(self._usage(dest_course_loc, expected))
|
||||
source = modulestore().get_item(BlockUsageLocator.make_relative(source_course_loc, expected))
|
||||
pub_copy = modulestore().get_item(BlockUsageLocator.make_relative(dest_course_loc, expected))
|
||||
# everything except previous_version & children should be the same
|
||||
self.assertEqual(source.category, pub_copy.category)
|
||||
self.assertEqual(source.update_version, pub_copy.update_version)
|
||||
self.assertEqual(self.user, pub_copy.edited_by)
|
||||
self.assertEqual(
|
||||
self.user, pub_copy.edited_by,
|
||||
"{} edited_by {} not {}".format(pub_copy.location, pub_copy.edited_by, self.user)
|
||||
)
|
||||
for field in source.fields.values():
|
||||
if field.name == 'children':
|
||||
self._compare_children(field.read_from(source), field.read_from(pub_copy), unexpected_blocks)
|
||||
@@ -1238,13 +1611,7 @@ class TestPublish(SplitModuleTest):
|
||||
self.assertEqual(field.read_from(source), field.read_from(pub_copy))
|
||||
for unexp in unexpected_blocks:
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().get_item(self._usage(dest_course_loc, unexp))
|
||||
|
||||
def _usage(self, course_loc, block_id):
|
||||
"""
|
||||
Generate a BlockUsageLocator for the combo of the course location and block id
|
||||
"""
|
||||
return BlockUsageLocator(package_id=course_loc.package_id, branch=course_loc.branch, block_id=block_id)
|
||||
modulestore().get_item(BlockUsageLocator.make_relative(dest_course_loc, unexp))
|
||||
|
||||
def _compare_children(self, source_children, dest_children, unexpected):
|
||||
"""
|
||||
@@ -1259,17 +1626,40 @@ class TestPublish(SplitModuleTest):
|
||||
dest_cursor += 1
|
||||
self.assertEqual(dest_cursor, len(dest_children))
|
||||
|
||||
class TestSchema(SplitModuleTest):
|
||||
"""
|
||||
Test the db schema (and possibly eventually migrations?)
|
||||
"""
|
||||
def test_schema(self):
|
||||
"""
|
||||
Test that the schema is set in each document
|
||||
"""
|
||||
db_connection = modulestore().db_connection
|
||||
for collection in [db_connection.course_index, db_connection.structures, db_connection.definitions]:
|
||||
self.assertEqual(
|
||||
collection.find({'schema_version': {'$exists': False}}).count(),
|
||||
0,
|
||||
"{0.name} has records without schema_version".format(collection)
|
||||
)
|
||||
self.assertEqual(
|
||||
collection.find({'schema_version': {'$ne': SplitMongoModuleStore.SCHEMA_VERSION}}).count(),
|
||||
0,
|
||||
"{0.name} has records with wrong schema_version".format(collection)
|
||||
)
|
||||
|
||||
#===========================================
|
||||
# This mocks the django.modulestore() function and is intended purely to disentangle
|
||||
# the tests from django
|
||||
def modulestore():
|
||||
"""
|
||||
Mock the django dependent global modulestore function to disentangle tests from django
|
||||
"""
|
||||
def load_function(engine_path):
|
||||
"""
|
||||
Load the given engine
|
||||
"""
|
||||
module_path, _, name = engine_path.rpartition('.')
|
||||
return getattr(import_module(module_path), name)
|
||||
|
||||
if SplitModuleTest.modulestore is None:
|
||||
SplitModuleTest.bootstrapDB()
|
||||
class_ = load_function(SplitModuleTest.MODULESTORE['ENGINE'])
|
||||
|
||||
options = {}
|
||||
@@ -1283,6 +1673,8 @@ def modulestore():
|
||||
**options
|
||||
)
|
||||
|
||||
SplitModuleTest.bootstrapDB()
|
||||
|
||||
return SplitModuleTest.modulestore
|
||||
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
[{"_id" : "GreekHero",
|
||||
"org" : "testx",
|
||||
"prettyid" : "test_course",
|
||||
"versions" : {
|
||||
"draft" : { "$oid" : "1d00000000000000dddd0000" }
|
||||
},
|
||||
"edited_on" : {"$date" : 1364481713238},
|
||||
"edited_by" : "test@edx.org"},
|
||||
|
||||
{"_id" : "wonderful",
|
||||
"org" : "testx",
|
||||
"prettyid" : "another_course",
|
||||
"versions" : {
|
||||
"draft" : { "$oid" : "1d00000000000000dddd2222" },
|
||||
"published" : { "$oid" : "1d00000000000000eeee0000" }
|
||||
},
|
||||
"edited_on" : {"$date" : 1364481313238},
|
||||
"edited_by" : "test@edx.org"},
|
||||
|
||||
{"_id" : "contender",
|
||||
"org" : "guestx",
|
||||
"prettyid" : "test_course",
|
||||
"versions" : {
|
||||
"draft" : { "$oid" : "1d00000000000000dddd5555" }},
|
||||
"edited_on" : {"$date" : 1364491313238},
|
||||
"edited_by" : "test@guestx.edu"}
|
||||
]
|
||||
@@ -1,356 +0,0 @@
|
||||
[
|
||||
{
|
||||
"_id": { "$oid" : "ad00000000000000dddd0000"},
|
||||
"category":"course",
|
||||
"fields":{
|
||||
"textbooks":[
|
||||
|
||||
],
|
||||
"grading_policy":{
|
||||
"GRADER":[
|
||||
{
|
||||
"min_count":4,
|
||||
"weight":0.15,
|
||||
"type":"Homework",
|
||||
"drop_count":2,
|
||||
"short_label":"HWa"
|
||||
},
|
||||
{
|
||||
"short_label":"",
|
||||
"min_count":12,
|
||||
"type":"Lab",
|
||||
"drop_count":2,
|
||||
"weight":0.15
|
||||
},
|
||||
{
|
||||
"short_label":"Midterm",
|
||||
"min_count":1,
|
||||
"type":"Midterm Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.3
|
||||
},
|
||||
{
|
||||
"short_label":"Final",
|
||||
"min_count":1,
|
||||
"type":"Final Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.4
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS":{
|
||||
"Pass":0.45
|
||||
}
|
||||
},
|
||||
"wiki_slug":null
|
||||
},
|
||||
"edit_info": {
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{"$date" : 1364481713238},
|
||||
"previous_version":{ "$oid" : "ad00000000000000dddd0001"},
|
||||
"original_version":{ "$oid" : "ad00000000000000dddd0010"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id":{ "$oid" : "ad00000000000000dddd0001"},
|
||||
"category":"course",
|
||||
"fields":{
|
||||
"textbooks":[
|
||||
|
||||
],
|
||||
"grading_policy":{
|
||||
"GRADER":[
|
||||
{
|
||||
"min_count":5,
|
||||
"weight":0.15,
|
||||
"type":"Homework",
|
||||
"drop_count":1,
|
||||
"short_label":"HWa"
|
||||
},
|
||||
{
|
||||
"short_label":"",
|
||||
"min_count":12,
|
||||
"type":"Lab",
|
||||
"drop_count":2,
|
||||
"weight":0.15
|
||||
},
|
||||
{
|
||||
"short_label":"Midterm",
|
||||
"min_count":1,
|
||||
"type":"Midterm Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.3
|
||||
},
|
||||
{
|
||||
"short_label":"Final",
|
||||
"min_count":1,
|
||||
"type":"Final Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.4
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS":{
|
||||
"Pass":0.55
|
||||
}
|
||||
},
|
||||
"wiki_slug":null
|
||||
},
|
||||
"edit_info": {
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{"$date" : 1364481713238},
|
||||
"previous_version":{ "$oid" : "ad00000000000000dddd0010"},
|
||||
"original_version":{ "$oid" : "ad00000000000000dddd0010"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id":{ "$oid" : "ad00000000000000dddd0010"},
|
||||
"category":"course",
|
||||
"fields":{
|
||||
"textbooks":[
|
||||
|
||||
],
|
||||
"grading_policy":{
|
||||
"GRADER":[
|
||||
{
|
||||
"min_count":5,
|
||||
"weight":0.15,
|
||||
"type":"Homework",
|
||||
"drop_count":1,
|
||||
"short_label":"HWa"
|
||||
},
|
||||
{
|
||||
"short_label":"",
|
||||
"min_count":2,
|
||||
"type":"Lab",
|
||||
"drop_count":0,
|
||||
"weight":0.15
|
||||
},
|
||||
{
|
||||
"short_label":"Midterm",
|
||||
"min_count":1,
|
||||
"type":"Midterm Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.3
|
||||
},
|
||||
{
|
||||
"short_label":"Final",
|
||||
"min_count":1,
|
||||
"type":"Final Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.4
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS":{
|
||||
"Pass":0.75
|
||||
}
|
||||
},
|
||||
"wiki_slug":null
|
||||
},
|
||||
"edit_info": {
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{"$date": 1364473713238},
|
||||
"previous_version":null,
|
||||
"original_version":{ "$oid" : "ad00000000000000dddd0010"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id":{ "$oid" : "ad00000000000000dddd0020"},
|
||||
"category":"course",
|
||||
"fields":{
|
||||
"textbooks":[
|
||||
|
||||
],
|
||||
"grading_policy":{
|
||||
"GRADER":[
|
||||
{
|
||||
"min_count":14,
|
||||
"weight":0.25,
|
||||
"type":"Homework",
|
||||
"drop_count":1,
|
||||
"short_label":"HWa"
|
||||
},
|
||||
{
|
||||
"short_label":"",
|
||||
"min_count":12,
|
||||
"type":"Lab",
|
||||
"drop_count":2,
|
||||
"weight":0.25
|
||||
},
|
||||
{
|
||||
"short_label":"Midterm",
|
||||
"min_count":1,
|
||||
"type":"Midterm Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.2
|
||||
},
|
||||
{
|
||||
"short_label":"Final",
|
||||
"min_count":1,
|
||||
"type":"Final Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.3
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS":{
|
||||
"Pass":0.45
|
||||
}
|
||||
},
|
||||
"wiki_slug":null
|
||||
},
|
||||
"edit_info": {
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{"$date": 1364481313238},
|
||||
"previous_version":{ "$oid" : "2d00000000000000dddd0020"},
|
||||
"original_version":{ "$oid" : "2d00000000000000dddd0020"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id":{ "$oid" : "2d00000000000000dddd0020"},
|
||||
"category":"course",
|
||||
"fields":{
|
||||
"textbooks":[
|
||||
|
||||
],
|
||||
"grading_policy":{
|
||||
"GRADER":[
|
||||
{
|
||||
"min_count":14,
|
||||
"weight":0.25,
|
||||
"type":"Homework",
|
||||
"drop_count":1,
|
||||
"short_label":"HWa"
|
||||
},
|
||||
{
|
||||
"short_label":"",
|
||||
"min_count":12,
|
||||
"type":"Lab",
|
||||
"drop_count":2,
|
||||
"weight":0.25
|
||||
},
|
||||
{
|
||||
"short_label":"Midterm",
|
||||
"min_count":1,
|
||||
"type":"Midterm Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.2
|
||||
},
|
||||
{
|
||||
"short_label":"Final",
|
||||
"min_count":1,
|
||||
"type":"Final Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.3
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS":{
|
||||
"Pass":0.95
|
||||
}
|
||||
},
|
||||
"wiki_slug":null
|
||||
},
|
||||
"edit_info": {
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{"$date" : 1364481313238},
|
||||
"previous_version":null,
|
||||
"original_version":{ "$oid" : "2d00000000000000dddd0020"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id":{ "$oid" : "3d00000000000000dddd0020"},
|
||||
"category":"course",
|
||||
"fields":{
|
||||
"textbooks":[
|
||||
|
||||
],
|
||||
"grading_policy":{
|
||||
"GRADER":[
|
||||
{
|
||||
"min_count":4,
|
||||
"weight":0.25,
|
||||
"type":"Homework",
|
||||
"drop_count":0,
|
||||
"short_label":"HW"
|
||||
},
|
||||
{
|
||||
"short_label":"Midterm",
|
||||
"min_count":1,
|
||||
"type":"Midterm Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.4
|
||||
},
|
||||
{
|
||||
"short_label":"Final",
|
||||
"min_count":1,
|
||||
"type":"Final Exam",
|
||||
"drop_count":0,
|
||||
"weight":0.35
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS":{
|
||||
"Pass":0.25
|
||||
}
|
||||
},
|
||||
"wiki_slug":null
|
||||
},
|
||||
"edit_info": {
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{"$date" : 1364481313238},
|
||||
"previous_version":null,
|
||||
"original_version":{ "$oid" : "2d00000000000000dddd0020"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id":{ "$oid" : "cd00000000000000dddd0020"},
|
||||
"category":"chapter",
|
||||
"fields":{},
|
||||
"edit_info": {
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{"$date" : 1364483713238},
|
||||
"previous_version":null,
|
||||
"original_version":{ "$oid" : "cd00000000000000dddd0020"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id":{ "$oid" : "cd00000000000000dddd0022"},
|
||||
"category":"chapter",
|
||||
"fields":{},
|
||||
"edit_info": {
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{"$date" : 1364483713238},
|
||||
"previous_version":null,
|
||||
"original_version":{ "$oid" : "cd00000000000000dddd0022"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id":{ "$oid" : "cd00000000000000dddd0032"},
|
||||
"category":"chapter",
|
||||
"fields":{},
|
||||
"edit_info": {
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{"$date" : 1364483713238},
|
||||
"previous_version":null,
|
||||
"original_version":{ "$oid" : "cd00000000000000dddd0032"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id":{ "$oid" : "0d00000040000000dddd0031"},
|
||||
"category":"problem",
|
||||
"fields": {"data": ""},
|
||||
"edit_info": {
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{"$date" : 1364483713238},
|
||||
"previous_version":null,
|
||||
"original_version":{ "$oid" : "0d00000040000000dddd0031"}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id":{ "$oid" : "0d00000040000000dddd0032"},
|
||||
"category":"problem",
|
||||
"fields": {"data": ""},
|
||||
"edit_info": {
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{"$date" : 1364483713238},
|
||||
"previous_version":null,
|
||||
"original_version":{ "$oid" : "0d00000040000000dddd0032"}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,495 +0,0 @@
|
||||
[
|
||||
{
|
||||
"_id": { "$oid" : "1d00000000000000dddd0000"},
|
||||
"root":"head12345",
|
||||
"original_version":{ "$oid" : "1d00000000000000dddd3333" },
|
||||
"previous_version":{ "$oid" : "1d00000000000000dddd1111" },
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364483713238
|
||||
},
|
||||
"blocks":{
|
||||
"head12345":{
|
||||
"category":"course",
|
||||
"definition":{ "$oid" : "ad00000000000000dddd0000"},
|
||||
"fields":{
|
||||
"children":[
|
||||
"chapter1",
|
||||
"chapter2",
|
||||
"chapter3"
|
||||
],
|
||||
"end":"2013-06-13T04:30",
|
||||
"tabs":[
|
||||
{
|
||||
"type":"courseware"
|
||||
},
|
||||
{
|
||||
"type":"course_info",
|
||||
"name":"Course Info"
|
||||
},
|
||||
{
|
||||
"type":"discussion",
|
||||
"name":"Discussion"
|
||||
},
|
||||
{
|
||||
"type":"wiki",
|
||||
"name":"Wiki"
|
||||
},
|
||||
{
|
||||
"type":"static_tab",
|
||||
"name":"Syllabus",
|
||||
"url_slug":"01356a17b5924b17a04b7fc2426a3798"
|
||||
},
|
||||
{
|
||||
"type":"static_tab",
|
||||
"name":"Advice for Students",
|
||||
"url_slug":"57e9991c0d794ff58f7defae3e042e39"
|
||||
}
|
||||
],
|
||||
"enrollment_start":"2013-01-01T05:00",
|
||||
"graceperiod":"2 hours 0 minutes 0 seconds",
|
||||
"start":"2013-02-14T05:00",
|
||||
"enrollment_end":"2013-03-02T05:00",
|
||||
"data_dir":"MITx-2-Base",
|
||||
"advertised_start":"Fall 2013",
|
||||
"display_name":"The Ancient Greek Hero"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd0000" },
|
||||
"previous_version":{ "$oid" : "1d00000000000000dddd1111" },
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364483713238
|
||||
}
|
||||
}
|
||||
},
|
||||
"chapter1":{
|
||||
"category":"chapter",
|
||||
"definition":{ "$oid" : "cd00000000000000dddd0020"},
|
||||
"fields":{
|
||||
"children":[
|
||||
|
||||
],
|
||||
"display_name":"Hercules"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd0000" },
|
||||
"previous_version":null,
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364483713238
|
||||
}
|
||||
}
|
||||
},
|
||||
"chapter2":{
|
||||
"category":"chapter",
|
||||
"definition":{ "$oid" : "cd00000000000000dddd0022"},
|
||||
"fields":{
|
||||
"children":[
|
||||
|
||||
],
|
||||
"display_name":"Hera heckles Hercules"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd0000" },
|
||||
"previous_version":null,
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364483713238
|
||||
}
|
||||
}
|
||||
},
|
||||
"chapter3":{
|
||||
"category":"chapter",
|
||||
"definition":{ "$oid" : "cd00000000000000dddd0032"},
|
||||
"fields":{
|
||||
"children":[
|
||||
"problem1",
|
||||
"problem3_2"
|
||||
],
|
||||
"display_name":"Hera cuckolds Zeus"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd0000" },
|
||||
"previous_version":null,
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364483713238
|
||||
}
|
||||
}
|
||||
},
|
||||
"problem1":{
|
||||
"category":"problem",
|
||||
"definition":{ "$oid" : "0d00000040000000dddd0031"},
|
||||
"fields":{
|
||||
"children":[
|
||||
|
||||
],
|
||||
"display_name":"Problem 3.1",
|
||||
"graceperiod":"4 hours 0 minutes 0 seconds"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd0000" },
|
||||
"previous_version":null,
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364483713238
|
||||
}
|
||||
}
|
||||
},
|
||||
"problem3_2":{
|
||||
"category":"problem",
|
||||
"definition":{ "$oid" : "0d00000040000000dddd0032"},
|
||||
"fields":{
|
||||
"children":[
|
||||
|
||||
],
|
||||
"display_name":"Problem 3.2"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd0000" },
|
||||
"previous_version":null,
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364483713238
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id": { "$oid" : "1d00000000000000dddd1111"},
|
||||
"root":"head12345",
|
||||
"original_version":{ "$oid" : "1d00000000000000dddd3333" },
|
||||
"previous_version":{ "$oid" : "1d00000000000000dddd3333" },
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364481713238
|
||||
},
|
||||
"blocks":{
|
||||
"head12345":{
|
||||
"category":"course",
|
||||
"definition":{ "$oid" : "ad00000000000000dddd0001"},
|
||||
"fields":{
|
||||
"children":[
|
||||
|
||||
],
|
||||
"end":"2013-04-13T04:30",
|
||||
"tabs":[
|
||||
{
|
||||
"type":"courseware"
|
||||
},
|
||||
{
|
||||
"type":"course_info",
|
||||
"name":"Course Info"
|
||||
},
|
||||
{
|
||||
"type":"discussion",
|
||||
"name":"Discussion"
|
||||
},
|
||||
{
|
||||
"type":"wiki",
|
||||
"name":"Wiki"
|
||||
},
|
||||
{
|
||||
"type":"static_tab",
|
||||
"name":"Syllabus",
|
||||
"url_slug":"01356a17b5924b17a04b7fc2426a3798"
|
||||
},
|
||||
{
|
||||
"type":"static_tab",
|
||||
"name":"Advice for Students",
|
||||
"url_slug":"57e9991c0d794ff58f7defae3e042e39"
|
||||
}
|
||||
],
|
||||
"enrollment_start":null,
|
||||
"graceperiod":"2 hours 0 minutes 0 seconds",
|
||||
"start":"2013-02-14T05:00",
|
||||
"enrollment_end":null,
|
||||
"data_dir":"MITx-2-Base",
|
||||
"advertised_start":null,
|
||||
"display_name":"The Ancient Greek Hero"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd1111" },
|
||||
"previous_version":{ "$oid" : "1d00000000000000dddd3333" },
|
||||
"edited_by":"testassist@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364481713238
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id": { "$oid" : "1d00000000000000dddd3333"},
|
||||
"root":"head12345",
|
||||
"original_version":{ "$oid" : "1d00000000000000dddd3333" },
|
||||
"previous_version":null,
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364473713238
|
||||
},
|
||||
"blocks":{
|
||||
"head12345":{
|
||||
"category":"course",
|
||||
"definition":{ "$oid" : "ad00000000000000dddd0010"},
|
||||
"fields":{
|
||||
"children":[
|
||||
|
||||
],
|
||||
"end":null,
|
||||
"tabs":[
|
||||
{
|
||||
"type":"courseware"
|
||||
},
|
||||
{
|
||||
"type":"course_info",
|
||||
"name":"Course Info"
|
||||
},
|
||||
{
|
||||
"type":"discussion",
|
||||
"name":"Discussion"
|
||||
},
|
||||
{
|
||||
"type":"wiki",
|
||||
"name":"Wiki"
|
||||
}
|
||||
],
|
||||
"enrollment_start":null,
|
||||
"graceperiod":null,
|
||||
"start":"2013-02-14T05:00",
|
||||
"enrollment_end":null,
|
||||
"data_dir":"MITx-2-Base",
|
||||
"advertised_start":null,
|
||||
"display_name":"The Ancient Greek Hero"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd3333" },
|
||||
"previous_version":null,
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364473713238
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id": { "$oid" : "1d00000000000000dddd2222"},
|
||||
"root":"head23456",
|
||||
"original_version":{ "$oid" : "1d00000000000000dddd4444" },
|
||||
"previous_version":{ "$oid" : "1d00000000000000dddd4444" },
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364481313238
|
||||
},
|
||||
"blocks":{
|
||||
"head23456":{
|
||||
"category":"course",
|
||||
"definition":{ "$oid" : "ad00000000000000dddd0020"},
|
||||
"fields":{
|
||||
"children":[
|
||||
|
||||
],
|
||||
"end":null,
|
||||
"tabs":[
|
||||
{
|
||||
"type":"courseware"
|
||||
},
|
||||
{
|
||||
"type":"course_info",
|
||||
"name":"Course Info"
|
||||
},
|
||||
{
|
||||
"type":"discussion",
|
||||
"name":"Discussion"
|
||||
},
|
||||
{
|
||||
"type":"wiki",
|
||||
"name":"Wiki"
|
||||
}
|
||||
],
|
||||
"enrollment_start":null,
|
||||
"graceperiod":null,
|
||||
"start":"2013-02-14T05:00",
|
||||
"enrollment_end":null,
|
||||
"data_dir":"MITx-2-Base",
|
||||
"advertised_start":null,
|
||||
"display_name":"The most wonderful course"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd2222" },
|
||||
"previous_version":{ "$oid" : "1d00000000000000dddd4444" },
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364481313238
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id": { "$oid" : "1d00000000000000dddd4444"},
|
||||
"root":"head23456",
|
||||
"original_version":{ "$oid" : "1d00000000000000dddd4444" },
|
||||
"previous_version":null,
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364480313238
|
||||
},
|
||||
"blocks":{
|
||||
"head23456":{
|
||||
"category":"course",
|
||||
"definition":{ "$oid" : "2d00000000000000dddd0020"},
|
||||
"fields":{
|
||||
"children":[
|
||||
|
||||
],
|
||||
"end":null,
|
||||
"tabs":[
|
||||
{
|
||||
"type":"courseware"
|
||||
},
|
||||
{
|
||||
"type":"course_info",
|
||||
"name":"Course Info"
|
||||
},
|
||||
{
|
||||
"type":"discussion",
|
||||
"name":"Discussion"
|
||||
},
|
||||
{
|
||||
"type":"wiki",
|
||||
"name":"Wiki"
|
||||
}
|
||||
],
|
||||
"enrollment_start":null,
|
||||
"graceperiod":null,
|
||||
"start":"2013-02-14T05:00",
|
||||
"enrollment_end":null,
|
||||
"data_dir":"MITx-2-Base",
|
||||
"advertised_start":null,
|
||||
"display_name":"A wonderful course"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd4444" },
|
||||
"previous_version":null,
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364480313238
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id": { "$oid" : "1d00000000000000eeee0000"},
|
||||
"root":"head23456",
|
||||
"original_version":{ "$oid" : "1d00000000000000eeee0000" },
|
||||
"previous_version":null,
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364481333238
|
||||
},
|
||||
"blocks":{
|
||||
"head23456":{
|
||||
"category":"course",
|
||||
"definition":{ "$oid" : "ad00000000000000dddd0020"},
|
||||
"fields":{
|
||||
"children":[
|
||||
|
||||
],
|
||||
"end":null,
|
||||
"tabs":[
|
||||
{
|
||||
"type":"courseware"
|
||||
},
|
||||
{
|
||||
"type":"course_info",
|
||||
"name":"Course Info"
|
||||
},
|
||||
{
|
||||
"type":"discussion",
|
||||
"name":"Discussion"
|
||||
},
|
||||
{
|
||||
"type":"wiki",
|
||||
"name":"Wiki"
|
||||
}
|
||||
],
|
||||
"enrollment_start":null,
|
||||
"graceperiod":null,
|
||||
"start":"2013-02-14T05:00",
|
||||
"enrollment_end":null,
|
||||
"data_dir":"MITx-2-Base",
|
||||
"advertised_start":null,
|
||||
"display_name":"The most wonderful course"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000eeee0000" },
|
||||
"previous_version":null,
|
||||
"edited_by":"test@edx.org",
|
||||
"edited_on":{
|
||||
"$date":1364481333238
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"_id": { "$oid" : "1d00000000000000dddd5555"},
|
||||
"root":"head345679",
|
||||
"original_version":{ "$oid" : "1d00000000000000dddd5555" },
|
||||
"previous_version":null,
|
||||
"edited_by":"test@guestx.edu",
|
||||
"edited_on":{
|
||||
"$date":1364491313238
|
||||
},
|
||||
"blocks":{
|
||||
"head345679":{
|
||||
"category":"course",
|
||||
"definition":{ "$oid" : "3d00000000000000dddd0020"},
|
||||
"fields":{
|
||||
"children":[
|
||||
|
||||
],
|
||||
"end":null,
|
||||
"tabs":[
|
||||
{
|
||||
"type":"courseware"
|
||||
},
|
||||
{
|
||||
"type":"course_info",
|
||||
"name":"Course Info"
|
||||
},
|
||||
{
|
||||
"type":"discussion",
|
||||
"name":"Discussion"
|
||||
},
|
||||
{
|
||||
"type":"wiki",
|
||||
"name":"Wiki"
|
||||
}
|
||||
],
|
||||
"enrollment_start":null,
|
||||
"graceperiod":null,
|
||||
"start":"2013-03-14T05:00",
|
||||
"enrollment_end":null,
|
||||
"data_dir":"MITx-3-Base",
|
||||
"advertised_start":null,
|
||||
"display_name":"Yet another contender"
|
||||
},
|
||||
"edit_info": {
|
||||
"update_version":{ "$oid" : "1d00000000000000dddd5555" },
|
||||
"previous_version":null,
|
||||
"edited_by":"test@guestx.edu",
|
||||
"edited_on":{
|
||||
"$date":1364491313238
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user