Commit 9cd1162a by Don Mitchell

Grading and details split out. Put the unimplemented tabs into a single

file not used.
parent 5674dd19
...@@ -1109,8 +1109,31 @@ def get_course_settings(request, org, course, name): ...@@ -1109,8 +1109,31 @@ def get_course_settings(request, org, course, name):
course_details = CourseDetails.fetch(location) course_details = CourseDetails.fetch(location)
return render_to_response('settings.html', { return render_to_response('settings.html', {
'active_tab': 'settings',
'context_course': course_module, 'context_course': course_module,
'course_location' : location,
'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder)
})
@login_required
@ensure_csrf_cookie
def course_config_graders_page(request, org, course, name):
"""
Send models and views as well as html for editing the course settings to the client.
org, course, name: Attributes of the Location for the item to edit
"""
location = ['i4x', org, course, 'course', name]
# check that logged in user has permissions to this item
if not has_access(request.user, location):
raise PermissionDenied()
course_module = modulestore().get_item(location)
course_details = CourseGradingModel.fetch(location)
return render_to_response('settings_graders.html', {
'context_course': course_module,
'course_location' : location,
'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder) 'course_details' : json.dumps(course_details, cls=CourseSettingsEncoder)
}) })
......
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object(); if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseDetails = Backbone.Model.extend({ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
defaults: { defaults: {
location : null, // the course's Location model, required location : null, // the course's Location model, required
start_date: null, // maps to 'start' start_date: null, // maps to 'start'
end_date: null, // maps to 'end' end_date: null, // maps to 'end'
enrollment_start: null, enrollment_start: null,
enrollment_end: null, enrollment_end: null,
syllabus: null, syllabus: null,
overview: "", overview: "",
intro_video: null, intro_video: null,
effort: null // an int or null effort: null // an int or null
}, },
// When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset) // When init'g from html script, ensure you pass {parse: true} as an option (2nd arg to reset)
parse: function(attributes) { parse: function(attributes) {
if (attributes['course_location']) { if (attributes['course_location']) {
attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true}); attributes.location = new CMS.Models.Location(attributes.course_location, {parse:true});
} }
if (attributes['start_date']) { if (attributes['start_date']) {
attributes.start_date = new Date(attributes.start_date); attributes.start_date = new Date(attributes.start_date);
} }
if (attributes['end_date']) { if (attributes['end_date']) {
attributes.end_date = new Date(attributes.end_date); attributes.end_date = new Date(attributes.end_date);
} }
if (attributes['enrollment_start']) { if (attributes['enrollment_start']) {
attributes.enrollment_start = new Date(attributes.enrollment_start); attributes.enrollment_start = new Date(attributes.enrollment_start);
} }
if (attributes['enrollment_end']) { if (attributes['enrollment_end']) {
attributes.enrollment_end = new Date(attributes.enrollment_end); attributes.enrollment_end = new Date(attributes.enrollment_end);
} }
return attributes; return attributes;
}, },
validate: function(newattrs) { validate: function(newattrs) {
// Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs // Returns either nothing (no return call) so that validate works or an object of {field: errorstring} pairs
// A bit funny in that the video key validation is asynchronous; so, it won't stop the validation. // A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
var errors = {}; var errors = {};
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) { if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
errors.end_date = "The course end date cannot be before the course start date."; errors.end_date = "The course end date cannot be before the course start date.";
} }
if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) { if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) {
errors.enrollment_start = "The course start date cannot be before the enrollment start date."; errors.enrollment_start = "The course start date cannot be before the enrollment start date.";
} }
if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) { if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date."; errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date.";
} }
if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) { if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) {
errors.enrollment_end = "The enrollment end date cannot be after the course end date."; errors.enrollment_end = "The enrollment end date cannot be after the course end date.";
} }
if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) { if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
if (this._videokey_illegal_chars.exec(newattrs.intro_video)) { if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
errors.intro_video = "Key should only contain letters, numbers, _, or -"; errors.intro_video = "Key should only contain letters, numbers, _, or -";
} }
// TODO check if key points to a real video using google's youtube api // TODO check if key points to a real video using google's youtube api
} }
if (!_.isEmpty(errors)) return errors; if (!_.isEmpty(errors)) return errors;
// NOTE don't return empty errors as that will be interpreted as an error state // NOTE don't return empty errors as that will be interpreted as an error state
}, },
url: function() { url: function() {
var location = this.get('location'); var location = this.get('location');
return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details'; return '/' + location.get('org') + "/" + location.get('course') + '/settings/' + location.get('name') + '/section/details';
}, },
_videokey_illegal_chars : /[^a-zA-Z0-9_-]/g, _videokey_illegal_chars : /[^a-zA-Z0-9_-]/g,
save_videosource: function(newsource) { save_videosource: function(newsource) {
// newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string // newsource either is <video youtube="speed:key, *"/> or just the "speed:key, *" string
// returns the videosource for the preview which iss the key whose speed is closest to 1 // returns the videosource for the preview which iss the key whose speed is closest to 1
if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null}, if (_.isEmpty(newsource) && !_.isEmpty(this.get('intro_video'))) this.save({'intro_video': null},
{ error : CMS.ServerError}); { error : CMS.ServerError});
// TODO remove all whitespace w/in string // TODO remove all whitespace w/in string
else { else {
if (this.get('intro_video') !== newsource) this.save('intro_video', newsource, if (this.get('intro_video') !== newsource) this.save('intro_video', newsource,
{ error : CMS.ServerError}); { error : CMS.ServerError});
} }
return this.videosourceSample(); return this.videosourceSample();
}, },
videosourceSample : function() { videosourceSample : function() {
if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video'); if (this.has('intro_video')) return "http://www.youtube.com/embed/" + this.get('intro_video');
else return ""; else return "";
} }
}); });
if (!CMS.Models['Settings']) CMS.Models.Settings = new Object(); if (!CMS.Models['Settings']) CMS.Models.Settings = new Object();
CMS.Models.Settings.CourseSettings = Backbone.Model.extend({ CMS.Models.Settings.CourseSettings = Backbone.Model.extend({
// a container for the models representing the n possible tabbed states // a container for the models representing the n possible tabbed states
defaults: { defaults: {
courseLocation: null, courseLocation: null,
// NOTE: keep these sync'd w/ the data-section names in settings-page-menu details: null,
details: null, faculty: null,
faculty: null, grading: null,
grading: null, problems: null,
problems: null, discussions: null
discussions: null },
},
retrieve: function(submodel, callback) { retrieve: function(submodel, callback) {
if (this.get(submodel)) callback(); if (this.get(submodel)) callback();
else { else {
var cachethis = this; var cachethis = this;
switch (submodel) { switch (submodel) {
case 'details': case 'details':
var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')}); var details = new CMS.Models.Settings.CourseDetails({location: this.get('courseLocation')});
details.fetch( { details.fetch( {
success : function(model) { success : function(model) {
cachethis.set('details', model); cachethis.set('details', model);
callback(model); callback(model);
} }
}); });
break; break;
case 'grading': case 'grading':
var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')}); var grading = new CMS.Models.Settings.CourseGradingPolicy({course_location: this.get('courseLocation')});
grading.fetch( { grading.fetch( {
success : function(model) { success : function(model) {
cachethis.set('grading', model); cachethis.set('grading', model);
callback(model); callback(model);
} }
}); });
break; break;
default: default:
break; break;
} }
} }
} }
}) })
\ No newline at end of file
<%inherit file="base.html" />
<%block name="title">Grading</%block>
<%block name="bodyclass">is-signedin course settings</%block>
<%namespace name='static' file='static_content.html'/>
<%!
from contentstore import utils
%>
<%block name="jsextra">
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/server_error.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/course_relative.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/validating_view.js')}"></script>
<script type="text/javascript" src="${static.url('js/models/settings/course_grading_policy.js')}"></script>
<script type="text/javascript" src="${static.url('js/views/settings/settings_grading_view.js')}"></script>
<script type="text/javascript">
$(document).ready(function(){
var editor = new CMS.Views.Settings.Grading({
el: $('.settings-grading'),
model : new CMS.Models.Settings.CourseGradingPolicy(${course_details|n},{parse:true})
});
editor.render();
});
</script>
</%block>
<%block name="content">
<!-- -->
<div class="main-wrapper">
<div class="inner-wrapper">
<h1>Settings</h1>
<article class="settings-overview">
<div class="settings-page-section main-column">
<section class="settings-grading is-shown">
<h2 class="title">Grading</h2>
<section class="settings-grading-range">
<header>
<h3>Overall Grade Range</h3>
<span class="detail">Course grade ranges and their values</span>
</header>
<div class="row">
<div class="grade-controls course-grading-range well">
<a href="#" class="new-grade-button"><span class="plus-icon"></span></a>
<div class="grade-slider">
<div class="grade-bar">
<ol class="increments">
<li class="increment-0">0</li>
<li class="increment-10">10</li>
<li class="increment-20">20</li>
<li class="increment-30">30</li>
<li class="increment-40">40</li>
<li class="increment-50">50</li>
<li class="increment-60">60</li>
<li class="increment-70">70</li>
<li class="increment-80">80</li>
<li class="increment-90">90</li>
<li class="increment-100">100</li>
</ol>
<ol class="grades">
</ol>
</div>
</div>
</div>
</div>
</section>
<section class="settings-grading-general">
<header>
<h3>General Grading</h3>
<span class="detail">Deadlines and Requirements</span>
</header>
<div class="row row-col2">
<label for="course-grading-graceperiod">Grace Period on Deadline:</label>
<div class="field">
<div class="input">
<input type="text" class="short time" id="course-grading-graceperiod" value="0:00" placeholder="e.g. 10 minutes">
<span class="tip tip-inline">leeway on due dates</span>
</div>
</div>
</div>
</section>
<section class="setting-grading-assignment-types">
<header>
<h3>Assignment Types</h3>
</header>
<div class="row">
<div class="field enum">
<ul class="input-list course-grading-assignment-list">
</ul>
<a href="#" class="new-button new-course-grading-item add-grading-data">
<span class="plus-icon white"></span>New Assignment Type
</a>
</div>
</div>
</section>
</section><!-- .settings-grading -->
</div>
</article>
</div>
</div>
<footer></footer>
</%block>
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
<div class="nav-sub"> <div class="nav-sub">
<ul> <ul>
<li class="nav-item"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Schedule &amp; Details</a></li> <li class="nav-item"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Schedule &amp; Details</a></li>
<li class="nav-item"><a href="${reverse('contentstore.views.get_course_settings', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Grading</a></li> <li class="nav-item"><a href="${reverse('contentstore.views.course_config_graders_page', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Grading</a></li>
<li class="nav-item"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">Course Team</a></li> <li class="nav-item"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">Course Team</a></li>
<li class="nav-item"><a href="${reverse('course_settings', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Advanced Settings</a></li> <li class="nav-item"><a href="${reverse('course_settings', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Advanced Settings</a></li>
</ul> </ul>
...@@ -84,7 +84,10 @@ ...@@ -84,7 +84,10 @@
<div class="wrapper wrapper-nav-sub"> <div class="wrapper wrapper-nav-sub">
<div class="nav-sub"> <div class="nav-sub">
<ul> <ul>
% if context_course:
<% ctx_loc = context_course.location %>
<li class="nav-item"><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">My Courses</a></li> <li class="nav-item"><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">My Courses</a></li>
% endif
<li class="nav-item"><a href="http://help.edge.edx.org/" rel="external">Help</a></li> <li class="nav-item"><a href="http://help.edge.edx.org/" rel="external">Help</a></li>
<li class="nav-item"><a class="action action-logout" href="${reverse('logout')}">Logout</a></li> <li class="nav-item"><a class="action action-logout" href="${reverse('logout')}">Logout</a></li>
</ul> </ul>
......
...@@ -43,6 +43,7 @@ urlpatterns = ('', ...@@ -43,6 +43,7 @@ urlpatterns = ('',
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$', 'contentstore.views.course_info', name='course_info'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$', 'contentstore.views.course_info', name='course_info'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course_info/updates/(?P<provided_id>.*)$', 'contentstore.views.course_info_updates', name='course_info'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course_info/updates/(?P<provided_id>.*)$', 'contentstore.views.course_info_updates', name='course_info'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings/(?P<name>[^/]+)$', 'contentstore.views.get_course_settings', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings/(?P<name>[^/]+)$', 'contentstore.views.get_course_settings', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/grades/(?P<name>[^/]+)$', 'contentstore.views.course_config_graders_page', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings/(?P<name>[^/]+)/section/(?P<section>[^/]+).*$', 'contentstore.views.course_settings_updates', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/settings/(?P<name>[^/]+)/section/(?P<section>[^/]+).*$', 'contentstore.views.course_settings_updates', name='course_settings'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/grades/(?P<name>[^/]+)/(?P<grader_index>.*)$', 'contentstore.views.course_grader_updates', name='course_settings'), url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/grades/(?P<name>[^/]+)/(?P<grader_index>.*)$', 'contentstore.views.course_grader_updates', name='course_settings'),
......
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