# HG changeset patch # User Nicolas Chauvat # Date 1246882495 -7200 # Node ID 3b2c1d55090a239a3e8e64e96a42778cac4fb335 # Parent 885873dc4361ccb3d24167693daa60d71f36f841# Parent 31269a9b9ec42a399b12aa2f423a13ab1540b489 merge diff -r 31269a9b9ec4 -r 3b2c1d55090a common/__init__.py --- a/common/__init__.py Mon Jul 06 14:13:49 2009 +0200 +++ b/common/__init__.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a common/uilib.py --- a/common/uilib.py Mon Jul 06 14:13:49 2009 +0200 +++ b/common/uilib.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a cwconfig.py --- a/cwconfig.py Mon Jul 06 14:13:49 2009 +0200 +++ b/cwconfig.py Mon Jul 06 14:14:55 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""" @@ -190,6 +194,12 @@ 'help': 'web server root url', 'group': 'main', 'inputlevel': 1, }), + ('allow-email-login', + {'type' : 'yn', + 'default': False, + 'help': 'allow users to login with their primary email if set', + 'group': 'main', 'inputlevel': 2, + }), ('use-request-subdomain', {'type' : 'yn', 'default': None, @@ -743,7 +753,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 +837,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 31269a9b9ec4 -r 3b2c1d55090a cwvreg.py --- a/cwvreg.py Mon Jul 06 14:13:49 2009 +0200 +++ b/cwvreg.py Mon Jul 06 14:14:55 2009 +0200 @@ -76,6 +76,11 @@ clear_cache(self, 'rqlhelper') # now we can load application's web objects self.register_objects(self.config.vregistry_path()) + # map lowered entity type names to their actual name + self.case_insensitive_etypes = {} + for etype in self.schema.entities(): + etype = str(etype) + self.case_insensitive_etypes[etype.lower()] = etype def update_schema(self, schema): """update .schema attribute on registered objects, necessary for some @@ -294,9 +299,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 31269a9b9ec4 -r 3b2c1d55090a dbapi.py --- a/dbapi.py Mon Jul 06 14:13:49 2009 +0200 +++ b/dbapi.py Mon Jul 06 14:14:55 2009 +0200 @@ -63,19 +63,19 @@ 'application' % nsid) return core.getProxyForURI(uri) -def repo_connect(repo, user, password, cnxprops=None): +def repo_connect(repo, login, password, cnxprops=None): """Constructor to create a new connection to the CubicWeb repository. Returns a Connection instance. """ cnxprops = cnxprops or ConnectionProperties('inmemory') - cnxid = repo.connect(unicode(user), password, cnxprops=cnxprops) + cnxid = repo.connect(unicode(login), password, cnxprops=cnxprops) cnx = Connection(repo, cnxid, cnxprops) if cnxprops.cnxtype == 'inmemory': cnx.vreg = repo.vreg return cnx -def connect(database=None, user=None, password=None, host=None, +def connect(database=None, login=None, password=None, host=None, group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True, initlog=True): """Constructor for creating a connection to the CubicWeb repository. @@ -111,11 +111,11 @@ vreg.set_schema(schema) else: vreg = None - cnx = repo_connect(repo, user, password, cnxprops) + cnx = repo_connect(repo, login, password, cnxprops) cnx.vreg = vreg return cnx -def in_memory_cnx(config, user, password): +def in_memory_cnx(config, login, password): """usefull method for testing and scripting to get a dbapi.Connection object connected to an in-memory repository instance """ @@ -128,7 +128,7 @@ repo = get_repository('inmemory', config=config, vreg=vreg) # connection to the CubicWeb repository cnxprops = ConnectionProperties('inmemory') - cnx = repo_connect(repo, user, password, cnxprops=cnxprops) + cnx = repo_connect(repo, login, password, cnxprops=cnxprops) return repo, cnx @@ -245,7 +245,7 @@ @property def user(self): if self._user is None and self.cnx: - self.set_user(self.cnx.user(self)) + self.set_user(self.cnx.user(self, {'lang': self.lang})) return self._user def set_user(self, user): @@ -367,6 +367,10 @@ """raise `BadSessionId` if the connection is no more valid""" self._repo.check_session(self.sessionid) + def set_session_props(self, **props): + """raise `BadSessionId` if the connection is no more valid""" + self._repo.set_session_props(self.sessionid, props) + def get_shared_data(self, key, default=None, pop=False): """return value associated to `key` in shared data""" return self._repo.get_shared_data(self.sessionid, key, default, pop) @@ -434,7 +438,8 @@ def user(self, req=None, props=None): """return the User object associated to this connection""" # cnx validity is checked by the call to .user_info - eid, login, groups, properties = self._repo.user_info(self.sessionid, props) + eid, login, groups, properties = self._repo.user_info(self.sessionid, + props) if req is None: req = self.request() rset = req.eid_rset(eid, 'CWUser') diff -r 31269a9b9ec4 -r 3b2c1d55090a debian/control --- a/debian/control Mon Jul 06 14:13:49 2009 +0200 +++ b/debian/control Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a devtools/apptest.py --- a/devtools/apptest.py Mon Jul 06 14:13:49 2009 +0200 +++ b/devtools/apptest.py Mon Jul 06 14:14:55 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): @@ -462,14 +462,13 @@ self.__close = repo.close self.cnxid = self.cnx.sessionid self.session = repo._sessions[self.cnxid] - # XXX copy schema since hooks may alter it and it may be not fully - # cleaned (missing some schema synchronization support) - try: - origschema = repo.__schema - except AttributeError: - origschema = repo.schema - repo.__schema = origschema if self.copy_schema: + # XXX copy schema since hooks may alter it and it may be not fully + # cleaned (missing some schema synchronization support) + try: + origschema = repo.__schema + except AttributeError: + repo.__schema = origschema = repo.schema repo.schema = deepcopy(origschema) repo.set_schema(repo.schema) # reset hooks repo.vreg.update_schema(repo.schema) diff -r 31269a9b9ec4 -r 3b2c1d55090a devtools/repotest.py --- a/devtools/repotest.py Mon Jul 06 14:13:49 2009 +0200 +++ b/devtools/repotest.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a devtools/testlib.py --- a/devtools/testlib.py Mon Jul 06 14:13:49 2009 +0200 +++ b/devtools/testlib.py Mon Jul 06 14:14:55 2009 +0200 @@ -245,6 +245,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 31269a9b9ec4 -r 3b2c1d55090a doc/book/en/admin/setup.rst --- a/doc/book/en/admin/setup.rst Mon Jul 06 14:13:49 2009 +0200 +++ b/doc/book/en/admin/setup.rst Mon Jul 06 14:14:55 2009 +0200 @@ -46,6 +46,13 @@ There is also a wide variety of cubes listed on http://www.cubicweb.org/Project available as debian packages and tarball. +The repositories are signed with `Logilab's gnupg key`_. To avoid warning on "apt-get update": +1. become root using sudo +2. download http://ftp.logilab.org/dists/logilab-dists-key.asc using e.g. wget +3. run "apt-key add logilab-dists-key.asc" +4. re-run apt-get update (manually or through the package manager, whichever you prefer) + +.. `Logilab's gnupg key` _http://ftp.logilab.org/dists/logilab-dists-key.asc Install from source ``````````````````` diff -r 31269a9b9ec4 -r 3b2c1d55090a i18n/fr.po --- a/i18n/fr.po Mon Jul 06 14:13:49 2009 +0200 +++ b/i18n/fr.po Mon Jul 06 14:14:55 2009 +0200 @@ -964,7 +964,7 @@ #, python-format msgid "at least one relation %(rtype)s is required on %(etype)s (%(eid)s)" msgstr "" -"L'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n" +"l'entité #%(eid)s de type %(etype)s doit nécessairement être reliée à une\n" "autre via la relation %(rtype)s" msgid "attribute" diff -r 31269a9b9ec4 -r 3b2c1d55090a schema.py --- a/schema.py Mon Jul 06 14:13:49 2009 +0200 +++ b/schema.py Mon Jul 06 14:14:55 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 @@ -108,6 +109,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 @@ -120,7 +131,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 @@ -345,6 +356,7 @@ """rql expression factory""" return ERQLExpression(expression, mainvars, eid) + class CubicWebRelationSchema(RelationSchema): RelationSchema._RPROPERTIES['eid'] = None _perms_checked = False diff -r 31269a9b9ec4 -r 3b2c1d55090a schemas/workflow.py --- a/schemas/workflow.py Mon Jul 06 14:13:49 2009 +0200 +++ b/schemas/workflow.py Mon Jul 06 14:14:55 2009 +0200 @@ -51,7 +51,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 31269a9b9ec4 -r 3b2c1d55090a selectors.py --- a/selectors.py Mon Jul 06 14:13:49 2009 +0200 +++ b/selectors.py Mon Jul 06 14:14:55 2009 +0200 @@ -482,8 +482,7 @@ def __call__(self, cls, req, *args, **kwargs): score = 0 for param in self.expected: - val = req.form.get(param) - if not val: + if not param in req.form: return 0 score += 1 return len(self.expected) @@ -623,6 +622,14 @@ etype = kwargs['etype'] except KeyError: return 0 + else: + # only check this is a known type if etype comes from req.form, + # else we want the error to propagate + try: + etype = cls.vreg.case_insensitive_etypes[etype.lower()] + req.form['etype'] = etype + except KeyError: + return 0 return self.score_class(cls.vreg.etype_class(etype), req) diff -r 31269a9b9ec4 -r 3b2c1d55090a server/checkintegrity.py --- a/server/checkintegrity.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/checkintegrity.py Mon Jul 06 14:14:55 2009 +0200 @@ -71,6 +71,14 @@ uniquecstrcheck_before_modification) from cubicweb.server.repository import FTIndexEntityOp repo = session.repo + cursor = session.pool['system'] + if not repo.system_source.indexer.has_fti_table(cursor): + from indexer import get_indexer + print 'no text index table' + indexer = get_indexer(repo.system_source.dbdriver) + # XXX indexer.init_fti(cursor) once index 0.7 is out + indexer.init_extensions(cursor) + cursor.execute(indexer.sql_init_fti()) repo.hm.unregister_hook(setmtime_before_update_entity, 'before_update_entity', '') repo.hm.unregister_hook(uniquecstrcheck_before_modification, diff -r 31269a9b9ec4 -r 3b2c1d55090a server/hookhelper.py --- a/server/hookhelper.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/hookhelper.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a server/hooks.py --- a/server/hooks.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/hooks.py Mon Jul 06 14:14:55 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): @@ -222,9 +222,10 @@ return if self.session.unsafe_execute(*self._rql()).rowcount < 1: etype = self.session.describe(self.eid)[0] - msg = self.session._('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)') - raise ValidationError(self.eid, {self.rtype: msg % {'rtype': self.rtype, - 'etype': etype, + _ = self.session._ + msg = _('at least one relation %(rtype)s is required on %(etype)s (%(eid)s)') + raise ValidationError(self.eid, {self.rtype: msg % {'rtype': _(self.rtype), + 'etype': _(etype), 'eid': self.eid}}) def commit_event(self): @@ -379,7 +380,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 31269a9b9ec4 -r 3b2c1d55090a server/repository.py --- a/server/repository.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/repository.py Mon Jul 06 14:14:55 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): @@ -393,10 +392,23 @@ except ZeroDivisionError: pass + def _login_from_email(self, login): + session = self.internal_session() + try: + rset = session.execute('Any L WHERE U login L, U primary_email M, ' + 'M address %(login)s', {'login': login}) + if rset.rowcount == 1: + login = rset[0][0] + finally: + session.close() + return login + def authenticate_user(self, session, login, password): """validate login / password, raise AuthenticationError on failure return associated CWUser instance on success """ + if self.vreg.config['allow-email-login'] and '@' in login: + login = self._login_from_email(login) for source in self.sources: if source.support_entity('CWUser'): try: @@ -406,11 +418,11 @@ continue else: raise AuthenticationError('authentication failed with all sources') - euser = self._build_user(session, eid) + cwuser = self._build_user(session, eid) if self.config.consider_user_state and \ - not euser.state in euser.AUTHENTICABLE_STATES: + not cwuser.state in cwuser.AUTHENTICABLE_STATES: raise AuthenticationError('user is not in authenticable state') - return euser + return cwuser def _build_user(self, session, eid): """return a CWUser entity for user with the given eid""" @@ -418,13 +430,13 @@ rql = cls.fetch_rql(session.user, ['X eid %(x)s']) rset = session.execute(rql, {'x': eid}, 'x') assert len(rset) == 1, rset - euser = rset.get_entity(0, 0) + cwuser = rset.get_entity(0, 0) # pylint: disable-msg=W0104 - # prefetch / cache euser's groups and properties. This is especially + # prefetch / cache cwuser's groups and properties. This is especially # useful for internal sessions to avoid security insertions - euser.groups - euser.properties - return euser + cwuser.groups + cwuser.properties + return cwuser # public (dbapi) interface ################################################ @@ -665,13 +677,22 @@ custom properties) """ session = self._get_session(sessionid, setpool=False) - if props: - # update session properties - for prop, value in props.items(): - session.change_property(prop, value) + if props is not None: + self.set_session_props(sessionid, props) user = session.user return user.eid, user.login, user.groups, user.properties + def set_session_props(self, sessionid, props): + """this method should be used by client to: + * check session id validity + * update user information on each user's request (i.e. groups and + custom properties) + """ + session = self._get_session(sessionid, setpool=False) + # update session properties + for prop, value in props.items(): + session.change_property(prop, value) + # public (inter-repository) interface ##################################### def entities_modified_since(self, etypes, mtime): diff -r 31269a9b9ec4 -r 3b2c1d55090a server/rqlannotation.py --- a/server/rqlannotation.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/rqlannotation.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a server/schemahooks.py --- a/server/schemahooks.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/schemahooks.py Mon Jul 06 14:14:55 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) @@ -247,8 +247,7 @@ """actually add the entity type to the application's schema""" eid = None # make pylint happy def commit_event(self): - eschema = self.schema.add_entity_type(self.kobj) - eschema.eid = self.eid + self.schema.add_entity_type(self.kobj) def before_add_eetype(session, entity): """before adding a CWEType entity: @@ -299,7 +298,8 @@ # register operation to modify the schema on commit # this have to be done before adding other relations definitions # or permission settings - AddCWETypeOp(session, etype, eid=entity.eid) + etype.eid = entity.eid + AddCWETypeOp(session, etype) # add meta creation_date, modification_date and owned_by relations for rql, kwargs in relrqls: session.execute(rql, kwargs) @@ -311,7 +311,6 @@ def commit_event(self): rschema = self.schema.add_relation_type(self.kobj) rschema.set_default_groups() - rschema.eid = self.eid def before_add_ertype(session, entity): """before adding a CWRType entity: @@ -331,12 +330,13 @@ schema on commit We don't know yeat this point if a table is necessary """ - AddCWRTypeOp(session, RelationType(name=entity['name'], - description=entity.get('description'), - meta=entity.get('meta', False), - inlined=entity.get('inlined', False), - symetric=entity.get('symetric', False)), - eid=entity.eid) + rtype = RelationType(name=entity['name'], + description=entity.get('description'), + meta=entity.get('meta', False), + inlined=entity.get('inlined', False), + symetric=entity.get('symetric', False)) + rtype.eid = entity.eid + AddCWRTypeOp(session, rtype) class AddErdefOp(EarlySchemaOperation): diff -r 31269a9b9ec4 -r 3b2c1d55090a server/session.py --- a/server/session.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/session.py Mon Jul 06 14:14:55 2009 +0200 @@ -266,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 @@ -307,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: @@ -317,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[:] = [] diff -r 31269a9b9ec4 -r 3b2c1d55090a server/sources/pyrorql.py --- a/server/sources/pyrorql.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/sources/pyrorql.py Mon Jul 06 14:14:55 2009 +0200 @@ -164,7 +164,12 @@ """ self.info('synchronizing pyro source %s', self.uri) cnx = self.get_connection() - extrepo = cnx._repo + try: + extrepo = cnx._repo + except AttributeError: + # fake connection wrapper returned when we can't connect to the + # external source (hence we've no chance to synchronize...) + return etypes = self.support_entities.keys() if mtime is None: mtime = self.last_update_time() @@ -212,7 +217,7 @@ nsgroup = self.config.get('pyro-ns-group') or self.repo.config['pyro-ns-group'] #cnxprops = ConnectionProperties(cnxtype=self.config['cnx-type']) return dbapi.connect(database=self.config['pyro-ns-id'], - user=self.config['cubicweb-user'], + login=self.config['cubicweb-user'], password=self.config['cubicweb-password'], host=nshost, port=nsport, group=nsgroup, setvreg=False) #cnxprops=cnxprops) diff -r 31269a9b9ec4 -r 3b2c1d55090a server/sources/rql2sql.py --- a/server/sources/rql2sql.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/sources/rql2sql.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a server/test/unittest_extlite.py --- a/server/test/unittest_extlite.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/test/unittest_extlite.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a server/test/unittest_hooks.py --- a/server/test/unittest_hooks.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/test/unittest_hooks.py Mon Jul 06 14:14:55 2009 +0200 @@ -242,7 +242,7 @@ class SchemaModificationHooksTC(RepositoryBasedTC): - copy_schema = True + #copy_schema = True def setUp(self): if not hasattr(self, '_repo'): @@ -471,6 +471,10 @@ self.commit() # should not be able anymore to add personne without prenom self.assertRaises(ValidationError, self.execute, 'INSERT Personne X: X nom "toto"') + self.execute('SET DEF cardinality "?1" ' + 'WHERE DEF relation_type RT, DEF from_entity E,' + 'RT name "prenom", E name "Personne"') + self.commit() class WorkflowHooksTC(RepositoryBasedTC): diff -r 31269a9b9ec4 -r 3b2c1d55090a server/test/unittest_migractions.py --- a/server/test/unittest_migractions.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/test/unittest_migractions.py Mon Jul 06 14:14:55 2009 +0200 @@ -23,7 +23,7 @@ class MigrationCommandsTC(RepositoryBasedTC): - copy_schema = True + copy_schema = False def setUp(self): if not hasattr(self, '_repo'): @@ -146,7 +146,7 @@ for cstr in eschema.constraints('name'): self.failUnless(hasattr(cstr, 'eid')) - def test_drop_entity_type(self): + def test_add_drop_entity_type(self): self.mh.cmd_add_entity_type('Folder2') todoeid = self.mh.cmd_add_state(u'todo', 'Folder2', initial=True) doneeid = self.mh.cmd_add_state(u'done', 'Folder2') @@ -161,7 +161,7 @@ self.failIf(self.execute('State X WHERE NOT X state_of ET')) self.failIf(self.execute('Transition X WHERE NOT X transition_of ET')) - def test_add_relation_type(self): + def test_add_drop_relation_type(self): self.mh.cmd_add_entity_type('Folder2', auto=False) self.mh.cmd_add_relation_type('filed_under2') self.failUnless('filed_under2' in self.schema) @@ -169,52 +169,40 @@ ['Affaire', 'Card', 'Division', 'Email', 'EmailThread', 'File', 'Folder2', 'Image', 'Note', 'Personne', 'Societe', 'SubDivision']) self.assertEquals(self.schema['filed_under2'].objects(), ('Folder2',)) - - - def test_drop_relation_type(self): - self.mh.cmd_add_entity_type('Folder2', auto=False) - self.mh.cmd_add_relation_type('filed_under2') - self.failUnless('filed_under2' in self.schema) self.mh.cmd_drop_relation_type('filed_under2') self.failIf('filed_under2' in self.schema) - def test_add_relation_definition(self): - self.mh.cmd_add_relation_definition('Societe', 'in_state', 'State') - self.assertEquals(sorted(str(x) for x in self.schema['in_state'].subjects()), - ['Affaire', 'CWUser', 'Division', 'Note', 'Societe', 'SubDivision']) - self.assertEquals(self.schema['in_state'].objects(), ('State',)) - def test_add_relation_definition_nortype(self): self.mh.cmd_add_relation_definition('Personne', 'concerne2', 'Affaire') self.assertEquals(self.schema['concerne2'].subjects(), ('Personne',)) self.assertEquals(self.schema['concerne2'].objects(), ('Affaire',)) + self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Affaire') + self.failIf('concerne2' in self.schema) - def test_drop_relation_definition1(self): - self.failUnless('concerne' in self.schema) + def test_drop_relation_definition_existant_rtype(self): self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) self.mh.cmd_drop_relation_definition('Personne', 'concerne', 'Affaire') self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire']) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Division', 'Note', 'Societe', 'SubDivision']) + self.mh.cmd_add_relation_definition('Personne', 'concerne', 'Affaire') + self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) + self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) + # trick: overwrite self.maxeid to avoid deletion of just reintroduced types + self.maxeid = self.execute('Any MAX(X)')[0][0] def test_drop_relation_definition_with_specialization(self): - self.failUnless('concerne' in self.schema) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) self.mh.cmd_drop_relation_definition('Affaire', 'concerne', 'Societe') - self.mh.cmd_drop_relation_definition('None', 'concerne', 'Societe') self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Note']) - - def test_drop_relation_definition2(self): - self.failUnless('evaluee' in self.schema) - self.mh.cmd_drop_relation_definition('Personne', 'evaluee', 'Note') - self.failUnless('evaluee' in self.schema) - self.assertEquals(sorted(self.schema['evaluee'].subjects()), - ['CWUser', 'Division', 'Societe', 'SubDivision']) - self.assertEquals(sorted(self.schema['evaluee'].objects()), - ['Note']) + self.mh.cmd_add_relation_definition('Affaire', 'concerne', 'Societe') + self.assertEquals(sorted(str(e) for e in self.schema['concerne'].subjects()), ['Affaire', 'Personne']) + self.assertEquals(sorted(str(e) for e in self.schema['concerne'].objects()), ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) + # trick: overwrite self.maxeid to avoid deletion of just reintroduced types + self.maxeid = self.execute('Any MAX(X)')[0][0] def test_rename_relation(self): self.skip('implement me') @@ -455,7 +443,6 @@ ex = self.assertRaises(ConfigurationError, self.mh.cmd_remove_cube, 'file') self.assertEquals(str(ex), "can't remove cube file, used as a dependency") - def test_set_state(self): user = self.session.user self.mh.set_state(user.eid, 'deactivated') diff -r 31269a9b9ec4 -r 3b2c1d55090a server/test/unittest_repository.py --- a/server/test/unittest_repository.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/test/unittest_repository.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a server/test/unittest_rql2sql.py --- a/server/test/unittest_rql2sql.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/test/unittest_rql2sql.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a server/test/unittest_schemaserial.py --- a/server/test/unittest_schemaserial.py Mon Jul 06 14:13:49 2009 +0200 +++ b/server/test/unittest_schemaserial.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a sobjects/supervising.py --- a/sobjects/supervising.py Mon Jul 06 14:13:49 2009 +0200 +++ b/sobjects/supervising.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a test/unittest_entity.py --- a/test/unittest_entity.py Mon Jul 06 14:13:49 2009 +0200 +++ b/test/unittest_entity.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a utils.py --- a/utils.py Mon Jul 06 14:13:49 2009 +0200 +++ b/utils.py Mon Jul 06 14:14:55 2009 +0200 @@ -206,8 +206,13 @@ def add_post_inline_script(self, content): self.post_inlined_scripts.append(content) - def add_onload(self, jscode): - self.add_post_inline_script(u"""jQuery(document).ready(function () { + def add_onload(self, jscode, jsoncall=False): + if jsoncall: + self.add_post_inline_script(u"""jQuery(CubicWeb).bind('ajax-loaded', function(event) { +%s +});""" % jscode) + else: + self.add_post_inline_script(u"""jQuery(document).ready(function () { %s });""" % jscode) diff -r 31269a9b9ec4 -r 3b2c1d55090a view.py --- a/view.py Mon Jul 06 14:13:49 2009 +0200 +++ b/view.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a web/application.py --- a/web/application.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/application.py Mon Jul 06 14:14:55 2009 +0200 @@ -14,10 +14,11 @@ from cubicweb import set_log_methods from cubicweb import (ValidationError, Unauthorized, AuthenticationError, - NoSelectableObject, RepositoryError) + NoSelectableObject, RepositoryError) from cubicweb.cwvreg import CubicWebRegistry -from cubicweb.web import (LOGGER, StatusResponse, DirectResponse, Redirect, NotFound, - RemoteCallFailed, ExplicitLogin, InvalidSession) +from cubicweb.web import (LOGGER, StatusResponse, DirectResponse, Redirect, + NotFound, RemoteCallFailed, ExplicitLogin, + InvalidSession, RequestError) from cubicweb.web.component import Component # make session manager available through a global variable so the debug view can @@ -348,7 +349,7 @@ raise except ValidationError, ex: self.validation_error_handler(req, ex) - except (Unauthorized, BadRQLQuery), ex: + except (Unauthorized, BadRQLQuery, RequestError), ex: self.error_handler(req, ex, tb=False) except Exception, ex: self.error_handler(req, ex, tb=True) @@ -404,8 +405,8 @@ return self.vreg.main_template(req, template, view=view) def main_template_id(self, req): - template = req.property_value('ui.main-template') - if template not in self.vreg.registry('views') : + template = req.form.get('__template', req.property_value('ui.main-template')) + if template not in self.vreg.registry('views'): template = 'main-template' return template diff -r 31269a9b9ec4 -r 3b2c1d55090a web/component.py --- a/web/component.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/component.py Mon Jul 06 14:14:55 2009 +0200 @@ -6,6 +6,7 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ __docformat__ = "restructuredtext en" +_ = unicode from logilab.common.deprecation import class_renamed from logilab.mtconverter import html_escape @@ -18,7 +19,6 @@ partial_has_related_entities, condition_compat, accepts_compat, has_relation_compat) -_ = unicode class EntityVComponent(Component): """abstract base class for additinal components displayed in content @@ -37,7 +37,7 @@ property_defs = { _('visible'): dict(type='Boolean', default=True, - help=_('display the box or not')), + help=_('display the component or not')), _('order'): dict(type='Int', default=99, help=_('display order of the component')), _('context'): dict(type='String', default='header', diff -r 31269a9b9ec4 -r 3b2c1d55090a web/controller.py --- a/web/controller.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/controller.py Mon Jul 06 14:14:55 2009 +0200 @@ -182,6 +182,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 31269a9b9ec4 -r 3b2c1d55090a web/data/cubicweb.ajax.js --- a/web/data/cubicweb.ajax.js Mon Jul 06 14:13:49 2009 +0200 +++ b/web/data/cubicweb.ajax.js Mon Jul 06 14:14:55 2009 +0200 @@ -11,15 +11,20 @@ function _loadAjaxHtmlHead(node, head, tag, srcattr) { var loaded = []; - jQuery('head ' + tag).each(function(i) { + var jqtagfilter = tag + '[' + srcattr + ']'; + jQuery('head ' + jqtagfilter).each(function(i) { loaded.push(this.getAttribute(srcattr)); }); node.find(tag).each(function(i) { - if (!loaded.contains(this.getAttribute(srcattr))) { + if (this.getAttribute(srcattr)) { + if (!loaded.contains(this.getAttribute(srcattr))) { + jQuery(this).appendTo(head); + } + } else { jQuery(this).appendTo(head); } }); - node.find(tag).remove(); + node.find(jqtagfilter).remove(); } /* diff -r 31269a9b9ec4 -r 3b2c1d55090a web/data/cubicweb.css --- a/web/data/cubicweb.css Mon Jul 06 14:13:49 2009 +0200 +++ b/web/data/cubicweb.css Mon Jul 06 14:14:55 2009 +0200 @@ -445,18 +445,14 @@ } div.sideBoxTitle { - padding: 0.2em 0px; background: #cfceb7; display: block; font: bold 100% Georgia; } div.sideBox { - padding: 0.2em 0px; + padding: 0 0 0.2em; margin-bottom: 0.5em; - background: #eeedd9; - min-width: 21em; - max-width: 50em; } ul.sideBox li{ @@ -467,6 +463,7 @@ div.sideBoxBody { padding: 0.2em 5px; + background: #eeedd9; } div.sideBoxBody a { @@ -575,8 +572,6 @@ } div.primaryRight{ - float:right; - } div.metadata { @@ -687,7 +682,7 @@ /***************************************/ table.listing { - margin: 10px 0em; + padding: 10px 0em; color: #000; width: 100%; border-right: 1px solid #dfdfdf; diff -r 31269a9b9ec4 -r 3b2c1d55090a web/data/cubicweb.edition.js --- a/web/data/cubicweb.edition.js Mon Jul 06 14:13:49 2009 +0200 +++ b/web/data/cubicweb.edition.js Mon Jul 06 14:14:55 2009 +0200 @@ -305,7 +305,9 @@ } function _clearPreviousErrors(formid) { + jQuery('#' + formid + 'ErrorMessage').remove(); jQuery('#' + formid + ' span.error').remove(); + jQuery('#' + formid + ' .error').removeClass('error'); } function _displayValidationerrors(formid, eid, errors) { @@ -324,7 +326,7 @@ field.before(span); } else { firsterrfield = formid; - globalerrors.push(fieldname + ': ' + errmsg); + globalerrors.push(_(fieldname) + ' : ' + errmsg); } } if (globalerrors.length) { @@ -334,7 +336,7 @@ var innernode = UL(null, map(LI, globalerrors)); } // insert DIV and innernode before the form - var div = DIV({'class' : "errorMessage"}); + var div = DIV({'class' : "errorMessage", 'id': formid + 'ErrorMessage'}); div.appendChild(innernode); jQuery('#' + formid).before(div); } diff -r 31269a9b9ec4 -r 3b2c1d55090a web/data/cubicweb.htmlhelpers.js --- a/web/data/cubicweb.htmlhelpers.js Mon Jul 06 14:13:49 2009 +0200 +++ b/web/data/cubicweb.htmlhelpers.js Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a web/formfields.py --- a/web/formfields.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/formfields.py Mon Jul 06 14:14:55 2009 +0200 @@ -7,6 +7,7 @@ """ __docformat__ = "restructuredtext en" +from warnings import warn from datetime import datetime from logilab.mtconverter import html_escape @@ -164,7 +165,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 31269a9b9ec4 -r 3b2c1d55090a web/formwidgets.py --- a/web/formwidgets.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/formwidgets.py Mon Jul 06 14:14:55 2009 +0200 @@ -231,6 +231,7 @@ """ type = 'radio' + # javascript widgets ########################################################### class DateTimePicker(TextInput): diff -r 31269a9b9ec4 -r 3b2c1d55090a web/request.py --- a/web/request.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/request.py Mon Jul 06 14:14:55 2009 +0200 @@ -61,6 +61,7 @@ class CubicWebRequestBase(DBAPIRequest): """abstract HTTP request, should be extended according to the HTTP backend""" + json_request = False # to be set to True by json controllers def __init__(self, vreg, https, form=None): super(CubicWebRequestBase, self).__init__(vreg) @@ -91,7 +92,7 @@ or an anonymous connection is open """ super(CubicWebRequestBase, self).set_connection(cnx, user) - # get request language: + # set request language vreg = self.vreg if self.user: try: @@ -114,6 +115,7 @@ def set_language(self, lang): self._ = self.__ = self.translations[lang] self.lang = lang + self.cnx.set_session_props(lang=lang) self.debug('request language: %s', lang) # input form parameters management ######################################## @@ -150,6 +152,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 @@ -331,7 +338,6 @@ params[name] = value params['eid'] = eid if len(params) < minparams: - print eid, params raise RequestError(self._('missing parameters for entity %s') % eid) return params @@ -447,6 +453,9 @@ # high level methods for HTML headers management ########################## + def add_onload(self, jscode): + self.html_headers.add_onload(jscode, self.json_request) + def add_js(self, jsfiles, localfile=True): """specify a list of JS files to include in the HTML headers :param jsfiles: a JS filename or a list of JS filenames diff -r 31269a9b9ec4 -r 3b2c1d55090a web/test/unittest_application.py --- a/web/test/unittest_application.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/test/unittest_application.py Mon Jul 06 14:14:55 2009 +0200 @@ -307,7 +307,7 @@ self.assertEquals(cnx.password, origcnx.password) self.assertEquals(cnx.anonymous_connection, False) self.assertEquals(path, 'view') - self.assertEquals(params, {'__message': 'welcome %s !' % origcnx.login}) + self.assertEquals(params, {'__message': 'welcome %s !' % cnx.user().login}) def _test_auth_fail(self, req): self.assertRaises(AuthenticationError, self.app.connect, req) @@ -351,8 +351,8 @@ req.form['__password'] = origcnx.password self._test_auth_fail(req) # option allow-email-login set + origcnx.login = address self.set_option('allow-email-login', True) - req, origcnx = self._init_auth('cookie') req.form['__login'] = address req.form['__password'] = origcnx.password self._test_auth_succeed(req, origcnx) diff -r 31269a9b9ec4 -r 3b2c1d55090a web/test/unittest_form.py --- a/web/test/unittest_form.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/test/unittest_form.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a web/test/unittest_views_baseviews.py --- a/web/test/unittest_views_baseviews.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/test/unittest_views_baseviews.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a web/uicfg.py --- a/web/uicfg.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/uicfg.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a web/views/__init__.py --- a/web/views/__init__.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/__init__.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a web/views/authentication.py --- a/web/views/authentication.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/authentication.py Mon Jul 06 14:14:55 2009 +0200 @@ -36,7 +36,10 @@ # calling cnx.user() check connection validity, raise # BadConnectionId on failure user = cnx.user(req) - if login and user.login != login: + # check cnx.login and not user.login, since in case of login by + # email, login and cnx.login are the email while user.login is the + # actual user login + if login and cnx.login != login: cnx.close() raise InvalidSession('login mismatch') except BadConnectionId: @@ -53,18 +56,6 @@ req.set_connection(cnx, user) return cnx - def login_from_email(self, login): - # XXX should not be called from web interface - session = self.repo.internal_session() - try: - rset = session.execute('Any L WHERE U login L, U primary_email M, ' - 'M address %(login)s', {'login': login}) - if rset.rowcount == 1: - login = rset[0][0] - finally: - session.close() - return login - def authenticate(self, req, _login=None, _password=None): """authenticate user and return corresponding user object @@ -79,8 +70,6 @@ login, password = _login, _password else: login, password = req.get_authorization() - if self.vreg.config['allow-email-login'] and '@' in (login or u''): - login = self.login_from_email(login) if not login: # No session and no login -> try anonymous login, password = self.vreg.config.anonymous_user() diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/basecomponents.py --- a/web/views/basecomponents.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/basecomponents.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a web/views/basecontrollers.py --- a/web/views/basecontrollers.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/basecontrollers.py Mon Jul 06 14:14:55 2009 +0200 @@ -178,37 +178,51 @@ req.execute(rql, {'x': eid, 'y': typed_eid(teid)}, ('x', 'y')) +def _validation_error(req, ex): + forminfo = req.get_session_data(req.form.get('__errorurl'), pop=True) + foreid = ex.entity + eidmap = req.data.get('eidmap', {}) + for var, eid in eidmap.items(): + if foreid == eid: + foreid = var + break + return (foreid, ex.errors) + +def _validate_form(req, vreg): + # XXX should use the `RemoteCallFailed` mechanism + try: + ctrl = vreg.select(vreg.registry_objects('controllers', 'edit'), + req=req) + except NoSelectableObject: + return (False, {None: req._('not authorized')}) + try: + ctrl.publish(None) + except ValidationError, ex: + req.cnx.rollback() + return (False, _validation_error(req, ex)) + except Redirect, ex: + try: + req.cnx.commit() # ValidationError may be raise on commit + except ValidationError, ex: + req.cnx.rollback() + return (False, _validation_error(req, ex)) + else: + return (True, ex.location) + except Exception, ex: + req.cnx.rollback() + req.exception('unexpected error while validating form') + return (False, req._(str(ex).decode('utf-8'))) + return (False, '???') + + class FormValidatorController(Controller): id = 'validateform' def publish(self, rset=None): - vreg = self.vreg - try: - ctrl = vreg.select(vreg.registry_objects('controllers', 'edit'), - req=self.req, appli=self.appli) - except NoSelectableObject: - status, args = (False, {None: self.req._('not authorized')}) - else: - try: - ctrl.publish(None, fromjson=True) - except ValidationError, err: - status, args = self.validation_error(err) - except Redirect, err: - try: - self.req.cnx.commit() # ValidationError may be raise on commit - except ValidationError, err: - status, args = self.validation_error(err) - else: - status, args = (True, err.location) - except Exception, err: - self.req.cnx.rollback() - self.exception('unexpected error in validateform') - try: - status, args = (False, self.req._(unicode(err))) - except UnicodeError: - status, args = (False, repr(err)) - else: - status, args = (False, '???') + self.req.json_request = True + # XXX unclear why we have a separated controller here vs + # js_validate_form on the json controller + status, args = _validate_form(self.req, self.vreg) self.req.set_content_type('text/html') jsarg = simplejson.dumps( (status, args) ) domid = self.req.form.get('__domid', 'entityForm').encode( @@ -217,14 +231,6 @@ window.parent.handleFormValidationResponse('%s', null, null, %s); """ % (domid, simplejson.dumps( (status, args) )) - def validation_error(self, err): - self.req.cnx.rollback() - try: - eid = err.entity.eid - except AttributeError: - eid = err.entity - return (False, (eid, err.errors)) - class JSonController(Controller): id = 'json' @@ -238,6 +244,7 @@ note: it's the responsability of js_* methods to set the correct response content type """ + self.req.json_request = True self.req.pageid = self.req.form.get('pageid') try: fname = self.req.form['fname'] @@ -377,27 +384,8 @@ return self.validate_form(action, names, values) def validate_form(self, action, names, values): - # XXX this method (and correspoding js calls) should use the new - # `RemoteCallFailed` mechansim self.req.form = self._rebuild_posted_form(names, values, action) - vreg = self.vreg - try: - ctrl = vreg.select(vreg.registry_objects('controllers', 'edit'), - req=self.req) - except NoSelectableObject: - return (False, {None: self.req._('not authorized')}) - try: - ctrl.publish(None, fromjson=True) - except ValidationError, err: - self.req.cnx.rollback() - return (False, (err.entity, err.errors)) - except Redirect, redir: - return (True, redir.location) - except Exception, err: - self.req.cnx.rollback() - self.exception('unexpected error in js_validateform') - return (False, self.req._(str(err).decode('utf-8'))) - return (False, '???') + return _validate_form(self.req, self.vreg) @jsonize def js_edit_field(self, action, names, values, rtype, eid, default): diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/boxes.py --- a/web/views/boxes.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/boxes.py Mon Jul 06 14:14:55 2009 +0200 @@ -18,10 +18,10 @@ from logilab.mtconverter import html_escape -from cubicweb.rtags import RelationTags from cubicweb.selectors import match_user_groups, non_final_entity +from cubicweb.view import EntityView +from cubicweb.schema import display_name from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem -from cubicweb.view import EntityView from cubicweb.web import uicfg from cubicweb.web.box import BoxTemplate diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/cwproperties.py --- a/web/views/cwproperties.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/cwproperties.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a web/views/editcontroller.py --- a/web/views/editcontroller.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/editcontroller.py Mon Jul 06 14:14:55 2009 +0200 @@ -25,9 +25,8 @@ class EditController(ViewController): id = 'edit' - def publish(self, rset=None, fromjson=False): + def publish(self, rset=None): """edit / create / copy / delete entity / relations""" - self.fromjson = fromjson for key in self.req.form: # There should be 0 or 1 action if key.startswith('__action_'): @@ -113,9 +112,8 @@ entity = execute(rql, formparams).get_entity(0, 0) eid = entity.eid except ValidationError, ex: - # ex.entity may be an int or an entity instance self._to_create[formparams['eid']] = ex.entity - if self.fromjson: + if self.req.json_request: # XXX (syt) why? ex.entity = formparams['eid'] raise self._to_create[formparams['eid']] = eid diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/editforms.py --- a/web/views/editforms.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/editforms.py Mon Jul 06 14:14:55 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) @@ -360,8 +368,13 @@ divid = '%s-%s-%s' % (peid, rtype, entity.eid) title = self.schema.rschema(rtype).display_name(self.req, role) removejs = self.removejs % (peid, rtype,entity.eid) + countkey = '%s_count' % rtype + try: + self.req.data[countkey] += 1 + except: + self.req.data[countkey] = 1 self.w(form.form_render(divid=divid, title=title, removejs=removejs, - **kwargs)) + counter=self.req.data[countkey], **kwargs)) def add_hiddens(self, form, entity, peid, rtype, role): # to ease overriding (see cubes.vcsfile.views.forms for instance) diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/editviews.py --- a/web/views/editviews.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/editviews.py Mon Jul 06 14:14:55 2009 +0200 @@ -6,6 +6,7 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ __docformat__ = "restructuredtext en" +_ = unicode from simplejson import dumps @@ -21,7 +22,6 @@ from cubicweb.web.views.editforms import relation_id from cubicweb.web.views.baseviews import FinalView -_ = unicode class SearchForAssociationView(EntityView): """view called by the edition view when the user asks to search for diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/facets.py --- a/web/views/facets.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/facets.py Mon Jul 06 14:14:55 2009 +0200 @@ -38,6 +38,11 @@ order = 1 roundcorners = True + needs_css = 'cubicweb.facets.css' + needs_js = ('cubicweb.ajax.js', 'cubicweb.formfilter.js') + + bkLinkBox_template = u'
%s
' + def facetargs(self): """this method returns the list of extra arguments that should be used by the facet @@ -56,8 +61,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) @@ -77,18 +82,8 @@ widgets.append(wdg) if not widgets: return + self.displayBookmarkLink(rset) w = self.w - eschema = self.schema.eschema('Bookmark') - if eschema.has_perm(req, 'add'): - bk_path = 'view?rql=%s' % rset.printable_rql() - bk_title = req._('my custom search') - linkto = 'bookmarked_by:%s:subject' % self.req.user.eid - bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto) - bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto) - w(u'
%s
' % ( - html_escape(bk_base_url), - html_escape(bk_add_url), - req._('bookmark this search'))) w(u'
' % ( divid, html_escape(dumps([divid, vid, paginate, self.facetargs()])))) w(u'
') @@ -106,6 +101,20 @@ import cubicweb cubicweb.info('after facets with rql: %s' % repr(rqlst)) + def displayBookmarkLink(self, rset): + eschema = self.schema.eschema('Bookmark') + if eschema.has_perm(self.req, 'add'): + bk_path = 'view?rql=%s' % rset.printable_rql() + bk_title = self.req._('my custom search') + linkto = 'bookmarked_by:%s:subject' % self.req.user.eid + bk_add_url = self.build_url('add/Bookmark', path=bk_path, title=bk_title, __linkto=linkto) + bk_base_url = self.build_url('add/Bookmark', title=bk_title, __linkto=linkto) + bk_link = u'%s' % ( + html_escape(bk_base_url), + html_escape(bk_add_url), + self.req._('bookmark this search')) + self.w(self.bkLinkBox_template % bk_link) + def get_facets(self, rset, mainvar): return self.vreg.possible_vobjects('facets', self.req, rset, context='facetbox', diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/formrenderers.py --- a/web/views/formrenderers.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/formrenderers.py Mon Jul 06 14:14:55 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 @@ -481,7 +500,7 @@ w(u'
') values['removemsg'] = self.req.__('remove this %s' % form.edited_entity.e_schema) w(u'
%(title)s ' - '#1 ' + '#%(counter)s ' '[%(removemsg)s]
' % values) # cleanup values diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/forms.py --- a/web/views/forms.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/forms.py Mon Jul 06 14:14:55 2009 +0200 @@ -119,8 +119,8 @@ def form_add_hidden(self, name, value=None, **kwargs): """add an hidden field to the form""" - field = StringField(name=name, widget=fwdgs.HiddenInput, initial=value, - **kwargs) + kwargs.setdefault('widget', fwdgs.HiddenInput) + field = StringField(name=name, initial=value, **kwargs) if 'id' in kwargs: # by default, hidden input don't set id attribute. If one is # explicitly specified, ensure it will be set diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/management.py --- a/web/views/management.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/management.py Mon Jul 06 14:14:55 2009 +0200 @@ -14,7 +14,7 @@ from cubicweb.selectors import yes, none_rset, match_user_groups, authenticated_user from cubicweb.view import AnyRsetView, StartupView, EntityView from cubicweb.common.uilib import html_traceback, rest_traceback -from cubicweb.web import formwidgets +from cubicweb.web import formwidgets as wdgs from cubicweb.web.formfields import guess_field SUBMIT_MSGID = _('Submit bug report') @@ -110,7 +110,7 @@ form = self.vreg.select_object('forms', 'base', self.req, entity=entity, form_renderer_id='base', submitmsg=msg, - form_buttons=[formwidgets.SubmitButton()], + form_buttons=[wdgs.SubmitButton()], domid='ownership%s' % entity.eid, __redirectvid='security', __redirectpath=entity.rest_path()) @@ -167,7 +167,7 @@ newperm.eid = self.req.varmaker.next() w(u'

