Merge branch 'danielli/pyfs' of https://github.com/edx/edx-platform into danielli/pyfs
This commit is contained in:
@@ -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;"/>
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}());
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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')
|
||||
@@ -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]
|
||||
|
||||
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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'))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user