--- a/devtools/__init__.py Thu Apr 08 13:38:36 2010 +0200
+++ b/devtools/__init__.py Thu Apr 08 14:11:49 2010 +0200
@@ -155,6 +155,9 @@
sources = super(TestServerConfiguration, self).sources()
if not sources:
sources = DEFAULT_SOURCES
+ if sources['system']['db-driver'] == 'sqlite':
+ # we need an abspath in case tests are changing the cwd
+ sources['system']['db-name'] = abspath(sources['system']['db-name'])
return sources
--- a/doc/book/en/development/devweb/js.rst Thu Apr 08 13:38:36 2010 +0200
+++ b/doc/book/en/development/devweb/js.rst Thu Apr 08 14:11:49 2010 +0200
@@ -5,7 +5,7 @@
*CubicWeb* uses quite a bit of javascript in its user interface and
ships with jquery (1.3.x) and parts of the jquery UI library, plus a
-number of homegrown files and also other thir party libraries.
+number of homegrown files and also other third party libraries.
All javascript files are stored in cubicweb/web/data/. There are
around thirty js files there. In a cube it goes to data/.
--- a/doc/book/en/development/testing.rst Thu Apr 08 13:38:36 2010 +0200
+++ b/doc/book/en/development/testing.rst Thu Apr 08 14:11:49 2010 +0200
@@ -24,6 +24,10 @@
In most of the cases, you will inherit `EnvBasedTC` to write Unittest or
functional tests for your entities, views, hooks, etc...
+XXX pytestconf.py & options (e.g --source to use a different db
+backend than sqlite)
+
+
Managing connections or users
+++++++++++++++++++++++++++++
@@ -33,10 +37,12 @@
By default, tests run with a user with admin privileges. This
user/connection must never be closed.
-qwq
-Before a self.login, one has to release the connection pool in use with a self.commit, self.rollback or self.close.
-When one is logged in as a normal user and wants to switch back to the admin user, one has to use self.restore_connection().
+Before a self.login, one has to release the connection pool in use
+with a self.commit, self.rollback or self.close.
+
+When one is logged in as a normal user and wants to switch back to the
+admin user, one has to use self.restore_connection().
Usually it looks like this:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/misc/migration/3.7.4_Any.py Thu Apr 08 14:11:49 2010 +0200
@@ -0,0 +1,1 @@
+sync_schema_props_perms('TrInfo', syncprops=False)
--- a/schemas/bootstrap.py Thu Apr 08 13:38:36 2010 +0200
+++ b/schemas/bootstrap.py Thu Apr 08 14:11:49 2010 +0200
@@ -311,8 +311,9 @@
def post_build_callback(schema):
"""set attributes permissions for schema/workflow entities"""
from cubicweb.schema import SCHEMA_TYPES, WORKFLOW_TYPES, META_RTYPES
+ wftypes = WORKFLOW_TYPES - set(('TrInfo',))
for eschema in schema.entities():
- if eschema in SCHEMA_TYPES or eschema in WORKFLOW_TYPES:
+ if eschema in SCHEMA_TYPES or eschema in wftypes:
for rschema in eschema.subject_relations():
if rschema.final and not rschema in META_RTYPES:
rdef = eschema.rdef(rschema)
--- a/server/sources/storages.py Thu Apr 08 13:38:36 2010 +0200
+++ b/server/sources/storages.py Thu Apr 08 14:11:49 2010 +0200
@@ -85,9 +85,16 @@
def entity_updated(self, entity, attr):
"""an entity using this storage for attr has been updatded"""
- binary = entity.pop(attr)
- fpath = self.current_fs_path(entity, attr)
- UpdateFileOp(entity._cw, filepath=fpath, filedata=binary.getvalue())
+ if entity._cw.transaction_data.get('fs_importing'):
+ oldpath = self.current_fs_path(entity, attr)
+ fpath = entity[attr].getvalue()
+ if oldpath != fpath:
+ DeleteFileOp(entity._cw, filepath=oldpath)
+ binary = Binary(file(fpath).read())
+ else:
+ binary = entity.pop(attr)
+ fpath = self.current_fs_path(entity, attr)
+ UpdateFileOp(entity._cw, filepath=fpath, filedata=binary.getvalue())
return binary
def entity_deleted(self, entity, attr):
--- a/server/test/unittest_storage.py Thu Apr 08 13:38:36 2010 +0200
+++ b/server/test/unittest_storage.py Thu Apr 08 14:11:49 2010 +0200
@@ -57,6 +57,11 @@
return req.create_entity('File', data=Binary(content),
data_format=u'text/plain', data_name=u'foo')
+ def fspath(self, entity):
+ fspath = self.execute('Any fspath(D) WHERE F eid %(f)s, F data D',
+ {'f': entity.eid})[0][0]
+ return fspath.getvalue()
+
def test_bfss_storage(self):
f1 = self.create_file()
expected_filepath = osp.join(self.tempdir, '%s_data' % f1.eid)
@@ -81,18 +86,14 @@
def test_bfss_sqlite_fspath(self):
f1 = self.create_file()
expected_filepath = osp.join(self.tempdir, '%s_data' % f1.eid)
- fspath = self.execute('Any fspath(D) WHERE F eid %(f)s, F data D',
- {'f': f1.eid})[0][0]
- self.assertEquals(fspath.getvalue(), expected_filepath)
+ self.assertEquals(self.fspath(f1), expected_filepath)
def test_bfss_fs_importing_doesnt_touch_path(self):
self.session.transaction_data['fs_importing'] = True
filepath = osp.abspath(__file__)
f1 = self.session.create_entity('File', data=Binary(filepath),
data_format=u'text/plain', data_name=u'foo')
- fspath = self.execute('Any fspath(D) WHERE F eid %(f)s, F data D',
- {'f': f1.eid})[0][0]
- self.assertEquals(fspath.getvalue(), filepath)
+ self.assertEquals(self.fspath(f1), filepath)
def test_source_storage_transparency(self):
with self.temporary_appobjects(DummyBeforeHook, DummyAfterHook):
@@ -180,5 +181,21 @@
self.assertEquals(f2.data.getvalue(), 'some other data')
+ def test_bfss_update_with_fs_importing(self):
+ # use self.session to use server-side cache
+ f1 = self.session.create_entity('File', data=Binary('some data'),
+ data_format=u'text/plain', data_name=u'foo')
+ old_fspath = self.fspath(f1)
+ self.session.transaction_data['fs_importing'] = True
+ new_fspath = osp.join(self.tempdir, 'newfile.txt')
+ file(new_fspath, 'w').write('the new data')
+ self.execute('SET F data %(d)s WHERE F eid %(f)s',
+ {'d': Binary(new_fspath), 'f': f1.eid})
+ self.commit()
+ self.assertEquals(f1.data.getvalue(), 'the new data')
+ self.assertEquals(self.fspath(f1), new_fspath)
+ self.failIf(osp.isfile(old_fspath))
+
+
if __name__ == '__main__':
unittest_main()
--- a/test/unittest_schema.py Thu Apr 08 13:38:36 2010 +0200
+++ b/test/unittest_schema.py Thu Apr 08 14:11:49 2010 +0200
@@ -241,6 +241,17 @@
self.failUnless('has_text' in schema['CWUser'].subject_relations())
self.failIf('has_text' in schema['EmailAddress'].subject_relations())
+ def test_permission_settings(self):
+ schema = loader.load(config)
+ aschema = schema['TrInfo'].rdef('comment')
+ self.assertEquals(aschema.get_groups('read'),
+ set(('managers', 'users', 'guests')))
+ self.assertEquals(aschema.get_rqlexprs('read'),
+ ())
+ self.assertEquals(aschema.get_groups('update'),
+ set(('managers',)))
+ self.assertEquals([x.expression for x in aschema.get_rqlexprs('update')],
+ ['U has_update_permission X'])
class BadSchemaRQLExprTC(TestCase):
def setUp(self):
@@ -257,16 +268,20 @@
self.assertEquals(str(ex), msg)
def test_rrqlexpr_on_etype(self):
- self._test('rrqlexpr_on_eetype.py', "can't use RRQLExpression on ToTo, use an ERQLExpression")
+ self._test('rrqlexpr_on_eetype.py',
+ "can't use RRQLExpression on ToTo, use an ERQLExpression")
def test_erqlexpr_on_rtype(self):
- self._test('erqlexpr_on_ertype.py', "can't use ERQLExpression on relation ToTo toto TuTu, use a RRQLExpression")
+ self._test('erqlexpr_on_ertype.py',
+ "can't use ERQLExpression on relation ToTo toto TuTu, use a RRQLExpression")
def test_rqlexpr_on_rtype_read(self):
- self._test('rqlexpr_on_ertype_read.py', "can't use rql expression for read permission of relation ToTo toto TuTu")
+ self._test('rqlexpr_on_ertype_read.py',
+ "can't use rql expression for read permission of relation ToTo toto TuTu")
def test_rrqlexpr_on_attr(self):
- self._test('rrqlexpr_on_attr.py', "can't use RRQLExpression on attribute ToTo.attr[String], use an ERQLExpression")
+ self._test('rrqlexpr_on_attr.py',
+ "can't use RRQLExpression on attribute ToTo.attr[String], use an ERQLExpression")
class NormalizeExpressionTC(TestCase):
@@ -277,8 +292,10 @@
class RQLExpressionTC(TestCase):
def test_comparison(self):
- self.assertEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWUser', 'X', 0))
- self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWGroup', 'X', 0))
+ self.assertEquals(ERQLExpression('X is CWUser', 'X', 0),
+ ERQLExpression('X is CWUser', 'X', 0))
+ self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0),
+ ERQLExpression('X is CWGroup', 'X', 0))
class GuessRrqlExprMainVarsTC(TestCase):
def test_exists(self):
--- a/web/_exceptions.py Thu Apr 08 13:38:36 2010 +0200
+++ b/web/_exceptions.py Thu Apr 08 14:11:49 2010 +0200
@@ -53,8 +53,7 @@
"""raised when a json remote call fails
"""
def __init__(self, reason=''):
- #super(RequestError, self).__init__() # XXX require py >= 2.5
- RequestError.__init__(self)
+ super(RequestError, self).__init__()
self.reason = reason
def dumps(self):
--- a/web/data/cubicweb.ajax.js Thu Apr 08 13:38:36 2010 +0200
+++ b/web/data/cubicweb.ajax.js Thu Apr 08 14:11:49 2010 +0200
@@ -240,6 +240,7 @@
setProgressCursor();
var props = {'fname' : fname, 'pageid' : pageid,
'arg': map(jQuery.toJSON, sliceList(arguments, 1))};
+ // XXX we should inline the content of loadRemote here
var deferred = loadRemote(JSON_BASE_URL, props, 'POST');
deferred = deferred.addErrback(remoteCallFailed);
deferred = deferred.addErrback(resetCursor);
--- a/web/data/cubicweb.compat.js Thu Apr 08 13:38:36 2010 +0200
+++ b/web/data/cubicweb.compat.js Thu Apr 08 14:11:49 2010 +0200
@@ -6,22 +6,26 @@
}
}
+// XXX looks completely unused (candidate for removal)
function getElementsByTagAndClassName(tag, klass, root) {
root = root || document;
// FIXME root is not used in this compat implementation
return jQuery(tag + '.' + klass);
}
+/* jQUery flattens arrays returned by the mapping function:
+ >>> y = ['a:b:c', 'd:e']
+ >>> jQuery.map(y, function(y) { return y.split(':');})
+ ["a", "b", "c", "d", "e"]
+ // where one would expect:
+ [ ["a", "b", "c"], ["d", "e"] ]
+ XXX why not the same argument order as $.map and forEach ?
+*/
function map(func, array) {
- // XXX jQUery tends to simplify lists with only one element :
- // >>> y = ['a:b:c']
- // >>> jQuery.map(y, function(y) { return y.split(':');})
- // ["a", "b", "c"]
- // where I would expect :
- // [ ["a", "b", "c"] ]
- // return jQuery.map(array, func);
var result = [];
- for (var i=0,length=array.length;i<length;i++) {
+ for (var i=0, length=array.length;
+ i<length;
+ i++) {
result.push(func(array[i]));
}
return result;
@@ -41,6 +45,7 @@
jQuery(node).addClass(klass);
}
+// XXX looks completely unused (candidate for removal)
function toggleElementClass(node, klass) {
jQuery(node).toggleClass(klass);
}
@@ -281,26 +286,35 @@
jQuery.extend(Deferred.prototype, {
__init__: function() {
- this.onSuccess = [];
- this.onFailure = [];
- this.req = null;
+ this._onSuccess = [];
+ this._onFailure = [];
+ this._req = null;
+ this._result = null;
+ this._error = null;
},
addCallback: function(callback) {
- this.onSuccess.push([callback, sliceList(arguments, 1)]);
+ if (this._req.readyState == 4) {
+ if (this._result) { callback.apply(null, this._result, this._req); }
+ }
+ else { this._onSuccess.push([callback, sliceList(arguments, 1)]); }
return this;
},
addErrback: function(callback) {
- this.onFailure.push([callback, sliceList(arguments, 1)]);
+ if (this._req.readyState == 4) {
+ if (this._error) { callback.apply(null, this._error, this._req); }
+ }
+ else { this._onFailure.push([callback, sliceList(arguments, 1)]); }
return this;
},
success: function(result) {
+ this._result = result;
try {
- for (var i=0; i<this.onSuccess.length; i++) {
- var callback = this.onSuccess[i][0];
- var args = merge([result, this.req], this.onSuccess[i][1]);
+ for (var i=0; i<this._onSuccess.length; i++) {
+ var callback = this._onSuccess[i][0];
+ var args = merge([result, this._req], this._onSuccess[i][1]);
callback.apply(null, args);
}
} catch (error) {
@@ -309,9 +323,10 @@
},
error: function(xhr, status, error) {
- for (var i=0; i<this.onFailure.length; i++) {
- var callback = this.onFailure[i][0];
- var args = merge([error, this.req], this.onFailure[i][1]);
+ this._error = error;
+ for (var i=0; i<this._onFailure.length; i++) {
+ var callback = this._onFailure[i][0];
+ var args = merge([error, this._req], this._onFailure[i][1]);
callback.apply(null, args);
}
}
@@ -319,6 +334,46 @@
});
+/*
+ * Asynchronously load an url and return a deferred
+ * whose callbacks args are decoded according to
+ * the Content-Type response header
+ */
+function loadRemote(url, data, reqtype) {
+ var d = new Deferred();
+ jQuery.ajax({
+ url: url,
+ type: reqtype,
+ data: data,
+
+ beforeSend: function(xhr) {
+ d._req = xhr;
+ },
+
+ success: function(data, status) {
+ if (d._req.getResponseHeader("content-type") == 'application/json') {
+ data = evalJSON(data);
+ }
+ d.success(data);
+ },
+
+ error: function(xhr, status, error) {
+ try {
+ if (xhr.status == 500) {
+ var reason_dict = evalJSON(xhr.responseText);
+ d.error(xhr, status, reason_dict['reason']);
+ return;
+ }
+ } catch(exc) {
+ log('error with server side error report:' + exc);
+ }
+ d.error(xhr, status, null);
+ }
+ });
+ return d;
+}
+
+
/** @id MochiKit.DateTime.toISOTime */
toISOTime = function (date, realISO/* = false */) {
if (typeof(date) == "undefined" || date === null) {
@@ -366,36 +421,6 @@
};
-/*
- * Asynchronously load an url and return a deferred
- * whose callbacks args are decoded according to
- * the Content-Type response header
- */
-function loadRemote(url, data, reqtype) {
- var d = new Deferred();
- jQuery.ajax({
- url: url,
- type: reqtype,
- data: data,
-
- beforeSend: function(xhr) {
- d.req = xhr;
- },
-
- success: function(data, status) {
- if (d.req.getResponseHeader("content-type") == 'application/json') {
- data = evalJSON(data);
- }
- d.success(data);
- },
-
- error: function(xhr, status, error) {
- error = evalJSON(xhr.responseText);
- d.error(xhr, status, error['reason']);
- }
- });
- return d;
-}
/* depth-first implementation of the nodeWalk function found
* in MochiKit.Base
--- a/web/data/cubicweb.python.js Thu Apr 08 13:38:36 2010 +0200
+++ b/web/data/cubicweb.python.js Thu Apr 08 14:11:49 2010 +0200
@@ -237,9 +237,9 @@
* ['d', 'e', 'f']
*/
function sliceList(lst, start, stop, step) {
- var start = start || 0;
- var stop = stop || lst.length;
- var step = step || 1;
+ start = start || 0;
+ stop = stop || lst.length;
+ step = step || 1;
if (stop < 0) {
stop = max(lst.length+stop, 0);
}
@@ -256,6 +256,7 @@
/* returns a partial func that calls a mehod on its argument
* py-equiv: return lambda obj: getattr(obj, methname)(*args)
*/
+// XXX looks completely unused (candidate for removal)
function methodcaller(methname) {
var args = sliceList(arguments, 1);
return function(obj) {
@@ -398,6 +399,7 @@
jQuery(CubicWeb).trigger('server-response', [false, document]);
});
+// XXX as of 2010-04-07, no known cube uses this
jQuery(CubicWeb).bind('ajax-loaded', function() {
log('[3.7] "ajax-loaded" event is deprecated, use "server-response" instead');
jQuery(CubicWeb).trigger('server-response', [false, document]);
--- a/web/views/iprogress.py Thu Apr 08 13:38:36 2010 +0200
+++ b/web/views/iprogress.py Thu Apr 08 14:11:49 2010 +0200
@@ -264,4 +264,4 @@
int(100.*budget/maxi), color))
self.w(u'%s<br/>'
u'<canvas class="progressbar" id="canvas%s" width="100" height="10"></canvas>'
- % (short_title.replace(' ',' '), cid))
+ % (xml_escape(short_title), cid))
--- a/web/views/primary.py Thu Apr 08 13:38:36 2010 +0200
+++ b/web/views/primary.py Thu Apr 08 14:11:49 2010 +0200
@@ -115,11 +115,8 @@
return u''
def render_entity_attributes(self, entity, siderelations=None):
- entity_attributes = self._section_def(entity, 'attributes')
- if not entity_attributes:
- return
- self.w(u'<table>')
- for rschema, tschemas, role, dispctrl in entity_attributes:
+ display_attributes = []
+ for rschema, _, role, dispctrl in self._section_def(entity, 'attributes'):
vid = dispctrl.get('vid', 'reledit')
if rschema.final or vid == 'reledit':
value = entity.view(vid, rtype=rschema.type, role=role)
@@ -129,16 +126,19 @@
value = self._cw.view(vid, rset)
else:
value = None
- if self.skip_none and (value is None or value == ''):
- continue
- try:
- self._render_attribute(dispctrl, rschema, value,
- role=role, table=True)
- except TypeError:
- warn('[3.6] _render_attribute prototype has changed, '
- 'please update %s' % self.__class___, DeprecationWarning)
- self._render_attribute(rschema, value, role=role, table=True)
- self.w(u'</table>')
+ if not self.skip_none or (value is not None and value != ''):
+ display_attributes.append( (rschema, role, dispctrl, value) )
+ if display_attributes:
+ self.w(u'<table>')
+ for rschema, role, dispctrl, value in display_attributes:
+ try:
+ self._render_attribute(dispctrl, rschema, value,
+ role=role, table=True)
+ except TypeError:
+ warn('[3.6] _render_attribute prototype has changed, please'
+ ' update %s' % self.__class___, DeprecationWarning)
+ self._render_attribute(rschema, value, role=role, table=True)
+ self.w(u'</table>')
def render_entity_relations(self, entity, siderelations=None):
for rschema, tschemas, role, dispctrl in self._section_def(entity, 'relations'):