Commit 9bb85992 by ichuang

Merge branch 'ps-fix-problem' of github.com:MITx/mitx into stable-edx4edx

parents 51b47a50 24fcdad4
This branch (re-)adds dynamic math and symbolicresponse.
Test cases included.
see doc/ for documentation.
......@@ -10,6 +10,7 @@ import os.path
from StringIO import StringIO
from mako.template import Template
from mako.lookup import TemplateLookup
from collections import defaultdict
from django.core.management.base import BaseCommand
from keystore.django import keystore
......@@ -42,9 +43,7 @@ class Command(BaseCommand):
# Simple lists
'chapter': 'Week',
'course': 'Course',
'sequential': 'LectureSequence',
'vertical': 'ProblemSet',
'section': {
'section': defaultdict(lambda: 'Section', {
'Lab': 'Lab',
'Lecture Sequence': 'LectureSequence',
'Homework': 'Homework',
......@@ -52,8 +51,13 @@ class Command(BaseCommand):
'Video': 'VideoSegment',
'Midterm': 'Exam',
'Final': 'Exam',
None: 'Section',
},
'Problems': 'ProblemSet',
}),
'videosequence': 'VideoSequence',
'problemset': 'ProblemSet',
'vertical': 'Section',
'sequential': 'Section',
'tab': 'Section',
# True types
'video': 'VideoSegment',
'html': 'HTML',
......@@ -78,6 +82,8 @@ class Command(BaseCommand):
e.set('url', 'i4x://mit.edu/6002xs12/{category}/{name}'.format(
category=category,
name=name))
else:
print "Skipping element with tag", e.tag
def handle_skip(e):
......@@ -150,6 +156,9 @@ class Command(BaseCommand):
'sequential': handle_list,
'vertical': handle_list,
'section': handle_list,
'videosequence': handle_list,
'problemset': handle_list,
'tab': handle_list,
# True types
'video': handle_video,
'html': handle_html,
......
from mitxmako.shortcuts import render_to_response
from keystore.django import keystore
from django.contrib.auth.decorators import login_required
from django_future.csrf import ensure_csrf_cookie
from django.http import HttpResponse
import json
@ensure_csrf_cookie
def index(request):
# TODO (cpennington): These need to be read in from the active user
org = 'mit.edu'
......@@ -11,3 +14,20 @@ def index(request):
course = keystore().get_item(['i4x', org, course, 'Course', name])
weeks = course.get_children()
return render_to_response('index.html', {'weeks': weeks})
def edit_item(request):
item_id = request.GET['id']
item = keystore().get_item(item_id)
return render_to_response('unit.html', {
'contents': item.get_html(),
'type': item.type,
'name': item.name,
})
def save_item(request):
item_id = request.POST['id']
data = json.loads(request.POST['data'])
keystore().update_item(item_id, data)
return HttpResponse(json.dumps({}))
......@@ -21,6 +21,9 @@ Longer TODO:
import sys
import tempfile
import os.path
import os
import errno
from path import path
############################ FEATURE CONFIGURATION #############################
......@@ -154,7 +157,38 @@ PIPELINE_CSS = {
PIPELINE_ALWAYS_RECOMPILE = ['sass/base-style.scss']
from x_module import XModuleDescriptor
js_file_dir = PROJECT_ROOT / "static" / "coffee" / "module"
try:
os.makedirs(js_file_dir)
except OSError as exc:
if exc.errno == errno.EEXIST:
pass
else:
raise
module_js_sources = []
for xmodule in XModuleDescriptor.load_classes():
js = xmodule.get_javascript()
for filetype in ('coffee', 'js'):
for idx, fragment in enumerate(js.get(filetype, [])):
path = os.path.join(js_file_dir, "{name}.{idx}.{type}".format(
name=xmodule.__name__,
idx=idx,
type=filetype))
with open(path, 'w') as js_file:
js_file.write(fragment)
module_js_sources.append(path.replace(PROJECT_ROOT / "static/", ""))
PIPELINE_JS = {
'main': {
'source_filenames': ['coffee/main.coffee', 'coffee/unit.coffee'],
'output_filename': 'js/main.js',
},
'module-js': {
'source_filenames': module_js_sources,
'output_filename': 'js/modules.js',
}
}
PIPELINE_COMPILERS = [
......
class @CMS
@bind = =>
$('a.module-edit').click ->
CMS.edit_item($(this).attr('id'))
return false
@edit_item = (id) =>
$.get('/edit_item', {id: id}, (data) =>
$('#module-html').empty().append(data)
CMS.bind()
$('section.edit-pane').show()
$('body').addClass('content')
new Unit('unit-wrapper', id)
)
$ ->
$.ajaxSetup
headers : { 'X-CSRFToken': $.cookie 'csrftoken' }
$('section.main-content').children().hide()
$('.editable').inlineEdit()
$('.editable-textarea').inlineEdit({control: 'textarea'})
heighest = 0
$('.cal ol > li').each ->
heighest = if $(this).height() > heighest then $(this).height() else heighest
$('.cal ol > li').css('height',heighest + 'px')
$('.add-new-section').click -> return false
$('.new-week .close').click ->
$(this).parents('.new-week').hide()
$('p.add-new-week').show()
return false
$('.save-update').click ->
$(this).parent().parent().hide()
return false
# $('html').keypress ->
# $('.wip').css('visibility', 'visible')
setHeight = ->
windowHeight = $(this).height()
contentHeight = windowHeight - 29
$('section.main-content > section').css('min-height', contentHeight)
$('body.content .cal').css('height', contentHeight)
$('.edit-week').click ->
$('body').addClass('content')
$('body.content .cal').css('height', contentHeight)
$('section.edit-pane').show()
return false
$('a.week-edit').click ->
$('body').addClass('content')
$('body.content .cal').css('height', contentHeight)
$('section.edit-pane').show()
return false
$('a.sequence-edit').click ->
$('body').addClass('content')
$('body.content .cal').css('height', contentHeight)
$('section.edit-pane').show()
return false
$('a.module-edit').click ->
$('body.content .cal').css('height', contentHeight)
$(document).ready(setHeight)
$(window).bind('resize', setHeight)
$('.video-new a').click ->
$('section.edit-pane').show()
return false
$('.problem-new a').click ->
$('section.edit-pane').show()
return false
CMS.bind()
class @Unit
constructor: (@element_id, @module_id) ->
@module = new window[$("##{@element_id}").attr('class')] 'module-html'
$("##{@element_id} .save-update").click (event) =>
event.preventDefault()
$.post("save_item", {
id: @module_id
data: JSON.stringify(@module.save())
})
$("##{@element_id} .cancel").click (event) =>
event.preventDefault()
CMS.edit_item(@module_id)
/*!
* jQuery Cookie Plugin
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2011, Klaus Hartl
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://www.opensource.org/licenses/mit-license.php
* http://www.opensource.org/licenses/GPL-2.0
*/
(function($) {
$.cookie = function(key, value, options) {
// key and at least value given, set cookie...
if (arguments.length > 1 && (!/Object/.test(Object.prototype.toString.call(value)) || value === null || value === undefined)) {
options = $.extend({}, options);
if (value === null || value === undefined) {
options.expires = -1;
}
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
value = String(value);
return (document.cookie = [
encodeURIComponent(key), '=', options.raw ? value : encodeURIComponent(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// key and possibly options given, get cookie...
options = value || {};
var decode = options.raw ? function(s) { return s; } : decodeURIComponent;
var pairs = document.cookie.split('; ');
for (var i = 0, pair; pair = pairs[i] && pairs[i].split('='); i++) {
if (decode(pair[0]) === key) return decode(pair[1] || ''); // IE saves cookies with empty string as "c; ", e.g. without "=" as opposed to EOMB, thus pair[1] may be undefined
}
return null;
};
})(jQuery);
$(document).ready(function(){
$('section.main-content').children().hide();
$(function(){
$('.editable').inlineEdit();
$('.editable-textarea').inlineEdit({control: 'textarea'});
});
var heighest = 0;
$('.cal ol > li').each(function(){
heighest = ($(this).height() > heighest) ? $(this).height() : heighest;
});
$('.cal ol > li').css('height',heighest + 'px');
$('.add-new-section').click(function() {
return false;
});
$('.new-week .close').click( function(){
$(this).parents('.new-week').hide();
$('p.add-new-week').show();
return false;
});
$('.save-update').click(function(){
$(this).parent().parent().hide();
return false;
});
setHeight = function(){
var windowHeight = $(this).height();
var contentHeight = windowHeight - 29;
$('section.main-content > section').css('min-height', contentHeight);
$('body.content .cal').css('height', contentHeight);
$('.edit-week').click( function() {
$('body').addClass('content');
$('body.content .cal').css('height', contentHeight);
$('section.week-new').show();
return false;
});
$('.cal ol li header h1 a').click( function() {
$('body').addClass('content');
$('body.content .cal').css('height', contentHeight);
$('section.week-edit').show();
return false;
});
$('a.sequence-edit').click(function(){
$('body').addClass('content');
$('body.content .cal').css('height', contentHeight);
$('section.sequence-edit').show();
return false;
});
}
$(document).ready(setHeight);
$(window).bind('resize', setHeight);
$('.video-new a').click(function(){
$('section.video-new').show();
return false;
});
$('a.video-edit').click(function(){
$('section.video-edit').show();
return false;
});
$('.problem-new a').click(function(){
$('section.problem-new').show();
return false;
});
$('a.problem-edit').click(function(){
$('section.problem-edit').show();
return false;
});
});
Sass Watch:
sass --watch cms/static/sass:cms/static/css -r ./cms/static/sass/bourbon/lib/bourbon.rb
sass --watch cms/static/sass:cms/static/sass -r ./cms/static/sass/bourbon/lib/bourbon.rb
......@@ -2,65 +2,14 @@ $fg-column: 70px;
$fg-gutter: 26px;
$fg-max-columns: 12;
$body-font-family: "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
$body-font-size: 14px;
$body-line-height: 20px;
// Base html styles
html {
height: 100%;
}
body {
@include clearfix();
height: 100%;
font: 14px $body-font-family;
> section {
display: table;
width: 100%;
}
> header {
background: #000;
color: #fff;
display: block;
float: none;
padding: 6px 20px;
width: 100%;
@include box-sizing(border-box);
nav {
@include clearfix;
h2 {
font-size: 14px;
text-transform: uppercase;
float: left;
}
ul {
float: left;
&.user-nav {
float: right;
}
li {
@include inline-block();
margin-left: 15px;
}
}
}
}
&.content {
section.main-content {
border-left: 2px solid #000;
@include box-sizing(border-box);
width: flex-grid(9);
float: left;
@include box-shadow( -2px 0 3px #ddd );
}
}
}
a {
text-decoration: none;
color: #888;
......@@ -77,6 +26,13 @@ input[type="submit"], .button {
padding: 6px;
}
textarea {
@include box-sizing(border-box);
display: block;
width: 100%;
}
// Extends
.new-module {
position: relative;
......@@ -111,3 +67,17 @@ input[type="submit"], .button {
display: block;
float: right;
}
.wip {
outline: 1px solid #f00 !important;
position: relative;
&:after {
content: "WIP";
font-size: 8px;
padding: 2px;
background: #f00;
color: #fff;
@include position(absolute, 0px 0px 0 0);
}
}
......@@ -49,7 +49,6 @@ section.cal {
ol {
list-style: none;
@include clearfix;
@include box-sizing(border-box);
border-left: 1px solid #333;
border-top: 1px solid #333;
width: 100%;
......
body {
@include clearfix();
height: 100%;
font: 14px $body-font-family;
> section {
display: table;
width: 100%;
}
> header {
background: #000;
color: #fff;
display: block;
float: none;
padding: 6px 20px;
width: 100%;
@include box-sizing(border-box);
nav {
@include clearfix;
h2 {
font-size: 14px;
text-transform: uppercase;
float: left;
}
ul {
float: left;
&.user-nav {
float: right;
}
li {
@include inline-block();
margin-left: 15px;
}
}
}
}
&.content {
section.main-content {
border-left: 2px solid #000;
@include box-sizing(border-box);
width: flex-grid(9);
float: left;
@include box-shadow( -2px 0 3px #ddd );
}
}
}
section.problem-new, section.problem-edit {
> section {
textarea {
@include box-sizing(border-box);
display: block;
width: 100%;
}
div.preview {
background: #eee;
@include box-sizing(border-box);
height: 40px;
padding: 10px;
width: 100%;
}
a.save {
@extend .button;
@include inline-block();
margin-top: 20px;
}
}
}
section.week-edit,
section.week-new,
section.sequence-edit {
> header {
border-bottom: 2px solid #333;
@include clearfix();
div {
section#unit-wrapper {
section.filters {
@include clearfix;
margin-bottom: 10px;
background: #efefef;
border: 1px solid #ddd;
ul {
@include clearfix();
padding: 6px 20px;
list-style: none;
padding: 6px;
h1 {
font-size: 18px;
text-transform: uppercase;
letter-spacing: 1px;
float: left;
}
li {
@include inline-block();
p {
float: right;
&.advanced {
float: right;
}
}
}
}
div.content {
display: table;
border: 1px solid;
width: 100%;
&.week {
section {
header {
background: #eee;
font-size: 12px;
padding: 6px;
border-bottom: 1px solid #ccc;
@include clearfix;
h2 {
text-transform: uppercase;
letter-spacing: 1px;
font-size: 12px;
@include inline-block();
margin-right: 20px;
}
ul {
list-style: none;
@include inline-block();
li {
@include inline-block();
margin-right: 10px;
p {
float: none;
}
}
float: left;
}
}
}
section.goals {
background: #eee;
padding: 6px 20px;
border-top: 1px solid #ccc;
&.modules {
@include box-sizing(border-box);
display: table-cell;
width: flex-grid(6, 9);
border-right: 1px solid #333;
ul {
list-style: none;
color: #999;
&.empty {
text-align: center;
vertical-align: middle;
li {
margin-bottom: 6px;
&:last-child {
margin-bottom: 0;
}
}
}
}
}
> section.content {
@include box-sizing(border-box);
padding: 20px;
section.filters {
@include clearfix;
margin-bottom: 10px;
background: #efefef;
border: 1px solid #ddd;
ul {
@include clearfix();
list-style: none;
padding: 6px;
li {
@include inline-block();
&.advanced {
float: right;
a {
@extend .button;
@include inline-block();
margin-top: 10px;
}
}
}
}
> div {
display: table;
border: 1px solid;
width: 100%;
section {
header {
background: #eee;
padding: 6px;
border-bottom: 1px solid #ccc;
@include clearfix;
h2 {
text-transform: uppercase;
letter-spacing: 1px;
font-size: 12px;
float: left;
}
}
ol {
list-style: none;
border-bottom: 1px solid #333;
&.modules {
@include box-sizing(border-box);
display: table-cell;
width: flex-grid(6, 9);
border-right: 1px solid #333;
li {
border-bottom: 1px solid #333;
&.empty {
text-align: center;
vertical-align: middle;
&:last-child{
border-bottom: 0;
}
a {
@extend .button;
@include inline-block();
margin-top: 10px;
color: #000;
}
}
ol {
list-style: none;
border-bottom: 1px solid #333;
li {
border-bottom: 1px solid #333;
&:last-child{
border-bottom: 0;
}
a {
color: #000;
}
ol {
list-style: none;
ol {
list-style: none;
li {
padding: 6px;
&:hover {
a.draggable {
opacity: 1;
}
}
li {
padding: 6px;
&:hover {
a.draggable {
float: right;
opacity: .5;
opacity: 1;
}
}
&.group {
padding: 0;
a.draggable {
float: right;
opacity: .5;
}
header {
padding: 6px;
background: none;
&.group {
padding: 0;
h3 {
font-size: 14px;
}
header {
padding: 6px;
background: none;
h3 {
font-size: 14px;
}
}
ol {
ol {
border-left: 4px solid #999;
border-bottom: 0;
li {
&:last-child {
border-bottom: 0;
}
li {
&:last-child {
border-bottom: 0;
}
}
}
......@@ -191,63 +117,63 @@ section.sequence-edit {
}
}
}
}
&.scratch-pad {
@include box-sizing(border-box);
display: table-cell;
width: flex-grid(3, 9) + flex-gutter(9);
vertical-align: top;
&.scratch-pad {
@include box-sizing(border-box);
display: table-cell;
width: flex-grid(3, 9) + flex-gutter(9);
vertical-align: top;
ol {
list-style: none;
border-bottom: 1px solid #999;
ol {
list-style: none;
border-bottom: 1px solid #999;
li {
border-bottom: 1px solid #999;
background: #f9f9f9;
li {
border-bottom: 1px solid #999;
background: #f9f9f9;
&:last-child {
border-bottom: 0;
}
&:last-child {
border-bottom: 0;
}
ul {
list-style: none;
ul {
list-style: none;
li {
padding: 6px;
li {
padding: 6px;
&:last-child {
border-bottom: 0;
}
&:last-child {
border-bottom: 0;
}
&:hover {
a.draggable {
opacity: 1;
}
&:hover {
a.draggable {
opacity: 1;
}
}
&.empty {
padding: 12px;
&.empty {
padding: 12px;
a {
@extend .button;
display: block;
text-align: center;
}
a {
@extend .button;
display: block;
text-align: center;
}
}
a.draggable {
float: right;
opacity: .3;
}
a.draggable {
float: right;
opacity: .3;
}
a {
color: #000;
}
a {
color: #000;
}
}
}
}
}
}
......
section.video-new, section.video-edit, section.problem-new, section.problem-edit {
position: absolute;
top: 72px;
right: 0;
background: #fff;
width: flex-grid(6);
@include box-shadow(0 0 6px #666);
border: 1px solid #333;
border-right: 0;
z-index: 4;
section#unit-wrapper {
> header {
background: #666;
@include clearfix;
color: #fff;
padding: 6px;
border-bottom: 1px solid #333;
-webkit-font-smoothing: antialiased;
h2 {
float: left;
font-size: 14px;
}
border-bottom: 2px solid #333;
@include clearfix();
padding: 6px 20px;
a {
color: #fff;
section {
float: left;
&.save-update {
float: right;
h1 {
font-size: 16px;
text-transform: uppercase;
letter-spacing: 1px;
@include inline-block();
}
&.cancel {
float: left;
p {
@include inline-block();
margin-left: 10px;
color: #999;
font-size: 12px;
font-style: italic;
}
}
div {
float: right;
color: #666;
a {
&.cancel {
margin-right: 20px;
font-style: italic;
font-size: 12px;
}
}
}
}
> section {
padding: 20px;
> header {
h1 {
font-size: 24px;
margin: 12px 0;
}
section.meta {
section {
&.status-settings {
float: left;
margin-bottom: 10px;
ul {
list-style: none;
@include border-radius(2px);
......@@ -80,11 +79,26 @@ section.video-new, section.video-edit, section.problem-new, section.problem-edit
}
}
&.meta {
&.author {
float: right;
dl {
dt {
font-weight: bold;
}
dd, dt {
@include inline-block();
}
}
}
&.tags {
background: #eee;
padding: 10px;
margin: 20px 0;
margin: 0 0 20px;
@include clearfix();
clear: both;
div {
float: left;
......@@ -103,25 +117,82 @@ section.video-new, section.video-edit, section.problem-new, section.problem-edit
}
}
section.notes {
margin-top: 20px;
padding: 6px;
//general styles for main content
div.preview {
background: #eee;
border: 1px solid #ccc;
@include box-sizing(border-box);
min-height: 40px;
padding: 10px;
width: 100%;
margin-top: 10px;
textarea {
@include box-sizing(border-box);
display: block;
width: 100%;
h1 {
font-size: 24px;
margin-bottom: 1em;
}
h2 {
font-size: 20px;
margin-bottom: 1em;
}
h3 {
font-size: 18;
margin-bottom: 1em;
}
ul {
padding-left: 20px;
margin-bottom: 1em;
}
p {
margin-bottom: 1em;
}
}
//notes
section.notes {
margin-top: 20px;
padding: 20px 0 0;
border-top: 1px solid #ccc;
h2 {
font-size: 14px;
margin-bottom: 6px;
}
input[type="submit"]{
margin-top: 10px;
form {
margin-bottom: 20px;
input[type="submit"]{
margin-top: 10px;
}
}
ul {
list-style: none;
li {
margin-bottom: 20px;
p {
margin-bottom: 10px;
&.author {
font-style: italic;
color: #999;
}
}
}
}
}
div.actions {
a.save-update {
@extend .button;
@include inline-block();
margin-top: 20px;
}
}
}
......
section.video-new, section.video-edit {
> section {
section.upload {
padding: 6px;
margin-bottom: 10px;
border: 1px solid #ddd;
a.upload-button {
@extend .button;
@include inline-block();
}
}
section.in-use {
h2 {
font-size: 14px;
}
div {
background: #eee;
text-align: center;
padding: 6px;
}
}
a.save-update {
@extend .button;
@include inline-block();
margin-top: 20px;
}
}
}
@import 'bourbon/bourbon';
@import 'reset';
@import 'base';
@import 'base', 'layout';
@import 'calendar';
@import 'week', 'video', 'problem', 'module-header';
@import 'section', 'unit';
......@@ -23,11 +23,18 @@
<%block name="content"></%block>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript" src="${ STATIC_URL}/js/jquery.min.js"></script>
<script type="text/javascript" src="${ STATIC_URL }/js/markitup/jquery.markitup.js"></script>
<script type="text/javascript" src="${ STATIC_URL }/js/markitup/sets/wiki/set.js"></script>
% if settings.MITX_FEATURES['USE_DJANGO_PIPELINE']:
<%static:js group='main'/>
% else:
<script src="${ STATIC_URL }/js/main.js"></script>
% endif
<%static:js group='module-js'/>
<script src="${ STATIC_URL }/js/jquery.inlineedit.js"></script>
<script src="${ STATIC_URL }/js/jquery.cookie.js"></script>
<script src="${ STATIC_URL }/js/jquery.leanModal.min.js"></script>
<script src="${ STATIC_URL }/js/jquery.tablednd.js"></script>
</body>
......
......@@ -7,13 +7,9 @@
<%include file="widgets/navigation.html"/>
<section class="main-content">
<%include file="widgets/week-edit.html"/>
<%include file="widgets/week-new.html"/>
<%include file="widgets/sequnce-edit.html"/>
<%include file="widgets/video-edit.html"/>
<%include file="widgets/video-new.html"/>
<%include file="widgets/problem-edit.html"/>
<%include file="widgets/problem-new.html"/>
<section class="edit-pane">
<div id="module-html"/>
</section>
</section>
</section>
......
<section id="unit-wrapper" class="${type}">
<header>
<section>
<h1 class="editable">${name}</h1>
<p>${type}</p>
</section>
<div class="actions">
<a href="#" class="cancel">Cancel</a>
<a href="" class="save-update">Save &amp; Update</a>
</div>
</header>
<section>
${contents}
</section>
</section>
......@@ -6,9 +6,6 @@
<a href="#" class="new-module">New Section</a>
</li>
<li>
<a href="#" class="new-module">New Module</a>
</li>
<li>
<a href="#" class="new-module">New Unit</a>
</li>
</ul>
......
<section class="html-edit">
<section class="meta wip">
<section class="status-settings">
<ul>
<li><a href="#" class="current">Scrap</a></li>
<li><a href="#">Draft</a></li>
<li><a href="#">Proofed</a></li>
<li><a href="#">Published</a></li>
</ul>
<a href="#" class="settings">Settings</a>
</section>
<section class="author">
<dl>
<dt>Last modified:</dt>
<dd>mm/dd/yy</dd>
<dt>By</dt>
<dd>Anant Agarwal</dd>
</dl>
</section>
<section class="tags">
<div>
<h2>Tags:</h2>
<p class="editable">Click to edit</p>
</div>
<div>
<h2>Goal</h2>
<p class="editable">Click to edit</p>
</div>
</section>
</section>
<textarea name="" class="edit-box" rows="8" cols="40">${module.definition['data']['text']}</textarea>
<div class="preview">${module.definition['data']['text']}</div>
<div class="actions wip">
<a href="" class="save-update">Save &amp; Update</a>
<a href="#" class="cancel">Cancel</a>
</div>
<%include file="notes.html"/>
</section>
<li class="create-module">
<li class="create-module wip">
<a href="#" class="new-module">
+ Add new module
</a>
......
<section class="cal">
<header>
<header class="wip">
<h2>Filter content:</h2>
<ul>
<li>
......@@ -38,7 +38,7 @@
% for week in weeks:
<li>
<header>
<h1><a href="#">${week.name}</a></h1>
<h1><a href="#" class="week-edit" id="${week.url}">${week.name}</a></h1>
<ul>
% if week.goals:
% for goal in week.goals:
......@@ -53,7 +53,7 @@
<ul>
% for module in week.get_children():
<li class="${module.type}">
<a href="#" class="${module.type}-edit">${module.name}</a>
<a href="#" class="module-edit" id="${module.url}">${module.name}</a>
<a href="#" class="draggable">handle</a>
</li>
% endfor
......
<section class="notes wip">
<h2>Notes</h2>
<ul>
<li>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
<p class="author">Anant Agarwal</p>
</li>
<li>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
<p class="author">Anant Agarwal</p>
</li>
</ul>
<form>
<h2>Add a note</h2>
<textarea name="" id= rows="8" cols="40"></textarea>
<input type="submit" name="" id="" value="post" />
</form>
</section>
<section class="problem-edit">
<header>
<a href="#" class="cancel">Cancel</a>
<a href="#" class="save-update">Save &amp; Update</a>
</header>
<section>
<header>
<h1 class="editable">New Problem</h1>
<section class="author">
<div>
<h2>Last modified:</h2>
<p>mm/dd/yy</p>
</div>
<div>
<h2>By</h2>
<p>Anant Agarwal</p>
</div>
</section>
<section class="status-settings">
<ul>
<li><a href="#" class="current">Scrap</a></li>
<li><a href="#">Draft</a></li>
<li><a href="#">Proofed</a></li>
<li><a href="#">Published</a></li>
</ul>
<a href="#" class="settings">Settings</a>
<section class="meta">
<section class="status-settings">
<ul>
<li><a href="#" class="current">Scrap</a></li>
<li><a href="#">Draft</a></li>
<li><a href="#">Proofed</a></li>
<li><a href="#">Published</a></li>
</ul>
<a href="#" class="settings">Settings</a>
</section>
<select name="" id="">
<option>Global</option>
</select>
</section>
<section class="meta">
<div>
<h2>Tags:</h2>
<p class="editable">Click to edit</p>
</div>
<section class="author">
<dl>
<dt>Last modified:</dt>
<dd>mm/dd/yy</dd>
<dt>By</dt>
<dd>Anant Agarwal</dd>
</dl>
</section>
<div>
<h2>Goal</h2>
<p class="editable">Click to edit</p>
</div>
</section>
</header>
<section class="tags">
<div>
<h2>Tags:</h2>
<p class="editable">Click to edit</p>
</div>
<section>
<textarea name="" id= rows="8" cols="40">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</textarea>
<div class="preview">
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
<div>
<h2>Goal</h2>
<p class="editable">Click to edit</p>
</div>
</section>
</section>
<section class="notes">
<h2>Add notes</h2>
<textarea name="" id= rows="8" cols="40"></textarea>
<input type="submit" name="" id="" value="post" />
<ul>
<li>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
<p class="author">Anant Agarwal</p>
</li>
<li>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</p>
<p class="author">Anant Agarwal</p>
</li>
</ul> </section>
<a href="" class="save-update">Save &amp; Update</a>
<section>
<textarea name="" id= rows="8" cols="40">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.</textarea>
<div class="preview">
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
</div>
<div class="actions">
<a href="#" class="cancel">Cancel</a>
<a href="" class="save-update">Save &amp; Update</a>
</div>
</section>
<%include file="notes.html"/>
</section>
<li>
<img src="http://placehold.it/300x180" alt="" /><h5>Video-file-name</h5>
</li>
<section class="caption-save">
<a href="#" class="close-box">Cancel</a>
<button class="close-box">Save changes</button>
</section>
<section class="sequence-edit">
<header>
<div class="week">
<h2><a href="">Week 1</a></h2>
<ul>
<li>
<p class="editable"><strong>Goal title:</strong> This is the goal body and is where the goal will be further explained</p>
</li>
</ul>
</div>
<div>
<h1 class="editable">Lecture sequence</h1>
<p><strong>Group type:</strong> Ordered Sequence</p>
</div>
</header>
<section class="filters">
<ul>
<li>
<label for="">Sort by</label>
<select>
<option value="">Recently Modified</option>
</select>
</li>
<section class="content">
<section class="filters">
<ul>
<li>
<label for="">Sort by</label>
<select>
<option value="">Recently Modified</option>
</select>
</li>
<li>
<label for="">Display</label>
<select>
<option value="">All content</option>
</select>
</li>
<li>
<select>
<option value="">Internal Only</option>
</select>
</li>
<li>
<label for="">Display</label>
<select>
<option value="">All content</option>
</select>
</li>
<li>
<select>
<option value="">Internal Only</option>
</select>
</li>
<li class="advanced">
<a href="#">Advanced filters</a>
</li>
<li class="advanced">
<a href="#">Advanced filters</a>
</li>
<li>
<input type="search" name="" id="" value="" />
</li>
</ul>
</section>
<div class="content">
<section class="modules">
<ol>
<li>
<input type="search" name="" id="" value="" />
<ol>
% for child in module.get_children():
<li>
<a href="#" class="module-edit" id="${child.url}">${child.name}</a>
<a href="#" class="draggable">handle</a>
</li>
%endfor
</ol>
</li>
</ul>
</section>
<div>
<section class="modules">
<ol>
<li>
<ol>
<li>
<a href="" class="problem-edit">Problem title 11</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="sequence-edit">Problem Group</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit">Problem title 14</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="video-edit">Video 3</a>
<a href="#" class="draggable">handle</a>
</li>
<li class="group">
<header>
<h3>
<a href="#" class="problem-edit">Problem group</a>
<a href="#" class="draggable">handle</a>
</h3>
</header>
<ol>
<li>
<a href="#" class="problem-edit">Problem title 11</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit">Problem title 11</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit">Problem title 11</a>
<a href="#" class="draggable">handle</a>
</li>
</ol>
</li>
<li>
<a href="#" class="problem-edit">Problem title 13</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit">Problem title 14</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="video-edit">Video 3</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="" class="problem-edit">Problem title 11</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="sequence-edit">Problem Group</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit">Problem title 14</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="video-edit">Video 3</a>
<a href="#" class="draggable">handle</a>
</li>
</ol>
</li>
<!-- <li class="new-module"> -->
<!-- <%include file="new-module.html"/> -->
<!-- </li> -->
</ol>
</section>
</ol>
</section>
<section class="scratch-pad">
<ol>
<li>
<header>
<h2>Section Scratch</h2>
</header>
<ul>
<li>
<a href="#" class="problem-edit">Problem title 11</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit">Problem title 13 </a>
<section class="scratch-pad">
<ol>
<li class="new-module">
<%include file="new-module.html"/>
</li>
<li>
<header>
<h2>Section Scratch</h2>
</header>
<ul>
<li>
<a href="#" class="problem-edit">Problem title 11</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit"> Problem title 14</a>
</li>
<li>
<a href="#" class="problem-edit">Problem title 13 </a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="" class="video-edit">Video 3</a>
<a href="#" class="draggable">handle</a>
</li>
</ul>
</li>
<li>
<header>
<h2>Course Scratch</h2>
</header>
<ul>
<li>
<a href="#" class="problem-edit">Problem title 11</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit">Problem title 13 </a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit"> Problem title 14</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="" class="video-edit">Video 3</a>
<a href="#" class="draggable">handle</a>
</li>
</ul>
</li>
</li>
<li>
<a href="#" class="problem-edit"> Problem title 14</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="" class="video-edit">Video 3</a>
<a href="#" class="draggable">handle</a>
</li>
</ul>
</li>
<li>
<header>
<h2>Course Scratch</h2>
</header>
<!-- <li class="new-module"> -->
<!-- <%include file="new-module.html"/> -->
<!-- </li> -->
</ol>
</section>
</div>
</section>
<ul>
<li>
<a href="#" class="problem-edit">Problem title 11</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit">Problem title 13 </a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="#" class="problem-edit"> Problem title 14</a>
<a href="#" class="draggable">handle</a>
</li>
<li>
<a href="" class="video-edit">Video 3</a>
<a href="#" class="draggable">handle</a>
</li>
</ul>
</li>
</ol>
</section>
</div>
</section>
<div class="tooltip">
<ul>
<li><a href="#view" rel="leanModal">View</a></li>
<li><a href="#">Download</a></li>
<li><a href="#" class="delete-speed">Delete</a></li>
</ul>
</div>
<li class="video-box">
<div class="thumb"><img src="http://placehold.it/100x65" /></div>
<div class="meta">
<strong>video-name</strong> 236mb Uploaded 6 hours ago by <em>Anant Agrawal</em>
<p>
<ul class="speed-list">
Speed
<li class="speed">
0.75x
<%include file="speed-tooltip.html"/>
</li>
<li class="speed">Normal
<%include file="speed-tooltip.html"/>
</li>
<li class="speed">1.25x
<%include file="speed-tooltip.html"/>
</li>
<li class="speed">1.5x
<%include file="speed-tooltip.html"/>
</li>
<li style="background: #eee;" ><a href="#upload" rel="leanModal" class="new-upload">+</a></li>
</ul>
</p>
<p>
<a href="#">Download All</a>
<a href="#" style="color: brown;" class="remove">Delete All</a>
<a href="#" class="edit-captions"> Edit Captions </a>
<a href="#" class="use-video">Use clip ⬆</a>
</p>
</div>
<div class="caption-box">
<%include file="captions.html"/>
<%include file="save-captions.html"/>
</div>
</li>
<li class="video-box">
<div class="thumb"><img src="http://placehold.it/155x90" /></div>
<div class="meta">
<strong>video-name</strong> 236mb
<p>Uploaded 6 hours ago by <em>Anant Agrawal</em></p>
<p>
<ul class="speed-list">
Speed
<li class="speed">
0.75x
<%include file="speed-tooltip.html"/>
</li>
<li class="speed">Normal
<%include file="speed-tooltip.html"/>
</li>
<li class="speed">1.25x
<%include file="speed-tooltip.html"/>
</li>
<li class="speed">1.5x
<%include file="speed-tooltip.html"/>
</li>
<li style="background: #eee;" ><a href="#upload" rel="leanModal" class="new-upload">+</a></li>
</ul>
</p>
<p>
<a href="#">Download all</a>
<a href="#" stle="color: brown;" class="remove-video">Remove ⬇ </a>
</p>
</div>
<div style="margin-top: 30px;">
<%include file="captions.html"/>
</div>
</li>
......@@ -6,4 +6,6 @@ from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('',
url(r'^$', 'contentstore.views.index', name='index'),
url(r'^edit_item$', 'contentstore.views.edit_item', name='edit_item'),
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
)
......@@ -12,13 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
log = logging.getLogger("mitx.common.lib.mitxmako")
from django.template import Context
from django.http import HttpResponse
from . import middleware
from django.conf import settings
def render_to_string(template_name, dictionary, context=None, namespace='main'):
context_instance = Context(dictionary)
# add dictionary to context_instance
......@@ -27,8 +30,11 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'):
context_dictionary = {}
context_instance['settings'] = settings
context_instance['MITX_ROOT_URL'] = settings.MITX_ROOT_URL
for d in middleware.requestcontext:
context_dictionary.update(d)
# In various testing contexts, there might not be a current request context.
if middleware.requestcontext is not None:
for d in middleware.requestcontext:
context_dictionary.update(d)
for d in context_instance:
context_dictionary.update(d)
if context:
......
......@@ -10,7 +10,8 @@ import StringIO
from datetime import timedelta
from lxml import etree
from x_module import XModule, XModuleDescriptor
from x_module import XModule
from mako_module import MakoModuleDescriptor
from progress import Progress
from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError
......@@ -63,8 +64,14 @@ class ComplexEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, obj)
class ModuleDescriptor(XModuleDescriptor):
pass
class CapaModuleDescriptor(MakoModuleDescriptor):
"""
Module implementing problems in the LON-CAPA format,
as implemented by capa.capa_problem
"""
mako_template = 'widgets/problem-edit.html'
class Module(XModule):
......
import json
import logging
from x_module import XModule, XModuleDescriptor
from x_module import XModule
from mako_module import MakoModuleDescriptor
from lxml import etree
from pkg_resources import resource_string
log = logging.getLogger("mitx.courseware")
#-----------------------------------------------------------------------------
class ModuleDescriptor(XModuleDescriptor):
pass
class HtmlModuleDescriptor(MakoModuleDescriptor):
"""
Module for putting raw html in a course
"""
mako_template = "widgets/html-edit.html"
# TODO (cpennington): Make this into a proper module
js = {'coffee': [resource_string(__name__, 'js/module/html.coffee')]}
class Module(XModule):
id_attribute = 'filename'
......
class @HTML
constructor: (@id) ->
@edit_box = $("##{@id} .edit-box")
@preview = $("##{@id} .preview")
@edit_box.on('input', =>
@preview.empty().append(@edit_box.val())
)
save: -> {text: @edit_box.val()}
from x_module import XModuleDescriptor
from mitxmako.shortcuts import render_to_string
class MakoModuleDescriptor(XModuleDescriptor):
"""
Module descriptor intended as a mixin that uses a mako template
to specify the module html.
Expects the descriptor to have the `mako_template` attribute set
with the name of the template to render, and it will pass
the descriptor as the `module` parameter to that template
"""
def get_html(self):
return render_to_string(self.mako_template, {
'module': self
})
......@@ -3,7 +3,8 @@ import logging
from lxml import etree
from x_module import XModule, XModuleDescriptor
from x_module import XModule
from mako_module import MakoModuleDescriptor
from xmodule.progress import Progress
log = logging.getLogger("mitx.common.lib.seq_module")
......@@ -12,9 +13,6 @@ log = logging.getLogger("mitx.common.lib.seq_module")
# OBSOLETE: This obsoletes 'type'
class_priority = ['video', 'problem']
class ModuleDescriptor(XModuleDescriptor):
pass
class Module(XModule):
''' Layout module which lays out content in a temporal sequence
'''
......@@ -118,5 +116,5 @@ class Module(XModule):
self.rendered = False
class SectionDescriptor(XModuleDescriptor):
pass
class SectionDescriptor(MakoModuleDescriptor):
mako_template = 'widgets/sequence-edit.html'
......@@ -5,6 +5,9 @@ setup(
version="0.1",
packages=find_packages(),
install_requires=['distribute'],
package_data={
'': ['js/*']
},
# See http://guide.python-distribute.org/creation.html#entry-points
# for a description of entry_points
......@@ -19,6 +22,9 @@ setup(
"TutorialIndex = seq_module:SectionDescriptor",
"Exam = seq_module:SectionDescriptor",
"VideoSegment = video_module:VideoSegmentDescriptor",
"ProblemSet = seq_module:SectionDescriptor",
"Problem = capa_module:CapaModuleDescriptor",
"HTML = html_module:HtmlModuleDescriptor",
]
}
)
......@@ -3,7 +3,6 @@ import pkg_resources
import logging
from keystore import Location
from progress import Progress
log = logging.getLogger('mitx.' + __name__)
......@@ -30,6 +29,12 @@ class Plugin(object):
return classes[0].load()
@classmethod
def load_classes(cls):
return [class_.load()
for class_
in pkg_resources.iter_entry_points(cls.entry_point)]
class XModule(object):
''' Implements a generic learning module.
......@@ -154,6 +159,7 @@ class XModuleDescriptor(Plugin):
and can generate XModules (which do know about student state).
"""
entry_point = "xmodule.v1"
js = {}
@staticmethod
def load_from_json(json_data, system):
......@@ -178,6 +184,19 @@ class XModuleDescriptor(Plugin):
"""
return cls(system=system, **json_data)
@classmethod
def get_javascript(cls):
"""
Return a dictionary containing some of the following keys:
coffee: A list of coffeescript fragments that should be compiled and
placed on the page
js: A list of javascript fragments that should be included on the page
All of these will be loaded onto the page in the CMS
"""
return cls.js
def __init__(self,
system,
definition=None,
......@@ -202,6 +221,7 @@ class XModuleDescriptor(Plugin):
self.definition = definition if definition is not None else {}
self.name = Location(kwargs.get('location')).name
self.type = Location(kwargs.get('location')).category
self.url = Location(kwargs.get('location')).url()
# For now, we represent goals as a list of strings, but this
# is one of the things that we are going to be iterating on heavily
......@@ -220,21 +240,27 @@ class XModuleDescriptor(Plugin):
else:
return [child for child in self._child_instances if child.type in categories]
def get_html(self):
"""
Return the html used to edit this module
"""
raise NotImplementedError("get_html() must be provided by specific modules")
def get_xml(self):
''' For conversions between JSON and legacy XML representations.
'''
if self.xml:
if self.xml:
return self.xml
else:
else:
raise NotImplementedError("JSON->XML Translation not implemented")
def get_json(self):
''' For conversions between JSON and legacy XML representations.
'''
if self.json:
if self.json:
raise NotImplementedError
return self.json # TODO: Return context as well -- files, etc.
else:
return self.json # TODO: Return context as well -- files, etc.
else:
raise NotImplementedError("XML->JSON Translation not implemented")
#def handle_cms_json(self):
......
Common test infrastructure for LMS + CMS
===========================
data/ has some test course data.
Once the course validation is separated from django, we should have scripts here that checks that a course consists only of xml that we understand.
This is a realistic course, with many different module types and a lot of structure. It is based on 6.002x.
This is a simple, but non-trivial, course using multiple module types and some nested structure.
This is a very very simple course, useful for initial debugging of processing code.
<course name="Toy Course" graceperiod="1 day 5 hours 59 minutes 59 seconds" showanswer="always" rerandomize="never">
<chapter name="Overview">
<section format="Video" name="Welcome">
<video youtube="0.75:izygArpw-Qo,1.0:p2Q6BrNhdh8,1.25:1EeWXzPdhSA,1.50:rABDYkeK0x8"/>
</section>
<section format="Lecture Sequence" name="System Usage Sequence">
<html id="Lab2A" filename="Lab2A.html"/>
<video name="S0V1: Video Resources" youtube="0.75:EuzkdzfR0i8,1.0:1bK-WdDi6Qw,1.25:0v1VzoDVUTM,1.50:Bxk_-ZJb240"/>
</section>
</chapter>
</course>
......@@ -10,46 +10,98 @@ from courseware.content_parser import course_file
import courseware.module_render
import xmodule
import mitxmako.middleware as middleware
middleware.MakoMiddleware()
def check_names(user, course):
'''
Complain if any problems have alphanumeric names.
TODO (vshnayder): there are some in 6.002x that don't. Is that actually a problem?
'''
all_ok = True
print "Confirming all problems have alphanumeric names"
for problem in course.xpath('//problem'):
filename = problem.get('filename')
if not filename.isalnum():
print "==============> Invalid (non-alphanumeric) filename", filename
all_ok = False
return all_ok
def check_rendering(user, course):
'''Check that all modules render'''
all_ok = True
print "Confirming all modules render. Nothing should print during this step. "
for module in course.xpath('//problem|//html|//video|//vertical|//sequential|/tab'):
module_class = xmodule.modx_modules[module.tag]
# TODO: Abstract this out in render_module.py
try:
module_class(etree.tostring(module),
module.get('id'),
ajax_url='',
state=None,
track_function = lambda x,y,z:None,
render_function = lambda x: {'content':'','type':'video'})
except Exception as ex:
print "==============> Error in ", etree.tostring(module)
print ""
print ex
all_ok = False
print "Module render check finished"
return all_ok
def check_sections(user, course):
all_ok = True
sections_dir = settings.DATA_DIR + "/sections"
print "Checking that all sections exist and parse properly"
if os.path.exists(sections_dir):
print "Checking all section includes are valid XML"
for f in os.listdir(sections_dir):
sectionfile = sections_dir + '/' + f
#print sectionfile
# skip non-xml files:
if not sectionfile.endswith('xml'):
continue
try:
etree.parse(sectionfile)
except Exception as ex:
print "================> Error parsing ", sectionfile
print ex
all_ok = False
print "checked all sections"
else:
print "Skipping check of include files -- no section includes dir ("+sections_dir+")"
return all_ok
class Command(BaseCommand):
help = "Does basic validity tests on course.xml."
def handle(self, *args, **options):
check = True
all_ok = True
# TODO (vshnayder): create dummy user objects. Anon, authenticated, staff.
# Check that everything works for each.
# The objects probably shouldn't be actual django users to avoid unneeded
# dependency on django.
# TODO: use args as list of files to check. Fix loading to work for other files.
sample_user = User.objects.all()[0]
print "Attempting to load courseware"
course = course_file(sample_user)
print "Confirming all problems have alphanumeric names"
for problem in course.xpath('//problem'):
filename = problem.get('filename')
if not filename.isalnum():
print "==============> Invalid (non-alphanumeric) filename", filename
check = False
print "Confirming all modules render. Nothing should print during this step. "
for module in course.xpath('//problem|//html|//video|//vertical|//sequential|/tab'):
module_class = xmodule.modx_modules[module.tag]
# TODO: Abstract this out in render_module.py
try:
module_class(etree.tostring(module),
module.get('id'),
ajax_url='',
state=None,
track_function = lambda x,y,z:None,
render_function = lambda x: {'content':'','type':'video'})
except:
print "==============> Error in ", etree.tostring(module)
check = False
print "Module render check finished"
sections_dir = settings.DATA_DIR+"sections"
if os.path.exists(sections_dir):
print "Checking all section includes are valid XML"
for f in os.listdir(sections_dir):
print f
etree.parse(sections_dir+'/'+f)
else:
print "Skipping check of include files -- no section includes dir ("+sections_dir+")"
to_run = [check_names,
# TODO (vshnayder) : make check_rendering work (use module_render.py),
# turn it on
# check_rendering,
check_sections,
]
for check in to_run:
all_ok = check(sample_user, course) and all_ok
# TODO: print "Checking course properly annotated with preprocess.py"
if check:
if all_ok:
print 'Courseware passes all checks!'
else:
print "Courseware fails some checks"
......@@ -20,10 +20,12 @@ jasmine.stubRequests = ->
settings.success data: jasmine.stubbedMetadata[match[1]]
else if match = settings.url.match /static\/subs\/(.+)\.srt\.sjson/
settings.success jasmine.stubbedCaption
else if settings.url.match /modx\/problem\/.+\/problem_get$/
settings.success html: readFixtures('problem_content.html')
else if settings.url == '/calculate' ||
settings.url == '/6002x/modx/sequence/1/goto_position' ||
settings.url.match(/event$/) ||
settings.url.match(/6002x\/modx\/problem\/.+\/problem_(check|reset|show|save)$/)
settings.url.match(/modx\/problem\/.+\/problem_(check|reset|show|save)$/)
# do nothing
else
throw "External request attempted for #{settings.url}, which is not defined."
......
......@@ -13,6 +13,7 @@ describe 'Problem', ->
spyOn($.fn, 'load').andCallFake (url, callback) ->
$(@).html readFixtures('problem_content.html')
callback()
jasmine.stubRequests()
describe 'constructor', ->
beforeEach ->
......@@ -21,12 +22,6 @@ describe 'Problem', ->
it 'set the element', ->
expect(@problem.element).toBe '#problem_1'
it 'set the content url', ->
expect(@problem.content_url).toEqual '/problem/url/problem_get?id=1'
it 'render the content', ->
expect($.fn.load).toHaveBeenCalledWith @problem.content_url, @problem.bind
describe 'bind', ->
beforeEach ->
spyOn window, 'update_schematics'
......@@ -57,8 +52,11 @@ describe 'Problem', ->
it 'bind the math input', ->
expect($('input.math')).toHandleWith 'keyup', @problem.refreshMath
it 'display the math input', ->
expect(@stubbedJax.root.toMathML).toHaveBeenCalled()
it 'replace math content on the page', ->
expect(MathJax.Hub.Queue.mostRecentCall.args).toEqual [
['Text', @stubbedJax, ''],
[@problem.updateMathML, @stubbedJax, $('#input_example_1').get(0)]
]
describe 'render', ->
beforeEach ->
......@@ -77,12 +75,19 @@ describe 'Problem', ->
expect(@problem.bind).toHaveBeenCalled()
describe 'with no content given', ->
beforeEach ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) ->
callback html: "Hello World"
@problem.render()
it 'load the content via ajax', ->
expect($.fn.load).toHaveBeenCalledWith @problem.content_url, @bind
expect(@problem.element.html()).toEqual 'Hello World'
it 're-bind the content', ->
expect(@problem.bind).toHaveBeenCalled()
describe 'check', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
@problem.answers = 'foo=1&bar=2'
......@@ -116,7 +121,6 @@ describe 'Problem', ->
describe 'reset', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
it 'log the problem_reset event', ->
......@@ -130,13 +134,13 @@ describe 'Problem', ->
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_reset', { id: 1 }, jasmine.any(Function)
it 'render the returned content', ->
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) -> callback("Reset!")
spyOn($, 'postWithPrefix').andCallFake (url, answers, callback) ->
callback html: "Reset!"
@problem.reset()
expect(@problem.element.html()).toEqual 'Reset!'
describe 'show', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
@problem.element.prepend '<div id="answer_1_1" /><div id="answer_1_2" />'
......@@ -154,18 +158,19 @@ describe 'Problem', ->
expect($.postWithPrefix).toHaveBeenCalledWith '/modx/problem/1/problem_show', jasmine.any(Function)
it 'show the answers', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback('1_1': 'One', '1_2': 'Two')
spyOn($, 'postWithPrefix').andCallFake (url, callback) ->
callback answers: '1_1': 'One', '1_2': 'Two'
@problem.show()
expect($('#answer_1_1')).toHaveHtml 'One'
expect($('#answer_1_2')).toHaveHtml 'Two'
it 'toggle the show answer button', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback({})
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(answers: {})
@problem.show()
expect($('.show')).toHaveValue 'Hide Answer'
it 'add the showed class to element', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback({})
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback(answers: {})
@problem.show()
expect(@problem.element).toHaveClass 'showed'
......@@ -179,7 +184,8 @@ describe 'Problem', ->
'''
it 'set the correct_answer attribute on the choice', ->
spyOn($, 'postWithPrefix').andCallFake (url, callback) -> callback('1_1': [2, 3])
spyOn($, 'postWithPrefix').andCallFake (url, callback) ->
callback answers: '1_1': [2, 3]
@problem.show()
expect($('label[for="input_1_1_1"]')).not.toHaveAttr 'correct_answer', 'true'
expect($('label[for="input_1_1_2"]')).toHaveAttr 'correct_answer', 'true'
......@@ -214,7 +220,6 @@ describe 'Problem', ->
describe 'save', ->
beforeEach ->
jasmine.stubRequests()
@problem = new Problem 1, '/problem/url/'
@problem.answers = 'foo=1&bar=2'
......@@ -236,23 +241,29 @@ describe 'Problem', ->
describe 'refreshMath', ->
beforeEach ->
@problem = new Problem 1, '/problem/url/'
@stubbedJax.root.toMathML.andReturn '<MathML>'
$('#input_example_1').val 'E=mc^2'
@problem.refreshMath target: $('#input_example_1').get(0)
it 'should queue the conversion and MathML element update', ->
expect(MathJax.Hub.Queue).toHaveBeenCalledWith ['Text', @stubbedJax, 'E=mc^2'],
[@problem.updateMathML, @stubbedJax, $('#input_example_1').get(0)]
describe 'updateMathML', ->
beforeEach ->
@problem = new Problem 1, '/problem/url/'
@stubbedJax.root.toMathML.andReturn '<MathML>'
describe 'when there is no exception', ->
beforeEach ->
@problem.refreshMath target: $('#input_example_1').get(0)
it 'should convert and display the MathML object', ->
expect(MathJax.Hub.Queue).toHaveBeenCalledWith ['Text', @stubbedJax, 'E=mc^2']
@problem.updateMathML @stubbedJax, $('#input_example_1').get(0)
it 'should display debug output in hidden div', ->
it 'convert jax to MathML', ->
expect($('#input_example_1_dynamath')).toHaveValue '<MathML>'
describe 'when there is an exception', ->
beforeEach ->
@stubbedJax.root.toMathML.andThrow {restart: true}
@problem.refreshMath target: $('#input_example_1').get(0)
@problem.updateMathML @stubbedJax, $('#input_example_1').get(0)
it 'should queue up the exception', ->
expect(MathJax.Callback.After).toHaveBeenCalledWith [@problem.refreshMath, @stubbedJax], true
......
......@@ -35,6 +35,9 @@ describe 'VideoCaption', ->
it 'bind player resize event', ->
expect($(@player)).toHandleWith 'resize', @caption.onWindowResize
it 'bind player seek event', ->
expect($(@player)).toHandleWith 'seek', @caption.onUpdatePlayTime
it 'bind player updatePlayTime event', ->
expect($(@player)).toHandleWith 'updatePlayTime', @caption.onUpdatePlayTime
......
class @Problem
constructor: (@id, url) ->
@element = $("#problem_#{id}")
@content_url = "#{url}problem_get?id=#{@id}"
@render()
$: (selector) ->
......@@ -17,7 +16,7 @@ class @Problem
@$('section.action input.save').click @save
@$('input.math').keyup(@refreshMath).each(@refreshMath)
update_progress: (response) =>
updateProgress: (response) =>
if response.progress_changed
@element.attr progress: response.progress_status
@element.trigger('progressChanged')
......@@ -27,10 +26,9 @@ class @Problem
@element.html(content)
@bind()
else
$.postWithPrefix "/modx/problem/#{@id}/problem_get", '', (response) =>
$.postWithPrefix "/modx/problem/#{@id}/problem_get", (response) =>
@element.html(response.html)
@bind()
check: =>
Logger.log 'problem_check', @answers
......@@ -38,7 +36,7 @@ class @Problem
switch response.success
when 'incorrect', 'correct'
@render(response.contents)
@update_progress response
@updateProgress response
else
alert(response.success)
......@@ -46,7 +44,7 @@ class @Problem
Logger.log 'problem_reset', @answers
$.postWithPrefix "/modx/problem/#{@id}/problem_reset", id: @id, (response) =>
@render(response.html)
@update_progress response
@updateProgress response
show: =>
if !@element.hasClass 'showed'
......@@ -62,7 +60,7 @@ class @Problem
MathJax.Hub.Queue ["Typeset", MathJax.Hub]
@$('.show').val 'Hide Answer'
@element.addClass 'showed'
@update_progress response
@updateProgress response
else
@$('[id^=answer_], [id^=solution_]').text ''
@$('[correct_answer]').attr correct_answer: null
......@@ -74,21 +72,22 @@ class @Problem
$.postWithPrefix "/modx/problem/#{@id}/problem_save", @answers, (response) =>
if response.success
alert 'Saved'
@update_progress response
@updateProgress response
refreshMath: (event, element) =>
element = event.target unless element
target = "display_#{element.id.replace(/^input_/, '')}"
if jax = MathJax.Hub.getAllJax(target)[0]
MathJax.Hub.Queue ['Text', jax, $(element).val()]
MathJax.Hub.Queue ['Text', jax, $(element).val()],
[@updateMathML, jax, element]
try
output = jax.root.toMathML ''
$("##{element.id}_dynamath").val(output)
catch exception
throw exception unless exception.restart
MathJax.Callback.After [@refreshMath, jax], exception.restart
updateMathML: (jax, element) =>
try
$("##{element.id}_dynamath").val(jax.root.toMathML '')
catch exception
throw exception unless exception.restart
MathJax.Callback.After [@refreshMath, jax], exception.restart
refreshAnswers: =>
@$('input.schematic').each (index, element) ->
......
......@@ -10,6 +10,7 @@ class @VideoCaption
$(window).bind('resize', @onWindowResize)
$(@player).bind('resize', @onWindowResize)
$(@player).bind('updatePlayTime', @onUpdatePlayTime)
$(@player).bind('seek', @onUpdatePlayTime)
$(@player).bind('play', @onPlay)
@$('.hide-subtitles').click @toggle
@$('.subtitles').mouseenter(@onMouseEnter).mouseleave(@onMouseLeave)
......
......@@ -23,3 +23,7 @@ The dev server will automatically compile sass files that have changed. Simply s
the server using:
$ rake runserver
To run it along side of development:
$ sass --watch lms/static/sass:lms/static/sass -r ./lms/static/sass/bourbon/lib/bourbon.rb
......@@ -62,6 +62,8 @@ div.book-wrapper {
@extend .clearfix;
li {
background-color: darken($cream, 4%);
&.last {
display: block;
float: left;
......@@ -77,6 +79,10 @@ div.book-wrapper {
display: block;
float: right;
}
&:hover {
background: none;
}
}
}
......
......@@ -205,7 +205,7 @@ h1.top-header {
border-top: 1px solid #fff;
// @include box-shadow(inset 0 1px 0 #fff, inset 1px 0 0 #fff);
font-size: 12px;
height:46px;
// height:46px;
line-height: 46px;
margin: (-$body-line-height) (-$body-line-height) $body-line-height;
text-shadow: 0 1px 0 #fff;
......@@ -224,7 +224,7 @@ h1.top-header {
}
&.block-link {
background: darken($cream, 5%);
// background: darken($cream, 5%);
border-left: 1px solid darken($cream, 20%);
@include box-shadow(inset 1px 0 0 lighten($cream, 5%));
display: block;
......
......@@ -11,13 +11,14 @@ nav.sequence-nav {
height: 100%;
padding-right: flex-grid(1, 9);
width: 100%;
padding-left: 0;
a {
@extend .block-link;
}
li {
border-left: 1px solid darken($cream, 20%);
border-left: 1px solid darken($cream, 10%);
display: table-cell;
min-width: 20px;
......@@ -29,18 +30,17 @@ nav.sequence-nav {
background-repeat: no-repeat;
&:hover {
background-color: lighten($cream, 3%);
background-color: lighten($cream, 8%);
}
}
.visited {
background-color: #DCCDA2;
background-color: darken($cream, 4%);
background-repeat: no-repeat;
@include box-shadow(inset 0 0 3px darken(#dccda2, 10%));
@include box-shadow(0);
&:hover {
background-color: $cream;
background-position: center center;
}
}
......@@ -51,7 +51,6 @@ nav.sequence-nav {
&:hover {
background-color: #fff;
background-position: center;
}
}
......@@ -61,86 +60,87 @@ nav.sequence-nav {
cursor: pointer;
display: block;
height: 17px;
padding: 15px 0 14px;
padding: 15px 0 17px;
position: relative;
@include transition(all, .4s, $ease-in-out-quad);
width: 100%;
&.progress {
border-bottom-style: solid;
border-bottom-width: 4px;
}
&.progress-none {
@extend .progress;
border-bottom-color: red;
}
&.progress-some {
@extend .progress;
border-bottom-color: yellow;
}
&.progress-done {
@extend .progress;
border-bottom-color: green;
}
background-position: center 8px;
//video
&.seq_video_inactive {
@extend .inactive;
background-image: url('../images/sequence-nav/video-icon-normal.png');
background-position: center;
background-image: url('../images/sequence-nav/video-icon-visited.png');
}
&.seq_video_visited {
@extend .visited;
background-image: url('../images/sequence-nav/video-icon-visited.png');
background-position: center;
background-image: url('../images/sequence-nav/video-icon-normal.png');
}
&.seq_video_active {
@extend .active;
background-image: url('../images/sequence-nav/video-icon-current.png');
background-position: center;
}
//other
&.seq_other_inactive {
@extend .inactive;
background-image: url('../images/sequence-nav/document-icon-normal.png');
background-position: center;
background-image: url('../images/sequence-nav/document-icon-visited.png');
}
&.seq_other_visited {
@extend .visited;
background-image: url('../images/sequence-nav/document-icon-visited.png');
background-position: center;
background-image: url('../images/sequence-nav/document-icon-normal.png');
}
&.seq_other_active {
@extend .active;
background-image: url('../images/sequence-nav/document-icon-current.png');
background-position: center;
}
//vertical & problems
&.seq_vertical_inactive, &.seq_problem_inactive {
@extend .inactive;
background-image: url('../images/sequence-nav/list-icon-normal.png');
background-position: center;
background-image: url('../images/sequence-nav/list-icon-visited.png');
}
&.seq_vertical_visited, &.seq_problem_visited {
@extend .visited;
background-image: url('../images/sequence-nav/list-icon-visited.png');
background-position: center;
background-image: url('../images/sequence-nav/list-icon-normal.png');
}
&.seq_vertical_active, &.seq_problem_active {
@extend .active;
background-image: url('../images/sequence-nav/list-icon-current.png');
background-position: center;
}
&:after {
content: " ";
display: block;
width: 11px;
height: 11px;
background: url('../images/sequence-nav/status/dash.png') no-repeat center;
@include position( absolute, 0 0 6px 50% );
margin-left: -5px;
}
//progress
&.progress-none {
&:after {
background: url('../images/sequence-nav/status/not-started.png') no-repeat center;
}
}
&.progress-some {
&:after {
background: url('../images/sequence-nav/status/wrong.png') no-repeat center;
}
}
&.progress-done {
&:after {
background: url('../images/sequence-nav/status/check.png') no-repeat center;
}
}
p {
......@@ -180,6 +180,8 @@ nav.sequence-nav {
}
&:hover {
background-position: center 8px;
p {
display: block;
margin-top: 4px;
......@@ -215,6 +217,7 @@ nav.sequence-nav {
display: block;
text-indent: -9999px;
@include transition(all, .2s, $ease-in-out-quad);
line-height: 49px;
&:hover {
opacity: .5;
......@@ -277,6 +280,7 @@ section.course-content {
@include border-radius(3px);
@include box-shadow(inset 0 0 0 1px lighten(#f6efd4, 5%));
@include inline-block();
padding-left: 0;
li {
float: left;
......
......@@ -52,14 +52,14 @@ section.course-index {
padding: 1em 1.5em;
li {
background: transparent;
border: 1px solid transparent;
@include border-radius(4px);
margin-bottom: lh(.5);
position: relative;
padding: 5px 36px 5px 10px;
a {
border: 1px solid transparent;
background: transparent;
@include border-radius(4px);
position: relative;
padding: 5px 36px 5px 10px;
text-decoration: none;
display: block;
color: #666;
......@@ -74,67 +74,70 @@ section.course-index {
display: block;
}
}
}
&:after {
background: transparent;
border-top: 1px solid rgb(180,180,180);
border-right: 1px solid rgb(180,180,180);
content: "";
display: block;
height: 12px;
margin-top: -6px;
opacity: 0;
position: absolute;
top: 50%;
right: 30px;
@include transform(rotate(45deg));
width: 12px;
}
&:hover {
@include background-image(linear-gradient(-90deg, rgba(245,245,245, 0.4), rgba(230,230,230, 0.4)));
border-color: rgb(200,200,200);
&:after {
opacity: 1;
right: 15px;
@include transition(all, 0.2s, linear);
background: transparent;
border-top: 1px solid rgb(180,180,180);
border-right: 1px solid rgb(180,180,180);
content: "";
display: block;
height: 12px;
margin-top: -6px;
opacity: 0;
position: absolute;
top: 50%;
right: 30px;
@include transform(rotate(45deg));
width: 12px;
}
> a p {
color: #333;
&:hover {
@include background-image(linear-gradient(-90deg, rgba(245,245,245, 0.4), rgba(230,230,230, 0.4)));
border-color: rgb(200,200,200);
&:after {
opacity: 1;
right: 15px;
@include transition(all, 0.2s, linear);
}
> a p {
color: #333;
}
}
}
&:active {
@include box-shadow(inset 0 1px 14px 0 rgba(0,0,0, 0.1));
top: 1px;
&:active {
@include box-shadow(inset 0 1px 14px 0 rgba(0,0,0, 0.1));
&:after {
opacity: 1;
right: 15px;
&:after {
opacity: 1;
right: 15px;
}
}
}
&.active {
background: rgb(240,240,240);
@include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(230,230,230)));
border-color: rgb(200,200,200);
font-weight: bold;
> a p {
color: #333;
> a {
background: rgb(240,240,240);
@include background-image(linear-gradient(-90deg, rgb(245,245,245), rgb(230,230,230)));
border-color: rgb(200,200,200);
&:after {
opacity: 1;
right: 15px;
}
p {
color: #333;
}
}
span.subtitle {
font-weight: normal;
}
&:after {
opacity: 1;
right: 15px;
}
}
}
}
......
......@@ -58,6 +58,11 @@ task :pylint => REPORT_DIR do
end
end
default_options = {
:lms => '8000',
:cms => '8001',
}
[:lms, :cms].each do |system|
task_name = "test_#{system}"
report_dir = File.join(REPORT_DIR, task_name)
......@@ -76,7 +81,7 @@ end
Other useful environments are devplus (for dev testing with a real local database)
desc
task system, [:env, :options] => [] do |t, args|
args.with_defaults(:env => 'dev', :options => '')
args.with_defaults(:env => 'dev', :options => default_options[system])
sh(django_admin(system, args.env, 'runserver', args.options))
end
end
......@@ -154,3 +159,15 @@ end
task :publish => :package do
sh("scp #{BUILD_DIR}/#{NORMALIZED_DEPLOY_NAME}_#{PKG_VERSION}*.deb #{PACKAGE_REPO}")
end
namespace :cms do
desc "Import course data within the given DATA_DIR variable"
task :import do
if ENV['DATA_DIR']
sh(django_admin(:cms, :dev, :import, ENV['DATA_DIR']))
else
raise "Please specify a DATA_DIR variable that point to your data directory.\n" +
"Example: \`rake cms:import DATA_DIR=../data\`"
end
end
end
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