# HG changeset patch # User Sylvain Thénault # Date 1246523703 -7200 # Node ID 1fbcf202882d4810aae380a555f7e31327e4862a # Parent 2b91abd9f5a4df9df8a64e8e61fee315aefbc018# Parent bf3603caaf0d71f4aea05c901cc47864de280cdf backport stable diff -r 2b91abd9f5a4 -r 1fbcf202882d common/__init__.py --- a/common/__init__.py Mon Jun 29 14:12:18 2009 +0200 +++ b/common/__init__.py Thu Jul 02 10:35:03 2009 +0200 @@ -18,9 +18,9 @@ rtype = 'String' @classmethod - def st_description(cls, funcnode): - return ', '.join(term.get_description() - for term in iter_funcnode_variables(funcnode)) + def st_description(cls, funcnode, mainindex, tr): + return ', '.join(sorted(term.get_description(mainindex, tr) + for term in iter_funcnode_variables(funcnode))) register_function(COMMA_JOIN) # XXX do not expose? @@ -41,8 +41,8 @@ rtype = 'String' @classmethod - def st_description(cls, funcnode): - return funcnode.children[0].get_description() + def st_description(cls, funcnode, mainindex, tr): + return funcnode.children[0].get_description(mainindex, tr) register_function(LIMIT_SIZE) diff -r 2b91abd9f5a4 -r 1fbcf202882d common/uilib.py --- a/common/uilib.py Mon Jun 29 14:12:18 2009 +0200 +++ b/common/uilib.py Thu Jul 02 10:35:03 2009 +0200 @@ -92,7 +92,9 @@ # fallback implementation, nicer one defined below if lxml is available def soup2xhtml(data, encoding): - return data + # normalize line break + # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 + return u'\n'.join(data.splitlines()) # fallback implementation, nicer one defined below if lxml> 2.0 is available def safe_cut(text, length): @@ -123,6 +125,10 @@ Note: the function considers a string with no surrounding tag as valid if
`data`
can be parsed by an XML parser """ + # normalize line break + # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 + data = u'\n'.join(data.splitlines()) + # XXX lxml 1.1 support still needed ? xmltree = etree.HTML('
%s
' % data) # NOTE: lxml 1.1 (etch platforms) doesn't recognize # the encoding=unicode parameter (lxml 2.0 does), this is diff -r 2b91abd9f5a4 -r 1fbcf202882d cwconfig.py --- a/cwconfig.py Mon Jun 29 14:12:18 2009 +0200 +++ b/cwconfig.py Thu Jul 02 10:35:03 2009 +0200 @@ -17,6 +17,8 @@ import sys import os import logging +from smtplib import SMTP +from threading import Lock from os.path import exists, join, expanduser, abspath, normpath, basename, isdir from logilab.common.decorators import cached @@ -29,6 +31,8 @@ CONFIGURATIONS = [] +SMTP_LOCK = Lock() + class metaconfiguration(type): """metaclass to automaticaly register configuration""" @@ -743,7 +747,7 @@ self.warning('site_erudi.py is deprecated, should be renamed to site_cubicweb.py') def _load_site_cubicweb(self, sitefile): - context = {} + context = {'__file__': sitefile} execfile(sitefile, context, context) self.info('%s loaded', sitefile) # cube specific options @@ -827,6 +831,28 @@ sourcedirs.append(self.i18n_lib_dir()) return i18n.compile_i18n_catalogs(sourcedirs, i18ndir, langs) + def sendmails(self, msgs): + """msgs: list of 2-uple (message object, recipients)""" + server, port = self['smtp-host'], self['smtp-port'] + SMTP_LOCK.acquire() + try: + try: + smtp = SMTP(server, port) + except Exception, ex: + self.exception("can't connect to smtp server %s:%s (%s)", + server, port, ex) + return + heloaddr = '%s <%s>' % (self['sender-name'], self['sender-addr']) + for msg, recipients in msgs: + try: + smtp.sendmail(heloaddr, recipients, msg.as_string()) + except Exception, ex: + self.exception("error sending mail to %s (%s)", + recipients, ex) + smtp.close() + finally: + SMTP_LOCK.release() + set_log_methods(CubicWebConfiguration, logging.getLogger('cubicweb.configuration')) # alias to get a configuration instance from an application id diff -r 2b91abd9f5a4 -r 1fbcf202882d cwvreg.py --- a/cwvreg.py Mon Jun 29 14:12:18 2009 +0200 +++ b/cwvreg.py Thu Jul 02 10:35:03 2009 +0200 @@ -303,9 +303,10 @@ def user_property_keys(self, withsitewide=False): if withsitewide: - return sorted(self['propertydefs']) + return sorted(k for k in self['propertydefs'] + if not k.startswith('sources.')) return sorted(k for k, kd in self['propertydefs'].iteritems() - if not kd['sitewide']) + if not kd['sitewide'] and not k.startswith('sources.')) def register_property(self, key, type, help, default=None, vocabulary=None, sitewide=False): diff -r 2b91abd9f5a4 -r 1fbcf202882d debian/control --- a/debian/control Mon Jun 29 14:12:18 2009 +0200 +++ b/debian/control Thu Jul 02 10:35:03 2009 +0200 @@ -76,7 +76,7 @@ Package: cubicweb-common Architecture: all XB-Python-Version: ${python:Versions} -Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.41.0), python-yams (>= 0.23.0), python-rql (>= 0.22.0) +Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.41.0), python-yams (>= 0.23.0), python-rql (>= 0.22.1) Recommends: python-simpletal (>= 4.0), python-lxml Conflicts: cubicweb-core Replaces: cubicweb-core diff -r 2b91abd9f5a4 -r 1fbcf202882d devtools/apptest.py --- a/devtools/apptest.py Mon Jun 29 14:12:18 2009 +0200 +++ b/devtools/apptest.py Thu Jul 02 10:35:03 2009 +0200 @@ -46,8 +46,8 @@ def sendmail(self, helo_addr, recipients, msg): MAILBOX.append(Email(recipients, msg)) -from cubicweb.server import hookhelper -hookhelper.SMTP = MockSMTP +from cubicweb import cwconfig +cwconfig.SMTP = MockSMTP def get_versions(self, checkversions=False): diff -r 2b91abd9f5a4 -r 1fbcf202882d devtools/repotest.py --- a/devtools/repotest.py Mon Jun 29 14:12:18 2009 +0200 +++ b/devtools/repotest.py Thu Jul 02 10:35:03 2009 +0200 @@ -208,6 +208,7 @@ class BasePlannerTC(BaseQuerierTC): + newsources = 0 def setup(self): clear_cache(self.repo, 'rel_type_sources') clear_cache(self.repo, 'rel_type_sources') @@ -220,7 +221,6 @@ self.schema = self.o.schema self.sources = self.o._repo.sources self.system = self.sources[-1] - self.newsources = 0 do_monkey_patch() def add_source(self, sourcecls, uri): diff -r 2b91abd9f5a4 -r 1fbcf202882d devtools/testlib.py --- a/devtools/testlib.py Mon Jun 29 14:12:18 2009 +0200 +++ b/devtools/testlib.py Thu Jul 02 10:35:03 2009 +0200 @@ -249,6 +249,8 @@ def iter_automatic_rsets(self, limit=10): """generates basic resultsets for each entity type""" etypes = self.to_test_etypes() + if not etypes: + return for etype in etypes: yield self.execute('Any X LIMIT %s WHERE X is %s' % (limit, etype)) etype1 = etypes.pop() diff -r 2b91abd9f5a4 -r 1fbcf202882d schema.py --- a/schema.py Mon Jun 29 14:12:18 2009 +0200 +++ b/schema.py Thu Jul 02 10:35:03 2009 +0200 @@ -14,6 +14,7 @@ from warnings import warn from logilab.common.decorators import cached, clear_cache, monkeypatch +from logilab.common.deprecation import obsolete from logilab.common.compat import any from yams import BadSchemaDefinition, buildobjs as ybo @@ -97,6 +98,16 @@ yams_add_relation(relations, format_attrdef, name+'_format', insertidx) yams_add_relation(relations, rdef, name, insertidx) + +yams_EntityType_add_relation = ybo.EntityType.add_relation +@monkeypatch(ybo.EntityType) +def add_relation(self, rdef, name=None): + yams_EntityType_add_relation(self, rdef, name) + if isinstance(rdef, RichString) and not rdef in self._defined: + format_attr_name = (name or rdef.name) + '_format' + rdef = self.get_relations(format_attr_name).next() + self._ensure_relation_type(rdef) + def display_name(req, key, form=''): """return a internationalized string for the key (schema entity or relation name) in a given form @@ -109,7 +120,7 @@ # ensure unicode # added .lower() in case no translation are available return unicode(req._(key)).lower() -__builtins__['display_name'] = display_name +__builtins__['display_name'] = obsolete('display_name should be imported from cubicweb.schema')(display_name) def ERSchema_display_name(self, req, form=''): """return a internationalized string for the entity/relation type name in diff -r 2b91abd9f5a4 -r 1fbcf202882d schemas/workflow.py --- a/schemas/workflow.py Mon Jun 29 14:12:18 2009 +0200 +++ b/schemas/workflow.py Thu Jul 02 10:35:03 2009 +0200 @@ -60,7 +60,7 @@ transition_of = SubjectRelation('CWEType', cardinality='+*', description=_('entity types which may use this transition'), constraints=[RQLConstraint('O final FALSE')]) - destination_state = SubjectRelation('State', cardinality='?*', + destination_state = SubjectRelation('State', cardinality='1*', constraints=[RQLConstraint('S transition_of ET, O state_of ET')], description=_('destination state for this transition')) diff -r 2b91abd9f5a4 -r 1fbcf202882d server/hookhelper.py --- a/server/hookhelper.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/hookhelper.py Thu Jul 02 10:35:03 2009 +0200 @@ -7,9 +7,6 @@ """ __docformat__ = "restructuredtext en" -from smtplib import SMTP -from threading import Lock - from cubicweb import RepositoryError from cubicweb.server.pool import SingleLastOperation @@ -47,8 +44,6 @@ # mail related ################################################################ -SMTP_LOCK = Lock() - class SendMailOp(SingleLastOperation): def __init__(self, session, msg=None, recipients=None, **kwargs): # may not specify msg yet, as @@ -70,26 +65,7 @@ self.repo.threaded_task(self.sendmails) def sendmails(self): - server, port = self.config['smtp-host'], self.config['smtp-port'] - SMTP_LOCK.acquire() - try: - try: - smtp = SMTP(server, port) - except Exception, ex: - self.exception("can't connect to smtp server %s:%s (%s)", - server, port, ex) - return - heloaddr = '%s <%s>' % (self.config['sender-name'], - self.config['sender-addr']) - for msg, recipients in self.to_send: - try: - smtp.sendmail(heloaddr, recipients, msg.as_string()) - except Exception, ex: - self.exception("error sending mail to %s (%s)", - recipients, ex) - smtp.close() - finally: - SMTP_LOCK.release() + self.config.sendmails(self.to_send) # state related ############################################################### diff -r 2b91abd9f5a4 -r 1fbcf202882d server/hooks.py --- a/server/hooks.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/hooks.py Thu Jul 02 10:35:03 2009 +0200 @@ -85,16 +85,16 @@ """ ftcontainer = session.repo.schema.rschema(rtype).fulltext_container if ftcontainer == 'subject': - FTIndexEntityOp(session, entity=session.entity(eidto)) + FTIndexEntityOp(session, entity=session.entity_from_eid(eidto)) elif ftcontainer == 'object': - FTIndexEntityOp(session, entity=session.entity(eidfrom)) + FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom)) def fti_update_after_delete_relation(session, eidfrom, rtype, eidto): """sync fulltext index when relevant relation is deleted. Reindexing both entities is necessary. """ if session.repo.schema.rschema(rtype).fulltext_container: - FTIndexEntityOp(session, entity=session.entity(eidto)) - FTIndexEntityOp(session, entity=session.entity(eidfrom)) + FTIndexEntityOp(session, entity=session.entity_from_eid(eidto)) + FTIndexEntityOp(session, entity=session.entity_from_eid(eidfrom)) class SyncOwnersOp(PreCommitOperation): @@ -379,7 +379,7 @@ etype = session.describe(fromeid)[0] if not (session.is_super_session or 'managers' in session.user.groups): if not state is None: - entity = session.entity(fromeid) + entity = session.entity_from_eid(fromeid) # we should find at least one transition going to this state try: iter(state.transitions(entity, toeid)).next() diff -r 2b91abd9f5a4 -r 1fbcf202882d server/repository.py --- a/server/repository.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/repository.py Thu Jul 02 10:35:03 2009 +0200 @@ -343,7 +343,6 @@ 'connections pools size)') def _free_pool(self, pool): - pool.rollback() self._available_pools.put_nowait(pool) def pinfo(self): diff -r 2b91abd9f5a4 -r 1fbcf202882d server/rqlannotation.py --- a/server/rqlannotation.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/rqlannotation.py Thu Jul 02 10:35:03 2009 +0200 @@ -22,21 +22,7 @@ has_text_query = False need_distinct = rqlst.distinct for rel in rqlst.iget_nodes(Relation): - if rel.neged(strict=True): - if rel.is_types_restriction(): - need_distinct = True - else: - rschema = getrschema(rel.r_type) - if not rschema.is_final(): - if rschema.inlined: - try: - var = rel.children[1].children[0].variable - except AttributeError: - pass # rewritten variable - else: - if not var.stinfo['constnode']: - need_distinct = True - elif getrschema(rel.r_type).symetric: + if getrschema(rel.r_type).symetric: for vref in rel.iget_nodes(VariableRef): stinfo = vref.variable.stinfo if not stinfo['constnode'] and stinfo['selected']: diff -r 2b91abd9f5a4 -r 1fbcf202882d server/schemahooks.py --- a/server/schemahooks.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/schemahooks.py Thu Jul 02 10:35:03 2009 +0200 @@ -38,7 +38,7 @@ def get_constraints(session, entity): constraints = [] for cstreid in session.transaction_data.get(entity.eid, ()): - cstrent = session.entity(cstreid) + cstrent = session.entity_from_eid(cstreid) cstr = CONSTRAINTS[cstrent.type].deserialize(cstrent.value) cstr.eid = cstreid constraints.append(cstr) diff -r 2b91abd9f5a4 -r 1fbcf202882d server/session.py --- a/server/session.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/session.py Thu Jul 02 10:35:03 2009 +0200 @@ -84,10 +84,6 @@ """return an entity class for the given entity type""" return self.vreg.etype_class(etype) - def entity(self, eid): - """return a result set for the given eid""" - return self.eid_rset(eid).get_entity(0, 0) - def system_sql(self, sql, args=None): """return a sql cursor on the system database""" if not sql.split(None, 1)[0].upper() == 'SELECT': @@ -270,6 +266,7 @@ assert not self.pending_operations self.transaction_data.clear() self._touch() + self.debug('commit session %s done (no db activity)', self.id) return if self.commit_state: return @@ -311,6 +308,7 @@ assert not self.pending_operations self.transaction_data.clear() self._touch() + self.debug('rollback session %s done (no db activity)', self.id) return try: while self.pending_operations: @@ -321,6 +319,7 @@ self.critical('rollback error', exc_info=sys.exc_info()) continue self.pool.rollback() + self.debug('rollback for session %s done', self.id) finally: self._touch() self.pending_operations[:] = [] @@ -359,16 +358,6 @@ self._threaddata.transaction_data = {} return self._threaddata.transaction_data - @obsolete('use direct access to session.transaction_data') - def query_data(self, key, default=None, setdefault=False, pop=False): - if setdefault: - assert not pop - return self.transaction_data.setdefault(key, default) - if pop: - return self.transaction_data.pop(key, default) - else: - return self.transaction_data.get(key, default) - @property def pending_operations(self): try: @@ -457,6 +446,21 @@ description.append(tuple(row_descr)) return description + @obsolete('use direct access to session.transaction_data') + def query_data(self, key, default=None, setdefault=False, pop=False): + if setdefault: + assert not pop + return self.transaction_data.setdefault(key, default) + if pop: + return self.transaction_data.pop(key, default) + else: + return self.transaction_data.get(key, default) + + @obsolete('use entity_from_eid(eid, etype=None)') + def entity(self, eid): + """return a result set for the given eid""" + return self.eid_rset(eid).get_entity(0, 0) + class ChildSession(Session): """child (or internal) session are used to hijack the security system diff -r 2b91abd9f5a4 -r 1fbcf202882d server/sources/rql2sql.py --- a/server/sources/rql2sql.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/sources/rql2sql.py Thu Jul 02 10:35:03 2009 +0200 @@ -490,10 +490,10 @@ sql.insert(1, 'FROM (SELECT 1) AS _T') sqls.append('\n'.join(sql)) if select.need_intersect: - if distinct or not self.dbms_helper.intersect_all_support: - return '\nINTERSECT\n'.join(sqls) - else: - return '\nINTERSECT ALL\n'.join(sqls) + #if distinct or not self.dbms_helper.intersect_all_support: + return '\nINTERSECT\n'.join(sqls) + #else: + # return '\nINTERSECT ALL\n'.join(sqls) elif distinct: return '\nUNION\n'.join(sqls) else: @@ -661,13 +661,27 @@ lhsvar, _, rhsvar, rhsconst = relation_info(relation) # we are sure here to have a lhsvar assert lhsvar is not None - lhssql = self._inlined_var_sql(lhsvar, relation.r_type) if isinstance(relation.parent, Not): self._state.done.add(relation.parent) - sql = "%s IS NULL" % lhssql if rhsvar is not None and not rhsvar._q_invariant: - sql = '(%s OR %s!=%s)' % (sql, lhssql, rhsvar.accept(self)) + # if the lhs variable is only linked to this relation, this mean we + # only want the relation to NOT exists + self._state.push_scope() + lhssql = self._inlined_var_sql(lhsvar, relation.r_type) + rhssql = rhsvar.accept(self) + restrictions, tables = self._state.pop_scope() + restrictions.append('%s=%s' % (lhssql, rhssql)) + if not tables: + sql = 'NOT EXISTS(SELECT 1 WHERE %s)' % ( + ' AND '.join(restrictions)) + else: + sql = 'NOT EXISTS(SELECT 1 FROM %s WHERE %s)' % ( + ', '.join(tables), ' AND '.join(restrictions)) + else: + lhssql = self._inlined_var_sql(lhsvar, relation.r_type) + sql = '%s IS NULL' % self._inlined_var_sql(lhsvar, relation.r_type) return sql + lhssql = self._inlined_var_sql(lhsvar, relation.r_type) if rhsconst is not None: return '%s=%s' % (lhssql, rhsconst.accept(self)) if isinstance(rhsvar, Variable) and not rhsvar.name in self._varmap: diff -r 2b91abd9f5a4 -r 1fbcf202882d server/test/unittest_extlite.py --- a/server/test/unittest_extlite.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/test/unittest_extlite.py Thu Jul 02 10:35:03 2009 +0200 @@ -7,37 +7,32 @@ sqlite_file = '_extlite_test.sqlite' def setUp(self): cnx1 = get_connection('sqlite', database=self.sqlite_file) - print 'SET IP' cu = cnx1.cursor() cu.execute('CREATE TABLE toto(name integer);') cnx1.commit() cnx1.close() - + def tearDown(self): try: os.remove(self.sqlite_file) except: pass + def test(self): lock = threading.Lock() - + def run_thread(): - print 'run_thread' cnx2 = get_connection('sqlite', database=self.sqlite_file) lock.acquire() - print 't2 sel1' cu = cnx2.cursor() cu.execute('SELECT name FROM toto') self.failIf(cu.fetchall()) cnx2.commit() - print 'done' lock.release() time.sleep(0.1) lock.acquire() - print 't2 sel2' cu.execute('SELECT name FROM toto') self.failUnless(cu.fetchall()) - print 'done' lock.release() cnx1 = get_connection('sqlite', database=self.sqlite_file) @@ -45,17 +40,13 @@ thread = threading.Thread(target=run_thread) thread.start() cu = cnx1.cursor() - print 't1 sel' cu.execute('SELECT name FROM toto') - print 'done' lock.release() time.sleep(0.1) cnx1.commit() lock.acquire() - print 't1 insert' cu.execute("INSERT INTO toto(name) VALUES ('toto')") cnx1.commit() - print 'done' lock.release() if __name__ == '__main__': diff -r 2b91abd9f5a4 -r 1fbcf202882d server/test/unittest_repository.py --- a/server/test/unittest_repository.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/test/unittest_repository.py Thu Jul 02 10:35:03 2009 +0200 @@ -56,13 +56,12 @@ namecol = SQL_PREFIX + 'name' finalcol = SQL_PREFIX + 'final' try: - sqlcursor = pool['system'] - sqlcursor.execute('SELECT %s FROM %s WHERE %s is NULL' % ( + cu = self.session.system_sql('SELECT %s FROM %s WHERE %s is NULL' % ( namecol, table, finalcol)) - self.assertEquals(sqlcursor.fetchall(), []) - sqlcursor.execute('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s' + self.assertEquals(cu.fetchall(), []) + cu = self.session.system_sql('SELECT %s FROM %s WHERE %s=%%(final)s ORDER BY %s' % (namecol, table, finalcol, namecol), {'final': 'TRUE'}) - self.assertEquals(sqlcursor.fetchall(), [(u'Boolean',), (u'Bytes',), + self.assertEquals(cu.fetchall(), [(u'Boolean',), (u'Bytes',), (u'Date',), (u'Datetime',), (u'Decimal',),(u'Float',), (u'Int',), @@ -358,38 +357,36 @@ entity.eid = -1 entity.complete = lambda x: None self.repo.add_info(self.session, entity, self.repo.sources_by_uri['system']) - cursor = self.session.pool['system'] - cursor.execute('SELECT * FROM entities WHERE eid = -1') - data = cursor.fetchall() + cu = self.session.system_sql('SELECT * FROM entities WHERE eid = -1') + data = cu.fetchall() self.assertIsInstance(data[0][3], datetime) data[0] = list(data[0]) data[0][3] = None self.assertEquals(tuplify(data), [(-1, 'Personne', 'system', None, None)]) self.repo.delete_info(self.session, -1) #self.repo.commit() - cursor.execute('SELECT * FROM entities WHERE eid = -1') - data = cursor.fetchall() + cu = self.session.system_sql('SELECT * FROM entities WHERE eid = -1') + data = cu.fetchall() self.assertEquals(data, []) class FTITC(RepositoryBasedTC): def test_reindex_and_modified_since(self): - cursor = self.session.pool['system'] eidp = self.execute('INSERT Personne X: X nom "toto", X prenom "tutu"')[0][0] self.commit() ts = datetime.now() self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1) - cursor.execute('SELECT mtime, eid FROM entities WHERE eid = %s' % eidp) - omtime = cursor.fetchone()[0] + cu = self.session.system_sql('SELECT mtime, eid FROM entities WHERE eid = %s' % eidp) + omtime = cu.fetchone()[0] # our sqlite datetime adapter is ignore seconds fraction, so we have to # ensure update is done the next seconds time.sleep(1 - (ts.second - int(ts.second))) self.execute('SET X nom "tata" WHERE X eid %(x)s', {'x': eidp}, 'x') self.commit() self.assertEquals(len(self.execute('Personne X WHERE X has_text "tutu"')), 1) - cursor.execute('SELECT mtime FROM entities WHERE eid = %s' % eidp) - mtime = cursor.fetchone()[0] + cu = self.session.system_sql('SELECT mtime FROM entities WHERE eid = %s' % eidp) + mtime = cu.fetchone()[0] self.failUnless(omtime < mtime) self.commit() date, modified, deleted = self.repo.entities_modified_since(('Personne',), omtime) diff -r 2b91abd9f5a4 -r 1fbcf202882d server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/test/unittest_rql2sql.py Thu Jul 02 10:35:03 2009 +0200 @@ -251,9 +251,9 @@ # Any O WHERE NOT S corrected_in O, S eid %(x)s, S concerns P, O version_of P, O in_state ST, NOT ST name "published", O modification_date MTIME ORDERBY MTIME DESC LIMIT 9 ('Any O WHERE NOT S ecrit_par O, S eid 1, S inline1 P, O inline2 P', - '''SELECT DISTINCT O.cw_eid + '''SELECT O.cw_eid FROM cw_Note AS S, cw_Personne AS O -WHERE (S.cw_ecrit_par IS NULL OR S.cw_ecrit_par!=O.cw_eid) AND S.cw_eid=1 AND O.cw_inline2=S.cw_inline1'''), +WHERE NOT EXISTS(SELECT 1 WHERE S.cw_ecrit_par=O.cw_eid) AND S.cw_eid=1 AND O.cw_inline2=S.cw_inline1'''), ('DISTINCT Any S ORDERBY stockproc(SI) WHERE NOT S ecrit_par O, S para SI', '''SELECT T1.C0 FROM (SELECT DISTINCT S.cw_eid AS C0, STOCKPROC(S.cw_para) AS C1 @@ -698,7 +698,6 @@ FROM cw_Tag AS S, cw_Tag AS T, tags_relation AS rel_tags0 WHERE NOT (T.cw_eid=28258) AND rel_tags0.eid_from=T.cw_eid AND rel_tags0.eid_to=S.cw_eid'''), - ('Any X,Y WHERE X created_by Y, X eid 5, NOT Y eid 6', '''SELECT 5, rel_created_by0.eid_to FROM created_by_relation AS rel_created_by0 @@ -736,25 +735,25 @@ WHERE NOT EXISTS(SELECT 1 FROM evaluee_relation AS rel_evaluee0,cw_CWUser AS Y WHERE rel_evaluee0.eid_from=Y.cw_eid AND rel_evaluee0.eid_to=X.cw_eid)'''), ('Any X,T WHERE X title T, NOT X is Bookmark', - '''SELECT DISTINCT X.cw_eid, X.cw_title + '''SELECT X.cw_eid, X.cw_title FROM cw_Card AS X -UNION -SELECT DISTINCT X.cw_eid, X.cw_title +UNION ALL +SELECT X.cw_eid, X.cw_title FROM cw_EmailThread AS X'''), ('Any K,V WHERE P is CWProperty, P pkey K, P value V, NOT P for_user U', - '''SELECT DISTINCT P.cw_pkey, P.cw_value + '''SELECT P.cw_pkey, P.cw_value FROM cw_CWProperty AS P WHERE P.cw_for_user IS NULL'''), ('Any S WHERE NOT X in_state S, X is IN(Affaire, CWUser)', - '''SELECT DISTINCT S.cw_eid -FROM cw_Affaire AS X, cw_State AS S -WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid) + '''SELECT S.cw_eid +FROM cw_State AS S +WHERE NOT EXISTS(SELECT 1 FROM cw_Affaire AS X WHERE X.cw_in_state=S.cw_eid) INTERSECT -SELECT DISTINCT S.cw_eid -FROM cw_CWUser AS X, cw_State AS S -WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)'''), +SELECT S.cw_eid +FROM cw_State AS S +WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS X WHERE X.cw_in_state=S.cw_eid)'''), ] OUTER_JOIN = [ @@ -1030,9 +1029,9 @@ WHERE N.cw_ecrit_par=P.cw_eid AND N.cw_eid=0'''), ('Any N WHERE NOT N ecrit_par P, P nom "toto"', - '''SELECT DISTINCT N.cw_eid + '''SELECT N.cw_eid FROM cw_Note AS N, cw_Personne AS P -WHERE (N.cw_ecrit_par IS NULL OR N.cw_ecrit_par!=P.cw_eid) AND P.cw_nom=toto'''), +WHERE NOT EXISTS(SELECT 1 WHERE N.cw_ecrit_par=P.cw_eid) AND P.cw_nom=toto'''), ('Any P WHERE N ecrit_par P, N eid 0', '''SELECT N.cw_ecrit_par @@ -1045,9 +1044,9 @@ WHERE N.cw_ecrit_par=P.cw_eid AND N.cw_eid=0'''), ('Any P WHERE NOT N ecrit_par P, P is Personne, N eid 512', - '''SELECT DISTINCT P.cw_eid + '''SELECT P.cw_eid FROM cw_Note AS N, cw_Personne AS P -WHERE (N.cw_ecrit_par IS NULL OR N.cw_ecrit_par!=P.cw_eid) AND N.cw_eid=512'''), +WHERE NOT EXISTS(SELECT 1 WHERE N.cw_ecrit_par=P.cw_eid) AND N.cw_eid=512'''), ('Any S,ES,T WHERE S state_of ET, ET name "CWUser", ES allowed_transition T, T destination_state S', '''SELECT T.cw_destination_state, rel_allowed_transition1.eid_from, T.cw_eid @@ -1070,23 +1069,23 @@ INTERSECT = [ ('Any SN WHERE NOT X in_state S, S name SN', - '''SELECT DISTINCT S.cw_name -FROM cw_Affaire AS X, cw_State AS S -WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid) + '''SELECT S.cw_name +FROM cw_State AS S +WHERE NOT EXISTS(SELECT 1 FROM cw_Affaire AS X WHERE X.cw_in_state=S.cw_eid) INTERSECT -SELECT DISTINCT S.cw_name -FROM cw_CWUser AS X, cw_State AS S -WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid) +SELECT S.cw_name +FROM cw_State AS S +WHERE NOT EXISTS(SELECT 1 FROM cw_CWUser AS X WHERE X.cw_in_state=S.cw_eid) INTERSECT -SELECT DISTINCT S.cw_name -FROM cw_Note AS X, cw_State AS S -WHERE (X.cw_in_state IS NULL OR X.cw_in_state!=S.cw_eid)'''), +SELECT S.cw_name +FROM cw_State AS S +WHERE NOT EXISTS(SELECT 1 FROM cw_Note AS X WHERE X.cw_in_state=S.cw_eid)'''), ('Any PN WHERE NOT X travaille S, X nom PN, S is IN(Division, Societe)', '''SELECT X.cw_nom FROM cw_Personne AS X WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,cw_Division AS S WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid) -INTERSECT ALL +INTERSECT SELECT X.cw_nom FROM cw_Personne AS X WHERE NOT EXISTS(SELECT 1 FROM travaille_relation AS rel_travaille0,cw_Societe AS S WHERE rel_travaille0.eid_from=X.cw_eid AND rel_travaille0.eid_to=S.cw_eid)'''), diff -r 2b91abd9f5a4 -r 1fbcf202882d server/test/unittest_schemaserial.py --- a/server/test/unittest_schemaserial.py Mon Jun 29 14:12:18 2009 +0200 +++ b/server/test/unittest_schemaserial.py Thu Jul 02 10:35:03 2009 +0200 @@ -94,7 +94,7 @@ ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute', {'rt': 'cardinality', 'oe': 'String', 'ctname': u'SizeConstraint', 'se': 'CWAttribute', 'value': u'max=2'}), ('INSERT CWConstraint X: X value %(value)s, X cstrtype CT, EDEF constrained_by X WHERE CT name %(ctname)s, EDEF relation_type ER, EDEF from_entity SE, EDEF to_entity OE, ER name %(rt)s, SE name %(se)s, OE name %(oe)s, EDEF is CWAttribute', - {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11', u'??', u'1?'"}), + {'rt': 'cardinality', 'oe': 'String', 'ctname': u'StaticVocabularyConstraint', 'se': 'CWAttribute', 'value': u"u'?1', u'11'"}), ]) diff -r 2b91abd9f5a4 -r 1fbcf202882d sobjects/supervising.py --- a/sobjects/supervising.py Mon Jun 29 14:12:18 2009 +0200 +++ b/sobjects/supervising.py Thu Jul 02 10:35:03 2009 +0200 @@ -48,7 +48,7 @@ events = ('before_delete_entity',) def _call(self, eid): - entity = self.session.entity(eid) + entity = self.session.entity_from_eid(eid) try: title = entity.dc_title() except: diff -r 2b91abd9f5a4 -r 1fbcf202882d test/unittest_entity.py --- a/test/unittest_entity.py Mon Jun 29 14:12:18 2009 +0200 +++ b/test/unittest_entity.py Thu Jul 02 10:35:03 2009 +0200 @@ -296,6 +296,8 @@ self.assertEquals(e.printable_value('content'), e['content']) e['content'] = u'été' self.assertEquals(e.printable_value('content'), e['content']) + e['content'] = u'hop\r\nhop\nhip\rmomo' + self.assertEquals(e.printable_value('content'), u'hop\nhop\nhip\nmomo') def test_fulltextindex(self): diff -r 2b91abd9f5a4 -r 1fbcf202882d view.py --- a/view.py Mon Jun 29 14:12:18 2009 +0200 +++ b/view.py Thu Jul 02 10:35:03 2009 +0200 @@ -19,7 +19,7 @@ from cubicweb.selectors import require_group_compat, accepts_compat from cubicweb.appobject import AppRsetObject from cubicweb.utils import UStringIO, HTMLStream - +from cubicweb.schema import display_name # robots control NOINDEX = u'' @@ -369,20 +369,20 @@ category = 'anyrsetview' - def columns_labels(self, tr=True): + def columns_labels(self, mainindex=0, tr=True): if tr: - translate = display_name + translate = lambda val, req=self.req: display_name(req, val) else: - translate = lambda req, val: val - rqlstdescr = self.rset.syntax_tree().get_description()[0] # XXX missing Union support + translate = lambda val: val + # XXX [0] because of missing Union support + rqlstdescr = self.rset.syntax_tree().get_description(mainindex, + translate)[0] labels = [] - for colindex, attr in enumerate(rqlstdescr): + for colindex, label in enumerate(rqlstdescr): # compute column header - if colindex == 0 or attr == 'Any': # find a better label - label = ','.join(translate(self.req, et) + if label == 'Any': # find a better label + label = ','.join(translate(et) for et in self.rset.column_types(colindex)) - else: - label = translate(self.req, attr) labels.append(label) return labels diff -r 2b91abd9f5a4 -r 1fbcf202882d web/controller.py --- a/web/controller.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/controller.py Thu Jul 02 10:35:03 2009 +0200 @@ -184,6 +184,12 @@ elif '__redirectpath' in self.req.form: # if redirect path was explicitly specified in the form, use it path = self.req.form['__redirectpath'] + if self._edited_entity: + msg = newparams.get('__message', '') + msg += ' (%s)' % ( + self._edited_entity.absolute_url(), + self.req._('click here to see created entity')) + newparams['__createdpath'] = self._edited_entity.rest_path() elif self._after_deletion_path: # else it should have been set during form processing path, params = self._after_deletion_path diff -r 2b91abd9f5a4 -r 1fbcf202882d web/data/cubicweb.css --- a/web/data/cubicweb.css Mon Jun 29 14:12:18 2009 +0200 +++ b/web/data/cubicweb.css Thu Jul 02 10:35:03 2009 +0200 @@ -445,16 +445,15 @@ } div.sideBoxTitle { - padding: 0.2em 0px; background: #cfceb7; display: block; font: bold 100% Georgia; + padding : 2px 0; } div.sideBox { - padding: 0.2em 0px; + padding: 0 0 0.2em; margin-bottom: 0.5em; - background: #eeedd9; min-width: 21em; max-width: 50em; } @@ -467,6 +466,7 @@ div.sideBoxBody { padding: 0.2em 5px; + background: #eeedd9; } div.sideBoxBody a { diff -r 2b91abd9f5a4 -r 1fbcf202882d web/data/cubicweb.htmlhelpers.js --- a/web/data/cubicweb.htmlhelpers.js Mon Jun 29 14:12:18 2009 +0200 +++ b/web/data/cubicweb.htmlhelpers.js Thu Jul 02 10:35:03 2009 +0200 @@ -254,7 +254,7 @@ //============= page loading events ==========================================// function roundedCornersOnLoad() { - jQuery('div.sideBox').corner('bottom 6px'); + jQuery('div.sideBoxBody').corner('bottom 6px'); jQuery('div.boxTitle, div.boxPrefTitle, div.sideBoxTitle, th.month').corner('top 6px'); } diff -r 2b91abd9f5a4 -r 1fbcf202882d web/formfields.py --- a/web/formfields.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/formfields.py Thu Jul 02 10:35:03 2009 +0200 @@ -164,7 +164,12 @@ widgets which desire it.""" if self.choices is not None: if callable(self.choices): - vocab = self.choices(req=form.req) + try: + vocab = self.choices(form=form) + except TypeError: + warn('vocabulary method (eg field.choices) should now take ' + 'the form instance as argument', DeprecationWarning) + vocab = self.choices(req=form.req) else: vocab = self.choices if vocab and not isinstance(vocab[0], (list, tuple)): diff -r 2b91abd9f5a4 -r 1fbcf202882d web/formwidgets.py --- a/web/formwidgets.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/formwidgets.py Thu Jul 02 10:35:03 2009 +0200 @@ -231,6 +231,7 @@ """ type = 'radio' + # javascript widgets ########################################################### class DateTimePicker(TextInput): diff -r 2b91abd9f5a4 -r 1fbcf202882d web/request.py --- a/web/request.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/request.py Thu Jul 02 10:35:03 2009 +0200 @@ -150,6 +150,11 @@ del self.form[k] else: self.form[k] = v + # special key for created entity, added in controller's reset method + if '__createdpath' in params: + self.message += ' (%s)' % ( + self.build_url(params.pop('__createdpath')), + self._('click here to see created entity')) def no_script_form_param(self, param, default=None, value=None): """ensure there is no script in a user form param diff -r 2b91abd9f5a4 -r 1fbcf202882d web/test/unittest_form.py --- a/web/test/unittest_form.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/test/unittest_form.py Thu Jul 02 10:35:03 2009 +0200 @@ -5,6 +5,7 @@ :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ +from __future__ import with_statement from xml.etree.ElementTree import fromstring @@ -98,6 +99,14 @@ inputs = pageinfo.find_tag('input', False) self.failIf(any(attrs for t, attrs in inputs if attrs.get('name') == '__linkto')) + def test_reledit_composite_field(self): + rset = self.execute('INSERT BlogEntry X: X title "cubicweb.org", X content "hop"') + form = self.vreg.select_object('views', 'reledit', self.request(), + rset=rset, row=0, rtype='content') + data = form.render(row=0, rtype='content') + self.failUnless('edits-content' in data) + self.failUnless('edits-content_format' in data) + # form view tests ######################################################### def test_massmailing_formview(self): diff -r 2b91abd9f5a4 -r 1fbcf202882d web/test/unittest_views_baseviews.py --- a/web/test/unittest_views_baseviews.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/test/unittest_views_baseviews.py Thu Jul 02 10:35:03 2009 +0200 @@ -103,9 +103,9 @@ def test_sortvalue_with_display_col(self): e, rset, view = self._prepare_entity() - rqlstdescr = rset.syntax_tree().get_description()[0] # XXX missing Union support + labels = rset.column_labels() table = TableWidget(view) - table.columns = view.get_columns(rqlstdescr, [1, 2], None, None, None, None, 0) + table.columns = view.get_columns(labels, [1, 2], None, None, None, None, 0) expected = ['loo"ong blabla'[:10], e.creation_date.strftime('%Y-%m-%d %H:%M')] got = [loadjson(value) for _, value in table.itercols(0)] self.assertListEqual(got, expected) diff -r 2b91abd9f5a4 -r 1fbcf202882d web/uicfg.py --- a/web/uicfg.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/uicfg.py Thu Jul 02 10:35:03 2009 +0200 @@ -129,6 +129,21 @@ self._counter += 1 tag.setdefault('order', self._counter) + def tag_subject_of(self, key, tag): + subj, rtype, obj = key + if obj != '*': + self.warning('using explict target type in display_ctrl.tag_subject_of() ' + 'has no effect, use (%s, %s, "*") instead of (%s, %s, %s)', + subj, rtype, subj, rtype, obj) + super(DisplayCtrlRelationTags, self).tag_subject_of((subj, rtype, '*'), tag) + + def tag_object_of(self, key, tag): + subj, rtype, obj = key + if subj != '*': + self.warning('using explict subject type in display_ctrl.tag_object_of() ' + 'has no effect, use ("*", %s, %s) instead of (%s, %s, %s)', + rtype, obj, subj, rtype, obj) + super(DisplayCtrlRelationTags, self).tag_object_of(('*', rtype, obj), tag) def init_primaryview_display_ctrl(rtag, sschema, rschema, oschema, role): if role == 'subject': diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/__init__.py --- a/web/views/__init__.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/__init__.py Thu Jul 02 10:35:03 2009 +0200 @@ -53,9 +53,15 @@ return True return False -VID_BY_MIMETYPE = {'text/xml': 'xml', - # XXX rss, owl... - } +# FIXME: VID_BY_MIMETYPE is unfortunately a bit too naive since +# some browsers (e.g. FF2) send a bunch of mimetypes in +# the Accept header, for instance: +# text/xml,application/xml,application/xhtml+xml,text/html;q=0.9, +# text/plain;q=0.8,image/png,*/*;q=0.5 +VID_BY_MIMETYPE = { + #'text/xml': 'xml', + # XXX rss, owl... +} def vid_from_rset(req, rset, schema): """given a result set, return a view id""" if rset is None: @@ -108,4 +114,7 @@ self._generate(tmpfile) self.w(open(tmpfile).read()) finally: - os.unlink(tmpfile) + try: + os.unlink(tmpfile) + except Exception, ex: + self.warning('cant delete %s: %s', tmpfile, ex) diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/basecomponents.py --- a/web/views/basecomponents.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/basecomponents.py Thu Jul 02 10:35:03 2009 +0200 @@ -110,11 +110,6 @@ self.w(self.req._('anonymous')) self.w(u''' [%s]''' % (self.req._('i18n_login_popup'))) - # FIXME maybe have an other option to explicitely authorise registration - # also provide a working register view -# if self.config['anonymous-user']: -# self.w(u''' [%s]''' -# % (self.req._('i18n_register_user'))) else: self.w(self.req._('anonymous')) self.w(u' [%s]' diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/cwproperties.py --- a/web/views/cwproperties.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/cwproperties.py Thu Jul 02 10:35:03 2009 +0200 @@ -299,11 +299,10 @@ _ = form.req._ if entity.has_eid(): return [(_(entity.pkey), entity.pkey)] - # key beginning with 'system.' should usually not be edited by hand choices = entity.vreg.user_property_keys() return [(u'', u'')] + sorted(zip((_(v) for v in choices), choices)) - + class PropertyValueField(StringField): """specific field for CWProperty.value which will be different according to the selected key type and vocabulary information diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/editforms.py --- a/web/views/editforms.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/editforms.py Thu Jul 02 10:35:03 2009 +0200 @@ -102,17 +102,25 @@ if not default: default = self.req._('not specified') if rschema.is_final(): - if getattr(entity, rtype) is None: - value = default - else: - value = entity.printable_value(rtype) + value = entity.printable_value(rtype) + if not entity.has_perm('update'): + self.w(value) + return else: rset = entity.related(rtype, role) # XXX html_escape but that depends of the actual vid value = html_escape(self.view(vid, rset, 'null') or default) - if not entity.has_perm('update'): + # XXX consider local roles ? + if role == 'subject'and not rschema.has_perm(self.req, 'add', + fromeid=entity.eid): self.w(value) return + elif role == 'object'and not rschema.has_perm(self.req, 'add', + toeid=entity.eid): + self.w(value) + return + if not value.strip(): + value = default if rschema.is_final(): form = self._build_attribute_form(entity, value, rtype, role, reload, row, col, default) diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/editviews.py diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/facets.py --- a/web/views/facets.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/facets.py Thu Jul 02 10:35:03 2009 +0200 @@ -38,6 +38,9 @@ order = 1 roundcorners = True + needs_css = 'cubicweb.facets.css' + needs_js = ('cubicweb.ajax.js', 'cubicweb.formfilter.js') + def facetargs(self): """this method returns the list of extra arguments that should be used by the facet @@ -56,8 +59,8 @@ def call(self, view=None): req = self.req - req.add_js( ('cubicweb.ajax.js', 'cubicweb.formfilter.js') ) - req.add_css('cubicweb.facets.css') + req.add_js( self.needs_js ) + req.add_css( self.needs_css) if self.roundcorners: req.html_headers.add_onload('jQuery(".facet").corner("tl br 10px");') rset, vid, divid, paginate = self._get_context(view) diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/formrenderers.py --- a/web/views/formrenderers.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/formrenderers.py Thu Jul 02 10:35:03 2009 +0200 @@ -215,12 +215,29 @@ class BaseFormRenderer(FormRenderer): - """use form_renderer_id = 'base' if you want base FormRenderer without - adaptation by selection + """use form_renderer_id = 'base' if you want base FormRenderer layout even + when selected for an entity """ id = 'base' +class EntityBaseFormRenderer(BaseFormRenderer): + """use form_renderer_id = 'base' if you want base FormRenderer layout even + when selected for an entity + """ + __select__ = entity_implements('Any') + + def display_field(self, form, field): + if not super(EntityBaseFormRenderer, self).display_field(form, field): + if isinstance(field, HiddenInitialValueField): + field = field.visible_field + ismeta = form.edited_entity.e_schema.is_metadata(field.name) + return ismeta is not None and ( + ismeta[0] in self.display_fields or + (ismeta[0], 'subject') in self.display_fields) + return True + + class HTableFormRenderer(FormRenderer): """display fields horizontally in a table @@ -310,9 +327,11 @@ w(u'') -class EntityFormRenderer(FormRenderer): +class EntityFormRenderer(EntityBaseFormRenderer): """specific renderer for entity edition form (edition)""" - __select__ = entity_implements('Any') & yes() + id = 'default' + # needs some additional points in some case (XXX explain cases) + __select__ = EntityBaseFormRenderer.__select__ & yes() _options = FormRenderer._options + ('display_relations_form',) display_relations_form = True diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/primary.py --- a/web/views/primary.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/primary.py Thu Jul 02 10:35:03 2009 +0200 @@ -14,10 +14,10 @@ from cubicweb import Unauthorized from cubicweb.view import EntityView +from cubicweb.schema import display_name from cubicweb.web import uicfg - class PrimaryView(EntityView): """the full view of an non final entity""" id = 'primary' @@ -119,7 +119,19 @@ def render_entity_attributes(self, entity, siderelations=None): for rschema, tschemas, role, dispctrl in self._section_def(entity, 'attributes'): - vid = dispctrl.get('vid', 'reledit') + # don't use reledit as default vid for composite relation + if rschema.is_final(): + defaultvid = 'reledit' + # XXX use entity.e_schema.role_rproperty(role, rschema, 'composite', tschemas[0]) once yams > 0.23.0 is out + elif role == 'subject' and \ + rschema.rproperty(entity.e_schema, tschemas[0], 'composite'): + defaultvid = 'csv' + elif role == 'object' and \ + rschema.rproperty(tschemas[0], entity.e_schema, 'composite'): + defaultvid = 'csv' + else: + defaultvid = 'reledit' + vid = dispctrl.get('vid', defaultvid) if rschema.is_final() or vid == 'reledit': value = entity.view(vid, rtype=rschema.type, role=role) else: diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/schema.py --- a/web/views/schema.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/schema.py Thu Jul 02 10:35:03 2009 +0200 @@ -49,6 +49,7 @@ % (entity.dc_type().capitalize(), html_escape(entity.dc_long_title()))) + # CWEType ###################################################################### class CWETypeOneLineView(baseviews.OneLineView): @@ -85,11 +86,10 @@ def cell_call(self, row, col): entity = self.entity(row, col) self.w(u'

