# HG changeset patch # User Sylvain Thénault # Date 1306313966 -7200 # Node ID 20ef219267748119309411db4a34176cdb89e2fc # Parent 598a4f051259a9d8f827548d07e0391d4c4eaa09# Parent 5338d895b891d3c73996135a187db9b6a9e7023f backport stable diff -r 598a4f051259 -r 20ef21926774 dbapi.py --- a/dbapi.py Mon May 23 11:36:43 2011 +0200 +++ b/dbapi.py Wed May 25 10:59:26 2011 +0200 @@ -30,6 +30,7 @@ from itertools import count from warnings import warn from os.path import join +from uuid import uuid4 from logilab.common.logging_ext import set_log_methods from logilab.common.decorators import monkeypatch @@ -246,7 +247,7 @@ if cnx is not None: self.sessionid = cnx.sessionid else: - self.sessionid = None + self.sessionid = uuid4().hex @property def anonymous_session(self): diff -r 598a4f051259 -r 20ef21926774 entity.py --- a/entity.py Mon May 23 11:36:43 2011 +0200 +++ b/entity.py Wed May 25 10:59:26 2011 +0200 @@ -62,6 +62,23 @@ return True +def remove_ambiguous_rels(attr_set, subjtypes, schema): + '''remove from `attr_set` the relations of entity types `subjtypes` that have + different entity type sets as target''' + for attr in attr_set.copy(): + rschema = schema.rschema(attr) + if rschema.final: + continue + ttypes = None + for subjtype in subjtypes: + cur_ttypes = rschema.objects(subjtype) + if ttypes is None: + ttypes = cur_ttypes + elif cur_ttypes != ttypes: + attr_set.remove(attr) + break + + class Entity(AppObject): """an entity instance has e_schema automagically set on the class and instances has access to their issuing cursor. @@ -222,6 +239,7 @@ if len(targettypes) > 1: # find fetch_attrs common to all destination types fetchattrs = user._cw.vreg['etypes'].fetch_attrs(targettypes) + remove_ambiguous_rels(fetchattrs, targettypes, user._cw.vreg.schema) else: fetchattrs = etypecls.fetch_attrs etypecls._fetch_restrictions(var, varmaker, fetchattrs, @@ -739,6 +757,10 @@ etypecls = self._cw.vreg['etypes'].etype_class(targettypes[0]) if len(targettypes) > 1: fetchattrs = self._cw.vreg['etypes'].fetch_attrs(targettypes) + # XXX we should fetch ambiguous relation objects too but not + # recurse on them in _fetch_restrictions; it is easier to remove + # them completely for now, as it would require an deeper api rewrite + remove_ambiguous_rels(fetchattrs, targettypes, self._cw.vreg.schema) else: fetchattrs = etypecls.fetch_attrs rql = etypecls.fetch_rql(self._cw.user, [restriction], fetchattrs, diff -r 598a4f051259 -r 20ef21926774 i18n/de.po --- a/i18n/de.po Mon May 23 11:36:43 2011 +0200 +++ b/i18n/de.po Wed May 25 10:59:26 2011 +0200 @@ -4040,7 +4040,7 @@ msgid "tr_count" msgstr "" -msgid "transaction undoed" +msgid "transaction undone" msgstr "Transaktion rückgängig gemacht" #, python-format diff -r 598a4f051259 -r 20ef21926774 i18n/en.po --- a/i18n/en.po Mon May 23 11:36:43 2011 +0200 +++ b/i18n/en.po Wed May 25 10:59:26 2011 +0200 @@ -3935,7 +3935,7 @@ msgid "tr_count" msgstr "transition number" -msgid "transaction undoed" +msgid "transaction undone" msgstr "" #, python-format diff -r 598a4f051259 -r 20ef21926774 i18n/es.po --- a/i18n/es.po Mon May 23 11:36:43 2011 +0200 +++ b/i18n/es.po Wed May 25 10:59:26 2011 +0200 @@ -4090,7 +4090,7 @@ msgid "tr_count" msgstr "n° de transición" -msgid "transaction undoed" +msgid "transaction undone" msgstr "Transacciones Anuladas" #, python-format diff -r 598a4f051259 -r 20ef21926774 i18n/fr.po --- a/i18n/fr.po Mon May 23 11:36:43 2011 +0200 +++ b/i18n/fr.po Wed May 25 10:59:26 2011 +0200 @@ -4091,7 +4091,7 @@ msgid "tr_count" msgstr "n° de transition" -msgid "transaction undoed" +msgid "transaction undone" msgstr "transaction annulées" #, python-format diff -r 598a4f051259 -r 20ef21926774 selectors.py --- a/selectors.py Mon May 23 11:36:43 2011 +0200 +++ b/selectors.py Wed May 25 10:59:26 2011 +0200 @@ -1342,6 +1342,8 @@ @lltrace def __call__(self, cls, req, rset=None, row=None, col=0, **kwargs): + if not req.cnx: + return 0 user = req.user if user is None: return int('guests' in self.expected) diff -r 598a4f051259 -r 20ef21926774 server/sources/rql2sql.py --- a/server/sources/rql2sql.py Mon May 23 11:36:43 2011 +0200 +++ b/server/sources/rql2sql.py Wed May 25 10:59:26 2011 +0200 @@ -72,7 +72,9 @@ stack.append(self.source_execute) FunctionDescr.update_cb_stack = default_update_cb_stack -LENGTH = SQL_FUNCTIONS_REGISTRY.get_function('LENGTH') +get_func_descr = SQL_FUNCTIONS_REGISTRY.get_function + +LENGTH = get_func_descr('LENGTH') def length_source_execute(source, session, value): return len(value.getvalue()) LENGTH.source_execute = length_source_execute @@ -271,7 +273,9 @@ # when a query is grouped, ensure sort terms are grouped as well for sortterm in sorts: term = sortterm.term - if not isinstance(term, Constant): + if not (isinstance(term, Constant) or \ + (isinstance(term, Function) and + get_func_descr(term.name).aggregat)): for vref in term.iget_nodes(VariableRef): if not vref in groups: groups.append(vref) @@ -308,12 +312,12 @@ break if not isinstance(node, Function): raise QueryError() - func = SQL_FUNCTIONS_REGISTRY.get_function(node.name) - if func.source_execute is None: + funcd = get_func_descr(node.name) + if funcd.source_execute is None: raise QueryError('%s can not be called on mapped attribute' % node.name) state.source_cb_funcs.add(node) - func.update_cb_stack(stack) + funcd.update_cb_stack(stack) # IGenerator implementation for RQL->SQL ####################################### diff -r 598a4f051259 -r 20ef21926774 server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Mon May 23 11:36:43 2011 +0200 +++ b/server/test/unittest_rql2sql.py Wed May 25 10:59:26 2011 +0200 @@ -536,6 +536,14 @@ '''SELECT _X.cw_eid FROM cw_CWEType AS _X WHERE _X.cw_description!=parent AND _X.cw_description!=_X.cw_name'''), + + ('DISTINCT Any X, SUM(C) GROUPBY X ORDERBY SUM(C) DESC WHERE H todo_by X, H duration C', + '''SELECT DISTINCT rel_todo_by0.eid_to, SUM(_H.cw_duration) +FROM cw_Affaire AS _H, todo_by_relation AS rel_todo_by0 +WHERE rel_todo_by0.eid_from=_H.cw_eid +GROUP BY rel_todo_by0.eid_to +ORDER BY 2 DESC'''), + ] ADVANCED_WITH_GROUP_CONCAT = [ diff -r 598a4f051259 -r 20ef21926774 server/test/unittest_undo.py --- a/server/test/unittest_undo.py Mon May 23 11:36:43 2011 +0200 +++ b/server/test/unittest_undo.py Wed May 25 10:59:26 2011 +0200 @@ -169,7 +169,7 @@ ['CWUser']) self.assertEqual([et.name for et in toto.is_instance_of], ['CWUser']) - # undoing shouldn't be visble in undoable transaction, and the undoed + # undoing shouldn't be visble in undoable transaction, and the undone # transaction should be removed txs = self.cnx.undoable_transactions() self.assertEqual(len(txs), 2) diff -r 598a4f051259 -r 20ef21926774 test/data/schema.py --- a/test/data/schema.py Mon May 23 11:36:43 2011 +0200 +++ b/test/data/schema.py Wed May 25 10:59:26 2011 +0200 @@ -18,9 +18,11 @@ from yams.buildobjs import (EntityType, String, SubjectRelation, RelationDefinition) + from cubicweb.schema import (WorkflowableEntityType, RQLConstraint, RQLVocabularyConstraint) + class Personne(EntityType): nom = String(required=True) prenom = String() @@ -36,22 +38,40 @@ RQLVocabularyConstraint('NOT (S connait P, P nom "toto")'), RQLVocabularyConstraint('S travaille P, P nom "tutu"')]) + class Societe(EntityType): nom = String() evaluee = SubjectRelation('Note') + fournit = SubjectRelation(('Service', 'Produit'), cardinality='1*') + + +class Service(EntityType): + fabrique_par = SubjectRelation('Personne', cardinality='1*') + + +class Produit(EntityType): + fabrique_par = SubjectRelation('Usine', cardinality='1*') + + +class Usine(EntityType): + lieu = String(required=True) + class Note(EntityType): type = String() ecrit_par = SubjectRelation('Personne') + class SubNote(Note): __specializes_schema__ = True description = String() + class tags(RelationDefinition): subject = 'Tag' object = ('Personne', 'Note') + class evaluee(RelationDefinition): subject = 'CWUser' object = 'Note' @@ -59,5 +79,3 @@ class StateFull(WorkflowableEntityType): name = String() - - diff -r 598a4f051259 -r 20ef21926774 test/unittest_entity.py --- a/test/unittest_entity.py Mon May 23 11:36:43 2011 +0200 +++ b/test/unittest_entity.py Wed May 25 10:59:26 2011 +0200 @@ -250,7 +250,7 @@ 'WHERE E eid %(x)s, E tags X, X is IN (Personne), X nom AA, ' 'X modification_date AB') - def test_related_rql_ambigous_cant_use_fetch_order(self): + def test_related_rql_ambiguous_cant_use_fetch_order(self): tag = self.vreg['etypes'].etype_class('Tag')(self.request()) for ttype in self.schema['tags'].objects(): self.vreg['etypes'].etype_class(ttype).fetch_attrs = ('modification_date',) @@ -258,6 +258,18 @@ 'Any X,AA ORDERBY AA DESC ' 'WHERE E eid %(x)s, E tags X, X modification_date AA') + def test_related_rql_cant_fetch_ambiguous_rtype(self): + soc_etype = self.vreg['etypes'].etype_class('Societe') + soc = soc_etype(self.request()) + soc_etype.fetch_attrs = ('fournit',) + self.vreg['etypes'].etype_class('Service').fetch_attrs = ('fabrique_par',) + self.vreg['etypes'].etype_class('Produit').fetch_attrs = ('fabrique_par',) + self.vreg['etypes'].etype_class('Usine').fetch_attrs = ('lieu',) + self.vreg['etypes'].etype_class('Personne').fetch_attrs = ('nom',) + # XXX should be improved: we could fetch fabrique_par object too + self.assertEqual(soc.cw_related_rql('fournit', 'subject'), + 'Any X WHERE E eid %(x)s, E fournit X') + def test_unrelated_rql_security_1_manager(self): user = self.request().user rql = user.cw_unrelated_rql('use_email', 'EmailAddress', 'subject')[0] diff -r 598a4f051259 -r 20ef21926774 test/unittest_schema.py --- a/test/unittest_schema.py Mon May 23 11:36:43 2011 +0200 +++ b/test/unittest_schema.py Wed May 25 10:59:26 2011 +0200 @@ -166,10 +166,11 @@ 'CWSource', 'CWSourceHostConfig', 'CWSourceSchemaConfig', 'CWUniqueTogetherConstraint', 'CWUser', 'ExternalUri', 'File', 'Float', 'Int', 'Interval', 'Note', - 'Password', 'Personne', + 'Password', 'Personne', 'Produit', 'RQLExpression', - 'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint', + 'Service', 'Societe', 'State', 'StateFull', 'String', 'SubNote', 'SubWorkflowExitPoint', 'Tag', 'TZDatetime', 'TZTime', 'Time', 'Transition', 'TrInfo', + 'Usine', 'Workflow', 'WorkflowTransition'] self.assertListEqual(sorted(expected_entities), entities) relations = sorted([str(r) for r in schema.relations()]) @@ -188,14 +189,14 @@ 'ecrit_par', 'eid', 'evaluee', 'expression', 'exprtype', - 'final', 'firstname', 'for_user', + 'fabrique_par', 'final', 'firstname', 'for_user', 'fournit', 'from_entity', 'from_state', 'fulltext_container', 'fulltextindexed', 'has_text', 'identity', 'in_group', 'in_state', 'indexed', 'initial_state', 'inlined', 'internationalizable', 'is', 'is_instance_of', - 'label', 'last_login_time', 'latest_retrieval', 'login', + 'label', 'last_login_time', 'latest_retrieval', 'lieu', 'login', 'mainvars', 'match_host', 'modification_date', diff -r 598a4f051259 -r 20ef21926774 web/application.py --- a/web/application.py Mon May 23 11:36:43 2011 +0200 +++ b/web/application.py Wed May 25 10:59:26 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -204,17 +204,34 @@ except InvalidSession: # try to open a new session, so we get an anonymous session if # allowed - try: - session = self.open_session(req) - except AuthenticationError: - req.remove_cookie(cookie, sessioncookie) - raise + session = self.open_session(req) + else: + if not session.cnx: + # session exists but is not bound to a connection. We should + # try to authenticate + loginsucceed = False + try: + if self.open_session(req, allow_no_cnx=False): + loginsucceed = True + except Redirect: + # may be raised in open_session (by postlogin mechanism) + # on successful connection + loginsucceed = True + raise + except AuthenticationError: + # authentication failed, continue to use this session + req.set_session(session) + finally: + if loginsucceed: + # session should be replaced by new session created + # in open_session + self.session_manager.close_session(session) def get_session(self, req, sessionid): return self.session_manager.get_session(req, sessionid) - def open_session(self, req): - session = self.session_manager.open_session(req) + def open_session(self, req, allow_no_cnx=True): + session = self.session_manager.open_session(req, allow_no_cnx=allow_no_cnx) cookie = req.get_cookie() sessioncookie = self.session_cookie(req) cookie[sessioncookie] = session.sessionid @@ -279,10 +296,7 @@ sessions (i.e. a new connection may be created or an already existing one may be reused """ - try: - self.session_handler.set_session(req) - except AuthenticationError: - req.set_session(DBAPISession(None)) + self.session_handler.set_session(req) # publish methods ######################################################### @@ -365,11 +379,12 @@ # redirect is raised by edit controller when everything went fine, # so try to commit try: - txuuid = req.cnx.commit() - if txuuid is not None: - msg = u'[%s]' %( - req.build_url('undo', txuuid=txuuid), req._('undo')) - req.append_to_redirect_message(msg) + if req.cnx: + txuuid = req.cnx.commit() + if txuuid is not None: + msg = u'[%s]' %( + req.build_url('undo', txuuid=txuuid), req._('undo')) + req.append_to_redirect_message(msg) except ValidationError, ex: self.validation_error_handler(req, ex) except Unauthorized, ex: diff -r 598a4f051259 -r 20ef21926774 web/data/cubicweb.ajax.js --- a/web/data/cubicweb.ajax.js Mon May 23 11:36:43 2011 +0200 +++ b/web/data/cubicweb.ajax.js Wed May 25 10:59:26 2011 +0200 @@ -738,43 +738,37 @@ } ); -remoteExec = cw.utils.deprecatedFunction( - '[3.9] remoteExec() is deprecated, use loadRemote instead', - function(fname /* ... */) { - setProgressCursor(); - var props = { - fname: fname, - pageid: pageid, - arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) - }; - var result = jQuery.ajax({ - url: JSON_BASE_URL, - data: props, - async: false, - traditional: true - }).responseText; - if (result) { - result = cw.evalJSON(result); - } - resetCursor(); - return result; +function remoteExec(fname /* ... */) { + setProgressCursor(); + var props = { + fname: fname, + pageid: pageid, + arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) + }; + var result = jQuery.ajax({ + url: JSON_BASE_URL, + data: props, + async: false, + traditional: true + }).responseText; + if (result) { + result = cw.evalJSON(result); } -); + resetCursor(); + return result; +} -asyncRemoteExec = cw.utils.deprecatedFunction( - '[3.9] asyncRemoteExec() is deprecated, use loadRemote instead', - function(fname /* ... */) { - setProgressCursor(); - var props = { - fname: fname, - pageid: pageid, - arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) - }; - // 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); - deferred = deferred.addCallback(resetCursor); - return deferred; - } -); +function asyncRemoteExec(fname /* ... */) { + setProgressCursor(); + var props = { + fname: fname, + pageid: pageid, + arg: $.map(cw.utils.sliceList(arguments, 1), jQuery.toJSON) + }; + // 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); + deferred = deferred.addCallback(resetCursor); + return deferred; +} diff -r 598a4f051259 -r 20ef21926774 web/views/basecomponents.py --- a/web/views/basecomponents.py Mon May 23 11:36:43 2011 +0200 +++ b/web/views/basecomponents.py Wed May 25 10:59:26 2011 +0200 @@ -29,8 +29,8 @@ from logilab.common.deprecation import class_renamed from rql import parse -from cubicweb.selectors import (yes, multi_etypes_rset, match_form_params, - match_context, configuration_values, +from cubicweb.selectors import (yes, no_cnx, match_form_params, match_context, + multi_etypes_rset, configuration_values, anonymous_user, authenticated_user) from cubicweb.schema import display_name from cubicweb.utils import wrap_on_write @@ -88,6 +88,7 @@ class ApplLogo(HeaderComponent): """build the instance logo, usually displayed in the header""" __regid__ = 'logo' + __select__ = yes() # no need for a cnx order = -1 def render(self, w): @@ -150,7 +151,7 @@ class AnonUserStatusLink(HeaderComponent): __regid__ = 'userstatus' - __select__ = HeaderComponent.__select__ & anonymous_user() + __select__ = anonymous_user() context = _('header-right') order = HeaderComponent.order - 10 @@ -159,7 +160,7 @@ class AuthenticatedUserStatus(AnonUserStatusLink): - __select__ = HeaderComponent.__select__ & authenticated_user() + __select__ = authenticated_user() def render(self, w): # display useractions and siteactions @@ -180,7 +181,7 @@ """display messages given using the __message parameter into a special div section """ - __select__ = yes() + __select__ = ~no_cnx() __regid__ = 'applmessages' # don't want user to hide this component using an cwproperty cw_property_defs = {} diff -r 598a4f051259 -r 20ef21926774 web/views/basetemplates.py --- a/web/views/basetemplates.py Mon May 23 11:36:43 2011 +0200 +++ b/web/views/basetemplates.py Wed May 25 10:59:26 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. diff -r 598a4f051259 -r 20ef21926774 web/views/debug.py --- a/web/views/debug.py Mon May 23 11:36:43 2011 +0200 +++ b/web/views/debug.py Wed May 25 10:59:26 2011 +0200 @@ -1,4 +1,4 @@ -# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of CubicWeb. @@ -133,6 +133,9 @@ if sessions: w(u'