--- a/debian/control Wed Oct 06 11:57:21 2010 +0200
+++ b/debian/control Wed Oct 06 14:04:26 2010 +0200
@@ -34,7 +34,7 @@
Replaces: cubicweb-multisources
Provides: cubicweb-multisources
Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-logilab-database (>= 1.3.1), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2
-Recommends: pyro, cubicweb-documentation (= ${source:Version})
+Recommends: pyro (< 4.0.0), cubicweb-documentation (= ${source:Version})
Description: server part of the CubicWeb framework
CubicWeb is a semantic web application framework.
.
@@ -69,7 +69,7 @@
XB-Python-Version: ${python:Versions}
Provides: cubicweb-web-frontend
Depends: ${python:Depends}, cubicweb-web (= ${source:Version}), cubicweb-ctl (= ${source:Version}), python-twisted-web
-Recommends: pyro, cubicweb-documentation (= ${source:Version})
+Recommends: pyro (< 4.0.0), cubicweb-documentation (= ${source:Version})
Description: twisted-based web interface for the CubicWeb framework
CubicWeb is a semantic web application framework.
.
--- a/devtools/testlib.py Wed Oct 06 11:57:21 2010 +0200
+++ b/devtools/testlib.py Wed Oct 06 14:04:26 2010 +0200
@@ -45,6 +45,7 @@
from cubicweb.sobjects import notification
from cubicweb.web import Redirect, application
from cubicweb.server.session import security_enabled
+from cubicweb.server.hook import SendMailOp
from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS
from cubicweb.devtools import BASE_URL, fake, htmlparser
from cubicweb.utils import json
@@ -291,6 +292,9 @@
# default test setup and teardown #########################################
def setUp(self):
+ # monkey patch send mail operation so emails are sent synchronously
+ self._old_mail_commit_event = SendMailOp.commit_event
+ SendMailOp.commit_event = SendMailOp.sendmails
pause_tracing()
previous_failure = self.__class__.__dict__.get('_repo_init_failed')
if previous_failure is not None:
@@ -312,6 +316,7 @@
for cnx in self._cnxs:
if not cnx._closed:
cnx.close()
+ SendMailOp.commit_event = self._old_mail_commit_event
def setup_database(self):
"""add your database setup code by overriding this method"""
--- a/doc/book/en/devrepo/devcore/dbapi.rst Wed Oct 06 11:57:21 2010 +0200
+++ b/doc/book/en/devrepo/devcore/dbapi.rst Wed Oct 06 14:04:26 2010 +0200
@@ -23,10 +23,11 @@
.. note::
If a query generates an error related to security (:exc:`Unauthorized`) or to
- integrity (:exc:`ValidationError`), a rollback is automatically done on the
- current transaction.
+ integrity (:exc:`ValidationError`), the transaction can still continue but you
+ won't be able to commit it, a rollback will be necessary to start a new
+ transaction.
- Also, a rollback is done if an error occurs during commit.
+ Also, a rollback is automatically done if an error occurs during commit.
Executing RQL queries from a view or a hook
--- a/server/querier.py Wed Oct 06 11:57:21 2010 +0200
+++ b/server/querier.py Wed Oct 06 14:04:26 2010 +0200
@@ -712,7 +712,7 @@
# * don't rollback if we're in the commit process, will be handled
# by the session
if session.commit_state is None:
- session.rollback(reset_pool=False)
+ session.commit_state = 'uncommitable'
raise
# build a description for the results if necessary
descr = ()
--- a/server/session.py Wed Oct 06 11:57:21 2010 +0200
+++ b/server/session.py Wed Oct 06 14:04:26 2010 +0200
@@ -31,7 +31,7 @@
from rql.nodes import ETYPE_PYOBJ_MAP, etype_from_pyobj
from yams import BASE_TYPES
-from cubicweb import Binary, UnknownEid, schema
+from cubicweb import Binary, UnknownEid, QueryError, schema
from cubicweb.req import RequestSessionBase
from cubicweb.dbapi import ConnectionProperties
from cubicweb.utils import make_uid, RepeatList
@@ -738,7 +738,10 @@
self._touch()
self.debug('commit session %s done (no db activity)', self.id)
return
- if self.commit_state:
+ cstate = self.commit_state
+ if cstate == 'uncommitable':
+ raise QueryError('transaction must be rollbacked')
+ if cstate is not None:
return
# on rollback, an operation should have the following state
# information:
--- a/server/sources/storages.py Wed Oct 06 11:57:21 2010 +0200
+++ b/server/sources/storages.py Wed Oct 06 14:04:26 2010 +0200
@@ -18,6 +18,7 @@
"""custom storages for the system source"""
from os import unlink, path as osp
+from contextlib import contextmanager
from yams.schema import role_name
@@ -96,6 +97,17 @@
return path
return None
+@contextmanager
+def fsimport(session):
+ present = 'fs_importing' in session.transaction_data
+ old_value = session.transaction_data.get('fs_importing')
+ session.transaction_data['fs_importing'] = True
+ yield
+ if present:
+ session.transaction_data['fs_importing'] = old_value
+ else:
+ del session.transaction_data['fs_importing']
+
class BytesFileSystemStorage(Storage):
"""store Bytes attribute value on the file system"""
--- a/server/test/unittest_repository.py Wed Oct 06 11:57:21 2010 +0200
+++ b/server/test/unittest_repository.py Wed Oct 06 14:04:26 2010 +0200
@@ -32,7 +32,7 @@
from yams.constraints import UniqueConstraint
from cubicweb import (BadConnectionId, RepositoryError, ValidationError,
- UnknownEid, AuthenticationError, Unauthorized)
+ UnknownEid, AuthenticationError, Unauthorized, QueryError)
from cubicweb.selectors import is_instance
from cubicweb.schema import CubicWebSchema, RQLConstraint
from cubicweb.dbapi import connect, multiple_connections_unfix
@@ -154,6 +154,10 @@
with self.temporary_appobjects(ValidationErrorAfterHook):
self.assertRaises(ValidationError,
self.execute, 'SET X name "toto" WHERE X is CWGroup, X name "guests"')
+ self.failUnless(self.execute('Any X WHERE X is CWGroup, X name "toto"'))
+ ex = self.assertRaises(QueryError, self.commit)
+ self.assertEqual(str(ex), 'transaction must be rollbacked')
+ self.rollback()
self.failIf(self.execute('Any X WHERE X is CWGroup, X name "toto"'))
def test_rollback_on_execute_unauthorized(self):
@@ -166,6 +170,10 @@
with self.temporary_appobjects(UnauthorizedAfterHook):
self.assertRaises(Unauthorized,
self.execute, 'SET X name "toto" WHERE X is CWGroup, X name "guests"')
+ self.failUnless(self.execute('Any X WHERE X is CWGroup, X name "toto"'))
+ ex = self.assertRaises(QueryError, self.commit)
+ self.assertEqual(str(ex), 'transaction must be rollbacked')
+ self.rollback()
self.failIf(self.execute('Any X WHERE X is CWGroup, X name "toto"'))
--- a/server/test/unittest_storage.py Wed Oct 06 11:57:21 2010 +0200
+++ b/server/test/unittest_storage.py Wed Oct 06 14:04:26 2010 +0200
@@ -19,7 +19,7 @@
from __future__ import with_statement
-from logilab.common.testlib import unittest_main, tag
+from logilab.common.testlib import unittest_main, tag, Tags
from cubicweb.devtools.testlib import CubicWebTC
import os.path as osp
@@ -52,6 +52,8 @@
class StorageTC(CubicWebTC):
+ tags = CubicWebTC.tags | Tags('Storage', 'BFSS')
+
def setup_database(self):
self.tempdir = tempfile.mkdtemp()
bfs_storage = storages.BytesFileSystemStorage(self.tempdir)
@@ -184,7 +186,7 @@
self.assertEqual(f1.data.getvalue(), file(filepath).read(),
'files content differ')
- @tag('Storage', 'BFSS', 'update')
+ @tag('update')
def test_bfss_update_with_existing_data(self):
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
@@ -198,7 +200,7 @@
f2 = self.execute('Any F WHERE F eid %(f)s, F is File', {'f': f1.eid}).get_entity(0, 0)
self.assertEqual(f2.data.getvalue(), 'some other data')
- @tag('Storage', 'BFSS', 'update', 'extension', 'commit')
+ @tag('update', 'extension', 'commit')
def test_bfss_update_with_different_extension_commited(self):
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
@@ -220,7 +222,7 @@
self.failUnless(osp.isfile(new_path))
self.assertEqual(osp.splitext(new_path)[1], '.jpg')
- @tag('Storage', 'BFSS', 'update', 'extension', 'rollback')
+ @tag('update', 'extension', 'rollback')
def test_bfss_update_with_different_extension_rollbacked(self):
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
@@ -245,6 +247,7 @@
self.assertEqual(old_path, new_path)
self.assertEqual(old_data, new_data)
+ @tag('fs_importing', 'update')
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'),
@@ -260,6 +263,35 @@
self.assertEqual(self.fspath(f1), new_fspath)
self.failIf(osp.isfile(old_fspath))
+ @tag('fsimport')
+ def test_clean(self):
+ fsimport = storages.fsimport
+ td = self.session.transaction_data
+ self.assertNotIn('fs_importing', td)
+ with fsimport(self.session):
+ self.assertIn('fs_importing', td)
+ self.assertTrue(td['fs_importing'])
+ self.assertNotIn('fs_importing', td)
+
+ @tag('fsimport')
+ def test_true(self):
+ fsimport = storages.fsimport
+ td = self.session.transaction_data
+ td['fs_importing'] = True
+ with fsimport(self.session):
+ self.assertIn('fs_importing', td)
+ self.assertTrue(td['fs_importing'])
+ self.assertTrue(td['fs_importing'])
+
+ @tag('fsimport')
+ def test_False(self):
+ fsimport = storages.fsimport
+ td = self.session.transaction_data
+ td['fs_importing'] = False
+ with fsimport(self.session):
+ self.assertIn('fs_importing', td)
+ self.assertTrue(td['fs_importing'])
+ self.assertFalse(td['fs_importing'])
if __name__ == '__main__':
unittest_main()
--- a/server/utils.py Wed Oct 06 11:57:21 2010 +0200
+++ b/server/utils.py Wed Oct 06 14:04:26 2010 +0200
@@ -117,6 +117,14 @@
sconfig.input_config(inputlevel=inputlevel)
return sconfig
+_MARKER=object()
+def func_name(func):
+ name = getattr(func, '__name__', _MARKER)
+ if name is _MARKER:
+ name = getattr(func, 'func_name', _MARKER)
+ if name is _MARKER:
+ name = repr(func)
+ return name
class LoopTask(object):
"""threaded task restarting itself once executed"""
@@ -124,7 +132,7 @@
if interval <= 0:
raise ValueError('Loop task interval must be > 0 '
'(current value: %f for %s)' % \
- (interval, func.__name__))
+ (interval, func_name(func)))
self.interval = interval
def auto_restart_func(self=self, func=func, args=args):
try:
@@ -132,7 +140,7 @@
finally:
self.start()
self.func = auto_restart_func
- self.name = func.__name__
+ self.name = func_name(func)
def __str__(self):
return '%s (%s seconds)' % (self.name, self.interval)
@@ -162,7 +170,7 @@
self.running_threads.remove(self)
Thread.__init__(self, target=auto_remove_func)
self.running_threads = running_threads
- self._name = target.__name__
+ self._name = func_name(target)
def start(self):
self.running_threads.append(self)
--- a/vregistry.py Wed Oct 06 11:57:21 2010 +0200
+++ b/vregistry.py Wed Oct 06 14:04:26 2010 +0200
@@ -240,8 +240,8 @@
msg = 'select ambiguity: %s\n(args: %s, kwargs: %s)'
if self.config.debugmode or self.config.mode == 'test':
# raise bare exception in debug mode
- raise Exception(msg % (winners, self.args, self.kwargs.keys()))
- self.error(msg, winners, self.args, self.kwargs.keys())
+ raise Exception(msg % (winners, args, kwargs.keys()))
+ self.error(msg, winners, args, kwargs.keys())
# return the result of calling the appobject
return winners[0](*args, **kwargs)
--- a/web/views/autoform.py Wed Oct 06 11:57:21 2010 +0200
+++ b/web/views/autoform.py Wed Oct 06 14:04:26 2010 +0200
@@ -941,10 +941,11 @@
global etype_relation_field
def etype_relation_field(etype, rtype, role='subject'):
- eschema = vreg.schema.eschema(etype)
try:
+ eschema = vreg.schema.eschema(etype)
return AutomaticEntityForm.field_by_name(rtype, role, eschema)
- except f.FieldNotFound:
+ except (KeyError, f.FieldNotFound):
+ # catch KeyError raised when etype/rtype not found in schema
AutomaticEntityForm.error('field for %s %s may not be found in schema' % (rtype, role))
return None
--- a/web/views/urlrewrite.py Wed Oct 06 11:57:21 2010 +0200
+++ b/web/views/urlrewrite.py Wed Oct 06 14:04:26 2010 +0200
@@ -163,7 +163,7 @@
return do_build_rset
def rgx_action(rql=None, args=None, cachekey=None, argsgroups=(), setuser=False,
- form=None, formgroups=(), transforms={}, controller=None):
+ form=None, formgroups=(), transforms={}, rqlformparams=(), controller=None):
def do_build_rset(inputurl, uri, req, schema,
cachekey=cachekey # necessary to avoid UnboundLocalError
):
@@ -183,6 +183,8 @@
kwargs[key] = typed_eid(value)
if setuser:
kwargs['u'] = req.user.eid
+ for param in rqlformparams:
+ kwargs.setdefault(param, req.form.get(param))
rset = req.execute(rql, kwargs, cachekey)
else:
rset = None