# HG changeset patch # User Sylvain Thénault # Date 1281458107 -7200 # Node ID c40652b93321be8173fd05255713e5433d210e72 # Parent ab4958e2654b392d4edc7387173700d3594d76cb# Parent 9001a74fcc821db364f7071112c7436148130dbb backport stable diff -r 9001a74fcc82 -r c40652b93321 cwvreg.py --- a/cwvreg.py Tue Aug 10 16:10:28 2010 +0200 +++ b/cwvreg.py Tue Aug 10 18:35:07 2010 +0200 @@ -713,7 +713,7 @@ vocab = pdef['vocabulary'] if vocab is not None: if callable(vocab): - vocab = vocab(key, None) # XXX need a req object + vocab = vocab(None) # XXX need a req object if not value in vocab: raise ValueError(_('unauthorized value')) return value diff -r 9001a74fcc82 -r c40652b93321 dbapi.py --- a/dbapi.py Tue Aug 10 16:10:28 2010 +0200 +++ b/dbapi.py Tue Aug 10 18:35:07 2010 +0200 @@ -313,19 +313,17 @@ # low level session data management ####################################### - def get_shared_data(self, key, default=None, pop=False): - """return value associated to `key` in shared data""" - return self.cnx.get_shared_data(key, default, pop) - - def set_shared_data(self, key, value, querydata=False): - """set value associated to `key` in shared data + def get_shared_data(self, key, default=None, pop=False, txdata=False): + """see :meth:`Connection.get_shared_data`""" + return self.cnx.get_shared_data(key, default, pop, txdata) - if `querydata` is true, the value will be added to the repository - session's query data which are cleared on commit/rollback of the current - transaction, and won't be available through the connexion, only on the - repository side. - """ - return self.cnx.set_shared_data(key, value, querydata) + def set_shared_data(self, key, value, txdata=False, querydata=None): + """see :meth:`Connection.set_shared_data`""" + if querydata is not None: + txdata = querydata + warn('[3.10] querydata argument has been renamed to txdata', + DeprecationWarning, stacklevel=2) + return self.cnx.set_shared_data(key, value, txdata) # server session compat layer ############################################# @@ -507,10 +505,12 @@ return DBAPIRequest(self.vreg, DBAPISession(self)) def check(self): - """raise `BadConnectionId` if the connection is no more valid""" + """raise `BadConnectionId` if the connection is no more valid, else + return its latest activity timestamp. + """ if self._closed is not None: raise ProgrammingError('Closed connection') - self._repo.check_session(self.sessionid) + return self._repo.check_session(self.sessionid) def set_session_props(self, **props): """raise `BadConnectionId` if the connection is no more valid""" @@ -518,23 +518,29 @@ raise ProgrammingError('Closed connection') self._repo.set_session_props(self.sessionid, props) - def get_shared_data(self, key, default=None, pop=False): - """return value associated to `key` in shared data""" - if self._closed is not None: - raise ProgrammingError('Closed connection') - return self._repo.get_shared_data(self.sessionid, key, default, pop) + def get_shared_data(self, key, default=None, pop=False, txdata=False): + """return value associated to key in the session's data dictionary or + session's transaction's data if `txdata` is true. - def set_shared_data(self, key, value, querydata=False): - """set value associated to `key` in shared data + If pop is True, value will be removed from the dictionnary. - if `querydata` is true, the value will be added to the repository - session's query data which are cleared on commit/rollback of the current - transaction, and won't be available through the connexion, only on the - repository side. + If key isn't defined in the dictionnary, value specified by the + `default` argument will be returned. """ if self._closed is not None: raise ProgrammingError('Closed connection') - return self._repo.set_shared_data(self.sessionid, key, value, querydata) + return self._repo.get_shared_data(self.sessionid, key, default, pop, txdata) + + def set_shared_data(self, key, value, txdata=False): + """set value associated to `key` in shared data + + if `txdata` is true, the value will be added to the repository session's + transaction's data which are cleared on commit/rollback of the current + transaction. + """ + if self._closed is not None: + raise ProgrammingError('Closed connection') + return self._repo.set_shared_data(self.sessionid, key, value, txdata) def get_schema(self): """Return the schema currently used by the repository. diff -r 9001a74fcc82 -r c40652b93321 entities/__init__.py --- a/entities/__init__.py Tue Aug 10 16:10:28 2010 +0200 +++ b/entities/__init__.py Tue Aug 10 18:35:07 2010 +0200 @@ -35,6 +35,11 @@ __regid__ = 'Any' __implements__ = () + @classmethod + def cw_create_url(cls, req, **kwargs): + """ return the url of the entity creation form for this entity type""" + return req.build_url('add/%s' % cls.__regid__, **kwargs) + # meta data api ########################################################### def dc_title(self): diff -r 9001a74fcc82 -r c40652b93321 etwist/twconfig.py --- a/etwist/twconfig.py Tue Aug 10 16:10:28 2010 +0200 +++ b/etwist/twconfig.py Tue Aug 10 18:35:07 2010 +0200 @@ -76,12 +76,6 @@ the repository rather than the user running the command', 'group': 'main', 'level': WebConfiguration.mode == 'system' }), - ('session-time', - {'type' : 'time', - 'default': '30min', - 'help': 'session expiration time, default to 30 minutes', - 'group': 'main', 'level': 1, - }), ('pyro-server', {'type' : 'yn', # pyro is only a recommends by default, so don't activate it here diff -r 9001a74fcc82 -r c40652b93321 goa/appobjects/components.py --- a/goa/appobjects/components.py Tue Aug 10 16:10:28 2010 +0200 +++ b/goa/appobjects/components.py Tue Aug 10 18:35:07 2010 +0200 @@ -15,9 +15,8 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""overrides some base views for cubicweb on google appengine +"""overrides some base views for cubicweb on google appengine""" -""" __docformat__ = "restructuredtext en" from logilab.mtconverter import xml_escape @@ -88,7 +87,8 @@ view = self.vreg.select('views', 'list', req, req.etype_rset(etype)) url = view.url() etypelink = u' %s' % (xml_escape(url), label) - yield (label, etypelink, self.add_entity_link(eschema, req)) + if eschema.has_perm(req, 'add'): + yield (label, etypelink, self.add_entity_link(etype)) ManageView.entity_types = entity_types_no_count diff -r 9001a74fcc82 -r c40652b93321 misc/migration/3.10.0_common.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/migration/3.10.0_common.py Tue Aug 10 18:35:07 2010 +0200 @@ -0,0 +1,1 @@ +option_group_changed('cleanup-session-time', 'web', 'main') diff -r 9001a74fcc82 -r c40652b93321 server/repository.py --- a/server/repository.py Tue Aug 10 16:10:28 2010 +0200 +++ b/server/repository.py Tue Aug 10 18:35:07 2010 +0200 @@ -267,7 +267,10 @@ # call instance level initialisation hooks self.hm.call_hooks('server_startup', repo=self) # register a task to cleanup expired session - self.looping_task(self.config['session-time']/3., self.clean_sessions) + self.cleanup_session_time = self.config['cleanup-session-time'] or 60 * 60 * 24 + assert self.cleanup_session_time > 0 + cleanup_session_interval = min(60*60, self.cleanup_session_time / 3) + self.looping_task(cleanup_session_interval, self.clean_sessions) assert isinstance(self._looping_tasks, list), 'already started' for i, (interval, func, args) in enumerate(self._looping_tasks): self._looping_tasks[i] = task = utils.LoopTask(interval, func, args) @@ -622,24 +625,32 @@ session.reset_pool() def check_session(self, sessionid): - """raise `BadConnectionId` if the connection is no more valid""" - self._get_session(sessionid, setpool=False) + """raise `BadConnectionId` if the connection is no more valid, else + return its latest activity timestamp. + """ + return self._get_session(sessionid, setpool=False).timestamp + + def get_shared_data(self, sessionid, key, default=None, pop=False, txdata=False): + """return value associated to key in the session's data dictionary or + session's transaction's data if `txdata` is true. - def get_shared_data(self, sessionid, key, default=None, pop=False): - """return the session's data dictionary""" + If pop is True, value will be removed from the dictionnary. + + If key isn't defined in the dictionnary, value specified by the + `default` argument will be returned. + """ session = self._get_session(sessionid, setpool=False) - return session.get_shared_data(key, default, pop) + return session.get_shared_data(key, default, pop, txdata) - def set_shared_data(self, sessionid, key, value, querydata=False): + def set_shared_data(self, sessionid, key, value, txdata=False): """set value associated to `key` in shared data - if `querydata` is true, the value will be added to the repository - session's query data which are cleared on commit/rollback of the current - transaction, and won't be available through the connexion, only on the - repository side. + if `txdata` is true, the value will be added to the repository session's + transaction's data which are cleared on commit/rollback of the current + transaction. """ session = self._get_session(sessionid, setpool=False) - session.set_shared_data(key, value, querydata) + session.set_shared_data(key, value, txdata) def commit(self, sessionid, txid=None): """commit transaction for the session with the given id""" @@ -771,7 +782,7 @@ """close sessions not used since an amount of time specified in the configuration """ - mintime = time() - self.config['session-time'] + mintime = time() - self.cleanup_session_time self.debug('cleaning session unused since %s', strftime('%T', localtime(mintime))) nbclosed = 0 diff -r 9001a74fcc82 -r c40652b93321 server/serverconfig.py --- a/server/serverconfig.py Tue Aug 10 16:10:28 2010 +0200 +++ b/server/serverconfig.py Tue Aug 10 18:35:07 2010 +0200 @@ -120,10 +120,16 @@ the repository rather than the user running the command', 'group': 'main', 'level': (CubicWebConfiguration.mode == 'installed') and 0 or 1, }), - ('session-time', + ('cleanup-session-time', {'type' : 'time', - 'default': '30min', - 'help': 'session expiration time, default to 30 minutes', + 'default': '24h', + 'help': 'duration of inactivity after which a session ' + 'will be closed, to limit memory consumption (avoid sessions that ' + 'never expire and cause memory leak when http-session-time is 0, or ' + 'because of bad client that never closes their connection). ' + 'So notice that even if http-session-time is 0 and the user don\'t ' + 'close his browser, he will have to reauthenticate after this time ' + 'of inactivity. Default to 24h.', 'group': 'main', 'level': 3, }), ('connections-pool-size', diff -r 9001a74fcc82 -r c40652b93321 server/session.py --- a/server/session.py Tue Aug 10 16:10:28 2010 +0200 +++ b/server/session.py Tue Aug 10 18:35:07 2010 +0200 @@ -618,16 +618,20 @@ # shared data handling ################################################### - def get_shared_data(self, key, default=None, pop=False): + def get_shared_data(self, key, default=None, pop=False, txdata=False): """return value associated to `key` in session data""" - if pop: - return self.data.pop(key, default) + if txdata: + data = self.transaction_data else: - return self.data.get(key, default) + data = self.data + if pop: + return data.pop(key, default) + else: + return data.get(key, default) - def set_shared_data(self, key, value, querydata=False): + def set_shared_data(self, key, value, txdata=False): """set value associated to `key` in session data""" - if querydata: + if txdata: self.transaction_data[key] = value else: self.data[key] = value diff -r 9001a74fcc82 -r c40652b93321 server/test/data/schema.py --- a/server/test/data/schema.py Tue Aug 10 16:10:28 2010 +0200 +++ b/server/test/data/schema.py Tue Aug 10 18:35:07 2010 +0200 @@ -17,7 +17,8 @@ # with CubicWeb. If not, see . from yams.buildobjs import (EntityType, RelationType, RelationDefinition, - SubjectRelation, RichString, String, Int, Boolean, Datetime) + SubjectRelation, RichString, String, Int, Float, + Boolean, Datetime) from yams.constraints import SizeConstraint from cubicweb.schema import (WorkflowableEntityType, RQLConstraint, ERQLExpression, RRQLExpression) @@ -39,7 +40,7 @@ description=_('more detailed description')) duration = Int() - invoiced = Int() + invoiced = Float() depends_on = SubjectRelation('Affaire') require_permission = SubjectRelation('CWPermission') diff -r 9001a74fcc82 -r c40652b93321 server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Tue Aug 10 16:10:28 2010 +0200 +++ b/server/test/unittest_msplanner.py Tue Aug 10 18:35:07 2010 +0200 @@ -1866,7 +1866,7 @@ [('FetchStep', [('Any WP WHERE 999999 multisource_rel WP, WP is Note', [{'WP': 'Note'}])], [self.cards], None, {'WP': u'table0.C0'}, []), ('OneFetchStep', [('Any S,SUM(DUR),SUM(I),(SUM(I) - SUM(DUR)),MIN(DI),MAX(DI) GROUPBY S ORDERBY S WHERE A duration DUR, A invoiced I, A modification_date DI, A in_state S, S name SN, (EXISTS(A concerne WP, WP is Note)) OR (EXISTS(A concerne 999999)), A is Affaire, S is State', - [{'A': 'Affaire', 'DI': 'Datetime', 'DUR': 'Int', 'I': 'Int', 'S': 'State', 'SN': 'String', 'WP': 'Note'}])], + [{'A': 'Affaire', 'DI': 'Datetime', 'DUR': 'Int', 'I': 'Float', 'S': 'State', 'SN': 'String', 'WP': 'Note'}])], None, None, [self.system], {'WP': u'table0.C0'}, [])], {'n': 999999}) diff -r 9001a74fcc82 -r c40652b93321 server/test/unittest_querier.py --- a/server/test/unittest_querier.py Tue Aug 10 16:10:28 2010 +0200 +++ b/server/test/unittest_querier.py Tue Aug 10 18:35:07 2010 +0200 @@ -544,12 +544,25 @@ self.assertEquals(rset.rows[0][0], 'ADMIN') self.assertEquals(rset.description, [('String',)]) -## def test_select_simplified(self): -## ueid = self.session.user.eid -## rset = self.execute('Any L WHERE %s login L'%ueid) -## self.assertEquals(rset.rows[0][0], 'admin') -## rset = self.execute('Any L WHERE %(x)s login L', {'x':ueid}) -## self.assertEquals(rset.rows[0][0], 'admin') + def test_select_float_abs(self): + # test positive number + eid = self.execute('INSERT Affaire A: A invoiced %(i)s', {'i': 1.2})[0][0] + rset = self.execute('Any ABS(I) WHERE X eid %(x)s, X invoiced I', {'x': eid}) + self.assertEquals(rset.rows[0][0], 1.2) + # test negative number + eid = self.execute('INSERT Affaire A: A invoiced %(i)s', {'i': -1.2})[0][0] + rset = self.execute('Any ABS(I) WHERE X eid %(x)s, X invoiced I', {'x': eid}) + self.assertEquals(rset.rows[0][0], 1.2) + + def test_select_int_abs(self): + # test positive number + eid = self.execute('INSERT Affaire A: A duration %(d)s', {'d': 12})[0][0] + rset = self.execute('Any ABS(D) WHERE X eid %(x)s, X duration D', {'x': eid}) + self.assertEquals(rset.rows[0][0], 12) + # test negative number + eid = self.execute('INSERT Affaire A: A duration %(d)s', {'d': -12})[0][0] + rset = self.execute('Any ABS(D) WHERE X eid %(x)s, X duration D', {'x': eid}) + self.assertEquals(rset.rows[0][0], 12) def test_select_searchable_text_1(self): rset = self.execute(u"INSERT Personne X: X nom 'bidüle'") diff -r 9001a74fcc82 -r c40652b93321 server/test/unittest_repository.py --- a/server/test/unittest_repository.py Tue Aug 10 16:10:28 2010 +0200 +++ b/server/test/unittest_repository.py Tue Aug 10 18:35:07 2010 +0200 @@ -161,7 +161,7 @@ def test_check_session(self): repo = self.repo cnxid = repo.connect(self.admlogin, password=self.admpassword) - self.assertEquals(repo.check_session(cnxid), None) + self.assertIsInstance(repo.check_session(cnxid), float) repo.close(cnxid) self.assertRaises(BadConnectionId, repo.check_session, cnxid) diff -r 9001a74fcc82 -r c40652b93321 utils.py --- a/utils.py Tue Aug 10 16:10:28 2010 +0200 +++ b/utils.py Tue Aug 10 18:35:07 2010 +0200 @@ -24,6 +24,7 @@ import decimal import datetime import random +from inspect import getargspec from itertools import repeat from uuid import uuid4 from warnings import warn @@ -64,6 +65,15 @@ '__doc__': cls.__doc__, '__module__': cls.__module__}) +def support_args(callable, *argnames): + """return true if the callable support given argument names""" + argspec = getargspec(callable) + if argspec[2]: + return True + for argname in argnames: + if argname not in argspec[0]: + return False + return True # use networkX instead ? # http://networkx.lanl.gov/reference/algorithms.traversal.html#module-networkx.algorithms.traversal.astar diff -r 9001a74fcc82 -r c40652b93321 view.py --- a/view.py Tue Aug 10 16:10:28 2010 +0200 +++ b/view.py Tue Aug 10 18:35:07 2010 +0200 @@ -319,21 +319,11 @@ clabel = vtitle return u'%s (%s)' % (clabel, self._cw.property_value('ui.site-title')) - def output_url_builder( self, name, url, args ): - self.w(u'\n') - + @deprecated('[3.10] use vreg["etypes"].etype_class(etype).cw_create_url(req)') def create_url(self, etype, **kwargs): """ return the url of the entity creation form for a given entity type""" - return self._cw.build_url('add/%s' % etype, **kwargs) + return self._cw.vreg["etypes"].etype_class(etype).cw_create_url( + self._cw, **kwargs) def field(self, label, value, row=True, show_label=True, w=None, tr=True, table=False): diff -r 9001a74fcc82 -r c40652b93321 vregistry.py --- a/vregistry.py Tue Aug 10 16:10:28 2010 +0200 +++ b/vregistry.py Tue Aug 10 18:35:07 2010 +0200 @@ -173,7 +173,7 @@ assert len(objects) == 1, objects return objects[0](*args, **kwargs) - def select(self, oid, *args, **kwargs): + def select(self, __oid, *args, **kwargs): """return the most specific object among those with the given oid according to the given context. @@ -181,14 +181,14 @@ raise :exc:`NoSelectableObject` if not object apply """ - return self._select_best(self[oid], *args, **kwargs) + return self._select_best(self[__oid], *args, **kwargs) - def select_or_none(self, oid, *args, **kwargs): + def select_or_none(self, __oid, *args, **kwargs): """return the most specific object among those with the given oid according to the given context, or None if no object applies. """ try: - return self.select(oid, *args, **kwargs) + return self.select(__oid, *args, **kwargs) except (NoSelectableObject, ObjectNotFound): return None select_object = deprecated('[3.6] use select_or_none instead of select_object' diff -r 9001a74fcc82 -r c40652b93321 web/application.py --- a/web/application.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/application.py Tue Aug 10 18:35:07 2010 +0200 @@ -31,7 +31,7 @@ from cubicweb import set_log_methods, cwvreg from cubicweb import ( ValidationError, Unauthorized, AuthenticationError, NoSelectableObject, - RepositoryError, CW_EVENT_MANAGER) + RepositoryError, BadConnectionId, CW_EVENT_MANAGER) from cubicweb.dbapi import DBAPISession from cubicweb.web import LOGGER, component from cubicweb.web import ( @@ -48,48 +48,43 @@ def __init__(self, vreg): self.session_time = vreg.config['http-session-time'] or None - if self.session_time is not None: - assert self.session_time > 0 - self.cleanup_session_time = self.session_time - else: - self.cleanup_session_time = vreg.config['cleanup-session-time'] or 1440 * 60 - assert self.cleanup_session_time > 0 - self.cleanup_anon_session_time = vreg.config['cleanup-anonymous-session-time'] or 5 * 60 - assert self.cleanup_anon_session_time > 0 self.authmanager = vreg['components'].select('authmanager', vreg=vreg) + interval = (self.session_time or 0) / 2. if vreg.config.anonymous_user() is not None: - self.clean_sessions_interval = max( - 5 * 60, min(self.cleanup_session_time / 2., - self.cleanup_anon_session_time / 2.)) - else: - self.clean_sessions_interval = max( - 5 * 60, - self.cleanup_session_time / 2.) + self.cleanup_anon_session_time = vreg.config['cleanup-anonymous-session-time'] or 5 * 60 + assert self.cleanup_anon_session_time > 0 + if self.session_time is not None: + self.cleanup_anon_session_time = min(self.session_time, + self.cleanup_anon_session_time) + interval = self.cleanup_anon_session_time / 2. + # we don't want to check session more than once every 5 minutes + self.clean_sessions_interval = max(5 * 60, interval) def clean_sessions(self): """cleanup sessions which has not been unused since a given amount of time. Return the number of sessions which have been closed. """ self.debug('cleaning http sessions') + session_time = self.session_time closed, total = 0, 0 for session in self.current_sessions(): - no_use_time = (time() - session.last_usage_time) total += 1 - if session.anonymous_session: - if no_use_time >= self.cleanup_anon_session_time: + try: + last_usage_time = session.cnx.check() + except BadConnectionId: + self.close_session(session) + closed += 1 + else: + no_use_time = (time() - last_usage_time) + if session.anonymous_session: + if no_use_time >= self.cleanup_anon_session_time: + self.close_session(session) + closed += 1 + elif session_time is not None and no_use_time >= session_time: self.close_session(session) closed += 1 - elif no_use_time >= self.cleanup_session_time: - self.close_session(session) - closed += 1 return closed, total - closed - def has_expired(self, session): - """return True if the web session associated to the session is expired - """ - return not (self.session_time is None or - time() < session.last_usage_time + self.session_time) - def current_sessions(self): """return currently open sessions""" raise NotImplementedError() @@ -213,8 +208,6 @@ except AuthenticationError: req.remove_cookie(cookie, self.SESSION_VAR) raise - # remember last usage time for web session tracking - session.last_usage_time = time() def get_session(self, req, sessionid): return self.session_manager.get_session(req, sessionid) @@ -224,8 +217,6 @@ cookie = req.get_cookie() cookie[self.SESSION_VAR] = session.sessionid req.set_cookie(cookie, self.SESSION_VAR, maxage=None) - # remember last usage time for web session tracking - session.last_usage_time = time() if not session.anonymous_session: self._postlogin(req) return session diff -r 9001a74fcc82 -r c40652b93321 web/formfields.py --- a/web/formfields.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/formfields.py Tue Aug 10 18:35:07 2010 +0200 @@ -73,6 +73,7 @@ FormatConstraint) from cubicweb import Binary, tags, uilib +from cubicweb.utils import support_args from cubicweb.web import INTERNAL_FIELD_VALUE, ProcessFormError, eid_param, \ formwidgets as fw, uicfg @@ -345,7 +346,12 @@ def initial_typed_value(self, form, load_bytes): if self.value is not _MARKER: if callable(self.value): - return self.value(form) + if support_args(self.value, 'form', 'field'): + return self.value(form, self) + else: + warn("[3.10] field's value callback must now take form and field as argument", + DeprecationWarning) + return self.value(form) return self.value formattr = '%s_%s_default' % (self.role, self.name) if hasattr(form, formattr): diff -r 9001a74fcc82 -r c40652b93321 web/test/unittest_session.py --- a/web/test/unittest_session.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/test/unittest_session.py Tue Aug 10 18:35:07 2010 +0200 @@ -7,10 +7,11 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ from cubicweb.devtools.testlib import CubicWebTC +from cubicweb.web import InvalidSession class SessionTC(CubicWebTC): - def test_auto_reconnection(self): + def test_session_expiration(self): sm = self.app.session_handler.session_manager # make is if the web session has been opened by the session manager sm._sessions[self.cnx.sessionid] = self.websession @@ -23,11 +24,8 @@ # fake an incoming http query with sessionid in session cookie # don't use self.request() which try to call req.set_session req = self.requestcls(self.vreg) - websession = sm.get_session(req, sessionid) - self.assertEquals(len(sm._sessions), 1) - self.assertIs(websession, self.websession) - self.assertEquals(websession.sessionid, sessionid) - self.assertNotEquals(websession.sessionid, websession.cnx.sessionid) + self.assertRaises(InvalidSession, sm.get_session, req, sessionid) + self.assertEquals(len(sm._sessions), 0) finally: # avoid error in tearDown by telling this connection is closed... self.cnx._closed = True diff -r 9001a74fcc82 -r c40652b93321 web/uicfg.py --- a/web/uicfg.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/uicfg.py Tue Aug 10 18:35:07 2010 +0200 @@ -107,11 +107,8 @@ def init_primaryview_display_ctrl(rtag, sschema, rschema, oschema, role): if role == 'subject': oschema = '*' - label = rschema.type else: sschema = '*' - label = '%s_%s' % (rschema, role) - rtag.setdefault((sschema, rschema, oschema, role), 'label', label) rtag.counter += 1 rtag.setdefault((sschema, rschema, oschema, role), 'order', rtag.counter) diff -r 9001a74fcc82 -r c40652b93321 web/views/authentication.py --- a/web/views/authentication.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/authentication.py Tue Aug 10 18:35:07 2010 +0200 @@ -74,7 +74,7 @@ self.repo = vreg.config.repository(vreg) self.log_queries = vreg.config['query-log-file'] self.authinforetreivers = sorted(vreg['webauth'].possible_objects(vreg), - key=lambda x: x.order) + key=lambda x: x.order) # 2-uple login / password, login is None when no anonymous access # configured self.anoninfo = vreg.config.anonymous_user() @@ -98,25 +98,11 @@ if login and session.login != login: raise InvalidSession('login mismatch') try: - lock = session.reconnection_lock - except AttributeError: - lock = session.reconnection_lock = Lock() - # need to be locked two avoid duplicated reconnections on concurrent - # requests - with lock: - cnx = session.cnx - try: - # calling cnx.user() check connection validity, raise - # BadConnectionId on failure - user = cnx.user(req) - except BadConnectionId: - # check if a connection should be automatically restablished - if (login is None or login == session.login): - cnx = self._authenticate(session.login, session.authinfo) - user = cnx.user(req) - session.cnx = cnx - else: - raise InvalidSession('bad connection id') + # calling cnx.user() check connection validity, raise + # BadConnectionId on failure + user = session.cnx.user(req) + except BadConnectionId: + raise InvalidSession('bad connection id') return user def authenticate(self, req): diff -r 9001a74fcc82 -r c40652b93321 web/views/bookmark.py --- a/web/views/bookmark.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/bookmark.py Tue Aug 10 18:35:07 2010 +0200 @@ -115,7 +115,8 @@ path = req.relative_path() # XXX if vtitle specified in params, extract it and use it as default value # for bookmark's title - url = self.create_url(self.etype, __linkto=linkto, path=path) + url = req.vreg['etypes'].etype_class('Bookmark').cw_create_url( + req, __linkto=linkto, path=path) boxmenu.append(self.mk_action(req._('bookmark this page'), url, category='manage', id='bookmark')) if rset: diff -r 9001a74fcc82 -r c40652b93321 web/views/cwproperties.py --- a/web/views/cwproperties.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/cwproperties.py Tue Aug 10 18:35:07 2010 +0200 @@ -353,7 +353,7 @@ if vocab is not None: if callable(vocab): # list() just in case its a generator function - self.choices = list(vocab(form._cw)) + self.choices = list(vocab()) else: self.choices = vocab wdg = Select() diff -r 9001a74fcc82 -r c40652b93321 web/views/debug.py --- a/web/views/debug.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/debug.py Tue Aug 10 18:35:07 2010 +0200 @@ -119,10 +119,15 @@ if sessions: w(u'
    ') for session in sessions: + try: + last_usage_time = session.cnx.check() + except BadConnectionId: + w(u'
  • %s (INVALID)
  • ' % session.sessionid) + continue w(u'
  • %s (%s: %s)
    ' % ( session.sessionid, _('last usage'), - strftime(dtformat, localtime(session.last_usage_time)))) + strftime(dtformat, localtime(last_usage_time)))) dict_to_html(w, session.data) w(u'
  • ') w(u'
