Merge branch 'danielli/pyfs' of https://github.com/edx/edx-platform into danielli/pyfs

This commit is contained in:
swdanielli
2014-08-25 15:40:06 -04:00
604 changed files with 121021 additions and 44470 deletions

View File

@@ -11,7 +11,7 @@
% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
<div class="status ${status.classname}" id="status_${id}">
% endif
<input type="text" name="input_${id}" aria-describedby="answer_${id}" id="input_${id}" value="${value|h}" style="display:none;"/>

View File

@@ -5,6 +5,8 @@ XASSET_SRCREF_PREFIX = 'xasset:'
XASSET_THUMBNAIL_TAIL_NAME = '.jpg'
STREAM_DATA_CHUNK_SIZE = 1024
import os
import logging
import StringIO
@@ -62,9 +64,6 @@ class StaticContent(object):
def get_id(self):
return self.location
def get_url_path(self):
return self.location.to_deprecated_string()
@property
def data(self):
return self._data
@@ -106,7 +105,9 @@ class StaticContent(object):
assert(isinstance(course_key, CourseKey))
placeholder_id = uuid.uuid4().hex
# create a dummy asset location with a fake but unique name. strip off the name, and return it
url_path = unicode(course_key.make_asset_key('asset', placeholder_id).for_branch(None))
url_path = StaticContent.serialize_asset_key_with_slash(
course_key.make_asset_key('asset', placeholder_id).for_branch(None)
)
return url_path.replace(placeholder_id, '')
@staticmethod
@@ -131,7 +132,7 @@ class StaticContent(object):
# Generate url of urlparse.path component
scheme, netloc, orig_path, params, query, fragment = urlparse(path)
loc = StaticContent.compute_location(course_id, orig_path)
loc_url = loc.to_deprecated_string()
loc_url = StaticContent.serialize_asset_key_with_slash(loc)
# parse the query params for "^/static/" and replace with the location url
orig_query = parse_qsl(query)
@@ -142,7 +143,7 @@ class StaticContent(object):
course_id,
query_value[len('/static/'):],
)
new_query_url = new_query.to_deprecated_string()
new_query_url = StaticContent.serialize_asset_key_with_slash(new_query)
new_query_list.append((query_name, new_query_url))
else:
new_query_list.append((query_name, query_value))
@@ -153,6 +154,17 @@ class StaticContent(object):
def stream_data(self):
yield self._data
@staticmethod
def serialize_asset_key_with_slash(asset_key):
"""
Legacy code expects the serialized asset key to start w/ a slash; so, do that in one place
:param asset_key:
"""
url = unicode(asset_key)
if not url.startswith('/'):
url = '/' + url # TODO - re-address this once LMS-11198 is tackled.
return url
class StaticContentStream(StaticContent):
def __init__(self, loc, name, content_type, stream, last_modified_at=None, thumbnail_location=None, import_path=None,
@@ -164,11 +176,26 @@ class StaticContentStream(StaticContent):
def stream_data(self):
while True:
chunk = self._stream.read(1024)
chunk = self._stream.read(STREAM_DATA_CHUNK_SIZE)
if len(chunk) == 0:
break
yield chunk
def stream_data_in_range(self, first_byte, last_byte):
"""
Stream the data between first_byte and last_byte (included)
"""
self._stream.seek(first_byte)
position = first_byte
while True:
if last_byte < position + STREAM_DATA_CHUNK_SIZE - 1:
chunk = self._stream.read(last_byte - position + 1)
yield chunk
break
chunk = self._stream.read(STREAM_DATA_CHUNK_SIZE)
position += STREAM_DATA_CHUNK_SIZE
yield chunk
def close(self):
self._stream.close()

View File

@@ -25,7 +25,7 @@ class MongoContentStore(ContentStore):
:param collection: ignores but provided for consistency w/ other doc_store_config patterns
"""
logging.debug('Using MongoDB for static content serving at host={0} db={1}'.format(host, db))
logging.debug('Using MongoDB for static content serving at host={0} port={1} db={2}'.format(host, port, db))
_db = pymongo.database.Database(
pymongo.MongoClient(
host=host,
@@ -66,7 +66,7 @@ class MongoContentStore(ContentStore):
self.delete(content_id) # delete is a noop if the entry doesn't exist; so, don't waste time checking
thumbnail_location = content.thumbnail_location.to_deprecated_list_repr() if content.thumbnail_location else None
with self.fs.new_file(_id=content_id, filename=content.get_url_path(), content_type=content.content_type,
with self.fs.new_file(_id=content_id, filename=unicode(content.location), content_type=content.content_type,
displayname=content.name, content_son=content_son,
thumbnail_location=thumbnail_location,
import_path=content.import_path,

View File

@@ -12,46 +12,42 @@
// for modified nodal analysis (MNA) stamps see
// http://www.analog-electronics.eu/analog-electronics/modified-nodal-analysis/modified-nodal-analysis.xhtml
cktsim = (function() {
var cktsim = (function() {
///////////////////////////////////////////////////////////////////////////////
//
// Circuit
//
//////////////////////////////////////////////////////////////////////////////
// types of "nodes" in the linear system
T_VOLTAGE = 0;
T_CURRENT = 1;
// types of "nodes" in the linear system
var T_VOLTAGE = 0;
var T_CURRENT = 1;
v_newt_lim = 0.3; // Voltage limited Newton great for Mos/diodes
v_abstol = 1e-6; // Absolute voltage error tolerance
i_abstol = 1e-12; // Absolute current error tolerance
eps = 1.0e-12; // A very small number compared to one.
dc_max_iters = 1000; // max iterations before giving pu
max_tran_iters = 20; // max iterations before giving up
time_step_increase_factor = 2.0; // How much can lte let timestep grow.
lte_step_decrease_factor = 8; // Limit lte one-iter timestep shrink.
nr_step_decrease_factor = 4; // Newton failure timestep shink.
reltol = 0.0001; // Relative tol to max observed value
lterel = 10; // LTE/Newton tolerance ratio (> 10!)
res_check_abs = Math.sqrt(i_abstol); // Loose Newton residue check
res_check_rel = Math.sqrt(reltol); // Loose Newton residue check
var v_newt_lim = 0.3; // Voltage limited Newton great for Mos/diodes
var v_abstol = 1e-6; // Absolute voltage error tolerance
var i_abstol = 1e-12; // Absolute current error tolerance
var eps = 1.0e-12; // A very small number compared to one.
var dc_max_iters = 1000; // max iterations before giving up
var max_tran_iters = 20; // max iterations before giving up
var time_step_increase_factor = 2.0; // How much can lte let timestep grow.
var lte_step_decrease_factor = 8; // Limit lte one-iter timestep shrink.
var nr_step_decrease_factor = 4; // Newton failure timestep shrink.
var reltol = 0.0001; // Relative tol to max observed value
var lterel = 10; // LTE/Newton tolerance ratio (> 10!)
var res_check_abs = Math.sqrt(i_abstol); // Loose Newton residue check
var res_check_rel = Math.sqrt(reltol); // Loose Newton residue check
function Circuit() {
this.node_map = new Array();
this.node_map = [];
this.ntypes = [];
this.initial_conditions = []; // ic's for each element
this.devices = []; // list of devices
this.device_map = new Array(); // map name -> device
this.voltage_sources = []; // list of voltage sources
this.current_sources = []; // list of current sources
this.initial_conditions = [];
this.devices = [];
this.device_map = [];
this.voltage_sources = [];
this.current_sources = [];
this.finalized = false;
this.diddc = false;
this.node_index = -1;
this.periods = 1
}
@@ -102,7 +98,7 @@ cktsim = (function() {
}
// Check for voltage source loops.
n_vsrc = this.voltage_sources.length;
var n_vsrc = this.voltage_sources.length;
if (n_vsrc > 0) { // At least one voltage source
var GV = mat_make(n_vsrc, this.N); // Loop check
for (var i = n_vsrc - 1; i >= 0; --i) {
@@ -187,7 +183,6 @@ cktsim = (function() {
return false;
}
return true;
}
// if converges: updates this.solution, this.soln_max, returns iter count
@@ -196,11 +191,12 @@ cktsim = (function() {
Circuit.prototype.find_solution = function(load,maxiters) {
var soln = this.solution;
var rhs = this.rhs;
var d_sol = new Array();
var d_sol = [];
var abssum_compare;
var converged,abssum_old=0, abssum_rhs;
var use_limiting = false;
var down_count = 0;
var thresh;
// iteratively solve until values convere or iteration limit exceeded
for (var iter = 0; iter < maxiters; iter++) {
@@ -221,7 +217,6 @@ cktsim = (function() {
use_limiting = true;
}
else { // Compute the Newton delta
//d_sol = mat_solve(this.matrix,rhs);
d_sol = mat_solve_rq(this.matrix,rhs);
// If norm going down for ten iters, stop limiting
@@ -267,12 +262,10 @@ cktsim = (function() {
}
}
//alert(numeric.prettyPrint(this.solution);)
if (converged == true) {
for (var i = this.N - 1; i >= 0; --i)
if (Math.abs(soln[i]) > this.soln_max[i])
this.soln_max[i] = Math.abs(soln[i]);
return iter+1;
}
}
@@ -281,7 +274,6 @@ cktsim = (function() {
// DC analysis
Circuit.prototype.dc = function() {
// Allocation matrices for linear part, etc.
if (this.finalize() == false)
return undefined;
@@ -315,7 +307,7 @@ cktsim = (function() {
// Note that a dc solution was computed
this.diddc = true;
// create solution dictionary
var result = new Array();
var result = [];
// capture node voltages
for (var name in this.node_map) {
var index = this.node_map[name];
@@ -348,7 +340,6 @@ cktsim = (function() {
for (var i = ckt.N-1; i >= 0; --i) {
var dqdt = ckt.alpha0*ckt.q[i] + ckt.alpha1*ckt.oldq[i] +
ckt.alpha2*ckt.old2q[i];
//alert(numeric.prettyPrint(dqdt));
rhs[i] = ckt.beta0[i]*ckt.c[i] + ckt.beta1[i]*ckt.oldc[i] - dqdt;
}
// matrix = beta0*G + alpha0*C.
@@ -401,7 +392,7 @@ cktsim = (function() {
}
return new_step;
}
// Standard to do a dc analysis before transient
// Otherwise, do the setup also done in dc.
no_dc = false;
@@ -424,7 +415,7 @@ cktsim = (function() {
// build array to hold list of results for each variable
// last entry is for timepoints.
var response = new Array(N + 1);
for (var i = N; i >= 0; --i) response[i] = new Array();
for (var i = N; i >= 0; --i) response[i] = [];
// Allocate back vectors for up to a second order method
this.old3sol = new Array(this.N);
@@ -473,9 +464,7 @@ cktsim = (function() {
period = Math.min(period, per);
}
this.periods = Math.ceil((tstop - tstart)/period);
//alert('number of periods ' + this.periods);
this.time = tstart;
// ntpts adjusted by numbers of periods in input
this.max_step = (tstop - tstart)/(this.periods*ntpts);
@@ -495,7 +484,6 @@ cktsim = (function() {
this.oldc[i] = this.c[i];
}
var beta0,beta1;
// Start with two pseudo-Euler steps, maximum 50000 steps/period
var max_nsteps = this.periods*50000;
@@ -511,7 +499,6 @@ cktsim = (function() {
this.old3q[i] = this.oldq[i];
this.old2q[i] = this.oldq[i];
this.oldq[i] = this.q[i];
}
if (step_index < 0) { // Take a prestep using BE
@@ -572,7 +559,6 @@ cktsim = (function() {
if (step_index > 0) new_step = time_step_increase_factor*this.min_step;
break;
} else if (iterations == undefined) { // NR nonconvergence, shrink by factor
//alert('timestep nonconvergence ' + this.time + ' ' + step_index);
this.time = this.oldt +
(this.time - this.oldt)/nr_step_decrease_factor;
} else { // Check the LTE and shrink step if needed.
@@ -587,7 +573,7 @@ cktsim = (function() {
}
// create solution dictionary
var result = new Array();
var result = [];
for (var name in this.node_map) {
var index = this.node_map[name];
result[name] = (index == -1) ? 0 : response[index];
@@ -629,7 +615,7 @@ cktsim = (function() {
// build array to hold list of magnitude and phases for each node
// last entry is for frequency values
var response = new Array(2*N + 1);
for (var i = 2*N; i >= 0; --i) response[i] = new Array();
for (var i = 2*N; i >= 0; --i) response[i] = [];
// multiplicative frequency increase between freq points
var delta_f = Math.exp(Math.LN10/npts);
@@ -685,7 +671,7 @@ cktsim = (function() {
}
// create solution dictionary
var result = new Array();
var result = [];
for (var name in this.node_map) {
var index = this.node_map[name];
result[name] = (index == -1) ? 0 : response[index];
@@ -695,7 +681,6 @@ cktsim = (function() {
return result;
}
// Helper for adding devices to a circuit, warns on duplicate device names.
Circuit.prototype.add_device = function(d,name) {
// Add device to list of devices and to device map
@@ -738,7 +723,6 @@ cktsim = (function() {
} // zero area diodes discarded.
}
Circuit.prototype.c = function(n1,n2,v,name) {
// try to convert string value into numeric value, barf if we can't
if ((typeof v) == 'string') {
@@ -774,6 +758,7 @@ cktsim = (function() {
}
Circuit.prototype.opamp = function(np,nn,no,ng,A,name) {
var ratio;
// try to convert string value into numeric value, barf if we can't
if ((typeof A) == 'string') {
ratio = parse_number(A,undefined);
@@ -899,7 +884,7 @@ cktsim = (function() {
function mat_v_mult(M,x,b,scale) {
var n = M.length;
var m = M[0].length;
if (n != b.length || m != x.length)
throw 'Rows of M mismatched to b or cols mismatch to x.';
@@ -914,7 +899,7 @@ cktsim = (function() {
function mat_scale_add(A, B, scalea, scaleb, C) {
var n = A.length;
var m = A[0].length;
if (n > B.length || m > B[0].length)
throw 'Row or columns of A to large for B';
if (n > C.length || m > C[0].length)
@@ -939,7 +924,7 @@ cktsim = (function() {
// variables (rows that can be removed without changing rank(M).
Circuit.prototype.algebraic = function(M) {
var Nr = M.length
Mc = mat_make(Nr, Nr);
var Mc = mat_make(Nr, Nr);
mat_copy(M,Mc);
var R = mat_rank(Mc);
@@ -969,7 +954,6 @@ cktsim = (function() {
for (var j = 0; j < m; j++)
dest[i][j] = src[i][j];
}
// Copy and transpose A -> using the bounds of A
function mat_copy_transposed(src,dest) {
var n = src.length;
@@ -989,7 +973,7 @@ cktsim = (function() {
var Nc = Mo[0].length; // Number of columns
var temp,i,j;
// Make a copy to avoid overwriting
M = mat_make(Nr, Nc);
var M = mat_make(Nr, Nc);
mat_copy(Mo,M);
// Find matrix maximum entry
@@ -1034,7 +1018,6 @@ cktsim = (function() {
}
}
// return the rank
return the_rank;
}
@@ -1043,7 +1026,7 @@ cktsim = (function() {
// M should have the extra column!
// Almost everything is in-lined for speed, sigh.
function mat_solve_rq(M, rhs) {
var scale;
var Nr = M.length; // Number of rows
var Nc = M[0].length; // Number of columns
@@ -1076,7 +1059,7 @@ cktsim = (function() {
}
// Calculate row norm, save if this is first (largest)
row_norm = Math.sqrt(maxsumsq);
var row_norm = Math.sqrt(maxsumsq);
if (row == 0) mat_scale = row_norm;
// Check for all zero rows
@@ -1087,7 +1070,6 @@ cktsim = (function() {
break;
}
// Nonzero row, eliminate from rows below
var Mr = M[row];
for (var col = Nc-1; col >= 0; --col) // Scale rhs also
@@ -1113,7 +1095,6 @@ cktsim = (function() {
}
}
// Return solution.
return x;
}
@@ -1169,7 +1150,6 @@ cktsim = (function() {
x[i] = temp/M[i][i];
}
// return solution
return x;
}
@@ -1286,7 +1266,6 @@ cktsim = (function() {
return result*multiplier;
}
}
// read decimal integer or floating-point number
while (true) {
if (s.charAt(index) >= '0' && s.charAt(index) <= '9')
@@ -1375,10 +1354,10 @@ cktsim = (function() {
// inflection_point(t) -- compute time after t when a time point is needed
// dc -- value at time 0
// period -- repeat period for periodic sources (0 if not periodic)
function parse_source(v) {
// generic parser: parse v as either <value> or <fun>(<value>,...)
var src = new Object();
var src = {};
src.period = 0; // Default not periodic
src.value = function(t) { return 0; } // overridden below
src.inflection_point = function(t) { return undefined; }; // may be overridden below
@@ -1517,7 +1496,7 @@ cktsim = (function() {
else return undefined;
}
}
// object has all the necessary info to compute the source value and inflection points
src.dc = src.value(0); // DC value is value at time 0
return src;
@@ -1590,7 +1569,6 @@ cktsim = (function() {
function VSource(npos,nneg,branch,v) {
Device.call(this);
this.src = parse_source(v);
this.npos = npos;
this.nneg = nneg;
@@ -1630,7 +1608,6 @@ cktsim = (function() {
function ISource(npos,nneg,v) {
Device.call(this);
this.src = parse_source(v);
this.npos = npos;
this.nneg = nneg;
@@ -1829,7 +1806,6 @@ cktsim = (function() {
}
///////////////////////////////////////////////////////////////////////////////
//
// Simple Voltage-Controlled Voltage Source Op Amp model
@@ -1849,7 +1825,6 @@ cktsim = (function() {
Opamp.prototype = new Device();
Opamp.prototype.constructor = Opamp;
Opamp.prototype.load_linear = function(ckt) {
// MNA stamp for VCVS: 1/A(v(no) - v(ng)) - (v(np)-v(nn))) = 0.
var invA = 1.0/this.gain;
@@ -1872,14 +1847,12 @@ cktsim = (function() {
}
///////////////////////////////////////////////////////////////////////////////
//
// Simplified MOS FET with no bulk connection and no body effect.
//
///////////////////////////////////////////////////////////////////////////////
function Fet(d,g,s,ratio,name,type) {
Device.call(this);
this.d = d;
@@ -2023,16 +1996,6 @@ function add_schematic_handler(other_onload) {
update_schematics();
}
}
/*
* THK: Attaching update_schematic to window.onload is rather presumptuous...
* The function is called for EVERY page load, whether in courseware or in
* course info, in 6.002x or the public health course. It is also redundant
* because courseware includes an explicit call to update_schematic after
* each ajax exchange. In this case, calling update_schematic twice appears
* to contribute to a bug in Firefox that does not render the schematic
* properly depending on timing.
*/
//window.onload = add_schematic_handler(window.onload);
// ask each schematic input widget to update its value field for submission
function prepare_schematics() {
@@ -2042,20 +2005,18 @@ function prepare_schematics() {
}
schematic = (function() {
background_style = 'rgb(220,220,220)';
element_style = 'rgb(255,255,255)';
thumb_style = 'rgb(128,128,128)';
normal_style = 'rgb(0,0,0)'; // default drawing color
component_style = 'rgb(64,64,255)'; // color for unselected components
selected_style = 'rgb(64,255,64)'; // highlight color for selected components
grid_style = "rgb(128,128,128)";
annotation_style = 'rgb(255,64,64)'; // color for diagram annotations
var background_style = 'rgb(220,220,220)';
var element_style = 'rgb(255,255,255)';
var thumb_style = 'rgb(128,128,128)';
var normal_style = 'rgb(0,0,0)'; // default drawing color
var component_style = 'rgb(64,64,255)'; // color for unselected components
var selected_style = 'rgb(64,255,64)'; // highlight color for selected components
var grid_style = "rgb(128,128,128)";
var annotation_style = 'rgb(255,64,64)'; // color for diagram annotations
var property_size = 5; // point size for Component property text
var annotation_size = 6; // point size for diagram annotations
property_size = 5; // point size for Component property text
annotation_size = 6; // point size for diagram annotations
// list of all the defined parts
parts_map = {
var parts_map = {
'g': [Ground, 'Ground connection'],
'L': [Label, 'Node label'],
'v': [VSource, 'Voltage source'],
@@ -2093,7 +2054,6 @@ schematic = (function() {
if (this.origin_y == undefined) this.origin_y = 0;
this.cursor_x = 0;
this.cursor_y = 0;
this.window_list = []; // list of pop-up windows in increasing z order
// use user-supplied list of parts if supplied
@@ -2101,7 +2061,7 @@ schematic = (function() {
this.edits_allowed = true;
var parts = input.getAttribute('parts');
if (parts == undefined || parts == 'None') {
parts = new Array();
parts = [];
for (var p in parts_map) parts.push(p);
} else if (parts == '') {
this.edits_allowed = false;
@@ -2138,7 +2098,7 @@ schematic = (function() {
this.submit_analyses = undefined;
// toolbar
this.tools = new Array();
this.tools = [];
this.toolbar = [];
/* DISABLE HELP BUTTON (target URL not consistent with multicourse hierarchy) -- SJSU
@@ -2182,7 +2142,7 @@ schematic = (function() {
this.tran_tstop = '1';
}
}
// set up diagram canvas
this.canvas = document.createElement('canvas');
this.width = input.getAttribute('width');
@@ -2233,13 +2193,11 @@ schematic = (function() {
this.status_div.style.height = status_height + 'px';
} else this.status_div = undefined;
this.connection_points = new Array(); // location string => list of cp's
this.connection_points = []; // location string => list of cp's
this.components = [];
this.dragging = false;
this.select_rect = undefined;
this.wire = undefined;
this.operating_point = undefined; // result from DC analysis
this.dc_results = undefined; // saved analysis results for submission
this.ac_results = undefined; // saved analysis results for submission
@@ -2280,7 +2238,7 @@ schematic = (function() {
if (tool != null) td.appendChild(tool);
}
}
// add canvas and parts bin to DOM
tr = document.createElement('tr');
table.appendChild(tr);
@@ -2339,13 +2297,12 @@ schematic = (function() {
this.zoomall();
}
part_w = 42; // size of a parts bin compartment
part_h = 42;
status_height = 18;
var part_w = 42; // size of a parts bin compartment
var part_h = 42;
var status_height = 18;
Schematic.prototype.add_component = function(new_c) {
this.components.push(new_c);
// create undoable edit record here
}
@@ -2358,7 +2315,6 @@ schematic = (function() {
return this.connection_points[cp.location];
}
// add connection point to list of connection points at that location
Schematic.prototype.add_connection_point = function(cp) {
var cplist = this.connection_points[cp.location];
if (cplist) cplist.push(cp);
@@ -2367,11 +2323,9 @@ schematic = (function() {
this.connection_points[cp.location] = cplist;
}
// return list of conincident connection points
return cplist;
}
// remove connection point from the list points at the old location
Schematic.prototype.remove_connection_point = function(cp,old_location) {
// remove cp from list at old location
var cplist = this.connection_points[old_location];
@@ -2387,13 +2341,11 @@ schematic = (function() {
}
}
// connection point has changed location: remove, then add
Schematic.prototype.update_connection_point = function(cp,old_location) {
this.remove_connection_point(cp,old_location);
return this.add_connection_point(cp);
}
// add a wire to the schematic
Schematic.prototype.add_wire = function(x1,y1,x2,y2) {
var new_wire = new Wire(x1,y1,x2,y2);
new_wire.add(this);
@@ -2464,7 +2416,6 @@ schematic = (function() {
Schematic.prototype.unselect_all = function(which) {
this.operating_point = undefined; // remove annotations
for (var i = this.components.length - 1; i >= 0; --i)
if (i != which) this.components[i].set_select(false);
}
@@ -2489,7 +2440,6 @@ schematic = (function() {
if (component.selected) component.move_end();
}
this.dragging = false;
this.clean_up_wires();
this.redraw_background();
}
@@ -2509,11 +2459,6 @@ schematic = (function() {
this.origin_x += cx*(this.scale - nscale);
this.origin_y += cy*(this.scale - nscale);
this.scale = nscale;
//this.origin_x = cx - this.width/(2*this.scale);
//this.origin_y = cy - this.height/(2*this.scale);
this.redraw_background();
}
@@ -2522,15 +2467,14 @@ schematic = (function() {
this.redraw_background();
}
zoom_factor = 1.25; // scaling is some power of zoom_factor
zoom_min = 0.5;
zoom_max = 4.0;
origin_min = -200; // in grids
origin_max = 200;
var zoom_factor = 1.25; // scaling is some power of zoom_factor
var zoom_min = 0.5;
var zoom_max = 4.0;
var origin_min = -200; // in grids
var origin_max = 200;
Schematic.prototype.zoomin = function() {
var nscale = this.scale * zoom_factor;
if (nscale < zoom_max) {
// keep center of view unchanged
this.origin_x += (this.width/2)*(1.0/this.scale - 1.0/nscale);
@@ -2542,7 +2486,6 @@ schematic = (function() {
Schematic.prototype.zoomout = function() {
var nscale = this.scale / zoom_factor;
if (nscale > zoom_min) {
// keep center of view unchanged
this.origin_x += (this.width/2)*(1.0/this.scale - 1.0/nscale);
@@ -2632,7 +2575,6 @@ schematic = (function() {
new_c.add(this);
}
// see what we've wrought
this.redraw();
}
@@ -2647,7 +2589,6 @@ schematic = (function() {
// use default value if no schematic info in value
if (value == undefined || value.indexOf('[') == -1)
value = initial_value;
if (value && value.indexOf('[') != -1) {
// convert string value into data structure
var json = JSON.parse(value);
@@ -2656,12 +2597,6 @@ schematic = (function() {
for (var i = json.length - 1; i >= 0; --i) {
var c = json[i];
if (c[0] == 'view') {
// special hack: view component lets us recreate view
// ignore saved view parameters as they sometimes screw students
//this.origin_x = c[1];
//this.origin_y = c[2];
//this.scale = c[3];
//this.ac_npts = c[4];
this.ac_fstart = c[5];
this.ac_fstop = c[6];
this.ac_source_name = c[7];
@@ -2684,20 +2619,15 @@ schematic = (function() {
var coords = c[1];
var properties = c[2];
// make the part
var part = new parts_map[type][0](coords[0],coords[1],coords[2]);
// give it its properties
for (var name in properties)
part.properties[name] = properties[name];
// add component to the diagram
part.add(this);
}
}
}
// see what we've got!
this.redraw_background();
}
@@ -2721,7 +2651,6 @@ schematic = (function() {
this.components[i].label_connections();
}
// generate a new label
Schematic.prototype.get_next_label = function() {
// generate next label in sequence
this.next_label += 1;
@@ -2746,7 +2675,6 @@ schematic = (function() {
this.input.value = JSON.stringify(this.json_with_analyses());
}
// produce a JSON representation of the diagram
Schematic.prototype.json = function() {
var json = [];
@@ -2764,7 +2692,6 @@ schematic = (function() {
return json;
}
// produce a JSON representation of the diagram
Schematic.prototype.json_with_analyses = function() {
var json = this.json();
@@ -2841,14 +2768,13 @@ schematic = (function() {
var fstart_lbl = 'Starting frequency (Hz)';
var fstop_lbl = 'Ending frequency (Hz)';
var source_name_lbl = 'Name of V or I source for ac'
if (this.find_probes().length == 0) {
alert("AC Analysis: there are no voltage probes in the diagram!");
return;
}
var fields = new Array();
//fields[npts_lbl] = build_input('text',10,this.ac_npts);
var fields = [];
fields[fstart_lbl] = build_input('text',10,this.ac_fstart);
fields[fstop_lbl] = build_input('text',10,this.ac_fstop);
fields[source_name_lbl] = build_input('text',10,this.ac_source_name);
@@ -2861,7 +2787,6 @@ schematic = (function() {
var sch = content.sch;
// retrieve parameters, remember for next time
//sch.ac_npts = content.fields[npts_lbl].value;
sch.ac_fstart = content.fields[fstart_lbl].value;
sch.ac_fstop = content.fields[fstop_lbl].value;
sch.ac_source_name = content.fields[source_name_lbl].value;
@@ -2873,9 +2798,7 @@ schematic = (function() {
});
}
// perform ac analysis
Schematic.prototype.ac_analysis = function(npts,fstart,fstop,ac_source_name) {
// run the analysis
var ckt = this.extract_circuit();
if (ckt === null) return;
var results = ckt.ac(npts,fstart,fstop,ac_source_name);
@@ -2889,7 +2812,6 @@ schematic = (function() {
for (var i = x_values.length - 1; i >= 0; --i)
x_values[i] = Math.log(x_values[i])/Math.LN10;
if (this.submit_analyses != undefined) {
var submit = this.submit_analyses['ac'];
if (submit != undefined) {
@@ -2919,7 +2841,6 @@ schematic = (function() {
var y_values = []; // list of [color, result_array]
var z_values = []; // list of [color, result_array]
var probes = this.find_probes();
var probe_maxv = [];
var probe_color = [];
@@ -2931,8 +2852,8 @@ schematic = (function() {
var v = results[label];
probe_maxv[i] = array_max(v); // magnitudes always > 0
}
var all_max = array_max(probe_maxv);
var all_max = array_max(probe_maxv);
if (all_max < 1.0e-16) {
alert('Zero ac response, -infinity on DB scale.');
} else {
@@ -2950,7 +2871,6 @@ schematic = (function() {
var color = probes[i][0];
var label = probes[i][1];
var offset = cktsim.parse_number(probes[i][2]);
var v = results[label];
// convert values into dB relative to source amplitude
var v_max = 1;
@@ -2977,15 +2897,13 @@ schematic = (function() {
var npts_lbl = 'Minimum number of timepoints';
var tstop_lbl = 'Stop Time (seconds)';
var probes = this.find_probes();
if (probes.length == 0) {
alert("Transient Analysis: there are no probes in the diagram!");
return;
}
var fields = new Array();
//fields[npts_lbl] = build_input('text',10,this.tran_npts);
var fields = [];
fields[tstop_lbl] = build_input('text',10,this.tran_tstop);
var content = build_table(fields);
@@ -2998,7 +2916,6 @@ schematic = (function() {
if (ckt === null) return;
// retrieve parameters, remember for next time
//sch.tran_npts = content.fields[npts_lbl].value;
sch.tran_tstop = content.fields[tstop_lbl].value;
// gather a list of nodes that are being probed. These
@@ -3109,7 +3026,6 @@ schematic = (function() {
}
}
// update diagram
this.redraw_background();
}
@@ -3161,7 +3077,6 @@ schematic = (function() {
}
}
this.unsel_bbox = [min_x,min_y,max_x,max_y];
this.redraw(); // background changed, redraw on screen
}
@@ -3200,7 +3115,7 @@ schematic = (function() {
var cplist = this.connection_points[location];
cplist[0].draw(c,cplist.length);
}
// draw new wire
if (this.wire) {
var r = this.wire;
@@ -3221,14 +3136,14 @@ schematic = (function() {
c.lineTo(r[0],r[1]);
c.stroke();
}
// display operating point results
if (this.operating_point) {
if (typeof this.operating_point == 'string')
this.message(this.operating_point);
else {
// make a copy of the operating_point info so we can mess with it
var temp = new Array();
var temp = [];
for (var i in this.operating_point) temp[i] = this.operating_point[i];
// run through connection points displaying (once) the voltage
@@ -3241,7 +3156,7 @@ schematic = (function() {
this.components[i].display_current(c,temp)
}
}
// add scrolling/zooming control
if (!this.diagram_only) {
var r = this.sctl_r;
@@ -3351,11 +3266,10 @@ schematic = (function() {
totalOffsetY += currentElement.offsetTop;
}
while (currentElement = currentElement.offsetParent);
// now compute relative position of click within the canvas
this.mouse_x = event.pageX - totalOffsetX;
this.mouse_y = event.pageY - totalOffsetY;
this.page_x = event.pageX;
this.page_y = event.pageY;
}
@@ -3594,12 +3508,10 @@ schematic = (function() {
// update moving corner of selection rectangle
sch.select_rect[2] = sch.canvas.mouse_x;
sch.select_rect[3] = sch.canvas.mouse_y;
//sch.message(sch.select_rect.toString());
}
// just redraw dynamic components
sch.redraw();
//sch.message(sch.canvas.page_x + ',' + sch.canvas.page_y + ';' + sch.canvas.mouse_x + ',' + sch.canvas.mouse_y + ';' + sch.cursor_x + ',' + sch.cursor_y);
return false;
}
@@ -3636,7 +3548,7 @@ schematic = (function() {
var s = [r[0]/sch.scale + sch.origin_x, r[1]/sch.scale + sch.origin_y,
r[2]/sch.scale + sch.origin_x, r[3]/sch.scale + sch.origin_y];
canonicalize(s);
if (!event.shiftKey) sch.unselect_all();
// select components that intersect selection rectangle
@@ -3707,7 +3619,7 @@ schematic = (function() {
Schematic.prototype.append_message = function(message) {
this.status.nodeValue += ' / '+message;
}
// set up a dialog with specified title, content and two buttons at
// the bottom: OK and Cancel. If Cancel is clicked, dialog goes away
// and we're done. If OK is clicked, dialog goes away and the
@@ -3737,7 +3649,6 @@ schematic = (function() {
body.style.padding = '5px';
dialog.appendChild(body);
// OK button
var ok_button = document.createElement('span');
ok_button.appendChild(document.createTextNode('OK'));
ok_button.dialog = dialog; // for the handler to use
@@ -3747,7 +3658,6 @@ schematic = (function() {
ok_button.style.padding = '5px';
ok_button.style.margin = '10px';
// cancel button
var cancel_button = document.createElement('span');
cancel_button.appendChild(document.createTextNode('Cancel'));
cancel_button.dialog = dialog; // for the handler to use
@@ -3770,7 +3680,6 @@ schematic = (function() {
this.window(title,dialog);
}
// callback when user click "Cancel" in a dialog
function dialog_cancel(event) {
if (!event) event = window.event;
var dialog = (window.event) ? event.srcElement.dialog : event.target.dialog;
@@ -3778,14 +3687,12 @@ schematic = (function() {
window_close(dialog.win);
}
// callback when user click "OK" in a dialog
function dialog_okay(event) {
if (!event) event = window.event;
var dialog = (window.event) ? event.srcElement.dialog : event.target.dialog;
window_close(dialog.win);
// invoke the callback with the dialog contents as the argument
if (dialog.callback) dialog.callback(dialog.content);
}
@@ -3824,7 +3731,6 @@ schematic = (function() {
return tbl;
}
// build an input field
function build_input(type,size,value) {
var input = document.createElement('input');
input.type = type;
@@ -3890,7 +3796,6 @@ schematic = (function() {
// add to DOM
win.style.background = 'white';
//win.style.zIndex = '1000';
win.style.position = 'absolute';
win.style.left = win.left + 'px';
win.style.top = win.top + 'px';
@@ -3943,7 +3848,7 @@ schematic = (function() {
document.addEventListener('mousemove',window_mouse_move,false);
document.addEventListener('mouseup',window_mouse_up,false);
document.tracking_window = win;
// remember where mouse is so we can compute dx,dy during drag
win.drag_x = event.pageX;
win.drag_y = event.pageY;
@@ -3953,7 +3858,7 @@ schematic = (function() {
function window_mouse_up(event) {
var win = document.tracking_window;
// show's over folks...
document.removeEventListener('mousemove',window_mouse_move,false);
document.removeEventListener('mouseup',window_mouse_up,false);
@@ -3965,7 +3870,7 @@ schematic = (function() {
function window_mouse_move(event) {
var win = document.tracking_window;
if (win.drag_x) {
var dx = event.pageX - win.drag_x;
var dy = event.pageY - win.drag_y;
@@ -3975,7 +3880,7 @@ schematic = (function() {
win.top += dy;
win.style.left = win.left + 'px';
win.style.top = win.top + 'px';
// update reference point
win.drag_x += dx;
win.drag_y += dy;
@@ -4076,17 +3981,17 @@ schematic = (function() {
}
}
help_icon = 'data:image/gif;base64,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';
var help_icon = 'data:image/gif;base64,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';
cut_icon = 'data:image/gif;base64,R0lGODlhEAAQALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAAcALAAAAAAQABAAAAQu8MhJqz1g5qs7lxv2gRkQfuWomarXEgDRHjJhf3YtyRav0xcfcFgR0nhB5OwTAQA7';
var cut_icon = 'data:image/gif;base64,R0lGODlhEAAQALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAAcALAAAAAAQABAAAAQu8MhJqz1g5qs7lxv2gRkQfuWomarXEgDRHjJhf3YtyRav0xcfcFgR0nhB5OwTAQA7';
copy_icon = 'data:image/gif;base64,R0lGODlhEAAQALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAAcALAAAAAAQABAAAAQ+8MhJ6wE4Wwqef9gmdV8HiKZJrCz3ecS7TikWfzExvk+M9a0a4MbTkXCgTMeoHPJgG5+yF31SLazsTMTtViIAOw==';
var copy_icon = 'data:image/gif;base64,R0lGODlhEAAQALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAAcALAAAAAAQABAAAAQ+8MhJ6wE4Wwqef9gmdV8HiKZJrCz3ecS7TikWfzExvk+M9a0a4MbTkXCgTMeoHPJgG5+yF31SLazsTMTtViIAOw==';
paste_icon = 'data:image/gif;base64,R0lGODlhEAAQALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAAcALAAAAAAQABAAAARL8MhJqwUYWJnxWp3GDcgAgCdQIqLKXmVLhhnyHiqpr7rME8AgocVDEB5IJHD0SyofBFzxGIQGAbvB0ZkcTq1CKK6z5YorwnR0w44AADs=';
var paste_icon = 'data:image/gif;base64,R0lGODlhEAAQALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//////yH5BAEAAAcALAAAAAAQABAAAARL8MhJqwUYWJnxWp3GDcgAgCdQIqLKXmVLhhnyHiqpr7rME8AgocVDEB5IJHD0SyofBFzxGIQGAbvB0ZkcTq1CKK6z5YorwnR0w44AADs=';
close_icon = 'data:image/gif;base64,R0lGODlhEAAQAMQAAGtra/f3/62tre/v9+bm787O1pycnHNzc6WlpcXFxd7e3tbW1nt7e7W1te/v74SEhMXFzmNjY+bm5v///87OzgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAAQABAAAAVt4DRMZGmSwRQQBUS9MAwRIyQ5Uq7neEFSDtxOF4T8cobIQaE4RAQ5yjHHiCCSD510QtFGvoCFdppDfBu7bYzy+D7WP5ggAgA8Y3FKwi5IAhIweW1vbBGEWy5rilsFi2tGAwSJixAFBCkpJ5ojIQA7';
var close_icon = 'data:image/gif;base64,R0lGODlhEAAQAMQAAGtra/f3/62tre/v9+bm787O1pycnHNzc6WlpcXFxd7e3tbW1nt7e7W1te/v74SEhMXFzmNjY+bm5v///87OzgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAAQABAAAAVt4DRMZGmSwRQQBUS9MAwRIyQ5Uq7neEFSDtxOF4T8cobIQaE4RAQ5yjHHiCCSD510QtFGvoCFdppDfBu7bYzy+D7WP5ggAgA8Y3FKwi5IAhIweW1vbBGEWy5rilsFi2tGAwSJixAFBCkpJ5ojIQA7';
grid_icon = 'data:image/gif;base64,R0lGODlhEAAQAMQAAAAAAP///zAwYT09bpGRqZ6et5iYsKWlvbi40MzM5cXF3czM5OHh5tTU2fDw84uMom49DbWKcfLy8g0NDcDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABQALAAAAAAQABAAAAUtICWOZGmeKDCqIlu68AvMdO2ueHvGuslTN6Bt6MsBd8Zg77hsDW3FpRJFrYpCADs=';
var grid_icon = 'data:image/gif;base64,R0lGODlhEAAQAMQAAAAAAP///zAwYT09bpGRqZ6et5iYsKWlvbi40MzM5cXF3czM5OHh5tTU2fDw84uMom49DbWKcfLy8g0NDcDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABQALAAAAAAQABAAAAUtICWOZGmeKDCqIlu68AvMdO2ueHvGuslTN6Bt6MsBd8Zg77hsDW3FpRJFrYpCADs=';
///////////////////////////////////////////////////////////////////////////////
//
@@ -4108,10 +4013,9 @@ schematic = (function() {
var gt = function (a, b) { return a >= b; };
var capmin = function (a, b) { return Math.min(a, b); };
var capmax = function (a, b) { return Math.max(a, b); };
var checkX = { thereYet: gt, cap: capmin };
var checkY = { thereYet: gt, cap: capmin };
if (fromY - toY > 0) {
checkY.thereYet = lt;
checkY.cap = capmax;
@@ -4120,7 +4024,7 @@ schematic = (function() {
checkX.thereYet = lt;
checkX.cap = capmax;
}
this.moveTo(fromX, fromY);
var offsetX = fromX;
var offsetY = fromY;
@@ -4128,13 +4032,13 @@ schematic = (function() {
while (!(checkX.thereYet(offsetX, toX) && checkY.thereYet(offsetY, toY))) {
var ang = Math.atan2(toY - fromY, toX - fromX);
var len = pattern[idx];
offsetX = checkX.cap(toX, offsetX + (Math.cos(ang) * len));
offsetY = checkY.cap(toY, offsetY + (Math.sin(ang) * len));
if (dash) this.lineTo(offsetX, offsetY);
else this.moveTo(offsetX, offsetY);
idx = (idx + 1) % pattern.length;
dash = !dash;
}
@@ -4413,7 +4317,7 @@ schematic = (function() {
var values = z_values[plot][2];
if (values == undefined) continue; // no data points
var offset = z_values[plot][1];
x = plot_x(x_values[0]);
z = plot_z(values[0] + offset);
c.beginPath();
@@ -4491,14 +4395,14 @@ schematic = (function() {
}
function array_max(a) {
max = -Infinity;
var max = -Infinity;
for (var i = a.length - 1; i >= 0; --i)
if (a[i] > max) max = a[i];
return max;
}
function array_min(a) {
min = Infinity;
var min = Infinity;
for (var i = a.length - 1; i >= 0; --i)
if (a[i] < min) min = a[i];
return min;
@@ -4542,13 +4446,13 @@ schematic = (function() {
var values = graph.y_values[plot][2];
var color = probe_colors_rgb[graph.y_values[plot][0]];
if (values == undefined || color == undefined) continue; // no data points or x-axis
// interpolate signal value at graph_x using values[index-1] and values[index]
var y1 = (index == 0) ? values[0] : values[index-1];
var y2 = values[index];
var y = y1;
if (graph_x != x1) y += (graph_x - x1)*(y2 - y1)/(x2 - x1);
// annotate plot with value of signal at marker
c.fillStyle = element_style;
c.fillText('\u2588\u2588\u2588\u2588\u2588',tx-3,ty);
@@ -4566,13 +4470,13 @@ schematic = (function() {
var values = graph.z_values[plot][2];
var color = probe_colors_rgb[graph.z_values[plot][0]];
if (values == undefined || color == undefined) continue; // no data points or x-axis
// interpolate signal value at graph_x using values[index-1] and values[index]
var z1 = (index == 0) ? values[0]: values[index-1];
var z2 = values[index];
var z = z1;
if (graph_x != x1) z += (graph_x - x1)*(z2 - z1)/(x2 - x1);
// annotate plot with value of signal at marker
c.fillStyle = element_style;
c.fillText('\u2588\u2588\u2588\u2588\u2588',tx+3,ty);
@@ -4849,7 +4753,7 @@ schematic = (function() {
r[3] = temp;
}
}
function between(x,x1,x2) {
return x1 <= x && x <= x2;
}
@@ -4883,7 +4787,7 @@ schematic = (function() {
this.y = y;
this.rotation = rotation;
this.selected = false;
this.properties = new Array();
this.properties = [];
this.bounding_box = [0,0,0,0]; // in device coords [left,top,right,bottom]
this.bbox = this.bounding_box; // in absolute coords
this.connections = [];
@@ -4945,7 +4849,7 @@ schematic = (function() {
this.y += dy;
this.update_coords();
}
Component.prototype.move_end = function() {
var dx = this.x - this.move_x;
var dy = this.y - this.move_y;
@@ -5025,7 +4929,7 @@ schematic = (function() {
this.sch.draw_arc(c,nx,ny,radius,0,2*Math.PI,false,1,filled);
}
rot_angle = [
var rot_angle = [
0.0, // NORTH (identity)
Math.PI/2, // EAST (rot270)
Math.PI, // SOUTH (rot180)
@@ -5034,7 +4938,7 @@ schematic = (function() {
Math.PI/2, // REAST (int-neg)
Math.PI, // RSOUTH (negx)
3*Math.PI/2, // RWEST (int-pos)
];
];
Component.prototype.draw_arc = function(c,x,y,radius,start_radians,end_radians) {
c.strokeStyle = this.selected ? selected_style :
@@ -5056,7 +4960,7 @@ schematic = (function() {
}
// result of rotating an alignment [rot*9 + align]
aOrient = [
var aOrient = [
0, 1, 2, 3, 4, 5, 6, 7, 8, // NORTH (identity)
2, 5, 8, 1, 4, 7, 0, 3, 6, // EAST (rot270)
8, 7, 6, 5, 4, 3, 2, 1, 0, // SOUTH (rot180)
@@ -5067,13 +4971,13 @@ schematic = (function() {
0, 3, 6, 1, 4, 7, 2, 5, 8 // RWEST (int-pos)
];
textAlign = [
var textAlign = [
'left', 'center', 'right',
'left', 'center', 'right',
'left', 'center', 'right'
];
textBaseline = [
var textBaseline = [
'top', 'top', 'top',
'middle', 'middle', 'middle',
'bottom', 'bottom', 'bottom'
@@ -5099,7 +5003,7 @@ schematic = (function() {
// create an undoable edit record here
}
}
Component.prototype.select = function(x,y,shiftKey) {
this.was_previously_selected = this.selected;
if (this.near(x,y)) {
@@ -5129,7 +5033,7 @@ schematic = (function() {
Component.prototype.edit_properties = function(x,y) {
if (this.near(x,y)) {
// make an <input> widget for each property
var fields = new Array();
var fields = [];
for (var i in this.properties)
// underscore at beginning of property name => system property
if (i.charAt(0) != '_')
@@ -5148,7 +5052,6 @@ schematic = (function() {
} else return false;
}
// clear the labels on all connections
Component.prototype.clear_labels = function() {
for (var i = this.connections.length - 1; i >=0; --i) {
this.connections[i].clear_label();
@@ -5186,7 +5089,7 @@ schematic = (function() {
//
////////////////////////////////////////////////////////////////////////////////
connection_point_radius = 2;
var connection_point_radius = 2;
function ConnectionPoint(parent,x,y) {
this.parent = parent;
@@ -5258,7 +5161,7 @@ schematic = (function() {
var v = vmap[this.label];
if (v != undefined) {
var label = v.toFixed(2) + 'V';
// first draw some solid blocks in the background
c.globalAlpha = 0.85;
this.parent.draw_text(c,'\u2588\u2588\u2588',this.offset_x,this.offset_y,
@@ -5287,7 +5190,7 @@ schematic = (function() {
//
////////////////////////////////////////////////////////////////////////////////
near_distance = 2; // how close to wire counts as "near by"
var near_distance = 2; // how close to wire counts as "near by"
function Wire(x1,y1,x2,y2) {
// arbitrarily call x1,y1 the origin
@@ -5316,7 +5219,7 @@ schematic = (function() {
Wire.prototype.toString = function() {
return '<Wire ('+this.x+','+this.y+') ('+(this.x+this.dx)+','+(this.y+this.dy)+')>';
}
// return connection point at other end of wire from specified cp
Wire.prototype.other_end = function(cp) {
if (cp == this.connections[0]) return this.connections[1];
@@ -5429,7 +5332,7 @@ schematic = (function() {
Ground.prototype.toString = function() {
return '<Ground ('+this.x+','+this.y+')>';
}
Ground.prototype.draw = function(c) {
Component.prototype.draw.call(this,c); // give superclass a shot
this.draw_line(c,0,0,0,8);
@@ -5470,7 +5373,7 @@ schematic = (function() {
Label.prototype.toString = function() {
return '<Label'+' ('+this.x+','+this.y+')>';
}
Label.prototype.draw = function(c) {
Component.prototype.draw.call(this,c); // give superclass a shot
this.draw_line(c,0,0,0,8);
@@ -5493,8 +5396,8 @@ schematic = (function() {
//
////////////////////////////////////////////////////////////////////////////////
probe_colors = ['red','green','blue','cyan','magenta','yellow','black','x-axis'];
probe_colors_rgb = {
var probe_colors = ['red','green','blue','cyan','magenta','yellow','black','x-axis'];
var probe_colors_rgb = {
'red': 'rgb(255,64,64)',
'green': 'rgb(64,255,64)',
'blue': 'rgb(64,64,255)',
@@ -5519,7 +5422,7 @@ schematic = (function() {
Probe.prototype.toString = function() {
return '<Probe ('+this.x+','+this.y+')>';
}
Probe.prototype.draw = function(c) {
// draw outline
this.draw_line(c,0,0,4,-4);
@@ -5551,7 +5454,7 @@ schematic = (function() {
Probe.prototype.edit_properties = function(x,y) {
if (inside(this.bbox,x,y)) {
var fields = new Array();
var fields = [];
fields['Plot color'] = build_select(probe_colors,this.properties['color']);
fields['Plot offset'] = build_input('text',10,this.properties['offset']);
@@ -5598,7 +5501,7 @@ schematic = (function() {
Ammeter.prototype.toString = function() {
return '<Ammeter ('+this.x+','+this.y+')>';
}
Ammeter.prototype.move_end = function() {
Component.prototype.move_end.call(this); // do the normal processing
@@ -5692,7 +5595,7 @@ schematic = (function() {
Resistor.prototype.toString = function() {
return '<Resistor '+this.properties['r']+' ('+this.x+','+this.y+')>';
}
Resistor.prototype.draw = function(c) {
Component.prototype.draw.call(this,c); // give superclass a shot
this.draw_line(c,0,0,0,12);
@@ -5735,7 +5638,7 @@ schematic = (function() {
Capacitor.prototype.toString = function() {
return '<Capacitor '+this.properties['r']+' ('+this.x+','+this.y+')>';
}
Capacitor.prototype.draw = function(c) {
Component.prototype.draw.call(this,c); // give superclass a shot
this.draw_line(c,0,0,0,22);
@@ -5773,7 +5676,7 @@ schematic = (function() {
Inductor.prototype.toString = function() {
return '<Inductor '+this.properties['l']+' ('+this.x+','+this.y+')>';
}
Inductor.prototype.draw = function(c) {
Component.prototype.draw.call(this,c); // give superclass a shot
this.draw_line(c,0,0,0,14);
@@ -5798,7 +5701,7 @@ schematic = (function() {
//
////////////////////////////////////////////////////////////////////////////////
diode_types = ['normal','ideal'];
var diode_types = ['normal','ideal'];
function Diode(x,y,rotation,name,area,type) {
Component.call(this,'d',x,y,rotation);
@@ -5816,7 +5719,7 @@ schematic = (function() {
Diode.prototype.toString = function() {
return '<Diode '+this.properties['area']+' ('+this.x+','+this.y+')>';
}
Diode.prototype.draw = function(c) {
Component.prototype.draw.call(this,c); // give superclass a shot
this.draw_line(c,0,0,0,16);
@@ -5846,7 +5749,7 @@ schematic = (function() {
Diode.prototype.edit_properties = function(x,y) {
if (inside(this.bbox,x,y)) {
var fields = new Array();
var fields = [];
fields['name'] = build_input('text',10,this.properties['name']);
fields['area'] = build_input('text',10,this.properties['area']);
fields['type'] = build_select(diode_types,this.properties['type']);
@@ -5887,7 +5790,7 @@ schematic = (function() {
NFet.prototype.toString = function() {
return '<NFet '+this.properties['W/L']+' ('+this.x+','+this.y+')>';
}
NFet.prototype.draw = function(c) {
Component.prototype.draw.call(this,c); // give superclass a shot
this.draw_line(c,0,0,0,16);
@@ -5895,7 +5798,6 @@ schematic = (function() {
this.draw_line(c,-8,16,-8,32);
this.draw_line(c,-8,32,0,32);
this.draw_line(c,0,32,0,48);
this.draw_line(c,-24,24,-12,24);
this.draw_line(c,-12,16,-12,32);
@@ -5933,7 +5835,7 @@ schematic = (function() {
PFet.prototype.toString = function() {
return '<PFet '+this.properties['W/L']+' ('+this.x+','+this.y+')>';
}
PFet.prototype.draw = function(c) {
Component.prototype.draw.call(this,c); // give superclass a shot
this.draw_line(c,0,0,0,16);
@@ -5941,9 +5843,7 @@ schematic = (function() {
this.draw_line(c,-8,16,-8,32);
this.draw_line(c,-8,32,0,32);
this.draw_line(c,0,32,0,48);
this.draw_line(c,-24,24,-16,24);
this.draw_circle(c,-14,24,2,false);
this.draw_line(c,-12,16,-12,32);
@@ -5982,7 +5882,7 @@ schematic = (function() {
OpAmp.prototype.toString = function() {
return '<OpAmp'+this.properties['A']+' ('+this.x+','+this.y+')>';
}
OpAmp.prototype.draw = function(c) {
Component.prototype.draw.call(this,c); // give superclass a shot
// triangle
@@ -6014,7 +5914,6 @@ schematic = (function() {
//
////////////////////////////////////////////////////////////////////////////////
function Source(x,y,rotation,name,type,value) {
Component.call(this,type,x,y,rotation);
this.properties['name'] = name;
@@ -6024,7 +5923,6 @@ schematic = (function() {
this.add_connection(0,48);
this.bounding_box = [-12,0,12,48];
this.update_coords();
this.content = document.createElement('div'); // used by edit_properties
}
Source.prototype = new Component();
@@ -6033,7 +5931,7 @@ schematic = (function() {
Source.prototype.toString = function() {
return '<'+this.type+'source '+this.properties['params']+' ('+this.x+','+this.y+')>';
}
Source.prototype.draw = function(c) {
Component.prototype.draw.call(this,c); // give superclass a shot
this.draw_line(c,0,0,0,12);
@@ -6041,15 +5939,10 @@ schematic = (function() {
this.draw_line(c,0,36,0,48);
if (this.type == 'v') { // voltage source
//this.draw_text(c,'+',0,12,1,property_size);
//this.draw_text(c,'\u2013',0,36,7,property_size); // minus sign
// draw + and -
this.draw_line(c,0,15,0,21);
this.draw_line(c,-3,18,3,18);
this.draw_line(c,-3,30,3,30);
// draw V
//this.draw_line(c,-3,20,0,28);
//this.draw_line(c,3,20,0,28);
} else if (this.type == 'i') { // current source
// draw arrow: pos to neg
this.draw_line(c,0,15,0,32);
@@ -6064,7 +5957,7 @@ schematic = (function() {
}
// map source function name to labels for each source parameter
source_functions = {
var source_functions = {
'dc': ['DC value'],
'impulse': ['Height',
@@ -6201,7 +6094,6 @@ schematic = (function() {
} else return false;
}
function VSource(x,y,rotation,name,value) {
Source.call(this,x,y,rotation,name,'v',value);
this.type = 'v';
@@ -6304,3 +6196,4 @@ schematic = (function() {
}
return module;
}());

View File

@@ -32,4 +32,6 @@ class @Conditional
else
$(element).show()
XBlock.initializeBlocks @el
# The children are rendered with a new request, so they have a different request-token.
# Use that token instead of @requestToken by simply not passing a token into initializeBlocks.
XBlock.initializeBlocks(@el)

View File

@@ -1,5 +1,6 @@
class @Sequence
constructor: (element) ->
@requestToken = $(element).data('request-token')
@el = $(element).find('.sequence')
@contents = @$('.seq_contents')
@content_container = @$('#seq_content')
@@ -102,7 +103,7 @@ class @Sequence
current_tab = @contents.eq(new_position - 1)
@content_container.html(current_tab.text()).attr("aria-labelledby", current_tab.attr("aria-labelledby"))
XBlock.initializeBlocks(@content_container)
XBlock.initializeBlocks(@content_container, @requestToken)
window.update_schematics() # For embedded circuit simulator exercises in 6.002x

View File

@@ -707,8 +707,8 @@ class EdxJSONEncoder(json.JSONEncoder):
ISO date strings
"""
def default(self, obj):
if isinstance(obj, Location):
return obj.to_deprecated_string()
if isinstance(obj, (CourseKey, UsageKey)):
return unicode(obj)
elif isinstance(obj, datetime.datetime):
if obj.tzinfo is not None:
if obj.utcoffset() is None:

View File

@@ -46,7 +46,7 @@ def strip_key(func):
# remove version and branch, by default
rem_vers = kwargs.pop('remove_version', True)
rem_branch = kwargs.pop('remove_branch', False)
rem_branch = kwargs.pop('remove_branch', True)
# helper function for stripping individual values
def strip_key_func(val):
@@ -171,15 +171,6 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
# return the default store
return self.default_modulestore
# return the first store, as the default
return self.default_modulestore
@property
def default_modulestore(self):
"""
Return the default modulestore
"""
return self.modulestores[0]
def _get_modulestore_by_type(self, modulestore_type):
"""
@@ -403,15 +394,13 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
if source_modulestore == dest_modulestore:
return source_modulestore.clone_course(source_course_id, dest_course_id, user_id, fields, **kwargs)
# ensure super's only called once. The delegation above probably calls it; so, don't move
# the invocation above the delegation call
super(MixedModuleStore, self).clone_course(source_course_id, dest_course_id, user_id, fields, **kwargs)
if dest_modulestore.get_modulestore_type() == ModuleStoreEnum.Type.split:
split_migrator = SplitMigrator(dest_modulestore, source_modulestore)
split_migrator.migrate_mongo_course(
source_course_id, user_id, dest_course_id.org, dest_course_id.course, dest_course_id.run, fields, **kwargs
)
# the super handles assets and any other necessities
super(MixedModuleStore, self).clone_course(source_course_id, dest_course_id, user_id, fields, **kwargs)
@strip_key
def create_item(self, user_id, course_key, block_type, block_id=None, fields=None, **kwargs):

View File

@@ -229,7 +229,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
# Convert the serialized fields values in self.cached_metadata
# to python values
metadata_to_inherit = self.cached_metadata.get(non_draft_loc.to_deprecated_string(), {})
metadata_to_inherit = self.cached_metadata.get(unicode(non_draft_loc), {})
inherit_metadata(module, metadata_to_inherit)
edit_info = json_data.get('edit_info')
@@ -238,10 +238,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
if not edit_info:
module.edited_by = module.edited_on = module.subtree_edited_on = \
module.subtree_edited_by = module.published_date = None
raw_metadata = json_data.get('metadata', {})
# published_date was previously stored as a list of time components instead of a datetime
if metadata.get('published_date'):
module.published_date = datetime(*metadata.get('published_date')[0:6]).replace(tzinfo=UTC)
module.published_by = metadata.get('published_by')
if raw_metadata.get('published_date'):
module.published_date = datetime(*raw_metadata.get('published_date')[0:6]).replace(tzinfo=UTC)
module.published_by = raw_metadata.get('published_by')
# otherwise restore the stored editing information
else:
module.edited_by = edit_info.get('edited_by')
@@ -267,7 +268,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
"""
Convert a single serialized UsageKey string in a ReferenceField into a UsageKey.
"""
key = Location.from_deprecated_string(ref_string)
key = Location.from_string(ref_string)
return key.replace(run=self.modulestore.fill_in_run(key.course_key).run)
def __setattr__(self, name, value):
@@ -280,22 +281,26 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
:param course_key: a CourseKey object for the given course
:param jsonfields: a dict of the jsonified version of the fields
"""
result = {}
for field_name, value in jsonfields.iteritems():
if value:
field = class_.fields.get(field_name)
if field is None:
continue
elif isinstance(field, Reference):
jsonfields[field_name] = self._convert_reference_to_key(value)
elif isinstance(field, ReferenceList):
jsonfields[field_name] = [
self._convert_reference_to_key(ele) for ele in value
]
elif isinstance(field, ReferenceValueDict):
for key, subvalue in value.iteritems():
assert isinstance(subvalue, basestring)
value[key] = self._convert_reference_to_key(subvalue)
return jsonfields
field = class_.fields.get(field_name)
if field is None:
continue
elif value is None:
result[field_name] = value
elif isinstance(field, Reference):
result[field_name] = self._convert_reference_to_key(value)
elif isinstance(field, ReferenceList):
result[field_name] = [
self._convert_reference_to_key(ele) for ele in value
]
elif isinstance(field, ReferenceValueDict):
result[field_name] = {
key: self._convert_reference_to_key(subvalue) for key, subvalue in value.iteritems()
}
else:
result[field_name] = value
return result
def lookup_item(self, location):
"""
@@ -522,7 +527,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
# manually pick it apart b/c the db has tag and we want as_published revision regardless
location = as_published(Location._from_deprecated_son(result['_id'], course_id.run))
location_url = location.to_deprecated_string()
location_url = unicode(location)
if location_url in results_by_url:
# found either draft or live to complement the other revision
existing_children = results_by_url[location_url].get('definition', {}).get('children', [])
@@ -1133,14 +1138,11 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
therefore propagate subtree edit info up the tree
"""
try:
definition_data = self._convert_reference_fields_to_strings(
xblock,
xblock.get_explicitly_set_fields_by_scope()
)
definition_data = self._serialize_scope(xblock, Scope.content)
now = datetime.now(UTC)
payload = {
'definition.data': definition_data,
'metadata': self._convert_reference_fields_to_strings(xblock, own_metadata(xblock)),
'metadata': self._serialize_scope(xblock, Scope.settings),
'edit_info.edited_on': now,
'edit_info.edited_by': user_id,
'edit_info.subtree_edited_on': now,
@@ -1152,7 +1154,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
payload['edit_info.published_by'] = user_id
if xblock.has_children:
children = self._convert_reference_fields_to_strings(xblock, {'children': xblock.children})
children = self._serialize_scope(xblock, Scope.children)
payload.update({'definition.children': children['children']})
self._update_single_item(xblock.scope_ids.usage_id, payload)
@@ -1193,25 +1195,27 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
return xblock
def _convert_reference_fields_to_strings(self, xblock, jsonfields):
def _serialize_scope(self, xblock, scope):
"""
Find all fields of type reference and convert the payload from UsageKeys to deprecated strings
:param xblock: the XBlock class
:param jsonfields: a dict of the jsonified version of the fields
"""
assert isinstance(jsonfields, dict)
for field_name, value in jsonfields.iteritems():
if value:
if isinstance(xblock.fields[field_name], Reference):
jsonfields[field_name] = value.to_deprecated_string()
elif isinstance(xblock.fields[field_name], ReferenceList):
jsonfields = {}
for field_name, field in xblock.fields.iteritems():
if (field.scope == scope and field.is_set_on(xblock)):
if isinstance(field, Reference):
jsonfields[field_name] = unicode(field.read_from(xblock))
elif isinstance(field, ReferenceList):
jsonfields[field_name] = [
ele.to_deprecated_string() for ele in value
unicode(ele) for ele in field.read_from(xblock)
]
elif isinstance(xblock.fields[field_name], ReferenceValueDict):
for key, subvalue in value.iteritems():
assert isinstance(subvalue, UsageKey)
value[key] = subvalue.to_deprecated_string()
elif isinstance(field, ReferenceValueDict):
jsonfields[field_name] = {
key: unicode(subvalue) for key, subvalue in field.read_from(xblock).iteritems()
}
else:
jsonfields[field_name] = field.read_json(xblock)
return jsonfields
def _get_raw_parent_location(self, location, revision=ModuleStoreEnum.RevisionOption.published_only):
@@ -1225,7 +1229,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
# create a query with tag, org, course, and the children field set to the given location
query = self._course_key_to_son(location.course_key)
query['definition.children'] = location.to_deprecated_string()
query['definition.children'] = unicode(location)
# if only looking for the PUBLISHED parent, set the revision in the query to None
if revision == ModuleStoreEnum.RevisionOption.published_only:
@@ -1300,7 +1304,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
if item['_id']['category'] != 'course':
# It would be nice to change this method to return UsageKeys instead of the deprecated string.
item_locs.add(
as_published(Location._from_deprecated_son(item['_id'], course_key.run)).to_deprecated_string()
unicode(as_published(Location._from_deprecated_son(item['_id'], course_key.run)))
)
all_reachable = all_reachable.union(item.get('definition', {}).get('children', []))
item_locs -= all_reachable

View File

@@ -60,14 +60,14 @@ def path_to_location(modulestore, usage_key):
# Found it!
path = (next_usage, path)
return flatten(path)
elif parent is None:
# Orphaned item.
return None
# otherwise, add parent locations at the end
newpath = (next_usage, path)
queue.append((parent, newpath))
# If we're here, there is no path
return None
if not modulestore.has_item(usage_key):
raise ItemNotFoundError(usage_key)
@@ -95,7 +95,9 @@ def path_to_location(modulestore, usage_key):
category = path[path_index].block_type
if category == 'sequential' or category == 'videosequence':
section_desc = modulestore.get_item(path[path_index])
child_locs = [c.location.version_agnostic() for c in section_desc.get_children()]
# this calls get_children rather than just children b/c old mongo includes private children
# in children but not in get_children
child_locs = [c.location for c in section_desc.get_children()]
# positions are 1-indexed, and should be strings to be consistent with
# url parsing.
position_list.append(str(child_locs.index(path[path_index + 1]) + 1))

View File

@@ -168,17 +168,16 @@ class SplitMigrator(object):
)
new_parent = self.split_modulestore.get_item(split_parent_loc, **kwargs)
# this only occurs if the parent was also awaiting adoption: skip this one, go to next
if any(new_locator == child.version_agnostic() for child in new_parent.children):
if any(new_locator.block_id == child.block_id for child in new_parent.children):
continue
# find index for module: new_parent may be missing quite a few of old_parent's children
new_parent_cursor = 0
for old_child_loc in old_parent.children:
if old_child_loc == draft_location:
if old_child_loc.block_id == draft_location.block_id:
break # moved cursor enough, insert it here
sibling_loc = new_draft_course_loc.make_usage_key(old_child_loc.category, old_child_loc.block_id)
# sibling may move cursor
for idx in range(new_parent_cursor, len(new_parent.children)):
if new_parent.children[idx].version_agnostic() == sibling_loc:
if new_parent.children[idx].block_id == old_child_loc.block_id:
new_parent_cursor = idx + 1
break # skipped sibs enough, pick back up scan
new_parent.children.insert(new_parent_cursor, new_locator)

View File

@@ -53,15 +53,18 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
self.default_class = default_class
self.local_modules = {}
def _load_item(self, block_id, course_entry_override=None, **kwargs):
if isinstance(block_id, BlockUsageLocator):
if isinstance(block_id.block_id, LocalId):
def _load_item(self, usage_key, course_entry_override=None, **kwargs):
# usage_key is either a UsageKey or just the block_id. if a usage_key,
if isinstance(usage_key, BlockUsageLocator):
if isinstance(usage_key.block_id, LocalId):
try:
return self.local_modules[block_id]
return self.local_modules[usage_key]
except KeyError:
raise ItemNotFoundError
else:
block_id = block_id.block_id
block_id = usage_key.block_id
else:
block_id = usage_key
json_data = self.module_data.get(block_id)
if json_data is None:
@@ -77,7 +80,12 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
raise ItemNotFoundError(block_id)
class_ = self.load_block_type(json_data.get('category'))
return self.xblock_from_json(class_, block_id, json_data, course_entry_override, **kwargs)
new_item = self.xblock_from_json(class_, block_id, json_data, course_entry_override, **kwargs)
if isinstance(usage_key, BlockUsageLocator):
# trust the passed in key to know the caller's expectations of which fields are filled in.
# particularly useful for strip_keys so may go away when we're version aware
new_item.location = usage_key
return new_item
# xblock's runtime does not always pass enough contextual information to figure out
# which named container (course x branch) or which parent is requesting an item. Because split allows
@@ -107,14 +115,15 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
if block_id is None:
block_id = LocalId()
block_course_key = CourseLocator(
version_guid=course_entry_override['structure']['_id'],
org=course_entry_override.get('org'),
course=course_entry_override.get('course'),
run=course_entry_override.get('run'),
branch=course_entry_override.get('branch'),
)
block_locator = BlockUsageLocator(
CourseLocator(
version_guid=course_entry_override['structure']['_id'],
org=course_entry_override.get('org'),
course=course_entry_override.get('course'),
run=course_entry_override.get('run'),
branch=course_entry_override.get('branch'),
),
block_course_key,
block_type=json_data.get('category'),
block_id=block_id,
)

View File

@@ -955,11 +955,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
In split, other than copying the assets, this is cheap as it merely creates a new version of the
existing course.
"""
super(SplitMongoModuleStore, self).clone_course(source_course_id, dest_course_id, user_id, fields, **kwargs)
source_index = self.get_course_index_info(source_course_id)
if source_index is None:
raise ItemNotFoundError("Cannot find a course at {0}. Aborting".format(source_course_id))
return self.create_course(
new_course = self.create_course(
dest_course_id.org, dest_course_id.course, dest_course_id.run,
user_id,
fields=fields,
@@ -968,6 +967,9 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
skip_auto_publish=True,
**kwargs
)
# don't copy assets until we create the course in case something's awry
super(SplitMongoModuleStore, self).clone_course(source_course_id, dest_course_id, user_id, fields, **kwargs)
return new_course
DEFAULT_ROOT_BLOCK_ID = 'course'
def create_course(
@@ -1501,17 +1503,20 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
original_structure = self._lookup_course(usage_locator.course_key)['structure']
if original_structure['root'] == usage_locator.block_id:
raise ValueError("Cannot delete the root of a course")
if encode_key_for_mongo(usage_locator.block_id) not in original_structure['blocks']:
raise ValueError("Cannot delete a block that does not exist")
index_entry = self._get_index_if_valid(usage_locator, force)
new_structure = self._version_structure(original_structure, user_id)
new_blocks = new_structure['blocks']
new_id = new_structure['_id']
encoded_block_id = self._get_parent_from_structure(usage_locator.block_id, original_structure)
parent_block = new_blocks[encoded_block_id]
parent_block['fields']['children'].remove(usage_locator.block_id)
parent_block['edit_info']['edited_on'] = datetime.datetime.now(UTC)
parent_block['edit_info']['edited_by'] = user_id
parent_block['edit_info']['previous_version'] = parent_block['edit_info']['update_version']
parent_block['edit_info']['update_version'] = new_id
if encoded_block_id:
parent_block = new_blocks[encoded_block_id]
parent_block['fields']['children'].remove(usage_locator.block_id)
parent_block['edit_info']['edited_on'] = datetime.datetime.now(UTC)
parent_block['edit_info']['edited_by'] = user_id
parent_block['edit_info']['previous_version'] = parent_block['edit_info']['update_version']
parent_block['edit_info']['update_version'] = new_id
def remove_subtree(block_id):
"""
@@ -1797,7 +1802,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
xblock_class = self.mixologist.mix(xblock_class)
for field_name, value in fields.iteritems():
if value:
if value is not None:
if isinstance(xblock_class.fields[field_name], Reference):
fields[field_name] = value.block_id
elif isinstance(xblock_class.fields[field_name], ReferenceList):

View File

@@ -52,6 +52,15 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
course_id = self._map_revision_to_branch(course_id)
return super(DraftVersioningModuleStore, self).get_course(course_id, depth=depth, **kwargs)
def clone_course(self, source_course_id, dest_course_id, user_id, fields=None, revision=None, **kwargs):
"""
See :py:meth: xmodule.modulestore.split_mongo.split.SplitMongoModuleStore.clone_course
"""
dest_course_id = self._map_revision_to_branch(dest_course_id, revision=revision)
return super(DraftVersioningModuleStore, self).clone_course(
source_course_id, dest_course_id, user_id, fields=fields, **kwargs
)
def get_courses(self, **kwargs):
"""
Returns all the courses on the Draft or Published branch depending on the branch setting.
@@ -76,6 +85,7 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
self.publish(location.version_agnostic(), user_id, blacklist=EXCLUDE_ALL, **kwargs)
def update_item(self, descriptor, user_id, allow_not_found=False, force=False, **kwargs):
descriptor.location = self._map_revision_to_branch(descriptor.location)
item = super(DraftVersioningModuleStore, self).update_item(
descriptor,
user_id,

View File

@@ -2,7 +2,6 @@
"""
Modulestore configuration for test cases.
"""
from uuid import uuid4
from django.test import TestCase
from django.contrib.auth.models import User
@@ -13,38 +12,48 @@ import datetime
import pytz
from xmodule.tabs import CoursewareTab, CourseInfoTab, StaticTab, DiscussionTab, ProgressTab, WikiTab
from xmodule.modulestore.tests.sample_courses import default_block_info_tree, TOY_BLOCK_INFO_TREE
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
def mixed_store_config(data_dir, mappings):
def mixed_store_config(data_dir, mappings, include_xml=True):
"""
Return a `MixedModuleStore` configuration, which provides
access to both Mongo- and XML-backed courses.
`data_dir` is the directory from which to load XML-backed courses.
`mappings` is a dictionary mapping course IDs to modulestores, for example:
Args:
data_dir (string): the directory from which to load XML-backed courses.
mappings (string): a dictionary mapping course IDs to modulestores, for example:
{
'MITx/2.01x/2013_Spring': 'xml',
'edx/999/2013_Spring': 'default'
}
{
'MITx/2.01x/2013_Spring': 'xml',
'edx/999/2013_Spring': 'default'
}
where 'xml' and 'default' are the two options provided by this configuration,
mapping (respectively) to XML-backed and Mongo-backed modulestores..
Keyword Args:
include_xml (boolean): If True, include an XML modulestore in the configuration.
Note that this will require importing multiple XML courses from disk,
so unless your tests really needs XML course fixtures or is explicitly
testing mixed modulestore, set this to False.
where 'xml' and 'default' are the two options provided by this configuration,
mapping (respectively) to XML-backed and Mongo-backed modulestores..
"""
draft_mongo_config = draft_mongo_store_config(data_dir)
xml_config = xml_store_config(data_dir)
split_mongo = split_mongo_store_config(data_dir)
stores = [
draft_mongo_store_config(data_dir)['default'],
split_mongo_store_config(data_dir)['default']
]
if include_xml:
stores.append(xml_store_config(data_dir)['default'])
store = {
'default': {
'ENGINE': 'xmodule.modulestore.mixed.MixedModuleStore',
'OPTIONS': {
'mappings': mappings,
'stores': [
draft_mongo_config['default'],
split_mongo['default'],
xml_config['default'],
]
'stores': stores,
}
}
}
@@ -67,7 +76,8 @@ def draft_mongo_store_config(data_dir):
'NAME': 'draft',
'ENGINE': 'xmodule.modulestore.mongo.draft.DraftModuleStore',
'DOC_STORE_CONFIG': {
'host': 'localhost',
'host': MONGO_HOST,
'port': MONGO_PORT_NUM,
'db': 'test_xmodule',
'collection': 'modulestore{0}'.format(uuid4().hex[:5]),
},
@@ -93,7 +103,8 @@ def split_mongo_store_config(data_dir):
'NAME': 'draft',
'ENGINE': 'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore',
'DOC_STORE_CONFIG': {
'host': 'localhost',
'host': MONGO_HOST,
'port': MONGO_PORT_NUM,
'db': 'test_xmodule',
'collection': 'modulestore{0}'.format(uuid4().hex[:5]),
},
@@ -229,6 +240,8 @@ class ModuleStoreTestCase(TestCase):
if hasattr(module_store, '_drop_database'):
module_store._drop_database() # pylint: disable=protected-access
_CONTENTSTORE.clear()
if hasattr(module_store, 'close_connections'):
module_store.close_connections()
@classmethod
def setUpClass(cls):

View File

@@ -0,0 +1,10 @@
"""
This file is intended to provide settings for the mongodb connection used for tests.
The settings can be provided by environment variables in the shell running the tests. This reads
in a variety of environment variables but provides sensible defaults in case those env var
overrides don't exist
"""
import os
MONGO_PORT_NUM = int(os.environ.get('EDXAPP_TEST_MONGO_PORT', '27017'))
MONGO_HOST = os.environ.get('EDXAPP_TEST_MONGO_HOST', 'localhost')

View File

@@ -1,6 +1,7 @@
"""
Test contentstore.mongo functionality
"""
import os
import logging
from uuid import uuid4
import unittest
@@ -17,12 +18,12 @@ from xmodule.contentstore.content import StaticContent
from xmodule.exceptions import NotFoundError
import ddt
from __builtin__ import delattr
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
log = logging.getLogger(__name__)
HOST = 'localhost'
PORT = 27017
HOST = MONGO_HOST
PORT = MONGO_PORT_NUM
DB = 'test_mongo_%s' % uuid4().hex[:5]

View File

@@ -11,7 +11,6 @@ and then for each combination of modulestores, performing the sequence:
4) Compare all modules in the source and destination modulestores to make sure that they line up
"""
import ddt
import itertools
import random
@@ -28,9 +27,12 @@ from xmodule.contentstore.mongo import MongoContentStore
from xmodule.modulestore.xml_importer import import_from_xml
from xmodule.modulestore.xml_exporter import export_to_xml
from xmodule.modulestore.split_mongo.split_draft import DraftVersioningModuleStore
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
COMMON_DOCSTORE_CONFIG = {
'host': 'localhost'
'host': MONGO_HOST,
'port': MONGO_PORT_NUM,
}
@@ -221,7 +223,7 @@ class MongoContentstoreBuilder(object):
MODULESTORE_SETUPS = (
MongoModulestoreBuilder(),
VersioningModulestoreBuilder(),
# VersioningModulestoreBuilder(), # FIXME LMS-11227
MixedModulestoreBuilder([('draft', MongoModulestoreBuilder())]),
MixedModulestoreBuilder([('split', VersioningModulestoreBuilder())]),
)
@@ -229,6 +231,8 @@ CONTENTSTORE_SETUPS = (MongoContentstoreBuilder(),)
COURSE_DATA_NAMES = (
'toy',
'manual-testing-complete',
'split_test_module',
'split_test_module_draft',
)

View File

@@ -15,17 +15,20 @@ from xmodule.exceptions import InvalidVersionError
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
# Mixed modulestore depends on django, so we'll manually configure some django settings
# before importing the module
# TODO remove this import and the configuration -- xmodule should not depend on django!
from django.conf import settings
from xmodule.modulestore.tests.factories import check_mongo_calls
from xmodule.modulestore.search import path_to_location
from xmodule.modulestore.exceptions import DuplicateCourseError
from xmodule.modulestore.exceptions import DuplicateCourseError, NoPathToItem
if not settings.configured:
settings.configure()
from xmodule.modulestore.mixed import MixedModuleStore
from xmodule.modulestore.draft_and_published import UnsupportedRevisionError
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
@ddt.ddt
@@ -34,8 +37,8 @@ class TestMixedModuleStore(unittest.TestCase):
Quasi-superclass which tests Location based apps against both split and mongo dbs (Locator and
Location-based dbs)
"""
HOST = 'localhost'
PORT = 27017
HOST = MONGO_HOST
PORT = MONGO_PORT_NUM
DB = 'test_mongo_%s' % uuid4().hex[:5]
COLLECTION = 'modulestore'
FS_ROOT = DATA_DIR
@@ -54,6 +57,7 @@ class TestMixedModuleStore(unittest.TestCase):
}
DOC_STORE_CONFIG = {
'host': HOST,
'port': PORT,
'db': DB,
'collection': COLLECTION,
}
@@ -469,7 +473,6 @@ class TestMixedModuleStore(unittest.TestCase):
)
# verify pre delete state (just to verify that the test is valid)
self.assertTrue(hasattr(private_vert, 'is_draft') or private_vert.location.branch == ModuleStoreEnum.BranchName.draft)
if hasattr(private_vert.location, 'version_guid'):
# change to the HEAD version
vert_loc = private_vert.location.for_version(private_leaf.location.version_guid)
@@ -696,20 +699,22 @@ class TestMixedModuleStore(unittest.TestCase):
Make sure that path_to_location works
"""
self.initdb(default_ms)
self._create_block_hierarchy()
course_key = self.course_locations[self.MONGO_COURSEID].course_key
should_work = (
(self.problem_x1a_2,
(course_key, u"Chapter_x", u"Sequential_x1", '1')),
(self.chapter_x,
(course_key, "Chapter_x", None, None)),
)
with self.store.branch_setting(ModuleStoreEnum.Branch.published_only, course_key):
self._create_block_hierarchy()
mongo_store = self.store._get_modulestore_for_courseid(self._course_key_from_string(self.MONGO_COURSEID))
for location, expected in should_work:
with check_mongo_calls(mongo_store, num_finds.pop(0), num_sends):
self.assertEqual(path_to_location(self.store, location), expected)
should_work = (
(self.problem_x1a_2,
(course_key, u"Chapter_x", u"Sequential_x1", '1')),
(self.chapter_x,
(course_key, "Chapter_x", None, None)),
)
mongo_store = self.store._get_modulestore_for_courseid(self._course_key_from_string(self.MONGO_COURSEID))
for location, expected in should_work:
with check_mongo_calls(mongo_store, num_finds.pop(0), num_sends):
self.assertEqual(path_to_location(self.store, location), expected)
not_found = (
course_key.make_usage_key('video', 'WelcomeX'),
@@ -719,6 +724,18 @@ class TestMixedModuleStore(unittest.TestCase):
with self.assertRaises(ItemNotFoundError):
path_to_location(self.store, location)
# Orphaned items should not be found.
orphan = course_key.make_usage_key('chapter', 'OrphanChapter')
self.store.create_item(
self.user_id,
orphan.course_key,
orphan.block_type,
block_id=orphan.block_id
)
with self.assertRaises(NoPathToItem):
path_to_location(self.store, orphan)
def test_xml_path_to_location(self):
"""
Make sure that path_to_location works: should be passed a modulestore

View File

@@ -36,12 +36,12 @@ from xmodule.exceptions import NotFoundError
from git.test.lib.asserts import assert_not_none
from xmodule.x_module import XModuleMixin
from xmodule.modulestore.mongo.base import as_draft
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
log = logging.getLogger(__name__)
HOST = 'localhost'
PORT = 27017
HOST = MONGO_HOST
PORT = MONGO_PORT_NUM
DB = 'test_mongo_%s' % uuid4().hex[:5]
COLLECTION = 'modulestore'
FS_ROOT = DATA_DIR # TODO (vshnayder): will need a real fs_root for testing load_item
@@ -91,12 +91,13 @@ class TestMongoModuleStore(unittest.TestCase):
# connect to the db
doc_store_config = {
'host': HOST,
'port': PORT,
'db': DB,
'collection': COLLECTION,
}
# since MongoModuleStore and MongoContentStore are basically assumed to be together, create this class
# as well
content_store = MongoContentStore(HOST, DB)
content_store = MongoContentStore(HOST, DB, port=PORT)
#
# Also test draft store imports
#
@@ -148,7 +149,7 @@ class TestMongoModuleStore(unittest.TestCase):
def test_mongo_modulestore_type(self):
store = DraftModuleStore(
None,
{'host': HOST, 'db': DB, 'collection': COLLECTION},
{'host': HOST, 'db': DB, 'port': PORT, 'collection': COLLECTION},
FS_ROOT, RENDER_TEMPLATE, default_class=DEFAULT_CLASS
)
assert_equals(store.get_modulestore_type(''), ModuleStoreEnum.Type.mongo)
@@ -156,7 +157,7 @@ class TestMongoModuleStore(unittest.TestCase):
def test_get_courses(self):
'''Make sure the course objects loaded properly'''
courses = self.draft_store.get_courses()
assert_equals(len(courses), 5)
assert_equals(len(courses), 6)
course_ids = [course.id for course in courses]
for course_key in [
@@ -831,6 +832,55 @@ class TestMongoModuleStore(unittest.TestCase):
self.assertEqual(component.published_date, published_date)
self.assertEqual(component.published_by, published_by)
def test_export_course_with_peer_component(self):
"""
Test export course when link_to_location is given in peer grading interface settings.
"""
name = "export_peer_component"
locations = self._create_test_tree(name)
# Insert the test block directly into the module store
problem_location = Location('edX', 'tree{}'.format(name), name, 'combinedopenended', 'test_peer_problem')
self.draft_store.create_child(
self.dummy_user,
locations["child"],
problem_location.block_type,
block_id=problem_location.block_id
)
interface_location = Location('edX', 'tree{}'.format(name), name, 'peergrading', 'test_peer_interface')
self.draft_store.create_child(
self.dummy_user,
locations["child"],
interface_location.block_type,
block_id=interface_location.block_id
)
self.draft_store._update_single_item(
as_draft(interface_location),
{
'definition.data': {},
'metadata': {
'link_to_location': unicode(problem_location),
'use_for_single_location': True,
},
},
)
component = self.draft_store.get_item(interface_location)
self.assertEqual(unicode(component.link_to_location), unicode(problem_location))
root_dir = path(mkdtemp())
# export_to_xml should work.
try:
export_to_xml(self.draft_store, self.content_store, interface_location.course_key, root_dir, 'test_export')
finally:
shutil.rmtree(root_dir)
class TestMongoKeyValueStore(object):

View File

@@ -22,6 +22,7 @@ from xmodule.x_module import XModuleMixin
from xmodule.fields import Date, Timedelta
from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
from xmodule.modulestore.tests.test_modulestore import check_has_course_method
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
BRANCH_NAME_DRAFT = ModuleStoreEnum.BranchName.draft
@@ -36,8 +37,9 @@ class SplitModuleTest(unittest.TestCase):
'''
# Snippets of what would be in the django settings envs file
DOC_STORE_CONFIG = {
'host': 'localhost',
'host': MONGO_HOST,
'db': 'test_xmodule',
'port': MONGO_PORT_NUM,
'collection': 'modulestore{0}'.format(uuid.uuid4().hex[:5]),
}
modulestore_options = {
@@ -1330,6 +1332,8 @@ class TestItemCrud(SplitModuleTest):
self.assertFalse(modulestore().has_item(deleted))
with self.assertRaises(VersionConflictError):
modulestore().has_item(locn_to_del)
with self.assertRaises(ValueError):
modulestore().delete_item(deleted, self.user_id)
self.assertTrue(modulestore().has_item(locn_to_del.course_agnostic()))
self.assertNotEqual(new_course_loc.version_guid, course.location.version_guid)

View File

@@ -9,6 +9,7 @@ from opaque_keys.edx.locator import CourseLocator, BlockUsageLocator
from xmodule.modulestore.split_mongo.split import SplitMongoModuleStore
from xmodule.modulestore.mongo import DraftMongoModuleStore
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
class SplitWMongoCourseBoostrapper(unittest.TestCase):
@@ -27,7 +28,8 @@ class SplitWMongoCourseBoostrapper(unittest.TestCase):
"""
# Snippet of what would be in the django settings envs file
db_config = {
'host': 'localhost',
'host': MONGO_HOST,
'port': MONGO_PORT_NUM,
'db': 'test_xmodule',
}

View File

@@ -10,6 +10,7 @@ from opaque_keys.edx.locations import Location
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.modulestore.xml_importer import _import_module_and_update_references
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.tests import DATA_DIR
from uuid import uuid4
@@ -21,8 +22,8 @@ class ModuleStoreNoSettings(unittest.TestCase):
"""
A mixin to create a mongo modulestore that avoids settings
"""
HOST = 'localhost'
PORT = 27017
HOST = MONGO_HOST
PORT = MONGO_PORT_NUM
DB = 'test_mongo_%s' % uuid4().hex[:5]
COLLECTION = 'modulestore'
FS_ROOT = DATA_DIR
@@ -36,6 +37,7 @@ class ModuleStoreNoSettings(unittest.TestCase):
}
DOC_STORE_CONFIG = {
'host': HOST,
'port': PORT,
'db': DB,
'collection': COLLECTION,
}

View File

@@ -4,7 +4,7 @@ Methods for exporting course data to XML
import logging
import lxml.etree
from xblock.fields import Scope
from xblock.fields import Scope, Reference, ReferenceList, ReferenceValueDict
from xmodule.contentstore.content import StaticContent
from xmodule.exceptions import NotFoundError
from xmodule.modulestore import EdxJSONEncoder, ModuleStoreEnum
@@ -16,6 +16,7 @@ import os
from path import path
import shutil
from xmodule.modulestore.draft_and_published import DIRECT_ONLY_CATEGORIES
from opaque_keys.edx.locator import CourseLocator
DRAFT_DIR = "drafts"
PUBLISHED_DIR = "published"
@@ -36,8 +37,7 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
`course_dir`: The name of the directory inside `root_dir` to write the course content to
"""
course = modulestore.get_course(course_key)
course = modulestore.get_course(course_key, depth=None) # None means infinite
fsm = OSFS(root_dir)
export_fs = course.runtime.export_fs = fsm.makeopendir(course_dir)
@@ -45,6 +45,10 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
# export only the published content
with modulestore.branch_setting(ModuleStoreEnum.Branch.published_only, course_key):
# change all of the references inside the course to use the xml expected key type w/o version & branch
xml_centric_course_key = CourseLocator(course_key.org, course_key.course, course_key.run, deprecated=True)
adapt_references(course, xml_centric_course_key, export_fs)
course.add_xml_to_node(root)
with export_fs.open('course.xml', 'w') as course_xml:
@@ -79,16 +83,16 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
course_image_file.write(course_image.data)
# export the static tabs
export_extra_content(export_fs, modulestore, course_key, 'static_tab', 'tabs', '.html')
export_extra_content(export_fs, modulestore, xml_centric_course_key, 'static_tab', 'tabs', '.html')
# export the custom tags
export_extra_content(export_fs, modulestore, course_key, 'custom_tag_template', 'custom_tags')
export_extra_content(export_fs, modulestore, xml_centric_course_key, 'custom_tag_template', 'custom_tags')
# export the course updates
export_extra_content(export_fs, modulestore, course_key, 'course_info', 'info', '.html')
export_extra_content(export_fs, modulestore, xml_centric_course_key, 'course_info', 'info', '.html')
# export the 'about' data (e.g. overview, etc.)
export_extra_content(export_fs, modulestore, course_key, 'about', 'about', '.html')
export_extra_content(export_fs, modulestore, xml_centric_course_key, 'about', 'about', '.html')
# export the grading policy
course_run_policy_dir = policies_dir.makeopendir(course.location.name)
@@ -100,33 +104,67 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
policy = {'course/' + course.location.name: own_metadata(course)}
course_policy.write(dumps(policy, cls=EdxJSONEncoder))
# NOTE: this code assumes that verticals are the top most draftable container
# should we change the application, then this assumption will no longer be valid
# NOTE: we need to explicitly implement the logic for setting the vertical's parent
# and index here since the XML modulestore cannot load draft modules
draft_verticals = modulestore.get_items(
course_key,
qualifiers={'category': 'vertical'},
revision=ModuleStoreEnum.RevisionOption.draft_only
)
if len(draft_verticals) > 0:
draft_course_dir = export_fs.makeopendir(DRAFT_DIR)
for draft_vertical in draft_verticals:
parent_loc = modulestore.get_parent_location(
draft_vertical.location,
revision=ModuleStoreEnum.RevisionOption.draft_preferred
#### DRAFTS ####
# xml backed courses don't support drafts!
if course.runtime.modulestore.get_modulestore_type() != ModuleStoreEnum.Type.xml:
# NOTE: this code assumes that verticals are the top most draftable container
# should we change the application, then this assumption will no longer be valid
# NOTE: we need to explicitly implement the logic for setting the vertical's parent
# and index here since the XML modulestore cannot load draft modules
with modulestore.branch_setting(ModuleStoreEnum.Branch.draft_preferred, course_key):
draft_verticals = modulestore.get_items(
course_key,
qualifiers={'category': 'vertical'},
revision=ModuleStoreEnum.RevisionOption.draft_only
)
# Don't try to export orphaned items.
if parent_loc is not None:
logging.debug('parent_loc = {0}'.format(parent_loc))
if parent_loc.category in DIRECT_ONLY_CATEGORIES:
draft_vertical.xml_attributes['parent_sequential_url'] = parent_loc.to_deprecated_string()
sequential = modulestore.get_item(parent_loc)
index = sequential.children.index(draft_vertical.location)
draft_vertical.xml_attributes['index_in_children_list'] = str(index)
draft_vertical.runtime.export_fs = draft_course_dir
node = lxml.etree.Element('unknown')
draft_vertical.add_xml_to_node(node)
if len(draft_verticals) > 0:
draft_course_dir = export_fs.makeopendir(DRAFT_DIR)
for draft_vertical in draft_verticals:
parent_loc = modulestore.get_parent_location(
draft_vertical.location,
revision=ModuleStoreEnum.RevisionOption.draft_preferred
)
# Don't try to export orphaned items.
if parent_loc is not None:
logging.debug('parent_loc = {0}'.format(parent_loc))
if parent_loc.category in DIRECT_ONLY_CATEGORIES:
draft_vertical.xml_attributes['parent_sequential_url'] = parent_loc.to_deprecated_string()
sequential = modulestore.get_item(parent_loc)
index = sequential.children.index(draft_vertical.location)
draft_vertical.xml_attributes['index_in_children_list'] = str(index)
draft_vertical.runtime.export_fs = draft_course_dir
adapt_references(draft_vertical, xml_centric_course_key, draft_course_dir)
node = lxml.etree.Element('unknown')
draft_vertical.add_xml_to_node(node)
def adapt_references(subtree, destination_course_key, export_fs):
"""
Map every reference in the subtree into destination_course_key and set it back into the xblock fields
"""
subtree.runtime.export_fs = export_fs # ensure everything knows where it's going!
for field_name, field in subtree.fields.iteritems():
if field.is_set_on(subtree):
if isinstance(field, Reference):
value = field.read_from(subtree)
if value is not None:
field.write_to(subtree, field.read_from(subtree).map_into_course(destination_course_key))
elif field_name == 'children':
# don't change the children field but do recurse over the children
[adapt_references(child, destination_course_key, export_fs) for child in subtree.get_children()]
elif isinstance(field, ReferenceList):
field.write_to(
subtree,
[ele.map_into_course(destination_course_key) for ele in field.read_from(subtree)]
)
elif isinstance(field, ReferenceValueDict):
field.write_to(
subtree, {
key: ele.map_into_course(destination_course_key) for key, ele in field.read_from(subtree).iteritems()
}
)
def _export_field_content(xblock_item, item_dir):
@@ -149,6 +187,7 @@ def export_extra_content(export_fs, modulestore, course_key, category_type, dirn
if len(items) > 0:
item_dir = export_fs.makeopendir(dirname)
for item in items:
adapt_references(item, course_key, export_fs)
with item_dir.open(item.location.name + file_suffix, 'w') as item_file:
item_file.write(item.data.encode('utf8'))

View File

@@ -431,7 +431,7 @@ def _import_module_and_update_references(
fields[field_name] = {
key: _convert_reference_fields_to_new_namespace(reference)
for key, reference
in reference_dict.items()
in reference_dict.iteritems()
}
elif field_name == 'xml_attributes':
value = field.read_from(module)

View File

@@ -18,7 +18,7 @@ function ABTestSelector(runtime, elem) {
var child_group_id = $(this).data('group-id').toString();
if(child_group_id === group_id) {
_this.content_container.html($(this).text());
XBlock.initializeBlocks(_this.content_container);
XBlock.initializeBlocks(_this.content_container, $(elem).data('request-token'));
}
});
}

View File

@@ -2,7 +2,6 @@ from lxml import etree
from xmodule.editing_module import XMLEditingDescriptor
from xmodule.xml_module import XmlDescriptor
import logging
import sys
from xblock.fields import String, Scope
from exceptions import SerializationError

View File

@@ -6,6 +6,7 @@ import logging
import json
from webob import Response
from uuid import uuid4
from operator import itemgetter
from xmodule.progress import Progress
from xmodule.seq_module import SequenceDescriptor
@@ -24,6 +25,8 @@ log = logging.getLogger('edx.' + __name__)
# Make '_' a no-op so we can scrape strings
_ = lambda text: text
DEFAULT_GROUP_NAME = _(u'Group ID {group_id}')
class ValidationMessageType(object):
"""
@@ -233,24 +236,41 @@ class SplitTestModule(SplitTestFields, XModule, StudioEditableModule):
Render the staff view for a split test module.
"""
fragment = Fragment()
contents = []
active_contents = []
inactive_contents = []
for group_id in self.group_id_to_child:
child_location = self.group_id_to_child[group_id]
for child_location in self.children: # pylint: disable=no-member
child_descriptor = self.get_child_descriptor_by_location(child_location)
child = self.system.get_module(child_descriptor)
rendered_child = child.render(STUDENT_VIEW, context)
fragment.add_frag_resources(rendered_child)
group_name, updated_group_id = self.get_data_for_vertical(child)
contents.append({
'group_id': group_id,
if updated_group_id is None: # inactive group
group_name = child.display_name
updated_group_id = [g_id for g_id, loc in self.group_id_to_child.items() if loc == child_location][0]
inactive_contents.append({
'group_name': _(u'{group_name} (inactive)').format(group_name=group_name),
'id': child.location.to_deprecated_string(),
'content': rendered_child.content,
'group_id': updated_group_id,
})
continue
active_contents.append({
'group_name': group_name,
'id': child.location.to_deprecated_string(),
'content': rendered_child.content
'content': rendered_child.content,
'group_id': updated_group_id,
})
# Sort active and inactive contents by group name.
sorted_active_contents = sorted(active_contents, key=itemgetter('group_name'))
sorted_inactive_contents = sorted(inactive_contents, key=itemgetter('group_name'))
# Use the new template
fragment.add_content(self.system.render_template('split_test_staff_view.html', {
'items': contents,
'items': sorted_active_contents + sorted_inactive_contents,
}))
fragment.add_css('.split-test-child { display: none; }')
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/split_test_staff.js'))
@@ -299,8 +319,16 @@ class SplitTestModule(SplitTestFields, XModule, StudioEditableModule):
for active_child_descriptor in children:
active_child = self.system.get_module(active_child_descriptor)
rendered_child = active_child.render(StudioEditableModule.get_preview_view_name(active_child), context)
if active_child.category == 'vertical':
group_name, group_id = self.get_data_for_vertical(active_child)
if group_name:
rendered_child.content = rendered_child.content.replace(
DEFAULT_GROUP_NAME.format(group_id=group_id),
group_name
)
fragment.add_frag_resources(rendered_child)
html = html + rendered_child.content
return html
def student_view(self, context):
@@ -343,6 +371,19 @@ class SplitTestModule(SplitTestFields, XModule, StudioEditableModule):
progress = reduce(Progress.add_counts, progresses, None)
return progress
def get_data_for_vertical(self, vertical):
"""
Return name and id of a group corresponding to `vertical`.
"""
user_partition = self.descriptor.get_selected_partition()
if user_partition:
for group in user_partition.groups:
group_id = unicode(group.id)
child_location = self.group_id_to_child.get(group_id, None)
if child_location == vertical.location:
return (group.name, group.id)
return (None, None)
@XBlock.needs('user_tags') # pylint: disable=abstract-method
@XBlock.wants('partitions')
@@ -595,7 +636,7 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor, StudioEditableDes
"editor_saved should only be called when a mutable modulestore is available"
modulestore = self.system.modulestore
dest_usage_key = self.location.replace(category="vertical", name=uuid4().hex)
metadata = {'display_name': group.name}
metadata = {'display_name': DEFAULT_GROUP_NAME.format(group_id=group.id)}
modulestore.create_item(
user_id,
self.location.course_key,

View File

@@ -16,7 +16,7 @@ from mock import Mock
from path import path
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds, Scope
from xblock.fields import ScopeIds, Scope, Reference, ReferenceList, ReferenceValueDict
from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin
from xmodule.modulestore.inheritance import InheritanceMixin, own_metadata
@@ -159,6 +159,21 @@ class LogicTest(unittest.TestCase):
return json.loads(self.xmodule.handle_ajax(dispatch, data))
def map_references(value, field, actual_course_key):
"""
Map the references in value to actual_course_key and return value
"""
if not value: # if falsey
return value
if isinstance(field, Reference):
return value.map_into_course(actual_course_key)
if isinstance(field, ReferenceList):
return [sub.map_into_course(actual_course_key) for sub in value]
if isinstance(field, ReferenceValueDict):
return {key: ele.map_into_course(actual_course_key) for key, ele in value.iteritems()}
return value
class CourseComparisonTest(unittest.TestCase):
"""
Mixin that has methods for comparing courses for equality.
@@ -197,23 +212,27 @@ class CourseComparisonTest(unittest.TestCase):
will be ignored for the purpose of equality checking.
"""
# compare published
expected_items = expected_store.get_items(expected_course_key, revision=ModuleStoreEnum.RevisionOption.published_only)
actual_items = actual_store.get_items(actual_course_key, revision=ModuleStoreEnum.RevisionOption.published_only)
self.assertGreater(len(expected_items), 0)
self._assertCoursesEqual(expected_items, actual_items, actual_course_key)
with expected_store.branch_setting(ModuleStoreEnum.Branch.published_only, expected_course_key):
with actual_store.branch_setting(ModuleStoreEnum.Branch.published_only, actual_course_key):
expected_items = expected_store.get_items(expected_course_key, revision=ModuleStoreEnum.RevisionOption.published_only)
actual_items = actual_store.get_items(actual_course_key, revision=ModuleStoreEnum.RevisionOption.published_only)
self.assertGreater(len(expected_items), 0)
self._assertCoursesEqual(expected_items, actual_items, actual_course_key)
# compare draft
if expected_store.get_modulestore_type(None) == ModuleStoreEnum.Type.split:
revision = ModuleStoreEnum.RevisionOption.draft_only
else:
revision = None
expected_items = expected_store.get_items(expected_course_key, revision=revision)
if actual_store.get_modulestore_type(None) == ModuleStoreEnum.Type.split:
revision = ModuleStoreEnum.RevisionOption.draft_only
else:
revision = None
actual_items = actual_store.get_items(actual_course_key, revision=revision)
self._assertCoursesEqual(expected_items, actual_items, actual_course_key, expect_drafts=True)
with expected_store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, expected_course_key):
with actual_store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, actual_course_key):
# compare draft
if expected_store.get_modulestore_type(None) == ModuleStoreEnum.Type.split:
revision = ModuleStoreEnum.RevisionOption.draft_only
else:
revision = None
expected_items = expected_store.get_items(expected_course_key, revision=revision)
if actual_store.get_modulestore_type(None) == ModuleStoreEnum.Type.split:
revision = ModuleStoreEnum.RevisionOption.draft_only
else:
revision = None
actual_items = actual_store.get_items(actual_course_key, revision=revision)
self._assertCoursesEqual(expected_items, actual_items, actual_course_key, expect_drafts=True)
def _assertCoursesEqual(self, expected_items, actual_items, actual_course_key, expect_drafts=False):
self.assertEqual(len(expected_items), len(actual_items))
@@ -239,7 +258,7 @@ class CourseComparisonTest(unittest.TestCase):
# compare fields
self.assertEqual(expected_item.fields, actual_item.fields)
for field_name in expected_item.fields:
for field_name, field in expected_item.fields.iteritems():
if (expected_item.scope_ids.usage_id, field_name) in self.field_exclusions:
continue
@@ -250,8 +269,8 @@ class CourseComparisonTest(unittest.TestCase):
if field_name == 'children':
continue
exp_value = getattr(expected_item, field_name)
actual_value = getattr(actual_item, field_name)
exp_value = map_references(field.read_from(expected_item), field, actual_course_key)
actual_value = field.read_from(actual_item)
self.assertEqual(
exp_value,
actual_value,
@@ -267,18 +286,15 @@ class CourseComparisonTest(unittest.TestCase):
# compare children
self.assertEqual(expected_item.has_children, actual_item.has_children)
if expected_item.has_children:
actual_course_key = actual_item.location.course_key.version_agnostic()
expected_children = [
course1_item_child.location.map_into_course(actual_course_key)
(course1_item_child.location.block_type, course1_item_child.location.block_id)
# get_children() rather than children to strip privates from public parents
for course1_item_child in expected_item.get_children()
# get_children was returning drafts for published parents :-(
if expect_drafts or not getattr(course1_item_child, 'is_draft', False)
]
actual_children = [
item_child.location.version_agnostic()
(item_child.location.block_type, item_child.location.block_id)
# get_children() rather than children to strip privates from public parents
for item_child in actual_item.get_children()
# get_children was returning drafts for published parents :-(
if expect_drafts or not getattr(item_child, 'is_draft', False)
]
self.assertEqual(expected_children, actual_children)

View File

@@ -1,8 +1,47 @@
import unittest
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.content import StaticContent, StaticContentStream
from xmodule.contentstore.content import ContentStore
from opaque_keys.edx.locations import SlashSeparatedCourseKey, AssetLocation
SAMPLE_STRING = """
This is a sample string with more than 1024 bytes, the default STREAM_DATA_CHUNK_SIZE
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,
when an unknown printer took a galley of type and scrambled it to make a type
specimen book. It has survived not only five centuries, but also the leap into
electronic typesetting, remaining essentially unchanged. It was popularised in
the 1960s with the release of Letraset sheets containing Lorem Ipsum passages,
nd more recently with desktop publishing software like Aldus PageMaker including
versions of Lorem Ipsum.
It is a long established fact that a reader will be distracted by the readable
content of a page when looking at its layout. The point of using Lorem Ipsum is
that it has a more-or-less normal distribution of letters, as opposed to using
'Content here, content here', making it look like readable English. Many desktop
ublishing packages and web page editors now use Lorem Ipsum as their default model
text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy.
Various versions have evolved over the years, sometimes by accident, sometimes on purpose
injected humour and the like).
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,
when an unknown printer took a galley of type and scrambled it to make a type
specimen book. It has survived not only five centuries, but also the leap into
electronic typesetting, remaining essentially unchanged. It was popularised in
the 1960s with the release of Letraset sheets containing Lorem Ipsum passages,
nd more recently with desktop publishing software like Aldus PageMaker including
versions of Lorem Ipsum.
It is a long established fact that a reader will be distracted by the readable
content of a page when looking at its layout. The point of using Lorem Ipsum is
that it has a more-or-less normal distribution of letters, as opposed to using
'Content here, content here', making it look like readable English. Many desktop
ublishing packages and web page editors now use Lorem Ipsum as their default model
text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy.
Various versions have evolved over the years, sometimes by accident, sometimes on purpose
injected humour and the like).
"""
class Content:
def __init__(self, location, content_type):
@@ -10,6 +49,30 @@ class Content:
self.content_type = content_type
class FakeGridFsItem:
"""
This class provides the basic methods to get data from a GridFS item
"""
def __init__(self, string_data):
self.cursor = 0
self.data = string_data
self.length = len(string_data)
def seek(self, position):
"""
Set the cursor at "position"
"""
self.cursor = position
def read(self, chunk_size):
"""
Read "chunk_size" bytes of data at position cursor and move the cursor
"""
chunk = self.data[self.cursor:(self.cursor + chunk_size)]
self.cursor += chunk_size
return chunk
class ContentTest(unittest.TestCase):
def test_thumbnail_none(self):
# We had a bug where a thumbnail location of None was getting transformed into a Location tuple, with
@@ -46,3 +109,39 @@ class ContentTest(unittest.TestCase):
AssetLocation(u'foo', u'bar', None, u'asset', u'images_course_image.jpg', None),
asset_location
)
def test_static_content_stream_stream_data(self):
"""
Test StaticContentStream stream_data function, asserts that we get all the bytes
"""
data = SAMPLE_STRING
item = FakeGridFsItem(data)
static_content_stream = StaticContentStream('loc', 'name', 'type', item, length=item.length)
total_length = 0
stream = static_content_stream.stream_data()
for chunck in stream:
total_length += len(chunck)
self.assertEqual(total_length, static_content_stream.length)
def test_static_content_stream_stream_data_in_range(self):
"""
Test StaticContentStream stream_data_in_range function,
asserts that we get the requested number of bytes
first_byte and last_byte are chosen to be simple but non trivial values
and to have total_length > STREAM_DATA_CHUNK_SIZE (1024)
"""
data = SAMPLE_STRING
item = FakeGridFsItem(data)
static_content_stream = StaticContentStream('loc', 'name', 'type', item, length=item.length)
first_byte = 100
last_byte = 1500
total_length = 0
stream = static_content_stream.stream_data_in_range(first_byte, last_byte)
for chunck in stream:
total_length += len(chunck)
self.assertEqual(total_length, last_byte - first_byte + 1)

View File

@@ -6,6 +6,7 @@ from xmodule.studio_editable import StudioEditableModule, StudioEditableDescript
from pkg_resources import resource_string
from copy import copy
# HACK: This shouldn't be hard-coded to two types
# OBSOLETE: This obsoletes 'type'
class_priority = ['video', 'problem']

View File

@@ -388,8 +388,8 @@ class XmlDescriptor(XModuleDescriptor):
url_path = name_to_pathname(self.url_name)
filepath = self._format_filepath(self.category, url_path)
resource_fs.makedir(os.path.dirname(filepath), recursive=True, allow_recreate=True)
with resource_fs.open(filepath, 'w') as file:
file.write(etree.tostring(xml_object, pretty_print=True, encoding='utf-8'))
with resource_fs.open(filepath, 'w') as fileobj:
fileobj.write(etree.tostring(xml_object, pretty_print=True, encoding='utf-8'))
# And return just a pointer with the category and filename.
record_object = etree.Element(self.category)