# HG changeset patch # User Sylvain Thénault # Date 1311790665 -7200 # Node ID d313666c171e9e613beb7f71a65fe284de96817b # Parent 73cadb5d0097c26554a547e8c8d492e04e216a9f# Parent 4df02855f4b31cd77b038ca6c7c9900785b256a7 backport stable diff -r 73cadb5d0097 -r d313666c171e .hgtags --- a/.hgtags Tue Jul 26 19:34:43 2011 +0200 +++ b/.hgtags Wed Jul 27 20:17:45 2011 +0200 @@ -216,3 +216,5 @@ cc0578049cbe8b1d40009728e36c17e45da1fc6b cubicweb-debian-version-3.13.1-1 f9227b9d61835f03163b8133a96da35db37a0c8d cubicweb-version-3.13.2 9ad5411199e00b2611366439b82f35d7d3285423 cubicweb-debian-version-3.13.2-1 +0e82e7e5a34f57d7239c7a42e48ba4d5e53abab2 cubicweb-version-3.13.3 +fb48c55cb80234bc0164c9bcc0e2cfc428836e5f cubicweb-debian-version-3.13.3-1 diff -r 73cadb5d0097 -r d313666c171e __pkginfo__.py --- a/__pkginfo__.py Tue Jul 26 19:34:43 2011 +0200 +++ b/__pkginfo__.py Wed Jul 27 20:17:45 2011 +0200 @@ -22,7 +22,7 @@ modname = distname = "cubicweb" -numversion = (3, 13, 2) +numversion = (3, 13, 3) version = '.'.join(str(num) for num in numversion) description = "a repository of entities / relations for knowledge management" diff -r 73cadb5d0097 -r d313666c171e debian/changelog --- a/debian/changelog Tue Jul 26 19:34:43 2011 +0200 +++ b/debian/changelog Wed Jul 27 20:17:45 2011 +0200 @@ -1,3 +1,9 @@ +cubicweb (3.13.3-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Wed, 27 Jul 2011 19:06:16 +0200 + cubicweb (3.13.2-1) unstable; urgency=low * new upstream release diff -r 73cadb5d0097 -r d313666c171e hooks/__init__.py --- a/hooks/__init__.py Tue Jul 26 19:34:43 2011 +0200 +++ b/hooks/__init__.py Wed Jul 27 20:17:45 2011 +0200 @@ -57,7 +57,7 @@ or not repo.config.source_enabled(source) or not source.config['synchronize']): continue - session = repo.internal_session() + session = repo.internal_session(safe=True) try: stats = source.pull_data(session) if stats.get('created'): diff -r 73cadb5d0097 -r d313666c171e hooks/metadata.py --- a/hooks/metadata.py Tue Jul 26 19:34:43 2011 +0200 +++ b/hooks/metadata.py Wed Jul 27 20:17:45 2011 +0200 @@ -42,8 +42,10 @@ def __call__(self): timestamp = datetime.now() edited = self.entity.cw_edited - edited.setdefault('creation_date', timestamp) - edited.setdefault('modification_date', timestamp) + if not edited.get('creation_date'): + edited['creation_date'] = timestamp + if not edited.get('modification_date'): + edited['modification_date'] = timestamp if not self._cw.get_shared_data('do-not-insert-cwuri'): cwuri = u'%s%s' % (self._cw.base_url(), self.entity.eid) edited.setdefault('cwuri', cwuri) diff -r 73cadb5d0097 -r d313666c171e i18n/de.po --- a/i18n/de.po Tue Jul 26 19:34:43 2011 +0200 +++ b/i18n/de.po Wed Jul 27 20:17:45 2011 +0200 @@ -1914,10 +1914,6 @@ msgid "cw_schema_object" msgstr "" -msgctxt "CWAttribute" -msgid "cw_schema_object" -msgstr "" - msgctxt "CWEType" msgid "cw_schema_object" msgstr "" diff -r 73cadb5d0097 -r d313666c171e i18n/en.po --- a/i18n/en.po Tue Jul 26 19:34:43 2011 +0200 +++ b/i18n/en.po Wed Jul 27 20:17:45 2011 +0200 @@ -1869,10 +1869,6 @@ msgid "cw_schema_object" msgstr "mapped by" -msgctxt "CWAttribute" -msgid "cw_schema_object" -msgstr "mapped by" - msgctxt "CWEType" msgid "cw_schema_object" msgstr "mapped by" @@ -4407,3 +4403,7 @@ #, python-format msgid "you should un-inline relation %s which is supported and may be crossed " msgstr "" + +#~ msgctxt "CWAttribute" +#~ msgid "cw_schema_object" +#~ msgstr "mapped by" diff -r 73cadb5d0097 -r d313666c171e i18n/es.po --- a/i18n/es.po Tue Jul 26 19:34:43 2011 +0200 +++ b/i18n/es.po Wed Jul 27 20:17:45 2011 +0200 @@ -1943,10 +1943,6 @@ msgid "cw_schema_object" msgstr "mapeado por" -msgctxt "CWAttribute" -msgid "cw_schema_object" -msgstr "mapeado por" - msgctxt "CWEType" msgid "cw_schema_object" msgstr "mapeado por" @@ -4576,3 +4572,7 @@ msgstr "" "usted debe quitar la puesta en línea de la relación %s que es aceptada y " "puede ser cruzada" + +#~ msgctxt "CWAttribute" +#~ msgid "cw_schema_object" +#~ msgstr "mapeado por" diff -r 73cadb5d0097 -r d313666c171e i18n/fr.po --- a/i18n/fr.po Tue Jul 26 19:34:43 2011 +0200 +++ b/i18n/fr.po Wed Jul 27 20:17:45 2011 +0200 @@ -1946,10 +1946,6 @@ msgid "cw_schema_object" msgstr "mappé par" -msgctxt "CWAttribute" -msgid "cw_schema_object" -msgstr "mappé par" - msgctxt "CWEType" msgid "cw_schema_object" msgstr "mappé par" @@ -4576,3 +4572,7 @@ msgstr "" "vous devriez enlevé la mise en ligne de la relation %s qui est supportée et " "peut-être croisée" + +#~ msgctxt "CWAttribute" +#~ msgid "cw_schema_object" +#~ msgstr "mappé par" diff -r 73cadb5d0097 -r d313666c171e server/repository.py --- a/server/repository.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/repository.py Wed Jul 27 20:17:45 2011 +0200 @@ -927,14 +927,16 @@ nbclosed += 1 return nbclosed - def internal_session(self, cnxprops=None): - """return a dbapi like connection/cursor using internal user which - have every rights on the repository. You'll *have to* commit/rollback - or close (rollback implicitly) the session once the job's done, else - you'll leak connections set up to the time where no one is - available, causing irremediable freeze... + def internal_session(self, cnxprops=None, safe=False): + """return a dbapi like connection/cursor using internal user which have + every rights on the repository. The `safe` argument is a boolean flag + telling if integrity hooks should be activated or not. + + *YOU HAVE TO* commit/rollback or close (rollback implicitly) the + session once the job's done, else you'll leak connections set up to the + time where no one is available, causing irremediable freeze... """ - session = InternalSession(self, cnxprops) + session = InternalSession(self, cnxprops, safe) session.set_cnxset() return session @@ -1030,7 +1032,7 @@ return extid def extid2eid(self, source, extid, etype, session=None, insert=True, - sourceparams=None): + complete=True, commit=True, sourceparams=None): """Return eid from a local id. If the eid is a negative integer, that means the entity is known but has been copied back to the system source hence should be ignored. @@ -1089,15 +1091,16 @@ session, extid, etype, eid, sourceparams) if source.should_call_hooks: self.hm.call_hooks('before_add_entity', session, entity=entity) - # XXX call add_info with complete=False ? - self.add_info(session, entity, source, extid) + self.add_info(session, entity, source, extid, complete=complete) source.after_entity_insertion(session, extid, entity, sourceparams) if source.should_call_hooks: self.hm.call_hooks('after_add_entity', session, entity=entity) - session.commit(free_cnxset) + if commit or free_cnxset: + session.commit(free_cnxset) return eid - except: - session.rollback(free_cnxset) + except Exception: + if commit or free_cnxset: + session.rollback(free_cnxset) raise def add_info(self, session, entity, source, extid=None, complete=True): diff -r 73cadb5d0097 -r d313666c171e server/rqlannotation.py --- a/server/rqlannotation.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/rqlannotation.py Wed Jul 27 20:17:45 2011 +0200 @@ -211,16 +211,22 @@ relation for the rhs variable """ principal = None + others = [] # sort for test predictability for rel in sorted(relations, key=lambda x: (x.children[0].name, x.r_type)): # only equality relation with a variable as rhs may be principal if rel.operator() not in ('=', 'IS') \ or not isinstance(rel.children[1].children[0], VariableRef) or rel.neged(strict=True): continue + if rel.optional: + others.append(rel) + continue if rel.scope is rel.stmt: return rel principal = rel if principal is None: + if others: + return others[0] raise BadRQLQuery('unable to find principal in %s' % ', '.join( r.as_string() for r in relations)) return principal diff -r 73cadb5d0097 -r d313666c171e server/session.py --- a/server/session.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/session.py Wed Jul 27 20:17:45 2011 +0200 @@ -1276,12 +1276,13 @@ is_internal_session = True running_dbapi_query = False - def __init__(self, repo, cnxprops=None): + def __init__(self, repo, cnxprops=None, safe=False): super(InternalSession, self).__init__(InternalManager(), repo, cnxprops, _id='internal') self.user._cw = self # XXX remove when "vreg = user._cw.vreg" hack in entity.py is gone self.cnxtype = 'inmemory' - self.disable_hook_categories('integrity') + if not safe: + self.disable_hook_categories('integrity') @property def cnxset(self): diff -r 73cadb5d0097 -r d313666c171e server/sources/__init__.py --- a/server/sources/__init__.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/sources/__init__.py Wed Jul 27 20:17:45 2011 +0200 @@ -25,6 +25,7 @@ from logging import getLogger from logilab.common import configuration +from logilab.common.deprecation import deprecated from yams.schema import role_name @@ -269,12 +270,6 @@ # external source api ###################################################### - def eid2extid(self, eid, session=None): - return self.repo.eid2extid(self, eid, session) - - def extid2eid(self, value, etype, session=None, **kwargs): - return self.repo.extid2eid(self, value, etype, session, **kwargs) - def support_entity(self, etype, write=False): """return true if the given entity's type is handled by this adapter if write is true, return true only if it's a RW support @@ -522,6 +517,15 @@ pass + @deprecated('[3.13] use repo.eid2extid(source, eid, session)') + def eid2extid(self, eid, session=None): + return self.repo.eid2extid(self, eid, session) + + @deprecated('[3.13] use extid2eid(source, value, etype, session, **kwargs)') + def extid2eid(self, value, etype, session=None, **kwargs): + return self.repo.extid2eid(self, value, etype, session, **kwargs) + + class TrFunc(object): """lower, upper""" def __init__(self, trname, index, attrname=None): diff -r 73cadb5d0097 -r d313666c171e server/sources/datafeed.py --- a/server/sources/datafeed.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/sources/datafeed.py Wed Jul 27 20:17:45 2011 +0200 @@ -147,6 +147,7 @@ return True def release_synchronization_lock(self, session): + session.set_cnxset() session.execute('SET X synchronizing FALSE WHERE X eid %(x)s', {'x': self.eid}) session.commit() @@ -220,9 +221,6 @@ entity.cw_edited['cwuri'] = unicode(lid) entity.cw_edited.set_defaults() sourceparams['parser'].before_entity_copy(entity, sourceparams) - # avoid query to search full-text indexed attributes - for attr in entity.e_schema.indexable_attributes(): - entity.cw_edited.setdefault(attr, u'') return entity def after_entity_insertion(self, session, lid, entity, sourceparams): @@ -267,15 +265,20 @@ """return an entity for the given uri. May return None if it should be skipped """ + session = self._cw # if cwsource is specified and repository has a source with the same # name, call extid2eid on that source so entity will be properly seen as # coming from this source - source = self._cw.repo.sources_by_uri.get( - sourceparams.pop('cwsource', None), self.source) + source_uri = sourceparams.pop('cwsource', None) + if source_uri is not None and source_uri != 'system': + source = session.repo.sources_by_uri.get(source_uri, self.source) + else: + source = self.source sourceparams['parser'] = self try: - eid = source.extid2eid(str(uri), etype, self._cw, - sourceparams=sourceparams) + eid = session.repo.extid2eid(source, str(uri), etype, session, + complete=False, commit=False, + sourceparams=sourceparams) except ValidationError, ex: self.source.error('error while creating %s: %s', etype, ex) return None @@ -285,14 +288,14 @@ # Don't give etype to entity_from_eid so we get UnknownEid if the # entity has been removed try: - entity = self._cw.entity_from_eid(-eid) + entity = session.entity_from_eid(-eid) except UnknownEid: return None self.notify_updated(entity) # avoid later update from the source's data return entity if self.sourceuris is not None: self.sourceuris.pop(str(uri), None) - return self._cw.entity_from_eid(eid, etype) + return session.entity_from_eid(eid, etype) def process(self, url, partialcommit=True): """main callback: process the url""" diff -r 73cadb5d0097 -r d313666c171e server/sources/ldapuser.py --- a/server/sources/ldapuser.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/sources/ldapuser.py Wed Jul 27 20:17:45 2011 +0200 @@ -310,7 +310,7 @@ except Exception: self.error('while trying to authenticate %s', user, exc_info=True) raise AuthenticationError() - eid = self.extid2eid(user['dn'], 'CWUser', session) + eid = self.repo.extid2eid(self, user['dn'], 'CWUser', session) if eid < 0: # user has been moved away from this source raise AuthenticationError() @@ -423,7 +423,7 @@ filteredres = [] for resdict in res: # get sure the entity exists in the system table - eid = self.extid2eid(resdict['dn'], 'CWUser', session) + eid = self.repo.extid2eid(self, resdict['dn'], 'CWUser', session) for eidfilter in eidfilters: if not eidfilter(eid): break @@ -537,7 +537,7 @@ res = cnx.result(all=0)[1] except ldap.NO_SUCH_OBJECT: self.info('ldap NO SUCH OBJECT') - eid = self.extid2eid(base, 'CWUser', session, insert=False) + eid = self.repo.extid2eid(self, base, 'CWUser', session, insert=False) if eid: self.warning('deleting ldap user with eid %s and dn %s', eid, base) @@ -646,6 +646,7 @@ """generate an LDAP filter for a rql query""" def __init__(self, source, session, args=None, mainvars=()): self.source = source + self.repo = source.repo self._ldap_attrs = source.user_rev_attrs self._base_filters = source.base_filters self._session = session @@ -751,7 +752,7 @@ }[rhs.operator] self._eidfilters.append(filter) return - dn = self.source.eid2extid(eid, self._session) + dn = self.repo.eid2extid(self.source, eid, self._session) raise GotDN(dn) try: filter = '(%s%s)' % (self._ldap_attrs[relation.r_type], diff -r 73cadb5d0097 -r d313666c171e server/sources/pyrorql.py --- a/server/sources/pyrorql.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/sources/pyrorql.py Wed Jul 27 20:17:45 2011 +0200 @@ -281,8 +281,8 @@ continue for etype, extid in deleted: try: - eid = self.extid2eid(str(extid), etype, session, - insert=False) + eid = self.repo.extid2eid(self, str(extid), etype, session, + insert=False) # entity has been deleted from external repository but is not known here if eid is not None: entity = session.entity_from_eid(eid, etype) @@ -423,7 +423,7 @@ def _entity_relations_and_kwargs(self, session, entity): relations = [] - kwargs = {'x': self.eid2extid(entity.eid, session)} + kwargs = {'x': self.repo.eid2extid(self, entity.eid, session)} for key, val in entity.cw_attr_cache.iteritems(): relations.append('X %s %%(%s)s' % (key, key)) kwargs[key] = val @@ -449,15 +449,15 @@ return cu = session.cnxset[self.uri] cu.execute('DELETE %s X WHERE X eid %%(x)s' % entity.__regid__, - {'x': self.eid2extid(entity.eid, session)}) + {'x': self.repo.eid2extid(self, entity.eid, session)}) self._query_cache.clear() def add_relation(self, session, subject, rtype, object): """add a relation to the source""" cu = session.cnxset[self.uri] cu.execute('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype, - {'x': self.eid2extid(subject, session), - 'y': self.eid2extid(object, session)}) + {'x': self.repo.eid2extid(self, subject, session), + 'y': self.repo.eid2extid(self, object, session)}) self._query_cache.clear() session.entity_from_eid(subject).cw_clear_all_caches() session.entity_from_eid(object).cw_clear_all_caches() @@ -470,8 +470,8 @@ return cu = session.cnxset[self.uri] cu.execute('DELETE X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype, - {'x': self.eid2extid(subject, session), - 'y': self.eid2extid(object, session)}) + {'x': self.repo.eid2extid(self, subject, session), + 'y': self.repo.eid2extid(self, object, session)}) self._query_cache.clear() session.entity_from_eid(subject).cw_clear_all_caches() session.entity_from_eid(object).cw_clear_all_caches() @@ -481,6 +481,7 @@ """translate a local rql query to be executed on a distant repository""" def __init__(self, source): self.source = source + self.repo = source.repo self.current_operator = None def _accept_children(self, node): @@ -676,7 +677,7 @@ def eid2extid(self, eid): try: - return self.source.eid2extid(eid, self._session) + return self.repo.eid2extid(self.source, eid, self._session) except UnknownEid: operator = self.current_operator if operator is not None and operator != '=': diff -r 73cadb5d0097 -r d313666c171e server/sources/rql2sql.py --- a/server/sources/rql2sql.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/sources/rql2sql.py Wed Jul 27 20:17:45 2011 +0200 @@ -1292,9 +1292,16 @@ relation.r_type) try: self._state.ignore_varmap = True - return '%s%s' % (lhssql, relation.children[1].accept(self)) + sql = lhssql + relation.children[1].accept(self) finally: self._state.ignore_varmap = False + if relation.optional == 'right': + leftalias = self._var_table(principal.children[0].variable) + rightalias = self._var_table(relation.children[0].variable) + self._state.replace_tables_by_outer_join( + leftalias, rightalias, 'LEFT', sql) + return '' + return sql return '' def _visit_attribute_relation(self, rel): @@ -1372,12 +1379,15 @@ def visit_comparison(self, cmp): """generate SQL for a comparison""" + optional = getattr(cmp, 'optional', None) # rql < 0.30 if len(cmp.children) == 2: - # XXX occurs ? + # simplified expression from HAVING clause lhs, rhs = cmp.children else: lhs = None rhs = cmp.children[0] + assert not optional + sql = None operator = cmp.operator if operator in ('LIKE', 'ILIKE'): if operator == 'ILIKE' and not self.dbhelper.ilike_support: @@ -1385,18 +1395,39 @@ else: operator = ' %s ' % operator elif operator == 'REGEXP': - return ' %s' % self.dbhelper.sql_regexp_match_expression(rhs.accept(self)) + sql = ' %s' % self.dbhelper.sql_regexp_match_expression(rhs.accept(self)) elif (operator == '=' and isinstance(rhs, Constant) and rhs.eval(self._args) is None): if lhs is None: - return ' IS NULL' - return '%s IS NULL' % lhs.accept(self) + sql = ' IS NULL' + else: + sql = '%s IS NULL' % lhs.accept(self) elif isinstance(rhs, Function) and rhs.name == 'IN': assert operator == '=' operator = ' ' - if lhs is None: - return '%s%s'% (operator, rhs.accept(self)) - return '%s%s%s'% (lhs.accept(self), operator, rhs.accept(self)) + if sql is None: + if lhs is None: + sql = '%s%s'% (operator, rhs.accept(self)) + else: + sql = '%s%s%s'% (lhs.accept(self), operator, rhs.accept(self)) + if optional is None: + return sql + leftvars = cmp.children[0].get_nodes(VariableRef) + assert len(leftvars) == 1 + leftalias = self._var_table(leftvars[0].variable.stinfo['attrvar']) + rightvars = cmp.children[1].get_nodes(VariableRef) + assert len(rightvars) == 1 + rightalias = self._var_table(rightvars[0].variable.stinfo['attrvar']) + if optional == 'right': + self._state.replace_tables_by_outer_join( + leftalias, rightalias, 'LEFT', sql) + elif optional == 'left': + self._state.replace_tables_by_outer_join( + rightalias, leftalias, 'LEFT', sql) + else: + self._state.replace_tables_by_outer_join( + leftalias, rightalias, 'FULL', sql) + return '' def visit_mathexpression(self, mexpr): """generate SQL for a mathematic expression""" @@ -1438,15 +1469,15 @@ value = constant.value if constant.type == 'etype': return value - if constant.type == 'Int' and isinstance(constant.parent, SortTerm): - return value + if constant.type == 'Int': # XXX Float? + return str(value) if constant.type in ('Date', 'Datetime'): rel = constant.relation() if rel is not None: rel._q_needcast = value return self.keyword_map[value]() if constant.type == 'Boolean': - value = self.dbhelper.boolean_value(value) + return str(self.dbhelper.boolean_value(value)) if constant.type == 'Substitute': try: # we may found constant from simplified var in varmap diff -r 73cadb5d0097 -r d313666c171e server/test/data/migratedapp/schema.py --- a/server/test/data/migratedapp/schema.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/test/data/migratedapp/schema.py Wed Jul 27 20:17:45 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. @@ -17,7 +17,7 @@ # with CubicWeb. If not, see . """cw.server.migraction test""" from yams.buildobjs import (EntityType, RelationType, RelationDefinition, - SubjectRelation, + SubjectRelation, Bytes, RichString, String, Int, Boolean, Datetime, Date) from yams.constraints import SizeConstraint, UniqueConstraint from cubicweb.schema import (WorkflowableEntityType, RQLConstraint, @@ -36,6 +36,7 @@ sujet = String(fulltextindexed=True, constraints=[SizeConstraint(256)]) concerne = SubjectRelation('Societe') + opt_attr = Bytes() class concerne(RelationType): __permissions__ = { diff -r 73cadb5d0097 -r d313666c171e server/test/data/schema.py --- a/server/test/data/schema.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/test/data/schema.py Wed Jul 27 20:17:45 2011 +0200 @@ -24,9 +24,6 @@ RQLConstraint, RQLUniqueConstraint, ERQLExpression, RRQLExpression) -class BFSSTestable(EntityType): - opt_attr = Bytes() - class Affaire(WorkflowableEntityType): __permissions__ = { 'read': ('managers', @@ -45,6 +42,7 @@ duration = Int() invoiced = Float() + opt_attr = Bytes() depends_on = SubjectRelation('Affaire') require_permission = SubjectRelation('CWPermission') diff -r 73cadb5d0097 -r d313666c171e server/test/unittest_datafeed.py --- a/server/test/unittest_datafeed.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/test/unittest_datafeed.py Wed Jul 27 20:17:45 2011 +0200 @@ -96,9 +96,8 @@ # test_delete_source req = self.request() - with self.debugged('DBG_RQL'): - req.execute('DELETE CWSource S WHERE S name "myfeed"') - self.commit() + req.execute('DELETE CWSource S WHERE S name "myfeed"') + self.commit() self.failIf(self.execute('Card X WHERE X title "cubicweb.org"')) self.failIf(self.execute('Any X WHERE X has_text "cubicweb.org"')) diff -r 73cadb5d0097 -r d313666c171e server/test/unittest_ldapuser.py --- a/server/test/unittest_ldapuser.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/test/unittest_ldapuser.py Wed Jul 27 20:17:45 2011 +0200 @@ -61,7 +61,7 @@ # no such user raise AuthenticationError() # don't check upassword ! - return self.extid2eid(user['dn'], 'CWUser', session) + return self.repo.extid2eid(self, user['dn'], 'CWUser', session) def setUpModule(*args): create_slapd_configuration(LDAPUserSourceTC.config) diff -r 73cadb5d0097 -r d313666c171e server/test/unittest_querier.py --- a/server/test/unittest_querier.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/test/unittest_querier.py Wed Jul 27 20:17:45 2011 +0200 @@ -800,6 +800,12 @@ 'Password', 'String', 'TZDatetime', 'TZTime', 'Time']) + req = self.session + req.create_entity('Personne', nom=u'louis', test=True) + self.assertEqual(len(req.execute('Any X WHERE X test %(val)s', {'val': True})), 1) + self.assertEqual(len(req.execute('Any X WHERE X test TRUE')), 1) + self.assertEqual(len(req.execute('Any X WHERE X test %(val)s', {'val': False})), 0) + self.assertEqual(len(req.execute('Any X WHERE X test FALSE')), 0) def test_select_constant(self): rset = self.execute('Any X, "toto" ORDERBY X WHERE X is CWGroup') diff -r 73cadb5d0097 -r d313666c171e server/test/unittest_repository.py --- a/server/test/unittest_repository.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/test/unittest_repository.py Wed Jul 27 20:17:45 2011 +0200 @@ -68,7 +68,7 @@ namecol, table, finalcol)) self.assertEqual(cu.fetchall(), []) cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s' - % (namecol, table, finalcol, namecol), {'final': 'TRUE'}) + % (namecol, table, finalcol, namecol), {'final': True}) self.assertEqual(cu.fetchall(), [(u'BigInt',), (u'Boolean',), (u'Bytes',), (u'Date',), (u'Datetime',), (u'Decimal',),(u'Float',), diff -r 73cadb5d0097 -r d313666c171e server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/test/unittest_rql2sql.py Wed Jul 27 20:17:45 2011 +0200 @@ -807,6 +807,11 @@ OUTER_JOIN = [ + + ('Any U,G WHERE U login L, G name L?, G is CWGroup', + '''SELECT _U.cw_eid, _G.cw_eid +FROM cw_CWUser AS _U LEFT OUTER JOIN cw_CWGroup AS _G ON (_G.cw_name=_U.cw_login)'''), + ('Any X,S WHERE X travaille S?', '''SELECT _X.cw_eid, rel_travaille0.eid_to FROM cw_Personne AS _X LEFT OUTER JOIN travaille_relation AS rel_travaille0 ON (rel_travaille0.eid_from=_X.cw_eid)''' @@ -969,6 +974,18 @@ '''SELECT _CFG.cw_ecrit_par, _CALIBCFG.cw_eid, _CFG.cw_eid FROM cw_Note AS _CFG LEFT OUTER JOIN cw_Note AS _CALIBCFG ON (_CALIBCFG.cw_ecrit_par=_CFG.cw_ecrit_par) WHERE _CFG.cw_ecrit_par=1'''), + + ('Any U,G WHERE U login UL, G name GL, G is CWGroup HAVING UPPER(UL)=UPPER(GL)?', + '''SELECT _U.cw_eid, _G.cw_eid +FROM cw_CWUser AS _U LEFT OUTER JOIN cw_CWGroup AS _G ON (UPPER(_U.cw_login)=UPPER(_G.cw_name))'''), + + ('Any U,G WHERE U login UL, G name GL, G is CWGroup HAVING UPPER(UL)?=UPPER(GL)', + '''SELECT _U.cw_eid, _G.cw_eid +FROM cw_CWGroup AS _G LEFT OUTER JOIN cw_CWUser AS _U ON (UPPER(_U.cw_login)=UPPER(_G.cw_name))'''), + + ('Any U,G WHERE U login UL, G name GL, G is CWGroup HAVING UPPER(UL)?=UPPER(GL)?', + '''SELECT _U.cw_eid, _G.cw_eid +FROM cw_CWUser AS _U FULL OUTER JOIN cw_CWGroup AS _G ON (UPPER(_U.cw_login)=UPPER(_G.cw_name))'''), ] VIRTUAL_VARS = [ @@ -1871,7 +1888,7 @@ backend = 'sqlite' def _norm_sql(self, sql): - return sql.strip().replace(' ILIKE ', ' LIKE ') + return sql.strip().replace(' ILIKE ', ' LIKE ').replace('TRUE', '1').replace('FALSE', '0') def test_date_extraction(self): self._check("Any MONTH(D) WHERE P is Personne, P creation_date D", diff -r 73cadb5d0097 -r d313666c171e server/test/unittest_storage.py --- a/server/test/unittest_storage.py Tue Jul 26 19:34:43 2011 +0200 +++ b/server/test/unittest_storage.py Wed Jul 27 20:17:45 2011 +0200 @@ -259,7 +259,7 @@ @tag('update', 'NULL') def test_bfss_update_to_None(self): - f = self.session.create_entity('BFSSTestable', opt_attr=Binary('toto')) + f = self.session.create_entity('Affaire', opt_attr=Binary('toto')) self.session.commit() self.session.set_pool() f.set_attributes(opt_attr=None) diff -r 73cadb5d0097 -r d313666c171e sobjects/parsers.py --- a/sobjects/parsers.py Tue Jul 26 19:34:43 2011 +0200 +++ b/sobjects/parsers.py Wed Jul 27 20:17:45 2011 +0200 @@ -75,7 +75,10 @@ if rschema == 'eid': continue attrtype = eschema.destination(rschema) - typeddict[rschema.type] = converters[attrtype](stringdict[rschema]) + value = stringdict[rschema] + if value is not None: + value = converters[attrtype](value) + typeddict[rschema.type] = value return typeddict def rtype_role_rql(rtype, role): @@ -244,7 +247,7 @@ except ValueError: return url + '?' + self._cw.build_url_params(**params) try: - etype = self._cw.vreg.case_insensitive_etypes[etype] + etype = self._cw.vreg.case_insensitive_etypes[etype.lower()] except KeyError: return url + '?' + self._cw.build_url_params(**params) if add_relations: @@ -294,9 +297,12 @@ # relation related = rels.setdefault(role, {}).setdefault(child.tag, []) related += [ritem for ritem, _ in self.parser.parse_etree(child)] - else: + elif child.text: # attribute item[child.tag] = unicode(child.text) + else: + # None attribute (empty tag) + item[child.tag] = None return item, rels diff -r 73cadb5d0097 -r d313666c171e sobjects/test/unittest_parsers.py --- a/sobjects/test/unittest_parsers.py Tue Jul 26 19:34:43 2011 +0200 +++ b/sobjects/test/unittest_parsers.py Wed Jul 27 20:17:45 2011 +0200 @@ -40,7 +40,7 @@ BASEXML = ''.join(u''' - + sthenault toto 2011-01-25 14:14:06 @@ -113,20 +113,28 @@ 2011-01-25 14:14:06 2010-01-22 10:27:59 2011-01-25 14:14:06 + + + '''.splitlines() ) + + class CWEntityXMLParserTC(CubicWebTC): + """/!\ this test use a pre-setup database /!\, if you modify above xml, + REMOVE THE DATABASE TEMPLATE else it won't be considered + """ test_db_id = 'xmlparser' @classmethod def pre_setup_database(cls, session, config): - source = session.create_entity('CWSource', name=u'myfeed', type=u'datafeed', + myfeed = session.create_entity('CWSource', name=u'myfeed', type=u'datafeed', parser=u'cw.entityxml', url=BASEXML) - session.create_entity('CWSource', name=u'myotherfeed', type=u'datafeed', - parser=u'cw.entityxml', url=OTHERXML) + myotherfeed = session.create_entity('CWSource', name=u'myotherfeed', type=u'datafeed', + parser=u'cw.entityxml', url=OTHERXML) session.commit() - source.init_mapping([(('CWUser', 'use_email', '*'), + myfeed.init_mapping([(('CWUser', 'use_email', '*'), u'role=subject\naction=copy'), (('CWUser', 'in_group', '*'), u'role=subject\naction=link\nlinkattr=name'), @@ -135,11 +143,18 @@ (('*', 'tags', 'CWUser'), u'role=object\naction=link-or-create\nlinkattr=name'), ]) + myotherfeed.init_mapping([(('CWUser', 'in_group', '*'), + u'role=subject\naction=link\nlinkattr=name'), + (('CWUser', 'in_state', '*'), + u'role=subject\naction=link\nlinkattr=name'), + ]) session.create_entity('Tag', name=u'hop') def test_complete_url(self): dfsource = self.repo.sources_by_uri['myfeed'] parser = dfsource._get_parser(self.session) + self.assertEqual(parser.complete_url('http://www.cubicweb.org/CWUser'), + 'http://www.cubicweb.org/CWUser?relation=tags-object&relation=in_group-subject&relation=in_state-subject&relation=use_email-subject&vid=xml') self.assertEqual(parser.complete_url('http://www.cubicweb.org/cwuser'), 'http://www.cubicweb.org/cwuser?relation=tags-object&relation=in_group-subject&relation=in_state-subject&relation=use_email-subject&vid=xml') self.assertEqual(parser.complete_url('http://www.cubicweb.org/cwuser?vid=rdf&relation=hop'), @@ -164,7 +179,7 @@ (u'EmailAddress', {})] } }) - session = self.repo.internal_session() + session = self.repo.internal_session(safe=True) stats = dfsource.pull_data(session, force=True, raise_on_error=True) self.assertEqual(sorted(stats.keys()), ['created', 'updated']) self.assertEqual(len(stats['created']), 2) @@ -248,7 +263,7 @@ def test_external_entity(self): dfsource = self.repo.sources_by_uri['myotherfeed'] - session = self.repo.internal_session() + session = self.repo.internal_session(safe=True) stats = dfsource.pull_data(session, force=True, raise_on_error=True) user = self.execute('CWUser X WHERE X login "sthenault"').get_entity(0, 0) self.assertEqual(user.creation_date, datetime(2010, 01, 22, 10, 27, 59)) @@ -256,6 +271,33 @@ self.assertEqual(user.cwuri, 'http://pouet.org/5') self.assertEqual(user.cw_source[0].name, 'myfeed') + def test_noerror_missing_fti_attribute(self): + dfsource = self.repo.sources_by_uri['myfeed'] + session = self.repo.internal_session(safe=True) + parser = dfsource._get_parser(session) + dfsource.process_urls(parser, [''' + + + how-to + + +'''], raise_on_error=True) + + def test_noerror_unspecified_date(self): + dfsource = self.repo.sources_by_uri['myfeed'] + session = self.repo.internal_session(safe=True) + parser = dfsource._get_parser(session) + dfsource.process_urls(parser, [''' + + + how-to + how-to + how-to + + + +'''], raise_on_error=True) + if __name__ == '__main__': from logilab.common.testlib import unittest_main unittest_main() diff -r 73cadb5d0097 -r d313666c171e web/data/cubicweb.css --- a/web/data/cubicweb.css Tue Jul 26 19:34:43 2011 +0200 +++ b/web/data/cubicweb.css Wed Jul 27 20:17:45 2011 +0200 @@ -309,7 +309,7 @@ } div#contentmain{ - margin-top: %(pageContentPadding)s + margin-top: %(pageContentPadding)s; } /*FIXME */ @@ -923,7 +923,7 @@ ul.startup li, ul.section li { - margin-left: 0px + margin-left: 0px; } ul.simple li,