# HG changeset patch # User Sylvain Thénault # Date 1243422092 -7200 # Node ID 45af1e015683ddab0ad5ef675cdf2794c637cfa2 # Parent 9865daa96cd729d0139c0a2b8e86cc7c8cb825f3# Parent 8b5009e27e2bf432e22f9bed7889c3e6c58b8567 backport stable branch diff -r 9865daa96cd7 -r 45af1e015683 .hgtags --- a/.hgtags Wed May 27 08:39:16 2009 +0200 +++ b/.hgtags Wed May 27 13:01:32 2009 +0200 @@ -28,3 +28,11 @@ 0e132fbae9cc5e004f4b79a8b842addad43519a7 cubicweb-debian-version-3_1_4-1 c14231e3a4f9120e2bb6a1d8690252fff5e48131 cubicweb-version-3_2_0 c9c492787a8aa1b7916e22eb6498cba1c8fa316c cubicweb-debian-version-3_2_0-1 +634c251dd032894850080c4e5aeb0a4e09f888c0 cubicweb-version-3_2_1 +e784f8847a124a93e5b385d7a92a2772c050fe82 cubicweb-debian-version-3_2_1-1 +6539ce84f04357ef65ccee0896a30997b16a4ece cubicweb-version-3_2_2 +92d1a15f08f7c5fa87643ffb4273d12cb3f41c63 cubicweb-debian-version-3_2_2-1 +6539ce84f04357ef65ccee0896a30997b16a4ece cubicweb-version-3_2_2 +9b21e068fef73c37bcb4e53d006a7bde485f390b cubicweb-version-3_2_2 +92d1a15f08f7c5fa87643ffb4273d12cb3f41c63 cubicweb-debian-version-3_2_2-1 +0e07514264aa1b0b671226f41725ea4c066c210a cubicweb-debian-version-3_2_2-1 diff -r 9865daa96cd7 -r 45af1e015683 MANIFEST.in --- a/MANIFEST.in Wed May 27 08:39:16 2009 +0200 +++ b/MANIFEST.in Wed May 27 13:01:32 2009 +0200 @@ -3,7 +3,7 @@ include bin/cubicweb-* include man/cubicweb-ctl.1 -recursive-include doc *.txt *.zargo *.png *.html makefile +recursive-include doc *.txt *.zargo *.png *.html makefile *.rst recursive-include misc * diff -r 9865daa96cd7 -r 45af1e015683 __init__.py --- a/__init__.py Wed May 27 08:39:16 2009 +0200 +++ b/__init__.py Wed May 27 13:01:32 2009 +0200 @@ -46,7 +46,7 @@ class Binary(StringIO): """customize StringIO to make sure we don't use unicode""" - def __init__(self, buf= ''): + def __init__(self, buf=''): assert isinstance(buf, (str, buffer)), \ "Binary objects must use raw strings, not %s" % buf.__class__ StringIO.__init__(self, buf) diff -r 9865daa96cd7 -r 45af1e015683 __pkginfo__.py --- a/__pkginfo__.py Wed May 27 08:39:16 2009 +0200 +++ b/__pkginfo__.py Wed May 27 13:01:32 2009 +0200 @@ -6,7 +6,7 @@ distname = "cubicweb" modname = "cubicweb" -numversion = (3, 2, 0) +numversion = (3, 2, 2) version = '.'.join(str(num) for num in numversion) license = 'LGPL v2' diff -r 9865daa96cd7 -r 45af1e015683 cwconfig.py --- a/cwconfig.py Wed May 27 08:39:16 2009 +0200 +++ b/cwconfig.py Wed May 27 13:01:32 2009 +0200 @@ -11,6 +11,7 @@ """ __docformat__ = "restructuredtext en" +_ = unicode import sys import os @@ -27,7 +28,6 @@ CONFIGURATIONS = [] -_ = unicode class metaconfiguration(type): """metaclass to automaticaly register configuration""" diff -r 9865daa96cd7 -r 45af1e015683 cwvreg.py --- a/cwvreg.py Wed May 27 08:39:16 2009 +0200 +++ b/cwvreg.py Wed May 27 13:01:32 2009 +0200 @@ -11,7 +11,7 @@ from rql import RQLHelper -from cubicweb import Binary, UnknownProperty, UnknownEid +from cubicweb import ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid from cubicweb.vregistry import VRegistry, ObjectNotFound, NoSelectableObject from cubicweb.rtags import RTAGS @@ -170,7 +170,10 @@ # browse ancestors from most specific to most generic and # try to find an associated custom entity class for baseschema in baseschemas: - btype = str(baseschema) + try: + btype = ETYPE_NAME_MAP[baseschema] + except KeyError: + btype = str(baseschema) try: cls = self.select(self.registry_objects('etypes', btype), etype) break @@ -372,6 +375,8 @@ default to a dump of the class registered for 'Any' """ usercls = super(MulCnxCubicWebRegistry, self).etype_class(etype) + if etype == 'Any': + return usercls usercls.e_schema = self.schema.eschema(etype) return usercls @@ -385,7 +390,14 @@ vobject.vreg = self vobject.schema = self.schema vobject.config = self.config - return super(MulCnxCubicWebRegistry, self).select(vobjects, *args, **kwargs) + selected = super(MulCnxCubicWebRegistry, self).select(vobjects, *args, + **kwargs) + # redo the same thing on the instance so it won't use equivalent class + # attributes (which may change) + selected.vreg = self + selected.schema = self.schema + selected.config = self.config + return selected from datetime import datetime, date, time, timedelta diff -r 9865daa96cd7 -r 45af1e015683 dbapi.py --- a/dbapi.py Wed May 27 08:39:16 2009 +0200 +++ b/dbapi.py Wed May 27 13:01:32 2009 +0200 @@ -13,7 +13,8 @@ from logging import getLogger from time import time, clock -from cubicweb import ConnectionError, RequestSessionMixIn, set_log_methods +from logilab.common.logging_ext import set_log_methods +from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn from cubicweb.cwvreg import CubicWebRegistry, MulCnxCubicWebRegistry from cubicweb.cwconfig import CubicWebNoAppConfiguration @@ -101,7 +102,12 @@ vreg = MulCnxCubicWebRegistry(config, initlog=initlog) else: vreg = CubicWebRegistry(config, initlog=initlog) - vreg.set_schema(repo.get_schema()) + schema = repo.get_schema() + for oldetype, newetype in ETYPE_NAME_MAP.items(): + if oldetype in schema: + print 'aliasing', newetype, 'to', oldetype + schema._entities[newetype] = schema._entities[oldetype] + vreg.set_schema(schema) else: vreg = None cnx = repo_connect(repo, user, password, cnxprops) @@ -325,11 +331,6 @@ self.vreg = None # session's data self.data = {} - # XXX < 3.2 bw compat - if 'EUser' in self._repo.get_schema(): - self._user_etype = 'EUser' - else: - self._user_etype = 'CWUser' def __repr__(self): if self.anonymous_connection: @@ -435,9 +436,9 @@ eid, login, groups, properties = self._repo.user_info(self.sessionid, props) if req is None: req = self.request() - rset = req.eid_rset(eid, self._user_etype) - user = self.vreg.etype_class(self._user_etype)(req, rset, row=0, groups=groups, - properties=properties) + rset = req.eid_rset(eid, 'CWUser') + user = self.vreg.etype_class('CWUser')(req, rset, row=0, groups=groups, + properties=properties) user['login'] = login # cache login return user diff -r 9865daa96cd7 -r 45af1e015683 debian/changelog --- a/debian/changelog Wed May 27 08:39:16 2009 +0200 +++ b/debian/changelog Wed May 27 13:01:32 2009 +0200 @@ -1,3 +1,15 @@ +cubicweb (3.2.2-1) unstable; urgency=low + + * new upstream release + + -- Nicolas Chauvat Wed, 27 May 2009 12:31:49 +0200 + +cubicweb (3.2.1-1) unstable; urgency=low + + * new upstream release + + -- Aurélien Campéas Mon, 25 May 2009 16:45:00 +0200 + cubicweb (3.2.0-1) unstable; urgency=low * new upstream release diff -r 9865daa96cd7 -r 45af1e015683 debian/control diff -r 9865daa96cd7 -r 45af1e015683 debian/cubicweb-common.install.in diff -r 9865daa96cd7 -r 45af1e015683 debian/rules diff -r 9865daa96cd7 -r 45af1e015683 devtools/htmlparser.py --- a/devtools/htmlparser.py Wed May 27 08:39:16 2009 +0200 +++ b/devtools/htmlparser.py Wed May 27 13:01:32 2009 +0200 @@ -88,13 +88,13 @@ self.input_tags = self.find_tag('input') self.title_tags = [self.h1_tags, self.h2_tags, self.h3_tags, self.h4_tags] - def find_tag(self, tag): + def find_tag(self, tag, gettext=True): """return a list which contains text of all "tag" elements """ if self.default_ns is None: iterstr = ".//%s" % tag else: iterstr = ".//{%s}%s" % (self.default_ns, tag) - if tag in ('a', 'input'): + if not gettext or tag in ('a', 'input'): return [(elt.text, elt.attrib) for elt in self.etree.iterfind(iterstr)] return [u''.join(elt.xpath('.//text()')) for elt in self.etree.iterfind(iterstr)] diff -r 9865daa96cd7 -r 45af1e015683 misc/migration/bootstrapmigration_repository.py --- a/misc/migration/bootstrapmigration_repository.py Wed May 27 08:39:16 2009 +0200 +++ b/misc/migration/bootstrapmigration_repository.py Wed May 27 13:01:32 2009 +0200 @@ -7,6 +7,15 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ +if applcubicwebversion < (3, 2, 2) and cubicwebversion >= (3, 2, 1): + from base64 import b64encode + for table in ('entities', 'deleted_entities'): + for eid, extid in sql('SELECT eid, extid FROM %s WHERE extid is NOT NULL' + % table, ask_confirm=False): + sql('UPDATE %s SET extid=%%(extid)s WHERE eid=%%(eid)s' % table, + {'extid': b64encode(extid), 'eid': eid}, ask_confirm=False) + checkpoint() + if applcubicwebversion < (3, 2, 0) and cubicwebversion >= (3, 2, 0): add_cube('card', update_database=False) @@ -24,7 +33,7 @@ add_relation_type('is_instance_of') # fill the relation using an efficient sql query instead of using rql sql('INSERT INTO is_instance_of_relation ' - ' SELECT * from is_relation') + ' SELECT * from is_relation') checkpoint() session.set_shared_data('do-not-insert-is_instance_of', False) diff -r 9865daa96cd7 -r 45af1e015683 rset.py --- a/rset.py Wed May 27 08:39:16 2009 +0200 +++ b/rset.py Wed May 27 13:01:32 2009 +0200 @@ -380,9 +380,6 @@ pass # build entity instance etype = self.description[row][col] - if etype == 'EUser': - import traceback - traceback.printstack() entity = self.vreg.etype_class(etype)(req, self, row, col) entity.set_eid(eid) # cache entity diff -r 9865daa96cd7 -r 45af1e015683 schemaviewer.py --- a/schemaviewer.py Wed May 27 08:39:16 2009 +0200 +++ b/schemaviewer.py Wed May 27 13:01:32 2009 +0200 @@ -93,10 +93,10 @@ return data def eschema_link_url(self, eschema): - return self.req.build_url('eetype/%s?vid=eschema' % eschema) + return self.req.build_url('cwetype/%s?vid=eschema' % eschema) def rschema_link_url(self, rschema): - return self.req.build_url('ertype/%s?vid=eschema' % rschema) + return self.req.build_url('cwrtype/%s?vid=eschema' % rschema) def possible_views(self, etype): rset = self.req.etype_rset(etype) diff -r 9865daa96cd7 -r 45af1e015683 selectors.py --- a/selectors.py Wed May 27 08:39:16 2009 +0200 +++ b/selectors.py Wed May 27 13:01:32 2009 +0200 @@ -114,7 +114,7 @@ def score_interface(cls_or_inst, cls, iface): - """Return true if the give object (maybe an instance or class) implements + """Return XXX if the give object (maybe an instance or class) implements the interface. """ if getattr(iface, '__registry__', None) == 'etypes': diff -r 9865daa96cd7 -r 45af1e015683 server/querier.py --- a/server/querier.py Wed May 27 08:39:16 2009 +0200 +++ b/server/querier.py Wed May 27 13:01:32 2009 +0200 @@ -599,7 +599,8 @@ except KeyError: raise QueryError('bad cache key %s (no value)' % key) except TypeError: - raise QueryError('bad cache key %s (value: %r)' % (key, args[key])) + raise QueryError('bad cache key %s (value: %r)' % ( + key, args[key])) except UnknownEid: # we want queries such as "Any X WHERE X eid 9999" # return an empty result instead of raising UnknownEid diff -r 9865daa96cd7 -r 45af1e015683 server/repository.py --- a/server/repository.py Wed May 27 08:39:16 2009 +0200 +++ b/server/repository.py Wed May 27 13:01:32 2009 +0200 @@ -636,9 +636,10 @@ self.exception('unexpected error') raise - def close(self, sessionid): + def close(self, sessionid, checkshuttingdown=True): """close the session with the given id""" - session = self._get_session(sessionid, setpool=True) + session = self._get_session(sessionid, setpool=True, + checkshuttingdown=checkshuttingdown) # operation uncommited before close are rollbacked before hook is called session.rollback() self.hm.call_hooks('session_close', session=session) @@ -691,7 +692,7 @@ """close every opened sessions""" for sessionid in self._sessions.keys(): try: - self.close(sessionid) + self.close(sessionid, checkshuttingdown=False) except: self.exception('error while closing session %s' % sessionid) @@ -720,9 +721,9 @@ session.set_pool() return session - def _get_session(self, sessionid, setpool=False): + def _get_session(self, sessionid, setpool=False, checkshuttingdown=True): """return the user associated to the given session identifier""" - if self._shutting_down: + if checkshuttingdown and self._shutting_down: raise Exception('Repository is shutting down') try: session = self._sessions[sessionid] @@ -793,10 +794,10 @@ raise UnknownEid(eid) return extid - def extid2eid(self, source, lid, etype, session=None, insert=True, + def extid2eid(self, source, extid, etype, session=None, insert=True, recreate=False): """get eid from a local id. An eid is attributed if no record is found""" - cachekey = (str(lid), source.uri) + cachekey = (extid, source.uri) try: return self._extid_cache[cachekey] except KeyError: @@ -805,17 +806,17 @@ if session is None: session = self.internal_session() reset_pool = True - eid = self.system_source.extid2eid(session, source, lid) + eid = self.system_source.extid2eid(session, source, extid) if eid is not None: self._extid_cache[cachekey] = eid - self._type_source_cache[eid] = (etype, source.uri, lid) + self._type_source_cache[eid] = (etype, source.uri, extid) if recreate: - entity = source.before_entity_insertion(session, lid, etype, eid) + entity = source.before_entity_insertion(session, extid, etype, eid) entity._cw_recreating = True if source.should_call_hooks: self.hm.call_hooks('before_add_entity', etype, session, entity) # XXX add fti op ? - source.after_entity_insertion(session, lid, entity) + source.after_entity_insertion(session, extid, entity) if source.should_call_hooks: self.hm.call_hooks('after_add_entity', etype, session, entity) if reset_pool: @@ -823,7 +824,7 @@ return eid if not insert: return - # no link between lid and eid, create one using an internal session + # no link between extid and eid, create one using an internal session # since the current session user may not have required permissions to # do necessary stuff and we don't want to commit user session. # @@ -835,13 +836,13 @@ try: eid = self.system_source.create_eid(session) self._extid_cache[cachekey] = eid - self._type_source_cache[eid] = (etype, source.uri, lid) - entity = source.before_entity_insertion(session, lid, etype, eid) + self._type_source_cache[eid] = (etype, source.uri, extid) + entity = source.before_entity_insertion(session, extid, etype, eid) if source.should_call_hooks: self.hm.call_hooks('before_add_entity', etype, session, entity) # XXX call add_info with complete=False ? - self.add_info(session, entity, source, lid) - source.after_entity_insertion(session, lid, entity) + self.add_info(session, entity, source, extid) + source.after_entity_insertion(session, extid, entity) if source.should_call_hooks: self.hm.call_hooks('after_add_entity', etype, session, entity) else: diff -r 9865daa96cd7 -r 45af1e015683 server/schemaserial.py --- a/server/schemaserial.py Wed May 27 08:39:16 2009 +0200 +++ b/server/schemaserial.py Wed May 27 13:01:32 2009 +0200 @@ -51,7 +51,7 @@ def _set_sql_prefix(prefix): """3.2.0 migration function: allow to unset/reset SQL_PREFIX""" for module in ('checkintegrity', 'migractions', 'schemahooks', - 'sources.rql2sql', 'sources.native'): + 'sources.rql2sql', 'sources.native', 'sqlutils'): try: sys.modules['cubicweb.server.%s' % module].SQL_PREFIX = prefix print 'changed SQL_PREFIX for %s' % module diff -r 9865daa96cd7 -r 45af1e015683 server/sources/ldapuser.py --- a/server/sources/ldapuser.py Wed May 27 08:39:16 2009 +0200 +++ b/server/sources/ldapuser.py Wed May 27 13:01:32 2009 +0200 @@ -20,6 +20,8 @@ FOR A PARTICULAR PURPOSE. """ +from base64 import b64decode + from logilab.common.textutils import get_csv from rql.nodes import Relation, VariableRef, Constant, Function @@ -151,7 +153,8 @@ def init(self): """method called by the repository once ready to handle request""" self.repo.looping_task(self._interval, self.synchronize) - self.repo.looping_task(self._query_cache.ttl.seconds/10, self._query_cache.clear_expired) + self.repo.looping_task(self._query_cache.ttl.seconds/10, + self._query_cache.clear_expired) def synchronize(self): """synchronize content known by this repository with content in the @@ -166,7 +169,8 @@ try: cursor = session.system_sql("SELECT eid, extid FROM entities WHERE " "source='%s'" % self.uri) - for eid, extid in cursor.fetchall(): + for eid, b64extid in cursor.fetchall(): + extid = b64decode(b64extid) # if no result found, _search automatically delete entity information res = self._search(session, extid, BASE) if res: diff -r 9865daa96cd7 -r 45af1e015683 server/sources/native.py --- a/server/sources/native.py Wed May 27 08:39:16 2009 +0200 +++ b/server/sources/native.py Wed May 27 13:01:32 2009 +0200 @@ -1,5 +1,11 @@ """Adapters for native cubicweb sources. +Notes: +* extid (aka external id, the primary key of an entity in the external source + from which it comes from) are stored in a varchar column encoded as a base64 + string. This is because it should actually be Bytes but we want an index on + it for fast querying. + :organization: Logilab :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr @@ -8,6 +14,7 @@ from threading import Lock from datetime import datetime +from base64 import b64decode, b64encode from logilab.common.cache import Cache from logilab.common.configuration import REQUIRED @@ -234,7 +241,7 @@ pwd = rset[0][0] except IndexError: raise AuthenticationError('bad login') - # passwords are stored using the bytea type, so we get a StringIO + # passwords are stored using the Bytes type, so we get a StringIO if pwd is not None: args['pwd'] = crypt_password(password, pwd.getvalue()[:2]) # get eid from login and (crypted) password @@ -456,22 +463,25 @@ raise UnknownEid(eid) if res is None: raise UnknownEid(eid) + if res[-1] is not None: + if not isinstance(res, list): + res = list(res) + res[-1] = b64decode(res[-1]) return res - def extid2eid(self, session, source, lid): - """get eid from a local id. An eid is attributed if no record is found""" + def extid2eid(self, session, source, extid): + """get eid from an external id. Return None if no record found.""" + assert isinstance(extid, str) cursor = session.system_sql('SELECT eid FROM entities WHERE ' 'extid=%(x)s AND source=%(s)s', - # str() necessary with pg 8.3 - {'x': str(lid), 's': source.uri}) + {'x': b64encode(extid), 's': source.uri}) # XXX testing rowcount cause strange bug with sqlite, results are there # but rowcount is 0 #if cursor.rowcount > 0: try: result = cursor.fetchone() if result: - eid = result[0] - return eid + return result[0] except: pass return None @@ -499,8 +509,11 @@ def add_info(self, session, entity, source, extid=None): """add type and source info for an eid into the system table""" # begin by inserting eid/type/source/extid into the entities table - attrs = {'type': str(entity.e_schema), 'eid': entity.eid, - 'extid': extid, 'source': source.uri, 'mtime': datetime.now()} + if extid is not None: + assert isinstance(extid, str) + extid = b64encode(extid) + attrs = {'type': entity.id, 'eid': entity.eid, 'extid': extid, + 'source': source.uri, 'mtime': datetime.now()} session.system_sql(self.sqlgen.insert('entities', attrs), attrs) def delete_info(self, session, eid, etype, uri, extid): @@ -510,6 +523,9 @@ attrs = {'eid': eid} session.system_sql(self.sqlgen.delete('entities', attrs), attrs) if self.has_deleted_entitites_table: + if extid is not None: + assert isinstance(extid, str), type(extid) + extid = b64encode(extid) attrs = {'type': etype, 'eid': eid, 'extid': extid, 'source': uri, 'dtime': datetime.now()} session.system_sql(self.sqlgen.insert('deleted_entities', attrs), attrs) diff -r 9865daa96cd7 -r 45af1e015683 server/sources/pyrorql.py --- a/server/sources/pyrorql.py Wed May 27 08:39:16 2009 +0200 +++ b/server/sources/pyrorql.py Wed May 27 13:01:32 2009 +0200 @@ -8,9 +8,9 @@ import threading from os.path import join - from time import mktime from datetime import datetime +from base64 import b64decode from Pyro.errors import PyroError, ConnectionClosedError @@ -177,7 +177,7 @@ try: exturi = cnx.describe(extid)[1] if exturi == 'system' or not exturi in repo.sources_by_uri: - eid = self.extid2eid(extid, etype, session) + eid = self.extid2eid(str(extid), etype, session) rset = session.eid_rset(eid, etype) entity = rset.get_entity(0, 0) entity.complete(entity.e_schema.indexable_attributes()) @@ -188,7 +188,8 @@ continue for etype, extid in deleted: try: - eid = self.extid2eid(extid, etype, session, insert=False) + eid = self.extid2eid(str(extid), etype, session, + insert=False) # entity has been deleted from external repository but is not known here if eid is not None: repo.delete_info(session, eid) @@ -307,7 +308,8 @@ etype = descr[rowindex][colindex] exttype, exturi, extid = cnx.describe(row[colindex]) if exturi == 'system' or not exturi in self.repo.sources_by_uri: - eid = self.extid2eid(row[colindex], etype, session) + eid = self.extid2eid(str(row[colindex]), etype, + session) row[colindex] = eid else: # skip this row @@ -494,7 +496,7 @@ # XXX what about optional relation or outer NOT EXISTS() raise except ReplaceByInOperator, ex: - rhs = 'IN (%s)' % ','.join(str(eid) for eid in ex.eids) + rhs = 'IN (%s)' % ','.join(eid for eid in ex.eids) self.need_translation = False self.current_operator = None if node.optional in ('right', 'both'): @@ -567,17 +569,15 @@ except UnknownEid: operator = self.current_operator if operator is not None and operator != '=': - # deal with query like X eid > 12 + # deal with query like "X eid > 12" # - # The problem is - # that eid order in the external source may differ from the - # local source + # The problem is that eid order in the external source may + # differ from the local source # - # So search for all eids from this - # source matching the condition locally and then to replace the - # > 12 branch by IN (eids) (XXX we may have to insert a huge - # number of eids...) - # planner so that + # So search for all eids from this source matching the condition + # locally and then to replace the "> 12" branch by "IN (eids)" + # + # XXX we may have to insert a huge number of eids...) sql = "SELECT extid FROM entities WHERE source='%s' AND type IN (%s) AND eid%s%s" etypes = ','.join("'%s'" % etype for etype in self.current_etypes) cu = self._session.system_sql(sql % (self.source.uri, etypes, @@ -586,6 +586,6 @@ # results rows = cu.fetchall() if rows: - raise ReplaceByInOperator((r[0] for r in rows)) + raise ReplaceByInOperator((b64decode(r[0]) for r in rows)) raise diff -r 9865daa96cd7 -r 45af1e015683 server/sqlutils.py --- a/server/sqlutils.py Wed May 27 08:39:16 2009 +0200 +++ b/server/sqlutils.py Wed May 27 13:01:32 2009 +0200 @@ -111,13 +111,14 @@ w(indexer.sql_drop_fti()) w('') w(dropschema2sql(schema, prefix=SQL_PREFIX, - skip_entities=skip_entities, skip_relations=skip_relations)) + skip_entities=skip_entities, + skip_relations=skip_relations)) return '\n'.join(output) try: from mx.DateTime import DateTimeType, DateTimeDeltaType except ImportError: - DateTimeType, DateTimeDeltaType = None + DateTimeType = DateTimeDeltaType = None class SQLAdapterMixIn(object): """Mixin for SQL data sources, getting a connection from a configuration diff -r 9865daa96cd7 -r 45af1e015683 server/test/unittest_multisources.py --- a/server/test/unittest_multisources.py Wed May 27 08:39:16 2009 +0200 +++ b/server/test/unittest_multisources.py Wed May 27 13:01:32 2009 +0200 @@ -77,7 +77,7 @@ 'type': u'Card', 'extid': None}) externent = rset.get_entity(3, 0) metainf = externent.metainformation() - self.assertEquals(metainf['source'], {'adapter': 'pyrorql', 'base-url': 'http://extern.org', 'uri': 'extern'}) + self.assertEquals(metainf['source'], {'adapter': 'pyrorql', 'base-url': 'http://extern.org/', 'uri': 'extern'}) self.assertEquals(metainf['type'], 'Card') self.assert_(metainf['extid']) etype = self.execute('Any ETN WHERE X is ET, ET name ETN, X eid %(x)s', @@ -149,7 +149,7 @@ self.execute('Any X ORDERBY DUMB_SORT(RF) WHERE X title RF') def test_in_eid(self): - iec1 = self.repo.extid2eid(self.repo.sources_by_uri['extern'], ec1, + iec1 = self.repo.extid2eid(self.repo.sources_by_uri['extern'], str(ec1), 'Card', self.session) rset = self.execute('Any X WHERE X eid IN (%s, %s)' % (iec1, self.ic1)) self.assertEquals(sorted(r[0] for r in rset.rows), sorted([iec1, self.ic1])) diff -r 9865daa96cd7 -r 45af1e015683 server/test/unittest_querier.py --- a/server/test/unittest_querier.py Wed May 27 08:39:16 2009 +0200 +++ b/server/test/unittest_querier.py Wed May 27 13:01:32 2009 +0200 @@ -6,7 +6,7 @@ from logilab.common.testlib import TestCase, unittest_main from rql import BadRQLQuery, RQLSyntaxError -from cubicweb import QueryError, Unauthorized +from cubicweb import QueryError, Unauthorized, Binary from cubicweb.server.sqlutils import SQL_PREFIX from cubicweb.server.utils import crypt_password from cubicweb.server.sources.native import make_schema @@ -208,6 +208,13 @@ # should return an empty result set self.failIf(self.execute('Any X WHERE X eid 99999999')) + def test_bytes_storage(self): + feid = self.execute('INSERT File X: X name "foo.pdf", X data_format "text/plain", X data %(data)s', + {'data': Binary("xxx")})[0][0] + fdata = self.execute('Any D WHERE X data D, X eid %(x)s', {'x': feid}, 'x')[0][0] + self.assertIsInstance(fdata, Binary) + self.assertEquals(fdata.getvalue(), 'xxx') + # selection queries tests ################################################# def test_select_1(self): @@ -470,7 +477,7 @@ self.assertEquals(rset.rows[0][0], self.ueid) def test_select_complex_sort(self): - self.skip('retry me once http://www.sqlite.org/cvstrac/tktview?tn=3773 is fixed') + """need sqlite including http://www.sqlite.org/cvstrac/tktview?tn=3773 fix""" rset = self.execute('Any X ORDERBY X,D LIMIT 5 WHERE X creation_date D') result = rset.rows result.sort() @@ -1073,7 +1080,7 @@ cursor = self.pool['system'] cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'" % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX)) - passwd = cursor.fetchone()[0].getvalue() + passwd = str(cursor.fetchone()[0]) self.assertEquals(passwd, crypt_password('toto', passwd[:2])) rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword '%s'" % passwd) self.assertEquals(len(rset.rows), 1) @@ -1087,7 +1094,7 @@ {'pwd': 'tutu'}) cursor.execute("SELECT %supassword from %sCWUser WHERE %slogin='bob'" % (SQL_PREFIX, SQL_PREFIX, SQL_PREFIX)) - passwd = cursor.fetchone()[0].getvalue() + passwd = str(cursor.fetchone()[0]) self.assertEquals(passwd, crypt_password('tutu', passwd[:2])) rset = self.execute("Any X WHERE X is CWUser, X login 'bob', X upassword '%s'" % passwd) self.assertEquals(len(rset.rows), 1) @@ -1212,8 +1219,9 @@ cause: old variable ref inserted into a fresh rqlst copy (in RQLSpliter._complex_select_plan) + + need sqlite including http://www.sqlite.org/cvstrac/tktview?tn=3773 fix """ - self.skip('retry me once http://www.sqlite.org/cvstrac/tktview?tn=3773 is fixed') self.execute('Any X ORDERBY D DESC WHERE X creation_date D') def test_nonregr_extra_joins(self): diff -r 9865daa96cd7 -r 45af1e015683 server/test/unittest_repository.py --- a/server/test/unittest_repository.py Wed May 27 08:39:16 2009 +0200 +++ b/server/test/unittest_repository.py Wed May 27 13:01:32 2009 +0200 @@ -211,11 +211,8 @@ t = threading.Thread(target=close_in_a_few_moment) t.start() try: - print 'execute' repo.execute(cnxid, 'DELETE CWUser X WHERE X login "toto"') - print 'commit' repo.commit(cnxid) - print 'commited' finally: t.join() @@ -286,7 +283,8 @@ repo = self.repo cnxid = repo.connect(*self.default_user_password()) session = repo._get_session(cnxid, setpool=True) - self.assertEquals(repo.type_and_source_from_eid(1, session), ('CWGroup', 'system', None)) + self.assertEquals(repo.type_and_source_from_eid(1, session), + ('CWGroup', 'system', None)) self.assertEquals(repo.type_from_eid(1, session), 'CWGroup') self.assertEquals(repo.source_from_eid(1, session).uri, 'system') self.assertEquals(repo.eid2extid(repo.system_source, 1, session), None) diff -r 9865daa96cd7 -r 45af1e015683 skeleton/__pkginfo__.py.tmpl --- a/skeleton/__pkginfo__.py.tmpl Wed May 27 08:39:16 2009 +0200 +++ b/skeleton/__pkginfo__.py.tmpl Wed May 27 13:01:32 2009 +0200 @@ -32,7 +32,7 @@ return [join(dirpath, fname) for fname in _listdir(dirpath) if fname[0] != '.' and not fname.endswith('.pyc') and not fname.endswith('~') - and not isdir(join(dirpath, fname))]¶ + and not isdir(join(dirpath, fname))] from glob import glob try: diff -r 9865daa96cd7 -r 45af1e015683 vregistry.py --- a/vregistry.py Wed May 27 08:39:16 2009 +0200 +++ b/vregistry.py Wed May 27 13:01:32 2009 +0200 @@ -237,7 +237,7 @@ # XXX automatic reloading management try: registry[obj.id].remove(registered) - except ValueError: + except KeyError: self.warning('can\'t remove %s, no id %s in the %s registry', removed_id, obj.id, registryname) except ValueError: @@ -254,11 +254,14 @@ replaced = replaced.classid() registryname = registryname or obj.__registry__ registry = self.registry(registryname) - registered_objs = registry[obj.id] + registered_objs = registry.get(obj.id, ()) for index, registered in enumerate(registered_objs): if registered.classid() == replaced: del registry[obj.id][index] break + else: + self.warning('trying to replace an unregistered view %s by %s', + replaced, obj) self.register(obj, registryname=registryname) # dynamic selection methods ############################################### diff -r 9865daa96cd7 -r 45af1e015683 web/data/favicon.ico Binary file web/data/favicon.ico has changed diff -r 9865daa96cd7 -r 45af1e015683 web/form.py --- a/web/form.py Wed May 27 08:39:16 2009 +0200 +++ b/web/form.py Wed May 27 13:01:32 2009 +0200 @@ -270,13 +270,21 @@ @iclassmethod def field_by_name(cls_or_self, name, role='subject'): - """return field with the given name and role""" + """return field with the given name and role. + Raise FieldNotFound if the field can't be found. + """ for field in cls_or_self._fieldsattr(): if field.name == name and field.role == role: return field raise FieldNotFound(name) @iclassmethod + def fields_by_name(cls_or_self, name, role='subject'): + """return a list of fields with the given name and role""" + return [field for field in cls_or_self._fieldsattr() + if field.name == name and field.role == role] + + @iclassmethod def remove_field(cls_or_self, field): """remove a field from form class or instance""" cls_or_self._fieldsattr().remove(field) @@ -341,7 +349,9 @@ # ensure rendervalues is a dict if rendervalues is None: rendervalues = {} - for field in self.fields: + # use a copy in case fields are modified while context is build (eg + # __linkto handling for instance) + for field in self.fields[:]: for field in field.actual_fields(self): field.form_init(self) value = self.form_field_display_value(field, rendervalues) @@ -443,12 +453,13 @@ self.edited_entity = self.complete_entity(self.row or 0, self.col or 0) self.form_add_hidden('__type', eidparam=True) self.form_add_hidden('eid') - if msg is not None: + if msg: # If we need to directly attach the new object to another one + self.form_add_hidden('__message', msg) + if not self.is_subform: for linkto in self.req.list_form_param('__linkto'): self.form_add_hidden('__linkto', linkto) msg = '%s %s' % (msg, self.req._('and linked')) - self.form_add_hidden('__message', msg) # in case of direct instanciation self.schema = self.edited_entity.schema self.vreg = self.edited_entity.vreg @@ -478,7 +489,16 @@ def _req_display_value(self, field): value = super(EntityFieldsForm, self)._req_display_value(field) if value is None: - value = self.edited_entity.linked_to(field.name, field.role) or None + value = self.edited_entity.linked_to(field.name, field.role) + if value: + searchedvalues = ['%s:%s:%s' % (field.name, eid, field.role) + for eid in value] + # remove associated __linkto hidden fields + for field in self.fields_by_name('__linkto'): + if field.initial in searchedvalues: + self.remove_field(field) + else: + value = None return value def _form_field_default_value(self, field, load_bytes): diff -r 9865daa96cd7 -r 45af1e015683 web/formrenderers.py --- a/web/formrenderers.py Wed May 27 08:39:16 2009 +0200 +++ b/web/formrenderers.py Wed May 27 13:01:32 2009 +0200 @@ -83,7 +83,7 @@ return tags.label(label, **attrs) def render_help(self, form, field): - help = [] + help = [] descr = field.help if descr: help.append('
%s
' % form.req._(descr)) diff -r 9865daa96cd7 -r 45af1e015683 web/test/unittest_form.py --- a/web/test/unittest_form.py Wed May 27 08:39:16 2009 +0200 +++ b/web/test/unittest_form.py Wed May 27 13:01:32 2009 +0200 @@ -73,6 +73,21 @@ form.form_build_context({}) self.assertEquals(form.form_field_display_value(field, {}), 'toto') + + def test_linkto_field_duplication(self): + e = self.etype_instance('CWUser') + e.eid = 'A' + e.req = self.req + geid = self.execute('CWGroup X WHERE X name "users"')[0][0] + self.req.form['__linkto'] = 'in_group:%s:subject' % geid + form = self.vreg.select_object('forms', 'edition', self.req, None, entity=e) + form.content_type = 'text/html' + pageinfo = self._check_html(form.form_render(), form, template=None) + inputs = pageinfo.find_tag('select', False) + self.failUnless(any(attrs for t, attrs in inputs if attrs.get('name') == 'in_group:A')) + inputs = pageinfo.find_tag('input', False) + self.failIf(any(attrs for t, attrs in inputs if attrs.get('name') == '__linkto')) + # form view tests ######################################################### def test_massmailing_formview(self): diff -r 9865daa96cd7 -r 45af1e015683 web/test/unittest_viewselector.py --- a/web/test/unittest_viewselector.py Wed May 27 08:39:16 2009 +0200 +++ b/web/test/unittest_viewselector.py Wed May 27 13:01:32 2009 +0200 @@ -1,8 +1,6 @@ # -*- coding: iso-8859-1 -*- """XXX rename, split, reorganize this """ -from __future__ import with_statement - from logilab.common.testlib import unittest_main from cubicweb.devtools.apptest import EnvBasedTC diff -r 9865daa96cd7 -r 45af1e015683 web/uicfg.py --- a/web/uicfg.py Wed May 27 08:39:16 2009 +0200 +++ b/web/uicfg.py Wed May 27 13:01:32 2009 +0200 @@ -66,13 +66,10 @@ """ __docformat__ = "restructuredtext en" +from cubicweb import neg_role from cubicweb.rtags import RelationTags, RelationTagsBool, RelationTagsSet from cubicweb.web import formwidgets -# primary view configuration ################################################## - -def dual_role(role): - return role == 'subject' and 'object' or 'subject' def card_from_role(card, role): if role == 'subject': @@ -80,10 +77,12 @@ assert role in ('object', 'sobject'), repr(role) return card[1] +# primary view configuration ################################################## + def init_primaryview_section(rtag, sschema, rschema, oschema, role): if rtag.get(sschema, rschema, oschema, role) is None: card = card_from_role(rschema.rproperty(sschema, oschema, 'cardinality'), role) - composed = rschema.rproperty(sschema, oschema, 'composite') == dual_role(role) + composed = rschema.rproperty(sschema, oschema, 'composite') == neg_role(role) if rschema.is_final(): if rschema.meta or oschema.type in ('Password', 'Bytes'): section = 'hidden' diff -r 9865daa96cd7 -r 45af1e015683 web/views/basecomponents.py --- a/web/views/basecomponents.py Wed May 27 08:39:16 2009 +0200 +++ b/web/views/basecomponents.py Wed May 27 13:01:32 2009 +0200 @@ -8,6 +8,7 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" +_ = unicode from rql import parse @@ -18,8 +19,6 @@ from cubicweb.web.htmlwidgets import (MenuWidget, PopupBoxMenu, BoxSeparator, BoxLink) -_ = unicode - VISIBLE_PROP_DEF = { _('visible'): dict(type='Boolean', default=False, help=_('display the component or not')), diff -r 9865daa96cd7 -r 45af1e015683 web/views/editcontroller.py --- a/web/views/editcontroller.py Wed May 27 08:39:16 2009 +0200 +++ b/web/views/editcontroller.py Wed May 27 13:01:32 2009 +0200 @@ -5,6 +5,7 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" + from decimal import Decimal from rql.utils import rqlvar_maker @@ -228,6 +229,9 @@ formparams['name'] = value[0] self.relations.append('X name %(name)s') value = val + else: + # no specified value, skip + return elif value is not None: if attrtype in ('Date', 'Datetime', 'Time'): try: diff -r 9865daa96cd7 -r 45af1e015683 web/views/management.py --- a/web/views/management.py Wed May 27 08:39:16 2009 +0200 +++ b/web/views/management.py Wed May 27 13:01:32 2009 +0200 @@ -23,6 +23,7 @@ class SecurityViewMixIn(object): """display security information for a given schema """ + def schema_definition(self, eschema, link=True, access_types=None): w = self.w _ = self.req._ @@ -40,7 +41,7 @@ for trad, group in sorted(groups): if link: l.append(u'%s
' % ( - self.build_url('egroup/%s' % group), group, trad)) + self.build_url('cwgroup/%s' % group), group, trad)) else: l.append(u'
%s
' % (group, trad)) w(u'%s' % u''.join(l)) @@ -68,6 +69,7 @@ __select__ = EntityView.__select__ & authenticated_user() title = _('security') + def call(self): self.w(u'
%s
' % self.req._('validating...')) super(SecurityManagementView, self).call()