--- 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)
--- 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 <div>`data`</div> 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('<div>%s</div>' % data)
# NOTE: lxml 1.1 (etch platforms) doesn't recognize
# the encoding=unicode parameter (lxml 2.0 does), this is
--- 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
--- 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):
--- 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')
--- 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
--- 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)
--- 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):
--- 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()
--- 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
```````````````````
--- 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"
--- 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
--- 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'))
--- 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)
--- 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,
--- 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 ###############################################################
--- 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()
--- 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):
--- 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']:
--- 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):
--- 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[:] = []
--- 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)
--- 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:
--- 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__':
--- 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):
--- 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')
--- 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)
--- 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)'''),
--- 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'"}),
])
--- 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:
--- 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):
--- 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)
--- 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'<meta name="ROBOTS" content="NOINDEX" />'
@@ -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
--- 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
--- 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',
--- 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 += ' (<a href="%s">%s</a>)' % (
+ 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
--- 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();
}
/*
--- 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;
--- 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);
}
--- 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');
}
--- 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)):
--- 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):
--- 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 += ' (<a href="%s">%s</a>)' % (
+ 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
--- 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)
--- 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):
--- 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)
--- 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':
--- 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)
--- 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()
--- 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''' [<a class="logout" href="javascript: popupLoginBox();">%s</a>]'''
% (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''' [<a class="logout" href="?vid=register">%s</a>]'''
-# % (self.req._('i18n_register_user')))
else:
self.w(self.req._('anonymous'))
self.w(u' [<a class="logout" href="%s">%s</a>]'
--- 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);
</script>""" % (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):
--- 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
--- 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
--- 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
--- 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)
--- 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
--- 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'<div class="facetTitle">%s</div>'
+
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'<div class="facetTitle"><a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a></div>' % (
- html_escape(bk_base_url),
- html_escape(bk_add_url),
- req._('bookmark this search')))
w(u'<form method="post" id="%sForm" cubicweb:facetargs="%s" action="">' % (
divid, html_escape(dumps([divid, vid, paginate, self.facetargs()]))))
w(u'<fieldset>')
@@ -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'<a cubicweb:target="%s" id="facetBkLink" href="%s">%s</a>' % (
+ 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',
--- 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'</tr>')
-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'<div class="iformBody">')
values['removemsg'] = self.req.__('remove this %s' % form.edited_entity.e_schema)
w(u'<div class="iformTitle"><span>%(title)s</span> '
- '#<span class="icounter">1</span> '
+ '#<span class="icounter">%(counter)s</span> '
'[<a href="javascript: %(removejs)s;noop();">%(removemsg)s</a>]</div>'
% values)
# cleanup values
--- 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
--- 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'<p>%s</p>' % _('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())
--- 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):
--- 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'<table width="100%"><tr><td style="width: 75%">')
- self.w(u'<div>')
self.w(u'<div class="mainInfo">')
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'</div>')
if self.main_related_section:
try:
self.render_entity_relations(entity)
@@ -74,9 +72,9 @@
'deprecated')
self.render_entity_relations(entity, [])
self.w(u'</div>')
+ # side boxes
if boxes or hasattr(self, 'render_side_related'):
self.w(u'</td><td>')
- # side boxes
self.w(u'<div class="primaryRight">')
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:
--- 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'<h2>%s</h2>' % _('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'<h2>%s</h2>' % _('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):
--- 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'<p>%s</p>' % _('This is the list of types defined in the data '
- 'model ofin this application.'))
- self.w(u'<p>%s</p>' % _('<em>meta</em> is True for types that are defined by the '
- 'framework itself (e.g. User and Group). '
- '<em>final</em> 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):
--- 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'</div>\n')
@@ -188,20 +187,15 @@
box.render(w=self.w)
self.w(u'<div class="clear"/>')
- 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'<h2>%s</h2>\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:
--- 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'</div>')
# 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),
--- 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'<li class="%s">' % u' '.join(liclasses))
@@ -145,7 +146,7 @@
w(u'<ul class="placeholder"><li>place holder</li></ul>')
# 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'</li>')
--- 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 @@
<etype>[[/<attribute name>]/<attribute value>]*
"""
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)
--- 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'<?xml version="1.0" encoding="%s"?>\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):
--- 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,