Commit e5a0ea86 by Matthew Mongeau

wip

parent 9d0418ee
$(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;
});
});
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;
> header {
background: #666;
@include clearfix;
color: #fff;
padding: 6px;
border-bottom: 1px solid #333;
-webkit-font-smoothing: antialiased;
h2 {
float: left;
font-size: 14px;
}
a {
color: #fff;
&.save-update {
float: right;
}
&.cancel {
float: left;
}
}
}
> section {
padding: 20px;
> header {
h1 {
font-size: 24px;
margin: 12px 0;
}
section {
&.status-settings {
ul {
list-style: none;
@include border-radius(2px);
border: 1px solid #999;
@include inline-block();
li {
@include inline-block();
border-right: 1px solid #999;
padding: 6px;
&:last-child {
border-right: 0;
}
&.current {
background: #eee;
}
}
}
a.settings {
@include inline-block();
margin: 0 20px;
border: 1px solid #999;
padding: 6px;
}
select {
float: right;
}
}
&.meta {
background: #eee;
padding: 10px;
margin: 20px 0;
@include clearfix();
div {
float: left;
margin-right: 20px;
h2 {
font-size: 14px;
@include inline-block();
}
p {
@include inline-block();
}
}
}
}
}
section.notes {
margin-top: 20px;
padding: 6px;
background: #eee;
border: 1px solid #ccc;
textarea {
@include box-sizing(border-box);
display: block;
width: 100%;
}
h2 {
font-size: 14px;
margin-bottom: 6px;
}
input[type="submit"]{
margin-top: 10px;
}
}
}
}
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.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;
}
}
}
section.week-edit,
section.week-new,
section.sequence-edit {
> header {
border-bottom: 2px solid #333;
@include clearfix();
div {
@include clearfix();
padding: 6px 20px;
h1 {
font-size: 18px;
text-transform: uppercase;
letter-spacing: 1px;
float: left;
}
p {
float: right;
}
&.week {
background: #eee;
font-size: 12px;
border-bottom: 1px solid #ccc;
h2 {
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;
}
}
}
}
}
section.goals {
background: #eee;
padding: 6px 20px;
border-top: 1px solid #ccc;
ul {
list-style: none;
color: #999;
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;
}
}
}
}
> 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;
}
}
&.modules {
@include box-sizing(border-box);
display: table-cell;
width: flex-grid(6, 9);
border-right: 1px solid #333;
&.empty {
text-align: center;
vertical-align: middle;
a {
@extend .button;
@include inline-block();
margin-top: 10px;
}
}
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;
li {
padding: 6px;
&:hover {
a.draggable {
opacity: 1;
}
}
a.draggable {
float: right;
opacity: .5;
}
&.group {
padding: 0;
header {
padding: 6px;
background: none;
h3 {
font-size: 14px;
}
}
ol {
border-left: 4px solid #999;
border-bottom: 0;
li {
&:last-child {
border-bottom: 0;
}
}
}
}
}
}
}
}
}
&.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;
li {
border-bottom: 1px solid #999;
background: #f9f9f9;
&:last-child {
border-bottom: 0;
}
ul {
list-style: none;
li {
padding: 6px;
&:last-child {
border-bottom: 0;
}
&:hover {
a.draggable {
opacity: 1;
}
}
&.empty {
padding: 12px;
a {
@extend .button;
display: block;
text-align: center;
}
}
a.draggable {
float: right;
opacity: .3;
}
a {
color: #000;
}
}
}
}
}
}
}
}
}
}
<ul class="tabs">
<li class="active">English (main)</li>
<li>French</li>
<li>English v2</li>
<li>+</li>
</ul>
<textarea class="captions">
{
"start": [
0, 2770, 5700, 7620, 10320, 12130, 13430, 15170, 17940, 20890, 22840, 26200, 28980, 30170, 32040, 33240, 36420, 37570, 41760, 44270, 48120, 50810, 52960, 54070, 56480, 58600, 59550, 62520, 67680, 69990, 74280, 77605, 81320, 85050, 88430, 92750, 96010, 99440, 103160, 106650, 110050, 114240, 118800, 120980, 122290, 125040, 127900, 130990, 133170, 134580, 139410, 143180, 144530, 147640, 150800, 153220, 156570, 161000, 162010, 162930, 164090, 165490, 167650, 170630, 172020, 174760, 178480, 181840, 185840, 188620, 194160, 196110, 199370, 201360, 204140, 209970, 211850, 215030, 218890, 221730, 225370, 228790, 231690, 234080, 236300, 237970, 240450, 244010, 247670, 251910, 253230, 260149, 263330, 266380, 269470, 273570, 278240, 280050, 282810, 288060, 292410, 297300, 298950, 300860, 302500, 304350, 309620, 312950, 318710, 322870, 323810, 328380, 332840, 334440, 338640, 341540, 345490, 348900, 350730, 354480, 357640, 362310, 365020, 366890, 368900, 373200, 374240, 379410, 381580, 381990, 385680, 389080, 390750, 393970, 395960, 397340, 401000, 403210, 405650, 408880, 411730, 415490, 421350, 425630, 427520, 430490, 435320, 436860, 439460, 443300, 447010, 450740, 453820, 456610, 460140, 463730, 466700, 471200, 472450, 475260, 476330, 480650, 483650, 486320, 489080, 491940, 496690, 501990, 502740, 507000, 511650, 513220, 517330, 519169, 524159, 528140, 529960, 531270, 535340, 541590, 543710, 545170, 550960, 551810, 555140, 556230, 557750, 560530, 564300, 566800, 567600, 569910, 573170, 578610, 580490, 585520, 586500, 589880, 591750, 596120, 597290, 600290, 602940, 606490, 608560, 610690, 612600, 613970, 616670, 621260, 622310, 624520, 626750, 629550, 632500, 635510, 637470, 638900, 640370, 644200, 647470, 648740, 652700, 653950 ],
"end": [
2770, 5700, 7620, 10320, 12130, 13430, 15170, 17940, 20890, 22840, 26200, 28980, 30170, 32040, 33240, 36420, 37570, 41760, 44269, 48120, 50809, 52960, 54070, 56480, 58599, 59550, 62519, 67680, 69990, 74280, 77605, 81320, 85050, 88429, 92750, 96010, 99440, 103160, 106649, 110050, 114240, 118800, 120980, 122290, 125040, 127900, 130990, 133170, 134579, 139410, 143180, 144530, 147640, 150799, 153220, 156570, 161000, 162010, 162929, 164090, 165490, 167650, 170630, 172019, 174760, 178480, 181840, 185840, 188620, 194160, 196109, 199370, 201360, 204140, 209970, 211850, 215030, 218890, 221730, 225369, 228790, 231690, 234079, 236300, 237970, 240450, 244010, 247670, 251910, 253230, 260149, 263330, 266380, 269469, 273570, 278240, 280050, 282810, 288060, 292410, 297300, 298950, 300860, 302500, 304350, 309620, 312950, 318710, 322870, 323810, 328380, 332840, 334440, 338640, 341539, 345490, 348900, 350729, 354480, 357640, 362310, 365020, 366890, 368900, 373200, 374240, 379410, 381580, 381990, 385680, 389080, 390750, 393970, 395960, 397340, 401000, 403210, 405650, 408880, 411730, 415490, 421350, 425630, 427520, 430490, 435320, 436860, 439460, 443299, 447010, 450740, 453820, 456610, 460140, 463729, 466700, 471200, 472450, 475260, 476330, 480650, 483650, 486320, 489080, 491940, 496690, 501990, 502740, 507000, 511650, 513220, 517330, 519169, 524159, 528140, 529960, 531270, 535340, 541590, 543710, 545170, 550959, 551810, 555140, 556230, 557750, 560530, 564300, 566800, 567599, 569910, 573170, 578610, 580490, 585520, 586500, 589880, 591750, 596120, 597290, 600290, 602939, 606490, 608560, 610689, 612600, 613970, 616670, 621260, 622310, 624520, 626750, 629550, 632500, 635510, 637470, 638900, 640370, 644200, 647470, 648740, 652700, 653950, 655050 ],
"text": [
"SUBJECT 1: The various methods\nI&#39;m going to show you--",
"in particular, the first method\none and method two for",
"solving non-linear equations--",
"are really just particular\nways of solving a pair of",
"equations where at least\none of each have some",
"non-linearity to them.",
"So let&#39;s start with the\ngraphical method.",
"And my circuit is on the\nright-hand side.",
"And I&#39;m showing you the same\nvoltage source resistor--",
"the Thevenin pattern--",
"connected to Device D. And\nbelow that, I have the",
"equation iD equals ae\nraised to bvD, which",
"is the device equation.",
"So as before, let me\ngo ahead and do all",
"the first few steps.",
"I go ahead and write the\nnode equation at vD.",
"And I got that.",
"I go in and substitute, as I\nalways do, in the node method",
"for the current, using\nthe device relation.",
"And just for fun here, let me\nkeep that separate for now.",
"In the analytical method, notice\nthat you ended up with",
"these two equations, and\nyou had to solve",
"for these two unknowns.",
"And we did that using\nanalytical methods.",
"In this video, I&#39;m going to\nsolve these using the",
"graphical method.",
"In order to do so here&#39;s what\nI&#39;m going to do I want to",
"start by rearranging the terms\nin my equation 1 to make it a",
"little bit more convenient\nto draw the graph.",
"So notice in equation 2, I\nhave iD equals something.",
"And then I have an expression\nin vD and iD.",
"So what I&#39;d like to do is let\nme start by taking this",
"equation here and expressing it\nin more of a standard form",
"so I can get something symmetric\nto equation 2.",
"So let me express this by\npulling iD to the left-hand",
"side all by itself, and so I\nget something like this.",
"So I get iD on the left-hand\nside, and then I&#39;m going to",
"move vD minus V divided by\nr do the right hand side.",
"So let&#39;s start with minus V\ndivided by R, and when I move",
"that to the right-hand side, it\nbecomes V divided by R. And",
"then when I move vD over R to\nthe right-hand side, I get",
"minus vD over R. And I&#39;ve simply\nnot done much here.",
"I just have done some\nrearranging of the terms in",
"equation 1.",
"And since I haven&#39;t done\nanything unique and different,",
"I&#39;m just going to label\nthis as 1 prime.",
"So I just got this from equation\n1, and I just labeled",
"that as 1 prime.",
"Continuing with the method--",
"and now I&#39;m summarizing for you\nequations 1 prime and 2--",
"we want both of them for iD on\nthe left-hand side expressed",
"as a function of vD.",
"And in equation 2,\nit is non-linear.",
"So the graphical method can\nbe summarized as follows.",
"To start, I want you to\nnotice something.",
"Essentially all we&#39;re trying to\ndo is find a solution, find",
"a value for vD and iD that\nsatisfies both equations, 1",
"prime and 2.",
"that&#39;s all we&#39;re trying to do.",
"It&#39;s just math here.",
"There&#39;s no circuits here.",
"We&#39;re just doing some relatively\nsimple math.",
"We just have to figure out what\niD and vD are, and we&#39;ll",
"use the graphical method.",
"So to do the graphical method,\nwhat I&#39;m going to do is I&#39;m",
"going to plot the two equations\nin a pair of graphs.",
"And let me first start by\nplotting equation 2.",
"And in equation 2, I&#39;m going\nto plot iD equals a",
"raised to ae bvD.",
"I&#39;m going to plot iD equals\na times e raised to bvD.",
"That&#39;s my equation 2.",
"And this is the plot\nthat I get for it.",
"It&#39;s a plot that I had before.",
"So that&#39;s my equation 2.",
"Notice that this plot here is\nsimply the constraint on iD",
"and vD imposed by the device.",
"This little circuit here\ncontaining the Thevenin",
"equivalent and connected to\nthe device, equation 2 is",
"simply the constraint imposed\nby the device.",
"The device properties are such\nthat it is going to constrain",
"iD and vD into some\nrelationship.",
"This constraint that I&#39;ve\nplotted here as equation 2 is",
"simply the constraint imposed\nby this device.",
"OK, next, let me\ngo to 1 prime.",
"And in 1 prime, let\nme go ahead and",
"plot the 1 prime equation.",
"And that equation, as we\nrewrite that here, is V",
"divided by R minus vD divided\nby R. So I&#39;m just going to",
"plot that equation for you\nin a second graph.",
"So how do I plot this?",
"So notice here that when vD is\n0, then iD is V divided by R.",
"So that is one point on\nthe straight line.",
"Notice that this is an equation\nfor a straight line.",
"It&#39;s a linear relationship\nbetween iD and vD.",
"Next, when iD is 0--\nso when iD is 0--",
"then notice that R and R can be\ncanceled out, and V will be",
"equal to vD.",
"So when iD is 0, V\nand vD are equal.",
"So therefore, vD equals V. So\nthis is the line when iD is 0.",
"And for that, vD equals V. So\nthen I get this [? long, ?]",
"straight line for the\nrelationship between vD and iD",
"according to 1 prime.",
"So what&#39;s the slope\nof this line here?",
"Can you tell me what the\nslope of this line is.",
"Let me give you a few seconds\nto think about it.",
null,
"OK, from the equation 1 prime--\nfrom this equation--",
"the slope is simply given by the\ncoefficient of vD, since",
"the slope is negative, which\nis why the line is inclined",
"the following way.",
"So the slope here is simply\nminus 1 divided by R.",
"So that is my constraint\nthat relates vD to iD.",
"And where did that constraint\ncome from?",
"That constraint is the\nconstraint on iD and vD that",
"has been imposed by the\nrest of the circuit.",
"So if the first constraint was\nimposed by the device, then",
"the second constraint is\nimposed by the Thevenin",
"equivalent that is connected\nto the device.",
"The Thevenin equivalent was\nthat V and R in series and",
"those two impose a Thevenin\nconstraint on the terminal",
"pair that relates vD and iD.",
"So now my next step is,\ngiven these two graphs",
"for iD versus vD--",
"and clearly those are\nmy two constraints.",
"Graph 2 says that iD and vD\nmust be somewhere on this",
"trajectory.",
"Graph 1 prime says, well, I&#39;m\nnot going to let vD and iD be",
"anywhere else but\non this curve.",
"That&#39;s it.",
"Both of them are fighting with\neach other and telling each",
"other, nope, I&#39;m not going to\nallow you to do anything.",
"You have to be on my curve.",
"So in this case, I have two\ncurves, and I need a point",
"that satisfies both curves.",
"And that is easy enough to do.",
"And I simply have to go\nand satisfy both these",
"constraints, and that will\ngive me the answer",
"for vD versus iD.",
"Before I do that, so I can go\nand solve it for you, let me",
"go and pick some values for\nthe various parameters.",
"So as before, I&#39;m going to pick\nV equals 1 volt, R equals",
"1 ohm, a, a quarter of an amp,\nand b to be one volt inverse.",
"So I&#39;ll pick the same parameters\nas I had done when",
"I did the analytical method.",
"Next, what I&#39;ll do is I&#39;ll\nsubstitute these parameters",
"into the two equations 1 prime\nand 2 and rewrite them with",
"the parameters substituted.",
"So for 1 prime, I get iD.",
"Since V is 1 and R is 1, I get\nV divided by R equals 1.",
"And then since R is\n1, I get minus vD.",
"So this is 1 prime, once I&#39;ve\nsubstituted the values.",
"And then for equation\n2, what do I get?",
"I get iD equals ae\nraised to bvD.",
"a is 1/4, so I write\nthat down.",
"b is 1, and so I\nget vD up here.",
"So I have my two equations in\nterms of the parameters I have",
"chosen, and now I can go ahead\nand plot the two equations and",
"see where they intersect.",
"I&#39;ve given you the form of the\ngraph here, and so let me go",
"ahead and plot this.",
"Let me go ahead and plot 2\nfirst. 2 looks like this,",
"where this point is 1/4.",
"That was the a point.",
"As I said before, this curve\nis simply my Equation 2.",
"Then let me go ahead and\nplot equation 1 prime.",
"And as you recall, that looked\nsomething like this where the",
"vD intercept was V and the\ny-intercept was given by V",
"divided by R.",
"And in this case, it was\n1, and V was also 1.",
"So those are my two curves, and\nhere&#39;s the point where the",
"two coincide.",
"And then I just have to go and\nfind the values of iD and vD",
"where they two intersect.",
"So at this point, it will be\n0.56 volts, and this point",
"will be 0.44 amps.",
"It&#39;s the same as what\nI calculated in",
"the analytical method.",
"So let me go ahead and write\nthat down. iD equals, in this",
"case, 0.44 amps, and vD\nequals 0.56 volts.",
"So basically, I&#39;ve just\ntaken the two graphs,",
"superimposed them.",
"This was 1 prime, and the\nnon-linear one was related to",
"Equation 2.",
"So before I jump off to one\nother thing, I can define",
"something for you.",
"Notice this curve here.",
"This is a straight line that\nreflects the Thevenin",
"constraint that I apply on\nmy non-linear device.",
"And I mentioned earlier we&#39;re\ngoing to do this again and",
"again and again.",
"I think you will see this at\nleast 10 more times in this",
"course where I take a Thevenin\nequivalent of the following",
"form, some voltage V, some R,\nand apply that in series",
"across something interesting.",
"So this line that I see here is\nthe constraint imposed by",
"the Thevenin equivalent.",
"And you can see that from\nthe equation 1 prime.",
"Now, there&#39;s a name\nfor this line.",
"So this line is called the &quot;load\nline.&quot; You will see more",
"reasons for this later.",
"But this line is called\nthe &quot;load line.&quot;",
"Let me show you one other little\ntrick, just in case you",
"didn&#39;t completely get how I\nsolved the graphical method.",
"But one little trick here.",
"If you like PowerPoint,\nyou will enjoy this.",
"And if you don&#39;t like\nPowerPoint, you",
"will hate me for it.",
"So if you look at these\ncurves, I have my two",
"equations in iD-- the 1 prime\nand 2-- and I&#39;ve plotted them",
"for you here.",
"On the left-hand side,\nI plotted equation 2.",
"Right-hand side, I plotted\nequation 1.",
"And fundamentally, all that the\ngraphical method is doing",
"is simply superimposing\nthe two graphs.",
"And by superimposing the two\ngraphs, it is finding the",
"point where the two\ncurves intersect.",
"And this is what you get.",
"So let me do that again.",
"So I take one of the curves and\nsuperimpose it on top of",
"the other curve given the\nsame axes and the same",
"scales for the axes.",
"And then I go ahead and find\nthe solution for the point",
"where the two intersect.",
null
]
}
</textarea>
<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="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 class="advanced">
<a href="#">Advanced filters</a>
</li>
<li>
<input type="search" name="" id="" value="" />
</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>
<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>
<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>
<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 class="new-module"> -->
<!-- <%include file="new-module.html"/> -->
<!-- </li> -->
</ol>
</section>
</div>
</section>
</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>
from nose.tools import assert_equals, assert_raises
from keystore import Location
from keystore.exceptions import InvalidLocationError
def check_string_roundtrip(url):
assert_equals(url, Location(url).url())
assert_equals(url, str(Location(url)))
def test_string_roundtrip():
check_string_roundtrip("tag://org/course/category/name")
check_string_roundtrip("tag://org/course/category/name/revision")
check_string_roundtrip("tag://org/course/category/name with spaces/revision")
def test_dict():
input_dict = {
'tag': 'tag',
'course': 'course',
'category': 'category',
'name': 'name',
'org': 'org'
}
assert_equals("tag://org/course/category/name", Location(input_dict).url())
assert_equals(dict(revision=None, **input_dict), Location(input_dict).dict())
input_dict['revision'] = 'revision'
assert_equals("tag://org/course/category/name/revision", Location(input_dict).url())
assert_equals(input_dict, Location(input_dict).dict())
def test_list():
input_list = ['tag', 'org', 'course', 'category', 'name']
assert_equals("tag://org/course/category/name", Location(input_list).url())
assert_equals(input_list + [None], Location(input_list).list())
input_list.append('revision')
assert_equals("tag://org/course/category/name/revision", Location(input_list).url())
assert_equals(input_list, Location(input_list).list())
def test_location():
input_list = ['tag', 'org', 'course', 'category', 'name']
assert_equals("tag://org/course/category/name", Location(Location(input_list)).url())
def test_invalid_locations():
assert_raises(InvalidLocationError, Location, "foo")
assert_raises(InvalidLocationError, Location, ["foo", "bar"])
assert_raises(InvalidLocationError, Location, ["foo", "bar", "baz", "blat", "foo/bar"])
assert_raises(InvalidLocationError, Location, None)
......@@ -11,6 +11,7 @@ from datetime import timedelta
from lxml import etree
from x_module import XModule, XModuleDescriptor
from progress import Progress
from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError
......@@ -79,24 +80,43 @@ class Module(XModule):
def get_xml_tags(c):
return ["problem"]
def get_state(self):
state = self.lcp.get_state()
state['attempts'] = self.attempts
return json.dumps(state)
def get_score(self):
return self.lcp.get_score()
def max_score(self):
return self.lcp.get_max_score()
def get_progress(self):
''' For now, just return score / max_score
'''
d = self.get_score()
score = d['score']
total = d['total']
if total > 0:
return Progress(score, total)
return None
def get_html(self):
return self.system.render_template('problem_ajax.html', {
'id': self.item_id,
'ajax_url': self.ajax_url,
})
def get_problem_html(self, encapsulate=True):
'''Return html for the problem. Adds check, reset, save buttons
as necessary based on the problem config and state.'''
html = self.lcp.get_html()
content = {'name': self.name,
'html': html,
......@@ -109,7 +129,7 @@ class Module(XModule):
reset_button = True
save_button = True
# If we're after deadline, or user has exhuasted attempts,
# If we're after deadline, or user has exhausted attempts,
# question is read-only.
if self.closed():
check_button = False
......@@ -154,11 +174,13 @@ class Module(XModule):
'attempts_used': self.attempts,
'attempts_allowed': self.max_attempts,
'explain': explain,
'progress': self.get_progress(),
}
html = self.system.render_template('problem.html', context)
if encapsulate:
html = '<div id="problem_{id}" class="problem" data-url="{ajax_url}">'.format(id=self.item_id, ajax_url=self.ajax_url) + html + "</div>"
html = '<div id="problem_{id}" class="problem" data-url="{ajax_url}">'.format(
id=self.item_id, ajax_url=self.ajax_url) + html + "</div>"
return html
......@@ -170,7 +192,8 @@ class Module(XModule):
dom2 = etree.fromstring(xml)
self.explanation = "problems/" + only_one(dom2.xpath('/problem/@explain'), default="closed")
self.explanation = "problems/" + only_one(dom2.xpath('/problem/@explain'),
default="closed")
# TODO: Should be converted to: self.explanation=only_one(dom2.xpath('/problem/@explain'), default="closed")
self.explain_available = only_one(dom2.xpath('/problem/@explain_available'))
......@@ -190,19 +213,19 @@ class Module(XModule):
self.grace_period = None
self.close_date = self.display_due_date
self.max_attempts =only_one(dom2.xpath('/problem/@attempts'))
if len(self.max_attempts)>0:
self.max_attempts =int(self.max_attempts)
self.max_attempts = only_one(dom2.xpath('/problem/@attempts'))
if len(self.max_attempts) > 0:
self.max_attempts = int(self.max_attempts)
else:
self.max_attempts =None
self.max_attempts = None
self.show_answer =only_one(dom2.xpath('/problem/@showanswer'))
self.show_answer = only_one(dom2.xpath('/problem/@showanswer'))
if self.show_answer =="":
self.show_answer ="closed"
if self.show_answer == "":
self.show_answer = "closed"
self.rerandomize =only_one(dom2.xpath('/problem/@rerandomize'))
if self.rerandomize =="" or self.rerandomize=="always" or self.rerandomize=="true":
self.rerandomize = only_one(dom2.xpath('/problem/@rerandomize'))
if self.rerandomize == "" or self.rerandomize=="always" or self.rerandomize=="true":
self.rerandomize="always"
elif self.rerandomize=="false" or self.rerandomize=="per_student":
self.rerandomize="per_student"
......@@ -253,23 +276,33 @@ class Module(XModule):
def handle_ajax(self, dispatch, get):
'''
This is called by courseware.module_render, to handle an AJAX call. "get" is request.POST
This is called by courseware.module_render, to handle an AJAX call.
"get" is request.POST.
Returns a json dictionary:
{ 'progress_changed' : True/False,
'progress' : 'none'/'in_progress'/'done',
<other request-specific values here > }
'''
if dispatch=='problem_get':
response = self.get_problem(get)
elif False: #self.close_date >
return json.dumps({"error":"Past due date"})
elif dispatch=='problem_check':
response = self.check_problem(get)
elif dispatch=='problem_reset':
response = self.reset_problem(get)
elif dispatch=='problem_save':
response = self.save_problem(get)
elif dispatch=='problem_show':
response = self.get_answer(get)
else:
return "Error"
return response
handlers = {
'problem_get': self.get_problem,
'problem_check': self.check_problem,
'problem_reset': self.reset_problem,
'problem_save': self.save_problem,
'problem_show': self.get_answer,
}
if dispatch not in handlers:
return 'Error'
before = self.get_progress()
d = handlers[dispatch](get)
after = self.get_progress()
d.update({
'progress_changed' : after != before,
'progress_status' : Progress.to_js_status_str(after),
})
return json.dumps(d, cls=ComplexEncoder)
def closed(self):
''' Is the student still allowed to submit answers? '''
......@@ -287,20 +320,19 @@ class Module(XModule):
'''
if self.show_answer == '':
return False
if self.show_answer == "never":
return False
if self.show_answer == 'attempted' and self.attempts == 0:
return False
if self.show_answer == 'attempted' and self.attempts > 0:
return True
if self.show_answer == 'answered' and self.lcp.done:
return True
if self.show_answer == 'answered' and not self.lcp.done:
return False
if self.show_answer == 'closed' and self.closed():
return True
if self.show_answer == 'closed' and not self.closed():
return False
if self.show_answer == 'attempted':
return self.attempts > 0
if self.show_answer == 'answered':
return self.lcp.done
if self.show_answer == 'closed':
return self.closed()
if self.show_answer == 'always':
return True
raise self.system.exception404 #TODO: Not 404
......@@ -310,45 +342,65 @@ class Module(XModule):
For the "show answer" button.
TODO: show answer events should be logged here, not just in the problem.js
Returns the answers: {'answers' : answers}
'''
if not self.answer_available():
raise self.system.exception404
else:
answers = self.lcp.get_question_answers()
return json.dumps(answers,
cls=ComplexEncoder)
return {'answers' : answers}
# Figure out if we should move these to capa_problem?
def get_problem(self, get):
''' Same as get_problem_html -- if we want to reconfirm we
have the right thing e.g. after several AJAX calls.'''
return self.get_problem_html(encapsulate=False)
''' Return results of get_problem_html, as a simple dict for json-ing.
{ 'html': <the-html> }
Used if we want to reconfirm we have the right thing e.g. after
several AJAX calls.
'''
return {'html' : self.get_problem_html(encapsulate=False)}
@staticmethod
def make_dict_of_responses(get):
'''Make dictionary of student responses (aka "answers")
get is POST dictionary.
'''
answers = dict()
for key in get:
# e.g. input_resistor_1 ==> resistor_1
_, _, name = key.partition('_')
answers[name] = get[key]
return answers
def check_problem(self, get):
''' Checks whether answers to a problem are correct, and
returns a map of correct/incorrect answers'''
returns a map of correct/incorrect answers:
{'success' : bool,
'contents' : html}
'''
event_info = dict()
event_info['state'] = self.lcp.get_state()
event_info['filename'] = self.filename
# make a dict of all the student responses ("answers").
answers=dict()
# input_resistor_1 ==> resistor_1
for key in get:
answers['_'.join(key.split('_')[1:])]=get[key]
answers = self.make_dict_of_responses(get)
event_info['answers']=answers
event_info['answers'] = answers
# Too late. Cannot submit
if self.closed():
event_info['failure']='closed'
event_info['failure'] = 'closed'
self.tracker('save_problem_check_fail', event_info)
# TODO (vshnayder): probably not 404?
raise self.system.exception404
# Problem submitted. Student should reset before checking
# again.
if self.lcp.done and self.rerandomize == "always":
event_info['failure']='unreset'
event_info['failure'] = 'unreset'
self.tracker('save_problem_check_fail', event_info)
raise self.system.exception404
......@@ -357,17 +409,22 @@ class Module(XModule):
lcp_id = self.lcp.problem_id
correct_map = self.lcp.grade_answers(answers)
except StudentInputError as inst:
self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state, system=self.system)
# TODO (vshnayder): why is this line here?
self.lcp = LoncapaProblem(self.filestore.open(self.filename),
id=lcp_id, state=old_state, system=self.system)
traceback.print_exc()
return json.dumps({'success':inst.message})
return {'success': inst.message}
except:
self.lcp = LoncapaProblem(self.filestore.open(self.filename), id=lcp_id, state=old_state, system=self.system)
# TODO: why is this line here?
self.lcp = LoncapaProblem(self.filestore.open(self.filename),
id=lcp_id, state=old_state, system=self.system)
traceback.print_exc()
raise Exception,"error in capa_module"
return json.dumps({'success':'Unknown Error'})
# TODO: Dead code... is this a bug, or just old?
return {'success':'Unknown Error'}
self.attempts = self.attempts + 1
self.lcp.done=True
self.lcp.done = True
success = 'correct' # success = correct if ALL questions in this problem are correct
for answer_id in correct_map:
......@@ -382,54 +439,64 @@ class Module(XModule):
html = self.get_problem_html(encapsulate=False) # render problem into HTML
except Exception,err:
log.error('failed to generate html')
raise Exception,err
raise
return json.dumps({'success': success,
return {'success': success,
'contents': html,
})
}
def save_problem(self, get):
'''
Save the passed in answers.
Returns a dict { 'success' : bool, ['error' : error-msg]},
with the error key only present if success is False.
'''
event_info = dict()
event_info['state'] = self.lcp.get_state()
event_info['filename'] = self.filename
answers=dict()
for key in get:
answers['_'.join(key.split('_')[1:])]=get[key]
answers = self.make_dict_of_responses(get)
event_info['answers'] = answers
# Too late. Cannot submit
if self.closed():
event_info['failure']='closed'
event_info['failure'] = 'closed'
self.tracker('save_problem_fail', event_info)
return "Problem is closed"
return {'success': False,
'error': "Problem is closed"}
# Problem submitted. Student should reset before saving
# again.
if self.lcp.done and self.rerandomize == "always":
event_info['failure']='done'
event_info['failure'] = 'done'
self.tracker('save_problem_fail', event_info)
return "Problem needs to be reset prior to save."
return {'success' : False,
'error' : "Problem needs to be reset prior to save."}
self.lcp.student_answers=answers
self.lcp.student_answers = answers
# TODO: should this be save_problem_fail? Looks like success to me...
self.tracker('save_problem_fail', event_info)
return json.dumps({'success':True})
return {'success': True}
def reset_problem(self, get):
''' Changes problem state to unfinished -- removes student answers,
and causes problem to rerender itself. '''
and causes problem to rerender itself.
Returns problem html as { 'html' : html-string }.
'''
event_info = dict()
event_info['old_state']=self.lcp.get_state()
event_info['filename']=self.filename
event_info['old_state'] = self.lcp.get_state()
event_info['filename'] = self.filename
if self.closed():
event_info['failure']='closed'
event_info['failure'] = 'closed'
self.tracker('reset_problem_fail', event_info)
return "Problem is closed"
if not self.lcp.done:
event_info['failure']='not_done'
event_info['failure'] = 'not_done'
self.tracker('reset_problem_fail', event_info)
return "Refresh the page and make an attempt before resetting."
......@@ -442,4 +509,4 @@ class Module(XModule):
event_info['new_state']=self.lcp.get_state()
self.tracker('reset_problem', event_info)
return json.dumps(self.get_problem_html(encapsulate=False))
return {'html' : self.get_problem_html(encapsulate=False)}
'''
Progress class for modules. Represents where a student is in a module.
Useful things to know:
- Use Progress.to_js_status_str() to convert a progress into a simple
status string to pass to js.
- Use Progress.to_js_detail_str() to convert a progress into a more detailed
string to pass to js.
In particular, these functions have a canonical handing of None.
For most subclassing needs, you should only need to reimplement
frac() and __str__().
'''
from collections import namedtuple
import numbers
class Progress(object):
'''Represents a progress of a/b (a out of b done)
a and b must be numeric, but not necessarily integer, with
0 <= a <= b and b > 0.
Progress can only represent Progress for modules where that makes sense. Other
modules (e.g. html) should return None from get_progress().
TODO: add tag for module type? Would allow for smarter merging.
'''
def __init__(self, a, b):
'''Construct a Progress object. a and b must be numbers, and must have
0 <= a <= b and b > 0
'''
# Want to do all checking at construction time, so explicitly check types
if not (isinstance(a, numbers.Number) and
isinstance(b, numbers.Number)):
raise TypeError('a and b must be numbers. Passed {0}/{1}'.format(a, b))
if not (0 <= a <= b and b > 0):
raise ValueError(
'fraction a/b = {0}/{1} must have 0 <= a <= b and b > 0'.format(a, b))
self._a = a
self._b = b
def frac(self):
''' Return tuple (a,b) representing progress of a/b'''
return (self._a, self._b)
def percent(self):
''' Returns a percentage progress as a float between 0 and 100.
subclassing note: implemented in terms of frac(), assumes sanity
checking is done at construction time.
'''
(a, b) = self.frac()
return 100.0 * a / b
def started(self):
''' Returns True if fractional progress is greater than 0.
subclassing note: implemented in terms of frac(), assumes sanity
checking is done at construction time.
'''
return self.frac()[0] > 0
def inprogress(self):
''' Returns True if fractional progress is strictly between 0 and 1.
subclassing note: implemented in terms of frac(), assumes sanity
checking is done at construction time.
'''
(a, b) = self.frac()
return a > 0 and a < b
def done(self):
''' Return True if this represents done.
subclassing note: implemented in terms of frac(), assumes sanity
checking is done at construction time.
'''
(a, b) = self.frac()
return a==b
def ternary_str(self):
''' Return a string version of this progress: either
"none", "in_progress", or "done".
subclassing note: implemented in terms of frac()
'''
(a, b) = self.frac()
if a == 0:
return "none"
if a < b:
return "in_progress"
return "done"
def __eq__(self, other):
''' Two Progress objects are equal if they have identical values.
Implemented in terms of frac()'''
if not isinstance(other, Progress):
return False
(a, b) = self.frac()
(a2, b2) = other.frac()
return a == a2 and b == b2
def __ne__(self, other):
''' The opposite of equal'''
return not self.__eq__(other)
def __str__(self):
''' Return a string representation of this string.
subclassing note: implemented in terms of frac().
'''
(a, b) = self.frac()
return "{0}/{1}".format(a, b)
@staticmethod
def add_counts(a, b):
'''Add two progress indicators, assuming that each represents items done:
(a / b) + (c / d) = (a + c) / (b + d).
If either is None, returns the other.
'''
if a is None:
return b
if b is None:
return a
# get numerators + denominators
(n, d) = a.frac()
(n2, d2) = b.frac()
return Progress(n + n2, d + d2)
@staticmethod
def to_js_status_str(progress):
'''
Return the "status string" version of the passed Progress
object that should be passed to js. Use this function when
sending Progress objects to js to limit dependencies.
'''
if progress is None:
return "NA"
return progress.ternary_str()
@staticmethod
def to_js_detail_str(progress):
'''
Return the "detail string" version of the passed Progress
object that should be passed to js. Use this function when
passing Progress objects to js to limit dependencies.
'''
if progress is None:
return "NA"
return str(progress)
import json
import logging
from lxml import etree
from x_module import XModule, XModuleDescriptor
from xmodule.progress import Progress
log = logging.getLogger("mitx.common.lib.seq_module")
# HACK: This shouldn't be hard-coded to two types
# OBSOLETE: This obsoletes 'type'
......@@ -37,6 +41,16 @@ class Module(XModule):
self.render()
return self.destroy_js
def get_progress(self):
''' Return the total progress, adding total done and total available.
(assumes that each submodule uses the same "units" for progress.)
'''
# TODO: Cache progress or children array?
children = self.get_children()
progresses = [child.get_progress() for child in children]
progress = reduce(Progress.add_counts, progresses)
return progress
def handle_ajax(self, dispatch, get): # TODO: bounds checking
''' get = request.POST instance '''
if dispatch=='goto_position':
......@@ -53,10 +67,15 @@ class Module(XModule):
titles = ["\n".join([i.get("name").strip() for i in e.iter() if i.get("name") is not None]) \
for e in self.xmltree]
children = self.get_children()
progresses = [child.get_progress() for child in children]
self.contents = self.rendered_children()
for contents, title in zip(self.contents, titles):
for contents, title, progress in zip(self.contents, titles, progresses):
contents['title'] = title
contents['progress_status'] = Progress.to_js_status_str(progress)
contents['progress_detail'] = Progress.to_js_detail_str(progress)
for (content, element_class) in zip(self.contents, child_classes):
new_class = 'other'
......@@ -68,16 +87,17 @@ class Module(XModule):
# Split </script> tags -- browsers handle this as end
# of script, even if it occurs mid-string. Do this after json.dumps()ing
# so that we can be sure of the quotations being used
params={'items':json.dumps(self.contents).replace('</script>', '<"+"/script>'),
'id':self.item_id,
params={'items': json.dumps(self.contents).replace('</script>', '<"+"/script>'),
'id': self.item_id,
'position': self.position,
'titles':titles,
'tag':self.xmltree.tag}
'titles': titles,
'tag': self.xmltree.tag}
if self.xmltree.tag in ['sequential', 'videosequence']:
self.content = self.system.render_template('seq_module.html', params)
if self.xmltree.tag == 'tab':
self.content = self.system.render_template('tab_module.html', params)
log.debug("rendered content: %s", content)
self.rendered = True
def __init__(self, system, xml, item_id, state=None):
......
<problem>
<script type="loncapa/python">
# from loncapa import *
x1 = 4 # lc_random(2,4,1)
y1 = 5 # lc_random(3,7,1)
x2 = 10 # lc_random(x1+1,9,1)
y2 = 20 # lc_random(y1+1,15,1)
m = (y2-y1)/(x2-x1)
b = y1 - m*x1
answer = "%s*x+%s" % (m,b)
answer = answer.replace('+-','-')
inverted_m = (x2-x1)/(y2-y1)
inverted_b = b
wrongans = "%s*x+%s" % (inverted_m,inverted_b)
wrongans = wrongans.replace('+-','-')
</script>
<text>
<p>Hints can be provided to students, based on the last response given, as well as the history of responses given. Here is an example of a hint produced by a Formula Response problem.</p>
<p>
What is the equation of the line which passess through ($x1,$y1) and
($x2,$y2)?</p>
<p>The correct answer is <tt>$answer</tt>. A common error is to invert the equation for the slope. Enter <tt>
$wrongans</tt> to see a hint.</p>
</text>
<formularesponse samples="x@-5:5#11" id="11" answer="$answer">
<responseparam description="Numerical Tolerance" type="tolerance" default="0.001" name="tol" />
<text>y = <textline size="25" /></text>
<hintgroup>
<formulahint samples="x@-5:5#11" answer="$wrongans" name="inversegrad">
</formulahint>
<hintpart on="inversegrad">
<text>You have inverted the slope in the question.</text>
</hintpart>
</hintgroup>
</formularesponse>
</problem>
<problem >
<text><h2>Example: String Response Problem</h2>
<br/>
</text>
<text>Which US state has Lansing as its capital?</text>
<stringresponse answer="Michigan" type="ci">
<textline size="20" />
<hintgroup>
<stringhint answer="wisconsin" type="cs" name="wisc">
</stringhint>
<stringhint answer="minnesota" type="cs" name="minn">
</stringhint>
<hintpart on="wisc">
<text>The state capital of Wisconsin is Madison.</text>
</hintpart>
<hintpart on="minn">
<text>The state capital of Minnesota is St. Paul.</text>
</hintpart>
<hintpart on="default">
<text>The state you are looking for is also known as the 'Great Lakes State'</text>
</hintpart>
</hintgroup>
</stringresponse>
</problem>
<problem>
<text>
<h2>Example: Symbolic Math Response Problem</h2>
<p>
A symbolic math response problem presents one or more symbolic math
input fields for input. Correctness of input is evaluated based on
the symbolic properties of the expression entered. The student enters
text, but sees a proper symbolic rendition of the entered formula, in
real time, next to the input box.
</p>
<p>This is a correct answer which may be entered below: </p>
<p><tt>cos(theta)*[[1,0],[0,1]] + i*sin(theta)*[[0,1],[1,0]]</tt></p>
<script>
from symmath import *
</script>
<text>Compute [mathjax] U = \exp\left( i \theta \left[ \begin{matrix} 0 &amp; 1 \\ 1 &amp; 0 \end{matrix} \right] \right) [/mathjax]
and give the resulting \(2 \times 2\) matrix. <br/>
Your input should be typed in as a list of lists, eg <tt>[[1,2],[3,4]]</tt>. <br/>
[mathjax]U=[/mathjax] <symbolicresponse cfn="symmath_check" answer="[[cos(theta),I*sin(theta)],[I*sin(theta),cos(theta)]]" options="matrix,imaginaryi" id="filenamedogi0VpEBOWedxsymmathresponse_1" state="unsubmitted">
<textline size="80" math="1" response_id="2" answer_id="1" id="filenamedogi0VpEBOWedxsymmathresponse_2_1"/>
</symbolicresponse>
<br/>
</text>
</text>
</problem>
#
# unittests for xmodule (and capa)
#
# Note: run this using a like like this:
#
# django-admin.py test --settings=lms.envs.test_ike --pythonpath=. common/lib/xmodule
import unittest
import os
import numpy
import xmodule
import capa.calc as calc
import capa.capa_problem as lcp
from xmodule import graders, x_module
from xmodule.graders import Score, aggregate_scores
from xmodule.progress import Progress
from nose.plugins.skip import SkipTest
class I4xSystem(object):
'''
This is an abstraction such that x_modules can function independent
of the courseware (e.g. import into other types of courseware, LMS,
or if we want to have a sandbox server for user-contributed content)
'''
def __init__(self):
self.ajax_url = '/'
self.track_function = lambda x: None
self.filestore = None
self.render_function = lambda x: {} # Probably incorrect
self.module_from_xml = lambda x: None # May need a real impl...
self.exception404 = Exception
self.DEBUG = True
def __repr__(self):
return repr(self.__dict__)
def __str__(self):
return str(self.__dict__)
i4xs = I4xSystem()
class ModelsTest(unittest.TestCase):
def setUp(self):
pass
def test_get_module_class(self):
vc = xmodule.get_module_class('video')
vc_str = "<class 'xmodule.video_module.Module'>"
self.assertEqual(str(vc), vc_str)
video_id = xmodule.get_default_ids()['video']
self.assertEqual(video_id, 'youtube')
def test_calc(self):
variables={'R1':2.0, 'R3':4.0}
functions={'sin':numpy.sin, 'cos':numpy.cos}
self.assertTrue(abs(calc.evaluator(variables, functions, "10000||sin(7+5)+0.5356"))<0.01)
self.assertEqual(calc.evaluator({'R1': 2.0, 'R3':4.0}, {}, "13"), 13)
self.assertEqual(calc.evaluator(variables, functions, "13"), 13)
self.assertEqual(calc.evaluator({'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5"), 5)
self.assertEqual(calc.evaluator({},{}, "-1"), -1)
self.assertEqual(calc.evaluator({},{}, "-0.33"), -.33)
self.assertEqual(calc.evaluator({},{}, "-.33"), -.33)
self.assertEqual(calc.evaluator(variables, functions, "R1*R3"), 8.0)
self.assertTrue(abs(calc.evaluator(variables, functions, "sin(e)-0.41"))<0.01)
self.assertTrue(abs(calc.evaluator(variables, functions, "k*T/q-0.025"))<0.001)
self.assertTrue(abs(calc.evaluator(variables, functions, "e^(j*pi)")+1)<0.00001)
self.assertTrue(abs(calc.evaluator(variables, functions, "j||1")-0.5-0.5j)<0.00001)
variables['t'] = 1.0
self.assertTrue(abs(calc.evaluator(variables, functions, "t")-1.0)<0.00001)
self.assertTrue(abs(calc.evaluator(variables, functions, "T")-1.0)<0.00001)
self.assertTrue(abs(calc.evaluator(variables, functions, "t", cs=True)-1.0)<0.00001)
self.assertTrue(abs(calc.evaluator(variables, functions, "T", cs=True)-298)<0.2)
exception_happened = False
try:
calc.evaluator({},{}, "5+7 QWSEKO")
except:
exception_happened = True
self.assertTrue(exception_happened)
try:
calc.evaluator({'r1':5},{}, "r1+r2")
except calc.UndefinedVariable:
pass
self.assertEqual(calc.evaluator(variables, functions, "r1*r3"), 8.0)
exception_happened = False
try:
calc.evaluator(variables, functions, "r1*r3", cs=True)
except:
exception_happened = True
self.assertTrue(exception_happened)
#-----------------------------------------------------------------------------
# tests of capa_problem inputtypes
class MultiChoiceTest(unittest.TestCase):
def test_MC_grade(self):
multichoice_file = os.path.dirname(__file__)+"/test_files/multichoice.xml"
test_lcp = lcp.LoncapaProblem(open(multichoice_file), '1', system=i4xs)
correct_answers = {'1_2_1':'choice_foil3'}
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
false_answers = {'1_2_1':'choice_foil2'}
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
def test_MC_bare_grades(self):
multichoice_file = os.path.dirname(__file__)+"/test_files/multi_bare.xml"
test_lcp = lcp.LoncapaProblem(open(multichoice_file), '1', system=i4xs)
correct_answers = {'1_2_1':'choice_2'}
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
false_answers = {'1_2_1':'choice_1'}
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
def test_TF_grade(self):
truefalse_file = os.path.dirname(__file__)+"/test_files/truefalse.xml"
test_lcp = lcp.LoncapaProblem(open(truefalse_file), '1', system=i4xs)
correct_answers = {'1_2_1':['choice_foil2', 'choice_foil1']}
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
false_answers = {'1_2_1':['choice_foil1']}
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
false_answers = {'1_2_1':['choice_foil1', 'choice_foil3']}
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
false_answers = {'1_2_1':['choice_foil3']}
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
false_answers = {'1_2_1':['choice_foil1', 'choice_foil2', 'choice_foil3']}
self.assertEquals(test_lcp.grade_answers(false_answers).get_correctness('1_2_1'), 'incorrect')
class ImageResponseTest(unittest.TestCase):
def test_ir_grade(self):
imageresponse_file = os.path.dirname(__file__)+"/test_files/imageresponse.xml"
test_lcp = lcp.LoncapaProblem(open(imageresponse_file), '1', system=i4xs)
correct_answers = {'1_2_1':'(490,11)-(556,98)',
'1_2_2':'(242,202)-(296,276)'}
test_answers = {'1_2_1':'[500,20]',
'1_2_2':'[250,300]',
}
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct')
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_2'), 'incorrect')
class SymbolicResponseTest(unittest.TestCase):
def test_sr_grade(self):
raise SkipTest() # This test fails due to dependencies on a local copy of snuggletex-webapp. Until we have figured that out, we'll just skip this test
symbolicresponse_file = os.path.dirname(__file__)+"/test_files/symbolicresponse.xml"
test_lcp = lcp.LoncapaProblem(open(symbolicresponse_file), '1', system=i4xs)
correct_answers = {'1_2_1':'cos(theta)*[[1,0],[0,1]] + i*sin(theta)*[[0,1],[1,0]]',
'1_2_1_dynamath': '''
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mstyle displaystyle="true">
<mrow>
<mi>cos</mi>
<mrow>
<mo>(</mo>
<mi>&#x3B8;</mi>
<mo>)</mo>
</mrow>
</mrow>
<mo>&#x22C5;</mo>
<mrow>
<mo>[</mo>
<mtable>
<mtr>
<mtd>
<mn>1</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
</mtr>
<mtr>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>1</mn>
</mtd>
</mtr>
</mtable>
<mo>]</mo>
</mrow>
<mo>+</mo>
<mi>i</mi>
<mo>&#x22C5;</mo>
<mrow>
<mi>sin</mi>
<mrow>
<mo>(</mo>
<mi>&#x3B8;</mi>
<mo>)</mo>
</mrow>
</mrow>
<mo>&#x22C5;</mo>
<mrow>
<mo>[</mo>
<mtable>
<mtr>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>1</mn>
</mtd>
</mtr>
<mtr>
<mtd>
<mn>1</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
</mtr>
</mtable>
<mo>]</mo>
</mrow>
</mstyle>
</math>
''',
}
wrong_answers = {'1_2_1':'2',
'1_2_1_dynamath':'''
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mstyle displaystyle="true">
<mn>2</mn>
</mstyle>
</math>''',
}
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
self.assertEquals(test_lcp.grade_answers(wrong_answers).get_correctness('1_2_1'), 'incorrect')
class OptionResponseTest(unittest.TestCase):
'''
Run this with
python manage.py test courseware.OptionResponseTest
'''
def test_or_grade(self):
optionresponse_file = os.path.dirname(__file__)+"/test_files/optionresponse.xml"
test_lcp = lcp.LoncapaProblem(open(optionresponse_file), '1', system=i4xs)
correct_answers = {'1_2_1':'True',
'1_2_2':'False'}
test_answers = {'1_2_1':'True',
'1_2_2':'True',
}
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_1'), 'correct')
self.assertEquals(test_lcp.grade_answers(test_answers).get_correctness('1_2_2'), 'incorrect')
class FormulaResponseWithHintTest(unittest.TestCase):
'''
Test Formula response problem with a hint
This problem also uses calc.
'''
def test_or_grade(self):
problem_file = os.path.dirname(__file__)+"/test_files/formularesponse_with_hint.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file), '1', system=i4xs)
correct_answers = {'1_2_1':'2.5*x-5.0'}
test_answers = {'1_2_1':'0.4*x-5.0'}
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
cmap = test_lcp.grade_answers(test_answers)
self.assertEquals(cmap.get_correctness('1_2_1'), 'incorrect')
self.assertTrue('You have inverted' in cmap.get_hint('1_2_1'))
class StringResponseWithHintTest(unittest.TestCase):
'''
Test String response problem with a hint
'''
def test_or_grade(self):
problem_file = os.path.dirname(__file__)+"/test_files/stringresponse_with_hint.xml"
test_lcp = lcp.LoncapaProblem(open(problem_file), '1', system=i4xs)
correct_answers = {'1_2_1':'Michigan'}
test_answers = {'1_2_1':'Minnesota'}
self.assertEquals(test_lcp.grade_answers(correct_answers).get_correctness('1_2_1'), 'correct')
cmap = test_lcp.grade_answers(test_answers)
self.assertEquals(cmap.get_correctness('1_2_1'), 'incorrect')
self.assertTrue('St. Paul' in cmap.get_hint('1_2_1'))
#-----------------------------------------------------------------------------
# Grading tests
class GradesheetTest(unittest.TestCase):
def test_weighted_grading(self):
scores = []
Score.__sub__=lambda me, other: (me.earned - other.earned) + (me.possible - other.possible)
all, graded = aggregate_scores(scores)
self.assertEqual(all, Score(earned=0, possible=0, graded=False, section="summary"))
self.assertEqual(graded, Score(earned=0, possible=0, graded=True, section="summary"))
scores.append(Score(earned=0, possible=5, graded=False, section="summary"))
all, graded = aggregate_scores(scores)
self.assertEqual(all, Score(earned=0, possible=5, graded=False, section="summary"))
self.assertEqual(graded, Score(earned=0, possible=0, graded=True, section="summary"))
scores.append(Score(earned=3, possible=5, graded=True, section="summary"))
all, graded = aggregate_scores(scores)
self.assertAlmostEqual(all, Score(earned=3, possible=10, graded=False, section="summary"))
self.assertAlmostEqual(graded, Score(earned=3, possible=5, graded=True, section="summary"))
scores.append(Score(earned=2, possible=5, graded=True, section="summary"))
all, graded = aggregate_scores(scores)
self.assertAlmostEqual(all, Score(earned=5, possible=15, graded=False, section="summary"))
self.assertAlmostEqual(graded, Score(earned=5, possible=10, graded=True, section="summary"))
class GraderTest(unittest.TestCase):
empty_gradesheet = {
}
incomplete_gradesheet = {
'Homework': [],
'Lab': [],
'Midterm' : [],
}
test_gradesheet = {
'Homework': [Score(earned=2, possible=20.0, graded=True, section='hw1'),
Score(earned=16, possible=16.0, graded=True, section='hw2')],
#The dropped scores should be from the assignments that don't exist yet
'Lab': [Score(earned=1, possible=2.0, graded=True, section='lab1'), #Dropped
Score(earned=1, possible=1.0, graded=True, section='lab2'),
Score(earned=1, possible=1.0, graded=True, section='lab3'),
Score(earned=5, possible=25.0, graded=True, section='lab4'), #Dropped
Score(earned=3, possible=4.0, graded=True, section='lab5'), #Dropped
Score(earned=6, possible=7.0, graded=True, section='lab6'),
Score(earned=5, possible=6.0, graded=True, section='lab7')],
'Midterm' : [Score(earned=50.5, possible=100, graded=True, section="Midterm Exam"),],
}
def test_SingleSectionGrader(self):
midtermGrader = graders.SingleSectionGrader("Midterm", "Midterm Exam")
lab4Grader = graders.SingleSectionGrader("Lab", "lab4")
badLabGrader = graders.SingleSectionGrader("Lab", "lab42")
for graded in [midtermGrader.grade(self.empty_gradesheet),
midtermGrader.grade(self.incomplete_gradesheet),
badLabGrader.grade(self.test_gradesheet)]:
self.assertEqual( len(graded['section_breakdown']), 1 )
self.assertEqual( graded['percent'], 0.0 )
graded = midtermGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.505 )
self.assertEqual( len(graded['section_breakdown']), 1 )
graded = lab4Grader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.2 )
self.assertEqual( len(graded['section_breakdown']), 1 )
def test_AssignmentFormatGrader(self):
homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2)
noDropGrader = graders.AssignmentFormatGrader("Homework", 12, 0)
#Even though the minimum number is 3, this should grade correctly when 7 assignments are found
overflowGrader = graders.AssignmentFormatGrader("Lab", 3, 2)
labGrader = graders.AssignmentFormatGrader("Lab", 7, 3)
#Test the grading of an empty gradesheet
for graded in [ homeworkGrader.grade(self.empty_gradesheet),
noDropGrader.grade(self.empty_gradesheet),
homeworkGrader.grade(self.incomplete_gradesheet),
noDropGrader.grade(self.incomplete_gradesheet) ]:
self.assertAlmostEqual( graded['percent'], 0.0 )
#Make sure the breakdown includes 12 sections, plus one summary
self.assertEqual( len(graded['section_breakdown']), 12 + 1 )
graded = homeworkGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.11 ) # 100% + 10% / 10 assignments
self.assertEqual( len(graded['section_breakdown']), 12 + 1 )
graded = noDropGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.0916666666666666 ) # 100% + 10% / 12 assignments
self.assertEqual( len(graded['section_breakdown']), 12 + 1 )
graded = overflowGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.8880952380952382 ) # 100% + 10% / 5 assignments
self.assertEqual( len(graded['section_breakdown']), 7 + 1 )
graded = labGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.9226190476190477 )
self.assertEqual( len(graded['section_breakdown']), 7 + 1 )
def test_WeightedSubsectionsGrader(self):
#First, a few sub graders
homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2)
labGrader = graders.AssignmentFormatGrader("Lab", 7, 3)
midtermGrader = graders.SingleSectionGrader("Midterm", "Midterm Exam")
weightedGrader = graders.WeightedSubsectionsGrader( [(homeworkGrader, homeworkGrader.category, 0.25), (labGrader, labGrader.category, 0.25),
(midtermGrader, midtermGrader.category, 0.5)] )
overOneWeightsGrader = graders.WeightedSubsectionsGrader( [(homeworkGrader, homeworkGrader.category, 0.5), (labGrader, labGrader.category, 0.5),
(midtermGrader, midtermGrader.category, 0.5)] )
#The midterm should have all weight on this one
zeroWeightsGrader = graders.WeightedSubsectionsGrader( [(homeworkGrader, homeworkGrader.category, 0.0), (labGrader, labGrader.category, 0.0),
(midtermGrader, midtermGrader.category, 0.5)] )
#This should always have a final percent of zero
allZeroWeightsGrader = graders.WeightedSubsectionsGrader( [(homeworkGrader, homeworkGrader.category, 0.0), (labGrader, labGrader.category, 0.0),
(midtermGrader, midtermGrader.category, 0.0)] )
emptyGrader = graders.WeightedSubsectionsGrader( [] )
graded = weightedGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.5106547619047619 )
self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 )
self.assertEqual( len(graded['grade_breakdown']), 3 )
graded = overOneWeightsGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.7688095238095238 )
self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 )
self.assertEqual( len(graded['grade_breakdown']), 3 )
graded = zeroWeightsGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.2525 )
self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 )
self.assertEqual( len(graded['grade_breakdown']), 3 )
graded = allZeroWeightsGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.0 )
self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 )
self.assertEqual( len(graded['grade_breakdown']), 3 )
for graded in [ weightedGrader.grade(self.empty_gradesheet),
weightedGrader.grade(self.incomplete_gradesheet),
zeroWeightsGrader.grade(self.empty_gradesheet),
allZeroWeightsGrader.grade(self.empty_gradesheet)]:
self.assertAlmostEqual( graded['percent'], 0.0 )
self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 )
self.assertEqual( len(graded['grade_breakdown']), 3 )
graded = emptyGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.0 )
self.assertEqual( len(graded['section_breakdown']), 0 )
self.assertEqual( len(graded['grade_breakdown']), 0 )
def test_graderFromConf(self):
#Confs always produce a graders.WeightedSubsectionsGrader, so we test this by repeating the test
#in test_graders.WeightedSubsectionsGrader, but generate the graders with confs.
weightedGrader = graders.grader_from_conf([
{
'type' : "Homework",
'min_count' : 12,
'drop_count' : 2,
'short_label' : "HW",
'weight' : 0.25,
},
{
'type' : "Lab",
'min_count' : 7,
'drop_count' : 3,
'category' : "Labs",
'weight' : 0.25
},
{
'type' : "Midterm",
'name' : "Midterm Exam",
'short_label' : "Midterm",
'weight' : 0.5,
},
])
emptyGrader = graders.grader_from_conf([])
graded = weightedGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.5106547619047619 )
self.assertEqual( len(graded['section_breakdown']), (12 + 1) + (7+1) + 1 )
self.assertEqual( len(graded['grade_breakdown']), 3 )
graded = emptyGrader.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.0 )
self.assertEqual( len(graded['section_breakdown']), 0 )
self.assertEqual( len(graded['grade_breakdown']), 0 )
#Test that graders can also be used instead of lists of dictionaries
homeworkGrader = graders.AssignmentFormatGrader("Homework", 12, 2)
homeworkGrader2 = graders.grader_from_conf(homeworkGrader)
graded = homeworkGrader2.grade(self.test_gradesheet)
self.assertAlmostEqual( graded['percent'], 0.11 )
self.assertEqual( len(graded['section_breakdown']), 12 + 1 )
#TODO: How do we test failure cases? The parser only logs an error when it can't parse something. Maybe it should throw exceptions?
# --------------------------------------------------------------------------
# Module progress tests
class ProgressTest(unittest.TestCase):
''' Test that basic Progress objects work. A Progress represents a
fraction between 0 and 1.
'''
not_started = Progress(0, 17)
part_done = Progress(2, 6)
half_done = Progress(3, 6)
also_half_done = Progress(1, 2)
done = Progress(7, 7)
def test_create_object(self):
# These should work:
p = Progress(0, 2)
p = Progress(1, 2)
p = Progress(2, 2)
p = Progress(2.5, 5.0)
p = Progress(3.7, 12.3333)
# These shouldn't
self.assertRaises(ValueError, Progress, 0, 0)
self.assertRaises(ValueError, Progress, 2, 0)
self.assertRaises(ValueError, Progress, 1, -2)
self.assertRaises(ValueError, Progress, 3, 2)
self.assertRaises(ValueError, Progress, -2, 5)
self.assertRaises(TypeError, Progress, 0, "all")
# check complex numbers just for the heck of it :)
self.assertRaises(TypeError, Progress, 2j, 3)
def test_frac(self):
p = Progress(1, 2)
(a, b) = p.frac()
self.assertEqual(a, 1)
self.assertEqual(b, 2)
def test_percent(self):
self.assertEqual(self.not_started.percent(), 0)
self.assertAlmostEqual(self.part_done.percent(), 33.33333333333333)
self.assertEqual(self.half_done.percent(), 50)
self.assertEqual(self.done.percent(), 100)
self.assertEqual(self.half_done.percent(), self.also_half_done.percent())
def test_started(self):
self.assertFalse(self.not_started.started())
self.assertTrue(self.part_done.started())
self.assertTrue(self.half_done.started())
self.assertTrue(self.done.started())
def test_inprogress(self):
# only true if working on it
self.assertFalse(self.done.inprogress())
self.assertFalse(self.not_started.inprogress())
self.assertTrue(self.part_done.inprogress())
self.assertTrue(self.half_done.inprogress())
def test_done(self):
self.assertTrue(self.done.done())
self.assertFalse(self.half_done.done())
self.assertFalse(self.not_started.done())
def test_str(self):
self.assertEqual(str(self.not_started), "0/17")
self.assertEqual(str(self.part_done), "2/6")
self.assertEqual(str(self.done), "7/7")
def test_ternary_str(self):
self.assertEqual(self.not_started.ternary_str(), "none")
self.assertEqual(self.half_done.ternary_str(), "in_progress")
self.assertEqual(self.done.ternary_str(), "done")
def test_to_js_status(self):
'''Test the Progress.to_js_status_str() method'''
self.assertEqual(Progress.to_js_status_str(self.not_started), "none")
self.assertEqual(Progress.to_js_status_str(self.half_done), "in_progress")
self.assertEqual(Progress.to_js_status_str(self.done), "done")
self.assertEqual(Progress.to_js_status_str(None), "NA")
def test_to_js_detail_str(self):
'''Test the Progress.to_js_detail_str() method'''
f = Progress.to_js_detail_str
for p in (self.not_started, self.half_done, self.done):
self.assertEqual(f(p), str(p))
# But None should be encoded as NA
self.assertEqual(f(None), "NA")
def test_add(self):
'''Test the Progress.add_counts() method'''
p = Progress(0, 2)
p2 = Progress(1, 3)
p3 = Progress(2, 5)
pNone = None
add = lambda a, b: Progress.add_counts(a, b).frac()
self.assertEqual(add(p, p), (0, 4))
self.assertEqual(add(p, p2), (1, 5))
self.assertEqual(add(p2, p3), (3, 8))
self.assertEqual(add(p2, pNone), p2.frac())
self.assertEqual(add(pNone, p2), p2.frac())
def test_equality(self):
'''Test that comparing Progress objects for equality
works correctly.'''
p = Progress(1, 2)
p2 = Progress(2, 4)
p3 = Progress(1, 2)
self.assertTrue(p == p3)
self.assertFalse(p == p2)
# Check != while we're at it
self.assertTrue(p != p2)
self.assertFalse(p != p3)
class ModuleProgressTest(unittest.TestCase):
''' Test that get_progress() does the right thing for the different modules
'''
def test_xmodule_default(self):
'''Make sure default get_progress exists, returns None'''
xm = x_module.XModule(i4xs, "<dummy />", "dummy")
p = xm.get_progress()
self.assertEqual(p, None)
import json
from x_module import XModule, XModuleDescriptor
from xmodule.progress import Progress
from lxml import etree
class ModuleDescriptor(XModuleDescriptor):
pass
class Module(XModule):
''' Layout module for laying out submodules vertically.'''
id_attribute = 'id'
def get_state(self):
return json.dumps({ })
@classmethod
def get_xml_tags(c):
return ["vertical", "problemset"]
def get_html(self):
return self.system.render_template('vert_module.html', {
'items': self.contents
})
def get_progress(self):
# TODO: Cache progress or children array?
children = self.get_children()
progresses = [child.get_progress() for child in children]
progress = reduce(Progress.add_counts, progresses)
return progress
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
xmltree=etree.fromstring(xml)
self.contents=[(e.get("name"),self.render_function(e)) \
for e in xmltree]
import json
import logging
from lxml import etree
from x_module import XModule, XModuleDescriptor
from progress import Progress
log = logging.getLogger("mitx.courseware.modules")
class ModuleDescriptor(XModuleDescriptor):
pass
class Module(XModule):
id_attribute = 'youtube'
video_time = 0
def handle_ajax(self, dispatch, get):
'''
Handle ajax calls to this video.
TODO (vshnayder): This is not being called right now, so the position
is not being saved.
'''
log.debug(u"GET {0}".format(get))
log.debug(u"DISPATCH {0}".format(dispatch))
if dispatch == 'goto_position':
self.position = int(float(get['position']))
log.info(u"NEW POSITION {0}".format(self.position))
return json.dumps({'success':True})
raise Http404()
def get_progress(self):
''' TODO (vshnayder): Get and save duration of youtube video, then return
fraction watched.
(Be careful to notice when video link changes and update)
For now, we have no way of knowing if the video has even been watched, so
just return None.
'''
return None
def get_state(self):
log.debug(u"STATE POSITION {0}".format(self.position))
return json.dumps({ 'position': self.position })
@classmethod
def get_xml_tags(c):
'''Tags in the courseware file guaranteed to correspond to the module'''
return ["video"]
def video_list(self):
return self.youtube
def get_html(self):
return self.system.render_template('video.html', {
'streams': self.video_list(),
'id': self.item_id,
'position': self.position,
'name': self.name,
'annotations': self.annotations,
})
def __init__(self, system, xml, item_id, state=None):
XModule.__init__(self, system, xml, item_id, state)
xmltree = etree.fromstring(xml)
self.youtube = xmltree.get('youtube')
self.name = xmltree.get('name')
self.position = 0
if state is not None:
state = json.loads(state)
if 'position' in state:
self.position = int(float(state['position']))
self.annotations=[(e.get("name"),self.render_function(e)) \
for e in xmltree]
class VideoSegmentDescriptor(XModuleDescriptor):
pass
......@@ -57,6 +57,13 @@ class XModule(object):
else:
raise "We should iterate through children and find a default name"
def get_children(self):
'''
Return module instances for all the children of this module.
'''
children = [self.module_from_xml(e) for e in self.__xmltree]
return children
def rendered_children(self):
'''
Render all children.
......@@ -90,6 +97,7 @@ class XModule(object):
self.tracker = system.track_function
self.filestore = system.filestore
self.render_function = system.render_function
self.module_from_xml = system.module_from_xml
self.DEBUG = system.DEBUG
self.system = system
......@@ -118,6 +126,15 @@ class XModule(object):
'''
return "Unimplemented"
def get_progress(self):
''' Return a progress.Progress object that represents how far the student has gone
in this module. Must be implemented to get correct progress tracking behavior in
nesting modules like sequence and vertical.
If this module has no notion of progress, return None.
'''
return None
def handle_ajax(self, dispatch, get):
''' dispatch is last part of the URL.
get is a dictionary-like object '''
......@@ -187,3 +204,12 @@ class XModuleDescriptor(Plugin):
# Full ==> what we edit
# '''
# raise NotImplementedError
class DescriptorSystem(object):
def __init__(self, load_item):
"""
load_item: Takes a Location and returns and XModuleDescriptor
"""
self.load_item = load_item
......@@ -15,6 +15,7 @@ _FIELDS = ['number', # 6.002x
'instructors', # ['Anant Agarwal']
'institution', # "MIT"
'grader', # a courseware.graders.CourseGrader object
'location',
#'start', # These should be datetime fields
#'end'
......@@ -27,6 +28,7 @@ class Course(namedtuple('Course', _FIELDS)):
"""Course objects encapsulate general information about a given run of a
course. This includes things like name, grading policy, etc.
"""
@property
def id(self):
return "{0.institution},{0.number},{0.run_id}".format(self).replace(" ", "_")
......
......@@ -115,7 +115,7 @@ def make_module_from_xml_fn(user, request, student_module_cache, course, positio
return module_from_xml
def toc_for_course(user, request, course_location, active_chapter, active_section):
def toc_for_course(user, request, course, active_chapter, active_section):
'''
Create a table of contents from the module store
......@@ -131,8 +131,8 @@ def toc_for_course(user, request, course_location, active_chapter, active_sectio
chapters with name 'hidden' are skipped.
'''
student_module_cache = StudentModuleCache(user, modulestore().get_item(course_location), depth=2)
(course, _, _, _) = get_module(user, request, course_location, student_module_cache)
# student_module_cache = StudentModuleCache(user, modulestore().get_item(course_location), depth=2)
# (course, _, _, _) = get_module(user, request, course_location, student_module_cache)
chapters = list()
for chapter in course.get_display_items():
......
......@@ -65,6 +65,14 @@ def courses(request):
'csrf' : csrf_token}
return render_to_response("courses.html", context)
@ensure_csrf_cookie
def courses(request):
csrf_token = csrf(request)['csrf_token']
# TODO: Clean up how 'error' is done.
context = {'courses' : settings.COURSES,
'csrf' : csrf_token}
return render_to_response("courses.html", context)
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def gradebook(request):
if 'course_admin' not in user_groups(request.user):
......@@ -75,8 +83,8 @@ def gradebook(request):
student_objects = User.objects.all()[:100]
student_info = []
coursename = multicourse_settings.get_coursename_from_request(request)
course_location = multicourse_settings.get_course_location(coursename)
coursename = course.name
course_location = course.path
for student in student_objects:
student_module_cache = StudentModuleCache(student, modulestore().get_item(course_location))
......@@ -133,8 +141,7 @@ def render_accordion(request, course, chapter, section):
Returns (initialization_javascript, content)'''
course_location = multicourse_settings.get_course_location(course)
toc = toc_for_course(request.user, request, course_location, chapter, section)
toc = toc_for_course(request.user, request, course, chapter, section)
active_chapter = 1
for i in range(len(toc)):
......@@ -221,7 +228,7 @@ def index(request, course=None, chapter=None, section=None,
look_for_module = chapter is not None and section is not None
if look_for_module:
course_location = multicourse_settings.get_course_location(course)
course_location = course.path #multicourse_settings.get_course_location(course)
section = get_section(course_location, chapter, section)
student_module_cache = StudentModuleCache(request.user, section)
module, _, _, _ = get_module(request.user, request, section.location, student_module_cache)
......
......@@ -20,80 +20,80 @@
# If COURSE_SETTINGS does not exist, then fallback to 6.002_Spring_2012 default,
# for now.
from django.conf import settings
# from django.conf import settings
#-----------------------------------------------------------------------------
# load course settings
if hasattr(settings,'COURSE_SETTINGS'): # in the future, this could be replaced by reading an XML file
COURSE_SETTINGS = settings.COURSE_SETTINGS
elif hasattr(settings,'COURSE_NAME'): # backward compatibility
COURSE_SETTINGS = {settings.COURSE_NAME: {'number': settings.COURSE_NUMBER,
'title': settings.COURSE_TITLE,
'location': settings.COURSE_LOCATION,
},
}
else: # default to 6.002_Spring_2012
COURSE_SETTINGS = {'6.002_Spring_2012': {'number': '6.002x',
'title': 'Circuits and Electronics',
'location': 'i4x://edx/6002xs12/course/6.002 Spring 2012',
},
}
#-----------------------------------------------------------------------------
# wrapper functions around course settings
def get_coursename_from_request(request):
if 'coursename' in request.session:
coursename = request.session['coursename']
settings.COURSE_TITLE = get_course_title(coursename) # overwrite settings.COURSE_TITLE based on this
else: coursename = None
return coursename
def get_course_settings(coursename):
if not coursename:
if hasattr(settings, 'COURSE_DEFAULT'):
coursename = settings.COURSE_DEFAULT
else:
coursename = '6.002_Spring_2012'
if coursename in COURSE_SETTINGS:
return COURSE_SETTINGS[coursename]
coursename = coursename.replace(' ', '_')
if coursename in COURSE_SETTINGS:
return COURSE_SETTINGS[coursename]
return None
def is_valid_course(coursename):
return get_course_settings(coursename) != None
def get_course_property(coursename, property):
cs = get_course_settings(coursename)
# raise exception instead?
if not cs:
return ''
if property in cs:
return cs[property]
# default
return ''
def get_course_xmlpath(coursename):
return get_course_property(coursename, 'xmlpath')
def get_course_title(coursename):
return get_course_property(coursename, 'title')
def get_course_number(coursename):
return get_course_property(coursename, 'number')
def get_course_location(coursename):
return get_course_property(coursename, 'location')
# if hasattr(settings,'COURSE_SETTINGS'): # in the future, this could be replaced by reading an XML file
# COURSE_SETTINGS = settings.COURSE_SETTINGS
#
# elif hasattr(settings,'COURSE_NAME'): # backward compatibility
# COURSE_SETTINGS = {settings.COURSE_NAME: {'number': settings.COURSE_NUMBER,
# 'title': settings.COURSE_TITLE,
# 'location': settings.COURSE_LOCATION,
# },
# }
# else: # default to 6.002_Spring_2012
# COURSE_SETTINGS = {'6.002_Spring_2012': {'number': '6.002x',
# 'title': 'Circuits and Electronics',
# 'location': 'i4x://edx/6002xs12/course/6.002 Spring 2012',
# },
# }
#
# #-----------------------------------------------------------------------------
# # wrapper functions around course settings
#
# def get_coursename_from_request(request):
# if 'coursename' in request.session:
# coursename = request.session['coursename']
# settings.COURSE_TITLE = get_course_title(coursename) # overwrite settings.COURSE_TITLE based on this
# else: coursename = None
# return coursename
#
# def get_course_settings(coursename):
# if not coursename:
# if hasattr(settings, 'COURSE_DEFAULT'):
# coursename = settings.COURSE_DEFAULT
# else:
# coursename = '6.002_Spring_2012'
# if coursename in COURSE_SETTINGS:
# return COURSE_SETTINGS[coursename]
# coursename = coursename.replace(' ', '_')
# if coursename in COURSE_SETTINGS:
# return COURSE_SETTINGS[coursename]
# return None
#
#
# def is_valid_course(coursename):
# return get_course_settings(coursename) != None
#
#
# def get_course_property(coursename, property):
# cs = get_course_settings(coursename)
#
# # raise exception instead?
# if not cs:
# return ''
#
# if property in cs:
# return cs[property]
#
# # default
# return ''
#
#
# def get_course_xmlpath(coursename):
# return get_course_property(coursename, 'xmlpath')
#
#
# def get_course_title(coursename):
# return get_course_property(coursename, 'title')
#
#
# def get_course_number(coursename):
# return get_course_property(coursename, 'number')
#
#
# def get_course_location(coursename):
# return get_course_property(coursename, 'location')
......@@ -18,7 +18,7 @@ describe 'VideoProgressSlider', ->
stop: @slider.onStop
it 'build the seek handle', ->
expect(@slider.handle).toBe '.ui-slider-handle'
expect(@slider.handle).toBe '.slider .ui-slider-handle'
expect($.fn.qtip).toHaveBeenCalledWith
content: "0:00"
position:
......
......@@ -11,7 +11,7 @@ class @VideoProgressSlider extends Subview
@buildHandle()
buildHandle: ->
@handle = @$('.ui-slider-handle')
@handle = @$('.slider .ui-slider-handle')
@handle.qtip
content: "#{Time.format(@slider.slider('value'))}"
position:
......
/*!
* 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);
.jobs {
margin: 60px auto 120px;
h1 + hr {
margin-bottom: 80px;
}
.message {
@include clearfix;
margin-bottom: 60px;
padding-bottom: 60px;
position: relative;
hr {
bottom: 0px;
margin: 0px;
position: absolute;
width: 100%;
}
.photo {
background: rgb(255,255,255);
border: 1px solid rgb(210,210,210);
padding: 1px;
width: flex-grid(4);
img {
background: rgb(245,245,245);
display: block;
height: 200px;
width: 100%;
}
}
&.left {
.photo {
float: left;
margin-right: flex-gutter();
}
}
&.right {
h2, p {
text-align: right;
}
.photo {
float: right;
margin-left: flex-gutter();
}
}
&:last-child {
margin-bottom: 0px;
}
}
.jobs-wrapper {
@include clearfix;
float: left;
width: flex-grid(12);
> h2 {
border-bottom: 1px solid rgb(220,220,220);
margin-bottom: 60px;
padding-bottom: 20px;
//text-align: center;
}
.jobs-sidebar {
@include box-sizing(border-box);
border-left: 1px solid rgb(220,220,220);
float: left;
padding-bottom: 20px;
padding-left: 20px;
width: flex-grid(3);
nav {
margin-bottom: 40px;
ol {
@include clearfix;
li {
float: left;
margin-right: flex-gutter();
width: flex-grid(12);
&:nth-child(4n) {
margin-right: 0px;
}
a {
display: block;
margin-left: -20px;
padding: 10px 0px 10px 20px;
position: relative;
text-transform: uppercase;
&::after {
@include background-image(linear-gradient(180deg, rgba(235,235,235, 0) 0%,
rgba(235,235,235, 1)));
bottom: 0px;
//content: "";
display: block;
height: 1px;
left: 0px;
position: absolute;
width: 100%;
}
&:hover {
background: rgb(245,245,245);
//@include background-image(linear-gradient(180deg, rgba(245,245,245, 0) 0%,
//rgba(245,245,245, 1) 80%,
//rgba(245,245,245, 1) 100%));
}
}
}
}
}
p + h2 {
margin-top: 40px;
}
}
.jobs-listing {
float: left;
margin-right: flex-gutter();
width: flex-grid(9);
.job {
border-bottom: 1px solid rgb(220,220,220);
padding: 40px 0px;
&:first-child {
padding-top: 0px;
}
&:last-child {
border: none;
padding-bottom: 0px;
}
.inner-wrapper {
}
h3 {
font-family: $sans-serif;
font-weight: bold;
margin-bottom: 15px;
}
}
}
}
}
......@@ -20,15 +20,15 @@
@import 'shared_modal';
// pages
@import "courseware/courseware", "courseware/sidebar", "courseware/video", "courseware/sequence-nav", "courseware/amplifier", "courseware/problems";
@import "textbook";
@import "info";
@import "profile";
@import "gradebook";
@import "wiki/basic-html", "wiki/sidebar", "wiki/create", "wiki/wiki", "wiki/table";
@import "help";
@import "discussion/askbot-original", "discussion/discussion","discussion/sidebar", "discussion/questions", "discussion/tags", "discussion/question-view" , "discussion/answers", "discussion/forms", "discussion/form-wmd-toolbar", "discussion/modals", "discussion/profile", "discussion/badges";
// @import "courseware/courseware", "courseware/sidebar", "courseware/video", "courseware/sequence-nav", "courseware/amplifier", "courseware/problems";
// @import "textbook";
// @import "info";
// @import "profile";
// @import "gradebook";
// @import "wiki/basic-html", "wiki/sidebar", "wiki/create", "wiki/wiki", "wiki/table";
// @import "help";
//
// @import "discussion/askbot-original", "discussion/discussion","discussion/sidebar", "discussion/questions", "discussion/tags", "discussion/question-view" , "discussion/answers", "discussion/forms", "discussion/form-wmd-toolbar", "discussion/modals", "discussion/profile", "discussion/badges";
// from prototype
@import 'index';
......
......@@ -12,11 +12,7 @@ div.course-wrapper {
@extend .table-wrapper;
ul, ol {
padding-left: lh();
li {
margin-bottom: lh(.5);
}
list-style: none;
}
section.course-content {
......@@ -36,6 +32,148 @@ div.course-wrapper {
}
}
ul {
li {
margin-bottom: lh(.5);
}
}
.problem-set {
position: relative;
@extend .clearfix;
h2 {
margin-top: 0;
margin-bottom: 15px;
width: flex-grid(2, 9);
padding-right: flex-gutter(9);
border-right: 1px dashed #ddd;
@include box-sizing(border-box);
display: table-cell;
vertical-align: top;
&.problem-header {
section.staff {
margin-top: 30px;
font-size: 80%;
}
}
@media screen and (max-width:1120px) {
display: block;
width: auto;
border-right: 0;
}
@media print {
display: block;
width: auto;
border-right: 0;
}
}
section.problem {
display: table-cell;
width: flex-grid(7, 9);
padding-left: flex-gutter(9);
@media screen and (max-width:1120px) {
display: block;
width: auto;
padding: 0;
}
@media print {
display: block;
width: auto;
padding: 0;
canvas, img {
page-break-inside: avoid;
}
}
span {
&.unanswered, &.ui-icon-bullet {
@include inline-block();
background: url('../images/unanswered-icon.png') center center no-repeat;
height: 14px;
position: relative;
top: 4px;
width: 14px;
}
&.correct, &.ui-icon-check {
@include inline-block();
background: url('../images/correct-icon.png') center center no-repeat;
height: 20px;
position: relative;
top: 6px;
width: 25px;
}
&.incorrect, &.ui-icon-close {
@include inline-block();
background: url('../images/incorrect-icon.png') center center no-repeat;
height: 20px;
width: 20px;
position: relative;
top: 6px;
}
}
}
div {
> span {
display: block;
margin-bottom: lh(.5);
&[answer] {
border-top: 1px solid #ededed;
border-bottom: 1px solid #ededed;
background: #f3f3f3;
margin: 0 (-(lh()));
padding: lh(.5) lh();
}
}
}
input[type="text"] {
display: inline-block;
width: 50%;
}
center {
display: block;
margin: lh() 0;
border: 1px solid #ccc;
padding: lh();
}
section.action {
margin-top: lh();
input[type="button"] {
padding: lh(.4) lh();
text-shadow: 0 -1px 0 #666;
}
}
}
section.problems-wrapper, div#seq_content {
@extend .problem-set;
}
section.problems-wrapper {
display: table;
width: 100%;
@media screen and (max-width:1120px) {
display: block;
width: auto;
}
}
div#seq_content {
h1 {
background: none;
......@@ -46,9 +184,6 @@ div.course-wrapper {
}
ol.vert-mod {
list-style: none;
padding-left: 0;
> li {
@extend .clearfix;
@extend .problem-set;
......@@ -86,6 +221,10 @@ div.course-wrapper {
height: 150px;
}
ul {
list-style: disc outside none;
padding-left: 1em;
}
nav.sequence-bottom {
ul {
......
<%inherit file="main.html" />
<%include file="guest_navigation.html" args="active_page='info'" />
<<<<<<< HEAD
<%namespace name='static' file='static_content.html'/>
<section class="container">
<section class="course-info">
......@@ -113,85 +112,6 @@
<section class="faq tab">
<h3>Frequently Asked Questions</h3>
=======
<section class="course-info">
<header class="course-profile">
<div class="intro-inner-wrapper">
<section class="intro">
<hgroup>
<h1>18th Century History <span class="course-number">(HC137)</span></h1>
<hr>
<h2><a href="#">HarvardX</a></h2>
</hgroup>
</section>
<section class="actions">
<a href="#" class="register">Register</a>
<section class="social-sharing">
</section>
</section>
</div>
</header>
<section class="container">
<section class="details">
<nav>
<a href="#" class="active">Overview</a>
<a href="#">FAQ</a>
<a href="#">Requirements</a>
<a href="#">Text-book</a>
<a href="#">Syllabus</a>
<a href="#">Reviews</a>
</nav>
<div class="inner-wrapper">
<section class="about">
<h2>About this course</h2>
<p>This course explores the history of the modern world since Chinggis Khan. It focuses on the connections between societies from the time of the Mongol conquests and the gradual, but accelerating ways in which connections became ties of inter-dependence. The relations between societies are what will concern us. The forces pulling the world together vary from religious to economic, political to intellectual. These forces bring the world together, but they also create new divisions. Nowadays, we call this "globalization." That term has tended to emphasize the drive to worldwide integration; the view of globalization taken in this course emphasizes disintegration as well as integration. We will tackle some very basic questions: How do we explain the staggering wealth of China in the centuries up to 1750, as well as China's recent ascent? Where did the United States come from, and where is it headed? What are the significance and legacies of empire in the world? How have world wars and revolutions shaped the international system over time? What exactly is globalization, and how does today's globalization compare with the past? How has the relationship between humans and nature changed over the centuries?</p>
<h2>Requirements</h2>
<p>In order to succeed in this course, you must have taken an AP level physics course in electricity and magnetism. You must know basic calculus and linear algebra and have some background in differential equations. Since more advanced mathematics will not show up until the second half of the course, the first half of the course will include an optional remedial differential equations component for those who need it.</p>
<p>The course web site was developed and tested primarily with Google Chrome. We support current versions of Mozilla Firefox as well. The video player is designed to work with Flash. While we provide a partial non-Flash fallback for the video, as well as partial support for Internet Explorer, other browsers, and tablets, portions of the functionality will be unavailable.</p>
</section>
<section class="course-staff">
<h2>Course staff</h3>
<article class="teacher">
<div class="teacher-image">
<img src="${static.url('images/anant.jpg')}" />
</div>
<h3>Anant Agarwal</h3>
<p>Director of MIT’s Computer Science and Artificial Intelligence Laboratory (CSAIL) and a professor of the Electrical Engineering and Computer Science department at MIT. His research focus is in parallel computer architectures and cloud software systems, and he is a founder of several successful startups, including Tilera, a company that produces scalable multicore processors. Prof. Agarwal won MIT’s Smullin and Jamieson prizes for teaching and co-authored the course textbook “Foundations of Analog and Digital Electronic Circuits.”</p>
</article>
<article class="teacher">
<div class="teacher-image">
<img src="${static.url('images/gerald.jpg')}" />
</div>
<h3>Gerald Sussman</h3>
<p>Professor of Electrical Engineering at MIT. He is a well known educator in the computer science community, perhaps best known as the author of Structure and Interpretation of Computer Programs, which is universally acknowledged as one of the top ten textbooks in computer science, and as the creator of Scheme, a popular teaching language. His research spans a range of topics, from artificial intelligence, to physics and chaotic systems, to supercomputer design.</p>
</article>
<article class="teacher">
<div class="teacher-image">
<img src="${static.url('images/piotr.jpg')}" />
</div>
<h3>Piotr Mitros</h3>
<p>Research Scientist at MIT. His research focus is in finding ways to apply techniques from control systems to optimizing the learning process. Dr. Mitros has worked as an analog designer at Texas Instruments, Talking Lights, and most recently, designed the analog front end for a novel medical imaging modality for Rhythmia Medical.</p>
</article>
</section>
<section class="syllabus">
<h2>Syllabus</h2>
</section>
<section class="textbook">
<h2>Textbook</h2>
<p>The course uses the textbook Foundations of Analog and Digital Electronic Circuits, by Anant Agarwal and Jeffrey H. Lang. Morgan Kaufmann Publishers, Elsevier, July 2005. While recommended, the book is not required: relevant sections will be provided electronically as part of the online course for personal use in connection with this course only. The copyright for the book is owned by Elsevier. The book can be purchased on <a href="http://www.amazon.com/exec/obidos/ASIN/1558607358/ref=nosim/mitopencourse-20">Amazon</a>.</p>
</section>
<section class="faq">
<h2>Frequently Asked Questions</h2>
>>>>>>> cleans up course info/profile page
<ul>
<li>What is the format of the class?
<p>The course will consist of 24 lectures, each lasting 50 minutes. There will be regular assignments consisting of map tests and short essays.</p>
......@@ -207,7 +127,6 @@
</li>
</ul>
</section>
<<<<<<< HEAD
<section class="more-info tab">
<section class="who-should-take">
......@@ -305,33 +224,5 @@
<p>Research Scientist at MIT. His research focus is in finding ways to apply techniques from control systems to optimizing the learning process. Dr. Mitros has worked as an analog designer at Texas Instruments, Talking Lights, and most recently, designed the analog front end for a novel medical imaging modality for Rhythmia Medical.</p></li>
</ul>
=======
</section>
</div>
<section class="course-sidebar">
<div class="media">
<div class="hero">
<img src="${static.url('images/history.png')}" />
</div>
</div>
<section class="dates">
<h3>Important Dates</h3>
<p>Class Starts: <span class="start-date">7/12/12</span></p>
<p>Final Exam: <span class="start-date">12/09/12</span></p>
<p>Total Length: <span class="course-length">15 weeks</span></p>
</section>
<section class="course-abstract">
<h3>Course Sumamry</h3>
<p>This course will examine the ways in which the world has grown more integrated yet more divided over the past 300 years.</p>
</section>
<section class="dates">
<p>Course Number <span class="start-date">HC137</span></p>
</section>
</section>
>>>>>>> cleans up course info/profile page
</section>
</section>
<%inherit file="main.html" />
<h1>Jobs</h1>
<section class="container jobs">
<h1>Want to change the future of education?</h1>
<hr class="horizontal-divider">
<section class="company-mission message left">
<div class="inner-wrapper">
<div class="photo">
<img src="">
</div>
<h2>Mission: Educate 1 billion people around the world</h2>
<p>“EdX represents a unique opportunity to improve education on our own campuses through online learning, while simultaneously creating a bold new educational path for millions of learners worldwide,” MIT President Susan Hockfield said.</p>
<p>Harvard President Drew Faust said, “edX gives Harvard and MIT an unprecedented opportunity to dramatically extend our collective reach by conducting groundbreaking research into effective education and by extending online access to quality higher education.”
</div>
<hr class="fade-right-hr-divider">
</section>
<section class="our-culture message right">
<div class="photo">
<img src="">
</div>
<h2>What it's like to work here</h2>
<p>“Harvard and MIT will use these new technologies and the research they will make possible to lead the direction of online learning in a way that benefits our students, our peers, and people across the nation and the globe,” Faust continued.</p>
<p>[fast-moving not-for-profit startup][institutional backing, funding, benefits, and stability][industry salaries]</p>
<hr class="fade-left-hr-divider">
</section>
<section class="benefits message left">
<div class="photo">
<img src="">
</div>
<h2>Mission: Educate 1 billion people around the world</h2>
<p>“EdX represents a unique opportunity to improve education on our own campuses through online learning, while simultaneously creating a bold new educational path for millions of learners worldwide,” MIT President Susan Hockfield said.</p>
<p>Harvard President Drew Faust said, “edX gives Harvard and MIT an unprecedented opportunity to dramatically extend our collective reach by conducting groundbreaking research into effective education and by extending online access to quality higher education.”
</section>
<section class="jobs-wrapper">
<h2>We are currently looking for</h2>
<section class="jobs-listing">
<article id="edx-fellow" class="job">
<div class="inner-wrapper">
<h3>edX Fellow</h3>
<p>The edX fellows program is intended as an alternative to the traditional academic track for candidates with a strong focus on teaching. Fellows act as contact points for departments at MIT and Harvard, and help facilitate putting together on-line courses. </p>
<p>If you're interested in this position, send an e-mail to <a href="">fellow-jobs@edxonline.org</a></p>
</div>
</article>
<article id="content-engineer" class="job">
<div class="inner-wrapper">
<h3>Content Engineer</h3>
<p>Content engineers help create the technology for specific courses. The tasks include:</p>
<ul>
<li>
<p>Development of course-specific user-facing elements, such as the circuit editor and simulator that Chris Terman and Jacob White developed for 6.002x</p>
</li>
<li>
<p>Simple integration of course materials into courses</p>
</li>
<li>
<p>Developing programs to grade rich design questions, such as the ones developed by Gerald Sussman to allow basic design problems in 6.002x.</p>
</li>
</ul>
<p>Knowledge of Python, XML, and/or JavaScript is desired. Strong interest and background in pedagogy and education is desired as well.</p>
<p>If you're interested in this position, send an e-mail to <a href="">content-engineer@edxonline.org</a></p>
</div>
</article>
<article id="technology-team" class="job">
<div class="inner-wrapper">
<h3>Technology Team</h3>
<p>[Looking for both back-end and front-end developers. Strong backgrounds in machine learning, education, user interaction design, big data, or social network analysis are desirable, but team members do wear many hats. Best candidate would be a masterful hacker who went and did startups after finishing their Ph.D. We should find a way to make some positions that parallel fellows, and can leverage MIT/Harvard prestige]</p>
<p>If you're interested in this position, send an e-mail to <a href="">content-engineer@edxonline.org</a></p>
</div>
</article>
</section>
<section class="jobs-sidebar">
<h2>Positions</h2>
<nav>
<ol>
<li>
<a href="#edx-fellow">edX Fellow</a>
</li>
<li>
<a href="#content-engineer">Content Engineer</a>
</li>
<li>
<a href="#technology-team">Technology Team</a>
</li>
</ol>
</nav>
<h2>How to Apply</h2>
<p>E-mail your resume, coverletter and any other materials to <a href="#">careers@edxonline.org</a></p>
<h2>Our Location</h2>
<p>11 Cambridge Center, Cambridge MA USA</p>
</section>
</section>
</section>
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