Commit 0895e90a by Don Mitchell

Generate split test data via split

parent 542b146f
import unittest import unittest
from django.conf import settings
from xmodule import templates from xmodule import templates
from xmodule.modulestore.tests import persistent_factories from xmodule.modulestore.tests import persistent_factories
...@@ -10,8 +9,6 @@ from xmodule.capa_module import CapaDescriptor ...@@ -10,8 +9,6 @@ from xmodule.capa_module import CapaDescriptor
from xmodule.modulestore.locator import CourseLocator, BlockUsageLocator, LocalId from xmodule.modulestore.locator import CourseLocator, BlockUsageLocator, LocalId
from xmodule.modulestore.exceptions import ItemNotFoundError, DuplicateCourseError from xmodule.modulestore.exceptions import ItemNotFoundError, DuplicateCourseError
from xmodule.html_module import HtmlDescriptor from xmodule.html_module import HtmlDescriptor
from xmodule.modulestore import inheritance
from xblock.core import XBlock
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
...@@ -81,25 +78,25 @@ class TemplateTests(unittest.TestCase): ...@@ -81,25 +78,25 @@ class TemplateTests(unittest.TestCase):
def test_temporary_xblocks(self): 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( test_course = persistent_factories.PersistentCourseFactory.create(
course_id='testx.tempcourse', org='testx', course_id='testx.tempcourse', org='testx',
display_name='fun test course', user_id='testbot' display_name='fun test course', user_id='testbot'
) )
test_chapter = self.load_from_json({'category': 'chapter', test_chapter = modulestore('split').create_xblock(
'fields': {'display_name': 'chapter n'}}, test_course.system, 'chapter', {'display_name': 'chapter n'}, parent_xblock=test_course
test_course.system, parent_xblock=test_course) )
self.assertIsInstance(test_chapter, SequenceDescriptor) self.assertIsInstance(test_chapter, SequenceDescriptor)
self.assertEqual(test_chapter.display_name, 'chapter n') self.assertEqual(test_chapter.display_name, 'chapter n')
self.assertIn(test_chapter, test_course.get_children()) self.assertIn(test_chapter, test_course.get_children())
# test w/ a definition (e.g., a problem) # test w/ a definition (e.g., a problem)
test_def_content = '<problem>boo</problem>' test_def_content = '<problem>boo</problem>'
test_problem = self.load_from_json({'category': 'problem', test_problem = modulestore('split').create_xblock(
'fields': {'data': test_def_content}}, test_course.system, 'problem', {'data': test_def_content}, parent_xblock=test_chapter
test_course.system, parent_xblock=test_chapter) )
self.assertIsInstance(test_problem, CapaDescriptor) self.assertIsInstance(test_problem, CapaDescriptor)
self.assertEqual(test_problem.data, test_def_content) self.assertEqual(test_problem.data, test_def_content)
self.assertIn(test_problem, test_chapter.get_children()) self.assertIn(test_problem, test_chapter.get_children())
...@@ -114,18 +111,19 @@ class TemplateTests(unittest.TestCase): ...@@ -114,18 +111,19 @@ class TemplateTests(unittest.TestCase):
course_id='testx.tempcourse', org='testx', course_id='testx.tempcourse', org='testx',
display_name='fun test course', user_id='testbot' display_name='fun test course', user_id='testbot'
) )
test_chapter = self.load_from_json({'category': 'chapter', test_chapter = modulestore('split').create_xblock(
'fields': {'display_name': 'chapter n'}}, test_course.system, 'chapter', {'display_name': 'chapter n'}, parent_xblock=test_course
test_course.system, parent_xblock=test_course) )
self.assertEqual(test_chapter.display_name, 'chapter n')
test_def_content = '<problem>boo</problem>' test_def_content = '<problem>boo</problem>'
# create child # create child
new_block = self.load_from_json({ new_block = modulestore('split').create_xblock(
'category': 'problem', test_course.system,
'fields': { 'problem',
fields={
'data': test_def_content, 'data': test_def_content,
'display_name': 'problem' 'display_name': 'problem'
}}, },
test_course.system,
parent_xblock=test_chapter parent_xblock=test_chapter
) )
self.assertIsNotNone(new_block.definition_locator) self.assertIsNotNone(new_block.definition_locator)
...@@ -242,44 +240,3 @@ class TemplateTests(unittest.TestCase): ...@@ -242,44 +240,3 @@ class TemplateTests(unittest.TestCase):
mapper = loc_mapper() mapper = loc_mapper()
self.assertEqual(modulestore('split').loc_mapper, 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
...@@ -22,12 +22,14 @@ log = logging.getLogger(__name__) ...@@ -22,12 +22,14 @@ log = logging.getLogger(__name__)
class LocalId(object): class LocalId(object):
""" """
Class for local ids for non-persisted xblocks Class for local ids for non-persisted xblocks (which can have hardcoded block_ids if necessary)
Should be hashable and distinguishable, but nothing else
""" """
def __init__(self, block_id=None):
self.block_id = block_id
super(LocalId, self).__init__()
def __str__(self): def __str__(self):
return "localid_{}".format(id(self)) return "localid_{}".format(self.block_id or id(self))
class Locator(object): class Locator(object):
......
...@@ -953,7 +953,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -953,7 +953,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
# check children # check children
original_entry = self._get_block_from_structure(original_structure, descriptor.location.block_id) original_entry = self._get_block_from_structure(original_structure, descriptor.location.block_id)
is_updated = is_updated or ( 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 # check metadata
if not is_updated: if not is_updated:
...@@ -992,6 +992,40 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -992,6 +992,40 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
# nothing changed, just return the one sent in # nothing changed, just return the one sent in
return descriptor 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): 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. create or update the xblock and all of its children. The xblock's location must specify a course.
...@@ -1050,8 +1084,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1050,8 +1084,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
# generate an id # generate an id
is_new = True is_new = True
is_updated = True is_updated = True
block_id = self._generate_block_id(structure_blocks, xblock.category) block_id = getattr(xblock.scope_ids.usage_id.block_id, 'block_id', None)
encoded_block_id = block_id 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 xblock.scope_ids.usage_id.block_id = block_id
else: else:
is_new = False is_new = False
......
''' """
Created on Mar 25, 2013 Test split modulestore w/o using any django stuff.
"""
@author: dmitchell
'''
import datetime import datetime
import subprocess
import unittest import unittest
import uuid import uuid
from importlib import import_module from importlib import import_module
from path import path
import re
import random
from xblock.fields import Scope from xblock.fields import Scope
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.exceptions import InsufficientSpecificationError, ItemNotFoundError, VersionConflictError, \ from xmodule.modulestore.exceptions import (InsufficientSpecificationError, ItemNotFoundError, VersionConflictError,
DuplicateItemError, DuplicateCourseError DuplicateItemError, DuplicateCourseError)
from xmodule.modulestore.locator import CourseLocator, BlockUsageLocator, VersionTree, DefinitionLocator from xmodule.modulestore.locator import CourseLocator, BlockUsageLocator, VersionTree, LocalId
from xmodule.modulestore.inheritance import InheritanceMixin from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.x_module import XModuleMixin from xmodule.x_module import XModuleMixin
from pytz import UTC from xmodule.fields import Date, Timedelta
from path import path from bson.objectid import ObjectId
import re
import random
class SplitModuleTest(unittest.TestCase): class SplitModuleTest(unittest.TestCase):
...@@ -52,52 +50,442 @@ class SplitModuleTest(unittest.TestCase): ...@@ -52,52 +50,442 @@ class SplitModuleTest(unittest.TestCase):
modulestore = None modulestore = None
# These version_guids correspond to values hard-coded in fixture files _date_field = Date()
# used for these tests. The files live in mitx/fixtures/splitmongo_json/* _time_delta_field = Timedelta()
COURSE_CONTENT = {
GUID_D0 = "1d00000000000000dddd0000" # v12345d "testx.GreekHero": {
GUID_D1 = "1d00000000000000dddd1111" # v12345d1 "org": "testx",
GUID_D2 = "1d00000000000000dddd2222" # v23456d "root_block_id": "head12345",
GUID_D3 = "1d00000000000000dddd3333" # v12345d0 "user_id": "test@edx.org",
GUID_D4 = "1d00000000000000dddd4444" # v23456d0 "fields": {
GUID_D5 = "1d00000000000000dddd5555" # v345679d "tabs": [
GUID_P = "1d00000000000000eeee0000" # v23456p {
"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 @staticmethod
def bootstrapDB(): def bootstrapDB():
''' '''
Loads the initial data into the db ensuring the collection name is Sets up the initial data into the db
unique.
''' '''
collection_prefix = SplitModuleTest.MODULESTORE['DOC_STORE_CONFIG']['collection'] + '.' split_store = modulestore()
dbname = SplitModuleTest.MODULESTORE['DOC_STORE_CONFIG']['db'] for course_id, course_spec in SplitModuleTest.COURSE_CONTENT.iteritems():
processes = [ course = split_store.create_course(
subprocess.Popen([ course_id, course_spec['org'], course_spec['user_id'],
'mongoimport', '-d', dbname, '-c', fields=course_spec['fields'],
collection_prefix + collection, '--jsonArray', root_block_id=course_spec['root_block_id']
'--file',
SplitModuleTest.COMMON_ROOT + '/test/data/splitmongo_json/' + collection + '.json'
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) )
for collection in ('active_versions', 'structures', 'definitions')] for revision in course_spec.get('revisions', []):
for p in processes: for block_id, fields in revision.get('update', {}).iteritems():
stdout, stderr = p.communicate() # cheat since course is most frequent
if p.returncode != 0: if course.location.block_id == block_id:
print "Couldn't run mongoimport:" block = course
print stdout else:
print stderr block_usage = BlockUsageLocator.make_relative(course.location, block_id)
raise Exception("DB did not init correctly") block = split_store.get_instance(course.location.package_id, block_usage)
for key, value in fields.iteritems():
@classmethod setattr(block, key, value)
def tearDownClass(cls): # 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)
def tearDown(self):
"""
Clear persistence between each test.
"""
collection_prefix = SplitModuleTest.MODULESTORE['DOC_STORE_CONFIG']['collection'] + '.' collection_prefix = SplitModuleTest.MODULESTORE['DOC_STORE_CONFIG']['collection'] + '.'
if SplitModuleTest.modulestore: if SplitModuleTest.modulestore:
for collection in ('active_versions', 'structures', 'definitions'): for collection in ('active_versions', 'structures', 'definitions'):
modulestore().db.drop_collection(collection_prefix + collection) modulestore().db.drop_collection(collection_prefix + collection)
# drop the modulestore to force re init # drop the modulestore to force re init
SplitModuleTest.modulestore = None SplitModuleTest.modulestore = None
super(SplitModuleTest, self).tearDown()
def findByIdInResult(self, collection, _id): def findByIdInResult(self, collection, _id):
""" """
...@@ -120,11 +508,7 @@ class SplitModuleCourseTests(SplitModuleTest): ...@@ -120,11 +508,7 @@ class SplitModuleCourseTests(SplitModuleTest):
self.assertEqual(len(courses), 3, "Wrong number of courses") self.assertEqual(len(courses), 3, "Wrong number of courses")
# check metadata -- NOTE no promised order # check metadata -- NOTE no promised order
course = self.findByIdInResult(courses, "head12345") course = self.findByIdInResult(courses, "head12345")
self.assertEqual(course.location.package_id, "GreekHero") self.assertEqual(course.location.package_id, "testx.GreekHero")
self.assertEqual(
str(course.location.version_guid), self.GUID_D0,
"course version mismatch"
)
self.assertEqual(course.category, 'course', 'wrong category') self.assertEqual(course.category, 'course', 'wrong category')
self.assertEqual(len(course.tabs), 6, "wrong number of tabs") self.assertEqual(len(course.tabs), 6, "wrong number of tabs")
self.assertEqual( self.assertEqual(
...@@ -135,13 +519,9 @@ class SplitModuleCourseTests(SplitModuleTest): ...@@ -135,13 +519,9 @@ class SplitModuleCourseTests(SplitModuleTest):
course.advertised_start, "Fall 2013", course.advertised_start, "Fall 2013",
"advertised_start" "advertised_start"
) )
self.assertEqual( self.assertEqual(len(course.children), 3, "children")
len(course.children), 3,
"children")
self.assertEqual(str(course.definition_locator.definition_id), "ad00000000000000dddd0000")
# check dates and graders--forces loading of descriptor # check dates and graders--forces loading of descriptor
self.assertEqual(course.edited_by, "testassist@edx.org") 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}) self.assertDictEqual(course.grade_cutoffs, {"Pass": 0.45})
def test_branch_requests(self): def test_branch_requests(self):
...@@ -151,9 +531,7 @@ class SplitModuleCourseTests(SplitModuleTest): ...@@ -151,9 +531,7 @@ class SplitModuleCourseTests(SplitModuleTest):
self.assertEqual(len(courses_published), 1, len(courses_published)) self.assertEqual(len(courses_published), 1, len(courses_published))
course = self.findByIdInResult(courses_published, "head23456") course = self.findByIdInResult(courses_published, "head23456")
self.assertIsNotNone(course, "published courses") self.assertIsNotNone(course, "published courses")
self.assertEqual(course.location.package_id, "wonderful") self.assertEqual(course.location.package_id, "testx.wonderful")
self.assertEqual(str(course.location.version_guid), self.GUID_P,
course.location.version_guid)
self.assertEqual(course.category, 'course', 'wrong category') self.assertEqual(course.category, 'course', 'wrong category')
self.assertEqual(len(course.tabs), 4, "wrong number of tabs") self.assertEqual(len(course.tabs), 4, "wrong number of tabs")
self.assertEqual(course.display_name, "The most wonderful course", self.assertEqual(course.display_name, "The most wonderful course",
...@@ -171,34 +549,31 @@ class SplitModuleCourseTests(SplitModuleTest): ...@@ -171,34 +549,31 @@ class SplitModuleCourseTests(SplitModuleTest):
self.assertIsNotNone(self.findByIdInResult(courses, "head12345")) self.assertIsNotNone(self.findByIdInResult(courses, "head12345"))
self.assertIsNotNone(self.findByIdInResult(courses, "head23456")) 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)
def test_get_course(self): def test_get_course(self):
''' '''
Test the various calling forms for get_course 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) course = modulestore().get_course(locator)
self.assertIsNone(course.location.package_id) 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(course.category, 'course')
self.assertEqual(len(course.tabs), 6) self.assertEqual(len(course.tabs), 6)
self.assertEqual(course.display_name, "The Ancient Greek Hero") self.assertEqual(course.display_name, "The Ancient Greek Hero")
self.assertEqual(course.graceperiod, datetime.timedelta(hours=2)) self.assertEqual(course.graceperiod, datetime.timedelta(hours=2))
self.assertIsNone(course.advertised_start) self.assertIsNone(course.advertised_start)
self.assertEqual(len(course.children), 0) 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 # check dates and graders--forces loading of descriptor
self.assertEqual(course.edited_by, "testassist@edx.org") self.assertEqual(course.edited_by, "testassist@edx.org")
self.assertDictEqual(course.grade_cutoffs, {"Pass": 0.55}) 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) course = modulestore().get_course(locator)
self.assertEqual(course.location.package_id, "GreekHero") self.assertEqual(course.location.package_id, "testx.GreekHero")
self.assertEqual(str(course.location.version_guid), self.GUID_D0)
self.assertEqual(course.category, 'course') self.assertEqual(course.category, 'course')
self.assertEqual(len(course.tabs), 6) self.assertEqual(len(course.tabs), 6)
self.assertEqual(course.display_name, "The Ancient Greek Hero") self.assertEqual(course.display_name, "The Ancient Greek Hero")
...@@ -208,14 +583,13 @@ class SplitModuleCourseTests(SplitModuleTest): ...@@ -208,14 +583,13 @@ class SplitModuleCourseTests(SplitModuleTest):
self.assertEqual(course.edited_by, "testassist@edx.org") self.assertEqual(course.edited_by, "testassist@edx.org")
self.assertDictEqual(course.grade_cutoffs, {"Pass": 0.45}) 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) course = modulestore().get_course(locator)
self.assertEqual(course.location.package_id, "wonderful") published_version = course.location.version_guid
self.assertEqual(str(course.location.version_guid), self.GUID_P)
locator = CourseLocator(package_id='wonderful', branch='draft') locator = CourseLocator(package_id='testx.wonderful', branch='draft')
course = modulestore().get_course(locator) 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): def test_get_course_negative(self):
# Now negative testing # Now negative testing
...@@ -225,13 +599,13 @@ class SplitModuleCourseTests(SplitModuleTest): ...@@ -225,13 +599,13 @@ class SplitModuleCourseTests(SplitModuleTest):
modulestore().get_course, CourseLocator(package_id='nosuchthing', branch='draft')) modulestore().get_course, CourseLocator(package_id='nosuchthing', branch='draft'))
self.assertRaises(ItemNotFoundError, self.assertRaises(ItemNotFoundError,
modulestore().get_course, modulestore().get_course,
CourseLocator(package_id='GreekHero', branch='published')) CourseLocator(package_id='testx.GreekHero', branch='published'))
def test_cache(self): def test_cache(self):
""" """
Test that the mechanics of caching work. 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) course = modulestore().get_course(locator)
block_map = modulestore().cache_items(course.system, course.children, depth=3) block_map = modulestore().cache_items(course.system, course.children, depth=3)
self.assertIn('chapter1', block_map) self.assertIn('chapter1', block_map)
...@@ -241,22 +615,32 @@ class SplitModuleCourseTests(SplitModuleTest): ...@@ -241,22 +615,32 @@ class SplitModuleCourseTests(SplitModuleTest):
""" """
get_course_successors(course_locator, version_history_depth=1) 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) result = modulestore().get_course_successors(locator)
self.assertIsInstance(result, VersionTree) self.assertIsInstance(result, VersionTree)
self.assertIsNone(result.locator.package_id) 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(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") self.assertEqual(len(result.children[0].children), 0, "descended more than one level")
result = modulestore().get_course_successors(locator, version_history_depth=2) result = modulestore().get_course_successors(locator, version_history_depth=2)
self.assertEqual(len(result.children), 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), 1) self.assertEqual(len(result.children[0].children), 1)
result = modulestore().get_course_successors(locator, version_history_depth=99) result = modulestore().get_course_successors(locator, version_history_depth=99)
self.assertEqual(len(result.children), 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), 1) self.assertEqual(len(result.children[0].children), 1)
self.assertEqual(result.children[0].children[0].locator.version_guid, versions[0])
class SplitModuleItemTests(SplitModuleTest): class SplitModuleItemTests(SplitModuleTest):
''' '''
...@@ -267,97 +651,98 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -267,97 +651,98 @@ class SplitModuleItemTests(SplitModuleTest):
''' '''
has_item(BlockUsageLocator) 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 # positive tests of various forms
locator = BlockUsageLocator(version_guid=self.GUID_D1, block_id='head12345') locator = BlockUsageLocator(version_guid=previous_version, block_id='head12345')
self.assertTrue(modulestore().has_item(package_id, locator),
"couldn't find in %s" % self.GUID_D1)
locator = BlockUsageLocator(package_id='GreekHero', block_id='head12345', branch='draft')
self.assertTrue( self.assertTrue(
modulestore().has_item(locator.package_id, locator), modulestore().has_item(package_id, locator), "couldn't find in %s" % previous_version
"couldn't find in 12345"
) )
locator = BlockUsageLocator(package_id='testx.GreekHero', block_id='head12345', branch='draft')
self.assertTrue( self.assertTrue(
modulestore().has_item(locator.package_id, BlockUsageLocator( modulestore().has_item(locator.package_id, locator),
package_id=locator.package_id,
branch='draft',
block_id=locator.block_id
)),
"couldn't find in draft 12345"
) )
self.assertFalse( self.assertFalse(
modulestore().has_item(locator.package_id, BlockUsageLocator( modulestore().has_item(locator.package_id, BlockUsageLocator(
package_id=locator.package_id, package_id=locator.package_id,
branch='published', branch='published',
block_id=locator.block_id)), block_id=locator.block_id)),
"found in published 12345" "found in published head"
)
locator.branch = 'draft'
self.assertTrue(
modulestore().has_item(locator.package_id, locator),
"not found in draft 12345"
) )
# not a course obj # 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( self.assertTrue(
modulestore().has_item(locator.package_id, locator), modulestore().has_item(locator.package_id, locator),
"couldn't find chapter1" "couldn't find chapter1"
) )
# in published course # 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( self.assertTrue(
modulestore().has_item( modulestore().has_item(
locator.package_id, locator.package_id,
BlockUsageLocator(package_id=locator.package_id, block_id=locator.block_id, branch='published') BlockUsageLocator(package_id=locator.package_id, block_id=locator.block_id, branch='published')
), "couldn't find in 23456" )
) )
locator.branch = 'published' 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): def test_negative_has_item(self):
# negative tests--not found # negative tests--not found
# no such course or block # no such course or block
package_id = 'GreekHero' package_id = 'testx.GreekHero'
locator = BlockUsageLocator(package_id="doesnotexist", block_id="head23456", branch='draft') locator = BlockUsageLocator(package_id="doesnotexist", block_id="head23456", branch='draft')
self.assertFalse(modulestore().has_item(package_id, locator)) 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)) self.assertFalse(modulestore().has_item(package_id, locator))
# negative tests--insufficient specification # negative tests--insufficient specification
self.assertRaises(InsufficientSpecificationError, BlockUsageLocator) self.assertRaises(InsufficientSpecificationError, BlockUsageLocator)
self.assertRaises(InsufficientSpecificationError,
modulestore().has_item, None, BlockUsageLocator(version_guid=self.GUID_D1)) locator = CourseLocator(package_id=package_id, branch='draft')
self.assertRaises(InsufficientSpecificationError, course = modulestore().get_course(locator)
modulestore().has_item, None, BlockUsageLocator(package_id='GreekHero')) 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): def test_get_item(self):
''' '''
get_item(blocklocator) 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 # 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) block = modulestore().get_item(locator)
self.assertIsInstance(block, CourseDescriptor) self.assertIsInstance(block, CourseDescriptor)
# get_instance just redirects to get_item, ignores package_id # get_instance just redirects to get_item, ignores package_id
self.assertIsInstance(modulestore().get_instance("package_id", locator), CourseDescriptor) self.assertIsInstance(modulestore().get_instance("package_id", locator), CourseDescriptor)
def verify_greek_hero(block): 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(len(block.tabs), 6, "wrong number of tabs")
self.assertEqual(block.display_name, "The Ancient Greek Hero") self.assertEqual(block.display_name, "The Ancient Greek Hero")
self.assertEqual(block.advertised_start, "Fall 2013") self.assertEqual(block.advertised_start, "Fall 2013")
self.assertEqual(len(block.children), 3) self.assertEqual(len(block.children), 3)
self.assertEqual(str(block.definition_locator.definition_id), "ad00000000000000dddd0000")
# check dates and graders--forces loading of descriptor # check dates and graders--forces loading of descriptor
self.assertEqual(block.edited_by, "testassist@edx.org") self.assertEqual(block.edited_by, "testassist@edx.org")
self.assertDictEqual( self.assertDictEqual(
block.grade_cutoffs, {"Pass": 0.45}, 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)) verify_greek_hero(modulestore().get_item(locator))
# get_instance just redirects to get_item, ignores package_id # get_instance just redirects to get_item, ignores package_id
verify_greek_hero(modulestore().get_instance("package_id", locator)) verify_greek_hero(modulestore().get_instance("package_id", locator))
...@@ -376,16 +761,15 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -376,16 +761,15 @@ class SplitModuleItemTests(SplitModuleTest):
def test_get_non_root(self): def test_get_non_root(self):
# not a course obj # 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) 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(block.category, 'chapter')
self.assertEqual(str(block.definition_locator.definition_id), "cd00000000000000dddd0020")
self.assertEqual(block.display_name, "Hercules") self.assertEqual(block.display_name, "Hercules")
self.assertEqual(block.edited_by, "testassist@edx.org") self.assertEqual(block.edited_by, "testassist@edx.org")
# in published course # 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( self.assertIsInstance(
modulestore().get_item(locator), modulestore().get_item(locator),
CourseDescriptor CourseDescriptor
...@@ -396,15 +780,15 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -396,15 +780,15 @@ class SplitModuleItemTests(SplitModuleTest):
locator = BlockUsageLocator(package_id="doesnotexist", block_id="head23456", branch='draft') locator = BlockUsageLocator(package_id="doesnotexist", block_id="head23456", branch='draft')
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
modulestore().get_item(locator) 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): with self.assertRaises(ItemNotFoundError):
modulestore().get_item(locator) modulestore().get_item(locator)
# negative tests--insufficient specification # negative tests--insufficient specification
with self.assertRaises(InsufficientSpecificationError): with self.assertRaises(InsufficientSpecificationError):
modulestore().get_item(BlockUsageLocator(version_guid=self.GUID_D1)) modulestore().get_item(BlockUsageLocator(version_guid=ObjectId()))
with self.assertRaises(InsufficientSpecificationError): 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 # pylint: disable=W0212
def test_matching(self): def test_matching(self):
...@@ -437,7 +821,7 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -437,7 +821,7 @@ class SplitModuleItemTests(SplitModuleTest):
''' '''
get_items(locator, qualifiers, [branch]) get_items(locator, qualifiers, [branch])
''' '''
locator = CourseLocator(version_guid=self.GUID_D0) locator = CourseLocator(package_id="testx.GreekHero", branch='draft')
# get all modules # get all modules
matches = modulestore().get_items(locator) matches = modulestore().get_items(locator)
self.assertEqual(len(matches), 6) self.assertEqual(len(matches), 6)
...@@ -465,11 +849,11 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -465,11 +849,11 @@ class SplitModuleItemTests(SplitModuleTest):
''' '''
get_parent_locations(locator, [block_id], [branch]): [BlockUsageLocator] 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) parents = modulestore().get_parent_locations(locator)
self.assertEqual(len(parents), 1) self.assertEqual(len(parents), 1)
self.assertEqual(parents[0].block_id, 'head12345') 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' locator.block_id = 'chapter2'
parents = modulestore().get_parent_locations(locator) parents = modulestore().get_parent_locations(locator)
self.assertEqual(len(parents), 1) self.assertEqual(len(parents), 1)
...@@ -482,7 +866,7 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -482,7 +866,7 @@ class SplitModuleItemTests(SplitModuleTest):
""" """
Test the existing get_children method on xdescriptors 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) block = modulestore().get_item(locator)
children = block.get_children() children = block.get_children()
expected_ids = [ expected_ids = [
...@@ -524,9 +908,9 @@ class TestItemCrud(SplitModuleTest): ...@@ -524,9 +908,9 @@ class TestItemCrud(SplitModuleTest):
create_item(course_or_parent_locator, category, user, definition_locator=None, fields): new_desciptor create_item(course_or_parent_locator, category, user, definition_locator=None, fields): new_desciptor
""" """
# grab link to course to ensure new versioning works # 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_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 # add minimal one w/o a parent
category = 'sequential' category = 'sequential'
new_module = modulestore().create_item( new_module = modulestore().create_item(
...@@ -534,7 +918,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -534,7 +918,7 @@ class TestItemCrud(SplitModuleTest):
fields={'display_name': 'new sequential'} fields={'display_name': 'new sequential'}
) )
# check that course version changed and course's previous is the other one # 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.assertNotEqual(new_module.location.version_guid, premod_course.location.version_guid)
self.assertIsNone(locator.version_guid, "Version inadvertently filled in") self.assertIsNone(locator.version_guid, "Version inadvertently filled in")
current_course = modulestore().get_course(locator) current_course = modulestore().get_course(locator)
...@@ -542,10 +926,8 @@ class TestItemCrud(SplitModuleTest): ...@@ -542,10 +926,8 @@ class TestItemCrud(SplitModuleTest):
history_info = modulestore().get_course_history_info(current_course.location) history_info = modulestore().get_course_history_info(current_course.location)
self.assertEqual(history_info['previous_version'], premod_course.location.version_guid) 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.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 # check block's info: category, definition_locator, and display_name
self.assertEqual(new_module.category, 'sequential') self.assertEqual(new_module.category, 'sequential')
self.assertIsNotNone(new_module.definition_locator) self.assertIsNotNone(new_module.definition_locator)
...@@ -561,19 +943,22 @@ class TestItemCrud(SplitModuleTest): ...@@ -561,19 +943,22 @@ class TestItemCrud(SplitModuleTest):
""" """
Test create_item w/ specifying the parent of the new item 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) premod_course = modulestore().get_course(locator)
category = 'chapter' category = 'chapter'
new_module = modulestore().create_item( new_module = modulestore().create_item(
locator, category, 'user123', locator, category, 'user123',
fields={'display_name': 'new chapter'}, 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 # 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) self.assertNotEqual(new_module.location.version_guid, premod_course.location.version_guid)
parent = modulestore().get_item(locator) parent = modulestore().get_item(locator)
self.assertIn(new_module.location.block_id, parent.children) 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): def test_unique_naming(self):
""" """
...@@ -581,9 +966,11 @@ class TestItemCrud(SplitModuleTest): ...@@ -581,9 +966,11 @@ class TestItemCrud(SplitModuleTest):
a definition id and new def data that it branches the definition in the db. 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. 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' category = 'problem'
premod_time = datetime.datetime.now(UTC) - datetime.timedelta(seconds=1)
new_payload = "<problem>empty</problem>" new_payload = "<problem>empty</problem>"
new_module = modulestore().create_item( new_module = modulestore().create_item(
locator, category, 'anotheruser', locator, category, 'anotheruser',
...@@ -593,7 +980,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -593,7 +980,7 @@ class TestItemCrud(SplitModuleTest):
another_module = modulestore().create_item( another_module = modulestore().create_item(
locator, category, 'anotheruser', locator, category, 'anotheruser',
fields={'display_name': 'problem 2', 'data': another_payload}, 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 # check that course version changed and course's previous is the other one
parent = modulestore().get_item(locator) parent = modulestore().get_item(locator)
...@@ -607,17 +994,15 @@ class TestItemCrud(SplitModuleTest): ...@@ -607,17 +994,15 @@ class TestItemCrud(SplitModuleTest):
self.assertIsNone(new_history['previous_version']) self.assertIsNone(new_history['previous_version'])
self.assertEqual(new_history['original_version'], new_module.definition_locator.definition_id) self.assertEqual(new_history['original_version'], new_module.definition_locator.definition_id)
self.assertEqual(new_history['edited_by'], "anotheruser") 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) 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): def test_encoded_naming(self):
""" """
Check that using odd characters in block id don't break ability to add and retrieve block. 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') parent_locator = BlockUsageLocator(package_id="guestx.contender", block_id="head345679", branch='draft')
chapter_locator = BlockUsageLocator(package_id="contender", block_id="foo.bar_-~:0", branch='draft') chapter_locator = BlockUsageLocator(package_id="guestx.contender", block_id="foo.bar_-~:0", branch='draft')
modulestore().create_item( modulestore().create_item(
parent_locator, 'chapter', 'anotheruser', parent_locator, 'chapter', 'anotheruser',
block_id=chapter_locator.block_id, block_id=chapter_locator.block_id,
...@@ -628,7 +1013,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -628,7 +1013,7 @@ class TestItemCrud(SplitModuleTest):
self.assertEqual(new_module.location.block_id, "foo.bar_-~:0") # hardcode to ensure BUL init didn't change 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 # now try making that a parent of something
new_payload = "<problem>empty</problem>" 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( modulestore().create_item(
chapter_locator, 'problem', 'anotheruser', chapter_locator, 'problem', 'anotheruser',
block_id=problem_locator.block_id, block_id=problem_locator.block_id,
...@@ -729,13 +1114,12 @@ class TestItemCrud(SplitModuleTest): ...@@ -729,13 +1114,12 @@ class TestItemCrud(SplitModuleTest):
""" """
test updating an items metadata ensuring the definition doesn't version but the course does if it should 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) problem = modulestore().get_item(locator)
pre_def_id = problem.definition_locator.definition_id pre_def_id = problem.definition_locator.definition_id
pre_version_guid = problem.location.version_guid pre_version_guid = problem.location.version_guid
self.assertIsNotNone(pre_def_id) self.assertIsNotNone(pre_def_id)
self.assertIsNotNone(pre_version_guid) 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") self.assertNotEqual(problem.max_attempts, 4, "Invalidates rest of test")
problem.max_attempts = 4 problem.max_attempts = 4
...@@ -758,16 +1142,13 @@ class TestItemCrud(SplitModuleTest): ...@@ -758,16 +1142,13 @@ class TestItemCrud(SplitModuleTest):
history_info = modulestore().get_course_history_info(current_course.location) history_info = modulestore().get_course_history_info(current_course.location)
self.assertEqual(history_info['previous_version'], pre_version_guid) 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.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): def test_update_children(self):
""" """
test updating an item's children ensuring the definition doesn't version but the course does if it should 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) block = modulestore().get_item(locator)
pre_def_id = block.definition_locator.definition_id pre_def_id = block.definition_locator.definition_id
pre_version_guid = block.location.version_guid pre_version_guid = block.location.version_guid
...@@ -793,7 +1174,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -793,7 +1174,7 @@ class TestItemCrud(SplitModuleTest):
""" """
test updating an item's definition: ensure it gets versioned as well as the course getting versioned 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) block = modulestore().get_item(locator)
pre_def_id = block.definition_locator.definition_id pre_def_id = block.definition_locator.definition_id
pre_version_guid = block.location.version_guid pre_version_guid = block.location.version_guid
...@@ -810,8 +1191,10 @@ class TestItemCrud(SplitModuleTest): ...@@ -810,8 +1191,10 @@ class TestItemCrud(SplitModuleTest):
""" """
Test updating metadata, children, and definition in a single call ensuring all the versioning occurs 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 # 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' category = 'problem'
new_payload = "<problem>empty</problem>" new_payload = "<problem>empty</problem>"
modulestore().create_item( modulestore().create_item(
...@@ -822,7 +1205,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -822,7 +1205,7 @@ class TestItemCrud(SplitModuleTest):
modulestore().create_item( modulestore().create_item(
locator, category, 'test_update_manifold', locator, category, 'test_update_manifold',
fields={'display_name': 'problem 2', 'data': another_payload}, fields={'display_name': 'problem 2', 'data': another_payload},
definition_locator=DefinitionLocator("0d00000040000000dddd0031"), definition_locator=original.definition_locator,
) )
# pylint: disable=W0212 # pylint: disable=W0212
modulestore()._clear_cache() modulestore()._clear_cache()
...@@ -878,6 +1261,9 @@ class TestItemCrud(SplitModuleTest): ...@@ -878,6 +1261,9 @@ class TestItemCrud(SplitModuleTest):
# check subtree # check subtree
def check_subtree(node): def check_subtree(node):
"""
Check contents of subtree recursively
"""
if node: if node:
node_loc = node.location node_loc = node.location
self.assertFalse(modulestore().has_item(reusable_location.package_id, self.assertFalse(modulestore().has_item(reusable_location.package_id,
...@@ -895,6 +1281,9 @@ class TestItemCrud(SplitModuleTest): ...@@ -895,6 +1281,9 @@ class TestItemCrud(SplitModuleTest):
check_subtree(nodes[0]) check_subtree(nodes[0])
def create_course_for_deletion(self): def create_course_for_deletion(self):
"""
Create a course we can delete
"""
course = modulestore().create_course('nihilx.deletion', 'nihilx', 'deleting_user') course = modulestore().create_course('nihilx.deletion', 'nihilx', 'deleting_user')
root = BlockUsageLocator( root = BlockUsageLocator(
package_id=course.location.package_id, package_id=course.location.package_id,
...@@ -905,6 +1294,9 @@ class TestItemCrud(SplitModuleTest): ...@@ -905,6 +1294,9 @@ class TestItemCrud(SplitModuleTest):
return modulestore().get_item(root) return modulestore().get_item(root)
def create_subtree_for_deletion(self, parent, category_queue): def create_subtree_for_deletion(self, parent, category_queue):
"""
Create a subtree in the tb deleted course
"""
if not category_queue: if not category_queue:
return return
node = modulestore().create_item(parent, category_queue[0], 'deleting_user') node = modulestore().create_item(parent, category_queue[0], 'deleting_user')
...@@ -915,28 +1307,23 @@ class TestItemCrud(SplitModuleTest): ...@@ -915,28 +1307,23 @@ class TestItemCrud(SplitModuleTest):
class TestCourseCreation(SplitModuleTest): class TestCourseCreation(SplitModuleTest):
""" """
Test create_course, duh :-) Test create_course
""" """
def test_simple_creation(self): def test_simple_creation(self):
""" """
The simplest case but probing all expected results from it. The simplest case but probing all expected results from it.
""" """
# Oddly getting differences of 200nsec # 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', 'create_user') new_course = modulestore().create_course('test_org.test_course', 'test_org', 'create_user')
new_locator = new_course.location new_locator = new_course.location
# check index entry # check index entry
index_info = modulestore().get_course_index_info(new_locator) index_info = modulestore().get_course_index_info(new_locator)
self.assertEqual(index_info['org'], 'test_org') self.assertEqual(index_info['org'], 'test_org')
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') self.assertEqual(index_info['edited_by'], 'create_user')
# check structure info # check structure info
structure_info = modulestore().get_course_history_info(new_locator) structure_info = modulestore().get_course_history_info(new_locator)
self.assertEqual(structure_info['original_version'], index_info['versions']['draft']) self.assertEqual(structure_info['original_version'], index_info['versions']['draft'])
self.assertIsNone(structure_info['previous_version']) 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') self.assertEqual(structure_info['edited_by'], 'create_user')
# check the returned course object # check the returned course object
self.assertIsInstance(new_course, CourseDescriptor) self.assertIsInstance(new_course, CourseDescriptor)
...@@ -952,8 +1339,7 @@ class TestCourseCreation(SplitModuleTest): ...@@ -952,8 +1339,7 @@ class TestCourseCreation(SplitModuleTest):
""" """
Test making a course which points to an existing draft and published but not making any changes to either. 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="testx.wonderful", branch='draft')
original_locator = CourseLocator(package_id="wonderful", branch='draft')
original_index = modulestore().get_course_index_info(original_locator) original_index = modulestore().get_course_index_info(original_locator)
new_draft = modulestore().create_course( new_draft = modulestore().create_course(
'best', 'leech', 'leech_master', 'best', 'leech', 'leech_master',
...@@ -962,18 +1348,14 @@ class TestCourseCreation(SplitModuleTest): ...@@ -962,18 +1348,14 @@ class TestCourseCreation(SplitModuleTest):
self.assertRegexpMatches(new_draft_locator.package_id, 'best') 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 # 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.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']) 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 # 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) 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_index['edited_by'], 'leech_master')
new_published_locator = CourseLocator(package_id=new_draft_locator.package_id, branch='published') new_published_locator = CourseLocator(package_id=new_draft_locator.package_id, branch='published')
new_published = modulestore().get_course(new_published_locator) new_published = modulestore().get_course(new_published_locator)
self.assertEqual(new_published.edited_by, 'test@edx.org') 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']) self.assertEqual(new_published.location.version_guid, original_index['versions']['published'])
# changing this course will not change the original course # changing this course will not change the original course
...@@ -987,12 +1369,9 @@ class TestCourseCreation(SplitModuleTest): ...@@ -987,12 +1369,9 @@ class TestCourseCreation(SplitModuleTest):
self.assertNotEqual(new_index['versions']['draft'], original_index['versions']['draft']) self.assertNotEqual(new_index['versions']['draft'], original_index['versions']['draft'])
new_draft = modulestore().get_course(new_draft_locator) new_draft = modulestore().get_course(new_draft_locator)
self.assertEqual(new_item.edited_by, 'leech_master') 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_item.location.version_guid, original_index['versions']['draft'])
self.assertNotEqual(new_draft.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) 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') self.assertEqual(structure_info['edited_by'], 'leech_master')
original_course = modulestore().get_course(original_locator) original_course = modulestore().get_course(original_locator)
...@@ -1008,8 +1387,7 @@ class TestCourseCreation(SplitModuleTest): ...@@ -1008,8 +1387,7 @@ class TestCourseCreation(SplitModuleTest):
""" """
Create a new course which overrides metadata and course_data Create a new course which overrides metadata and course_data
""" """
pre_time = datetime.datetime.now(UTC) original_locator = CourseLocator(package_id="guestx.contender", branch='draft')
original_locator = CourseLocator(package_id="contender", branch='draft')
original = modulestore().get_course(original_locator) original = modulestore().get_course(original_locator)
original_index = modulestore().get_course_index_info(original_locator) original_index = modulestore().get_course_index_info(original_locator)
fields = {} fields = {}
...@@ -1029,12 +1407,9 @@ class TestCourseCreation(SplitModuleTest): ...@@ -1029,12 +1407,9 @@ class TestCourseCreation(SplitModuleTest):
self.assertRegexpMatches(new_draft_locator.package_id, 'counter') 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 # 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.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']) 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 # 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) 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_index['edited_by'], 'leech_master')
self.assertEqual(new_draft.display_name, fields['display_name']) self.assertEqual(new_draft.display_name, fields['display_name'])
self.assertDictEqual( self.assertDictEqual(
...@@ -1046,7 +1421,7 @@ class TestCourseCreation(SplitModuleTest): ...@@ -1046,7 +1421,7 @@ class TestCourseCreation(SplitModuleTest):
""" """
Test changing the org, pretty id, etc of a course. Test that it doesn't allow changing the id, etc. 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 = modulestore().get_course_index_info(locator)
course_info['org'] = 'funkyU' course_info['org'] = 'funkyU'
modulestore().update_course_index(course_info) modulestore().update_course_index(course_info)
...@@ -1059,17 +1434,18 @@ class TestCourseCreation(SplitModuleTest): ...@@ -1059,17 +1434,18 @@ class TestCourseCreation(SplitModuleTest):
self.assertEqual(course_info['org'], 'moreFunky') self.assertEqual(course_info['org'], 'moreFunky')
# an allowed but not necessarily recommended way to revert the draft version # an allowed but not necessarily recommended way to revert the draft version
head_course = modulestore().get_course(locator)
versions = course_info['versions'] versions = course_info['versions']
versions['draft'] = self.GUID_D1 versions['draft'] = head_course.previous_version
modulestore().update_course_index(course_info) modulestore().update_course_index(course_info)
course = modulestore().get_course(locator) 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 # 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) modulestore().update_course_index(course_info)
course = modulestore().get_course(CourseLocator(package_id=locator.package_id, branch="published")) 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): def test_create_with_root(self):
""" """
...@@ -1111,11 +1487,11 @@ class TestInheritance(SplitModuleTest): ...@@ -1111,11 +1487,11 @@ class TestInheritance(SplitModuleTest):
""" """
# Note, not testing value where defined (course) b/c there's no # Note, not testing value where defined (course) b/c there's no
# defined accessor for it on CourseDescriptor. # 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) node = modulestore().get_item(locator)
# inherited # inherited
self.assertEqual(node.graceperiod, datetime.timedelta(hours=2)) 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) node = modulestore().get_item(locator)
# overridden # overridden
self.assertEqual(node.graceperiod, datetime.timedelta(hours=4)) self.assertEqual(node.graceperiod, datetime.timedelta(hours=4))
...@@ -1130,14 +1506,14 @@ class TestPublish(SplitModuleTest): ...@@ -1130,14 +1506,14 @@ class TestPublish(SplitModuleTest):
self.user = random.getrandbits(32) self.user = random.getrandbits(32)
def tearDown(self): def tearDown(self):
SplitModuleTest.tearDownClass() SplitModuleTest.tearDown(self)
def test_publish_safe(self): def test_publish_safe(self):
""" """
Test the standard patterns: publish to new branch, revise and publish Test the standard patterns: publish to new branch, revise and publish
""" """
source_course = CourseLocator(package_id="GreekHero", branch='draft') source_course = CourseLocator(package_id="testx.GreekHero", branch='draft')
dest_course = CourseLocator(package_id="GreekHero", branch="published") dest_course = CourseLocator(package_id="testx.GreekHero", branch="published")
modulestore().xblock_publish(self.user, source_course, dest_course, ["head12345"], ["chapter2", "chapter3"]) modulestore().xblock_publish(self.user, source_course, dest_course, ["head12345"], ["chapter2", "chapter3"])
expected = ["head12345", "chapter1"] expected = ["head12345", "chapter1"]
self._check_course( self._check_course(
...@@ -1145,19 +1521,19 @@ class TestPublish(SplitModuleTest): ...@@ -1145,19 +1521,19 @@ class TestPublish(SplitModuleTest):
) )
# add a child under chapter1 # add a child under chapter1
new_module = modulestore().create_item( 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'}, fields={'display_name': 'new sequential'},
) )
# remove chapter1 from expected b/c its pub'd version != the source anymore since source changed # remove chapter1 from expected b/c its pub'd version != the source anymore since source changed
expected.remove("chapter1") expected.remove("chapter1")
# check that it's not in published course # check that it's not in published course
with self.assertRaises(ItemNotFoundError): 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 # publish it
modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None) modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None)
expected.append(new_module.location.block_id) expected.append(new_module.location.block_id)
# check that it is in the published course and that its parent is the chapter # 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( self.assertEqual(
modulestore().get_parent_locations(pub_module.location)[0].block_id, "chapter1" modulestore().get_parent_locations(pub_module.location)[0].block_id, "chapter1"
) )
...@@ -1169,7 +1545,7 @@ class TestPublish(SplitModuleTest): ...@@ -1169,7 +1545,7 @@ class TestPublish(SplitModuleTest):
modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None) modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None)
expected.append(new_module.location.block_id) expected.append(new_module.location.block_id)
# check that it is in the published course (no error means it worked) # 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( self._check_course(
source_course, dest_course, expected, ["chapter2", "chapter3", "problem1", "problem3_2"] source_course, dest_course, expected, ["chapter2", "chapter3", "problem1", "problem3_2"]
) )
...@@ -1178,13 +1554,13 @@ class TestPublish(SplitModuleTest): ...@@ -1178,13 +1554,13 @@ class TestPublish(SplitModuleTest):
""" """
Test the exceptions which preclude successful publication 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 does not exist
destination_course = CourseLocator(package_id="Unknown", branch="published") destination_course = CourseLocator(package_id="Unknown", branch="published")
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
modulestore().xblock_publish(self.user, source_course, destination_course, ["chapter3"], None) modulestore().xblock_publish(self.user, source_course, destination_course, ["chapter3"], None)
# publishing into a new branch w/o publishing the root # 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): with self.assertRaises(ItemNotFoundError):
modulestore().xblock_publish(self.user, source_course, destination_course, ["chapter3"], None) modulestore().xblock_publish(self.user, source_course, destination_course, ["chapter3"], None)
# publishing a subdag w/o the parent already in course # publishing a subdag w/o the parent already in course
...@@ -1196,17 +1572,17 @@ class TestPublish(SplitModuleTest): ...@@ -1196,17 +1572,17 @@ class TestPublish(SplitModuleTest):
""" """
Test publishing moves and deletes. Test publishing moves and deletes.
""" """
source_course = CourseLocator(package_id="GreekHero", branch='draft') source_course = CourseLocator(package_id="testx.GreekHero", branch='draft')
dest_course = CourseLocator(package_id="GreekHero", branch="published") dest_course = CourseLocator(package_id="testx.GreekHero", branch="published")
modulestore().xblock_publish(self.user, source_course, dest_course, ["head12345"], ["chapter2"]) modulestore().xblock_publish(self.user, source_course, dest_course, ["head12345"], ["chapter2"])
expected = ["head12345", "chapter1", "chapter3", "problem1", "problem3_2"] expected = ["head12345", "chapter1", "chapter3", "problem1", "problem3_2"]
self._check_course(source_course, dest_course, expected, ["chapter2"]) self._check_course(source_course, dest_course, expected, ["chapter2"])
# now move problem1 and delete problem3_2 # now move problem1 and delete problem3_2
chapter1 = modulestore().get_item(self._usage(source_course, "chapter1")) chapter1 = modulestore().get_item(BlockUsageLocator.make_relative(source_course, "chapter1"))
chapter3 = modulestore().get_item(self._usage(source_course, "chapter3")) chapter3 = modulestore().get_item(BlockUsageLocator.make_relative(source_course, "chapter3"))
chapter1.children.append("problem1") chapter1.children.append("problem1")
chapter3.children.remove("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"]) modulestore().xblock_publish(self.user, source_course, dest_course, ["head12345"], ["chapter2"])
expected = ["head12345", "chapter1", "chapter3", "problem1"] expected = ["head12345", "chapter1", "chapter3", "problem1"]
self._check_course(source_course, dest_course, expected, ["chapter2", "problem3_2"]) self._check_course(source_course, dest_course, expected, ["chapter2", "problem3_2"])
...@@ -1215,13 +1591,18 @@ class TestPublish(SplitModuleTest): ...@@ -1215,13 +1591,18 @@ class TestPublish(SplitModuleTest):
""" """
Check that the course has the expected blocks and does not have the unexpected blocks 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: for expected in expected_blocks:
source = modulestore().get_item(self._usage(source_course_loc, expected)) source = modulestore().get_item(BlockUsageLocator.make_relative(source_course_loc, expected))
pub_copy = modulestore().get_item(self._usage(dest_course_loc, expected)) pub_copy = modulestore().get_item(BlockUsageLocator.make_relative(dest_course_loc, expected))
# everything except previous_version & children should be the same # everything except previous_version & children should be the same
self.assertEqual(source.category, pub_copy.category) self.assertEqual(source.category, pub_copy.category)
self.assertEqual(source.update_version, pub_copy.update_version) 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(): for field in source.fields.values():
if field.name == 'children': if field.name == 'children':
self._compare_children(field.read_from(source), field.read_from(pub_copy), unexpected_blocks) self._compare_children(field.read_from(source), field.read_from(pub_copy), unexpected_blocks)
...@@ -1229,13 +1610,7 @@ class TestPublish(SplitModuleTest): ...@@ -1229,13 +1610,7 @@ class TestPublish(SplitModuleTest):
self.assertEqual(field.read_from(source), field.read_from(pub_copy)) self.assertEqual(field.read_from(source), field.read_from(pub_copy))
for unexp in unexpected_blocks: for unexp in unexpected_blocks:
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
modulestore().get_item(self._usage(dest_course_loc, unexp)) modulestore().get_item(BlockUsageLocator.make_relative(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)
def _compare_children(self, source_children, dest_children, unexpected): def _compare_children(self, source_children, dest_children, unexpected):
""" """
...@@ -1252,15 +1627,18 @@ class TestPublish(SplitModuleTest): ...@@ -1252,15 +1627,18 @@ class TestPublish(SplitModuleTest):
#=========================================== #===========================================
# This mocks the django.modulestore() function and is intended purely to disentangle
# the tests from django
def modulestore(): def modulestore():
"""
Mock the django dependent global modulestore function to disentangle tests from django
"""
def load_function(engine_path): def load_function(engine_path):
"""
Load the given engine
"""
module_path, _, name = engine_path.rpartition('.') module_path, _, name = engine_path.rpartition('.')
return getattr(import_module(module_path), name) return getattr(import_module(module_path), name)
if SplitModuleTest.modulestore is None: if SplitModuleTest.modulestore is None:
SplitModuleTest.bootstrapDB()
class_ = load_function(SplitModuleTest.MODULESTORE['ENGINE']) class_ = load_function(SplitModuleTest.MODULESTORE['ENGINE'])
options = {} options = {}
...@@ -1274,6 +1652,8 @@ def modulestore(): ...@@ -1274,6 +1652,8 @@ def modulestore():
**options **options
) )
SplitModuleTest.bootstrapDB()
return SplitModuleTest.modulestore return SplitModuleTest.modulestore
......
[{"_id" : "GreekHero",
"org" : "testx",
"versions" : {
"draft" : { "$oid" : "1d00000000000000dddd0000" }
},
"edited_on" : {"$date" : 1364481713238},
"edited_by" : "test@edx.org"},
{"_id" : "wonderful",
"org" : "testx",
"versions" : {
"draft" : { "$oid" : "1d00000000000000dddd2222" },
"published" : { "$oid" : "1d00000000000000eeee0000" }
},
"edited_on" : {"$date" : 1364481313238},
"edited_by" : "test@edx.org"},
{"_id" : "contender",
"org" : "guestx",
"versions" : {
"draft" : { "$oid" : "1d00000000000000dddd5555" }},
"edited_on" : {"$date" : 1364491313238},
"edited_by" : "test@guestx.edu"}
]
[
{
"_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"}
}
}
]
\ No newline at end of file
[
{
"_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
}
}
}
}
}
]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment