Merge branch 'master' of github.com:MITx/mitx into fix/cdodge/export-draft-modules
Conflicts: common/lib/xmodule/xmodule/modulestore/draft.py
This commit is contained in:
@@ -108,6 +108,22 @@ def add_histogram(get_html, module, user):
|
||||
histogram = grade_histogram(module_id)
|
||||
render_histogram = len(histogram) > 0
|
||||
|
||||
if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
|
||||
[filepath, filename] = getattr(module.descriptor, 'xml_attributes', {}).get('filename', ['', None])
|
||||
osfs = module.system.filestore
|
||||
if filename is not None and osfs.exists(filename):
|
||||
# if original, unmangled filename exists then use it (github
|
||||
# doesn't like symlinks)
|
||||
filepath = filename
|
||||
data_dir = osfs.root_path.rsplit('/')[-1]
|
||||
giturl = getattr(module.lms, 'giturl', '') or 'https://github.com/MITx'
|
||||
edit_link = "%s/%s/tree/master/%s" % (giturl, data_dir, filepath)
|
||||
else:
|
||||
edit_link = False
|
||||
# Need to define all the variables that are about to be used
|
||||
giturl = ""
|
||||
data_dir = ""
|
||||
|
||||
source_file = module.lms.source_file # source used to generate the problem XML, eg latex or word
|
||||
|
||||
# useful to indicate to staff if problem has been released or not
|
||||
@@ -121,12 +137,15 @@ def add_histogram(get_html, module, user):
|
||||
|
||||
staff_context = {'fields': [(field.name, getattr(module, field.name)) for field in module.fields],
|
||||
'lms_fields': [(field.name, getattr(module.lms, field.name)) for field in module.lms.fields],
|
||||
'xml_attributes' : getattr(module.descriptor, 'xml_attributes', {}),
|
||||
'location': module.location,
|
||||
'xqa_key': module.lms.xqa_key,
|
||||
'source_file': source_file,
|
||||
'source_url': '%s/%s/tree/master/%s' % (giturl, data_dir, source_file),
|
||||
'category': str(module.__class__.__name__),
|
||||
# Template uses element_id in js function names, so can't allow dashes
|
||||
'element_id': module.location.html_id().replace('-', '_'),
|
||||
'edit_link': edit_link,
|
||||
'user': user,
|
||||
'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER', 'http://xqa:server@content-qa.mitx.mit.edu/xqa'),
|
||||
'histogram': json.dumps(histogram),
|
||||
|
||||
@@ -180,6 +180,7 @@ class CourseFields(object):
|
||||
has_children = True
|
||||
checklists = List(scope=Scope.settings)
|
||||
info_sidebar_name = String(scope=Scope.settings, default='Course Handouts')
|
||||
show_timezone = Boolean(help="True if timezones should be shown on dates in the courseware", scope=Scope.settings, default=True)
|
||||
|
||||
# An extra property is used rather than the wiki_slug/number because
|
||||
# there are courses that change the number for different runs. This allows
|
||||
|
||||
@@ -2,7 +2,7 @@ from datetime import datetime
|
||||
|
||||
from . import ModuleStoreBase, Location, namedtuple_to_son
|
||||
from .exceptions import ItemNotFoundError
|
||||
import logging
|
||||
from .inheritance import own_metadata
|
||||
|
||||
DRAFT = 'draft'
|
||||
|
||||
@@ -118,7 +118,7 @@ class DraftModuleStore(ModuleStoreBase):
|
||||
try:
|
||||
draft_item = self.get_item(location)
|
||||
if not getattr(draft_item, 'is_draft', False):
|
||||
self.clone_item(location, draft_loc)
|
||||
self.clone_item(location, draft_loc)
|
||||
except ItemNotFoundError, e:
|
||||
if not allow_not_found:
|
||||
raise e
|
||||
@@ -167,7 +167,6 @@ class DraftModuleStore(ModuleStoreBase):
|
||||
"""
|
||||
return super(DraftModuleStore, self).delete_item(as_draft(location))
|
||||
|
||||
|
||||
def get_parent_locations(self, location, course_id):
|
||||
'''Find all locations that are the parents of this location. Needed
|
||||
for path_to_location().
|
||||
@@ -186,8 +185,7 @@ class DraftModuleStore(ModuleStoreBase):
|
||||
draft.cms.published_by = published_by_id
|
||||
super(DraftModuleStore, self).update_item(location, draft._model_data._kvs._data)
|
||||
super(DraftModuleStore, self).update_children(location, draft._model_data._kvs._children)
|
||||
super(DraftModuleStore, self).update_metadata(location, draft._model_data._kvs._metadata)
|
||||
|
||||
super(DraftModuleStore, self).update_metadata(location, own_metadata(draft))
|
||||
self.delete_item(location)
|
||||
|
||||
def unpublish(self, location):
|
||||
@@ -226,6 +224,6 @@ class DraftModuleStore(ModuleStoreBase):
|
||||
|
||||
# convert the dict - which is used for look ups - back into a list
|
||||
for key, value in to_process_dict.iteritems():
|
||||
queried_children.append(value)
|
||||
queried_children.append(value)
|
||||
|
||||
return queried_children
|
||||
|
||||
@@ -8,7 +8,8 @@ INHERITABLE_METADATA = (
|
||||
# How many days early to show a course element to beta testers (float)
|
||||
# intended to be set per-course, but can be overridden in for specific
|
||||
# elements. Can be a float.
|
||||
'days_early_for_beta'
|
||||
'days_early_for_beta',
|
||||
'giturl' # for git edit link
|
||||
)
|
||||
|
||||
def compute_inherited_metadata(descriptor):
|
||||
|
||||
@@ -171,7 +171,10 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
model_data = DbModel(kvs, class_, None, MongoUsage(self.course_id, location))
|
||||
module = class_(self, location, model_data)
|
||||
if self.cached_metadata is not None:
|
||||
metadata_to_inherit = self.cached_metadata.get(location.url(), {})
|
||||
# parent container pointers don't differentiate between draft and non-draft
|
||||
# so when we do the lookup, we should do so with a non-draft location
|
||||
non_draft_loc = location._replace(revision=None)
|
||||
metadata_to_inherit = self.cached_metadata.get(non_draft_loc.url(), {})
|
||||
inherit_metadata(module, metadata_to_inherit)
|
||||
return module
|
||||
except:
|
||||
@@ -255,11 +258,11 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
|
||||
# get all collections in the course, this query should not return any leaf nodes
|
||||
# note this is a bit ugly as when we add new categories of containers, we have to add it here
|
||||
query = {
|
||||
'_id.org': location.org,
|
||||
'_id.course': location.course,
|
||||
'_id.category': {'$in': ['course', 'chapter', 'sequential', 'vertical']}
|
||||
}
|
||||
query = {'_id.org': location.org,
|
||||
'_id.course': location.course,
|
||||
'_id.category': {'$in': ['course', 'chapter', 'sequential', 'vertical',
|
||||
'wrapper', 'problemset', 'conditional']}
|
||||
}
|
||||
# we just want the Location, children, and inheritable metadata
|
||||
record_filter = {'_id': 1, 'definition.children': 1}
|
||||
|
||||
@@ -277,6 +280,17 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
# now go through the results and order them by the location url
|
||||
for result in resultset:
|
||||
location = Location(result['_id'])
|
||||
# We need to collate between draft and non-draft
|
||||
# i.e. draft verticals can have children which are not in non-draft versions
|
||||
location = location._replace(revision=None)
|
||||
location_url = location.url()
|
||||
if location_url in results_by_url:
|
||||
existing_children = results_by_url[location_url].get('definition', {}).get('children', [])
|
||||
additional_children = result.get('definition', {}).get('children', [])
|
||||
total_children = existing_children + additional_children
|
||||
if 'definition' not in results_by_url[location_url]:
|
||||
results_by_url[location_url]['definition'] = {}
|
||||
results_by_url[location_url]['definition']['children'] = total_children
|
||||
results_by_url[location.url()] = result
|
||||
if location.category == 'course':
|
||||
root = location.url()
|
||||
@@ -288,17 +302,12 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
"""
|
||||
Helper method for computing inherited metadata for a specific location url
|
||||
"""
|
||||
my_metadata = {}
|
||||
# check for presence of metadata key. Note that a given module may not yet be fully formed.
|
||||
# example: update_item -> update_children -> update_metadata sequence on new item create
|
||||
# if we get called here without update_metadata called first then 'metadata' hasn't been set
|
||||
# as we're not fully transactional at the DB layer. Same comment applies to below key name
|
||||
# check
|
||||
my_metadata = results_by_url[url].get('metadata', {})
|
||||
for key in my_metadata.keys():
|
||||
if key not in INHERITABLE_METADATA:
|
||||
del my_metadata[key]
|
||||
results_by_url[url]['metadata'] = my_metadata
|
||||
|
||||
# go through all the children and recurse, but only if we have
|
||||
# in the result set. Remember results will not contain leaf nodes
|
||||
|
||||
@@ -5,6 +5,7 @@ from xmodule.util import date_utils
|
||||
import datetime
|
||||
import time
|
||||
|
||||
|
||||
def test_get_time_struct_display():
|
||||
assert_equals("", date_utils.get_time_struct_display(None, ""))
|
||||
test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0))
|
||||
@@ -15,12 +16,20 @@ def test_get_time_struct_display():
|
||||
def test_get_default_time_display():
|
||||
assert_equals("", date_utils.get_default_time_display(None))
|
||||
test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0))
|
||||
assert_equals("Mar 12, 1992 at 03:03 PM",
|
||||
assert_equals(
|
||||
"Mar 12, 1992 at 15:03 UTC",
|
||||
date_utils.get_default_time_display(test_time))
|
||||
assert_equals(
|
||||
"Mar 12, 1992 at 15:03 UTC",
|
||||
date_utils.get_default_time_display(test_time, True))
|
||||
assert_equals(
|
||||
"Mar 12, 1992 at 15:03",
|
||||
date_utils.get_default_time_display(test_time, False))
|
||||
|
||||
|
||||
def test_time_to_datetime():
|
||||
assert_equals(None, date_utils.time_to_datetime(None))
|
||||
test_time = time.struct_time((1992, 3, 12, 15, 3, 30, 1, 71, 0))
|
||||
assert_equals(datetime.datetime(1992, 3, 12, 15, 3, 30),
|
||||
assert_equals(
|
||||
datetime.datetime(1992, 3, 12, 15, 3, 30),
|
||||
date_utils.time_to_datetime(test_time))
|
||||
|
||||
@@ -2,15 +2,18 @@ import time
|
||||
import datetime
|
||||
|
||||
|
||||
def get_default_time_display(time_struct):
|
||||
def get_default_time_display(time_struct, show_timezone=True):
|
||||
"""
|
||||
Converts a time struct to a string representation. This is the default
|
||||
representation used in Studio and LMS.
|
||||
It is of the form "Apr 09, 2013 at 04:00 PM".
|
||||
It is of the form "Apr 09, 2013 at 16:00" or "Apr 09, 2013 at 16:00 UTC",
|
||||
depending on the value of show_timezone.
|
||||
|
||||
If None is passed in, an empty string will be returned.
|
||||
If None is passed in for time_struct, an empty string will be returned.
|
||||
The default value of show_timezone is True.
|
||||
"""
|
||||
return get_time_struct_display(time_struct, "%b %d, %Y at %I:%M %p")
|
||||
timezone = "" if time_struct is None or not show_timezone else " UTC"
|
||||
return get_time_struct_display(time_struct, "%b %d, %Y at %H:%M") + timezone
|
||||
|
||||
|
||||
def get_time_struct_display(time_struct, format):
|
||||
|
||||
@@ -102,6 +102,7 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
'start', 'due', 'graded', 'display_name', 'url_name', 'hide_from_toc',
|
||||
'ispublic', # if True, then course is listed for all users; see
|
||||
'xqa_key', # for xqaa server access
|
||||
'giturl', # url of git server for origin of file
|
||||
# information about testcenter exams is a dict (of dicts), not a string,
|
||||
# so it cannot be easily exportable as a course element's attribute.
|
||||
'testcenter_info',
|
||||
@@ -221,6 +222,7 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
definition, children = cls.definition_from_xml(definition_xml, system)
|
||||
if definition_metadata:
|
||||
definition['definition_metadata'] = definition_metadata
|
||||
definition['filename'] = [ filepath, filename ]
|
||||
|
||||
return definition, children
|
||||
|
||||
@@ -314,6 +316,7 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
model_data['children'] = children
|
||||
|
||||
model_data['xml_attributes'] = {}
|
||||
model_data['xml_attributes']['filename'] = definition.get('filename', ['', None]) # for git link
|
||||
for key, value in metadata.items():
|
||||
if key not in set(f.name for f in cls.fields + cls.lms.fields):
|
||||
model_data['xml_attributes'][key] = value
|
||||
|
||||
@@ -24,7 +24,7 @@ $(function() {
|
||||
|
||||
$('.datepair input.time').each(function() {
|
||||
var $this = $(this);
|
||||
var opts = { 'showDuration': true, 'timeFormat': 'g:ia', 'scrollDefaultNow': true };
|
||||
var opts = { 'showDuration': true, 'timeFormat': 'H:i', 'scrollDefaultNow': true };
|
||||
|
||||
if ($this.hasClass('start') || $this.hasClass('end')) {
|
||||
opts.onSelect = doDatepair;
|
||||
|
||||
@@ -12,13 +12,20 @@
|
||||
</sequential>
|
||||
</section>
|
||||
</chapter>
|
||||
<chapter name="Chapter 2">
|
||||
<chapter name="Chapter 2" url_name='chapter_2'>
|
||||
<section name="Problem Set 1">
|
||||
<sequential>
|
||||
<problem type="lecture" showanswer="attempted" rerandomize="true" display_name="A simple coding problem" name="Simple coding problem" filename="ps01-simple" url_name="ps01-simple"/>
|
||||
</sequential>
|
||||
</section>
|
||||
<video name="Lost Video" youtube="1.0:TBvX7HzxexQ"/>
|
||||
<sequential format="Lecture Sequence" url_name='test_sequence'>
|
||||
<vertical url_name='test_vertical'>
|
||||
<html url_name='test_html'>
|
||||
Foobar
|
||||
</html>
|
||||
</vertical>
|
||||
</sequential>
|
||||
</chapter>
|
||||
|
||||
</course>
|
||||
|
||||
Reference in New Issue
Block a user