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)
|
||||
|
||||
Reference in New Issue
Block a user