') diff -r 9001a74fcc82 -r c40652b93321 web/views/editcontroller.py --- a/web/views/editcontroller.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/editcontroller.py Tue Aug 10 18:35:07 2010 +0200 @@ -115,7 +115,7 @@ form = req.form # so we're able to know the main entity from the repository side if '__maineid' in form: - req.set_shared_data('__maineid', form['__maineid'], querydata=True) + req.set_shared_data('__maineid', form['__maineid'], txdata=True) # no specific action, generic edition self._to_create = req.data['eidmap'] = {} self._pending_fields = req.data['pendingfields'] = set() diff -r 9001a74fcc82 -r c40652b93321 web/views/editforms.py --- a/web/views/editforms.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/editforms.py Tue Aug 10 18:35:07 2010 +0200 @@ -169,7 +169,9 @@ def url(self): """return the url associated with this view""" - return self.create_url(self._cw.form.get('etype')) + req = self._cw + return req.vreg["etypes"].etype_class(req.form['etype']).cw_create_url( + req) def submited_message(self): """return the message that will be displayed on successful edition""" diff -r 9001a74fcc82 -r c40652b93321 web/views/facets.py --- a/web/views/facets.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/facets.py Tue Aug 10 18:35:07 2010 +0200 @@ -97,9 +97,9 @@ return if vid is None: vid = req.form.get('vid') - if self.bk_linkbox_template: - self.display_bookmark_link(rset) w = self.w + if self.bk_linkbox_template and req.vreg.schema['Bookmark'].has_perm(req, 'add'): + w(self.bookmark_link(rset)) w(u'
' % ( divid, xml_escape(json_dumps([divid, vid, paginate, self.facetargs()])))) w(u'
') @@ -113,22 +113,22 @@ wdg.render(w=self.w) w(u'
\n
\n') - def display_bookmark_link(self, rset): - eschema = self._cw.vreg.schema.eschema('Bookmark') - if eschema.has_perm(self._cw, 'add'): - bk_path = 'rql=%s' % self._cw.url_quote(rset.printable_rql()) - if self._cw.form.get('vid'): - bk_path += '&vid=%s' % self._cw.url_quote(self._cw.form['vid']) - bk_path = 'view?' + bk_path - bk_title = self._cw._('my custom search') - linkto = 'bookmarked_by:%s:subject' % self._cw.user.eid - bk_add_url = self._cw.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto) - bk_base_url = self._cw.build_url('add/Bookmark', title=bk_title, __linkto=linkto) - bk_link = u'%s' % ( - xml_escape(bk_base_url), - xml_escape(bk_add_url), - self._cw._('bookmark this search')) - self.w(self.bk_linkbox_template % bk_link) + def bookmark_link(self, rset): + req = self._cw + bk_path = u'rql=%s' % req.url_quote(rset.printable_rql()) + if req.form.get('vid'): + bk_path += u'&vid=%s' % req.url_quote(req.form['vid']) + bk_path = u'view?' + bk_path + bk_title = req._('my custom search') + linkto = u'bookmarked_by:%s:subject' % req.user.eid + bkcls = req.vreg['etypes'].etype_class('Bookmark') + bk_add_url = bkcls.cw_create_url(req, path=bk_path, title=bk_title, + __linkto=linkto) + bk_base_url = bkcls.cw_create_url(req, title=bk_title, __linkto=linkto) + bk_link = u'%s' % ( + xml_escape(bk_base_url), xml_escape(bk_add_url), + req._('bookmark this search')) + return self.bk_linkbox_template % bk_link def get_facets(self, rset, rqlst, mainvar): return self._cw.vreg['facets'].poss_visible_objects( diff -r 9001a74fcc82 -r c40652b93321 web/views/formrenderers.py --- a/web/views/formrenderers.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/formrenderers.py Tue Aug 10 18:35:07 2010 +0200 @@ -41,7 +41,7 @@ from cubicweb import tags from cubicweb.appobject import AppObject from cubicweb.selectors import is_instance, yes -from cubicweb.utils import json_dumps +from cubicweb.utils import json_dumps, support_args from cubicweb.web import eid_param, formwidgets as fwdgs @@ -53,6 +53,8 @@ name, value, checked, attrs) def field_label(form, field): + if callable(field.label): + return field.label(form, field) # XXX with 3.6 we can now properly rely on 'if field.role is not None' and # stop having a tuple for label if isinstance(field.label, tuple): # i.e. needs contextual translation @@ -133,7 +135,12 @@ help = [] descr = field.help if callable(descr): - descr = descr(form) + if support_args(descr, 'form', 'field'): + descr = descr(form, field) + else: + warn("[3.10] field's help callback must now take form and field as argument", + DeprecationWarning) + descr = descr(form) if descr: help.append('
%s
' % self._cw._(descr)) example = field.example_format(self._cw) diff -r 9001a74fcc82 -r c40652b93321 web/views/primary.py --- a/web/views/primary.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/primary.py Tue Aug 10 18:35:07 2010 +0200 @@ -25,6 +25,7 @@ from logilab.mtconverter import xml_escape from cubicweb import Unauthorized +from cubicweb.utils import support_args from cubicweb.selectors import match_kwargs from cubicweb.view import EntityView from cubicweb.schema import VIRTUAL_RTYPES, display_name @@ -143,10 +144,15 @@ if display_attributes: self.w(u'') for rschema, role, dispctrl, value in display_attributes: - try: - self._render_attribute(dispctrl, rschema, value, - role=role, table=True) - except TypeError: + if support_args(self._render_attribute, 'label'): + label = self._rel_label(entity, rschema, role, dispctrl) + self._render_attribute(label, value, table=True) + elif support_args(self._render_attribute, 'dispctrl'): + warn('[3.10] _render_attribute prototype has changed, please' + ' update %s' % self.__class___, DeprecationWarning) + self._render_attribute(dispctrl, rschema, value, role=role, + table=True) + else: warn('[3.6] _render_attribute prototype has changed, please' ' update %s' % self.__class___, DeprecationWarning) self._render_attribute(rschema, value, role=role, table=True) @@ -166,9 +172,14 @@ continue rset = self._relation_rset(entity, rschema, role, dispctrl) if rset: - try: + if support_args(self._render_relation, 'label'): + label = self._rel_label(entity, rschema, role, dispctrl) + self._render_relation(label, dispctrl, rset, 'autolimited') + elif not support_args(self._render_relation, 'showlabel'): + warn('[3.10] _render_relation prototype has changed, ' + 'please update %s' % self.__class__, DeprecationWarning) self._render_relation(dispctrl, rset, 'autolimited') - except TypeError: + else: warn('[3.6] _render_relation prototype has changed, ' 'please update %s' % self.__class__, DeprecationWarning) self._render_relation(rset, dispctrl, 'autolimited', @@ -205,7 +216,7 @@ rset = self._relation_rset(entity, rschema, role, dispctrl) if not rset: continue - label = display_name(self._cw, rschema.type, role) + label = self._rel_label(entity, rschema, role, dispctrl) vid = dispctrl.get('vid', 'sidebox') sideboxes.append( (label, rset, vid, dispctrl) ) sideboxes += self._cw.vreg['boxes'].poss_visible_objects( @@ -251,25 +262,16 @@ rset = dispctrl['filter'](rset) return rset - def _render_relation(self, dispctrl, rset, defaultvid): + def _render_relation(self, label, dispctrl, rset, defaultvid): self.w(u'
') - if dispctrl.get('showlabel', self.show_rel_label): - self.w(u'

