Merge branch 'feature/cale/cms-master' of github.com:MITx/mitx into feature/cdodge/cas-crud-section-subsection
Conflicts: cms/static/js/base.js
This commit is contained in:
@@ -26,4 +26,4 @@ class Command(BaseCommand):
|
||||
print "Importing. Data_dir={data}, course_dirs={courses}".format(
|
||||
data=data_dir,
|
||||
courses=course_dirs)
|
||||
import_from_xml(modulestore(), data_dir, course_dirs, load_error_modules=False)
|
||||
import_from_xml(modulestore('direct'), data_dir, course_dirs, load_error_modules=False)
|
||||
|
||||
@@ -141,8 +141,6 @@ class AuthTestCase(ContentStoreTestCase):
|
||||
"""Make sure pages that do require login work."""
|
||||
auth_pages = (
|
||||
reverse('index'),
|
||||
reverse('edit_item'),
|
||||
reverse('save_item'),
|
||||
)
|
||||
|
||||
# These are pages that should just load when the user is logged in
|
||||
@@ -181,6 +179,7 @@ class AuthTestCase(ContentStoreTestCase):
|
||||
|
||||
TEST_DATA_MODULESTORE = copy.deepcopy(settings.MODULESTORE)
|
||||
TEST_DATA_MODULESTORE['default']['OPTIONS']['fs_root'] = path('common/test/data')
|
||||
TEST_DATA_MODULESTORE['direct']['OPTIONS']['fs_root'] = path('common/test/data')
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MODULESTORE)
|
||||
class EditTestCase(ContentStoreTestCase):
|
||||
@@ -195,17 +194,17 @@ class EditTestCase(ContentStoreTestCase):
|
||||
xmodule.modulestore.django._MODULESTORES = {}
|
||||
xmodule.modulestore.django.modulestore().collection.drop()
|
||||
|
||||
def check_edit_item(self, test_course_name):
|
||||
def check_edit_unit(self, test_course_name):
|
||||
import_from_xml(modulestore(), 'common/test/data/', [test_course_name])
|
||||
|
||||
for descriptor in modulestore().get_items(Location(None, None, None, None, None)):
|
||||
for descriptor in modulestore().get_items(Location(None, None, 'vertical', None, None)):
|
||||
print "Checking ", descriptor.location.url()
|
||||
print descriptor.__class__, descriptor.location
|
||||
resp = self.client.get(reverse('edit_item'), {'id': descriptor.location.url()})
|
||||
resp = self.client.get(reverse('edit_unit', kwargs={'location': descriptor.location.url()}))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
def test_edit_item_toy(self):
|
||||
self.check_edit_item('toy')
|
||||
def test_edit_unit_toy(self):
|
||||
self.check_edit_unit('toy')
|
||||
|
||||
def test_edit_item_full(self):
|
||||
self.check_edit_item('full')
|
||||
def test_edit_unit_full(self):
|
||||
self.check_edit_unit('full')
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from django.conf import settings
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.draft import DRAFT
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
|
||||
def get_course_location_for_item(location):
|
||||
'''
|
||||
@@ -32,16 +35,42 @@ def get_course_location_for_item(location):
|
||||
return location
|
||||
|
||||
|
||||
def get_lms_link_for_item(item):
|
||||
def get_lms_link_for_item(location):
|
||||
location = Location(location)
|
||||
if settings.LMS_BASE is not None:
|
||||
lms_link = "{lms_base}/courses/{course_id}/jump_to/{location}".format(
|
||||
lms_base=settings.LMS_BASE,
|
||||
# TODO: These will need to be changed to point to the particular instance of this problem in the particular course
|
||||
course_id = modulestore().get_containing_courses(item.location)[0].id,
|
||||
location=item.location,
|
||||
course_id = modulestore().get_containing_courses(location)[0].id,
|
||||
location=location,
|
||||
)
|
||||
else:
|
||||
lms_link = None
|
||||
|
||||
return lms_link
|
||||
|
||||
|
||||
class UnitState(object):
|
||||
draft = 'draft'
|
||||
private = 'private'
|
||||
public = 'public'
|
||||
|
||||
|
||||
def compute_unit_state(unit):
|
||||
"""
|
||||
Returns whether this unit is 'draft', 'public', or 'private'.
|
||||
|
||||
'draft' content is in the process of being edited, but still has a previous
|
||||
version visible in the LMS
|
||||
'public' content is locked and visible in the LMS
|
||||
'private' content is editabled and not visible in the LMS
|
||||
"""
|
||||
|
||||
if unit.metadata.get('is_draft', False):
|
||||
try:
|
||||
modulestore('direct').get_item(unit.location)
|
||||
return UnitState.draft
|
||||
except ItemNotFoundError:
|
||||
return UnitState.private
|
||||
else:
|
||||
return UnitState.public
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
from util.json_request import expect_json
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
import mimetypes
|
||||
import StringIO
|
||||
import exceptions
|
||||
import json
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import StringIO
|
||||
import sys
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -43,7 +44,7 @@ from cache_toolbox.core import set_cached_content, get_cached_content, del_cache
|
||||
from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role
|
||||
from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group
|
||||
from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME
|
||||
from .utils import get_course_location_for_item, get_lms_link_for_item
|
||||
from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state
|
||||
|
||||
from xmodule.templates import all_templates
|
||||
|
||||
@@ -157,20 +158,37 @@ def edit_subsection(request, location):
|
||||
|
||||
item = modulestore().get_item(location)
|
||||
|
||||
lms_link = get_lms_link_for_item(item)
|
||||
lms_link = get_lms_link_for_item(location)
|
||||
|
||||
# make sure that location references a 'sequential', otherwise return BadRequest
|
||||
if item.location.category != 'sequential':
|
||||
return HttpResponseBadRequest
|
||||
|
||||
logging.debug('Start = {0}'.format(item.start))
|
||||
parent_locs = modulestore().get_parent_locations(location)
|
||||
|
||||
# we're for now assuming a single parent
|
||||
if len(parent_locs) != 1:
|
||||
logging.error('Multiple (or none) parents have been found for {0}'.format(location))
|
||||
|
||||
# this should blow up if we don't find any parents, which would be erroneous
|
||||
parent = modulestore().get_item(parent_locs[0])
|
||||
|
||||
# remove all metadata from the generic dictionary that is presented in a more normalized UI
|
||||
|
||||
policy_metadata = dict((key,value) for key, value in item.metadata.iteritems()
|
||||
if key not in ['display_name', 'start', 'due', 'format'] and key not in item.system_metadata_fields)
|
||||
|
||||
logging.debug(policy_metadata)
|
||||
|
||||
return render_to_response('edit_subsection.html',
|
||||
{'subsection': item,
|
||||
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'),
|
||||
'lms_link': lms_link
|
||||
'lms_link': lms_link,
|
||||
'parent_item' : parent,
|
||||
'policy_metadata' : policy_metadata
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_unit(request, location):
|
||||
"""
|
||||
@@ -186,7 +204,8 @@ def edit_unit(request, location):
|
||||
|
||||
item = modulestore().get_item(location)
|
||||
|
||||
lms_link = get_lms_link_for_item(item)
|
||||
# The non-draft location
|
||||
lms_link = get_lms_link_for_item(item.location._replace(revision=None))
|
||||
|
||||
component_templates = defaultdict(list)
|
||||
|
||||
@@ -213,14 +232,25 @@ def edit_unit(request, location):
|
||||
containing_section_locs = modulestore().get_parent_locations(containing_subsection.location)
|
||||
containing_section = modulestore().get_item(containing_section_locs[0])
|
||||
|
||||
unit_state = compute_unit_state(item)
|
||||
|
||||
try:
|
||||
published_date = time.strftime('%B %d, %Y', item.metadata.get('published_date'))
|
||||
except TypeError:
|
||||
published_date = None
|
||||
|
||||
return render_to_response('unit.html', {
|
||||
'unit': item,
|
||||
'unit_location': location,
|
||||
'components': components,
|
||||
'component_templates': component_templates,
|
||||
'lms_link': lms_link,
|
||||
'draft_preview_link': lms_link,
|
||||
'published_preview_link': lms_link,
|
||||
'subsection': containing_subsection,
|
||||
'section': containing_section,
|
||||
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty')
|
||||
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'),
|
||||
'unit_state': unit_state,
|
||||
'published_date': published_date,
|
||||
})
|
||||
|
||||
|
||||
@@ -238,7 +268,6 @@ def preview_component(request, location):
|
||||
})
|
||||
|
||||
|
||||
|
||||
def user_author_string(user):
|
||||
'''Get an author string for commits by this user. Format:
|
||||
first last <email@email.com>.
|
||||
@@ -407,6 +436,13 @@ def get_module_previews(request, descriptor):
|
||||
preview_html.append(module.get_html())
|
||||
return preview_html
|
||||
|
||||
|
||||
def _xmodule_recurse(item, action):
|
||||
for child in item.get_children():
|
||||
_xmodule_recurse(child, action)
|
||||
|
||||
action(item)
|
||||
|
||||
def _delete_item(item, recurse=False):
|
||||
if recurse:
|
||||
children = item.get_children()
|
||||
@@ -430,8 +466,11 @@ def delete_item(request):
|
||||
|
||||
item = modulestore().get_item(item_location)
|
||||
|
||||
_delete_item(item, delete_children)
|
||||
|
||||
if delete_children:
|
||||
_xmodule_recurse(item, lambda i: modulestore().delete_item(i.location))
|
||||
else:
|
||||
modulestore().delete_item(item.location)
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
@@ -465,9 +504,12 @@ def save_item(request):
|
||||
# update existing metadata with submitted metadata (which can be partial)
|
||||
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
|
||||
for metadata_key in posted_metadata.keys():
|
||||
# NOTE: We don't want clients to be able to delete 'system metadata' which are not intended to be user
|
||||
# editable
|
||||
if posted_metadata[metadata_key] is None and metadata_key not in existing_item.system_metadata_fields:
|
||||
|
||||
# let's strip out any metadata fields from the postback which have been identified as system metadata
|
||||
# and therefore should not be user-editable, so we should accept them back from the client
|
||||
if metadata_key in existing_item.system_metadata_fields:
|
||||
del posted_metadata[metadata_key]
|
||||
elif posted_metadata[metadata_key] is None:
|
||||
# remove both from passed in collection as well as the collection read in from the modulestore
|
||||
if metadata_key in existing_item.metadata:
|
||||
del existing_item.metadata[metadata_key]
|
||||
@@ -482,6 +524,51 @@ def save_item(request):
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def create_draft(request):
|
||||
location = request.POST['id']
|
||||
|
||||
# check permissions for this user within this course
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
# This clones the existing item location to a draft location (the draft is implicit,
|
||||
# because modulestore is a Draft modulestore)
|
||||
modulestore().clone_item(location, location)
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def publish_draft(request):
|
||||
location = request.POST['id']
|
||||
|
||||
# check permissions for this user within this course
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
item = modulestore().get_item(location)
|
||||
_xmodule_recurse(item, lambda i: modulestore().publish(i.location, request.user.id))
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def unpublish_unit(request):
|
||||
location = request.POST['id']
|
||||
|
||||
# check permissions for this user within this course
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
item = modulestore().get_item(location)
|
||||
_xmodule_recurse(item, lambda i: modulestore().unpublish(i.location))
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def clone_item(request):
|
||||
@@ -506,7 +593,12 @@ def clone_item(request):
|
||||
new_item.metadata['display_name'] = display_name
|
||||
|
||||
modulestore().update_metadata(new_item.location.url(), new_item.own_metadata)
|
||||
modulestore().update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()])
|
||||
|
||||
if parent_location.category not in ('vertical',):
|
||||
parent_update_modulestore = modulestore('direct')
|
||||
else:
|
||||
parent_update_modulestore = modulestore()
|
||||
parent_update_modulestore.update_children(parent_location, parent.definition.get('children', []) + [new_item.location.url()])
|
||||
|
||||
return HttpResponse(json.dumps({'id': dest_location.url()}))
|
||||
|
||||
@@ -688,3 +780,9 @@ def asset_index(request, location):
|
||||
# points to the temporary course landing page with log in and sign up
|
||||
def landing(request, org, course, coursename):
|
||||
return render_to_response('temp-course-landing.html', {})
|
||||
|
||||
def static_pages(request, org, course, coursename):
|
||||
return render_to_response('static-pages.html', {})
|
||||
|
||||
def edit_static(request, org, course, coursename):
|
||||
return render_to_response('edit-static-page.html', {})
|
||||
|
||||
@@ -14,17 +14,23 @@ LOGGING = get_logger_config(ENV_ROOT / "log",
|
||||
tracking_filename="tracking.log",
|
||||
debug=True)
|
||||
|
||||
modulestore_options = {
|
||||
'default_class': 'xmodule.raw_module.RawDescriptor',
|
||||
'host': 'localhost',
|
||||
'db': 'xmodule',
|
||||
'collection': 'modulestore',
|
||||
'fs_root': GITHUB_REPO_ROOT,
|
||||
'render_template': 'mitxmako.shortcuts.render_to_string',
|
||||
}
|
||||
|
||||
MODULESTORE = {
|
||||
'default': {
|
||||
'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
|
||||
'OPTIONS': modulestore_options
|
||||
},
|
||||
'direct': {
|
||||
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
|
||||
'OPTIONS': {
|
||||
'default_class': 'xmodule.raw_module.RawDescriptor',
|
||||
'host': 'localhost',
|
||||
'db': 'xmodule',
|
||||
'collection': 'modulestore',
|
||||
'fs_root': GITHUB_REPO_ROOT,
|
||||
'render_template': 'mitxmako.shortcuts.render_to_string',
|
||||
}
|
||||
'OPTIONS': modulestore_options
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,17 +38,23 @@ STATICFILES_DIRS += [
|
||||
if os.path.isdir(COMMON_TEST_DATA_ROOT / course_dir)
|
||||
]
|
||||
|
||||
modulestore_options = {
|
||||
'default_class': 'xmodule.raw_module.RawDescriptor',
|
||||
'host': 'localhost',
|
||||
'db': 'test_xmodule',
|
||||
'collection': 'modulestore',
|
||||
'fs_root': GITHUB_REPO_ROOT,
|
||||
'render_template': 'mitxmako.shortcuts.render_to_string',
|
||||
}
|
||||
|
||||
MODULESTORE = {
|
||||
'default': {
|
||||
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
|
||||
'OPTIONS': {
|
||||
'default_class': 'xmodule.raw_module.RawDescriptor',
|
||||
'host': 'localhost',
|
||||
'db': 'test_xmodule',
|
||||
'collection': 'modulestore',
|
||||
'fs_root': GITHUB_REPO_ROOT,
|
||||
'render_template': 'mitxmako.shortcuts.render_to_string',
|
||||
}
|
||||
'OPTIONS': modulestore_options
|
||||
},
|
||||
'direct': {
|
||||
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
|
||||
'OPTIONS': modulestore_options
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ class CMS.Views.ModuleEdit extends Backbone.View
|
||||
|
||||
return _metadata
|
||||
|
||||
cloneTemplate: (template) ->
|
||||
cloneTemplate: (parent, template) ->
|
||||
$.post("/clone_item", {
|
||||
parent_location: @$el.parent().data('id')
|
||||
parent_location: parent
|
||||
template: template
|
||||
}, (data) =>
|
||||
@model.set(id: data.id)
|
||||
|
||||
@@ -5,9 +5,35 @@ class CMS.Views.UnitEdit extends Backbone.View
|
||||
'click .new-component-templates .new-component-template a': 'saveNewComponent'
|
||||
'click .new-component-templates .cancel-button': 'closeNewComponent'
|
||||
'click .new-component-button': 'showNewComponentForm'
|
||||
'click .unit-actions .save-button': 'save'
|
||||
'click #save-draft': 'saveDraft'
|
||||
'click #delete-draft': 'deleteDraft'
|
||||
'click #create-draft': 'createDraft'
|
||||
'click #publish-draft': 'publishDraft'
|
||||
'change #visibility': 'setVisibility'
|
||||
|
||||
initialize: =>
|
||||
@visibilityView = new CMS.Views.UnitEdit.Visibility(
|
||||
el: @$('#visibility')
|
||||
model: @model
|
||||
)
|
||||
|
||||
@saveView = new CMS.Views.UnitEdit.SaveDraftButton(
|
||||
el: @$('#save-draft')
|
||||
model: @model
|
||||
)
|
||||
|
||||
@locationView = new CMS.Views.UnitEdit.LocationState(
|
||||
el: @$('.section-item.editing a')
|
||||
model: @model
|
||||
)
|
||||
|
||||
@nameView = new CMS.Views.UnitEdit.NameEdit(
|
||||
el: @$('.unit-name-input')
|
||||
model: @model
|
||||
)
|
||||
|
||||
@model.on('change:state', @render)
|
||||
|
||||
@$newComponentItem = @$('.new-component-item')
|
||||
@$newComponentTypePicker = @$('.new-component')
|
||||
@$newComponentTemplatePickers = @$('.new-component-templates')
|
||||
@@ -15,7 +41,13 @@ class CMS.Views.UnitEdit extends Backbone.View
|
||||
|
||||
@$('.components').sortable(
|
||||
handle: '.drag-handle'
|
||||
update: (event, ui) => @saveOrder()
|
||||
update: (event, ui) => @model.set('children', @components())
|
||||
helper: 'clone'
|
||||
opacity: '0.5'
|
||||
placeholder: 'component-placeholder'
|
||||
forcePlaceholderSize: true
|
||||
axis: 'y'
|
||||
items: '> .component'
|
||||
)
|
||||
|
||||
@$('.component').each((idx, element) =>
|
||||
@@ -26,10 +58,10 @@ class CMS.Views.UnitEdit extends Backbone.View
|
||||
id: $(element).data('id'),
|
||||
)
|
||||
)
|
||||
update: (event, ui) => @model.set('children', @components())
|
||||
)
|
||||
|
||||
@model.components = @components()
|
||||
|
||||
# New component creation
|
||||
showNewComponentForm: (event) =>
|
||||
event.preventDefault()
|
||||
@$newComponentItem.addClass('adding')
|
||||
@@ -56,21 +88,31 @@ class CMS.Views.UnitEdit extends Backbone.View
|
||||
event.preventDefault()
|
||||
|
||||
editor = new CMS.Views.ModuleEdit(
|
||||
onDelete: @deleteComponent
|
||||
model: new CMS.Models.Module()
|
||||
)
|
||||
|
||||
@$newComponentItem.before(editor.$el)
|
||||
|
||||
editor.cloneTemplate($(event.currentTarget).data('location'))
|
||||
editor.cloneTemplate(
|
||||
@$el.data('id'),
|
||||
$(event.currentTarget).data('location')
|
||||
)
|
||||
|
||||
@closeNewComponent(event)
|
||||
|
||||
components: => @$('.component').map((idx, el) -> $(el).data('id')).get()
|
||||
|
||||
saveOrder: =>
|
||||
@model.save(
|
||||
children: @components()
|
||||
)
|
||||
wait: (value) =>
|
||||
@$('.unit-body').toggleClass("waiting", value)
|
||||
|
||||
render: =>
|
||||
if @model.hasChanged('state')
|
||||
@$el.toggleClass("edit-state-#{@model.previous('state')} edit-state-#{@model.get('state')}")
|
||||
@wait(false)
|
||||
|
||||
saveDraft: =>
|
||||
@model.save()
|
||||
|
||||
deleteComponent: (event) =>
|
||||
$component = $(event.currentTarget).parents('.component')
|
||||
@@ -78,6 +120,94 @@ class CMS.Views.UnitEdit extends Backbone.View
|
||||
id: $component.data('id')
|
||||
}, =>
|
||||
$component.remove()
|
||||
@saveOrder()
|
||||
@model.set('children', @components())
|
||||
)
|
||||
|
||||
deleteDraft: (event) ->
|
||||
@wait(true)
|
||||
|
||||
$.post('/delete_item', {
|
||||
id: @$el.data('id')
|
||||
delete_children: true
|
||||
}, =>
|
||||
window.location.reload()
|
||||
)
|
||||
|
||||
createDraft: (event) ->
|
||||
@wait(true)
|
||||
|
||||
$.post('/create_draft', {
|
||||
id: @$el.data('id')
|
||||
}, =>
|
||||
@model.set('state', 'draft')
|
||||
)
|
||||
|
||||
publishDraft: (event) ->
|
||||
@wait(true)
|
||||
@saveDraft()
|
||||
|
||||
$.post('/publish_draft', {
|
||||
id: @$el.data('id')
|
||||
}, =>
|
||||
@model.set('state', 'public')
|
||||
)
|
||||
|
||||
setVisibility: (event) ->
|
||||
if @$('#visibility').val() == 'private'
|
||||
target_url = '/unpublish_unit'
|
||||
else
|
||||
target_url = '/publish_draft'
|
||||
|
||||
@wait(true)
|
||||
|
||||
$.post(target_url, {
|
||||
id: @$el.data('id')
|
||||
}, =>
|
||||
@model.set('state', @$('#visibility').val())
|
||||
)
|
||||
|
||||
class CMS.Views.UnitEdit.NameEdit extends Backbone.View
|
||||
events:
|
||||
"keyup .unit-display-name-input": "saveName"
|
||||
|
||||
initialize: =>
|
||||
@model.on('change:metadata', @render)
|
||||
@saveName
|
||||
|
||||
render: =>
|
||||
@$('.unit-display-name-input').val(@model.get('metadata').display_name)
|
||||
|
||||
saveName: =>
|
||||
# Treat the metadata dictionary as immutable
|
||||
metadata = $.extend({}, @model.get('metadata'))
|
||||
metadata.display_name = @$('.unit-display-name-input').val()
|
||||
@model.set('metadata', metadata)
|
||||
|
||||
class CMS.Views.UnitEdit.LocationState extends Backbone.View
|
||||
initialize: =>
|
||||
@model.on('change:state', @render)
|
||||
|
||||
render: =>
|
||||
@$el.toggleClass("#{@model.previous('state')}-item #{@model.get('state')}-item")
|
||||
|
||||
class CMS.Views.UnitEdit.Visibility extends Backbone.View
|
||||
initialize: =>
|
||||
@model.on('change:state', @render)
|
||||
@render()
|
||||
|
||||
render: =>
|
||||
@$el.val(@model.get('state'))
|
||||
|
||||
class CMS.Views.UnitEdit.SaveDraftButton extends Backbone.View
|
||||
initialize: =>
|
||||
@model.on('change:children', @enable)
|
||||
@model.on('change:metadata', @enable)
|
||||
@model.on('sync', @disable)
|
||||
|
||||
@disable()
|
||||
|
||||
disable: =>
|
||||
@$el.addClass('disabled')
|
||||
|
||||
enable: =>
|
||||
@$el.removeClass('disabled')
|
||||
@@ -36,6 +36,7 @@ $(document).ready(function() {
|
||||
$('.set-date').bind('click', showDateSetter);
|
||||
$('.remove-date').bind('click', removeDateSetter);
|
||||
|
||||
<<<<<<< HEAD
|
||||
// add new/delete section
|
||||
$('.new-courseware-section-button').bind('click', addNewSection);
|
||||
$('.delete-section-button').bind('click', deleteSection);
|
||||
@@ -43,9 +44,42 @@ $(document).ready(function() {
|
||||
// add new/delete subsection
|
||||
$('.new-subsection-item').bind('click', addNewSubsection);
|
||||
$('.delete-subsection-button').bind('click', deleteSubsection);
|
||||
=======
|
||||
// add/remove policy metadata button click handlers
|
||||
$('.add-policy-data').bind('click', addPolicyMetadata);
|
||||
$('.remove-policy-data').bind('click', removePolicyMetadata);
|
||||
|
||||
$('.sync-date').bind('click', syncReleaseDate);
|
||||
>>>>>>> 2cf14ae339aed0150be353cbcac25c377956b7ab
|
||||
|
||||
});
|
||||
|
||||
function syncReleaseDate(e) {
|
||||
e.preventDefault();
|
||||
$("#start_date").val("");
|
||||
$("#start_time").val("");
|
||||
}
|
||||
|
||||
function addPolicyMetadata(e) {
|
||||
e.preventDefault();
|
||||
var template =$('#add-new-policy-element-template > li');
|
||||
var newNode = template.clone();
|
||||
var _parent_el = $(this).parent('ol:.policy-list');
|
||||
newNode.insertBefore('.add-policy-data');
|
||||
$('.remove-policy-data').bind('click', removePolicyMetadata);
|
||||
}
|
||||
|
||||
function removePolicyMetadata(e) {
|
||||
e.preventDefault();
|
||||
policy_name = $(this).data('policy-name');
|
||||
var _parent_el = $(this).parent('li:.policy-list-element');
|
||||
if ($(_parent_el).hasClass("new-policy-list-element"))
|
||||
_parent_el.remove();
|
||||
else
|
||||
_parent_el.appendTo("#policy-to-delete");
|
||||
}
|
||||
|
||||
|
||||
// This method only changes the ordering of the child objects in a subsection
|
||||
function onUnitReordered() {
|
||||
var subsection_id = $(this).data('subsection-id');
|
||||
@@ -94,7 +128,7 @@ function saveSubsection(e) {
|
||||
|
||||
var id = $(this).data('id');
|
||||
|
||||
// pull all metadata editable fields on page
|
||||
// pull all 'normalized' metadata editable fields on page
|
||||
var metadata_fields = $('input[data-metadata-name]');
|
||||
|
||||
metadata = {};
|
||||
@@ -103,6 +137,20 @@ function saveSubsection(e) {
|
||||
metadata[$(el).data("metadata-name")] = el.value;
|
||||
}
|
||||
|
||||
// now add 'free-formed' metadata which are presented to the user as dual input fields (name/value)
|
||||
$('ol.policy-list > li.policy-list-element').each( function(i, element) {
|
||||
name = $(element).children('.policy-list-name').val();
|
||||
val = $(element).children('.policy-list-value').val();
|
||||
metadata[name] = val;
|
||||
});
|
||||
|
||||
// now add any 'removed' policy metadata which is stored in a separate hidden div
|
||||
// 'null' presented to the server means 'remove'
|
||||
$("#policy-to-delete > li.policy-list-element").each(function(i, element) {
|
||||
name = $(element).children('.policy-list-name').val();
|
||||
if (name != "")
|
||||
metadata[name] = null;
|
||||
});
|
||||
|
||||
// Piece back together the date/time UI elements into one date/time string
|
||||
// NOTE: our various "date/time" metadata elements don't always utilize the same formatting string
|
||||
|
||||
@@ -29,6 +29,10 @@ h1 {
|
||||
margin: 36px 6px;
|
||||
}
|
||||
|
||||
.waiting {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.page-actions {
|
||||
float: right;
|
||||
margin-top: 42px;
|
||||
@@ -128,13 +132,15 @@ label {
|
||||
}
|
||||
|
||||
.new-unit-item,
|
||||
.new-subsection-item {
|
||||
.new-subsection-item,
|
||||
.new-policy-item {
|
||||
@include grey-button;
|
||||
margin: 5px 8px;
|
||||
padding: 3px 10px 4px 10px;
|
||||
font-size: 10px;
|
||||
|
||||
.new-folder-icon,
|
||||
.new-policy-icon,
|
||||
.new-unit-icon {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
|
||||
@@ -16,6 +16,18 @@
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 0 0 rgba(0, 0, 0, 0));
|
||||
@include transition(background-color .15s, box-shadow .15s);
|
||||
|
||||
&.disabled {
|
||||
border: 1px solid $lightGrey !important;
|
||||
border-radius: 3px !important;
|
||||
background: $lightGrey !important;
|
||||
color: $darkGrey !important;
|
||||
pointer-events: none;
|
||||
cursor: none;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include box-shadow(0 1px 0 rgba(255, 255, 255, .3) inset, 0 1px 1px rgba(0, 0, 0, .15));
|
||||
}
|
||||
@@ -161,13 +173,33 @@
|
||||
background: #fffcf1;
|
||||
}
|
||||
|
||||
.draft-item,
|
||||
.hidden-item,
|
||||
.draft-item:after,
|
||||
.public-item:after,
|
||||
.private-item:after {
|
||||
margin-left: 3px;
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.draft-item:after {
|
||||
content: "- draft";
|
||||
}
|
||||
|
||||
.public-item:after {
|
||||
content: "- public";
|
||||
}
|
||||
|
||||
.private-item:after {
|
||||
content: "- private";
|
||||
}
|
||||
|
||||
.public-item,
|
||||
.private-item {
|
||||
color: #a4aab7;
|
||||
}
|
||||
|
||||
.has-new-draft-item {
|
||||
.draft-item {
|
||||
color: #9f7d10;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,9 +117,8 @@
|
||||
}
|
||||
|
||||
.draft-tag,
|
||||
.hidden-tag,
|
||||
.private-tag,
|
||||
.has-new-draft-tag {
|
||||
.public-tag,
|
||||
.private-tag {
|
||||
margin-left: 3px;
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
@@ -127,7 +126,7 @@
|
||||
color: #a4aab7;
|
||||
}
|
||||
|
||||
.has-new-draft-tag {
|
||||
.draft-tag {
|
||||
color: #9f7d10;
|
||||
}
|
||||
|
||||
@@ -171,6 +170,14 @@
|
||||
background: url(../img/new-unit-icon.png) right no-repeat;
|
||||
}
|
||||
|
||||
.new-policy-icon {
|
||||
display: inline-block;
|
||||
width: 23px;
|
||||
height: 12px;
|
||||
margin-right: 8px;
|
||||
background: url(../img/new-unit-icon.png) right no-repeat;
|
||||
}
|
||||
|
||||
.textbook-icon {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
@@ -244,4 +251,4 @@
|
||||
margin-left: 10px;
|
||||
vertical-align: middle;
|
||||
background: url(../img/blue-spinner.gif) no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ body.no-header {
|
||||
color: #fff;
|
||||
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(255, 255, 255, 0.05) inset);
|
||||
|
||||
.left {
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
.drop-icon {
|
||||
margin-left: 5px;
|
||||
font-size: 11px;
|
||||
|
||||
64
cms/static/sass/_static-pages.scss
Normal file
64
cms/static/sass/_static-pages.scss
Normal file
@@ -0,0 +1,64 @@
|
||||
.static-pages {
|
||||
.new-static-page-button {
|
||||
@include grey-button;
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.static-page-item {
|
||||
position: relative;
|
||||
margin: 10px 0;
|
||||
padding: 22px 20px;
|
||||
border: 1px solid $darkGrey;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1));
|
||||
|
||||
.page-name {
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
margin-top: 19px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-static-page {
|
||||
.main-wrapper {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.static-page-details {
|
||||
@extend .window;
|
||||
padding: 32px 40px;
|
||||
|
||||
.row {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.page-display-name-input {
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.page-contents {
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
height: 360px;
|
||||
padding: 15px;
|
||||
border: 1px solid #b0b6c2;
|
||||
border-radius: 2px;
|
||||
@include linear-gradient(top, rgba(255, 255, 255, 0), rgba(255, 255, 255, .3));
|
||||
background-color: #edf1f5;
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1) inset);
|
||||
font-family: Monaco, monospace;
|
||||
font-size: 13px;
|
||||
color: #3c3c3c;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
@@ -75,19 +75,17 @@
|
||||
&.editing {
|
||||
border-color: #6696d7;
|
||||
|
||||
&:hover {
|
||||
.drag-handle,
|
||||
.component-actions a {
|
||||
background-color: $blue;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
border-color: $blue;
|
||||
}
|
||||
.drag-handle,
|
||||
.component-actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.rendered-component {
|
||||
&.component-placeholder {
|
||||
border-color: #6696d7;
|
||||
}
|
||||
|
||||
.xmodule_display {
|
||||
padding: 40px 20px 20px;
|
||||
}
|
||||
|
||||
@@ -230,14 +228,24 @@
|
||||
@include edit-box;
|
||||
display: none;
|
||||
padding: 20px;
|
||||
border-radius: 0 0 3px 3px;
|
||||
border-radius: 2px 2px 0 0;
|
||||
@include linear-gradient(top, rgba(0, 0, 0, 0), rgba(0, 0, 0, .1));
|
||||
background-color: $blue;
|
||||
color: #fff;
|
||||
@include box-shadow(none);
|
||||
|
||||
.metadata_edit {
|
||||
margin-bottom: 20px;
|
||||
font-size: 13px;
|
||||
|
||||
li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
@@ -245,6 +253,7 @@
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -394,3 +403,37 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.edit-state-draft {
|
||||
.visibility {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#create-draft {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-state-public {
|
||||
#save-draft,
|
||||
#delete-draft,
|
||||
#publish-draft,
|
||||
.component-actions,
|
||||
.new-component-item,
|
||||
#published-alert {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-state-private {
|
||||
#delete-draft,
|
||||
#publish-draft,
|
||||
#published-alert,
|
||||
#create-draft, {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
@import "subsection";
|
||||
@import "unit";
|
||||
@import "assets";
|
||||
@import "static-pages";
|
||||
@import "course-info";
|
||||
@import "landing";
|
||||
@import "graphics";
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
${preview}
|
||||
<div class="component-actions">
|
||||
<a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a>
|
||||
<a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a>
|
||||
</div>
|
||||
<a href="#" class="drag-handle"></a>
|
||||
<div class="component-editor">
|
||||
<div class="module-editor">
|
||||
${editor}
|
||||
@@ -11,4 +5,9 @@ ${preview}
|
||||
<a href="#" class="save-button">Save</a>
|
||||
<a href="#" class="cancel-button">Cancel</a>
|
||||
</div>
|
||||
|
||||
<div class="component-actions">
|
||||
<a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a>
|
||||
<a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a>
|
||||
</div>
|
||||
<a href="#" class="drag-handle"></a>
|
||||
${preview}
|
||||
41
cms/templates/edit-static-page.html
Normal file
41
cms/templates/edit-static-page.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Edit Static Page</%block>
|
||||
<%block name="bodyclass">edit-static-page</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<div class="main-column">
|
||||
<article class="static-page-details">
|
||||
<div class="row">
|
||||
<label>Display Name:</label>
|
||||
<input type="text" value="Syllabus" class="page-display-name-input" data-metadata-name="display_name"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Page Content:</label>
|
||||
<textarea class="page-contents"></textarea>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="sidebar">
|
||||
<div class="unit-properties window">
|
||||
<h4>Page Settings</h4>
|
||||
<div class="window-contents">
|
||||
<div class="row visibility">
|
||||
<label class="inline-label">Visibility:</label>
|
||||
<select>
|
||||
<option>Public</option>
|
||||
<option>Private</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row unit-actions">
|
||||
<a href="#" class="save-button">Save</a>
|
||||
<a href="#" target="_blank" class="preview-button">Preview</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -19,25 +19,42 @@
|
||||
<div class="inner-wrapper">
|
||||
<div class="main-column">
|
||||
<article class="subsection-body window">
|
||||
<div class="subsection-name-input">
|
||||
<label>Display Name:</label>
|
||||
<input type="text" value="${subsection.metadata['display_name']}" class="subsection-display-name-input" data-metadata-name="display_name"/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Format:</label>
|
||||
<input type="text" value="${subsection.metadata['format'] if 'format' in subsection.metadata else ''}" class="unit-subtitle" data-metadata-name="subtitle"/>
|
||||
</div>
|
||||
<div class="unit-list">
|
||||
<label>Units:</label>
|
||||
${units.enum_units(subsection)}
|
||||
</div>
|
||||
<div class='wip-box'>
|
||||
<label>Policy:</label>
|
||||
<textarea class="text-editor">Policy blah, blah, blah…</textarea>
|
||||
</div>
|
||||
<div class="subsection-name-input">
|
||||
<label>Display Name:</label>
|
||||
<input type="text" value="${subsection.metadata['display_name']}" class="subsection-display-name-input" data-metadata-name="display_name"/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Format:</label>
|
||||
<input type="text" value="${subsection.metadata['format'] if 'format' in subsection.metadata else ''}" class="unit-subtitle" data-metadata-name="format"/>
|
||||
</div>
|
||||
<div class="unit-list">
|
||||
<label>Units:</label>
|
||||
${units.enum_units(subsection)}
|
||||
</div>
|
||||
<div>
|
||||
<label>Policy:</label>
|
||||
<ol class='policy-list'>
|
||||
% for policy_name in policy_metadata.keys():
|
||||
<li class="policy-list-element">
|
||||
<input type="text" class="policy-list-name" name="${policy_name}" value="${policy_name}" disabled size="15"/>: <input type="text" class="policy-list-value" name="${policy_metadata[policy_name]}" value="${policy_metadata[policy_name]}" size="40"/><a href="#" class="delete-icon remove-policy-data"></a>
|
||||
</li>
|
||||
% endfor
|
||||
|
||||
<a href="#" class="new-policy-item add-policy-data" >
|
||||
<span class="new-policy-icon"></span>New Policy Data
|
||||
</a>
|
||||
</ol>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div id="policy-to-delete" style="display:none">
|
||||
</div>
|
||||
|
||||
<div id="add-new-policy-element-template" style="display:none">
|
||||
<li class="policy-list-element new-policy-list-element"><input type="text" class="policy-list-name" autocomplete="off" size="15"/>: <input type="text" class="policy-list-value" size=40 autocomplete="off"/><a href="#" class="delete-icon remove-policy-data"></a></li>
|
||||
</div>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="unit-properties window">
|
||||
<h4>Subsection Settings</h4>
|
||||
@@ -46,12 +63,15 @@
|
||||
<label>Release date:<!-- <span class="description">Determines when this subsection and the units within it will be released publicly.</span>--></label>
|
||||
<div class="datepair" data-language="javascript">
|
||||
<%
|
||||
start_time = datetime.fromtimestamp(mktime(subsection.start)) if subsection.start is not None else None
|
||||
start_date = datetime.fromtimestamp(mktime(subsection.start)) if subsection.start is not None else None
|
||||
parent_start_date = datetime.fromtimestamp(mktime(parent_item.start)) if parent_item.start is not None else None
|
||||
%>
|
||||
<input type="text" id="start_date" value="${start_time.strftime('%m/%d/%Y') if start_time is not None else ''}" placeholder="MM/DD/YYYY" class="date" size='15'/>
|
||||
<input type="text" id="start_time" value="${start_time.strftime('%H:%M') if start_time is not None else ''}" placeholder="HH:MM" class="time" size='10'/>
|
||||
<input type="text" id="start_date" name="start_date" value="${start_date.strftime('%m/%d/%Y') if start_date is not None else ''}" placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
|
||||
<input type="text" id="start_time" name="start_time" value="${start_date.strftime('%H:%M') if start_date is not None else ''}" placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
|
||||
</div>
|
||||
<p class="notice wip-box">The date above differs from the release date of Week 1 – 10/10/2012 at 12:00 am. <a href="#" class="sync-date">Sync to Week 1.</a></p>
|
||||
% if subsection.start != parent_item.start and subsection.start:
|
||||
<p class="notice">The date above differs from the release date of ${parent_item.display_name} – ${parent_start_date.strftime('%m/%d/%Y')} at ${parent_start_date.strftime('%H:%M')}. <a href="#" class="sync-date">Sync to ${parent_item.display_name}.</a></p>
|
||||
% endif
|
||||
</div>
|
||||
<div class="due-date-input row">
|
||||
<label>Due date:</label>
|
||||
@@ -62,8 +82,8 @@
|
||||
# due date uses it own formatting for stringifying the date. As with capa_module.py, there's a utility module available for us to use
|
||||
due_date = dateutil.parser.parse(subsection.metadata.get('due')) if 'due' in subsection.metadata else None
|
||||
%>
|
||||
<input type="text" id="due_date" value="${due_date.strftime('%Y-%m-%d') if due_date is not None else ''}" placeholder="MM/DD/YYYY" class="date" size='15' />
|
||||
<input type="text" id="due_time" value="${due_date.strftime('%H:%M') if due_date is not None else ''}" placeholder="HH:MM" class="time" size='10' />
|
||||
<input type="text" id="due_date" name="due_date" value="${due_date.strftime('%m/%d/%Y') if due_date is not None else ''}" placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
|
||||
<input type="text" id="due_time" name="due_time" value="${due_date.strftime('%H:%M') if due_date is not None else ''}" placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
|
||||
<a href="#" class="remove-date">Remove due date</a>
|
||||
</p>
|
||||
</div>
|
||||
@@ -85,6 +105,7 @@
|
||||
<script src="${static.url('js/vendor/date.js')}"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
// expand the due-date area if the values are set
|
||||
if ($('#due_date').val() != '') {
|
||||
var $block = $('.set-date').closest('.due-date-input');
|
||||
$('.set-date').hide();
|
||||
|
||||
41
cms/templates/static-pages.html
Normal file
41
cms/templates/static-pages.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Static Pages</%block>
|
||||
<%block name="bodyclass">static-pages</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<h1>Static Pages</h1>
|
||||
<div class="page-actions">
|
||||
|
||||
</div>
|
||||
<article class="static-page-overview">
|
||||
<a href="#" class="new-static-page-button wip-box"><span class="plus-icon"></span> New Static Page</a>
|
||||
<ul class="static-page-list">
|
||||
<li class="static-page-item">
|
||||
<a href="#" class="page-name">Course Info</a>
|
||||
<div class="item-actions">
|
||||
<a href="#" class="edit-button wip"><span class="delete-icon"></span></a>
|
||||
<a href="#" class="drag-handle wip"></a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="static-page-item">
|
||||
<a href="#" class="page-name">Textbook</a>
|
||||
<div class="item-actions">
|
||||
<a href="#" class="edit-button wip"><span class="delete-icon"></span></a>
|
||||
<a href="#" class="drag-handle wip"></a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="static-page-item">
|
||||
<a href="#" class="page-name">Syllabus</a>
|
||||
<div class="item-actions">
|
||||
<a href="#" class="edit-button wip"><span class="delete-icon"></span></a>
|
||||
<a href="#" class="drag-handle wip"></a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -8,18 +8,27 @@
|
||||
new CMS.Views.UnitEdit({
|
||||
el: $('.main-wrapper'),
|
||||
model: new CMS.Models.Module({
|
||||
id: '${unit.location.url()}'
|
||||
id: '${unit_location}',
|
||||
state: '${unit_state}'
|
||||
})
|
||||
});
|
||||
</script>
|
||||
</%block>
|
||||
<%block name="content">
|
||||
<div class="main-wrapper">
|
||||
<div class="main-wrapper edit-state-${unit_state}" data-id="${unit_location}">
|
||||
<div class="inner-wrapper">
|
||||
<div class="alert" id="published-alert">
|
||||
<p class="alert-message"><strong>You are editing a draft.</strong>
|
||||
% if published_date:
|
||||
This unit was originally published on ${published_date}.
|
||||
% endif
|
||||
</p>
|
||||
<a href="${published_preview_link}" target="_blank" class="alert-action secondary">Preview the published version</a>
|
||||
</div>
|
||||
<div class="main-column">
|
||||
<article class="unit-body window">
|
||||
<p class="unit-name-input"><label>Display Name:</label><input type="text" value="${unit.display_name}" class="unit-display-name-input" /></p>
|
||||
<ol class="components" data-id="${unit.location.url()}">
|
||||
<ol class="components">
|
||||
% for id in components:
|
||||
<li class="component" data-id="${id}"/>
|
||||
% endfor
|
||||
@@ -60,31 +69,26 @@
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="sidebar wip-box">
|
||||
<div class="sidebar">
|
||||
<div class="unit-properties window">
|
||||
<h4>Unit Properties</h4>
|
||||
<div class="window-contents">
|
||||
<div class="due-date-input row">
|
||||
<label>Due date:</label>
|
||||
<a href="#" class="set-date">Set a due date</a>
|
||||
<div class="date-setter">
|
||||
<p class="date-description"><input type="text" value="10/20/2012" class="date-input" /> <input type="text" value="6:00 am" class="time-input" />
|
||||
<a href="#" class="remove-date">Remove due date</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row visibility">
|
||||
<label class="inline-label">Visibility:</label>
|
||||
<select>
|
||||
<option>Public</option>
|
||||
<option>Private</option>
|
||||
<select id='visibility'>
|
||||
<option value="public">Public</option>
|
||||
<option value="private">Private</option>
|
||||
</select>
|
||||
</div>
|
||||
<a id="create-draft" href="#">This unit has been published. Click here to edit it.</a>
|
||||
<a id="publish-draft" href="#">This unit has already been published. Click here to release your changes to it</a>
|
||||
<div class="row status">
|
||||
<p>This unit is scheduled to be released to <strong>students</strong> on <strong>10/12/2012</strong> with the subsection <a href="#">"Administrivia and Circuit Elements."</a></p>
|
||||
<p>This unit is scheduled to be released to <strong>students</strong> on <strong>${subsection.start}</strong> with the subsection <a href="${reverse('edit_subsection', kwargs={'location': subsection.location})}">"${subsection.display_name}"</a></p>
|
||||
</div>
|
||||
<div class="row unit-actions">
|
||||
<a href="#" class="save-button">Save</a>
|
||||
<a href="${lms_link}" target="_blank" class="preview-button">Preview</a>
|
||||
<a id="save-draft" href="#" class="save-button">Save Draft</a>
|
||||
<a id="delete-draft" href="#" class="save-button">Delete Draft</a>
|
||||
<a href="${draft_preview_link}" target="_blank" class="preview-button">Preview</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,4 +118,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</%block>
|
||||
</%block>
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
<header class="primary-header">
|
||||
<nav class="inner-wrapper">
|
||||
<div class="left">
|
||||
<a href="/"><span class="home-icon"></span></a>
|
||||
<a href="/"><span class="home-icon"></span></a>
|
||||
<a href="#" class="class-name wip-box">6.002x Circuits and Electronics <span class="drop-icon">▾</span></a>
|
||||
<ul class="class-nav">
|
||||
<li><a href="overview-full.html" class="active">Courseware</a></li>
|
||||
<li><a href="updates.html" class="wip-box">Course Info</a></li>
|
||||
<li><a href="textbook.html" class="wip-box">Textbook</a></li>
|
||||
<li><a href="#" class="wip-box">Pages</a></li>
|
||||
<li><a href="#" class="wip-box">Assets</a></li>
|
||||
<li><a href="#" class="wip-box">Users</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="right">
|
||||
@@ -18,7 +19,6 @@
|
||||
% else:
|
||||
<a href="${reverse('login')}">Log in</a>
|
||||
% endif
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<h3>Metadata</h3>
|
||||
<ul>
|
||||
% for keyname in editable_metadata_fields:
|
||||
<li>${keyname}: <input type='text' data-metadata-name='${keyname}' value='${metadata[keyname]}' size='60' /></li>
|
||||
<li><label>${keyname}:</label> <input type='text' data-metadata-name='${keyname}' value='${metadata[keyname]}' size='60' /></li>
|
||||
% endfor
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%! from contentstore.utils import compute_unit_state %>
|
||||
|
||||
<!--
|
||||
This def will enumerate through a passed in subsection and list all of the units
|
||||
@@ -8,16 +9,16 @@ This def will enumerate through a passed in subsection and list all of the units
|
||||
% for unit in subsection.get_children():
|
||||
<li class="leaf unit" data-id="${unit.location}">
|
||||
<%
|
||||
unit_state = compute_unit_state(unit)
|
||||
if unit.location == selected:
|
||||
selected_class = 'editing'
|
||||
else:
|
||||
selected_class = ''
|
||||
%>
|
||||
<div class="section-item ${selected_class}">
|
||||
<a href="${reverse('edit_unit', args=[unit.location])}" class="private-item">
|
||||
<a href="${reverse('edit_unit', args=[unit.location])}" class="${unit_state}-item">
|
||||
<span class="${unit.category}-icon"></span>
|
||||
${unit.display_name}
|
||||
<span class="private-tag wip">- private</span>
|
||||
</a>
|
||||
% if actions:
|
||||
<div class="item-actions">
|
||||
|
||||
11
cms/urls.py
11
cms/urls.py
@@ -1,8 +1,6 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls import patterns, include, url
|
||||
|
||||
import django.contrib.auth.views
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
# from django.contrib import admin
|
||||
# admin.autodiscover()
|
||||
@@ -15,12 +13,15 @@ urlpatterns = ('',
|
||||
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
|
||||
url(r'^delete_item$', 'contentstore.views.delete_item', name='delete_item'),
|
||||
url(r'^clone_item$', 'contentstore.views.clone_item', name='clone_item'),
|
||||
url(r'^create_draft$', 'contentstore.views.create_draft', name='create_draft'),
|
||||
url(r'^publish_draft$', 'contentstore.views.publish_draft', name='publish_draft'),
|
||||
url(r'^unpublish_unit$', 'contentstore.views.unpublish_unit', name='unpublish_unit'),
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)$',
|
||||
'contentstore.views.course_index', name='course_index'),
|
||||
url(r'^github_service_hook$', 'github_sync.views.github_post_receive'),
|
||||
url(r'^preview/modx/(?P<preview_id>[^/]*)/(?P<location>.*?)/(?P<dispatch>[^/]*)$',
|
||||
'contentstore.views.preview_dispatch', name='preview_dispatch'),
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)/upload_asset$',
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)/upload_asset$',
|
||||
'contentstore.views.upload_asset', name='upload_asset'),
|
||||
url(r'^manage_users/(?P<location>.*?)$', 'contentstore.views.manage_users', name='manage_users'),
|
||||
url(r'^add_user/(?P<location>.*?)$',
|
||||
@@ -30,6 +31,8 @@ urlpatterns = ('',
|
||||
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)/remove_user$',
|
||||
'contentstore.views.remove_user', name='remove_user'),
|
||||
url(r'^assets/(?P<location>.*?)$', 'contentstore.views.asset_index', name='asset_index'),
|
||||
url(r'^pages/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.static_pages', name='static_pages'),
|
||||
url(r'^edit_static/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.edit_static', name='edit_static'),
|
||||
|
||||
# temporary landing page for a course
|
||||
url(r'^landing/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$', 'contentstore.views.landing', name='landing')
|
||||
@@ -54,6 +57,6 @@ urlpatterns += (
|
||||
|
||||
if settings.DEBUG:
|
||||
## Jasmine
|
||||
urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
|
||||
urlpatterns = urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
|
||||
|
||||
urlpatterns = patterns(*urlpatterns)
|
||||
|
||||
@@ -2,6 +2,8 @@ class @HTMLEditingDescriptor
|
||||
constructor: (@element) ->
|
||||
@edit_box = CodeMirror.fromTextArea($(".edit-box", @element)[0], {
|
||||
mode: "text/html"
|
||||
lineNumbers: true
|
||||
lineWrapping: true
|
||||
})
|
||||
|
||||
save: ->
|
||||
|
||||
@@ -2,6 +2,8 @@ class @JSONEditingDescriptor extends XModule.Descriptor
|
||||
constructor: (@element) ->
|
||||
@edit_box = CodeMirror.fromTextArea($(".edit-box", @element)[0], {
|
||||
mode: { name: "javascript", json: true }
|
||||
lineNumbers: true
|
||||
lineWrapping: true
|
||||
})
|
||||
|
||||
save: ->
|
||||
|
||||
@@ -2,6 +2,8 @@ class @XMLEditingDescriptor extends XModule.Descriptor
|
||||
constructor: (@element) ->
|
||||
@edit_box = CodeMirror.fromTextArea($(".edit-box", @element)[0], {
|
||||
mode: "xml"
|
||||
lineNumbers: true
|
||||
lineWrapping: true
|
||||
})
|
||||
|
||||
save: ->
|
||||
|
||||
@@ -240,11 +240,15 @@ class ModuleStore(object):
|
||||
An abstract interface for a database backend that stores XModuleDescriptor
|
||||
instances
|
||||
"""
|
||||
def has_item(self, location):
|
||||
"""
|
||||
Returns True if location exists in this ModuleStore.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_item(self, location, depth=0):
|
||||
"""
|
||||
Returns an XModuleDescriptor instance for the item at location.
|
||||
If location.revision is None, returns the item with the most
|
||||
recent revision
|
||||
|
||||
If any segment of the location is None except revision, raises
|
||||
xmodule.modulestore.exceptions.InsufficientSpecificationError
|
||||
@@ -345,7 +349,8 @@ class ModuleStore(object):
|
||||
Returns a list containing the top level XModuleDescriptors of the courses
|
||||
in this modulestore.
|
||||
'''
|
||||
raise NotImplementedError
|
||||
course_filter = Location("i4x", category="course")
|
||||
return self.get_items(course_filter)
|
||||
|
||||
def get_parent_locations(self, location):
|
||||
'''Find all locations that are the parents of this location. Needed
|
||||
|
||||
189
common/lib/xmodule/xmodule/modulestore/draft.py
Normal file
189
common/lib/xmodule/xmodule/modulestore/draft.py
Normal file
@@ -0,0 +1,189 @@
|
||||
from datetime import datetime
|
||||
|
||||
from . import ModuleStoreBase, Location
|
||||
from .exceptions import ItemNotFoundError
|
||||
|
||||
DRAFT = 'draft'
|
||||
|
||||
|
||||
def as_draft(location):
|
||||
"""
|
||||
Returns the Location that is the draft for `location`
|
||||
"""
|
||||
return Location(location)._replace(revision=DRAFT)
|
||||
|
||||
|
||||
def wrap_draft(item):
|
||||
"""
|
||||
Sets `item.metadata['is_draft']` to `True` if the item is a
|
||||
draft, and false otherwise. Sets the item's location to the
|
||||
non-draft location in either case
|
||||
"""
|
||||
item.metadata['is_draft'] = item.location.revision == DRAFT
|
||||
item.location = item.location._replace(revision=None)
|
||||
return item
|
||||
|
||||
|
||||
class DraftModuleStore(ModuleStoreBase):
|
||||
"""
|
||||
This mixin modifies a modulestore to give it draft semantics.
|
||||
That is, edits made to units are stored to locations that have the revision DRAFT,
|
||||
and when reads are made, they first read with revision DRAFT, and then fall back
|
||||
to the baseline revision only if DRAFT doesn't exist.
|
||||
|
||||
This module also includes functionality to promote DRAFT modules (and optionally
|
||||
their children) to published modules.
|
||||
"""
|
||||
|
||||
def get_item(self, location, depth=0):
|
||||
"""
|
||||
Returns an XModuleDescriptor instance for the item at location.
|
||||
If location.revision is None, returns the item with the most
|
||||
recent revision
|
||||
|
||||
If any segment of the location is None except revision, raises
|
||||
xmodule.modulestore.exceptions.InsufficientSpecificationError
|
||||
|
||||
If no object is found at that location, raises
|
||||
xmodule.modulestore.exceptions.ItemNotFoundError
|
||||
|
||||
location: Something that can be passed to Location
|
||||
|
||||
depth (int): An argument that some module stores may use to prefetch
|
||||
descendents of the queried modules for more efficient results later
|
||||
in the request. The depth is counted in the number of calls to
|
||||
get_children() to cache. None indicates to cache all descendents
|
||||
"""
|
||||
try:
|
||||
return wrap_draft(super(DraftModuleStore, self).get_item(as_draft(location), depth))
|
||||
except ItemNotFoundError:
|
||||
return wrap_draft(super(DraftModuleStore, self).get_item(location, depth))
|
||||
|
||||
def get_instance(self, course_id, location):
|
||||
"""
|
||||
Get an instance of this location, with policy for course_id applied.
|
||||
TODO (vshnayder): this may want to live outside the modulestore eventually
|
||||
"""
|
||||
try:
|
||||
return wrap_draft(super(DraftModuleStore, self).get_instance(course_id, as_draft(location)))
|
||||
except ItemNotFoundError:
|
||||
return wrap_draft(super(DraftModuleStore, self).get_instance(course_id, location))
|
||||
|
||||
def get_items(self, location, depth=0):
|
||||
"""
|
||||
Returns a list of XModuleDescriptor instances for the items
|
||||
that match location. Any element of location that is None is treated
|
||||
as a wildcard that matches any value
|
||||
|
||||
location: Something that can be passed to Location
|
||||
|
||||
depth: An argument that some module stores may use to prefetch
|
||||
descendents of the queried modules for more efficient results later
|
||||
in the request. The depth is counted in the number of calls to
|
||||
get_children() to cache. None indicates to cache all descendents
|
||||
"""
|
||||
draft_loc = as_draft(location)
|
||||
draft_items = super(DraftModuleStore, self).get_items(draft_loc, depth)
|
||||
items = super(DraftModuleStore, self).get_items(location, depth)
|
||||
|
||||
draft_locs_found = set(item.location._replace(revision=None) for item in draft_items)
|
||||
non_draft_items = [
|
||||
item
|
||||
for item in items
|
||||
if (item.location.revision != DRAFT
|
||||
and item.location._replace(revision=None) not in draft_locs_found)
|
||||
]
|
||||
return [wrap_draft(item) for item in draft_items + non_draft_items]
|
||||
|
||||
def clone_item(self, source, location):
|
||||
"""
|
||||
Clone a new item that is a copy of the item at the location `source`
|
||||
and writes it to `location`
|
||||
"""
|
||||
return wrap_draft(super(DraftModuleStore, self).clone_item(source, as_draft(location)))
|
||||
|
||||
def update_item(self, location, data):
|
||||
"""
|
||||
Set the data in the item specified by the location to
|
||||
data
|
||||
|
||||
location: Something that can be passed to Location
|
||||
data: A nested dictionary of problem data
|
||||
"""
|
||||
draft_loc = as_draft(location)
|
||||
draft_item = self.get_item(location)
|
||||
if not draft_item.metadata['is_draft']:
|
||||
self.clone_item(location, draft_loc)
|
||||
|
||||
return super(DraftModuleStore, self).update_item(draft_loc, data)
|
||||
|
||||
def update_children(self, location, children):
|
||||
"""
|
||||
Set the children for the item specified by the location to
|
||||
children
|
||||
|
||||
location: Something that can be passed to Location
|
||||
children: A list of child item identifiers
|
||||
"""
|
||||
draft_loc = as_draft(location)
|
||||
draft_item = self.get_item(location)
|
||||
if not draft_item.metadata['is_draft']:
|
||||
self.clone_item(location, draft_loc)
|
||||
|
||||
return super(DraftModuleStore, self).update_children(draft_loc, children)
|
||||
|
||||
def update_metadata(self, location, metadata):
|
||||
"""
|
||||
Set the metadata for the item specified by the location to
|
||||
metadata
|
||||
|
||||
location: Something that can be passed to Location
|
||||
metadata: A nested dictionary of module metadata
|
||||
"""
|
||||
draft_loc = as_draft(location)
|
||||
draft_item = self.get_item(location)
|
||||
|
||||
if not draft_item.metadata['is_draft']:
|
||||
self.clone_item(location, draft_loc)
|
||||
|
||||
if 'is_draft' in metadata:
|
||||
del metadata['is_draft']
|
||||
|
||||
return super(DraftModuleStore, self).update_metadata(draft_loc, metadata)
|
||||
|
||||
def delete_item(self, location):
|
||||
"""
|
||||
Delete an item from this modulestore
|
||||
|
||||
location: Something that can be passed to Location
|
||||
"""
|
||||
return super(DraftModuleStore, self).delete_item(as_draft(location))
|
||||
|
||||
def get_parent_locations(self, location):
|
||||
'''Find all locations that are the parents of this location. Needed
|
||||
for path_to_location().
|
||||
|
||||
returns an iterable of things that can be passed to Location.
|
||||
'''
|
||||
return super(DraftModuleStore, self).get_parent_locations(location)
|
||||
|
||||
def publish(self, location, published_by_id):
|
||||
"""
|
||||
Save a current draft to the underlying modulestore
|
||||
"""
|
||||
draft = self.get_item(location)
|
||||
metadata = {}
|
||||
metadata.update(draft.metadata)
|
||||
metadata['published_date'] = tuple(datetime.utcnow().timetuple())
|
||||
metadata['published_by'] = published_by_id
|
||||
super(DraftModuleStore, self).update_item(location, draft.definition.get('data', {}))
|
||||
super(DraftModuleStore, self).update_children(location, draft.definition.get('children', []))
|
||||
super(DraftModuleStore, self).update_metadata(location, metadata)
|
||||
self.delete_item(location)
|
||||
|
||||
def unpublish(self, location):
|
||||
"""
|
||||
Turn the published version into a draft, removing the published version
|
||||
"""
|
||||
super(DraftModuleStore, self).clone_item(location, as_draft(location))
|
||||
super(DraftModuleStore, self).delete_item(location)
|
||||
@@ -13,6 +13,7 @@ from xmodule.mako_module import MakoDescriptorSystem
|
||||
from xmodule.error_module import ErrorDescriptor
|
||||
|
||||
from . import ModuleStoreBase, Location
|
||||
from .draft import DraftModuleStore
|
||||
from .exceptions import (ItemNotFoundError,
|
||||
DuplicateItemError)
|
||||
|
||||
@@ -69,17 +70,21 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
)
|
||||
|
||||
|
||||
def location_to_query(location):
|
||||
def location_to_query(location, wildcard=True):
|
||||
"""
|
||||
Takes a Location and returns a SON object that will query for that location.
|
||||
Fields in location that are None are ignored in the query
|
||||
|
||||
If `wildcard` is True, then a None in a location is treated as a wildcard
|
||||
query. Otherwise, it is searched for literally
|
||||
"""
|
||||
query = SON()
|
||||
# Location dict is ordered by specificity, and SON
|
||||
# will preserve that order for queries
|
||||
for key, val in Location(location).dict().iteritems():
|
||||
if val is not None:
|
||||
query['_id.{key}'.format(key=key)] = val
|
||||
if wildcard and val is None:
|
||||
continue
|
||||
query['_id.{key}'.format(key=key)] = val
|
||||
|
||||
return query
|
||||
|
||||
@@ -202,18 +207,27 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
ItemNotFoundError.
|
||||
'''
|
||||
item = self.collection.find_one(
|
||||
location_to_query(location),
|
||||
location_to_query(location, wildcard=False),
|
||||
sort=[('revision', pymongo.ASCENDING)],
|
||||
)
|
||||
if item is None:
|
||||
raise ItemNotFoundError(location)
|
||||
return item
|
||||
|
||||
def has_item(self, location):
|
||||
"""
|
||||
Returns True if location exists in this ModuleStore.
|
||||
"""
|
||||
location = Location.ensure_fully_specified(location)
|
||||
try:
|
||||
self._find_one(location)
|
||||
return True
|
||||
except ItemNotFoundError:
|
||||
return False
|
||||
|
||||
def get_item(self, location, depth=0):
|
||||
"""
|
||||
Returns an XModuleDescriptor instance for the item at location.
|
||||
If location.revision is None, returns the item with the most
|
||||
recent revision.
|
||||
|
||||
If any segment of the location is None except revision, raises
|
||||
xmodule.modulestore.exceptions.InsufficientSpecificationError
|
||||
@@ -321,16 +335,10 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
'''Find all locations that are the parents of this location. Needed
|
||||
for path_to_location().
|
||||
|
||||
If there is no data at location in this modulestore, raise
|
||||
ItemNotFoundError.
|
||||
|
||||
returns an iterable of things that can be passed to Location. This may
|
||||
be empty if there are no parents.
|
||||
'''
|
||||
location = Location.ensure_fully_specified(location)
|
||||
# Check that it's actually in this modulestore.
|
||||
self._find_one(location)
|
||||
# now get the parents
|
||||
items = self.collection.find({'definition.children': location.url()},
|
||||
{'_id': True})
|
||||
return [i['_id'] for i in items]
|
||||
@@ -341,3 +349,8 @@ class MongoModuleStore(ModuleStoreBase):
|
||||
are loaded on demand, rather than up front
|
||||
"""
|
||||
return {}
|
||||
|
||||
|
||||
# DraftModuleStore is first, because it needs to intercept calls to MongoModuleStore
|
||||
class DraftMongoModuleStore(DraftModuleStore, MongoModuleStore):
|
||||
pass
|
||||
|
||||
@@ -60,10 +60,8 @@ def path_to_location(modulestore, course_id, location):
|
||||
(loc, path) = queue.pop() # Takes from the end
|
||||
loc = Location(loc)
|
||||
|
||||
# get_parent_locations should raise ItemNotFoundError if location
|
||||
# isn't found so we don't have to do it explicitly. Call this
|
||||
# first to make sure the location is there (even if it's a course, and
|
||||
# we would otherwise immediately exit).
|
||||
# Call get_parent_locations first to make sure the location is there
|
||||
# (even if it's a course, and we would otherwise immediately exit).
|
||||
parents = modulestore.get_parent_locations(loc)
|
||||
|
||||
# print 'Processing loc={0}, path={1}'.format(loc, path)
|
||||
@@ -81,6 +79,9 @@ def path_to_location(modulestore, course_id, location):
|
||||
# If we're here, there is no path
|
||||
return None
|
||||
|
||||
if not modulestore.has_item(location):
|
||||
raise ItemNotFoundError
|
||||
|
||||
path = find_path_to_course()
|
||||
if path is None:
|
||||
raise NoPathToItem(location)
|
||||
|
||||
@@ -477,11 +477,16 @@ class XMLModuleStore(ModuleStoreBase):
|
||||
except KeyError:
|
||||
raise ItemNotFoundError(location)
|
||||
|
||||
def has_item(self, location):
|
||||
"""
|
||||
Returns True if location exists in this ModuleStore.
|
||||
"""
|
||||
location = Location(location)
|
||||
return any(location in course_modules for course_modules in self.modules.values())
|
||||
|
||||
def get_item(self, location, depth=0):
|
||||
"""
|
||||
Returns an XModuleDescriptor instance for the item at location.
|
||||
If location.revision is None, returns the most item with the most
|
||||
recent revision
|
||||
|
||||
If any segment of the location is None except revision, raises
|
||||
xmodule.modulestore.exceptions.InsufficientSpecificationError
|
||||
@@ -545,14 +550,8 @@ class XMLModuleStore(ModuleStoreBase):
|
||||
'''Find all locations that are the parents of this location. Needed
|
||||
for path_to_location().
|
||||
|
||||
If there is no data at location in this modulestore, raise
|
||||
ItemNotFoundError.
|
||||
|
||||
returns an iterable of things that can be passed to Location. This may
|
||||
be empty if there are no parents.
|
||||
'''
|
||||
location = Location.ensure_fully_specified(location)
|
||||
if not self.parent_tracker.is_known(location):
|
||||
raise ItemNotFoundError(location)
|
||||
|
||||
return self.parent_tracker.parents(location)
|
||||
|
||||
@@ -75,6 +75,6 @@ def update_templates():
|
||||
), exc_info=True)
|
||||
continue
|
||||
|
||||
modulestore().update_item(template_location, template.data)
|
||||
modulestore().update_children(template_location, template.children)
|
||||
modulestore().update_metadata(template_location, template.metadata)
|
||||
modulestore('direct').update_item(template_location, template.data)
|
||||
modulestore('direct').update_children(template_location, template.children)
|
||||
modulestore('direct').update_metadata(template_location, template.metadata)
|
||||
|
||||
@@ -409,7 +409,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
|
||||
|
||||
# cdodge: this is a list of metadata names which are 'system' metadata
|
||||
# and should not be edited by an end-user
|
||||
system_metadata_fields = [ 'data_dir' ]
|
||||
system_metadata_fields = ['data_dir', 'published_date', 'published_by']
|
||||
|
||||
# A list of descriptor attributes that must be equal for the descriptors to
|
||||
# be equal
|
||||
|
||||
104
common/static/js/vendor/date.js
vendored
Normal file
104
common/static/js/vendor/date.js
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Version: 1.0 Alpha-1
|
||||
* Build Date: 13-Nov-2007
|
||||
* Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved.
|
||||
* License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
|
||||
* Website: http://www.datejs.com/ or http://www.coolite.com/datejs/
|
||||
*/
|
||||
Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}};
|
||||
Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
|
||||
return-1;};Date.getDayNumberFromName=function(name){var n=Date.CultureInfo.dayNames,m=Date.CultureInfo.abbreviatedDayNames,o=Date.CultureInfo.shortestDayNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
|
||||
return-1;};Date.isLeapYear=function(year){return(((year%4===0)&&(year%100!==0))||(year%400===0));};Date.getDaysInMonth=function(year,month){return[31,(Date.isLeapYear(year)?29:28),31,30,31,30,31,31,30,31,30,31][month];};Date.getTimezoneOffset=function(s,dst){return(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST[s.toUpperCase()]:Date.CultureInfo.abbreviatedTimeZoneStandard[s.toUpperCase()];};Date.getTimezoneAbbreviation=function(offset,dst){var n=(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST:Date.CultureInfo.abbreviatedTimeZoneStandard,p;for(p in n){if(n[p]===offset){return p;}}
|
||||
return null;};Date.prototype.clone=function(){return new Date(this.getTime());};Date.prototype.compareTo=function(date){if(isNaN(this)){throw new Error(this);}
|
||||
if(date instanceof Date&&!isNaN(date)){return(this>date)?1:(this<date)?-1:0;}else{throw new TypeError(date);}};Date.prototype.equals=function(date){return(this.compareTo(date)===0);};Date.prototype.between=function(start,end){var t=this.getTime();return t>=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};Date.prototype.addSeconds=function(value){return this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return this;};Date.prototype.addYears=function(value){return this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof config=="number"){this._orient=config;return this;}
|
||||
var x=config;if(x.millisecond||x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);}
|
||||
if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);}
|
||||
if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);}
|
||||
if(x.hour||x.hours){this.addHours(x.hour||x.hours);}
|
||||
if(x.month||x.months){this.addMonths(x.month||x.months);}
|
||||
if(x.year||x.years){this.addYears(x.year||x.years);}
|
||||
if(x.day||x.days){this.addDays(x.day||x.days);}
|
||||
return this;};Date._validate=function(value,min,max,name){if(typeof value!="number"){throw new TypeError(value+" is not a Number.");}else if(value<min||value>max){throw new RangeError(value+" is not a valid value for "+name+".");}
|
||||
return true;};Date.validateMillisecond=function(n){return Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return Date._validate(n,0,11,"months");};Date.validateYear=function(n){return Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;}
|
||||
if(!x.second&&x.second!==0){x.second=-1;}
|
||||
if(!x.minute&&x.minute!==0){x.minute=-1;}
|
||||
if(!x.hour&&x.hour!==0){x.hour=-1;}
|
||||
if(!x.day&&x.day!==0){x.day=-1;}
|
||||
if(!x.month&&x.month!==0){x.month=-1;}
|
||||
if(!x.year&&x.year!==0){x.year=-1;}
|
||||
if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());}
|
||||
if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());}
|
||||
if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());}
|
||||
if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());}
|
||||
if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());}
|
||||
if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());}
|
||||
if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());}
|
||||
if(x.timezone){this.setTimezone(x.timezone);}
|
||||
if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);}
|
||||
return this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};Date.prototype.isLeapYear=function(){var y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||this.is().sun());};Date.prototype.getDaysInMonth=function(){return Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var diff=(day-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};Date.prototype.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};Date.prototype.getDayOfYear=function(){return Math.floor((this-new Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new Date(y,0,1).getDay();if(offset==8){offset=1;}
|
||||
var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}}
|
||||
return w;};Date.prototype.isDST=function(){console.log('isDST');return this.toString().match(/(E|C|M|P)(S|D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return this;};Date.prototype.setTimezone=function(s){return this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var self=this;var p=function p(s){return(s.toString().length==1)?"0"+s:s;};return format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g,function(format){switch(format){case"hh":return p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return p(self.getHours());case"H":return self.getHours();case"mm":return p(self.getMinutes());case"m":return self.getMinutes();case"ss":return p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return self.getFullYear();case"yy":return self.getFullYear().toString().substring(2,4);case"dddd":return self.getDayName();case"ddd":return self.getDayName(true);case"dd":return p(self.getDate());case"d":return self.getDate().toString();case"MMMM":return self.getMonthName();case"MMM":return self.getMonthName(true);case"MM":return p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();};
|
||||
Date.now=function(){return new Date();};Date.today=function(){return Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var c={};c[this._dateElement]=this;return Date.now().add(c);};Number.prototype.ago=function(){var c={};c[this._dateElement]=this*-1;return Date.now().add(c);};(function(){var $D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),de;var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;}
|
||||
return this.moveToDayOfWeek(n,this._orient);};};for(var i=0;i<dx.length;i++){$D[dx[i]]=$D[dx[i].substring(0,3)]=df(i);}
|
||||
var mf=function(n){return function(){if(this._is){this._is=false;return this.getMonth()===n;}
|
||||
return this.moveToMonth(n,this._orient);};};for(var j=0;j<mx.length;j++){$D[mx[j]]=$D[mx[j].substring(0,3)]=mf(j);}
|
||||
var ef=function(j){return function(){if(j.substring(j.length-1)!="s"){j+="s";}
|
||||
return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k<px.length;k++){de=px[k].toLowerCase();$D[de]=$D[de+"s"]=ef(px[k]);$N[de]=$N[de+"s"]=nf(de);}}());Date.prototype.toJSONString=function(){return this.toString("yyyy-MM-ddThh:mm:ssZ");};Date.prototype.toShortDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortDatePattern);};Date.prototype.toLongDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.longDatePattern);};Date.prototype.toShortTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortTimePattern);};Date.prototype.toLongTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.longTimePattern);};Date.prototype.getOrdinal=function(){switch(this.getDate()){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};
|
||||
(function(){Date.Parsing={Exception:function(s){this.message="Parse error at '"+s.substring(0,10)+" ...'";}};var $P=Date.Parsing;var _=$P.Operators={rtoken:function(r){return function(s){var mx=s.match(r);if(mx){return([mx[0],s.substring(mx[0].length)]);}else{throw new $P.Exception(s);}};},token:function(s){return function(s){return _.rtoken(new RegExp("^\s*"+s+"\s*"))(s);};},stoken:function(s){return _.rtoken(new RegExp("^"+s));},until:function(p){return function(s){var qx=[],rx=null;while(s.length){try{rx=p.call(this,s);}catch(e){qx.push(rx[0]);s=rx[1];continue;}
|
||||
break;}
|
||||
return[qx,s];};},many:function(p){return function(s){var rx=[],r=null;while(s.length){try{r=p.call(this,s);}catch(e){return[rx,s];}
|
||||
rx.push(r[0]);s=r[1];}
|
||||
return[rx,s];};},optional:function(p){return function(s){var r=null;try{r=p.call(this,s);}catch(e){return[null,s];}
|
||||
return[r[0],r[1]];};},not:function(p){return function(s){try{p.call(this,s);}catch(e){return[null,s];}
|
||||
throw new $P.Exception(s);};},ignore:function(p){return p?function(s){var r=null;r=p.call(this,s);return[null,r[1]];}:null;},product:function(){var px=arguments[0],qx=Array.prototype.slice.call(arguments,1),rx=[];for(var i=0;i<px.length;i++){rx.push(_.each(px[i],qx));}
|
||||
return rx;},cache:function(rule){var cache={},r=null;return function(s){try{r=cache[s]=(cache[s]||rule.call(this,s));}catch(e){r=cache[s]=e;}
|
||||
if(r instanceof $P.Exception){throw r;}else{return r;}};},any:function(){var px=arguments;return function(s){var r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
|
||||
try{r=(px[i].call(this,s));}catch(e){r=null;}
|
||||
if(r){return r;}}
|
||||
throw new $P.Exception(s);};},each:function(){var px=arguments;return function(s){var rx=[],r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
|
||||
try{r=(px[i].call(this,s));}catch(e){throw new $P.Exception(s);}
|
||||
rx.push(r[0]);s=r[1];}
|
||||
return[rx,s];};},all:function(){var px=arguments,_=_;return _.each(_.optional(px));},sequence:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;if(px.length==1){return px[0];}
|
||||
return function(s){var r=null,q=null;var rx=[];for(var i=0;i<px.length;i++){try{r=px[i].call(this,s);}catch(e){break;}
|
||||
rx.push(r[0]);try{q=d.call(this,r[1]);}catch(ex){q=null;break;}
|
||||
s=q[1];}
|
||||
if(!r){throw new $P.Exception(s);}
|
||||
if(q){throw new $P.Exception(q[1]);}
|
||||
if(c){try{r=c.call(this,r[1]);}catch(ey){throw new $P.Exception(r[1]);}}
|
||||
return[rx,(r?r[1]:s)];};},between:function(d1,p,d2){d2=d2||d1;var _fn=_.each(_.ignore(d1),p,_.ignore(d2));return function(s){var rx=_fn.call(this,s);return[[rx[0][0],r[0][2]],rx[1]];};},list:function(p,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return(p instanceof Array?_.each(_.product(p.slice(0,-1),_.ignore(d)),p.slice(-1),_.ignore(c)):_.each(_.many(_.each(p,_.ignore(d))),px,_.ignore(c)));},set:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return function(s){var r=null,p=null,q=null,rx=null,best=[[],s],last=false;for(var i=0;i<px.length;i++){q=null;p=null;r=null;last=(px.length==1);try{r=px[i].call(this,s);}catch(e){continue;}
|
||||
rx=[[r[0]],r[1]];if(r[1].length>0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;}
|
||||
if(!last&&q[1].length===0){last=true;}
|
||||
if(!last){var qx=[];for(var j=0;j<px.length;j++){if(i!=j){qx.push(px[j]);}}
|
||||
p=_.set(qx,d).call(this,q[1]);if(p[0].length>0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}}
|
||||
if(rx[1].length<best[1].length){best=rx;}
|
||||
if(best[1].length===0){break;}}
|
||||
if(best[0].length===0){return best;}
|
||||
if(c){try{q=c.call(this,best[1]);}catch(ey){throw new $P.Exception(best[1]);}
|
||||
best[1]=q[1];}
|
||||
return best;};},forward:function(gr,fname){return function(s){return gr[fname].call(this,s);};},replace:function(rule,repl){return function(s){var r=rule.call(this,s);return[repl,r[1]];};},process:function(rule,fn){return function(s){var r=rule.call(this,s);return[fn.call(this,r[0]),r[1]];};},min:function(min,rule){return function(s){var rx=rule.call(this,s);if(rx[0].length<min){throw new $P.Exception(s);}
|
||||
return rx;};}};var _generator=function(op){return function(){var args=null,rx=[];if(arguments.length>1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];}
|
||||
if(args){for(var i=0,px=args.shift();i<px.length;i++){args.unshift(px[i]);rx.push(op.apply(null,args));args.shift();return rx;}}else{return op.apply(null,arguments);}};};var gx="optional not ignore cache".split(/\s/);for(var i=0;i<gx.length;i++){_[gx[i]]=_generator(_[gx[i]]);}
|
||||
var _vector=function(op){return function(){if(arguments[0]instanceof Array){return op.apply(null,arguments[0]);}else{return op.apply(null,arguments);}};};var vx="each any all".split(/\s/);for(var j=0;j<vx.length;j++){_[vx[j]]=_vector(_[vx[j]]);}}());(function(){var flattenAndCompact=function(ax){var rx=[];for(var i=0;i<ax.length;i++){if(ax[i]instanceof Array){rx=rx.concat(flattenAndCompact(ax[i]));}else{if(ax[i]){rx.push(ax[i]);}}}
|
||||
return rx;};Date.Grammar={};Date.Translator={hour:function(s){return function(){this.hour=Number(s);};},minute:function(s){return function(){this.minute=Number(s);};},second:function(s){return function(){this.second=Number(s);};},meridian:function(s){return function(){this.meridian=s.slice(0,1).toLowerCase();};},timezone:function(s){return function(){var n=s.replace(/[^\d\+\-]/g,"");if(n.length){this.timezoneOffset=Number(n);}else{this.timezone=s.toLowerCase();}};},day:function(x){var s=x[0];return function(){this.day=Number(s.match(/\d+/)[0]);};},month:function(s){return function(){this.month=((s.length==3)?Date.getMonthNumberFromName(s):(Number(s)-1));};},year:function(s){return function(){var n=Number(s);this.year=((s.length>2)?n:(n+(((n+2000)<Date.CultureInfo.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];var now=new Date();this.year=now.getFullYear();this.month=now.getMonth();this.day=1;this.hour=0;this.minute=0;this.second=0;for(var i=0;i<x.length;i++){if(x[i]){x[i].call(this);}}
|
||||
this.hour=(this.meridian=="p"&&this.hour<13)?this.hour+12:this.hour;if(this.day>Date.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");}
|
||||
var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});}
|
||||
return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;}
|
||||
for(var i=0;i<x.length;i++){if(typeof x[i]=="function"){x[i].call(this);}}
|
||||
if(this.now){return new Date();}
|
||||
var today=Date.today();var method=null;var expression=!!(this.days!=null||this.orient||this.operator);if(expression){var gap,mod,orient;orient=((this.orient=="past"||this.operator=="subtract")?-1:1);if(this.weekday){this.unit="day";gap=(Date.getDayNumberFromName(this.weekday)-today.getDay());mod=7;this.days=gap?((gap+(orient*mod))%mod):(orient*mod);}
|
||||
if(this.month){this.unit="month";gap=(this.month-today.getMonth());mod=12;this.months=gap?((gap+(orient*mod))%mod):(orient*mod);this.month=null;}
|
||||
if(!this.unit){this.unit="day";}
|
||||
if(this[this.unit+"s"]==null||this.operator!=null){if(!this.value){this.value=1;}
|
||||
if(this.unit=="week"){this.unit="day";this.value=this.value*7;}
|
||||
this[this.unit+"s"]=this.value*orient;}
|
||||
return today.add(this);}else{if(this.meridian&&this.hour){this.hour=(this.hour<13&&this.meridian=="p")?this.hour+12:this.hour;}
|
||||
if(this.weekday&&!this.day){this.day=(today.addDays((Date.getDayNumberFromName(this.weekday)-today.getDay()))).getDate();}
|
||||
if(this.month&&!this.day){this.day=1;}
|
||||
return today.set(this);}}};var _=Date.Parsing.Operators,g=Date.Grammar,t=Date.Translator,_fn;g.datePartDelimiter=_.rtoken(/^([\s\-\.\,\/\x27]+)/);g.timePartDelimiter=_.stoken(":");g.whiteSpace=_.rtoken(/^\s*/);g.generalDelimiter=_.rtoken(/^(([\s\,]|at|on)+)/);var _C={};g.ctoken=function(keys){var fn=_C[keys];if(!fn){var c=Date.CultureInfo.regexPatterns;var kx=keys.split(/\s+/),px=[];for(var i=0;i<kx.length;i++){px.push(_.replace(_.rtoken(c[kx[i]]),kx[i]));}
|
||||
fn=_C[keys]=_.any.apply(null,px);}
|
||||
return fn;};g.ctoken2=function(key){return _.rtoken(Date.CultureInfo.regexPatterns[key]);};g.h=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2]|[1-9])/),t.hour));g.hh=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2])/),t.hour));g.H=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|[0-9])/),t.hour));g.HH=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3])/),t.hour));g.m=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.minute));g.mm=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.minute));g.s=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.second));g.ss=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.second));g.hms=_.cache(_.sequence([g.H,g.mm,g.ss],g.timePartDelimiter));g.t=_.cache(_.process(g.ctoken2("shortMeridian"),t.meridian));g.tt=_.cache(_.process(g.ctoken2("longMeridian"),t.meridian));g.z=_.cache(_.process(_.rtoken(/^(\+|\-)?\s*\d\d\d\d?/),t.timezone));g.zz=_.cache(_.process(_.rtoken(/^(\+|\-)\s*\d\d\d\d/),t.timezone));g.zzz=_.cache(_.process(g.ctoken2("timezone"),t.timezone));g.timeSuffix=_.each(_.ignore(g.whiteSpace),_.set([g.tt,g.zzz]));g.time=_.each(_.optional(_.ignore(_.stoken("T"))),g.hms,g.timeSuffix);g.d=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1]|\d)/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.dd=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1])/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.ddd=g.dddd=_.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),function(s){return function(){this.weekday=s;};}));g.M=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d|\d)/),t.month));g.MM=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d)/),t.month));g.MMM=g.MMMM=_.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"),t.month));g.y=_.cache(_.process(_.rtoken(/^(\d\d?)/),t.year));g.yy=_.cache(_.process(_.rtoken(/^(\d\d)/),t.year));g.yyy=_.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/),t.year));g.yyyy=_.cache(_.process(_.rtoken(/^(\d\d\d\d)/),t.year));_fn=function(){return _.each(_.any.apply(null,arguments),_.not(g.ctoken2("timeContext")));};g.day=_fn(g.d,g.dd);g.month=_fn(g.M,g.MMM);g.year=_fn(g.yyyy,g.yy);g.orientation=_.process(g.ctoken("past future"),function(s){return function(){this.orient=s;};});g.operator=_.process(g.ctoken("add subtract"),function(s){return function(){this.operator=s;};});g.rday=_.process(g.ctoken("yesterday tomorrow today now"),t.rday);g.unit=_.process(g.ctoken("minute hour day week month year"),function(s){return function(){this.unit=s;};});g.value=_.process(_.rtoken(/^\d\d?(st|nd|rd|th)?/),function(s){return function(){this.value=s.replace(/\D/g,"");};});g.expression=_.set([g.rday,g.operator,g.value,g.unit,g.orientation,g.ddd,g.MMM]);_fn=function(){return _.set(arguments,g.datePartDelimiter);};g.mdy=_fn(g.ddd,g.month,g.day,g.year);g.ymd=_fn(g.ddd,g.year,g.month,g.day);g.dmy=_fn(g.ddd,g.day,g.month,g.year);g.date=function(s){return((g[Date.CultureInfo.dateElementOrder]||g.mdy).call(this,s));};g.format=_.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),function(fmt){if(g[fmt]){return g[fmt];}else{throw Date.Parsing.Exception(fmt);}}),_.process(_.rtoken(/^[^dMyhHmstz]+/),function(s){return _.ignore(_.stoken(s));}))),function(rules){return _.process(_.each.apply(null,rules),t.finishExact);});var _F={};var _get=function(f){return _F[f]=(_F[f]||g.format(f)[0]);};g.formats=function(fx){if(fx instanceof Array){var rx=[];for(var i=0;i<fx.length;i++){rx.push(_get(fx[i]));}
|
||||
return _.any.apply(null,rx);}else{return _get(fx);}};g._formats=g.formats(["yyyy-MM-ddTHH:mm:ss","ddd, MMM dd, yyyy H:mm:ss tt","ddd MMM d yyyy HH:mm:ss zzz","d"]);g._start=_.process(_.set([g.date,g.time,g.expression],g.generalDelimiter,g.whiteSpace),t.finish);g.start=function(s){try{var r=g._formats.call({},s);if(r[1].length===0){return r;}}catch(e){}
|
||||
return g._start.call({},s);};}());Date._parse=Date.parse;Date.parse=function(s){var r=null;if(!s){return null;}
|
||||
try{r=Date.Grammar.start.call({},s);}catch(e){return null;}
|
||||
return((r[1].length===0)?r[0]:null);};Date.getParseFunction=function(fx){var fn=Date.Grammar.formats(fx);return function(s){var r=null;try{r=fn.call({},s);}catch(e){return null;}
|
||||
return((r[1].length===0)?r[0]:null);};};Date.parseExact=function(s,fx){return Date.getParseFunction(fx)(s);};
|
||||
@@ -57,7 +57,7 @@ def mongo_store_config(data_dir):
|
||||
'OPTIONS': {
|
||||
'default_class': 'xmodule.raw_module.RawDescriptor',
|
||||
'host': 'localhost',
|
||||
'db': 'xmodule',
|
||||
'db': 'test_xmodule',
|
||||
'collection': 'modulestore',
|
||||
'fs_root': data_dir,
|
||||
'render_template': 'mitxmako.shortcuts.render_to_string',
|
||||
|
||||
@@ -4,16 +4,18 @@ Settings for the LMS that runs alongside the CMS on AWS
|
||||
|
||||
from ..dev import *
|
||||
|
||||
modulestore_options = {
|
||||
'default_class': 'xmodule.raw_module.RawDescriptor',
|
||||
'host': 'localhost',
|
||||
'db': 'xmodule',
|
||||
'collection': 'modulestore',
|
||||
'fs_root': DATA_DIR,
|
||||
'render_template': 'mitxmako.shortcuts.render_to_string',
|
||||
}
|
||||
|
||||
MODULESTORE = {
|
||||
'default': {
|
||||
'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore',
|
||||
'OPTIONS': {
|
||||
'default_class': 'xmodule.raw_module.RawDescriptor',
|
||||
'host': 'localhost',
|
||||
'db': 'xmodule',
|
||||
'collection': 'modulestore',
|
||||
'fs_root': DATA_DIR,
|
||||
'render_template': 'mitxmako.shortcuts.render_to_string',
|
||||
}
|
||||
}
|
||||
'OPTIONS': modulestore_options
|
||||
},
|
||||
}
|
||||
|
||||
12
lms/envs/cms/preview_dev.py
Normal file
12
lms/envs/cms/preview_dev.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Settings for the LMS that runs alongside the CMS on AWS
|
||||
"""
|
||||
|
||||
from .dev import *
|
||||
|
||||
MODULESTORE = {
|
||||
'default': {
|
||||
'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore',
|
||||
'OPTIONS': modulestore_options
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user