%s

' % _('add a new permission')) form = self.vreg.select_object('forms', 'base', self.req, entity=newperm, - form_buttons=[formwidgets.SubmitButton()], + form_buttons=[wdgs.SubmitButton()], domid='reqperm%s' % entity.eid, __redirectvid='security', __redirectpath=entity.rest_path()) @@ -177,7 +177,7 @@ cwpermschema = newperm.e_schema if permnames is not None: field = guess_field(cwpermschema, self.schema.rschema('name'), - widget=formwidgets.Select({'size': 1}), + widget=wdgs.Select({'size': 1}), choices=permnames) else: field = guess_field(cwpermschema, self.schema.rschema('name')) @@ -247,15 +247,17 @@ form = self.vreg.select_object('forms', 'base', self.req, rset=None, mainform=False) binfo = text_error_description(ex, excinfo, req, eversion, cversions) - form.form_add_hidden('description', binfo) + form.form_add_hidden('description', binfo, + # we must use a text area to keep line breaks + widget=wdgs.TextArea({'class': 'hidden'})) form.form_add_hidden('__bugreporting', '1') if submitmail: - form.form_buttons = [formwidgets.SubmitButton(MAIL_SUBMIT_MSGID)] + form.form_buttons = [wdgs.SubmitButton(MAIL_SUBMIT_MSGID)] form.action = req.build_url('reportbug') w(form.form_render()) if submiturl: form.form_add_hidden('description_format', 'text/rest') - form.form_buttons = [formwidgets.SubmitButton(SUBMIT_MSGID)] + form.form_buttons = [wdgs.SubmitButton(SUBMIT_MSGID)] form.action = submiturl w(form.form_render()) diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/navigation.py --- a/web/views/navigation.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/navigation.py Mon Jul 06 14:14:55 2009 +0200 @@ -6,6 +6,7 @@ :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses """ __docformat__ = "restructuredtext en" +_ = unicode from rql.nodes import VariableRef, Constant @@ -19,8 +20,6 @@ from cubicweb.common.uilib import cut from cubicweb.web.component import EntityVComponent, NavigationComponent -_ = unicode - class PageNavigation(NavigationComponent): diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/primary.py --- a/web/views/primary.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/primary.py Mon Jul 06 14:14:55 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' @@ -52,7 +52,6 @@ boxes = self._prepare_side_boxes(entity) if boxes or hasattr(self, 'render_side_related'): self.w(u'
') - self.w(u'
') self.w(u'
') self.content_navigation_components('navcontenttop') try: @@ -63,7 +62,6 @@ warn('siderelations argument of render_entity_attributes is ' 'deprecated (%s)' % self.__class__) self.render_entity_attributes(entity, []) - self.w(u'
') if self.main_related_section: try: self.render_entity_relations(entity) @@ -74,9 +72,9 @@ 'deprecated') self.render_entity_relations(entity, []) self.w(u'
') + # side boxes if boxes or hasattr(self, 'render_side_related'): self.w(u'
') - # side boxes self.w(u'
') if hasattr(self, 'render_side_related'): warn('render_side_related is deprecated') @@ -119,7 +117,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 31269a9b9ec4 -r 3b2c1d55090a web/views/schema.py --- a/web/views/schema.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/schema.py Mon Jul 06 14:14:55 2009 +0200 @@ -42,6 +42,7 @@ % (entity.dc_type().capitalize(), html_escape(entity.dc_long_title()))) + # CWEType ###################################################################### class CWETypeOneLineView(baseviews.OneLineView): @@ -81,11 +82,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, ' @@ -93,25 +93,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 31269a9b9ec4 -r 3b2c1d55090a web/views/startup.py --- a/web/views/startup.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/startup.py Mon Jul 06 14:14:55 2009 +0200 @@ -14,6 +14,7 @@ from cubicweb.view import StartupView from cubicweb.selectors import match_user_groups, implements +from cubicweb.schema import display_name from cubicweb.common.uilib import ureport_as_html from cubicweb.web import ajax_replace_url, uicfg, httpcache from cubicweb.web.views import tabs @@ -195,15 +196,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, SecurityViewMixIn): diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/tableview.py --- a/web/views/tableview.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/tableview.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a web/views/tabs.py --- a/web/views/tabs.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/tabs.py Mon Jul 06 14:14:55 2009 +0200 @@ -24,7 +24,7 @@ """ def _prepare_bindings(self, vid, reloadable): - self.req.html_headers.add_onload(u""" + self.req.add_onload(u""" jQuery('#lazy-%(vid)s').bind('%(event)s', function(event) { load_now('#lazy-%(vid)s', '#%(vid)s-hole', %(reloadable)s); });""" % {'event': 'load_%s' % vid, 'vid': vid, @@ -59,7 +59,7 @@ on dom readyness """ self.req.add_js('cubicweb.lazy.js') - self.req.html_headers.add_onload("trigger_load('%s');" % vid) + self.req.add_onload("trigger_load('%s');" % vid) class TabsMixin(LazyViewMixin): @@ -93,22 +93,11 @@ return selected_tabs def render_tabs(self, tabs, default, entity=None): - # tabbed views do no support concatenation - # hence we delegate to the default tab if there is more than on entity - # in the result set + # delegate to the default tab if there is more than one entity + # in the result set (tabs are pretty useless there) if entity and len(self.rset) > 1: entity.view(default, w=self.w) return - # XXX (syt) fix below add been introduced at some point to fix something - # (http://intranet.logilab.fr/jpl/ticket/32174 ?) but this is not a clean - # way. We must not consider form['rql'] here since it introduces some - # other failures on non rql queries (plain text, shortcuts,... handled by - # magicsearch) which has a single result whose primary view is using tabs - # (https://www.logilab.net/cwo/ticket/342789) - #rql = self.req.form.get('rql') - #if rql: - # self.req.execute(rql).get_entity(0,0).view(default, w=self.w) - # return self.req.add_css('ui.tabs.css') self.req.add_js(('ui.core.js', 'ui.tabs.js', 'cubicweb.ajax.js', 'cubicweb.tabs.js', 'cubicweb.lazy.js')) @@ -143,7 +132,7 @@ w(u'
') # call the set_tab() JS function *after* each tab is generated # because the callback binding needs to be done before - self.req.html_headers.add_onload(u""" + self.req.add_onload(u""" jQuery('#entity-tabs-%(eeid)s > ul').tabs( { selected: %(tabindex)s }); set_tab('%(vid)s', '%(cookiename)s'); """ % {'tabindex' : tabs.index(active_tab), diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/treeview.py --- a/web/views/treeview.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/treeview.py Mon Jul 06 14:14:55 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 31269a9b9ec4 -r 3b2c1d55090a web/views/urlpublishing.py --- a/web/views/urlpublishing.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/urlpublishing.py Mon Jul 06 14:14:55 2009 +0200 @@ -144,18 +144,12 @@ [[/]/]* """ priority = 2 - def __init__(self, urlpublisher): - super(RestPathEvaluator, self).__init__(urlpublisher) - self.etype_map = {} - for etype in self.schema.entities(): - etype = str(etype) - self.etype_map[etype.lower()] = etype def evaluate_path(self, req, parts): if not (0 < len(parts) < 4): raise PathDontMatch() try: - etype = self.etype_map[parts.pop(0).lower()] + etype = self.vreg.case_insensitive_etypes[parts.pop(0).lower()] except KeyError: raise PathDontMatch() cls = self.vreg.etype_class(etype) diff -r 31269a9b9ec4 -r 3b2c1d55090a web/views/xmlrss.py --- a/web/views/xmlrss.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/views/xmlrss.py Mon Jul 06 14:14:55 2009 +0200 @@ -79,7 +79,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): diff -r 31269a9b9ec4 -r 3b2c1d55090a web/webconfig.py --- a/web/webconfig.py Mon Jul 06 14:13:49 2009 +0200 +++ b/web/webconfig.py Mon Jul 06 14:14:55 2009 +0200 @@ -80,12 +80,6 @@ 'if anonymous-user is set', 'group': 'main', 'inputlevel': 1, }), - ('allow-email-login', - {'type' : 'yn', - 'default': False, - 'help': 'allow users to login with their primary email if set', - 'group': 'main', 'inputlevel': 2, - }), ('query-log-file', {'type' : 'string', 'default': None,