--- a/__pkginfo__.py Fri Sep 07 13:48:55 2012 +0200
+++ b/__pkginfo__.py Fri Sep 07 14:01:59 2012 +0200
@@ -43,7 +43,7 @@
'logilab-common': '>= 0.58.0',
'logilab-mtconverter': '>= 0.8.0',
'rql': '>= 0.31.2',
- 'yams': '>= 0.34.0',
+ 'yams': '>= 0.36.0',
#gettext # for xgettext, msgcat, etc...
# web dependancies
'simplejson': '>= 2.0.9',
--- a/debian/control Fri Sep 07 13:48:55 2012 +0200
+++ b/debian/control Fri Sep 07 14:01:59 2012 +0200
@@ -107,7 +107,7 @@
Package: cubicweb-common
Architecture: all
XB-Python-Version: ${python:Versions}
-Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.58.0), python-yams (>= 0.34.0), python-rql (>= 0.31.2), python-lxml
+Depends: ${misc:Depends}, ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.8.0), python-logilab-common (>= 0.58.0), python-yams (>= 0.36.0), python-rql (>= 0.31.2), python-lxml
Recommends: python-simpletal (>= 4.0), python-crypto
Conflicts: cubicweb-core
Replaces: cubicweb-core
--- a/doc/book/en/annexes/faq.rst Fri Sep 07 13:48:55 2012 +0200
+++ b/doc/book/en/annexes/faq.rst Fri Sep 07 14:01:59 2012 +0200
@@ -364,7 +364,7 @@
>>> crypted = crypt_password('joepass')
>>> rset = rql('Any U WHERE U is CWUser, U login "joe"')
>>> joe = rset.get_entity(0,0)
- >>> joe.set_attributes(upassword=Binary(crypted))
+ >>> joe.cw_set(upassword=Binary(crypted))
Please, refer to the script example is provided in the `misc/examples/chpasswd.py` file.
--- a/doc/book/en/devrepo/entityclasses/application-logic.rst Fri Sep 07 13:48:55 2012 +0200
+++ b/doc/book/en/devrepo/entityclasses/application-logic.rst Fri Sep 07 14:01:59 2012 +0200
@@ -38,7 +38,7 @@
object was built.
Setting an attribute or relation value can be done in the context of a
-Hook/Operation, using the obj.set_relations(x=42) notation or a plain
+Hook/Operation, using the obj.cw_set(x=42) notation or a plain
RQL SET expression.
In views, it would be preferable to encapsulate the necessary logic in
--- a/doc/book/en/devrepo/entityclasses/data-as-objects.rst Fri Sep 07 13:48:55 2012 +0200
+++ b/doc/book/en/devrepo/entityclasses/data-as-objects.rst Fri Sep 07 14:01:59 2012 +0200
@@ -16,50 +16,47 @@
`Formatting and output generation`:
-* `view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
+* :meth:`view(__vid, __registry='views', **kwargs)`, applies the given view to the entity
(and returns an unicode string)
-* `absolute_url(*args, **kwargs)`, returns an absolute URL including the base-url
+* :meth:`absolute_url(*args, **kwargs)`, returns an absolute URL including the base-url
-* `rest_path()`, returns a relative REST URL to get the entity
+* :meth:`rest_path()`, returns a relative REST URL to get the entity
-* `printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
+* :meth:`printable_value(attr, value=_marker, attrtype=None, format='text/html', displaytime=True)`,
returns a string enabling the display of an attribute value in a given format
(the value is automatically recovered if necessary)
`Data handling`:
-* `as_rset()`, converts the entity into an equivalent result set simulating the
+* :meth:`as_rset()`, converts the entity into an equivalent result set simulating the
request `Any X WHERE X eid _eid_`
-* `complete(skip_bytes=True)`, executes a request that recovers at
+* :meth:`complete(skip_bytes=True)`, executes a request that recovers at
once all the missing attributes of an entity
-* `get_value(name)`, returns the value associated to the attribute name given
+* :meth:`get_value(name)`, returns the value associated to the attribute name given
in parameter
-* `related(rtype, role='subject', limit=None, entities=False)`,
+* :meth:`related(rtype, role='subject', limit=None, entities=False)`,
returns a list of entities related to the current entity by the
relation given in parameter
-* `unrelated(rtype, targettype, role='subject', limit=None)`,
+* :meth:`unrelated(rtype, targettype, role='subject', limit=None)`,
returns a result set corresponding to the entities not (yet)
related to the current entity by the relation given in parameter
and satisfying its constraints
-* `set_attributes(**kwargs)`, updates the attributes list with the corresponding
- values given named parameters
+* :meth:`cw_set(**kwargs)`, updates entity's attributes and/or relation with the
+ corresponding values given named parameters. To set a relation where this
+ entity is the object of the relation, use `reverse_<relation>` as argument
+ name. Values may be an entity, a list of entities, or None (meaning that all
+ relations of the given type from or to this object should be deleted).
-* `set_relations(**kwargs)`, add relations to the given object. To
- set a relation where this entity is the object of the relation,
- use `reverse_<relation>` as argument name. Values may be an
- entity, a list of entities, or None (meaning that all relations of
- the given type from or to this object should be deleted).
-
-* `copy_relations(ceid)`, copies the relations of the entities having the eid
+* :meth:`copy_relations(ceid)`, copies the relations of the entities having the eid
given in the parameters on the current entity
-* `delete()` allows to delete the entity
+* :meth:`cw_delete()` allows to delete the entity
The :class:`AnyEntity` class
@@ -81,40 +78,30 @@
`Standard meta-data (Dublin Core)`:
-* `dc_title()`, returns a unicode string corresponding to the
+* :meth:`dc_title()`, returns a unicode string corresponding to the
meta-data `Title` (used by default is the first non-meta attribute
of the entity schema)
-* `dc_long_title()`, same as dc_title but can return a more
+* :meth:`dc_long_title()`, same as dc_title but can return a more
detailed title
-* `dc_description(format='text/plain')`, returns a unicode string
+* :meth:`dc_description(format='text/plain')`, returns a unicode string
corresponding to the meta-data `Description` (looks for a
description attribute by default)
-* `dc_authors()`, returns a unicode string corresponding to the meta-data
+* :meth:`dc_authors()`, returns a unicode string corresponding to the meta-data
`Authors` (owners by default)
-* `dc_creator()`, returns a unicode string corresponding to the
+* :meth:`dc_creator()`, returns a unicode string corresponding to the
creator of the entity
-* `dc_date(date_format=None)`, returns a unicode string corresponding to
+* :meth:`dc_date(date_format=None)`, returns a unicode string corresponding to
the meta-data `Date` (update date by default)
-* `dc_type(form='')`, returns a string to display the entity type by
+* :meth:`dc_type(form='')`, returns a string to display the entity type by
specifying the preferred form (`plural` for a plural form)
-* `dc_language()`, returns the language used by the entity
-
-
-`Misc methods`:
-
-* `after_deletion_path`, return (path, parameters) which should be
- used as redirect information when this entity is being deleted
-
-* `pre_web_edit`, callback called by the web editcontroller when an
- entity will be created/modified, to let a chance to do some entity
- specific stuff (does nothing by default)
+* :meth:`dc_language()`, returns the language used by the entity
Inheritance
-----------
--- a/doc/book/en/devrepo/repo/hooks.rst Fri Sep 07 13:48:55 2012 +0200
+++ b/doc/book/en/devrepo/repo/hooks.rst Fri Sep 07 14:01:59 2012 +0200
@@ -206,10 +206,11 @@
Reminder
~~~~~~~~
-You should never use the `entity.foo = 42` notation to update an
-entity. It will not do what you expect (updating the
-database). Instead, use the :meth:`set_attributes` and
-:meth:`set_relations` methods.
+You should never use the `entity.foo = 42` notation to update an entity. It will
+not do what you expect (updating the database). Instead, use the
+:meth:`~cubicweb.entity.Entity.cw_set` method or direct access to entity's
+:attr:`cw_edited` attribute if you're writing a hook for 'before_add_entity' or
+'before_update_entity' event.
How to choose between a before and an after event ?
--- a/doc/book/en/devrepo/testing.rst Fri Sep 07 13:48:55 2012 +0200
+++ b/doc/book/en/devrepo/testing.rst Fri Sep 07 14:01:59 2012 +0200
@@ -70,13 +70,13 @@
def test_cannot_create_cycles(self):
# direct obvious cycle
- self.assertRaises(ValidationError, self.kw1.set_relations,
+ self.assertRaises(ValidationError, self.kw1.cw_set,
subkeyword_of=self.kw1)
# testing indirect cycles
kw3 = self.execute('INSERT Keyword SK: SK name "kwgroup2", SK included_in C, '
'SK subkeyword_of K WHERE C name "classif1", K eid %s'
% self.kw1.eid).get_entity(0,0)
- self.kw1.set_relations(subkeyword_of=kw3)
+ self.kw1.cw_set(subkeyword_of=kw3)
self.assertRaises(ValidationError, self.commit)
The test class defines a :meth:`setup_database` method which populates the
@@ -192,10 +192,10 @@
description=u'cubicweb is beautiful')
blog_entry_1 = req.create_entity('BlogEntry', title=u'hop',
content=u'cubicweb hop')
- blog_entry_1.set_relations(entry_of=cubicweb_blog)
+ blog_entry_1.cw_set(entry_of=cubicweb_blog)
blog_entry_2 = req.create_entity('BlogEntry', title=u'yes',
content=u'cubicweb yes')
- blog_entry_2.set_relations(entry_of=cubicweb_blog)
+ blog_entry_2.cw_set(entry_of=cubicweb_blog)
self.assertEqual(len(MAILBOX), 0)
self.commit()
self.assertEqual(len(MAILBOX), 2)
--- a/doc/book/en/devweb/index.rst Fri Sep 07 13:48:55 2012 +0200
+++ b/doc/book/en/devweb/index.rst Fri Sep 07 14:01:59 2012 +0200
@@ -10,6 +10,7 @@
publisher
controllers
request
+ searchbar
views/index
rtags
ajax
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/book/en/devweb/searchbar.rst Fri Sep 07 14:01:59 2012 +0200
@@ -0,0 +1,41 @@
+.. _searchbar:
+
+RQL search bar
+--------------
+
+The RQL search bar is a visual component, hidden by default, the tiny *search*
+input being enough for common use cases.
+
+An autocompletion helper is provided to help you type valid queries, both
+in terms of syntax and in terms of schema validity.
+
+.. autoclass:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder
+
+
+How search is performed
++++++++++++++++++++++++
+
+You can use the *rql search bar* to either type RQL queries, plain text queries
+or standard shortcuts such as *<EntityType>* or *<EntityType> <attrname> <value>*.
+
+Ultimately, all queries are translated to rql since it's the only
+language understood on the server (data) side. To transform the user
+query into RQL, CubicWeb uses the so-called *magicsearch component*,
+defined in :mod:`cubicweb.web.views.magicsearch`, which in turn
+delegates to a number of query preprocessor that are responsible of
+interpreting the user query and generating corresponding RQL.
+
+The code of the main processor loop is easy to understand:
+
+.. sourcecode:: python
+
+ for proc in self.processors:
+ try:
+ return proc.process_query(uquery, req)
+ except (RQLSyntaxError, BadRQLQuery):
+ pass
+
+The idea is simple: for each query processor, try to translate the
+query. If it fails, try with the next processor, if it succeeds,
+we're done and the RQL query will be executed.
+
--- a/doc/book/en/tutorials/advanced/part02_security.rst Fri Sep 07 13:48:55 2012 +0200
+++ b/doc/book/en/tutorials/advanced/part02_security.rst Fri Sep 07 14:01:59 2012 +0200
@@ -196,7 +196,7 @@
for eid in self.get_data():
entity = self.session.entity_from_eid(eid)
if entity.visibility == 'parent':
- entity.set_attributes(visibility=u'authenticated')
+ entity.cw_set(visibility=u'authenticated')
class SetVisibilityHook(hook.Hook):
__regid__ = 'sytweb.setvisibility'
@@ -215,7 +215,7 @@
parent = self._cw.entity_from_eid(self.eidto)
child = self._cw.entity_from_eid(self.eidfrom)
if child.visibility == 'parent':
- child.set_attributes(visibility=parent.visibility)
+ child.cw_set(visibility=parent.visibility)
Notice:
@@ -344,7 +344,7 @@
self.assertEquals(len(req.execute('Folder X')), 0) # restricted...
# may_be_read_by propagation
self.restore_connection()
- folder.set_relations(may_be_read_by=toto)
+ folder.cw_set(may_be_read_by=toto)
self.commit()
photo1.clear_all_caches()
self.failUnless(photo1.may_be_read_by)
--- a/doc/book/en/tutorials/advanced/part04_ui-base.rst Fri Sep 07 13:48:55 2012 +0200
+++ b/doc/book/en/tutorials/advanced/part04_ui-base.rst Fri Sep 07 14:01:59 2012 +0200
@@ -294,6 +294,7 @@
folder in which the current file (e.g. `self.entity`) is located.
.. Note::
+
The :class:`IBreadCrumbs` interface is a `breadcrumbs` method, but the default
:class:`IBreadCrumbsAdapter` provides a default implementation for it that will look
at the value returned by its `parent_entity` method. It also provides a
@@ -331,6 +332,7 @@
navigate through the web site to see if everything is ok...
.. Note::
+
In the 'cubicweb-ctl i18ncube' command, `sytweb` refers to the **cube**, while
in the two other, it refers to the **instance** (if you can't see the
difference, reread CubicWeb's concept chapter !).
@@ -363,4 +365,4 @@
.. _`several improvments`: http://www.cubicweb.org/blogentry/1179899
.. _`3.8`: http://www.cubicweb.org/blogentry/917107
.. _`first blog of this series`: http://www.cubicweb.org/blogentry/824642
-.. _`an earlier post`: http://www.cubicweb.org/867464
\ No newline at end of file
+.. _`an earlier post`: http://www.cubicweb.org/867464
--- a/entities/authobjs.py Fri Sep 07 13:48:55 2012 +0200
+++ b/entities/authobjs.py Fri Sep 07 14:01:59 2012 +0200
@@ -101,7 +101,7 @@
kwargs['for_user'] = self
self._cw.create_entity('CWProperty', **kwargs)
else:
- prop.set_attributes(value=value)
+ prop.cw_set(value=value)
def matching_groups(self, groups):
"""return the number of the given group(s) in which the user is
--- a/entities/sources.py Fri Sep 07 13:48:55 2012 +0200
+++ b/entities/sources.py Fri Sep 07 14:01:59 2012 +0200
@@ -51,7 +51,7 @@
continue
raise
cfgstr = unicode(generate_source_config(sconfig), self._cw.encoding)
- self.set_attributes(config=cfgstr)
+ self.cw_set(config=cfgstr)
class CWSource(_CWSourceCfgMixIn, AnyEntity):
@@ -181,5 +181,5 @@
def write_log(self, session, **kwargs):
if 'status' not in kwargs:
kwargs['status'] = getattr(self, '_status', u'success')
- self.set_attributes(log=u'<br/>'.join(self._logs), **kwargs)
+ self.cw_set(log=u'<br/>'.join(self._logs), **kwargs)
self._logs = []
--- a/entities/test/unittest_base.py Fri Sep 07 13:48:55 2012 +0200
+++ b/entities/test/unittest_base.py Fri Sep 07 14:01:59 2012 +0200
@@ -70,7 +70,7 @@
email1 = self.execute('INSERT EmailAddress X: X address "maarten.ter.huurne@philips.com"').get_entity(0, 0)
email2 = self.execute('INSERT EmailAddress X: X address "maarten@philips.com"').get_entity(0, 0)
email3 = self.execute('INSERT EmailAddress X: X address "toto@logilab.fr"').get_entity(0, 0)
- email1.set_relations(prefered_form=email2)
+ email1.cw_set(prefered_form=email2)
self.assertEqual(email1.prefered.eid, email2.eid)
self.assertEqual(email2.prefered.eid, email2.eid)
self.assertEqual(email3.prefered.eid, email3.eid)
@@ -104,10 +104,10 @@
e = self.execute('CWUser U WHERE U login "member"').get_entity(0, 0)
self.assertEqual(e.dc_title(), 'member')
self.assertEqual(e.name(), 'member')
- e.set_attributes(firstname=u'bouah')
+ e.cw_set(firstname=u'bouah')
self.assertEqual(e.dc_title(), 'member')
self.assertEqual(e.name(), u'bouah')
- e.set_attributes(surname=u'lôt')
+ e.cw_set(surname=u'lôt')
self.assertEqual(e.dc_title(), 'member')
self.assertEqual(e.name(), u'bouah lôt')
--- a/entities/test/unittest_wfobjs.py Fri Sep 07 13:48:55 2012 +0200
+++ b/entities/test/unittest_wfobjs.py Fri Sep 07 14:01:59 2012 +0200
@@ -63,7 +63,7 @@
# gnark gnark
bar = wf.add_state(u'bar')
self.commit()
- bar.set_attributes(name=u'foo')
+ bar.cw_set(name=u'foo')
with self.assertRaises(ValidationError) as cm:
self.commit()
self.assertEqual(cm.exception.errors, {'name-subject': 'workflow already have a state of that name'})
@@ -86,7 +86,7 @@
# gnark gnark
biz = wf.add_transition(u'biz', (bar,), foo)
self.commit()
- biz.set_attributes(name=u'baz')
+ biz.cw_set(name=u'baz')
with self.assertRaises(ValidationError) as cm:
self.commit()
self.assertEqual(cm.exception.errors, {'name-subject': 'workflow already have a transition of that name'})
@@ -516,7 +516,7 @@
['rest'])
self.assertEqual(parse_hist(iworkflowable.workflow_history),
[('asleep', 'asleep', 'rest', None)])
- user.set_attributes(surname=u'toto') # fulfill condition
+ user.cw_set(surname=u'toto') # fulfill condition
self.commit()
iworkflowable.fire_transition('rest')
self.commit()
--- a/entity.py Fri Sep 07 13:48:55 2012 +0200
+++ b/entity.py Fri Sep 07 14:01:59 2012 +0200
@@ -452,26 +452,13 @@
return mainattr, needcheck
@classmethod
- def cw_instantiate(cls, execute, **kwargs):
- """add a new entity of this given type
-
- Example (in a shell session):
-
- >>> companycls = vreg['etypes'].etype_class(('Company')
- >>> personcls = vreg['etypes'].etype_class(('Person')
- >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
- >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
- ... works_for=c)
-
- You can also set relation where the entity has 'object' role by
- prefixing the relation by 'reverse_'.
- """
- rql = 'INSERT %s X' % cls.__regid__
+ def _cw_build_entity_query(cls, kwargs):
relations = []
restrictions = set()
- pending_relations = []
+ pendingrels = []
eschema = cls.e_schema
qargs = {}
+ attrcache = {}
for attr, value in kwargs.items():
if attr.startswith('reverse_'):
attr = attr[len('reverse_'):]
@@ -487,10 +474,13 @@
value = iter(value).next()
else:
# prepare IN clause
- pending_relations.append( (attr, role, value) )
+ pendingrels.append( (attr, role, value) )
continue
if rschema.final: # attribute
relations.append('X %s %%(%s)s' % (attr, attr))
+ attrcache[attr] = value
+ elif value is None:
+ pendingrels.append( (attr, role, value) )
else:
rvar = attr.upper()
if role == 'object':
@@ -503,19 +493,52 @@
if hasattr(value, 'eid'):
value = value.eid
qargs[attr] = value
+ rql = u''
if relations:
- rql = '%s: %s' % (rql, ', '.join(relations))
+ rql += ', '.join(relations)
if restrictions:
- rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
- created = execute(rql, qargs).get_entity(0, 0)
- for attr, role, values in pending_relations:
+ rql += ' WHERE %s' % ', '.join(restrictions)
+ return rql, qargs, pendingrels, attrcache
+
+ @classmethod
+ def _cw_handle_pending_relations(cls, eid, pendingrels, execute):
+ for attr, role, values in pendingrels:
if role == 'object':
restr = 'Y %s X' % attr
else:
restr = 'X %s Y' % attr
+ if values is None:
+ execute('DELETE %s WHERE X eid %%(x)s' % restr, {'x': eid})
+ continue
execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
restr, ','.join(str(getattr(r, 'eid', r)) for r in values)),
- {'x': created.eid}, build_descr=False)
+ {'x': eid}, build_descr=False)
+
+ @classmethod
+ def cw_instantiate(cls, execute, **kwargs):
+ """add a new entity of this given type
+
+ Example (in a shell session):
+
+ >>> companycls = vreg['etypes'].etype_class(('Company')
+ >>> personcls = vreg['etypes'].etype_class(('Person')
+ >>> c = companycls.cw_instantiate(session.execute, name=u'Logilab')
+ >>> p = personcls.cw_instantiate(session.execute, firstname=u'John', lastname=u'Doe',
+ ... works_for=c)
+
+ You can also set relations where the entity has 'object' role by
+ prefixing the relation name by 'reverse_'. Also, relation values may be
+ an entity or eid, a list of entities or eids.
+ """
+ rql, qargs, pendingrels, attrcache = cls._cw_build_entity_query(kwargs)
+ if rql:
+ rql = 'INSERT %s X: %s' % (cls.__regid__, rql)
+ else:
+ rql = 'INSERT %s X' % (cls.__regid__)
+ created = execute(rql, qargs).get_entity(0, 0)
+ created._cw_update_attr_cache(attrcache)
+ created.cw_attr_cache.update(attrcache)
+ cls._cw_handle_pending_relations(created.eid, pendingrels, execute)
return created
def __init__(self, req, rset=None, row=None, col=0):
@@ -535,6 +558,24 @@
def __cmp__(self, other):
raise NotImplementedError('comparison not implemented for %s' % self.__class__)
+ def _cw_update_attr_cache(self, attrcache):
+ # if context is a repository session, don't consider dont-cache-attrs as
+ # the instance already hold modified values and loosing them could
+ # introduce severe problems
+ if self._cw.is_request:
+ for attr in self._cw.get_shared_data('%s.dont-cache-attrs' % self.eid,
+ default=(), txdata=True, pop=True):
+ attrcache.pop(attr, None)
+ self.cw_attr_cache.pop(attr, None)
+ self.cw_attr_cache.update(attrcache)
+
+ def _cw_dont_cache_attribute(self, attr):
+ """repository side method called when some attribute have been
+ transformed by a hook, hence original value should not be cached by
+ client
+ """
+ self._cw.transaction_data.setdefault('%s.dont-cache-attrs' % self.eid, set()).add(attr)
+
def __json_encode__(self):
"""custom json dumps hook to dump the entity's eid
which is not part of dict structure itself
@@ -1215,54 +1256,41 @@
# raw edition utilities ###################################################
- def set_attributes(self, **kwargs): # XXX cw_set_attributes
+ def cw_set(self, **kwargs):
+ """update this entity using given attributes / relation, working in the
+ same fashion as :meth:`cw_instantiate`.
+
+ Example (in a shell session):
+
+ >>> c = rql('Any X WHERE X is Company').get_entity(0, 0)
+ >>> p = rql('Any X WHERE X is Person').get_entity(0, 0)
+ >>> c.set(name=u'Logilab')
+ >>> p.set(firstname=u'John', lastname=u'Doe', works_for=c)
+
+ You can also set relations where the entity has 'object' role by
+ prefixing the relation name by 'reverse_'. Also, relation values may be
+ an entity or eid, a list of entities or eids, or None (meaning that all
+ relations of the given type from or to this object should be deleted).
+ """
_check_cw_unsafe(kwargs)
assert kwargs
assert self.cw_is_saved(), "should not call set_attributes while entity "\
"hasn't been saved yet"
- relations = ['X %s %%(%s)s' % (key, key) for key in kwargs]
- # and now update the database
- kwargs['x'] = self.eid
- self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
- kwargs)
- kwargs.pop('x')
+ rql, qargs, pendingrels, attrcache = self._cw_build_entity_query(kwargs)
+ if rql:
+ rql = 'SET ' + rql
+ qargs['x'] = self.eid
+ if ' WHERE ' in rql:
+ rql += ', X eid %(x)s'
+ else:
+ rql += ' WHERE X eid %(x)s'
+ self._cw.execute(rql, qargs)
# update current local object _after_ the rql query to avoid
# interferences between the query execution itself and the cw_edited /
# skip_security machinery
- self.cw_attr_cache.update(kwargs)
-
- def set_relations(self, **kwargs): # XXX cw_set_relations
- """add relations to the given object. To set a relation where this entity
- is the object of the relation, use 'reverse_'<relation> as argument name.
-
- Values may be an entity or eid, a list of entities or eids, or None
- (meaning that all relations of the given type from or to this object
- should be deleted).
- """
- # XXX update cache
- _check_cw_unsafe(kwargs)
- for attr, values in kwargs.iteritems():
- if attr.startswith('reverse_'):
- restr = 'Y %s X' % attr[len('reverse_'):]
- else:
- restr = 'X %s Y' % attr
- if values is None:
- self._cw.execute('DELETE %s WHERE X eid %%(x)s' % restr,
- {'x': self.eid})
- continue
- if not isinstance(values, (tuple, list, set, frozenset)):
- values = (values,)
- eids = []
- for val in values:
- try:
- eids.append(str(val.eid))
- except AttributeError:
- try:
- eids.append(str(typed_eid(val)))
- except (ValueError, TypeError):
- raise Exception('expected an Entity or eid, got %s' % val)
- self._cw.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
- restr, ','.join(eids)), {'x': self.eid})
+ self._cw_update_attr_cache(attrcache)
+ self._cw_handle_pending_relations(self.eid, pendingrels, self._cw.execute)
+ # XXX update relation cache
def cw_delete(self, **kwargs):
assert self.has_eid(), self.eid
@@ -1277,6 +1305,21 @@
# deprecated stuff #########################################################
+ @deprecated('[3.16] use cw_set() instead')
+ def set_attributes(self, **kwargs): # XXX cw_set_attributes
+ self.cw_set(**kwargs)
+
+ @deprecated('[3.16] use cw_set() instead')
+ def set_relations(self, **kwargs): # XXX cw_set_relations
+ """add relations to the given object. To set a relation where this entity
+ is the object of the relation, use 'reverse_'<relation> as argument name.
+
+ Values may be an entity or eid, a list of entities or eids, or None
+ (meaning that all relations of the given type from or to this object
+ should be deleted).
+ """
+ self.cw_set(**kwargs)
+
@deprecated('[3.13] use entity.cw_clear_all_caches()')
def clear_all_caches(self):
return self.cw_clear_all_caches()
--- a/hooks/test/unittest_hooks.py Fri Sep 07 13:48:55 2012 +0200
+++ b/hooks/test/unittest_hooks.py Fri Sep 07 14:01:59 2012 +0200
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -67,10 +67,9 @@
entity = self.request().create_entity('Workflow', name=u'wf1',
description_format=u'text/html',
description=u'yo')
- entity.set_attributes(name=u'wf2')
+ entity.cw_set(name=u'wf2')
self.assertEqual(entity.description, u'yo')
- entity.set_attributes(description=u'R&D<p>yo')
- entity.cw_attr_cache.pop('description')
+ entity.cw_set(description=u'R&D<p>yo')
self.assertEqual(entity.description, u'R&D<p>yo</p>')
def test_metadata_cwuri(self):
--- a/hooks/test/unittest_syncschema.py Fri Sep 07 13:48:55 2012 +0200
+++ b/hooks/test/unittest_syncschema.py Fri Sep 07 14:01:59 2012 +0200
@@ -294,7 +294,7 @@
def test_change_fulltext_container(self):
req = self.request()
target = req.create_entity(u'EmailAddress', address=u'rick.roll@dance.com')
- target.set_relations(reverse_use_email=req.user)
+ target.cw_set(reverse_use_email=req.user)
self.commit()
rset = req.execute('Any X WHERE X has_text "rick.roll"')
self.assertIn(req.user.eid, [item[0] for item in rset])
--- a/hooks/workflow.py Fri Sep 07 13:48:55 2012 +0200
+++ b/hooks/workflow.py Fri Sep 07 14:01:59 2012 +0200
@@ -335,7 +335,7 @@
return
entity = self._cw.entity_from_eid(self.eidfrom)
try:
- entity.set_attributes(modification_date=datetime.now())
+ entity.cw_set(modification_date=datetime.now())
except RepositoryError, ex:
# usually occurs if entity is coming from a read-only source
# (eg ldap user)
--- a/i18n/de.po Fri Sep 07 13:48:55 2012 +0200
+++ b/i18n/de.po Fri Sep 07 14:01:59 2012 +0200
@@ -1001,9 +1001,6 @@
msgid "abstract base class for transitions"
msgstr "abstrakte Basisklasse für Übergänge"
-msgid "action menu"
-msgstr ""
-
msgid "action(s) on this selection"
msgstr "Aktionen(en) bei dieser Auswahl"
@@ -4169,6 +4166,9 @@
msgid "toggle check boxes"
msgstr "Kontrollkästchen umkehren"
+msgid "toggle filter"
+msgstr "filter verbergen/zeigen"
+
msgid "tr_count"
msgstr ""
@@ -4631,9 +4631,3 @@
#~ msgstr ""
#~ "Kann die Relation %(rtype)s der Entität %(eid)s nicht wieder herstellen, "
#~ "diese Relation existiert nicht mehr in dem Schema."
-
-#~ msgid "log out first"
-#~ msgstr "Melden Sie sich zuerst ab."
-
-#~ msgid "week"
-#~ msgstr "Woche"
--- a/i18n/en.po Fri Sep 07 13:48:55 2012 +0200
+++ b/i18n/en.po Fri Sep 07 14:01:59 2012 +0200
@@ -963,9 +963,6 @@
msgid "abstract base class for transitions"
msgstr ""
-msgid "action menu"
-msgstr ""
-
msgid "action(s) on this selection"
msgstr ""
@@ -4069,6 +4066,9 @@
msgid "toggle check boxes"
msgstr ""
+msgid "toggle filter"
+msgstr ""
+
msgid "tr_count"
msgstr "transition number"
--- a/i18n/es.po Fri Sep 07 13:48:55 2012 +0200
+++ b/i18n/es.po Fri Sep 07 14:01:59 2012 +0200
@@ -1011,9 +1011,6 @@
msgid "abstract base class for transitions"
msgstr "Clase de base abstracta para la transiciones"
-msgid "action menu"
-msgstr ""
-
msgid "action(s) on this selection"
msgstr "Acción(es) en esta selección"
@@ -4219,6 +4216,9 @@
msgid "toggle check boxes"
msgstr "Cambiar valor"
+msgid "toggle filter"
+msgstr "esconder/mostrar el filtro"
+
msgid "tr_count"
msgstr "n° de transición"
@@ -4682,18 +4682,3 @@
#~ msgstr ""
#~ "No puede restaurar la relación %(rtype)s de la entidad %(eid)s, esta "
#~ "relación ya no existe en el esquema."
-
-#~ msgid "day"
-#~ msgstr "día"
-
-#~ msgid "log out first"
-#~ msgstr "Desconéctese primero"
-
-#~ msgid "month"
-#~ msgstr "mes"
-
-#~ msgid "today"
-#~ msgstr "hoy"
-
-#~ msgid "week"
-#~ msgstr "sem."
--- a/i18n/fr.po Fri Sep 07 13:48:55 2012 +0200
+++ b/i18n/fr.po Fri Sep 07 14:01:59 2012 +0200
@@ -1011,9 +1011,6 @@
msgid "abstract base class for transitions"
msgstr "classe de base abstraite pour les transitions"
-msgid "action menu"
-msgstr "actions"
-
msgid "action(s) on this selection"
msgstr "action(s) sur cette sélection"
@@ -1253,7 +1250,7 @@
msgstr "anonyme"
msgid "anyrsetview"
-msgstr "vues \"tous les rset\""
+msgstr "vues pour tout rset"
msgid "april"
msgstr "avril"
@@ -4140,6 +4137,10 @@
msgid "there is no transaction #%s"
msgstr "Il n'y a pas de transaction #%s"
+#, python-format
+msgid "there is no transaction #%s"
+msgstr ""
+
msgid "this action is not reversible!"
msgstr ""
"Attention ! Cette opération va détruire les données de façon irréversible."
@@ -4228,7 +4229,10 @@
msgstr "à faire par"
msgid "toggle check boxes"
-msgstr "inverser les cases à cocher"
+msgstr "afficher/masquer les cases à cocher"
+
+msgid "toggle filter"
+msgstr "afficher/masquer le filtre"
msgid "tr_count"
msgstr "n° de transition"
--- a/misc/migration/3.10.0_Any.py Fri Sep 07 13:48:55 2012 +0200
+++ b/misc/migration/3.10.0_Any.py Fri Sep 07 14:01:59 2012 +0200
@@ -34,5 +34,5 @@
for x in rql('Any X,XK WHERE X pkey XK, '
'X pkey ~= "boxes.%" OR '
'X pkey ~= "contentnavigation.%"').entities():
- x.set_attributes(pkey=u'ctxcomponents.' + x.pkey.split('.', 1)[1])
+ x.cw_set(pkey=u'ctxcomponents.' + x.pkey.split('.', 1)[1])
--- a/misc/migration/3.11.0_Any.py Fri Sep 07 13:48:55 2012 +0200
+++ b/misc/migration/3.11.0_Any.py Fri Sep 07 14:01:59 2012 +0200
@@ -81,5 +81,5 @@
rset = session.execute('Any V WHERE X is CWProperty, X value V, X pkey %(k)s',
{'k': pkey})
timestamp = int(rset[0][0])
- sourceentity.set_attributes(latest_retrieval=datetime.fromtimestamp(timestamp))
+ sourceentity.cw_set(latest_retrieval=datetime.fromtimestamp(timestamp))
session.execute('DELETE CWProperty X WHERE X pkey %(k)s', {'k': pkey})
--- a/misc/migration/3.14.0_Any.py Fri Sep 07 13:48:55 2012 +0200
+++ b/misc/migration/3.14.0_Any.py Fri Sep 07 14:01:59 2012 +0200
@@ -9,5 +9,5 @@
expression = rqlcstr.value
mainvars = guess_rrqlexpr_mainvars(expression)
yamscstr = CONSTRAINTS[rqlcstr.type](expression, mainvars)
- rqlcstr.set_attributes(value=yamscstr.serialize())
+ rqlcstr.cw_set(value=yamscstr.serialize())
print 'updated', rqlcstr.type, rqlcstr.value.strip()
--- a/misc/migration/3.15.0_Any.py Fri Sep 07 13:48:55 2012 +0200
+++ b/misc/migration/3.15.0_Any.py Fri Sep 07 14:01:59 2012 +0200
@@ -4,7 +4,7 @@
config = source.dictconfig
host = config.pop('host', u'ldap')
protocol = config.pop('protocol', u'ldap')
- source.set_attributes(url=u'%s://%s' % (protocol, host))
+ source.cw_set(url=u'%s://%s' % (protocol, host))
source.update_config(skip_unknown=True, **config)
commit()
--- a/misc/scripts/chpasswd.py Fri Sep 07 13:48:55 2012 +0200
+++ b/misc/scripts/chpasswd.py Fri Sep 07 14:01:59 2012 +0200
@@ -42,7 +42,7 @@
crypted = crypt_password(pass1)
cwuser = rset.get_entity(0,0)
-cwuser.set_attributes(upassword=Binary(crypted))
+cwuser.cw_set(upassword=Binary(crypted))
commit()
print("password updated.")
--- a/misc/scripts/ldapuser2ldapfeed.py Fri Sep 07 13:48:55 2012 +0200
+++ b/misc/scripts/ldapuser2ldapfeed.py Fri Sep 07 14:01:59 2012 +0200
@@ -87,7 +87,7 @@
source_ent = rql('CWSource S WHERE S eid %(s)s', {'s': source.eid}).get_entity(0, 0)
-source_ent.set_attributes(type=u"ldapfeed", parser=u"ldapfeed")
+source_ent.cw_set(type=u"ldapfeed", parser=u"ldapfeed")
if raw_input('Commit ?') in 'yY':
--- a/predicates.py Fri Sep 07 13:48:55 2012 +0200
+++ b/predicates.py Fri Sep 07 14:01:59 2012 +0200
@@ -737,12 +737,16 @@
See :class:`~cubicweb.predicates.EClassPredicate` documentation for entity
class lookup / score rules according to the input context.
- .. note:: when interface is an entity class, the score will reflect class
- proximity so the most specific object will be selected.
+ .. note::
+
+ when interface is an entity class, the score will reflect class
+ proximity so the most specific object will be selected.
- .. note:: deprecated in cubicweb >= 3.9, use either
- :class:`~cubicweb.predicates.is_instance` or
- :class:`~cubicweb.predicates.adaptable`.
+ .. note::
+
+ deprecated in cubicweb >= 3.9, use either
+ :class:`~cubicweb.predicates.is_instance` or
+ :class:`~cubicweb.predicates.adaptable`.
"""
def __init__(self, *expected_ifaces, **kwargs):
--- a/req.py Fri Sep 07 13:48:55 2012 +0200
+++ b/req.py Fri Sep 07 14:01:59 2012 +0200
@@ -62,6 +62,8 @@
:attribute vreg.schema: the instance's schema
:attribute vreg.config: the instance's configuration
"""
+ is_request = True # False for repository session
+
def __init__(self, vreg):
self.vreg = vreg
try:
--- a/server/edition.py Fri Sep 07 13:48:55 2012 +0200
+++ b/server/edition.py Fri Sep 07 14:01:59 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -61,6 +61,8 @@
# attributes, else we may accidentaly skip a desired security check
if attr not in self:
self.skip_security.add(attr)
+ # mark attribute as needing purge by the client
+ self.entity._cw_dont_cache_attribute(attr)
self.edited_attribute(attr, value)
def __delitem__(self, attr):
--- a/server/hook.py Fri Sep 07 13:48:55 2012 +0200
+++ b/server/hook.py Fri Sep 07 14:01:59 2012 +0200
@@ -152,7 +152,7 @@
On those events, the entity has no `cw_edited` dictionary.
-.. note:: `self.entity.set_attributes(age=42)` will set the `age` attribute to
+.. note:: `self.entity.cw_set(age=42)` will set the `age` attribute to
42. But to do so, it will generate a rql query that will have to be processed,
hence may trigger some hooks, etc. This could lead to infinitely looping hooks.
--- a/server/migractions.py Fri Sep 07 13:48:55 2012 +0200
+++ b/server/migractions.py Fri Sep 07 14:01:59 2012 +0200
@@ -1321,7 +1321,7 @@
except Exception:
self.cmd_create_entity('CWProperty', pkey=unicode(pkey), value=value)
else:
- prop.set_attributes(value=value)
+ prop.cw_set(value=value)
# other data migration commands ###########################################
--- a/server/session.py Fri Sep 07 13:48:55 2012 +0200
+++ b/server/session.py Fri Sep 07 14:01:59 2012 +0200
@@ -242,6 +242,7 @@
:attr:`running_dbapi_query`, boolean flag telling if the executing query
is coming from a dbapi connection or is a query from within the repository
"""
+ is_request = False
is_internal_session = False
def __init__(self, user, repo, cnxprops=None, _id=None):
--- a/server/sources/datafeed.py Fri Sep 07 13:48:55 2012 +0200
+++ b/server/sources/datafeed.py Fri Sep 07 14:01:59 2012 +0200
@@ -396,7 +396,7 @@
attrs = dict( (k, v) for k, v in attrs.iteritems()
if v != getattr(entity, k))
if attrs:
- entity.set_attributes(**attrs)
+ entity.cw_set(**attrs)
self.notify_updated(entity)
class DataFeedXMLParser(DataFeedParser):
--- a/server/test/unittest_repository.py Fri Sep 07 13:48:55 2012 +0200
+++ b/server/test/unittest_repository.py Fri Sep 07 14:01:59 2012 +0200
@@ -523,7 +523,7 @@
self.commit()
self.assertEqual(len(c.reverse_fiche), 1)
- def test_set_attributes_in_before_update(self):
+ def test_cw_set_in_before_update(self):
# local hook
class DummyBeforeHook(Hook):
__regid__ = 'dummy-before-hook'
@@ -535,31 +535,31 @@
pendings = self._cw.transaction_data.setdefault('pending', set())
if self.entity.eid not in pendings:
pendings.add(self.entity.eid)
- self.entity.set_attributes(alias=u'foo')
+ self.entity.cw_set(alias=u'foo')
with self.temporary_appobjects(DummyBeforeHook):
req = self.request()
addr = req.create_entity('EmailAddress', address=u'a@b.fr')
- addr.set_attributes(address=u'a@b.com')
+ addr.cw_set(address=u'a@b.com')
rset = self.execute('Any A,AA WHERE X eid %(x)s, X address A, X alias AA',
{'x': addr.eid})
self.assertEqual(rset.rows, [[u'a@b.com', u'foo']])
- def test_set_attributes_in_before_add(self):
+ def test_cw_set_in_before_add(self):
# local hook
class DummyBeforeHook(Hook):
__regid__ = 'dummy-before-hook'
__select__ = Hook.__select__ & is_instance('EmailAddress')
events = ('before_add_entity',)
def __call__(self):
- # set_attributes is forbidden within before_add_entity()
- self.entity.set_attributes(alias=u'foo')
+ # cw_set is forbidden within before_add_entity()
+ self.entity.cw_set(alias=u'foo')
with self.temporary_appobjects(DummyBeforeHook):
req = self.request()
# XXX will fail with python -O
self.assertRaises(AssertionError, req.create_entity,
'EmailAddress', address=u'a@b.fr')
- def test_multiple_edit_set_attributes(self):
+ def test_multiple_edit_cw_set(self):
"""make sure cw_edited doesn't get cluttered
by previous entities on multiple set
"""
@@ -665,7 +665,7 @@
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEqual(rset.rows, [])
- req.user.set_relations(use_email=toto)
+ req.user.cw_set(use_email=toto)
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEqual(rset.rows, [[req.user.eid]])
@@ -675,11 +675,11 @@
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'toto'})
self.assertEqual(rset.rows, [])
tutu = req.create_entity('EmailAddress', address=u'tutu@logilab.fr')
- req.user.set_relations(use_email=tutu)
+ req.user.cw_set(use_email=tutu)
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'tutu'})
self.assertEqual(rset.rows, [[req.user.eid]])
- tutu.set_attributes(address=u'hip@logilab.fr')
+ tutu.cw_set(address=u'hip@logilab.fr')
self.commit()
rset = req.execute('Any X WHERE X has_text %(t)s', {'t': 'tutu'})
self.assertEqual(rset.rows, [])
@@ -791,7 +791,7 @@
personnes.append(p)
abraham = req.create_entity('Personne', nom=u'Abraham', prenom=u'John', sexe=u'M')
for j in xrange(0, 2000, 100):
- abraham.set_relations(personne_composite=personnes[j:j+100])
+ abraham.cw_set(personne_composite=personnes[j:j+100])
t1 = time.time()
self.info('creation: %.2gs', (t1 - t0))
req.cnx.commit()
@@ -817,7 +817,7 @@
t1 = time.time()
self.info('creation: %.2gs', (t1 - t0))
for j in xrange(100, 2000, 100):
- abraham.set_relations(personne_composite=personnes[j:j+100])
+ abraham.cw_set(personne_composite=personnes[j:j+100])
t2 = time.time()
self.info('more relations: %.2gs', (t2-t1))
req.cnx.commit()
@@ -837,7 +837,7 @@
t1 = time.time()
self.info('creation: %.2gs', (t1 - t0))
for j in xrange(100, 2000, 100):
- abraham.set_relations(personne_inlined=personnes[j:j+100])
+ abraham.cw_set(personne_inlined=personnes[j:j+100])
t2 = time.time()
self.info('more relations: %.2gs', (t2-t1))
req.cnx.commit()
@@ -918,7 +918,7 @@
p1 = req.create_entity('Personne', nom=u'Vincent')
p2 = req.create_entity('Personne', nom=u'Florent')
w = req.create_entity('Affaire', ref=u'wc')
- w.set_relations(todo_by=[p1,p2])
+ w.cw_set(todo_by=[p1,p2])
w.cw_clear_all_caches()
self.commit()
self.assertEqual(len(w.todo_by), 1)
@@ -929,9 +929,9 @@
p1 = req.create_entity('Personne', nom=u'Vincent')
p2 = req.create_entity('Personne', nom=u'Florent')
w = req.create_entity('Affaire', ref=u'wc')
- w.set_relations(todo_by=p1)
+ w.cw_set(todo_by=p1)
self.commit()
- w.set_relations(todo_by=p2)
+ w.cw_set(todo_by=p2)
w.cw_clear_all_caches()
self.commit()
self.assertEqual(len(w.todo_by), 1)
--- a/server/test/unittest_storage.py Fri Sep 07 13:48:55 2012 +0200
+++ b/server/test/unittest_storage.py Fri Sep 07 14:01:59 2012 +0200
@@ -99,7 +99,7 @@
f1 = self.create_file()
self.commit()
self.assertEqual(file(expected_filepath).read(), 'the-data')
- f1.set_attributes(data=Binary('the new data'))
+ f1.cw_set(data=Binary('the new data'))
self.rollback()
self.assertEqual(file(expected_filepath).read(), 'the-data')
f1.cw_delete()
@@ -204,7 +204,7 @@
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
data_format=u'text/plain', data_name=u'foo')
- # NOTE: do not use set_attributes() which would automatically
+ # NOTE: do not use cw_set() which would automatically
# update f1's local dict. We want the pure rql version to work
self.execute('SET F data %(d)s WHERE F eid %(f)s',
{'d': Binary('some other data'), 'f': f1.eid})
@@ -218,7 +218,7 @@
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
data_format=u'text/plain', data_name=u'foo.txt')
- # NOTE: do not use set_attributes() which would automatically
+ # NOTE: do not use cw_set() which would automatically
# update f1's local dict. We want the pure rql version to work
self.commit()
old_path = self.fspath(f1)
@@ -240,7 +240,7 @@
# use self.session to use server-side cache
f1 = self.session.create_entity('File', data=Binary('some data'),
data_format=u'text/plain', data_name=u'foo.txt')
- # NOTE: do not use set_attributes() which would automatically
+ # NOTE: do not use cw_set() which would automatically
# update f1's local dict. We want the pure rql version to work
self.commit()
old_path = self.fspath(f1)
@@ -265,7 +265,7 @@
f = self.session.create_entity('Affaire', opt_attr=Binary('toto'))
self.session.commit()
self.session.set_cnxset()
- f.set_attributes(opt_attr=None)
+ f.cw_set(opt_attr=None)
self.session.commit()
@tag('fs_importing', 'update')
--- a/server/test/unittest_undo.py Fri Sep 07 13:48:55 2012 +0200
+++ b/server/test/unittest_undo.py Fri Sep 07 14:01:59 2012 +0200
@@ -203,7 +203,7 @@
c.cw_delete()
txuuid = self.commit()
c2 = session.create_entity('Card', title=u'hip', content=u'hip')
- p.set_relations(fiche=c2)
+ p.cw_set(fiche=c2)
self.commit()
self.assertUndoTransaction(txuuid, [
"Can't restore object relation fiche to entity "
@@ -217,7 +217,7 @@
session = self.session
g = session.create_entity('CWGroup', name=u'staff')
session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
- self.toto.set_relations(in_group=g)
+ self.toto.cw_set(in_group=g)
self.commit()
self.toto.cw_delete()
txuuid = self.commit()
@@ -265,7 +265,7 @@
email = self.request().create_entity('EmailAddress', address=u'tutu@cubicweb.org')
prop = self.request().create_entity('CWProperty', pkey=u'ui.default-text-format',
value=u'text/html')
- tutu.set_relations(use_email=email, reverse_for_user=prop)
+ tutu.cw_set(use_email=email, reverse_for_user=prop)
self.commit()
with self.assertRaises(ValidationError) as cm:
self.cnx.undo_transaction(txuuid)
@@ -278,7 +278,7 @@
g = session.create_entity('CWGroup', name=u'staff')
txuuid = self.commit()
session.execute('DELETE U in_group G WHERE U eid %(x)s', {'x': self.toto.eid})
- self.toto.set_relations(in_group=g)
+ self.toto.cw_set(in_group=g)
self.commit()
with self.assertRaises(ValidationError) as cm:
self.cnx.undo_transaction(txuuid)
@@ -304,7 +304,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis', fiche=c)
self.commit()
- p.set_relations(fiche=None)
+ p.cw_set(fiche=None)
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
self.commit()
@@ -319,7 +319,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis', fiche=c)
self.commit()
- p.set_relations(fiche=None)
+ p.cw_set(fiche=None)
txuuid = self.commit()
c.cw_delete()
self.commit()
@@ -339,7 +339,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis')
self.commit()
- p.set_relations(fiche=c)
+ p.cw_set(fiche=c)
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
self.commit()
@@ -354,7 +354,7 @@
c = session.create_entity('Card', title=u'hop', content=u'hop')
p = session.create_entity('Personne', nom=u'louis')
self.commit()
- p.set_relations(fiche=c)
+ p.cw_set(fiche=c)
txuuid = self.commit()
c.cw_delete()
self.commit()
@@ -369,7 +369,7 @@
c2 = session.create_entity('Card', title=u'hip', content=u'hip')
p = session.create_entity('Personne', nom=u'louis', fiche=c1)
self.commit()
- p.set_relations(fiche=c2)
+ p.cw_set(fiche=c2)
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
self.commit()
@@ -385,7 +385,7 @@
c2 = session.create_entity('Card', title=u'hip', content=u'hip')
p = session.create_entity('Personne', nom=u'louis', fiche=c1)
self.commit()
- p.set_relations(fiche=c2)
+ p.cw_set(fiche=c2)
txuuid = self.commit()
c1.cw_delete()
self.commit()
@@ -401,7 +401,7 @@
p = session.create_entity('Personne', nom=u'toto')
session.commit()
self.session.set_cnxset()
- p.set_attributes(nom=u'titi')
+ p.cw_set(nom=u'titi')
txuuid = self.commit()
self.assertUndoTransaction(txuuid)
p.cw_clear_all_caches()
@@ -412,7 +412,7 @@
p = session.create_entity('Personne', nom=u'toto')
session.commit()
self.session.set_cnxset()
- p.set_attributes(nom=u'titi')
+ p.cw_set(nom=u'titi')
txuuid = self.commit()
p.cw_delete()
self.commit()
--- a/sobjects/ldapparser.py Fri Sep 07 13:48:55 2012 +0200
+++ b/sobjects/ldapparser.py Fri Sep 07 14:01:59 2012 +0200
@@ -84,7 +84,7 @@
attrs = dict( (k, v) for k, v in attrs.iteritems()
if v != getattr(entity, k))
if attrs:
- entity.set_attributes(**attrs)
+ entity.cw_set(**attrs)
self.notify_updated(entity)
def ldap2cwattrs(self, sdict, tdict=None):
@@ -113,7 +113,7 @@
if entity.__regid__ == 'EmailAddress':
return
groups = [self._get_group(n) for n in self.source.user_default_groups]
- entity.set_relations(in_group=groups)
+ entity.cw_set(in_group=groups)
self._process_email(entity, sourceparams)
def is_deleted(self, extid, etype, eid):
@@ -140,9 +140,9 @@
email = self.extid2entity(emailextid, 'EmailAddress',
address=emailaddr)
if entity.primary_email:
- entity.set_relations(use_email=email)
+ entity.cw_set(use_email=email)
else:
- entity.set_relations(primary_email=email)
+ entity.cw_set(primary_email=email)
elif self.sourceuris:
# pop from sourceuris anyway, else email may be removed by the
# source once import is finished
--- a/test/unittest_entity.py Fri Sep 07 13:48:55 2012 +0200
+++ b/test/unittest_entity.py Fri Sep 07 14:01:59 2012 +0200
@@ -701,23 +701,23 @@
self.assertEqual(card4.rest_path(), unicode(card4.eid))
- def test_set_attributes(self):
+ def test_cw_set_attributes(self):
req = self.request()
person = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien')
self.assertEqual(person.prenom, u'adrien')
self.assertEqual(person.nom, u'di mascio')
- person.set_attributes(prenom=u'sylvain', nom=u'thénault')
+ person.cw_set(prenom=u'sylvain', nom=u'thénault')
person = self.execute('Personne P').get_entity(0, 0) # XXX retreival needed ?
self.assertEqual(person.prenom, u'sylvain')
self.assertEqual(person.nom, u'thénault')
- def test_set_relations(self):
+ def test_cw_set_relations(self):
req = self.request()
person = req.create_entity('Personne', nom=u'chauvat', prenom=u'nicolas')
note = req.create_entity('Note', type=u'x')
- note.set_relations(ecrit_par=person)
+ note.cw_set(ecrit_par=person)
note = req.create_entity('Note', type=u'y')
- note.set_relations(ecrit_par=person.eid)
+ note.cw_set(ecrit_par=person.eid)
self.assertEqual(len(person.reverse_ecrit_par), 2)
def test_metainformation_and_external_absolute_url(self):
--- a/web/data/cubicweb.css Fri Sep 07 13:48:55 2012 +0200
+++ b/web/data/cubicweb.css Fri Sep 07 14:01:59 2012 +0200
@@ -545,6 +545,16 @@
padding-left: 2em;
}
+/* actions around tables */
+.tableactions span {
+ padding: 0 18px;
+ height: 24px;
+ background: #F8F8F8;
+ border: 1px solid #DFDFDF;
+ border-bottom: none;
+ border-radius: 4px 4px 0 0;
+}
+
/* custom boxes */
.search_box div.boxBody {
--- a/web/data/cubicweb.old.css Fri Sep 07 13:48:55 2012 +0200
+++ b/web/data/cubicweb.old.css Fri Sep 07 14:01:59 2012 +0200
@@ -899,6 +899,16 @@
padding-left: 0.5em;
}
+/* actions around tables */
+.tableactions span {
+ padding: 0 18px;
+ height: 24px;
+ background: #F8F8F8;
+ border: 1px solid #DFDFDF;
+ border-bottom: none;
+ border-radius: 4px 4px 0 0;
+}
+
/***************************************/
/* error view (views/management.py) */
/***************************************/
--- a/web/request.py Fri Sep 07 13:48:55 2012 +0200
+++ b/web/request.py Fri Sep 07 14:01:59 2012 +0200
@@ -170,7 +170,6 @@
@property
def authmode(self):
"""Authentification mode of the instance
-
(see :ref:`WebServerConfig`)"""
return self.vreg.config['auth-mode']
--- a/web/test/unittest_magicsearch.py Fri Sep 07 13:48:55 2012 +0200
+++ b/web/test/unittest_magicsearch.py Fri Sep 07 14:01:59 2012 +0200
@@ -230,5 +230,118 @@
self.assertEqual(rset.rql, 'Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s')
self.assertEqual(rset.args, {'text': u'utilisateur Smith'})
+
+class RQLSuggestionsBuilderTC(CubicWebTC):
+ def suggestions(self, rql):
+ req = self.request()
+ rbs = self.vreg['components'].select('rql.suggestions', req)
+ return rbs.build_suggestions(rql)
+
+ def test_no_restrictions_rql(self):
+ self.assertListEqual([], self.suggestions(''))
+ self.assertListEqual([], self.suggestions('An'))
+ self.assertListEqual([], self.suggestions('Any X'))
+ self.assertListEqual([], self.suggestions('Any X, Y'))
+
+ def test_invalid_rql(self):
+ self.assertListEqual([], self.suggestions('blabla'))
+ self.assertListEqual([], self.suggestions('Any X WHERE foo, bar'))
+
+ def test_is_rql(self):
+ self.assertListEqual(['Any X WHERE X is %s' % eschema
+ for eschema in sorted(self.vreg.schema.entities())
+ if not eschema.final],
+ self.suggestions('Any X WHERE X is'))
+
+ self.assertListEqual(['Any X WHERE X is Personne', 'Any X WHERE X is Project'],
+ self.suggestions('Any X WHERE X is P'))
+
+ self.assertListEqual(['Any X WHERE X is Personne, Y is Personne',
+ 'Any X WHERE X is Personne, Y is Project'],
+ self.suggestions('Any X WHERE X is Personne, Y is P'))
+
+
+ def test_relations_rql(self):
+ self.assertListEqual(['Any X WHERE X is Personne, X ass A',
+ 'Any X WHERE X is Personne, X datenaiss A',
+ 'Any X WHERE X is Personne, X description A',
+ 'Any X WHERE X is Personne, X fax A',
+ 'Any X WHERE X is Personne, X nom A',
+ 'Any X WHERE X is Personne, X prenom A',
+ 'Any X WHERE X is Personne, X promo A',
+ 'Any X WHERE X is Personne, X salary A',
+ 'Any X WHERE X is Personne, X sexe A',
+ 'Any X WHERE X is Personne, X tel A',
+ 'Any X WHERE X is Personne, X test A',
+ 'Any X WHERE X is Personne, X titre A',
+ 'Any X WHERE X is Personne, X travaille A',
+ 'Any X WHERE X is Personne, X web A',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X '))
+ self.assertListEqual(['Any X WHERE X is Personne, X tel A',
+ 'Any X WHERE X is Personne, X test A',
+ 'Any X WHERE X is Personne, X titre A',
+ 'Any X WHERE X is Personne, X travaille A',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X t'))
+ # try completion on selected
+ self.assertListEqual(['Any X WHERE X is Personne, Y is Societe, X tel A',
+ 'Any X WHERE X is Personne, Y is Societe, X test A',
+ 'Any X WHERE X is Personne, Y is Societe, X titre A',
+ 'Any X WHERE X is Personne, Y is Societe, X travaille Y',
+ ],
+ self.suggestions('Any X WHERE X is Personne, Y is Societe, X t'))
+ # invalid relation should not break
+ self.assertListEqual([],
+ self.suggestions('Any X WHERE X is Personne, X asdasd'))
+
+ def test_attribute_vocabulary_rql(self):
+ self.assertListEqual(['Any X WHERE X is Personne, X promo "bon"',
+ 'Any X WHERE X is Personne, X promo "pasbon"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X promo "'))
+ self.assertListEqual(['Any X WHERE X is Personne, X promo "pasbon"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X promo "p'))
+ # "bon" should be considered complete, hence no suggestion
+ self.assertListEqual([],
+ self.suggestions('Any X WHERE X is Personne, X promo "bon"'))
+ # no valid vocabulary starts with "po"
+ self.assertListEqual([],
+ self.suggestions('Any X WHERE X is Personne, X promo "po'))
+
+ def test_attribute_value_rql(self):
+ # suggestions should contain any possible value for
+ # a given attribute (limited to 10)
+ req = self.request()
+ for i in xrange(15):
+ req.create_entity('Personne', nom=u'n%s' % i, prenom=u'p%s' % i)
+ self.assertListEqual(['Any X WHERE X is Personne, X nom "n0"',
+ 'Any X WHERE X is Personne, X nom "n1"',
+ 'Any X WHERE X is Personne, X nom "n10"',
+ 'Any X WHERE X is Personne, X nom "n11"',
+ 'Any X WHERE X is Personne, X nom "n12"',
+ 'Any X WHERE X is Personne, X nom "n13"',
+ 'Any X WHERE X is Personne, X nom "n14"',
+ 'Any X WHERE X is Personne, X nom "n2"',
+ 'Any X WHERE X is Personne, X nom "n3"',
+ 'Any X WHERE X is Personne, X nom "n4"',
+ 'Any X WHERE X is Personne, X nom "n5"',
+ 'Any X WHERE X is Personne, X nom "n6"',
+ 'Any X WHERE X is Personne, X nom "n7"',
+ 'Any X WHERE X is Personne, X nom "n8"',
+ 'Any X WHERE X is Personne, X nom "n9"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X nom "'))
+ self.assertListEqual(['Any X WHERE X is Personne, X nom "n1"',
+ 'Any X WHERE X is Personne, X nom "n10"',
+ 'Any X WHERE X is Personne, X nom "n11"',
+ 'Any X WHERE X is Personne, X nom "n12"',
+ 'Any X WHERE X is Personne, X nom "n13"',
+ 'Any X WHERE X is Personne, X nom "n14"',
+ ],
+ self.suggestions('Any X WHERE X is Personne, X nom "n1'))
+
+
if __name__ == '__main__':
unittest_main()
--- a/web/test/unittest_reledit.py Fri Sep 07 13:48:55 2012 +0200
+++ b/web/test/unittest_reledit.py Fri Sep 07 14:01:59 2012 +0200
@@ -175,8 +175,8 @@
def setup_database(self):
super(ClickAndEditFormUICFGTC, self).setup_database()
- self.tick.set_relations(concerns=self.proj)
- self.proj.set_relations(manager=self.toto)
+ self.tick.cw_set(concerns=self.proj)
+ self.proj.cw_set(manager=self.toto)
def test_with_uicfg(self):
old_rctl = reledit_ctrl._tagdefs.copy()
--- a/web/test/unittest_urlrewrite.py Fri Sep 07 13:48:55 2012 +0200
+++ b/web/test/unittest_urlrewrite.py Fri Sep 07 14:01:59 2012 +0200
@@ -105,9 +105,9 @@
def setup_database(self):
req = self.request()
self.p1 = self.create_user(req, u'user1')
- self.p1.set_attributes(firstname=u'joe', surname=u'Dalton')
+ self.p1.cw_set(firstname=u'joe', surname=u'Dalton')
self.p2 = self.create_user(req, u'user2')
- self.p2.set_attributes(firstname=u'jack', surname=u'Dalton')
+ self.p2.cw_set(firstname=u'jack', surname=u'Dalton')
def test_rgx_action_with_transforms(self):
class TestSchemaBasedRewriter(SchemaBasedRewriter):
--- a/web/views/ajaxcontroller.py Fri Sep 07 13:48:55 2012 +0200
+++ b/web/views/ajaxcontroller.py Fri Sep 07 14:01:59 2012 +0200
@@ -28,7 +28,7 @@
functions that can be called from the javascript world.
To register a new remote function, either decorate your function
-with the :func:`cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:
+with the :func:`~cubicweb.web.views.ajaxcontroller.ajaxfunc` decorator:
.. sourcecode:: python
@@ -39,7 +39,7 @@
def list_users(self):
return [u for (u,) in self._cw.execute('Any L WHERE U login L')]
-or inherit from :class:`cubicwbe.web.views.ajaxcontroller.AjaxFunction` and
+or inherit from :class:`~cubicweb.web.views.ajaxcontroller.AjaxFunction` and
implement the ``__call__`` method:
.. sourcecode:: python
--- a/web/views/basecomponents.py Fri Sep 07 13:48:55 2012 +0200
+++ b/web/views/basecomponents.py Fri Sep 07 14:01:59 2012 +0200
@@ -59,6 +59,14 @@
# display multilines query as one line
rql = rset is not None and rset.printable_rql(encoded=False) or req.form.get('rql', '')
rql = rql.replace(u"\n", u" ")
+ rql_suggestion_comp = self._cw.vreg['components'].select_or_none('rql.suggestions', self._cw)
+ if rql_suggestion_comp is not None:
+ # enable autocomplete feature only if the rql
+ # suggestions builder is available
+ self._cw.add_css('jquery.ui.css')
+ self._cw.add_js(('cubicweb.ajax.js', 'jquery.ui.js'))
+ self._cw.add_onload('$("#rql").autocomplete({source: "%s"});'
+ % (req.build_url('json', fname='rql_suggest')))
self.w(u'''<div id="rqlinput" class="%s"><form action="%s"><fieldset>
<input type="text" id="rql" name="rql" value="%s" title="%s" tabindex="%s" accesskey="q" class="searchField" />
''' % (not self.cw_propval('visible') and 'hidden' or '',
--- a/web/views/magicsearch.py Fri Sep 07 13:48:55 2012 +0200
+++ b/web/views/magicsearch.py Fri Sep 07 14:01:59 2012 +0200
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -15,19 +15,23 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""a query processor to handle quick search shortcuts for cubicweb"""
+"""a query processor to handle quick search shortcuts for cubicweb
+"""
__docformat__ = "restructuredtext en"
import re
from logging import getLogger
-from warnings import warn
+
+from yams.interfaces import IVocabularyConstraint
from rql import RQLSyntaxError, BadRQLQuery, parse
+from rql.utils import rqlvar_maker
from rql.nodes import Relation
from cubicweb import Unauthorized, typed_eid
from cubicweb.view import Component
+from cubicweb.web.views.ajaxcontroller import ajaxfunc
LOGGER = getLogger('cubicweb.magicsearch')
@@ -408,3 +412,247 @@
# explicitly specified processor: don't try to catch the exception
return proc.process_query(uquery)
raise BadRQLQuery(self._cw._('sorry, the server is unable to handle this query'))
+
+
+
+## RQL suggestions builder ####################################################
+class RQLSuggestionsBuilder(Component):
+ """main entry point is `build_suggestions()` which takes
+ an incomplete RQL query and returns a list of suggestions to complete
+ the query.
+
+ This component is enabled by default and is used to provide autocompletion
+ in the RQL search bar. If you don't want this feature in your application,
+ just unregister it or make it unselectable.
+
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.build_suggestions
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.etypes_suggestion_set
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.possible_etypes
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.possible_relations
+ .. automethod:: cubicweb.web.views.magicsearch.RQLSuggestionsBuilder.vocabulary
+ """
+ __regid__ = 'rql.suggestions'
+
+ #: maximum number of results to fetch when suggesting attribute values
+ attr_value_limit = 20
+
+ def build_suggestions(self, user_rql):
+ """return a list of suggestions to complete `user_rql`
+
+ :param user_rql: an incomplete RQL query
+ """
+ req = self._cw
+ try:
+ if 'WHERE' not in user_rql: # don't try to complete if there's no restriction
+ return []
+ variables, restrictions = [part.strip() for part in user_rql.split('WHERE', 1)]
+ if ',' in restrictions:
+ restrictions, incomplete_part = restrictions.rsplit(',', 1)
+ user_rql = '%s WHERE %s' % (variables, restrictions)
+ else:
+ restrictions, incomplete_part = '', restrictions
+ user_rql = variables
+ select = parse(user_rql, print_errors=False).children[0]
+ req.vreg.rqlhelper.annotate(select)
+ req.vreg.solutions(req, select, {})
+ if restrictions:
+ return ['%s, %s' % (user_rql, suggestion)
+ for suggestion in self.rql_build_suggestions(select, incomplete_part)]
+ else:
+ return ['%s WHERE %s' % (user_rql, suggestion)
+ for suggestion in self.rql_build_suggestions(select, incomplete_part)]
+ except Exception, exc: # we never want to crash
+ self.debug('failed to build suggestions: %s', exc)
+ return []
+
+ ## actual completion entry points #########################################
+ def rql_build_suggestions(self, select, incomplete_part):
+ """
+ :param select: the annotated select node (rql syntax tree)
+ :param incomplete_part: the part of the rql query that needs
+ to be completed, (e.g. ``X is Pr``, ``X re``)
+ """
+ chunks = incomplete_part.split(None, 2)
+ if not chunks: # nothing to complete
+ return []
+ if len(chunks) == 1: # `incomplete` looks like "MYVAR"
+ return self._complete_rqlvar(select, *chunks)
+ elif len(chunks) == 2: # `incomplete` looks like "MYVAR some_rel"
+ return self._complete_rqlvar_and_rtype(select, *chunks)
+ elif len(chunks) == 3: # `incomplete` looks like "MYVAR some_rel something"
+ return self._complete_relation_object(select, *chunks)
+ else: # would be anything else, hard to decide what to do here
+ return []
+
+ # _complete_* methods are considered private, at least while the API
+ # isn't stabilized.
+ def _complete_rqlvar(self, select, rql_var):
+ """return suggestions for "variable only" incomplete_part
+
+ as in :
+
+ - Any X WHERE X
+ - Any X WHERE X is Project, Y
+ - etc.
+ """
+ return ['%s %s %s' % (rql_var, rtype, dest_var)
+ for rtype, dest_var in self.possible_relations(select, rql_var)]
+
+ def _complete_rqlvar_and_rtype(self, select, rql_var, user_rtype):
+ """return suggestions for "variable + rtype" incomplete_part
+
+ as in :
+
+ - Any X WHERE X is
+ - Any X WHERE X is Person, X firstn
+ - etc.
+ """
+ # special case `user_type` == 'is', return every possible type.
+ if user_rtype == 'is':
+ return self._complete_is_relation(select, rql_var)
+ else:
+ return ['%s %s %s' % (rql_var, rtype, dest_var)
+ for rtype, dest_var in self.possible_relations(select, rql_var)
+ if rtype.startswith(user_rtype)]
+
+ def _complete_relation_object(self, select, rql_var, user_rtype, user_value):
+ """return suggestions for "variable + rtype + some_incomplete_value"
+
+ as in :
+
+ - Any X WHERE X is Per
+ - Any X WHERE X is Person, X firstname "
+ - Any X WHERE X is Person, X firstname "Pa
+ - etc.
+ """
+ # special case `user_type` == 'is', return every possible type.
+ if user_rtype == 'is':
+ return self._complete_is_relation(select, rql_var, user_value)
+ elif user_value:
+ if user_value[0] in ('"', "'"):
+ # if finished string, don't suggest anything
+ if len(user_value) > 1 and user_value[-1] == user_value[0]:
+ return []
+ user_value = user_value[1:]
+ return ['%s %s "%s"' % (rql_var, user_rtype, value)
+ for value in self.vocabulary(select, rql_var,
+ user_rtype, user_value)]
+ return []
+
+ def _complete_is_relation(self, select, rql_var, prefix=''):
+ """return every possible types for rql_var
+
+ :param prefix: if specified, will only return entity types starting
+ with the specified value.
+ """
+ return ['%s is %s' % (rql_var, etype)
+ for etype in self.possible_etypes(select, rql_var, prefix)]
+
+ def etypes_suggestion_set(self):
+ """returns the list of possible entity types to suggest
+
+ The default is to return any non-final entity type available
+ in the schema.
+
+ Can be overridden for instance if an application decides
+ to restrict this list to a meaningful set of business etypes.
+ """
+ schema = self._cw.vreg.schema
+ return set(eschema.type for eschema in schema.entities() if not eschema.final)
+
+ def possible_etypes(self, select, rql_var, prefix=''):
+ """return all possible etypes for `rql_var`
+
+ The returned list will always be a subset of meth:`etypes_suggestion_set`
+
+ :param select: the annotated select node (rql syntax tree)
+ :param rql_var: the variable name for which we want to know possible types
+ :param prefix: if specified, will only return etypes starting with it
+ """
+ available_etypes = self.etypes_suggestion_set()
+ possible_etypes = set()
+ for sol in select.solutions:
+ if rql_var in sol and sol[rql_var] in available_etypes:
+ possible_etypes.add(sol[rql_var])
+ if not possible_etypes:
+ # `Any X WHERE X is Person, Y is`
+ # -> won't have a solution, need to give all etypes
+ possible_etypes = available_etypes
+ return sorted(etype for etype in possible_etypes if etype.startswith(prefix))
+
+ def possible_relations(self, select, rql_var, include_meta=False):
+ """returns a list of couple (rtype, dest_var) for each possible
+ relations with `rql_var` as subject.
+
+ ``dest_var`` will be picked among availabel variables if types match,
+ otherwise a new one will be created.
+ """
+ schema = self._cw.vreg.schema
+ relations = set()
+ untyped_dest_var = rqlvar_maker(defined=select.defined_vars).next()
+ # for each solution
+ # 1. find each possible relation
+ # 2. for each relation:
+ # 2.1. if the relation is meta, skip it
+ # 2.2. for each possible destination type, pick up possible
+ # variables for this type or use a new one
+ for sol in select.solutions:
+ etype = sol[rql_var]
+ sol_by_types = {}
+ for varname, var_etype in sol.items():
+ # don't push subject var to avoid "X relation X" suggestion
+ if varname != rql_var:
+ sol_by_types.setdefault(var_etype, []).append(varname)
+ for rschema in schema[etype].subject_relations():
+ if include_meta or not rschema.meta:
+ for dest in rschema.objects(etype):
+ for varname in sol_by_types.get(dest.type, (untyped_dest_var,)):
+ suggestion = (rschema.type, varname)
+ if suggestion not in relations:
+ relations.add(suggestion)
+ return sorted(relations)
+
+ def vocabulary(self, select, rql_var, user_rtype, rtype_incomplete_value):
+ """return acceptable vocabulary for `rql_var` + `user_rtype` in `select`
+
+ Vocabulary is either found from schema (Yams) definition or
+ directly from database.
+ """
+ schema = self._cw.vreg.schema
+ vocab = []
+ for sol in select.solutions:
+ # for each solution :
+ # - If a vocabulary constraint exists on `rql_var+user_rtype`, use it
+ # to define possible values
+ # - Otherwise, query the database to fetch available values from
+ # database (limiting results to `self.attr_value_limit`)
+ try:
+ eschema = schema.eschema(sol[rql_var])
+ rdef = eschema.rdef(user_rtype)
+ except KeyError: # unknown relation
+ continue
+ cstr = rdef.constraint_by_interface(IVocabularyConstraint)
+ if cstr is not None:
+ # a vocabulary is found, use it
+ vocab += [value for value in cstr.vocabulary()
+ if value.startswith(rtype_incomplete_value)]
+ elif rdef.final:
+ # no vocab, query database to find possible value
+ vocab_rql = 'DISTINCT Any V LIMIT %s WHERE X is %s, X %s V' % (
+ self.attr_value_limit, eschema.type, user_rtype)
+ vocab_kwargs = {}
+ if rtype_incomplete_value:
+ vocab_rql += ', X %s LIKE %%(value)s' % user_rtype
+ vocab_kwargs['value'] = '%s%%' % rtype_incomplete_value
+ vocab += [value for value, in
+ self._cw.execute(vocab_rql, vocab_kwargs)]
+ return sorted(set(vocab))
+
+
+
+@ajaxfunc(output_type='json')
+def rql_suggest(self):
+ rql_builder = self._cw.vreg['components'].select_or_none('rql.suggestions', self._cw)
+ if rql_builder:
+ return rql_builder.build_suggestions(self._cw.form['term'])
+ return []
--- a/web/views/tableview.py Fri Sep 07 13:48:55 2012 +0200
+++ b/web/views/tableview.py Fri Sep 07 14:01:59 2012 +0200
@@ -290,20 +290,17 @@
return attrs
def render_actions(self, w, actions):
- box = MenuWidget('', '', _class='tableActionsBox', islist=False)
- label = tags.span(self._cw._('action menu'))
- menu = PopupBoxMenu(label, isitem=False, link_class='actionsBox',
- ident='%sActions' % self.view.domid)
- box.append(menu)
+ w(u'<div class="tableactions">')
for action in actions:
- menu.append(action)
- box.render(w=w)
- w(u'<div class="clear"></div>')
+ w(u'<span>')
+ action.render(w)
+ w(u'</span>')
+ w(u'</div>')
def show_hide_filter_actions(self, currentlydisplayed=False):
divid = self.view.domid
showhide = u';'.join(toggle_action('%s%s' % (divid, what))[11:]
- for what in ('Form', 'Show', 'Hide', 'Actions'))
+ for what in ('Form', 'Actions'))
showhide = 'javascript:' + showhide
self._cw.add_onload(u'''\
$(document).ready(function() {
@@ -313,10 +310,8 @@
$('#%(id)sShow').attr('class', 'hidden');
}
});''' % {'id': divid})
- showlabel = self._cw._('show filter form')
- hidelabel = self._cw._('hide filter form')
- return [component.Link(showhide, showlabel, id='%sShow' % divid),
- component.Link(showhide, hidelabel, id='%sHide' % divid)]
+ showlabel = self._cw._('toggle filter')
+ return [component.Link(showhide, showlabel, id='%sToggle' % divid)]
class AbstractColumnRenderer(object):