%s

' % self._cw._(dispctrl['label'])) + if label: + self.w(u'

%s

' % label) self.wview(dispctrl.get('vid', defaultvid), rset, initargs={'dispctrl': dispctrl}) self.w(u'
') - def _render_attribute(self, dispctrl, rschema, value, - role='subject', table=False): - if rschema.final: - showlabel = dispctrl.get('showlabel', self.show_attr_label) - else: - showlabel = dispctrl.get('showlabel', self.show_rel_label) - if dispctrl.get('label'): - label = self._cw._(dispctrl.get('label')) - else: - label = display_name(self._cw, rschema.type, role) - self.field(label, value, show_label=showlabel, tr=False, table=table) + def _render_attribute(self, label, value, table=False): + self.field(label, value, tr=False, table=table) def _rel_label(self, entity, rschema, role, dispctrl): if rschema.final: diff -r 9001a74fcc82 -r c40652b93321 web/views/sessions.py --- a/web/views/sessions.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/sessions.py Tue Aug 10 18:35:07 2010 +0200 @@ -17,8 +17,8 @@ # with CubicWeb. If not, see . """web session component: by dfault the session is actually the db connection object :/ +""" -""" __docformat__ = "restructuredtext en" from cubicweb.web import InvalidSession @@ -51,9 +51,6 @@ if not sessionid in self._sessions: raise InvalidSession() session = self._sessions[sessionid] - if self.has_expired(session): - self.close_session(session) - raise InvalidSession() try: user = self.authmanager.validate_session(req, session) except InvalidSession: diff -r 9001a74fcc82 -r c40652b93321 web/views/startup.py --- a/web/views/startup.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/views/startup.py Tue Aug 10 18:35:07 2010 +0200 @@ -159,15 +159,14 @@ url = self._cw.build_url(etype) etypelink = u' %s (%d)' % ( xml_escape(url), label, nb) - yield (label, etypelink, self.add_entity_link(eschema, req)) + if eschema.has_perm(req, 'add'): + yield (label, etypelink, self.add_entity_link(etype)) - def add_entity_link(self, eschema, req): - """creates a [+] link for adding an entity if user has permission to do so""" - if not eschema.has_perm(req, 'add'): - return u'' + def add_entity_link(self, etype): + """creates a [+] link for adding an entity""" + url = self._cw.vreg["etypes"].etype_class(etype).cw_create_url(self._cw) return u'[+]' % ( - xml_escape(self.create_url(eschema.type)), - self._cw.__('add a %s' % eschema)) + xml_escape(url), self._cw.__('add a %s' % etype)) class IndexView(ManageView): diff -r 9001a74fcc82 -r c40652b93321 web/webconfig.py --- a/web/webconfig.py Tue Aug 10 16:10:28 2010 +0200 +++ b/web/webconfig.py Tue Aug 10 18:35:07 2010 +0200 @@ -135,17 +135,6 @@ "Should be 0 or greater than repository\'s session-time.", 'group': 'web', 'level': 2, }), - ('cleanup-session-time', - {'type' : 'time', - 'default': '24h', - 'help': 'duration of inactivity after which a connection ' - 'will be closed, to limit memory consumption (avoid sessions that ' - 'never expire and cause memory leak when http-session-time is 0). ' - 'So even if http-session-time is 0 and the user don\'t close his ' - 'browser, he will have to reauthenticate after this time of ' - 'inactivity. Default to 24h.', - 'group': 'web', 'level': 3, - }), ('cleanup-anonymous-session-time', {'type' : 'time', 'default': '5min',