Commit 374413a0 by Lyla Fischer

Merge branch 'cas' into bug/lyla/templates

parents b0e62b08 094382d5
...@@ -37,6 +37,7 @@ from xmodule.error_module import ErrorDescriptor ...@@ -37,6 +37,7 @@ from xmodule.error_module import ErrorDescriptor
from xmodule.errortracker import exc_info_to_str from xmodule.errortracker import exc_info_to_str
from github_sync import export_to_github from github_sync import export_to_github
from static_replace import replace_urls from static_replace import replace_urls
from external_auth.views import ssl_login_shortcut
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
...@@ -88,7 +89,7 @@ def signup(request): ...@@ -88,7 +89,7 @@ def signup(request):
csrf_token = csrf(request)['csrf_token'] csrf_token = csrf(request)['csrf_token']
return render_to_response('signup.html', {'csrf': csrf_token}) return render_to_response('signup.html', {'csrf': csrf_token})
@ssl_login_shortcut
@ensure_csrf_cookie @ensure_csrf_cookie
def login_page(request): def login_page(request):
""" """
...@@ -109,7 +110,7 @@ def index(request): ...@@ -109,7 +110,7 @@ def index(request):
courses = modulestore().get_items(['i4x', None, None, 'course', None]) courses = modulestore().get_items(['i4x', None, None, 'course', None])
# filter out courses that we don't have access to # filter out courses that we don't have access to
courses = filter(lambda course: has_access(request.user, course.location) and course.location.course != 'templates', courses) courses = filter(lambda course: has_access(request.user, course.location) and course.location.course != 'templates' and course.location.org!='' and course.location.course!='' and course.location.name!='', courses)
return render_to_response('index.html', { return render_to_response('index.html', {
'new_course_template' : Location('i4x', 'edx', 'templates', 'course', 'Empty'), 'new_course_template' : Location('i4x', 'edx', 'templates', 'course', 'Empty'),
...@@ -118,7 +119,8 @@ def index(request): ...@@ -118,7 +119,8 @@ def index(request):
course.location.org, course.location.org,
course.location.course, course.location.course,
course.location.name])) course.location.name]))
for course in courses] for course in courses],
'user': request.user
}) })
...@@ -272,6 +274,26 @@ def edit_unit(request, location): ...@@ -272,6 +274,26 @@ def edit_unit(request, location):
containing_section_locs = modulestore().get_parent_locations(containing_subsection.location) containing_section_locs = modulestore().get_parent_locations(containing_subsection.location)
containing_section = modulestore().get_item(containing_section_locs[0]) containing_section = modulestore().get_item(containing_section_locs[0])
# cdodge hack. We're having trouble previewing drafts via jump_to redirect
# so let's generate the link url here
# need to figure out where this item is in the list of children as the preview will need this
index =1
for child in containing_subsection.get_children():
if child.location == item.location:
break
index = index + 1
preview_lms_link = '//{preview}{lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}'.format(
preview='preview.',
lms_base=settings.LMS_BASE,
org=course.location.org,
course=course.location.course,
course_name=course.location.name,
section=containing_section.location.name,
subsection=containing_subsection.location.name,
index=index)
unit_state = compute_unit_state(item) unit_state = compute_unit_state(item)
try: try:
...@@ -698,7 +720,17 @@ def upload_asset(request, org, course, coursename): ...@@ -698,7 +720,17 @@ def upload_asset(request, org, course, coursename):
contentstore().save(content) contentstore().save(content)
del_cached_content(content.location) del_cached_content(content.location)
response = HttpResponse('Upload completed') # readback the saved content - we need the database timestamp
readback = contentstore().find(content.location)
response_payload = {'displayname' : content.name,
'uploadDate' : get_date_display(readback.last_modified_at),
'url' : StaticContent.get_url_path_from_location(content.location),
'thumb_url' : StaticContent.get_url_path_from_location(thumbnail_content.location) if thumbnail_content is not None else None,
'msg' : 'Upload completed'
}
response = HttpResponse(json.dumps(response_payload))
response['asset_url'] = StaticContent.get_url_path_from_location(content.location) response['asset_url'] = StaticContent.get_url_path_from_location(content.location)
return response return response
...@@ -710,7 +742,7 @@ This view will return all CMS users who are editors for the specified course ...@@ -710,7 +742,7 @@ This view will return all CMS users who are editors for the specified course
def manage_users(request, location): def manage_users(request, location):
# check that logged in user has permissions to this item # check that logged in user has permissions to this item
if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME): if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME) and not has_access(request.user, location, role=EDITOR_ROLE_NAME):
raise PermissionDenied() raise PermissionDenied()
course_module = modulestore().get_item(location) course_module = modulestore().get_item(location)
...@@ -720,7 +752,9 @@ def manage_users(request, location): ...@@ -720,7 +752,9 @@ def manage_users(request, location):
'context_course': course_module, 'context_course': course_module,
'staff': get_users_in_course_group_by_role(location, STAFF_ROLE_NAME), 'staff': get_users_in_course_group_by_role(location, STAFF_ROLE_NAME),
'add_user_postback_url' : reverse('add_user', args=[location]).rstrip('/'), 'add_user_postback_url' : reverse('add_user', args=[location]).rstrip('/'),
'remove_user_postback_url' : reverse('remove_user', args=[location]).rstrip('/') 'remove_user_postback_url' : reverse('remove_user', args=[location]).rstrip('/'),
'allow_actions' : has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME),
'request_user_id' : request.user.id
}) })
...@@ -845,6 +879,10 @@ def asset_index(request, org, course, name): ...@@ -845,6 +879,10 @@ def asset_index(request, org, course, name):
course_reference = StaticContent.compute_location(org, course, name) course_reference = StaticContent.compute_location(org, course, name)
assets = contentstore().get_all_content_for_course(course_reference) assets = contentstore().get_all_content_for_course(course_reference)
# sort in reverse upload date order
assets = sorted(assets, key=lambda asset: asset['uploadDate'], reverse=True)
thumbnails = contentstore().get_all_content_thumbnails_for_course(course_reference) thumbnails = contentstore().get_all_content_thumbnails_for_course(course_reference)
asset_display = [] asset_display = []
for asset in assets: for asset in assets:
......
...@@ -32,7 +32,8 @@ from xmodule.static_content import write_descriptor_styles, write_descriptor_js, ...@@ -32,7 +32,8 @@ from xmodule.static_content import write_descriptor_styles, write_descriptor_js,
MITX_FEATURES = { MITX_FEATURES = {
'USE_DJANGO_PIPELINE': True, 'USE_DJANGO_PIPELINE': True,
'GITHUB_PUSH': False, 'GITHUB_PUSH': False,
'ENABLE_DISCUSSION_SERVICE': False 'ENABLE_DISCUSSION_SERVICE': False,
'AUTH_USE_MIT_CERTIFICATES' : False,
} }
# needed to use lms student app # needed to use lms student app
......
# dev environment for ichuang/mit
# FORCE_SCRIPT_NAME = '/cms'
from .common import *
from logsettings import get_logger_config
from .dev import *
import socket
MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True
MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 for nginx ssl proxy
...@@ -57,12 +57,12 @@ class CMS.Views.ModuleEdit extends Backbone.View ...@@ -57,12 +57,12 @@ class CMS.Views.ModuleEdit extends Backbone.View
data = @module.save() data = @module.save()
data.metadata = @metadata() data.metadata = @metadata()
@model.save(data).done( => @model.save(data).done( =>
alert("Your changes have been saved.") showToastMessage("Your changes have been saved.", null, 3)
@module = null @module = null
@render() @render()
@$el.removeClass('editing') @$el.removeClass('editing')
).fail( -> ).fail( ->
alert("There was an error saving your changes. Please try again.") showToastMessage("There was an error saving your changes. Please try again.", null, 3)
) )
clickCancelButton: (event) -> clickCancelButton: (event) ->
......
...@@ -117,6 +117,8 @@ class CMS.Views.UnitEdit extends Backbone.View ...@@ -117,6 +117,8 @@ class CMS.Views.UnitEdit extends Backbone.View
@model.save() @model.save()
deleteComponent: (event) => deleteComponent: (event) =>
if not confirm 'Are you sure you want to delete this component? This action cannot be undone.'
return
$component = $(event.currentTarget).parents('.component') $component = $(event.currentTarget).parents('.component')
$.post('/delete_item', { $.post('/delete_item', {
id: $component.data('id') id: $component.data('id')
...@@ -183,6 +185,7 @@ class CMS.Views.UnitEdit.NameEdit extends Backbone.View ...@@ -183,6 +185,7 @@ class CMS.Views.UnitEdit.NameEdit extends Backbone.View
# Treat the metadata dictionary as immutable # Treat the metadata dictionary as immutable
metadata = $.extend({}, @model.get('metadata')) metadata = $.extend({}, @model.get('metadata'))
metadata.display_name = @$('.unit-display-name-input').val() metadata.display_name = @$('.unit-display-name-input').val()
$('.unit-location .editing .unit-name').html(metadata.display_name)
@model.set('metadata', metadata) @model.set('metadata', metadata)
class CMS.Views.UnitEdit.LocationState extends Backbone.View class CMS.Views.UnitEdit.LocationState extends Backbone.View
......
...@@ -80,11 +80,17 @@ $(document).ready(function() { ...@@ -80,11 +80,17 @@ $(document).ready(function() {
}); });
function showImportSubmit(e) { function showImportSubmit(e) {
$('.file-name').html($(this).val()) var filepath = $(this).val();
if(filepath.substr(filepath.length - 6, 6) == 'tar.gz') {
$('.error-block').hide();
$('.file-name').html($(this).val());
$('.file-name-block').show(); $('.file-name-block').show();
$('.import .choose-file-button').hide(); $('.import .choose-file-button').hide();
$('.submit-button').show(); $('.submit-button').show();
$('.progress').show(); $('.progress').show();
} else {
$('.error-block').html('File format not supported. Please upload a file with a <code>tar.gz</code> extension.').show();
}
} }
function syncReleaseDate(e) { function syncReleaseDate(e) {
...@@ -321,7 +327,7 @@ function startUpload(e) { ...@@ -321,7 +327,7 @@ function startUpload(e) {
function resetUploadBar(){ function resetUploadBar(){
var percentVal = '0%'; var percentVal = '0%';
$('.upload-modal .progress-fill').width(percentVal) $('.upload-modal .progress-fill').width(percentVal);
$('.upload-modal .progress-fill').html(percentVal); $('.upload-modal .progress-fill').html(percentVal);
} }
...@@ -335,9 +341,21 @@ function displayFinishedUpload(xhr) { ...@@ -335,9 +341,21 @@ function displayFinishedUpload(xhr) {
if(xhr.status = 200){ if(xhr.status = 200){
markAsLoaded(); markAsLoaded();
} }
var resp = JSON.parse(xhr.responseText);
$('.upload-modal .copy-button').attr('href', xhr.getResponseHeader('asset_url')); $('.upload-modal .copy-button').attr('href', xhr.getResponseHeader('asset_url'));
$('.upload-modal .progress-fill').html(xhr.responseText); $('.upload-modal .progress-fill').html(resp.msg);
$('.upload-modal .choose-file-button').html('Load Another File').show(); $('.upload-modal .choose-file-button').html('Load Another File').show();
$('.upload-modal .progress-fill').width('100%');
// see if this id already exists, if so, then user must have updated an existing piece of content
$("tr[data-id='" + resp.url + "']").remove();
var template = $('#new-asset-element').html();
var html = Mustache.to_html(template, resp);
$('table > tbody > tr:first').before(html);
$("tr[data-id='" + resp.url + "'] a.show-xml").toggle(showEmbeddableXML, hideEmbeddableXML);
} }
function markAsLoaded() { function markAsLoaded() {
...@@ -483,7 +501,7 @@ function addNewCourse(e) { ...@@ -483,7 +501,7 @@ function addNewCourse(e) {
e.preventDefault(); e.preventDefault();
var $newCourse = $($('#new-course-template').html()); var $newCourse = $($('#new-course-template').html());
$('.new-course-button').after($newCourse); $('.new-course-button').after($newCourse);
$newCourse.find('.new-course-org').focus().select(); $newCourse.find('.new-course-name').focus().select();
$newCourse.find('.new-course-save').bind('click', saveNewCourse); $newCourse.find('.new-course-save').bind('click', saveNewCourse);
$newCourse.find('.new-course-cancel').bind('click', cancelNewCourse); $newCourse.find('.new-course-cancel').bind('click', cancelNewCourse);
} }
...@@ -491,14 +509,17 @@ function addNewCourse(e) { ...@@ -491,14 +509,17 @@ function addNewCourse(e) {
function saveNewCourse(e) { function saveNewCourse(e) {
e.preventDefault(); e.preventDefault();
var $newCourse = $(this).closest('.new-course');
template = $(this).data('template'); template = $(this).data('template');
org = $(this).prevAll('.new-course-org').val(); org = $newCourse.find('.new-course-org').val();
number = $(this).prevAll('.new-course-number').val(); number = $newCourse.find('.new-course-number').val();
display_name = $(this).prevAll('.new-course-name').val(); display_name = $newCourse.find('.new-course-name').val();
if (org == '' || number == '' || display_name == ''){ if (org == '' || number == '' || display_name == ''){
alert('You must specify all fields in order to create a new course.') alert('You must specify all fields in order to create a new course.');
return;
} }
$.post('/create_new_course', $.post('/create_new_course',
......
...@@ -45,7 +45,7 @@ input.courseware-unit-search-input { ...@@ -45,7 +45,7 @@ input.courseware-unit-search-input {
} }
header { header {
height: 67px; height: 47px;
.item-details { .item-details {
float: left; float: left;
...@@ -80,6 +80,7 @@ input.courseware-unit-search-input { ...@@ -80,6 +80,7 @@ input.courseware-unit-search-input {
} }
h4 { h4 {
display: none;
font-size: 12px; font-size: 12px;
color: #878e9d; color: #878e9d;
...@@ -102,6 +103,17 @@ input.courseware-unit-search-input { ...@@ -102,6 +103,17 @@ input.courseware-unit-search-input {
border-top-width: 0; border-top-width: 0;
} }
} }
&.new-section {
header {
height: auto;
@include clearfix;
}
.expand-collapse-icon {
visibility: hidden;
}
}
} }
.new-section-name, .new-section-name,
...@@ -114,12 +126,14 @@ input.courseware-unit-search-input { ...@@ -114,12 +126,14 @@ input.courseware-unit-search-input {
@include blue-button; @include blue-button;
padding: 2px 20px 5px; padding: 2px 20px 5px;
margin: 0 5px; margin: 0 5px;
color: #fff !important;
} }
.new-section-name-cancel, .new-section-name-cancel,
.new-subsection-name-cancel { .new-subsection-name-cancel {
@include white-button; @include white-button;
padding: 2px 20px 5px; padding: 2px 20px 5px;
color: #8891a1 !important;
} }
.dummy-calendar { .dummy-calendar {
......
...@@ -42,3 +42,56 @@ ...@@ -42,3 +42,56 @@
padding: 20px; padding: 20px;
text-align: center; text-align: center;
} }
.new-course {
padding: 15px 25px;
margin-top: 20px;
border-radius: 3px;
border: 1px solid $darkGrey;
background: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, .1);
@include clearfix;
.row {
margin-bottom: 15px;
@include clearfix;
}
.column {
float: left;
width: 48%;
}
.column:first-child {
margin-right: 4%;
}
.course-info {
width: 600px;
}
label {
display: block;
font-size: 13px;
font-weight: 700;
}
.new-course-org,
.new-course-number,
.new-course-name {
width: 100%;
}
.new-course-name {
font-size: 19px;
font-weight: 300;
}
.new-course-save {
@include blue-button;
}
.new-course-cancel {
@include white-button;
}
}
\ No newline at end of file
...@@ -11,11 +11,14 @@ ...@@ -11,11 +11,14 @@
margin-right: 3%; margin-right: 3%;
font-size: 14px; font-size: 14px;
h3 { h2 {
font-weight: 700;
font-size: 19px;
margin-bottom: 20px; margin-bottom: 20px;
font-size: 18px; }
strong {
font-weight: 700; font-weight: 700;
color: $error-red;
} }
p + p { p + p {
...@@ -39,12 +42,17 @@ ...@@ -39,12 +42,17 @@
font-weight: 300; font-weight: 300;
} }
.file-name-block { .file-name-block,
.error-block {
display: none; display: none;
margin-bottom: 15px; margin-bottom: 15px;
font-size: 13px; font-size: 13px;
} }
.error-block {
color: $error-red;
}
.choose-file-button { .choose-file-button {
@include blue-button; @include blue-button;
padding: 10px 50px 11px; padding: 10px 50px 11px;
...@@ -67,4 +75,28 @@ ...@@ -67,4 +75,28 @@
white-space: normal; white-space: normal;
} }
} }
.progress-bar {
display: none;
width: 350px;
height: 30px;
margin: 30px auto 10px;
border: 1px solid $blue;
&.loaded {
border-color: #66b93d;
.progress-fill {
background: #66b93d;
}
}
}
.progress-fill {
width: 0%;
height: 30px;
background: $blue;
color: #fff;
line-height: 48px;
}
} }
\ No newline at end of file
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
border: 1px solid #d1ddec; border: 1px solid #d1ddec;
border-radius: 3px; border-radius: 3px;
background: #fff; background: #fff;
@include transition(border-color .15s); @include transition(none);
&:hover { &:hover {
border-color: #6696d7; border-color: #6696d7;
...@@ -253,6 +253,8 @@ ...@@ -253,6 +253,8 @@
.CodeMirror { .CodeMirror {
border: 1px solid #3c3c3c; border: 1px solid #3c3c3c;
background: #fff;
color: #3c3c3c;
} }
h3 { h3 {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
@import 'reset'; @import 'reset';
@import 'mixins'; @import 'mixins';
@import "fonts";
@import "variables"; @import "variables";
@import "cms_mixins"; @import "cms_mixins";
@import "base"; @import "base";
......
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %> <%! from django.core.urlresolvers import reverse %>
<%block name="bodyclass">assets</%block> <%block name="bodyclass">assets</%block>
<%block name="title">CMS Courseware Overview</%block> <%block name="title">Courseware Assets</%block>
<%namespace name='static' file='static_content.html'/>
<%block name="jsextra">
<script src="${static.url('js/vendor/mustache.js')}"></script>
</%block>
<%block name="content"> <%block name="content">
<script type="text/template" id="new-asset-element">
<tr data-id='{{url}}'>
<td class="thumb-col">
<div class="thumb">
{{#thumb_url}}
<img src="{{thumb_url}}">
{{/thumb_url}}
</div>
</td>
<td class="name-col">
<a href="{{url}}" class="filename">{{displayname}}</a>
<div class="embeddable-xml"></div>
</td>
<td class="date-col">
{{uploadDate}}
</td>
<td class="embed-col">
<a class="show-xml" href="{{url}}">XML</a>
</td>
</tr>
</script>
<div class="main-wrapper"> <div class="main-wrapper">
<div class="inner-wrapper"> <div class="inner-wrapper">
<h1>Asset Library</h1> <h1>Asset Library</h1>
...@@ -22,9 +50,9 @@ ...@@ -22,9 +50,9 @@
<th class="embed-col">Embed</th> <th class="embed-col">Embed</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody id="asset_table_body">
% for asset in assets: % for asset in assets:
<tr> <tr data-id="${asset['url']}">
<td class="thumb-col"> <td class="thumb-col">
<div class="thumb"> <div class="thumb">
% if asset['thumb_url'] is not None: % if asset['thumb_url'] is not None:
......
...@@ -11,21 +11,23 @@ ...@@ -11,21 +11,23 @@
<h1>Import</h1> <h1>Import</h1>
<article class="import-overview"> <article class="import-overview">
<div class="description"> <div class="description">
<h3>Importing a new course will delete all course content currently associated with your course <h2>Please <a href="https://edge.edx.org/courses/edX/edx101/edX_Studio_Reference/about" target="_blank">read the documentation</a> before attempting an import!</h2>
and replace it with the contents of the uploaded file.</h3> <p><strong>Importing a new course will delete all content currently associated with your course
and replace it with the contents of the uploaded file.</strong></p>
<p>File uploads must be zip files containing, at a minimum, a <code>course.xml</code> file.</p> <p>File uploads must be zip files containing, at a minimum, a <code>course.xml</code> file.</p>
<p>Please note that if your course has any problems with auto-generated <code>url_name</code> nodes, <p>Please note that if your course has any problems with auto-generated <code>url_name</code> nodes,
re-importing your course could cause the loss of student data associated with those problems.</p> re-importing your course could cause the loss of student data associated with those problems.</p>
</div> </div>
<form action="${reverse('import_course', kwargs=dict(org=context_course.location.org, course=context_course.location.course, name=context_course.location.name))}" method="post" enctype="multipart/form-data" class="import-form"> <form action="${reverse('import_course', kwargs=dict(org=context_course.location.org, course=context_course.location.course, name=context_course.location.name))}" method="post" enctype="multipart/form-data" class="import-form">
<h2>Course to import:</h2> <h2>Course to import:</h2>
<p class="error-block"></p>
<a href="#" class="choose-file-button">Choose File</a> <a href="#" class="choose-file-button">Choose File</a>
<p class="file-name-block"><span class="file-name"></span><a href="#" class="choose-file-button-inline">change</a></p> <p class="file-name-block"><span class="file-name"></span><a href="#" class="choose-file-button-inline">change</a></p>
<input type="file" name="course-data" class="file-input"> <input type="file" name="course-data" class="file-input">
<input type="submit" value="Replace my course with the one above" class="submit-button"> <input type="submit" value="Replace my course with the one above" class="submit-button">
<div class="progress" style="position:relative; margin-top:5px; width:250px; height:15px; border: 1px solid #ddd; padding: 1px; border-radius: 3px; display:none;"> <div class="progress-bar">
<div class="bar" style="background-color: #B4F5B4; width:0%; height:10px; border-radius: 3px;"></div> <div class="progress-fill"></div>
<div class="percent" style="margin-top:5px;">0%</div> <div class="percent">0%</div>
</div> </div>
</form> </form>
</article> </article>
...@@ -37,29 +39,35 @@ ...@@ -37,29 +39,35 @@
<script> <script>
(function() { (function() {
var bar = $('.bar'); var bar = $('.progress-bar');
var fill = $('.progress-fill');
var percent = $('.percent'); var percent = $('.percent');
var status = $('#status'); var status = $('#status');
var submitBtn = $('.submit-button');
$('form').ajaxForm({ $('form').ajaxForm({
beforeSend: function() { beforeSend: function() {
status.empty(); status.empty();
var percentVal = '0%'; var percentVal = '0%';
bar.width(percentVal) bar.show();
fill.width(percentVal);
percent.html(percentVal); percent.html(percentVal);
submitBtn.hide();
}, },
uploadProgress: function(event, position, total, percentComplete) { uploadProgress: function(event, position, total, percentComplete) {
var percentVal = percentComplete + '%'; var percentVal = percentComplete + '%';
bar.width(percentVal) fill.width(percentVal);
percent.html(percentVal); percent.html(percentVal);
}, },
complete: function(xhr) { complete: function(xhr) {
if (xhr.status == 200) { if (xhr.status == 200) {
alert('Your import has been successful.'); alert('Your import was successful.');
window.location = '${successful_import_redirect_url}'; window.location = '${successful_import_redirect_url}';
} }
else else
alert('Your import has failed.\n\n' + xhr.responseText); alert('Your import has failed.\n\n' + xhr.responseText);
submitBtn.show();
bar.hide();
} }
}); });
})(); })();
......
...@@ -4,16 +4,29 @@ ...@@ -4,16 +4,29 @@
<%block name="header_extras"> <%block name="header_extras">
<script type="text/template" id="new-course-template"> <script type="text/template" id="new-course-template">
<section class="courseware-section new-course"> <section class="new-course">
<header>
<div class="item-details"> <div class="item-details">
<h3 class="course-info"> <form class="course-info">
<input type="text" placeholder="Organization" class="new-course-org" /> <div class="row">
<input type="text" placeholder="Course Number" class="new-course-number" /> <label>Course Name</label>
<input type="text" placeholder="Course Name" class="new-course-name" /> <input type="text" class="new-course-name" />
<a href="#" class="new-course-save" data-template="${new_course_template}">Save</a><a href="#" class="new-course-cancel">Cancel</a></h3> </div>
<div class="row">
<div class="column">
<label>Organization</label>
<input type="text" class="new-course-org" />
</div>
<div class="column">
<label>Course Number</label>
<input type="text" class="new-course-number" />
</div>
</div>
<div class="row">
<a href="#" class="new-course-save" data-template="${new_course_template}">Save</a>
<a href="#" class="new-course-cancel">Cancel</a>
</div>
</form>
</div> </div>
</header>
</section> </section>
</script> </script>
</%block> </%block>
...@@ -23,6 +36,7 @@ ...@@ -23,6 +36,7 @@
<div class="inner-wrapper"> <div class="inner-wrapper">
<h1>My Courses</h1> <h1>My Courses</h1>
<article class="my-classes"> <article class="my-classes">
% if user.is_active:
<a href="#" class="new-course-button"><span class="plus-icon"></span> New Course</a> <a href="#" class="new-course-button"><span class="plus-icon"></span> New Course</a>
<ul class="class-list"> <ul class="class-list">
%for course, url in courses: %for course, url in courses:
...@@ -37,6 +51,13 @@ ...@@ -37,6 +51,13 @@
</li> </li>
%endfor %endfor
</ul> </ul>
% else:
<div class='warn-msg'>
<p>
In order to start authoring courses using edX studio, please click on the activation link in your email.
</p>
</div>
% endif
</article> </article>
</div> </div>
</div> </div>
......
...@@ -55,10 +55,11 @@ ...@@ -55,10 +55,11 @@
if(json.success) { if(json.success) {
location.href = "${reverse('index')}"; location.href = "${reverse('index')}";
} else if($('#login_error').length == 0) { } else if($('#login_error').length == 0) {
$('#login_form').prepend('<div id="login_error">Email or password is incorrect.</div>'); $('#login_form').prepend('<div id="login_error">' + json.value + '</div>');
$('#login_error').slideDown(150); $('#login_error').slideDown(150);
} else { } else {
$('#login_error').stop().slideDown(150); $('#login_error').stop().slideDown(150);
$('#login_error').html(json.value);
} }
} }
); );
......
...@@ -16,20 +16,26 @@ ...@@ -16,20 +16,26 @@
<span class="plus-icon"></span>New User <span class="plus-icon"></span>New User
</a> </a>
</div> </div>
%if allow_actions:
<div class="new-user-form"> <div class="new-user-form">
<label>email: </label><input type="text" id="email" class="email-input" autocomplete="off" placeholder="email@example.com"> <label>email: </label><input type="text" id="email" class="email-input" autocomplete="off" placeholder="email@example.com">
<a href="#" id="add_user" class="add-button">save</a> <a href="#" id="add_user" class="add-button">save</a>
<a href="#" class="cancel-button">cancel</a> <a href="#" class="cancel-button">cancel</a>
</div> </div>
%endif
<div> <div>
<ol class="user-list"> <ol class="user-list">
% for user in staff: % for user in staff:
<li> <li>
<span class="user-name">${user.username}</span> <span class="user-name">${user.username}</span>
<span class="user-email">${user.email}</span> <span class="user-email">${user.email}</span>
%if allow_actions :
<div class="item-actions"> <div class="item-actions">
<a href="#" class="delete-button"><span class="delete-icon"></span></a> %if request_user_id != user.id:
<a href="#" class="delete-button remove-user" data-id="${user.email}"><span class="delete-icon"></span></a>
%endif
</div> </div>
%endif
</li> </li>
% endfor % endfor
</ol> </ol>
......
...@@ -3,17 +3,12 @@ ...@@ -3,17 +3,12 @@
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<%block name="content">
<section class="container activation"> <section class="container activation">
<section class="message"> <section class="message">
%if not already_active:
<h1 class="valid">Activation Complete!</h1>
%else:
<h1>Account already active!</h1>
%endif
<hr class="horizontal-divider">
<p> <p style='padding-top:100px; text-align:center;'>
%if not already_active: %if not already_active:
Thanks for activating your account. Thanks for activating your account.
%else: %else:
...@@ -21,10 +16,11 @@ ...@@ -21,10 +16,11 @@
%endif %endif
%if user_logged_in: %if user_logged_in:
Visit your <a href="${reverse('dashboard')}">dashboard</a> to see your courses. Visit your <a href="/">dashboard</a> to see your courses.
%else: %else:
You can now <a href="#login-modal" rel="leanModal">login</a>. You can now <a href="#login-modal" rel="leanModal">login</a>.
%endif %endif
</p> </p>
</section> </section>
</section> </section>
</%block>
<%inherit file="base.html" /> <%inherit file="base.html" />
<%! from django.core.urlresolvers import reverse %>
<%block name="title">Sign up</%block> <%block name="title">Sign up</%block>
<%block name="bodyclass">no-header</%block> <%block name="bodyclass">no-header</%block>
...@@ -82,7 +84,7 @@ ...@@ -82,7 +84,7 @@
submit_data, submit_data,
function(json) { function(json) {
if(json.success) { if(json.success) {
$('#register').html(json.value); location.href = "${reverse('index')}";
} else { } else {
$('#register_error').html(json.value).stop().slideDown(150); $('#register_error').html(json.value).stop().slideDown(150);
} }
......
...@@ -108,9 +108,7 @@ ...@@ -108,9 +108,7 @@
<span class="folder-icon"></span> <span class="folder-icon"></span>
<span class="subsection-name"><span class="subsection-name-value">${subsection.display_name}</span></span> <span class="subsection-name"><span class="subsection-name-value">${subsection.display_name}</span></span>
</a> </a>
<ol>
${units.enum_units(subsection, actions=False, selected=unit.location)} ${units.enum_units(subsection, actions=False, selected=unit.location)}
</ol>
</li> </li>
</ol> </ol>
</li> </li>
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" class="class-name">${context_course.display_name}</a> <a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" class="class-name">${context_course.display_name}</a>
<ul class="class-nav"> <ul class="class-nav">
<li><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='courseware-tab'>Courseware</a></li> <li><a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='courseware-tab'>Courseware</a></li>
<li><a href="${reverse('static_pages', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}" id='pages-tab' class="wip-box">Pages</a></li> <li><a href="${reverse('static_pages', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, coursename=ctx_loc.name))}" id='pages-tab' style="display:none">Pages</a></li>
<li><a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='assets-tab'>Assets</a></li> <li><a href="${reverse('asset_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='assets-tab'>Assets</a></li>
<li><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}" id='users-tab'>Users</a></li> <li><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}" id='users-tab'>Users</a></li>
<li><a href="${reverse('import_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='import-tab'>Import</a></li> <li><a href="${reverse('import_course', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" id='import-tab'>Import</a></li>
......
% if metadata: % if metadata:
<%
import hashlib
hlskey = hashlib.md5(module.location.url()).hexdigest()
%>
<section class="metadata_edit"> <section class="metadata_edit">
<h3>Metadata</h3> <h3>Metadata</h3>
<ul> <ul>
% for keyname in editable_metadata_fields: % for keyname in editable_metadata_fields:
<li><label>${keyname}:</label> <input type='text' data-metadata-name='${keyname}' value='${metadata[keyname]}' size='60' /></li> <li>
% if keyname=='source_code':
<a href="#hls-modal-${hlskey}" style="color:yellow;" id="hls-trig-${hlskey}" >Edit High Level Source</a>
% else:
<label>${keyname}:</label>
<input type='text' data-metadata-name='${keyname}' value='${metadata[keyname]}' size='60' />
% endif
</li>
% endfor % endfor
</ul> </ul>
% if 'source_code' in editable_metadata_fields:
<%include file="source-edit.html" />
% endif
</section> </section>
% endif % endif
<%
import hashlib
hlskey = hashlib.md5(module.location.url()).hexdigest()
%>
<section id="hls-modal-${hlskey}" class="upload-modal modal" style="width:90%!important; left:5%!important; margin-left:0px!important; height:90%; overflow:auto; background:#ECF7D3;" >
<a href="#" class="close-button"><span class="close-icon"></span></a>
<div id="hls-div">
<header>
<h2>High Level Source Editing</h2>
</header>
<form id="hls-form">
<section class="source-edit">
<textarea name="" data-metadata-name="source_code" class="source-edit-box hls-data" rows="8" cols="40">${metadata['source_code']|h}</textarea>
</section>
<div class="submit">
<button type="reset" class="hls-compile">Save &amp; Compile to edX XML</button>
<button type="reset" class="hls-save">Save</button>
<button type="reset" class="hls-refresh">Refresh</button>
</div>
</form>
</div>
</section>
<script type="text/javascript" src="/static/js/vendor/CodeMirror/stex.js"></script>
<script type="text/javascript">
$('#hls-trig-${hlskey}').leanModal({ top:40, overlay:0.8, closeButton: ".close-button"});
$('#hls-modal-${hlskey}').data('editor',CodeMirror.fromTextArea($('#hls-modal-${hlskey}').find('.hls-data')[0], {lineNumbers: true, mode: 'stex'}));
$('#hls-trig-${hlskey}').click(function(){slow_refresh_hls($('#hls-modal-${hlskey}'))})
// refresh button
$('#hls-modal-${hlskey}').find('.hls-refresh').click(function(){refresh_hls($('#hls-modal-${hlskey}'))});
function refresh_hls(el){
el.data('editor').refresh();
}
function slow_refresh_hls(el){
el.delay(200).queue(function(){
refresh_hls(el);
$(this).dequeue();
});
// resize the codemirror box
h = el.height();
el.find('.CodeMirror-scroll').height(h-100);
}
// compile & save button
$('#hls-modal-${hlskey}').find('.hls-compile').click(compile_hls_${hlskey});
function compile_hls_${hlskey}(){
editor = $('#hls-modal-${hlskey}').data('editor')
var myquery = { latexin: editor.getValue() };
$.ajax({
url: '${metadata.get('source_processor_url','https://qisx.mit.edu:5443/latex2edx')}',
type: 'GET',
contentType: 'application/json',
data: escape(JSON.stringify(myquery)),
crossDomain: true,
dataType: 'jsonp',
jsonpCallback: 'process_return_${hlskey}',
beforeSend: function (xhr) { xhr.setRequestHeader ("Authorization", "Basic eHFhOmFnYXJ3YWw="); },
timeout : 7000,
success: function(result) {
console.log(result);
},
error: function() {
alert('Error: cannot connect to latex2edx server');
console.log('error!');
}
});
// $('#hls-modal-${hlskey}').hide();
}
function process_return_${hlskey}(datadict){
// datadict is json of array with "xml" and "message"
// if "xml" value is '' then the conversion failed
xml = datadict.xml;
console.log('xml:');
console.log(xml);
if (xml.length==0){
alert('Conversion failed! error:'+ datadict.message);
}else{
set_raw_edit_box(xml,'${hlskey}');
save_hls($('#hls-modal-${hlskey}'));
}
}
function set_raw_edit_box(data,key){
// get the codemirror editor for the raw-edit-box
// it's a CodeMirror-wrap class element
$('#hls-modal-'+key).closest('.component').find('.CodeMirror-wrap')[0].CodeMirror.setValue(data);
}
// save button
$('#hls-modal-${hlskey}').find('.hls-save').click(function(){save_hls($('#hls-modal-${hlskey}'))});
function save_hls(el){
el.find('.hls-data').val(el.data('editor').getValue());
el.closest('.component').find('.save-button').click();
}
</script>
...@@ -18,7 +18,7 @@ This def will enumerate through a passed in subsection and list all of the units ...@@ -18,7 +18,7 @@ This def will enumerate through a passed in subsection and list all of the units
<div class="section-item ${selected_class}"> <div class="section-item ${selected_class}">
<a href="${reverse('edit_unit', args=[unit.location])}" class="${unit_state}-item"> <a href="${reverse('edit_unit', args=[unit.location])}" class="${unit_state}-item">
<span class="${unit.category}-icon"></span> <span class="${unit.category}-icon"></span>
${unit.display_name} <span class="unit-name">${unit.display_name}</span>
</a> </a>
% if actions: % if actions:
<div class="item-actions"> <div class="item-actions">
......
...@@ -217,6 +217,52 @@ def ssl_dn_extract_info(dn): ...@@ -217,6 +217,52 @@ def ssl_dn_extract_info(dn):
return (user, email, fullname) return (user, email, fullname)
def ssl_get_cert_from_request(request):
"""
Extract user information from certificate, if it exists, returning (user, email, fullname).
Else return None.
"""
certkey = "SSL_CLIENT_S_DN" # specify the request.META field to use
cert = request.META.get(certkey, '')
if not cert:
cert = request.META.get('HTTP_' + certkey, '')
if not cert:
try:
# try the direct apache2 SSL key
cert = request._req.subprocess_env.get(certkey, '')
except Exception:
return ''
return cert
(user, email, fullname) = ssl_dn_extract_info(cert)
return (user, email, fullname)
def ssl_login_shortcut(fn):
"""
Python function decorator for login procedures, to allow direct login
based on existing ExternalAuth record and MIT ssl certificate.
"""
def wrapped(*args, **kwargs):
if not settings.MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES']:
return fn(*args, **kwargs)
request = args[0]
cert = ssl_get_cert_from_request(request)
if not cert: # no certificate information - show normal login window
return fn(*args, **kwargs)
(user, email, fullname) = ssl_dn_extract_info(cert)
return external_login_or_signup(request,
external_id=email,
external_domain="ssl:MIT",
credentials=cert,
email=email,
fullname=fullname)
return wrapped
@csrf_exempt @csrf_exempt
def ssl_login(request): def ssl_login(request):
""" """
...@@ -234,17 +280,7 @@ def ssl_login(request): ...@@ -234,17 +280,7 @@ def ssl_login(request):
Else continues on with student.views.index, and no authentication. Else continues on with student.views.index, and no authentication.
""" """
certkey = "SSL_CLIENT_S_DN" # specify the request.META field to use cert = ssl_get_cert_from_request(request)
cert = request.META.get(certkey, '')
if not cert:
cert = request.META.get('HTTP_' + certkey, '')
if not cert:
try:
# try the direct apache2 SSL key
cert = request._req.subprocess_env.get(certkey, '')
except Exception:
cert = None
if not cert: if not cert:
# no certificate information - go onward to main index # no certificate information - go onward to main index
......
...@@ -63,7 +63,7 @@ class StudentInputError(Exception): ...@@ -63,7 +63,7 @@ class StudentInputError(Exception):
class LoncapaResponse(object): class LoncapaResponse(object):
''' """
Base class for CAPA responsetypes. Each response type (ie a capa question, Base class for CAPA responsetypes. Each response type (ie a capa question,
which is part of a capa problem) is represented as a subclass, which is part of a capa problem) is represented as a subclass,
which should provide the following methods: which should provide the following methods:
...@@ -89,7 +89,7 @@ class LoncapaResponse(object): ...@@ -89,7 +89,7 @@ class LoncapaResponse(object):
- required_attributes : list of required attributes (each a string) on the main response XML stanza - required_attributes : list of required attributes (each a string) on the main response XML stanza
- hint_tag : xhtml tag identifying hint associated with this response inside hintgroup - hint_tag : xhtml tag identifying hint associated with this response inside hintgroup
''' """
__metaclass__ = abc.ABCMeta # abc = Abstract Base Class __metaclass__ = abc.ABCMeta # abc = Abstract Base Class
response_tag = None response_tag = None
...@@ -164,6 +164,8 @@ class LoncapaResponse(object): ...@@ -164,6 +164,8 @@ class LoncapaResponse(object):
- renderer : procedure which produces HTML given an ElementTree - renderer : procedure which produces HTML given an ElementTree
''' '''
tree = etree.Element('span') # render ourself as a <span> + our content tree = etree.Element('span') # render ourself as a <span> + our content
if self.xml.get('inline',''): # problem author can make this span display:inline
tree.set('class','inline')
for item in self.xml: for item in self.xml:
item_xhtml = renderer(item) # call provided procedure to do the rendering item_xhtml = renderer(item) # call provided procedure to do the rendering
if item_xhtml is not None: tree.append(item_xhtml) if item_xhtml is not None: tree.append(item_xhtml)
......
...@@ -618,12 +618,14 @@ class CapaModule(XModule): ...@@ -618,12 +618,14 @@ class CapaModule(XModule):
if self.closed(): if self.closed():
event_info['failure'] = 'closed' event_info['failure'] = 'closed'
self.system.track_function('reset_problem_fail', event_info) self.system.track_function('reset_problem_fail', event_info)
return "Problem is closed" return {'success': False,
'error': "Problem is closed"}
if not self.lcp.done: if not self.lcp.done:
event_info['failure'] = 'not_done' event_info['failure'] = 'not_done'
self.system.track_function('reset_problem_fail', event_info) self.system.track_function('reset_problem_fail', event_info)
return "Refresh the page and make an attempt before resetting." return {'success': False,
'error': "Refresh the page and make an attempt before resetting."}
self.lcp.do_reset() self.lcp.do_reset()
if self.rerandomize in ["always", "onreset"]: if self.rerandomize in ["always", "onreset"]:
......
...@@ -3,6 +3,7 @@ class @HTMLModule ...@@ -3,6 +3,7 @@ class @HTMLModule
constructor: (@element) -> constructor: (@element) ->
@el = $(@element) @el = $(@element)
@setCollapsibles() @setCollapsibles()
MathJax.Hub.Queue ["Typeset", MathJax.Hub, @el[0]]
$: (selector) -> $: (selector) ->
$(selector, @el) $(selector, @el)
......
---
metadata:
display_name: E-text Written in LaTeX
source_processor_url: https://qisx.mit.edu:5443/latex2edx
source_code: |
\subsection{Example of E-text in LaTeX}
It is very convenient to write complex equations in LaTeX.
\begin{equation}
x = \frac{-b\pm\sqrt{b^2-4*a*c}}{2a}
\end{equation}
Seize the moment.
data: |
<html>
<h2>Example: E-text page</h2>
<p>
It is very convenient to write complex equations in LaTeX.
</p>
</html>
children: []
---
metadata:
display_name: Problem Written in LaTeX
source_processor_url: https://qisx.mit.edu:5443/latex2edx
source_code: |
% Nearly any kind of edX problem can be authored using Latex as
% the source language. Write latex as usual, including equations. The
% key new feature is the \edXabox{} macro, which specifies an "Answer
% Box" that queries students for a response, and specifies what the
% epxected (correct) answer is.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "option" problem}
Where is the earth?
\edXabox{options='up','down' expect='down'}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "symbolic" problem}
What is Einstein's equation for the energy equivalent of a mass $m$?
\edXabox{type='symbolic' size='90' expect='m*c^2' }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "numerical" problem}
Estimate the energy savings (in J/y) if all the people
($3\times 10^8$) in the U.~S. switched from U.~S. code to low flow
shower heads.
\edXinline{Energy saved = }\edXabox{expect="0.52" type="numerical" tolerance='0.02' inline='1' } %
\edXinline{~EJ/year}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "multiple choice" problem}
What color is a bannana?
\edXabox{ type="multichoice" expect="Yellow" options="Red","Green","Yellow","Blue" }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "string response" problem}
In what U.S. state is Detroit located?
\edXabox{ type="string" expect="Michigan" options="ci" }
An explanation of the answer can be provided by using the edXsolution
macro. Click on "Show Answer" to see the solution.
\begin{edXsolution}
Detroit is near Canada, but it is actually in the United States.
\end{edXsolution}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "custom response" problem}
This problem demonstrates the use of a custom python script used for
checking the answer.
\begin{edXscript}
def sumtest(expect,ans):
(a1,a2) = map(float,eval(ans))
return (a1+a2)==10
\end{edXscript}
Enter a python list of two numbers which sum to 10, eg [9,1]:
\edXabox{expect="[1,9]" type="custom" cfn="sumtest"}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example image}
Include image by using the edXxml macro:
\edXxml{<img src="http://autoid.mit.edu/images/mit_dome.jpg"/>}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example show/hide explanation}
Extra explanations can be tucked away behind a "showhide" toggle flag:
\edXshowhide{sh1}{More explanation}{This is a hidden explanation. It
can contain equations: $\alpha = \frac{2}{\sqrt{1+\gamma}}$ }
This is some text after the showhide example.
data: |
<?xml version="1.0"?>
<problem>
<text>
<p>
<h4>Example "option" problem</h4>
</p>
<p>
Where is the earth? </p>
<p>
<optionresponse>
<optioninput options="('up','down')" correct="down"/>
</optionresponse>
</p>
<p>
<h4>Example "symbolic" problem</h4>
</p>
<p>
What is Einstein's equation for the energy equivalent of a mass [mathjaxinline]m[/mathjaxinline]? </p>
<p>
<symbolicresponse expect="m*c^2">
<textline size="90" correct_answer="m*c^2" math="1"/>
</symbolicresponse>
</p>
<p>
<h4>Example "numerical" problem</h4>
</p>
<p>
Estimate the energy savings (in J/y) if all the people ([mathjaxinline]3\times 10^8[/mathjaxinline]) in the U.&#xA0;S. switched from U.&#xA0;S. code to low flow shower heads. </p>
<p>
<p style="display:inline">Energy saved = </p>
<numericalresponse inline="1" answer="0.52">
<textline inline="1">
<responseparam description="Numerical Tolerance" type="tolerance" default="0.02" name="tol"/>
</textline>
</numericalresponse>
<p style="display:inline">&#xA0;EJ/year</p>
</p>
<p>
<h4>Example "multiple choice" problem</h4>
</p>
<p>
What color is a bannana? </p>
<p>
<choiceresponse>
<checkboxgroup>
<choice correct="false" name="1">
<text>Red</text>
</choice>
<choice correct="false" name="2">
<text>Green</text>
</choice>
<choice correct="true" name="3">
<text>Yellow</text>
</choice>
<choice correct="false" name="4">
<text>Blue</text>
</choice>
</checkboxgroup>
</choiceresponse>
</p>
<p>
<h4>Example "string response" problem</h4>
</p>
<p>
In what U.S. state is Detroit located? </p>
<p>
<stringresponse answer="Michigan">
<textline/>
</stringresponse>
</p>
<p>
An explanation of the answer can be provided by using the edXsolution macro: </p>
<p>
<solution>
<font color="blue">Answer: </font>
<font color="blue">Detroit is near Canada, but it is actually in the United States. </font>
</solution>
</p>
<p>
<h4>Example "custom response" problem</h4>
</p>
<p>
This problem demonstrates the use of a custom python script used for checking the answer. </p>
<script type="text/python" system_path="python_lib">
def sumtest(expect,ans):
(a1,a2) = map(float,eval(ans))
return (a1+a2)==10
</script>
<p>
Enter a python list of two numbers which sum to 10, eg [9,1]: </p>
<p>
<customresponse cfn="sumtest" expect="[1,9]">
<textline correct_answer="[1,9]"/>
</customresponse>
</p>
<p>
<h4>Example image</h4>
</p>
<p>
Include image by using the edXxml macro: </p>
<p>
<img src="http://autoid.mit.edu/images/mit_dome.jpg"/>
</p>
<p>
<h4>Example show/hide explanation</h4>
</p>
<p>
Extra explanations can be tucked away behind a "showhide" toggle flag: </p>
<p>
<table class="wikitable collapsible collapsed">
<tbody>
<tr>
<th> More explanation [<a href="javascript:$('#sh1').toggle()" id="sh1l">show</a>]</th>
</tr>
<tr id="sh1" style="display:none">
<td>
<p>
This is a hidden explanation. It can contain equations: [mathjaxinline]\alpha = \frac{2}{\sqrt {1+\gamma }}[/mathjaxinline] </p>
<p>
This is some text after the showhide example. </p>
</td>
</tr>
</tbody>
</table>
</p>
</text>
</problem>
children: []
---
metadata:
display_name: Problem with Adaptive Hint
source_processor_url: https://qisx.mit.edu:5443/latex2edx
source_code: |
\subsection{Problem With Adaptive Hint}
% Adaptive hints are messages provided to students which depend on
% student input. These hints are produced using a script embedded
% within the problem (written in Python).
%
% Here is an example. This example uses LaTeX as a high-level
% soure language for the problem. The problem can also be coded
% directly in XML.
This problem demonstrates a question with hints, based on using the
{\tt hintfn} method.
\begin{edXscript}
def test_str(expect, ans):
print expect, ans
ans = ans.strip("'")
ans = ans.strip('"')
return expect == ans.lower()
def hint_fn(answer_ids, student_answers, new_cmap, old_cmap):
aid = answer_ids[0]
ans = str(student_answers[aid]).lower()
print 'hint_fn called, ans=', ans
hint = ''
if 'java' in ans:
hint = 'that is only good for drinking'
elif 'perl' in ans:
hint = 'not that rich'
elif 'pascal' in ans:
hint = 'that is a beatnick language'
elif 'fortran' in ans:
hint = 'those were the good days'
elif 'clu' in ans:
hint = 'you must be invariant'
if hint:
hint = "<font color='blue'>Hint: {0}</font>".format(hint)
new_cmap.set_hint_and_mode(aid,hint,'always')
\end{edXscript}
What is the best programming language that exists today? You may
enter your answer in upper or lower case, with or without quotes.
\edXabox{type="custom" cfn='test_str' expect='python' hintfn='hint_fn'}
data: |
<?xml version="1.0"?>
<problem>
<text>
<p>
<h4>Problem With Adaptive Hint</h4>
</p>
<p>
This problem demonstrates a question with hints, based on using the <tt class="tt">hintfn</tt> method. </p>
<script type="text/python" system_path="python_lib">
def test_str(expect, ans):
print expect, ans
ans = ans.strip("'")
ans = ans.strip('"')
return expect == ans.lower()
def hint_fn(answer_ids, student_answers, new_cmap, old_cmap):
aid = answer_ids[0]
ans = str(student_answers[aid]).lower()
print 'hint_fn called, ans=', ans
hint = ''
if 'java' in ans:
hint = 'that is only good for drinking'
elif 'perl' in ans:
hint = 'not that rich'
elif 'pascal' in ans:
hint = 'that is a beatnick language'
elif 'fortran' in ans:
hint = 'those were the good days'
elif 'clu' in ans:
hint = 'you must be invariant'
if hint:
hint = "&lt;font color='blue'&gt;Hint: {0}&lt;/font&gt;".format(hint)
new_cmap.set_hint_and_mode(aid,hint,'always')
</script>
<p>
What is the best programming language that exists today? You may enter your answer in upper or lower case, with or without quotes. </p>
<p>
<customresponse cfn="test_str" expect="python">
<textline correct_answer="python"/>
<hintgroup hintfn="hint_fn"/>
</customresponse>
</p>
</text>
</problem>
children: []
...@@ -369,6 +369,9 @@ class ResourceTemplates(object): ...@@ -369,6 +369,9 @@ class ResourceTemplates(object):
return [] return []
for template_file in resource_listdir(__name__, dirname): for template_file in resource_listdir(__name__, dirname):
if not template_file.endswith('.yaml'):
log.warning("Skipping unknown template file %s" % template_file)
continue
template_content = resource_string(__name__, os.path.join(dirname, template_file)) template_content = resource_string(__name__, os.path.join(dirname, template_file))
template = yaml.load(template_content) template = yaml.load(template_content)
templates.append(Template(**template)) templates.append(Template(**template))
......
/*
* Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de)
* Licence: MIT
*/
CodeMirror.defineMode("stex", function(cmCfg, modeCfg)
{
function pushCommand(state, command) {
state.cmdState.push(command);
}
function peekCommand(state) {
if (state.cmdState.length>0)
return state.cmdState[state.cmdState.length-1];
else
return null;
}
function popCommand(state) {
if (state.cmdState.length>0) {
var plug = state.cmdState.pop();
plug.closeBracket();
}
}
function applyMostPowerful(state) {
var context = state.cmdState;
for (var i = context.length - 1; i >= 0; i--) {
var plug = context[i];
if (plug.name=="DEFAULT")
continue;
return plug.styleIdentifier();
}
return null;
}
function addPluginPattern(pluginName, cmdStyle, brackets, styles) {
return function () {
this.name=pluginName;
this.bracketNo = 0;
this.style=cmdStyle;
this.styles = styles;
this.brackets = brackets;
this.styleIdentifier = function(content) {
if (this.bracketNo<=this.styles.length)
return this.styles[this.bracketNo-1];
else
return null;
};
this.openBracket = function(content) {
this.bracketNo++;
return "bracket";
};
this.closeBracket = function(content) {
};
};
}
var plugins = new Array();
plugins["importmodule"] = addPluginPattern("importmodule", "tag", "{[", ["string", "builtin"]);
plugins["documentclass"] = addPluginPattern("documentclass", "tag", "{[", ["", "atom"]);
plugins["usepackage"] = addPluginPattern("documentclass", "tag", "[", ["atom"]);
plugins["begin"] = addPluginPattern("documentclass", "tag", "[", ["atom"]);
plugins["end"] = addPluginPattern("documentclass", "tag", "[", ["atom"]);
plugins["DEFAULT"] = function () {
this.name="DEFAULT";
this.style="tag";
this.styleIdentifier = function(content) {
};
this.openBracket = function(content) {
};
this.closeBracket = function(content) {
};
};
function setState(state, f) {
state.f = f;
}
function normal(source, state) {
if (source.match(/^\\[a-zA-Z@]+/)) {
var cmdName = source.current();
cmdName = cmdName.substr(1, cmdName.length-1);
var plug;
if (plugins.hasOwnProperty(cmdName)) {
plug = plugins[cmdName];
} else {
plug = plugins["DEFAULT"];
}
plug = new plug();
pushCommand(state, plug);
setState(state, beginParams);
return plug.style;
}
// escape characters
if (source.match(/^\\[$&%#{}_]/)) {
return "tag";
}
// white space control characters
if (source.match(/^\\[,;!\/]/)) {
return "tag";
}
var ch = source.next();
if (ch == "%") {
// special case: % at end of its own line; stay in same state
if (!source.eol()) {
setState(state, inCComment);
}
return "comment";
}
else if (ch=='}' || ch==']') {
plug = peekCommand(state);
if (plug) {
plug.closeBracket(ch);
setState(state, beginParams);
} else
return "error";
return "bracket";
} else if (ch=='{' || ch=='[') {
plug = plugins["DEFAULT"];
plug = new plug();
pushCommand(state, plug);
return "bracket";
}
else if (/\d/.test(ch)) {
source.eatWhile(/[\w.%]/);
return "atom";
}
else {
source.eatWhile(/[\w-_]/);
return applyMostPowerful(state);
}
}
function inCComment(source, state) {
source.skipToEnd();
setState(state, normal);
return "comment";
}
function beginParams(source, state) {
var ch = source.peek();
if (ch == '{' || ch == '[') {
var lastPlug = peekCommand(state);
var style = lastPlug.openBracket(ch);
source.eat(ch);
setState(state, normal);
return "bracket";
}
if (/[ \t\r]/.test(ch)) {
source.eat(ch);
return null;
}
setState(state, normal);
lastPlug = peekCommand(state);
if (lastPlug) {
popCommand(state);
}
return normal(source, state);
}
return {
startState: function() { return { f:normal, cmdState:[] }; },
copyState: function(s) { return { f: s.f, cmdState: s.cmdState.slice(0, s.cmdState.length) }; },
token: function(stream, state) {
var t = state.f(stream, state);
var w = stream.current();
return t;
}
};
});
CodeMirror.defineMIME("text/x-stex", "stex");
CodeMirror.defineMIME("text/x-latex", "stex");
...@@ -325,6 +325,12 @@ def jump_to(request, course_id, location): ...@@ -325,6 +325,12 @@ def jump_to(request, course_id, location):
except NoPathToItem: except NoPathToItem:
raise Http404("This location is not in any class: {0}".format(location)) raise Http404("This location is not in any class: {0}".format(location))
# cdodge: the CAS is generating a link to the LMS for 'subsections' (aka sequentials)
# and there is no associated 'Position' for this. The above Path_to_location is returning None for Position
# however, this ends up producing a 404 on the redirect
if position is None:
position = 0
# Rely on index to do all error handling and access control. # Rely on index to do all error handling and access control.
return redirect('courseware_position', return redirect('courseware_position',
course_id=course_id, course_id=course_id,
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
</div> </div>
<div class="row submit"> <div class="row submit">
<input name="submit" type="submit" value="Log In" class="log-in-submit-button" tabindex="3"> <input name="submit" type="submit" value="Log In" class="log-in-submit-button" tabindex="3">
<a href="#" class="forgot-button">Forgot password?</a> <a href="#forgot-password-modal" rel="leanModal" class="pwd-reset forgot-button">Forgot password?</a>
</div> </div>
</form> </form>
</div> </div>
...@@ -57,3 +57,4 @@ ...@@ -57,3 +57,4 @@
</%block> </%block>
<%include file="../signup_modal.html" /> <%include file="../signup_modal.html" />
<%include file="../forgot_password_modal.html" />
\ No newline at end of file
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