%s

' % _('Attributes')) - rset = self.req.execute('Any N,F,D,GROUP_CONCAT(C),I,J,DE,A ' - 'GROUPBY N,F,D,AA,A,I,J,DE ' + rset = self.req.execute('Any N,F,D,I,J,DE,A ' 'ORDERBY AA WHERE A is CWAttribute, ' 'A ordernum AA, A defaultval D, ' - 'A constrained_by C?, A description DE, ' + 'A description DE, ' 'A fulltextindexed I, A internationalizable J, ' 'A relation_type R, R name N, ' 'A to_entity O, O name F, ' @@ -97,25 +97,22 @@ {'x': entity.eid}) self.wview('editable-table', rset, 'null', displayfilter=True) self.w(u'

%s

' % _('Relations')) - rset = self.req.execute('Any N,C,F,M,K,D,A ORDERBY N ' - 'WITH N,C,F,M,D,K,A BEING (' - '(Any N,C,F,M,K,D,A ' - 'ORDERBY N WHERE A is CWRelation, ' - 'A description D, A composite K?, ' - 'A relation_type R, R name N, ' - 'A to_entity O, O name F, ' - 'A cardinality C, O meta M, ' - 'A from_entity S, S eid %(x)s)' - ' UNION ' - '(Any N,C,F,M,K,D,A ' - 'ORDERBY N WHERE A is CWRelation, ' - 'A description D, A composite K?, ' - 'A relation_type R, R name N, ' - 'A from_entity S, S name F, ' - 'A cardinality C, S meta M, ' - 'A to_entity O, O eid %(x)s))' - ,{'x': entity.eid}) - self.wview('editable-table', rset, 'null', displayfilter=True) + rset = self.req.execute( + 'Any R,C,TT,K,D,A,RN,TTN ORDERBY RN ' + 'WHERE A is CWRelation, A description D, A composite K?, ' + 'A relation_type R, R name RN, A to_entity TT, TT name TTN, ' + 'A cardinality C, A from_entity S, S eid %(x)s', + {'x': entity.eid}) + self.wview('editable-table', rset, 'null', displayfilter=True, + displaycols=range(6), mainindex=5) + rset = self.req.execute( + 'Any R,C,TT,K,D,A,RN,TTN ORDERBY RN ' + 'WHERE A is CWRelation, A description D, A composite K?, ' + 'A relation_type R, R name RN, A from_entity TT, TT name TTN, ' + 'A cardinality C, A to_entity O, O eid %(x)s', + {'x': entity.eid}) + self.wview('editable-table', rset, 'null', displayfilter=True, + displaycols=range(6), mainindex=5) class CWETypeSImageView(EntityView): diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/startup.py --- a/web/views/startup.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/startup.py Thu Jul 02 10:35:03 2009 +0200 @@ -194,15 +194,9 @@ id = 'schema-text' def call(self): - self.w(u'

