Commit bfb49133 by Calen Pennington

Merge pull request #843 from MITx/feature/cdodge/subsection-edit-page

Feature/cdodge/subsection edit page
parents c6cf4b5b d57aa315
...@@ -161,12 +161,28 @@ def edit_subsection(request, location): ...@@ -161,12 +161,28 @@ def edit_subsection(request, location):
if item.location.category != 'sequential': if item.location.category != 'sequential':
return HttpResponseBadRequest 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', return render_to_response('edit_subsection.html',
{'subsection': item, {'subsection': item,
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'), '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
}) })
...@@ -485,9 +501,12 @@ def save_item(request): ...@@ -485,9 +501,12 @@ def save_item(request):
# update existing metadata with submitted metadata (which can be partial) # 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' # 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(): 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 # let's strip out any metadata fields from the postback which have been identified as system metadata
if posted_metadata[metadata_key] is None and metadata_key not in existing_item.system_metadata_fields: # 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 # remove both from passed in collection as well as the collection read in from the modulestore
if metadata_key in existing_item.metadata: if metadata_key in existing_item.metadata:
del existing_item.metadata[metadata_key] del existing_item.metadata[metadata_key]
......
...@@ -36,8 +36,40 @@ $(document).ready(function() { ...@@ -36,8 +36,40 @@ $(document).ready(function() {
$('.set-date').bind('click', showDateSetter); $('.set-date').bind('click', showDateSetter);
$('.remove-date').bind('click', removeDateSetter); $('.remove-date').bind('click', removeDateSetter);
// add/remove policy metadata button click handlers
$('.add-policy-data').bind('click', addPolicyMetadata);
$('.remove-policy-data').bind('click', removePolicyMetadata);
$('.sync-date').bind('click', syncReleaseDate);
}); });
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 // This method only changes the ordering of the child objects in a subsection
function onUnitReordered() { function onUnitReordered() {
var subsection_id = $(this).data('subsection-id'); var subsection_id = $(this).data('subsection-id');
...@@ -86,7 +118,7 @@ function saveSubsection(e) { ...@@ -86,7 +118,7 @@ function saveSubsection(e) {
var id = $(this).data('id'); 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]'); var metadata_fields = $('input[data-metadata-name]');
metadata = {}; metadata = {};
...@@ -95,6 +127,20 @@ function saveSubsection(e) { ...@@ -95,6 +127,20 @@ function saveSubsection(e) {
metadata[$(el).data("metadata-name")] = el.value; 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 // 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 // NOTE: our various "date/time" metadata elements don't always utilize the same formatting string
......
...@@ -132,13 +132,15 @@ label { ...@@ -132,13 +132,15 @@ label {
} }
.new-unit-item, .new-unit-item,
.new-subsection-item { .new-subsection-item,
.new-policy-item {
@include grey-button; @include grey-button;
margin: 5px 8px; margin: 5px 8px;
padding: 3px 10px 4px 10px; padding: 3px 10px 4px 10px;
font-size: 10px; font-size: 10px;
.new-folder-icon, .new-folder-icon,
.new-policy-icon,
.new-unit-icon { .new-unit-icon {
position: relative; position: relative;
top: 2px; top: 2px;
......
...@@ -170,6 +170,14 @@ ...@@ -170,6 +170,14 @@
background: url(../img/new-unit-icon.png) right no-repeat; 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 { .textbook-icon {
display: inline-block; display: inline-block;
width: 32px; width: 32px;
...@@ -243,4 +251,4 @@ ...@@ -243,4 +251,4 @@
margin-left: 10px; margin-left: 10px;
vertical-align: middle; vertical-align: middle;
background: url(../img/blue-spinner.gif) no-repeat; background: url(../img/blue-spinner.gif) no-repeat;
} }
\ No newline at end of file
...@@ -19,25 +19,42 @@ ...@@ -19,25 +19,42 @@
<div class="inner-wrapper"> <div class="inner-wrapper">
<div class="main-column"> <div class="main-column">
<article class="subsection-body window"> <article class="subsection-body window">
<div class="subsection-name-input"> <div class="subsection-name-input">
<label>Display Name:</label> <label>Display Name:</label>
<input type="text" value="${subsection.metadata['display_name']}" class="subsection-display-name-input" data-metadata-name="display_name"/> <input type="text" value="${subsection.metadata['display_name']}" class="subsection-display-name-input" data-metadata-name="display_name"/>
</div> </div>
<div> <div>
<label>Format:</label> <label>Format:</label>
<input type="text" value="${subsection.metadata['format'] if 'format' in subsection.metadata else ''}" class="unit-subtitle" data-metadata-name="subtitle"/> <input type="text" value="${subsection.metadata['format'] if 'format' in subsection.metadata else ''}" class="unit-subtitle" data-metadata-name="format"/>
</div> </div>
<div class="unit-list"> <div class="unit-list">
<label>Units:</label> <label>Units:</label>
${units.enum_units(subsection)} ${units.enum_units(subsection)}
</div> </div>
<div class='wip-box'> <div>
<label>Policy:</label> <label>Policy:</label>
<textarea class="text-editor">Policy blah, blah, blah…</textarea> <ol class='policy-list'>
</div> % 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"/>:&nbsp;<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> </article>
</div> </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"/>:&nbsp;<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="sidebar">
<div class="unit-properties window"> <div class="unit-properties window">
<h4>Subsection Settings</h4> <h4>Subsection Settings</h4>
...@@ -46,12 +63,15 @@ ...@@ -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> <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"> <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_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" 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_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> </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>
<div class="due-date-input row"> <div class="due-date-input row">
<label>Due date:</label> <label>Due date:</label>
...@@ -62,8 +82,8 @@ ...@@ -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 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 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_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" 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_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> <a href="#" class="remove-date">Remove due date</a>
</p> </p>
</div> </div>
...@@ -85,6 +105,7 @@ ...@@ -85,6 +105,7 @@
<script src="${static.url('js/vendor/date.js')}"></script> <script src="${static.url('js/vendor/date.js')}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function() { $(document).ready(function() {
// expand the due-date area if the values are set
if ($('#due_date').val() != '') { if ($('#due_date').val() != '') {
var $block = $('.set-date').closest('.due-date-input'); var $block = $('.set-date').closest('.due-date-input');
$('.set-date').hide(); $('.set-date').hide();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment