[book] some more documentation and cleanups
* merged existing facets documentation with the one I've just written and
put almost everything within the code
* added a note about __depends__ and __recommends__ and about the recommends
semantic (extracted from a post on the ml)
* added a note about write security checking (extracted from a post on the ml)
* fixed some dumb sphinx errors
--- a/doc/book/en/annexes/docstrings-conventions.rst Wed Aug 18 13:58:12 2010 +0200
+++ b/doc/book/en/annexes/docstrings-conventions.rst Wed Aug 18 16:53:05 2010 +0200
@@ -1,8 +1,8 @@
Javascript docstrings
=====================
-Whereas in Python source code we only need to include a module docstrings
-using the directive `.. automodule:: mypythonmodule`, we will have to
+Whereas in Python source code we only need to include a module docstrings
+using the directive `.. automodule:: mypythonmodule`, we will have to
explicitely define Javascript modules and functions in the doctrings since
there is no native directive to include Javascript files.
@@ -32,7 +32,7 @@
Basically we document javascript with RestructuredText docstring
following the same convention as documenting Python code.
-The doctring in Javascript files must be contained in standard
+The doctring in Javascript files must be contained in standard
Javascript comment signs, starting with `/**` and ending with `*/`,
such as::
@@ -44,7 +44,7 @@
* All the follwing line will be prefixed with a `*` followed by a space.
* ...
* ...
- */
+ */
Comments line prefixed by `//` will be ignored. They are reserved for source
@@ -61,12 +61,12 @@
Its purpose is to define the function prototype such as::
- .. function:: loadxhtml(url, data, reqtype, mode)
+ .. function:: loadxhtml(url, data, reqtype, mode)
-If any namespace is used, we should add it in the prototype for now,
-until we define an appropriate directive.
-::
- .. function:: jQuery.fn.loadxhtml(url, data, reqtype, mode)
+If any namespace is used, we should add it in the prototype for now,
+until we define an appropriate directive::
+
+ .. function:: jQuery.fn.loadxhtml(url, data, reqtype, mode)
Function parameters
~~~~~~~~~~~~~~~~~~~
@@ -76,7 +76,7 @@
Example of a javascript function docstring::
- .. function:: loadxhtml(url, data, reqtype, mode)
+ .. function:: loadxhtml(url, data, reqtype, mode)
cubicweb loadxhtml plugin to make jquery handle xhtml response
--- a/doc/book/en/annexes/rql/language.rst Wed Aug 18 13:58:12 2010 +0200
+++ b/doc/book/en/annexes/rql/language.rst Wed Aug 18 16:53:05 2010 +0200
@@ -146,27 +146,27 @@
- For grouped queries (e.g. with a GROUPBY clause), all
selected variables should be grouped at the right of the keyword.
-- To group and/or sort by attributes, we can do::
-
- X,L user U, U login L GROUPBY L, X ORDERBY L
-
- If the sorting method (SORT_METHOD) is not specified, then the sorting is
ascendant (`ASC`).
-- Aggregate Functions: COUNT, MIN, MAX, AVG, SUM
+- Aggregate Functions: COUNT, MIN, MAX, AVG, SUM, GROUP_CONCAT
Having
``````
-The HAVING clause, as in SQL, has been originally introduced to restrict a query according to value returned by an aggregate function, e.g.::
+The HAVING clause, as in SQL, has been originally introduced to restrict a query
+according to value returned by an aggregate function, e.g.::
Any X GROUPBY X WHERE X relation Y HAVING COUNT(Y) > 10
It may however be used for something else...
-In the WHERE clause, we are limited to 3-expression_, such thing can't be expressed directly as in the SQL's way. But this can be expressed using HAVING comparison expression.
+In the WHERE clause, we are limited to 3-expression, such thing can't be
+expressed directly as in the SQL's way. But this can be expressed using HAVING
+comparison expression.
-For instance, let's say you want to get people whose uppercased first name equals to another person uppercased first name::
+For instance, let's say you want to get people whose uppercased first name equals
+to another person uppercased first name::
Person X WHERE X firstname XFN, Y firstname YFN HAVING X > Y, UPPER(XFN) = UPPER(YFN)
@@ -174,18 +174,23 @@
Person X WHERE X birthday XB HAVING YEAR(XB) = 2000
-That lets you use transformation functions not only in selection but for restriction as well and to by-pass limitation of the WHERE clause, which was the major flaw in the RQL language.
+That lets you use transformation functions not only in selection but for
+restriction as well and to by-pass limitation of the WHERE clause, which was the
+major flaw in the RQL language.
-Notice that while we would like this to work without the HAVING clause, this can't be currently be done because it introduces an ambiguity in RQL's grammar that can't be handled by Yapps_, the parser's generator we're using.
+Notice that while we would like this to work without the HAVING clause, this
+can't be currently be done because it introduces an ambiguity in RQL's grammar
+that can't be handled by Yapps_, the parser's generator we're using.
Negation
````````
-* A query such as `Document X WHERE NOT X owned_by U` means "the
- documents have no relation `owned_by`".
-* But the query `Document X WHERE NOT X owned_by U, U login "syt"`
- means "the documents have no relation `owned_by` with the user
- syt". They may have a relation "owned_by" with another user.
+* A query such as `Document X WHERE NOT X owned_by U` means "the documents have
+ no relation `owned_by`".
+
+* But the query `Document X WHERE NOT X owned_by U, U login "syt"` means "the
+ documents have no relation `owned_by` with the user syt". They may have a
+ relation "owned_by" with another user.
Identity
````````
--- a/doc/book/en/devrepo/cubes/layout.rst Wed Aug 18 13:58:12 2010 +0200
+++ b/doc/book/en/devrepo/cubes/layout.rst Wed Aug 18 16:53:05 2010 +0200
@@ -108,8 +108,24 @@
The :file:`__pkginfo__.py` file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-It contains metadata describing your cube, mostly useful for
-packaging.
+It contains metadata describing your cube, mostly useful for packaging.
+
+Two important attributes of this module are __depends__ and __recommends__
+dictionaries that indicates what should be installed (and each version if
+necessary) for the cube to work.
+
+Dependency on other cubes are expected to be of the form 'cubicweb-<cubename>'.
+
+When an instance is created, dependencies are automatically installed, while
+recommends are not.
+
+Recommends may be seen as a kind of 'weak dependency'. Eg, the most important
+effect of recommending a cube is that, if cube A recommends cube B, the cube B
+will be loaded before the cube A (same thing happend when A depends on B).
+
+Having this behaviour is sometime desired: on schema creation, you may rely on
+something defined in the other's schema; on database creation, on something
+created by the other's postcreate, and so on.
:file:`migration/precreate.py` and :file:`migration/postcreate.py`
--- a/doc/book/en/devrepo/datamodel/definition.rst Wed Aug 18 13:58:12 2010 +0200
+++ b/doc/book/en/devrepo/datamodel/definition.rst Wed Aug 18 16:53:05 2010 +0200
@@ -412,6 +412,46 @@
* special relations "has_<ACTION>_permission" can not be used
+Important notes about write permissions checking
+````````````````````````````````````````````````
+
+Write permissions (e.g. 'add', 'update', 'delete') are checked in core hooks.
+
+When a permission is checked slightly vary according to if it's an entity or
+relation, and if the relation is an attribute relation or not). It's important to
+understand that since according to when a permission is checked, values returned
+by rql expressions may changes, hence the permission being granted or not.
+
+Here are the current rules:
+
+1. permission to add/update entity and its attributes are checked:
+
+ - on commit if the entity has been added
+
+ - in an 'after_update_entity' hook if the entity has been updated. If it fails
+ at this time, it will be retried on commit (hence you get the permission if
+ you have it just after the modification or *at* commit time)
+
+2. permission to delete an entity is checked in 'before_delete_entity' hook
+
+3. permission to add a relation is checked either:
+
+ - in 'before_add_relation' hook if the relation type is in the
+ `BEFORE_ADD_RELATIONS` set
+
+ - else at commit time if the relation type is in the `ON_COMMIT_ADD_RELATIONS`
+ set
+
+ - else in 'after_add_relation' hook (the default)
+
+4. permission to delete a relation is checked in 'before_delete_relation' hook
+
+Last but not least, remember queries issued from hooks and operation are by
+default 'unsafe', eg there are no read or write security checks.
+
+See :mod:`cubicweb.hooks.security` for more details.
+
+
.. _yams_example:
Defining your schema using yams
--- a/doc/book/en/devweb/facets.rst Wed Aug 18 13:58:12 2010 +0200
+++ b/doc/book/en/devweb/facets.rst Wed Aug 18 16:53:05 2010 +0200
@@ -1,172 +1,23 @@
The facets system
-----------------
-Facets allow to restrict searches according to some criteria. CubicWeb
-has a builtin `facet`_ system to define restrictions `filters`_ really
-as easily as possible. A few base classes for facets are provided in
-``cubicweb.web.facet.py``. All classes inherits from the base class
-``AbstractFacet``.
+Facets allow to restrict searches according to some user friendly criterias.
+CubicWeb has a builtin `facet`_ system to define restrictions `filters`_ really
+as easily as possible.
-Here is an overview of the facets rendering pick from the `tracker` cube:
+Here is an exemple of the facets rendering picked from our
+http://www.cubicweb.org web site:
.. image:: ../images/facet_overview.png
-Facets will appear on each page presenting more than one entity.
-
-
-
-VocabularyFacet
-~~~~~~~~~~~~~~~~
-The ``VocabularyFacet`` inherits from the ``AbstractFacet``.
-A class which inherits from VocabularyFacets must redefine these methods:
-
-.. automethod:: cubicweb.web.facet.VocabularyFacet.vocabulary
-.. automethod:: cubicweb.web.facet.VocabularyFacet.possible_values
-
-RelationFacet
-~~~~~~~~~~~~~~
-
-The ``RelationFacet`` inherits from the ``VocabularyFacet``. It allows to filter entities according to certain relation's values. Generally, you just have to define some class attributes like:
-
-- rtype: the name of the relation
-- role: the default value is set to `subject`
-- target_attr: needed if it is not the default attribute of the entity
-
-
-To illustrate this facet, let's take for example an *excerpt* of the schema of an office location search application:
-
-.. sourcecode:: python
-
- class Office(WorkflowableEntityType):
- price = Int(description='euros / m2 / HC / HT')
- surface = Int(description='m2')
- description = RichString(fulltextindexed=True)
- has_address = SubjectRelation('PostalAddress',
- cardinality='1?',
- composite='subject')
- proposed_by = SubjectRelation('Agency')
- comments = ObjectRelation('Comment',
- cardinality='1*',
- composite='object')
- screenshots = SubjectRelation(('File', 'Image'),
- cardinality='*1',
- composite='subject')
-
-
-We define a facet to filter offices according to the attribute
-`postalcode` of their associated `PostalAdress`.
-
-.. sourcecode:: python
-
- class PostalCodeFacet(RelationFacet):
- __regid__ = 'postalcode-facet' # every registered class must have an id
- __select__ = implements('Office') # this facet should only be selected when
- # visualizing offices
- rtype = 'has_address' # this facet is a filter on the entity linked to
- # the office thrhough the relation
- # has_address
- target_attr = 'postalcode' # the filter's key is the attribute "postal_code"
- # of the target PostalAddress entity
-
-
-AttributeFacet
-~~~~~~~~~~~~~~
-
-The ``AttributeFacet`` inherits from the ``RelationFacet``. It allows to filter entities according to certain attribute's values.
-
-The example below resumes the former schema. We define now a filter based on the `surface` attribute of the
-`Office`.
-
-.. sourcecode:: python
+Facets will appear on each page presenting more than one entity that may be
+filtered according to some known criteria.
- class SurfaceFacet(AttributeFacet):
- __regid__ = 'surface-facet' # every registered class must have an id
- __select__ = implements('Office') # this facet should only be selected when
- # visualizing offices
- rtype = 'surface' # the filter's key is the attribute "surface"
- comparator = '>=' # override the default value of operator since
- # we want to filter according to a
- # minimal
- # value, not an exact one
-
- def rset_vocabulary(self, ___):
- """override the default vocabulary method since we want to hard-code
- our threshold values.
- Not overriding would generate a filter box with all existing surfaces
- defined in the database.
- """
- return [('> 200', '200'), ('> 250', '250'),
- ('> 275', '275'), ('> 300', '300')]
-
-RangeFacet
-~~~~~~~~~~
-The ``RangeFacet`` inherits from the ``AttributeFacet``. It allows to filter entities according to certain attributes of numerical type.
-
-The ``RangeFacet`` displays a slider using `jquery`_ to choose a lower bound and an upper bound.
-
-The example below defines a facet to filter a selection of books according to their number of pages.
-
-.. sourcecode:: python
-
- class BookPagesFacet(RangeFacet):
- __regid__ = 'priority-facet'
- __select__ = RangeFacet.__select__ & implements('Book')
- rtype = 'pages'
-
-The image below display the rendering of the ``RangeFacet``:
-
-.. image:: ../images/facet_range.png
-
-DateRangeFacet
-~~~~~~~~~~~~~~
-The ``DateRangeFacet`` inherits from the ``RangeFacet``. It allows to filter entities according to certain attributes of date type.
+Base classes for facets
+~~~~~~~~~~~~~~~~~~~~~~~
+.. automodule:: cubicweb.web.facet
-Here is an example of code that defines a facet to filter
-musical works according to their composition date:
-
-.. sourcecode:: python
-
- class CompositionDateFacet(DateRangeFacet):
- # 1. make sure this facet is displayed only on Track selection
- __select__ = DateRangeFacet.__select__ & implements('Track')
- # 2. give the facet an id required by CubicWeb)
- __regid__ = 'compdate-facet'
- # 3. specify the attribute name that actually stores the date in the DB
- rtype = 'composition_date'
-
-With this facet, on each page displaying tracks, you'll be able to filter them
-according to their composition date with a jquery slider.
-
-The image below display the rendering of the ``DateRangeFacet``:
-
-.. image:: ../images/facet_date_range.png
-
-
-HasRelationFacet
-~~~~~~~~~~~~~~~~
-
-The ``DateRangeFacet`` inherits from the ``AbstractFacet``. It will
-display a simple checkbox and lets you refine your selection in order
-to get only entities that actually use this relation.
-
-Here is an example of the rendering of the ``HasRelationFacet`` to
-filter entities with image and the corresponding code:
-
-.. image:: ../images/facet_has_image.png
-
-.. sourcecode:: python
-
- class HasImageFacet(HasRelationFacet):
- __regid__ = 'hasimage-facet'
- __select__ = HasRelationFacet.__select__ & implements('Book')
- rtype = 'has_image'
-
-
-
-To use ``HasRelationFacet`` on a reverse relation add ``role = 'object'`` in
-it's definitions.
.. _facet: http://en.wikipedia.org/wiki/Faceted_browser
.. _filters: http://www.cubicweb.org/blogentry/154152
-.. _jquery: http://www.jqueryui.com/
--- a/doc/book/en/devweb/views/breadcrumbs.rst Wed Aug 18 13:58:12 2010 +0200
+++ b/doc/book/en/devweb/views/breadcrumbs.rst Wed Aug 18 16:53:05 2010 +0200
@@ -45,8 +45,8 @@
Here is the API of the ``IBreadCrumbsAdapter`` class:
-.. automethod:: cubicweb.web.views.ibreadcrumbs.IBreadCrumbs.parent_entity
-.. automethod:: cubicweb.web.views.ibreadcrumbs.IBreadCrumbs.breadcrumbs
+.. automethod:: cubicweb.web.views.ibreadcrumbs.IBreadCrumbsAdapter.parent_entity
+.. automethod:: cubicweb.web.views.ibreadcrumbs.IBreadCrumbsAdapter.breadcrumbs
If the breadcrumbs method return a list of entities, the
``cubicweb.web.views.ibreadcrumbs.BreadCrumbView`` is used to display
--- a/doc/book/en/index.rst Wed Aug 18 13:58:12 2010 +0200
+++ b/doc/book/en/index.rst Wed Aug 18 16:53:05 2010 +0200
@@ -62,5 +62,3 @@
* the :ref:`genindex`,
* the :ref:`modindex`,
-
-.. |cubicweb| replace:: *CubicWeb*
--- a/selectors.py Wed Aug 18 13:58:12 2010 +0200
+++ b/selectors.py Wed Aug 18 16:53:05 2010 +0200
@@ -188,8 +188,6 @@
.. autoclass:: cubicweb.appobject.traced_selection
-
-.. |cubicweb| replace:: *CubicWeb*
"""
__docformat__ = "restructuredtext en"
--- a/web/facet.py Wed Aug 18 13:58:12 2010 +0200
+++ b/web/facet.py Wed Aug 18 16:53:05 2010 +0200
@@ -15,8 +15,33 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
-"""contains utility functions and some visual component to restrict results of
-a search
+"""
+The :mod:`cubicweb.web.facet` module contains a set of abstract classes to use
+as bases to build your own facets
+
+All facet classes inherits from the :class:`AbstractFacet` class, though you'll
+usually find some more handy class that do what you want.
+
+Let's see available classes.
+
+Classes you'll want to use
+--------------------------
+.. autoclass:: cubicweb.web.facet.RelationFacet
+.. autoclass:: cubicweb.web.facet.RelationAttributeFacet
+.. autoclass:: cubicweb.web.facet.HasRelationFacet
+.. autoclass:: cubicweb.web.facet.AttributeFacet
+.. autoclass:: cubicweb.web.facet.RangeFacet
+.. autoclass:: cubicweb.web.facet.DateRangeFacet
+
+Classes for facets implementor
+------------------------------
+Unless you didn't find the class that does the job you want above, you may want
+to skip those classes...
+
+.. autoclass:: cubicweb.web.facet.AbstractFacet
+.. autoclass:: cubicweb.web.facet.VocabularyFacet
+
+.. comment: XXX widgets
"""
__docformat__ = "restructuredtext en"
@@ -268,9 +293,40 @@
toremove.add(rqlst.defined_vars[ovarname])
+## base facet classes ##########################################################
-## base facet classes #########################################################
class AbstractFacet(AppObject):
+ """Abstract base class for all facets. Facets are stored in their own
+ 'facets' registry. They are similar to contextual components since the use
+ the following configurable properties:
+
+ * `visible`, boolean flag telling if a facet should be displayed or not
+
+ * `order`, integer to control facets display order
+
+ * `context`, telling if a facet should be displayed in the table form filter
+ (context = 'tablefilter') or in the facet box (context = 'facetbox') or in
+ both (context = '')
+
+ The following methods define the facet API:
+
+ .. automethod:: cubicweb.web.facet.AbstractFacet.get_widget
+ .. automethod:: cubicweb.web.facet.AbstractFacet.add_rql_restrictions
+
+ Facets will have the following attributes set (beside the standard
+ :class:`~cubicweb.appobject.AppObject` ones):
+
+ * `rqlst`, the rql syntax tree being facetted
+
+ * `filtered_variable`, the variable node in this rql syntax tree that we're
+ interested in filtering
+
+ Facets implementors may also be interested in the following properties /
+ methods:
+
+ .. automethod:: cubicweb.web.facet.AbstractFacet.operator
+ .. automethod:: cubicweb.web.facet.AbstractFacet.rqlexec
+ """
__abstract__ = True
__registry__ = 'facets'
cw_property_defs = {
@@ -302,27 +358,54 @@
@property
def operator(self):
+ """Return the operator (AND or OR) to use for this facet when multiple
+ values are selected.
+ """
# OR between selected values by default
return self._cw.form.get(self.__regid__ + '_andor', 'OR')
+ def rqlexec(self, rql, args=None):
+ """Utility method to execute some rql queries, and simply returning an
+ empty list if :exc:`Unauthorized` is raised.
+ """
+ try:
+ return self._cw.execute(rql, args)
+ except Unauthorized:
+ return []
+
def get_widget(self):
- """return the widget instance to use to display this facet
+ """Return the widget instance to use to display this facet, or None if
+ the facet can't do anything valuable (only one value in the vocabulary
+ for instance).
"""
raise NotImplementedError
def add_rql_restrictions(self):
- """add restriction for this facet into the rql syntax tree"""
+ """When some facet criteria has been updated, this method is called to
+ add restriction for this facet into the rql syntax tree. It should get
+ back its value in form parameters, and modify the syntax tree
+ (`self.rqlst`) accordingly.
+ """
raise NotImplementedError
class VocabularyFacet(AbstractFacet):
+ """This abstract class extend :class:`AbstractFacet` to use the
+ :class:`FacetVocabularyWidget` as widget, suitable for facets that may
+ restrict values according to a (usually computed) vocabulary.
+
+ A class which inherits from VocabularyFacet must define at least these methods:
+
+ .. automethod:: cubicweb.web.facet.VocabularyFacet.vocabulary
+ .. automethod:: cubicweb.web.facet.VocabularyFacet.possible_values
+ """
needs_update = True
def get_widget(self):
- """return the widget instance to use to display this facet
+ """Return the widget instance to use to display this facet.
- default implentation expects a .vocabulary method on the facet and
- return a combobox displaying this vocabulary
+ This implementation expects a .vocabulary method on the facet and
+ return a combobox displaying this vocabulary.
"""
vocab = self.vocabulary()
if len(vocab) <= 1:
@@ -337,25 +420,19 @@
return wdg
def vocabulary(self):
- """return vocabulary for this facet, eg a list of 2-uple (label, value)
+ """Return vocabulary for this facet, eg a list of 2-uple (label, value).
"""
raise NotImplementedError
def possible_values(self):
- """return a list of possible values (as string since it's used to
- compare to a form value in javascript) for this facet
+ """Return a list of possible values (as string since it's used to
+ compare to a form value in javascript) for this facet.
"""
raise NotImplementedError
def support_and(self):
return False
- def rqlexec(self, rql, args=None):
- try:
- return self._cw.execute(rql, args)
- except Unauthorized:
- return []
-
class RelationFacet(VocabularyFacet):
"""Base facet to filter some entities according to other entities to which
@@ -382,6 +459,38 @@
result of this function's result instead of direct value
* `sortasc`: boolean flag to control ascendant/descendant sorting
+
+ To illustrate this facet, let's take for example an *excerpt* of the schema
+ of an office location search application:
+
+ .. sourcecode:: python
+
+ class Office(WorkflowableEntityType):
+ price = Int(description='euros / m2 / HC / HT')
+ surface = Int(description='m2')
+ has_address = SubjectRelation('PostalAddress',
+ cardinality='1?',
+ composite='subject')
+ proposed_by = SubjectRelation('Agency')
+
+
+ We can simply define a facet to filter offices according to the agency
+ proposing it:
+
+ .. sourcecode:: python
+
+ class AgencyFacet(RelationFacet):
+ __regid__ = 'agency'
+ # this facet should only be selected when visualizing offices
+ __select__ = RelationFacet.__select__ & implements('Office')
+ # this facet is a filter on the 'Agency' entities linked to the office
+ # through the 'proposed_by' relation, where the office is the subject
+ # of the relation
+ rtype = 'has_address'
+ # 'subject' is the default but setting it explicitly doesn't hurt...
+ role = 'subject'
+ # we want to display the agency's name
+ target_attr = 'name'
"""
__select__ = partial_relation_possible() & match_context_prop()
# class attributes to configure the relation facet
@@ -532,6 +641,32 @@
String
* you can specify a comparison operator using `comparator`
+
+
+ Back to our example... if you want to search office by postal code and that
+ you use a :class:`RelationFacet` for that, you won't get the expected
+ behaviour: if two offices have the same postal code, they've however two
+ different addresses. So you'll see in the facet the same postal code twice,
+ though linked to a different address entity. There is a great chance your
+ users won't understand that...
+
+ That's where this class come in ! It's used to said that you want to filter
+ according to the *attribute value* of a relatied entity, not to the entity
+ itself. Now here is the source code for the facet:
+
+ .. sourcecode:: python
+
+ class PostalCodeFacet(RelationAttributeFacet):
+ __regid__ = 'postalcode'
+ # this facet should only be selected when visualizing offices
+ __select__ = RelationAttributeFacet.__select__ & implements('Office')
+ # this facet is a filter on the PostalAddress entities linked to the
+ # office through the 'has_address' relation, where the office is the
+ # subject of the relation
+ rtype = 'has_address'
+ role = 'subject'
+ # we want to search according to address 'postal_code' attribute
+ target_attr = 'postalcode'
"""
_select_target_entity = False
# attribute type
@@ -579,7 +714,32 @@
* `target_attr` doesn't make sense here (you specify the attribute using `rtype`
* `role` neither, it's systematically 'subject'
+
+ So, suppose that in our office search example you want to refine search according
+ to the office's surface. Here is a code snippet achieving this:
+
+ .. sourcecode:: python
+
+ class SurfaceFacet(AttributeFacet):
+ __regid__ = 'surface'
+ __select__ = AttributeFacet.__select__ & implements('Office')
+ # this facet is a filter on the office'surface
+ rtype = 'surface'
+ # override the default value of operator since we want to filter
+ # according to a minimal value, not an exact one
+ comparator = '>='
+
+ def vocabulary(self):
+ '''override the default vocabulary method since we want to
+ hard-code our threshold values.
+
+ Not overriding would generate a filter containing all existing
+ surfaces defined in the database.
+ '''
+ return [('> 200', '200'), ('> 250', '250'),
+ ('> 275', '275'), ('> 300', '300')]
"""
+
_select_target_entity = True
def vocabulary(self):
@@ -617,27 +777,35 @@
self.attrtype, self.comparator)
-class FilterRQLBuilder(object):
- """called by javascript to get a rql string from filter form"""
+class RangeFacet(AttributeFacet):
+ """This class allows to filter entities according to an attribute of
+ numerical type.
+
+ It displays a slider using `jquery`_ to choose a lower bound and an upper
+ bound.
- def __init__(self, req):
- self._cw = req
+ The example below provides an alternative to the surface facet seen earlier,
+ in a more powerful way since
+
+ * lower/upper boundaries are computed according to entities to filter
+ * user can specify lower/upper boundaries, not only the lower one
+
+ .. sourcecode:: python
- def build_rql(self):#, tablefilter=False):
- form = self._cw.form
- facetids = form['facets'].split(',')
- select = self._cw.vreg.parse(self._cw, form['baserql']).children[0] # XXX Union unsupported yet
- mainvar = filtered_variable(select)
- toupdate = []
- for facetid in facetids:
- facet = get_facet(self._cw, facetid, select, mainvar)
- facet.add_rql_restrictions()
- if facet.needs_update:
- toupdate.append(facetid)
- return select.as_string(), toupdate
+ class SurfaceFacet(RangeFacet):
+ __regid__ = 'surface'
+ __select__ = RangeFacet.__select__ & implements('Office')
+ # this facet is a filter on the office'surface
+ rtype = 'surface'
+ All this with even less code!
-class RangeFacet(AttributeFacet):
+ The image below display the rendering of the slider:
+
+ .. image:: ../images/facet_range.png
+
+ .. _jquery: http://www.jqueryui.com/
+ """
attrtype = 'Float' # only numerical types are supported
@property
@@ -678,6 +846,13 @@
class DateRangeFacet(RangeFacet):
+ """This class works similarly as the :class:`RangeFacet` but for attribute
+ of date type.
+
+ The image below display the rendering of the slider for a date range:
+
+ .. image:: ../images/facet_date_range.png
+ """
attrtype = 'Date' # only date types are supported
@property
@@ -690,6 +865,25 @@
class HasRelationFacet(AbstractFacet):
+ """This class simply filter according to the presence of a relation
+ (whatever the entity at the other end). It display a simple checkbox that
+ lets you refine your selection in order to get only entities that actually
+ have this relation. You simply have to define which relation using the
+ `rtype` and `role` attributes.
+
+ Here is an example of the rendering of thos facet to filter book with image
+ and the corresponding code:
+
+ .. image:: ../images/facet_has_image.png
+
+ .. sourcecode:: python
+
+ class HasImageFacet(HasRelationFacet):
+ __regid__ = 'hasimage'
+ __select__ = HasRelationFacet.__select__ & implements('Book')
+ rtype = 'has_image'
+ role = 'subject'
+ """
rtype = None # override me in subclass
role = 'subject' # role of filtered entity in the relation
@@ -908,3 +1102,25 @@
def _render(self):
pass
+
+# other classes ################################################################
+
+class FilterRQLBuilder(object):
+ """called by javascript to get a rql string from filter form"""
+
+ def __init__(self, req):
+ self._cw = req
+
+ def build_rql(self):#, tablefilter=False):
+ form = self._cw.form
+ facetids = form['facets'].split(',')
+ # XXX Union unsupported yet
+ select = self._cw.vreg.parse(self._cw, form['baserql']).children[0]
+ mainvar = filtered_variable(select)
+ toupdate = []
+ for facetid in facetids:
+ facet = get_facet(self._cw, facetid, select, mainvar)
+ facet.add_rql_restrictions()
+ if facet.needs_update:
+ toupdate.append(facetid)
+ return select.as_string(), toupdate