%s

' % _('This is the list of types defined in the data ' - 'model ofin this application.')) - self.w(u'

%s

' % _('meta is True for types that are defined by the ' - 'framework itself (e.g. User and Group). ' - 'final is True for types that can not be the ' - 'subject of a relation (e.g. Int and String).')) - rset = self.req.execute('Any X,M,F ORDERBY N WHERE X is CWEType, X name N, ' - 'X meta M, X final F') - self.wview('editable-table', rset, displayfilter=True) + rset = self.req.execute('Any X ORDERBY N WHERE X is CWEType, X name N, ' + 'X final FALSE') + self.wview('table', rset, displayfilter=True) class ManagerSchemaPermissionsView(StartupView, management.SecurityViewMixIn): diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/tableview.py --- a/web/views/tableview.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/tableview.py Thu Jul 02 10:35:03 2009 +0200 @@ -93,7 +93,7 @@ def call(self, title=None, subvid=None, displayfilter=None, headers=None, displaycols=None, displayactions=None, actions=(), divid=None, - cellvids=None, cellattrs=None): + cellvids=None, cellattrs=None, mainindex=None): """Dumps a table displaying a composite query :param title: title added before table @@ -101,19 +101,18 @@ :param displayfilter: filter that selects rows to display :param headers: columns' titles """ - rset = self.rset req = self.req req.add_js('jquery.tablesorter.js') req.add_css(('cubicweb.tablesorter.css', 'cubicweb.tableview.css')) - rqlst = rset.syntax_tree() - # get rql description first since the filter form may remove some - # necessary information - rqlstdescr = rqlst.get_description()[0] # XXX missing Union support - mainindex = self.main_var_index() + # compute label first since the filter form may remove some necessary + # information from the rql syntax tree + if mainindex is None: + mainindex = self.main_var_index() + computed_labels = self.columns_labels(mainindex) hidden = True if not subvid and 'subvid' in req.form: subvid = req.form.pop('subvid') - divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(rset)) + divid = divid or req.form.get('divid') or 'rs%s' % make_uid(id(self.rset)) actions = list(actions) if mainindex is None: displayfilter, displayactions = False, False @@ -154,8 +153,8 @@ self.render_actions(divid, actions) # render table table = TableWidget(self) - for column in self.get_columns(rqlstdescr, displaycols, headers, subvid, - cellvids, cellattrs, mainindex): + for column in self.get_columns(computed_labels, displaycols, headers, + subvid, cellvids, cellattrs, mainindex): table.append_column(column) table.render(self.w) self.w(u'\n') @@ -188,20 +187,15 @@ box.render(w=self.w) self.w(u'
') - def get_columns(self, rqlstdescr, displaycols, headers, subvid, cellvids, - cellattrs, mainindex): + def get_columns(self, computed_labels, displaycols, headers, subvid, + cellvids, cellattrs, mainindex): columns = [] - for colindex, attr in enumerate(rqlstdescr): + for colindex, label in enumerate(computed_labels): if colindex not in displaycols: continue # compute column header if headers is not None: label = headers[displaycols.index(colindex)] - elif colindex == 0 or attr == 'Any': # find a better label - label = ','.join(display_name(self.req, et) - for et in self.rset.column_types(colindex)) - else: - label = display_name(self.req, attr) if colindex == mainindex: label += ' (%s)' % self.rset.rowcount column = TableColumn(label, colindex) @@ -214,7 +208,6 @@ column.append_renderer(self.finalview, colindex) else: column.append_renderer(subvid or 'incontext', colindex) - if cellattrs and colindex in cellattrs: for name, value in cellattrs[colindex].iteritems(): column.add_attr(name, value) @@ -297,7 +290,7 @@ title = None def call(self, title=None, subvid=None, headers=None, divid=None, - displaycols=None, displayactions=None): + displaycols=None, displayactions=None, mainindex=None): """Dumps a table displaying a composite query""" actrql = self.req.form['actualrql'] self.ensure_ro_rql(actrql) @@ -312,7 +305,8 @@ title = self.req.form.pop('title') if title: self.w(u'

%s

\n' % title) - mainindex = self.main_var_index() + if mainindex is None: + mainindex = self.main_var_index() if mainindex is not None: actions = self.form_filter(divid, displaycols, displayactions, True) else: diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/treeview.py --- a/web/views/treeview.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/treeview.py Thu Jul 02 10:35:03 2009 +0200 @@ -106,7 +106,8 @@ liclasses = [] is_last = row == len(self.rset) - 1 is_open = self.open_state(entity.eid, treeid) - if not hasattr(entity, 'is_leaf') or entity.is_leaf(): + is_leaf = not hasattr(entity, 'is_leaf') or entity.is_leaf() + if is_leaf: if is_last: liclasses.append('last') w(u'
  • ' % u' '.join(liclasses)) @@ -145,7 +146,7 @@ w(u'
    • place holder
    ') # the local node info self.wview(vid, self.rset, row=row, col=col) - if is_open: # => not leaf => rql is defined + if is_open and not is_leaf: # => rql is defined self.wview(parentvid, self.req.execute(rql), treeid=treeid, initial_load=False) w(u'
  • ') diff -r 2b91abd9f5a4 -r 1fbcf202882d web/views/xmlrss.py --- a/web/views/xmlrss.py Mon Jun 29 14:12:18 2009 +0200 +++ b/web/views/xmlrss.py Thu Jul 02 10:35:03 2009 +0200 @@ -77,7 +77,7 @@ w = self.w rset, descr = self.rset, self.rset.description eschema = self.schema.eschema - labels = self.columns_labels(False) + labels = self.columns_labels(tr=False) w(u'\n' % self.req.encoding) w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql()))) for rowindex, row in enumerate(self.rset):