Commit f6fb64f1 by cahrens

Saving checkbox state, client template for individual checklist.

parent 1aa8786b
...@@ -1103,11 +1103,7 @@ def course_info_updates(request, org, course, provided_id=None): ...@@ -1103,11 +1103,7 @@ def course_info_updates(request, org, course, provided_id=None):
if not has_access(request.user, location): if not has_access(request.user, location):
raise PermissionDenied() raise PermissionDenied()
# NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!! real_method = get_request_method(request)
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
else:
real_method = request.method
if request.method == 'GET': if request.method == 'GET':
return HttpResponse(json.dumps(get_course_updates(location)), mimetype="application/json") return HttpResponse(json.dumps(get_course_updates(location)), mimetype="application/json")
...@@ -1130,11 +1126,7 @@ def module_info(request, module_location): ...@@ -1130,11 +1126,7 @@ def module_info(request, module_location):
if not has_access(request.user, location): if not has_access(request.user, location):
raise PermissionDenied() raise PermissionDenied()
# NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!! real_method = get_request_method(request)
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
else:
real_method = request.method
rewrite_static_links = request.GET.get('rewrite_url_links', 'True') in ['True', 'true'] rewrite_static_links = request.GET.get('rewrite_url_links', 'True') in ['True', 'true']
logging.debug('rewrite_static_links = {0} {1}'.format(request.GET.get('rewrite_url_links', 'False'), rewrite_static_links)) logging.debug('rewrite_static_links = {0} {1}'.format(request.GET.get('rewrite_url_links', 'False'), rewrite_static_links))
...@@ -1259,21 +1251,18 @@ def course_grader_updates(request, org, course, name, grader_index=None): ...@@ -1259,21 +1251,18 @@ def course_grader_updates(request, org, course, name, grader_index=None):
location = get_location_and_verify_access(request, org, course, name) location = get_location_and_verify_access(request, org, course, name)
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META: real_method = get_request_method(request)
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
else:
real_method = request.method
if real_method == 'GET': if real_method == 'GET':
# Cannot just do a get w/o knowing the course name :-( # Cannot just do a get w/o knowing the course name :-(
return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(['i4x', org, course, 'course', name]), grader_index)), return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(location), grader_index)),
mimetype="application/json") mimetype="application/json")
elif real_method == "DELETE": elif real_method == "DELETE":
# ??? Shoudl this return anything? Perhaps success fail? # ??? Should this return anything? Perhaps success fail?
CourseGradingModel.delete_grader(Location(['i4x', org, course, 'course', name]), grader_index) CourseGradingModel.delete_grader(Location(location), grader_index)
return HttpResponse() return HttpResponse()
elif request.method == 'POST': # post or put, doesn't matter. elif request.method == 'POST': # post or put, doesn't matter.
return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(['i4x', org, course, 'course', name]), request.POST)), return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(location), request.POST)),
mimetype="application/json") mimetype="application/json")
...@@ -1289,11 +1278,7 @@ def course_advanced_updates(request, org, course, name): ...@@ -1289,11 +1278,7 @@ def course_advanced_updates(request, org, course, name):
""" """
location = get_location_and_verify_access(request, org, course, name) location = get_location_and_verify_access(request, org, course, name)
# NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!! real_method = get_request_method(request)
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
else:
real_method = request.method
if real_method == 'GET': if real_method == 'GET':
return HttpResponse(json.dumps(CourseMetadata.fetch(location)), mimetype="application/json") return HttpResponse(json.dumps(CourseMetadata.fetch(location)), mimetype="application/json")
...@@ -1320,13 +1305,32 @@ def get_checklists(request, org, course, name): ...@@ -1320,13 +1305,32 @@ def get_checklists(request, org, course, name):
course_module.metadata[key] = template_module.metadata[key] course_module.metadata[key] = template_module.metadata[key]
modulestore.update_metadata(location, course_module.metadata) modulestore.update_metadata(location, course_module.metadata)
checklists = course_module.metadata[key]
return render_to_response('checklists.html', return render_to_response('checklists.html',
{ {
'context_course': course_module, 'context_course': course_module,
'checklists' : course_module.metadata[key] 'checklists' : checklists,
'checklists_json' : json.dumps(checklists)
}) })
@login_required
def update_checklist(request, org, course, name, checklist_index=None):
location = get_location_and_verify_access(request, org, course, name)
modulestore = get_modulestore(location)
course_module = modulestore.get_item(location)
key = "checklists"
real_method = get_request_method(request)
if checklist_index is not None and (real_method == 'POST' or real_method == 'PUT'):
modified_checklist = json.loads(request.body)
(course_module.metadata[key])[int(checklist_index)] = modified_checklist
modulestore.update_metadata(location, course_module.metadata)
return HttpResponse(json.dumps(modified_checklist), mimetype="application/json")
elif request.method == 'GET':
# TODO: Would we ever get in this condition? Any point in having this code?
return HttpResponse(json.dumps(course_module.metadata[key]), mimetype="application/json")
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
...@@ -1593,3 +1597,12 @@ def get_location_and_verify_access(request, org, course, name): ...@@ -1593,3 +1597,12 @@ def get_location_and_verify_access(request, org, course, name):
raise PermissionDenied() raise PermissionDenied()
return location return location
def get_request_method(request):
# NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!!
if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
else:
real_method = request.method
return real_method
<section class="course-checklist" id="<%= 'course-checklist' + checklistIndex %>">
<% var widthPercentage = 'width:' + percentChecked + '%;'; %>
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value" style="<%= widthPercentage %>">
<span class="int"><%= percentChecked %></span>% of checklist completed</span></span>
<header>
<h3 class="checklist-title title-2 is-selectable" title="Collapse/Expand this Checklist">
<i class="ss-icon ss-symbolicons-standard icon-arrow ui-toggle-expansion">&#x25BE;</i>
<%= checklistShortDescription %></h3>
<span class="checklist-status status">
Tasks Completed: <span class="status-count"><%= itemsChecked %></span>/<span class="status-amount"><%= items.length %></span>
<i class="ss-icon ss-symbolicons-standard icon-confirm">&#x2713;</i>
</span>
</header>
<ul class="list list-tasks">
<% var taskIndex = 0; %>
<% _.each(items, function(item) { %>
<% var checked = item['is_checked']; %>
<li
<% if (checked) { %>
class="task is-completed"
<% } else { %>
class="task"
<% } %>
>
<% var taskId = 'course-checklist' + checklistIndex + '-task' + taskIndex; %>
<input type="checkbox" class="task-input" data-checklist="<%= checklistIndex %>" data-task="<%= taskIndex %>"
name="<%= taskId %>" id="<%= taskId %>"
<% if (checked) { %>
checked="checked"
<% } %>
>
<label class="task-details" for="<%= taskId %>">
<h4 class="task-name title title-3"><%= item['short_description'] %></h4>
<p class="task-description"><%= item['long_description'] %></p>
</label>
<% if (item['action_text'] !== '' && item['action_url'] !== '') { %>
<ul class="list-actions task-actions">
<li>
<a href="<%= item['action_url'] %>" class="action action-primary"><%= item['action_text'] %></a>
</li>
</ul>
<% } %>
</li>
<% taskIndex+=1; }) %>
</ul>
</section>
\ No newline at end of file
...@@ -14,10 +14,6 @@ $(document).ready(function () { ...@@ -14,10 +14,6 @@ $(document).ready(function () {
// scopes (namely the course-info tab) // scopes (namely the course-info tab)
window.$modalCover = $modalCover; window.$modalCover = $modalCover;
// Control whether template caching in local memory occurs (see template_loader.js). Caching screws up development but may
// be a good optimization in production (it works fairly well)
window.cachetemplates = false;
$body.append($modalCover); $body.append($modalCover);
$newComponentItem = $('.new-component-item'); $newComponentItem = $('.new-component-item');
$newComponentTypePicker = $('.new-component'); $newComponentTypePicker = $('.new-component');
......
if (!CMS.Models['Checklists']) CMS.Models.Checklists = new Object(); CMS.Models.Checklist = Backbone.Model.extend({
\ No newline at end of file });
CMS.Models.ChecklistCollection = Backbone.Collection.extend({
model : CMS.Models.Checklist,
parse: function(response) {
_.each(response,
function( element, idx ) {
element.id = idx;
});
return response;
}
});
// <!-- from https://github.com/Gazler/Underscore-Template-Loader/blob/master/index.html --> // <!-- from https://github.com/Gazler/Underscore-Template-Loader/blob/master/index.html -->
// TODO Figure out how to initialize w/ static views from server (don't call .load but instead inject in django as strings) // TODO Figure out how to initialize w/ static views from server (don't call .load but instead inject in django as strings)
// so this only loads the lazily loaded ones. // so this only loads the lazily loaded ones.
(function() { (function () {
if (typeof window.templateLoader == 'function') return; if (typeof window.templateLoader == 'function') return;
var templateLoader = { var templateLoader = {
templateVersion: "0.0.15", templateVersion: "0.0.15",
templates: {}, templates: {},
loadRemoteTemplate: function(templateName, filename, callback) { // Control whether template caching in local memory occurs. Caching screws up development but may
if (!this.templates[templateName]) { // be a good optimization in production (it works fairly well).
var self = this; cacheTemplates: false,
jQuery.ajax({url : filename, loadRemoteTemplate: function (templateName, filename, callback) {
success : function(data) { if (!this.templates[templateName]) {
self.addTemplate(templateName, data); var self = this;
self.saveLocalTemplates(); jQuery.ajax({url: filename,
callback(data); success: function (data) {
}, self.addTemplate(templateName, data);
error : function(xhdr, textStatus, errorThrown) { self.saveLocalTemplates();
console.log(textStatus); }, callback(data);
dataType : "html" },
}) error: function (xhdr, textStatus, errorThrown) {
} console.log(textStatus);
else { },
callback(this.templates[templateName]); dataType: "html"
} })
}, }
else {
addTemplate: function(templateName, data) { callback(this.templates[templateName]);
// is there a reason this doesn't go ahead and compile the template? _.template(data) }
// I suppose localstorage use would still req raw string rather than compiled version, but that sd work },
// if it maintains a separate cache of uncompiled ones
this.templates[templateName] = data; addTemplate: function (templateName, data) {
}, // is there a reason this doesn't go ahead and compile the template? _.template(data)
// I suppose localstorage use would still req raw string rather than compiled version, but that sd work
localStorageAvailable: function() { // if it maintains a separate cache of uncompiled ones
try { this.templates[templateName] = data;
// window.cachetemplates is global set in base.js to turn caching on/off },
return window.cachetemplates && 'localStorage' in window && window['localStorage'] !== null;
} catch (e) { localStorageAvailable: function () {
return false; try {
} return this.cacheTemplates && 'localStorage' in window && window['localStorage'] !== null;
}, } catch (e) {
return false;
saveLocalTemplates: function() { }
if (this.localStorageAvailable) { },
localStorage.setItem("templates", JSON.stringify(this.templates));
localStorage.setItem("templateVersion", this.templateVersion); saveLocalTemplates: function () {
} if (this.localStorageAvailable()) {
}, localStorage.setItem("templates", JSON.stringify(this.templates));
localStorage.setItem("templateVersion", this.templateVersion);
loadLocalTemplates: function() { }
if (this.localStorageAvailable) { },
var templateVersion = localStorage.getItem("templateVersion");
if (templateVersion && templateVersion == this.templateVersion) { loadLocalTemplates: function () {
var templates = localStorage.getItem("templates"); if (this.localStorageAvailable()) {
if (templates) { var templateVersion = localStorage.getItem("templateVersion");
templates = JSON.parse(templates); if (templateVersion && templateVersion == this.templateVersion) {
for (var x in templates) { var templates = localStorage.getItem("templates");
if (!this.templates[x]) { if (templates) {
this.addTemplate(x, templates[x]); templates = JSON.parse(templates);
for (var x in templates) {
if (!this.templates[x]) {
this.addTemplate(x, templates[x]);
}
}
}
}
else {
localStorage.removeItem("templates");
localStorage.removeItem("templateVersion");
} }
}
} }
}
else {
localStorage.removeItem("templates");
localStorage.removeItem("templateVersion");
}
} }
}
}; };
templateLoader.loadLocalTemplates(); templateLoader.loadLocalTemplates();
window.templateLoader = templateLoader; window.templateLoader = templateLoader;
})(); })();
...@@ -5,44 +5,79 @@ CMS.Views.Checklists = Backbone.View.extend({ ...@@ -5,44 +5,79 @@ CMS.Views.Checklists = Backbone.View.extend({
events : { events : {
'click .course-checklist .checklist-title' : "toggleChecklist", 'click .course-checklist .checklist-title' : "toggleChecklist",
'click .course-checklist .task label' : "toggleTask", 'click .course-checklist .task input' : "toggleTask"
'click .demo-checklistviz' : "demoUpdateProgress"
}, },
initialize : function() { initialize : function() {
// adding class and title needs to happen in HTML var self = this;
// $('.course-checklist .checklist-title').each(function(e){ // instantiates an editor template for each update in the collection
// $(this).addClass('is-selectable').attr('title','Collapse/Expand this Checklist').bind('click', this.toggleChecklist); window.templateLoader.loadRemoteTemplate("checklist",
// }); "/static/client_templates/checklist.html",
function (raw_template) {
self.template = _.template(raw_template);
self.render();
}
);
}, },
toggleChecklist : function(e) { render: function() {
(e).preventDefault(); // catch potential outside call before template loaded
$(e.target).closest('.course-checklist').toggleClass('is-collapsed'); if (!this.template) return this;
this.$el.empty();
var self = this;
_.each(this.collection.models,
function(checklist, index) {
self.$el.append(self.renderTemplate(checklist, index));
});
return this;
}, },
toggleTask : function (e) { renderTemplate: function (checklist, index) {
$(e.target).closest('.task').toggleClass('is-completed'); var checklistItems = checklist.attributes['items'];
var itemsChecked = 0;
_.each(checklistItems,
function(checklist) {
if (checklist['is_checked']) {
itemsChecked +=1;
}
});
var percentChecked = Math.round((itemsChecked/checklistItems.length)*100);
return this.template({
checklistIndex : index,
checklistShortDescription : checklist.attributes['short_description'],
items: checklistItems,
itemsChecked: itemsChecked,
percentChecked: percentChecked});
}, },
// TODO: remove toggleChecklist : function(e) {
demoUpdateProgress : function(e) {
(e).preventDefault(); (e).preventDefault();
$('#course-checklist0 .viz-checklist-status .viz-checklist-status-value').css('width','25%'); $(e.target).closest('.course-checklist').toggleClass('is-collapsed');
}, },
// TODO: not used. In-progress update checklist progress (based on checkbox check/uncheck events) toggleTask : function (e) {
updateChecklistProgress : function(e) { var self = this;
var $statusCount = this.$el.closest('.course-checklist').find('.status-count');
var $statusAmount = this.$el.closest('.course-checklist').find('.status-amount');
if (this.$el.attr('checked')) { var completed = 'is-completed';
console.log('adding'); var $checkbox = $(e.target);
} var $task = $checkbox.closest('.task');
$task.toggleClass(completed);
else { var checklist_index = $checkbox.data('checklist');
console.log('subtracting'); var task_index = $checkbox.data('task');
} var model = this.collection.at(checklist_index);
model.attributes.items[task_index].is_checked = $task.hasClass(completed);
model.save({},
{
success : function() {
var updatedTemplate = self.renderTemplate(model, checklist_index);
self.$el.find('#course-checklist'+checklist_index).first().replaceWith(updatedTemplate);
},
error : CMS.ServerError
});
} }
}); });
\ No newline at end of file
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %>
<%block name="title">Course Checklists</%block> <%block name="title">Course Checklists</%block>
<%block name="bodyclass">is-signedin course uxdesign checklists</%block> <%block name="bodyclass">is-signedin course uxdesign checklists</%block>
<%namespace name='static' file='static_content.html'/> <%namespace name='static' file='static_content.html'/>
<%block name="jsextra"> <%block name="jsextra">
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/checklists_view.js')}"></script> <script type="text/javascript" src="${static.url('js/views/checklists_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/checklists.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript"> <script type="text/javascript">
$(document).ready(function () { $(document).ready(function () {
// TODO: do a fetch instead of passing text into constructor.
var checklistCollection = new CMS.Models.ChecklistCollection(${checklists_json | n}, {parse: true});
checklistCollection.url = "${reverse('checklists_updates', kwargs=dict(org=context_course.location.org, course=context_course.location.course, name=context_course.location.name))}";
var editor = new CMS.Views.Checklists({ var editor = new CMS.Views.Checklists({
el: $('.course-checklists') el: $('.course-checklists'),
collection: checklistCollection
}); });
// No need to call render yet-- state is not being injected. editor.render();
// editor.render();
}); });
...@@ -42,46 +49,6 @@ ...@@ -42,46 +49,6 @@
<article class="content-primary" role="main"> <article class="content-primary" role="main">
<form id="course-checklists" class="course-checklists" method="post" action=""> <form id="course-checklists" class="course-checklists" method="post" action="">
<h2 class="title title-3 sr">Current Checklists</h2> <h2 class="title title-3 sr">Current Checklists</h2>
% for checklist in checklists:
<section class="course-checklist" id=${'course-checklist' + str(loop.index)}>
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value"><span class="int">0</span>% of checklist completed</span></span>
<header>
<h3 class="checklist-title title-2 is-selectable" title="Collapse/Expand this Checklist">
<i class="ss-icon ss-symbolicons-standard icon-arrow ui-toggle-expansion">&#x25BE;</i>
${checklist['short_description']}</h3>
<span class="checklist-status status">
Tasks Completed: <span class="status-count">0</span>/<span class="status-amount">4</span>
<i class="ss-icon ss-symbolicons-standard icon-confirm">&#x2713;</i>
</span>
</header>
<ul class="list list-tasks">
% for item in checklist['items']:
<li class="task">
<input type="checkbox" class="task-input" name=${'course-checklist' + str(loop.parent.index) + '-task' + str(loop.index)}
id=${'course-checklist' + str(loop.parent.index) + '-task' + str(loop.index)}
value=${item['is_checked']}>
<label class="task-details" for=${'course-checklist' + str(loop.parent.index) + '-task' + str(loop.index)}>
<h4 class="task-name title title-3">${item['short_description']}</h4>
<p class="task-description">${item['long_description']}</p>
</label>
% if item['action_text'] is not '' and item['action_url'] is not '':
<ul class="list-actions task-actions">
<li>
<a href=${item['action_url']} class="action action-primary">${item['action_text']}</a>
</li>
</ul>
% endif
</li>
% endfor
</ul>
</section>
% endfor
</form> </form>
</article> </article>
......
...@@ -84,6 +84,8 @@ urlpatterns = ('', ...@@ -84,6 +84,8 @@ urlpatterns = ('',
# User creation and updating views # User creation and updating views
urlpatterns += ( urlpatterns += (
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/checklists/(?P<name>[^/]+)$', 'contentstore.views.get_checklists', name='checklists'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/checklists/(?P<name>[^/]+)$', 'contentstore.views.get_checklists', name='checklists'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/checklists/(?P<name>[^/]+)/update(/)?(?P<checklist_index>.+)?.*$',
'contentstore.views.update_checklist', name='checklists_updates'),
url(r'^howitworks$', 'contentstore.views.howitworks', name='howitworks'), url(r'^howitworks$', 'contentstore.views.howitworks', name='howitworks'),
url(r'^signup$', 'contentstore.views.signup', name='signup'), url(r'^signup$', 'contentstore.views.signup', name='signup'),
......
...@@ -23,10 +23,10 @@ metadata: ...@@ -23,10 +23,10 @@ metadata:
"long_description": "They'll help you learn the other course authoring tools available to you, and will also help you find help when you need it.", "long_description": "They'll help you learn the other course authoring tools available to you, and will also help you find help when you need it.",
"is_checked": false, "is_checked": false,
"action_url": "", "action_url": "",
"action_text": ""}], "action_text": ""}]
"completed" : false}, },
{"short_description" : "Draft a Rough Course Outline", {"short_description" : "Draft a Rough Course Outline",
"items" : [{"short_description": "Create your first Section and Subsection", "items" : [{"short_description": "Create your first Section and Subsection",
"long_description": "Walk through the mechanics of building your course's first section and subsection through your course outline to start.", "long_description": "Walk through the mechanics of building your course's first section and subsection through your course outline to start.",
"is_checked": false, "is_checked": false,
"action_url": "/course/", "action_url": "/course/",
...@@ -60,10 +60,10 @@ metadata: ...@@ -60,10 +60,10 @@ metadata:
"long_description": "Some course authors find creating a section for unsorted, in-progress work useful. To do this, create a section and set the release date to the distant future.", "long_description": "Some course authors find creating a section for unsorted, in-progress work useful. To do this, create a section and set the release date to the distant future.",
"is_checked": false, "is_checked": false,
"action_url": "/course/", "action_url": "/course/",
"action_text": "Edit in Course Outline"}], "action_text": "Edit in Course Outline"}]
"completed" : false}, },
{"short_description" : "Explore edX's Support Tools", {"short_description" : "Explore edX's Support Tools",
"items" : [{"short_description": "Explore the Studio Help Forum", "items" : [{"short_description": "Explore the Studio Help Forum",
"long_description": "Access the Studio Help forum from the menu that appears when you click your user name in the top right corner of Studio.", "long_description": "Access the Studio Help forum from the menu that appears when you click your user name in the top right corner of Studio.",
"is_checked": false, "is_checked": false,
"action_url": "http://help.edge.edx.org/", "action_url": "http://help.edge.edx.org/",
...@@ -77,10 +77,10 @@ metadata: ...@@ -77,10 +77,10 @@ metadata:
"long_description": "View the searchable Studio documentation to find answers to your questions or information about how to do specific tasks. Once you download the PDF, you can view it offline.", "long_description": "View the searchable Studio documentation to find answers to your questions or information about how to do specific tasks. Once you download the PDF, you can view it offline.",
"is_checked": false, "is_checked": false,
"action_url": "/", "action_url": "/",
"action_text": "Download Documentation"}], "action_text": "Download Documentation"}]
"completed" : false}, },
{"short_description" : "Draft your Course Introduction", {"short_description" : "Draft your Course Introduction",
"items" : [{"short_description": "Drafting a Course Description", "items" : [{"short_description": "Drafting a Course Description",
"long_description": "Courses on edX each have their own introduction page, including a course video, description, and more. Draft the introduction students will read before deciding to enroll in your course.", "long_description": "Courses on edX each have their own introduction page, including a course video, description, and more. Draft the introduction students will read before deciding to enroll in your course.",
"is_checked": false, "is_checked": false,
"action_url": "/settings-details/", "action_url": "/settings-details/",
...@@ -99,8 +99,8 @@ metadata: ...@@ -99,8 +99,8 @@ metadata:
"long_description": "Before a student jumps into a course without the necessary preparation, we'd like them to understand the prerequisites that will make them more likely to succeed.", "long_description": "Before a student jumps into a course without the necessary preparation, we'd like them to understand the prerequisites that will make them more likely to succeed.",
"is_checked": false, "is_checked": false,
"action_url": "/settings-details/", "action_url": "/settings-details/",
"action_text": "Edit Course Details &amp; Schedule"}], "action_text": "Edit Course Details &amp; Schedule"}]
"completed" : false} }
] ]
data: { 'textbooks' : [ ], 'wiki_slug' : null } data: { 'textbooks' : [ ], 'wiki_slug' : null }
children: [] children: []
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