@@ -1484,7 +1484,7 @@ When adding a new Permission, you need to:
148414844. check it in the appropriate hasPermission methods in your tracker's
14851485 extensions/detectors/interfaces.py modules
14861486
1487- The ``addPermission`` method takes a three optional parameters:
1487+ The ``addPermission`` method takes a four optional parameters:
14881488
14891489**check**
14901490 A function to be executed which returns boolean determining whether
@@ -1520,6 +1520,98 @@ The ``addPermission`` method takes a three optional parameters:
15201520 permission. When searching there is no item defined, so a check
15211521 function does not make any sense.
15221522
1523+ **filter**
1524+ This optional function returns parameters for the ``filter`` method
1525+ when getting ``Class`` items (users, issues etc.) from the
1526+ database. It filters items at the database level (using SQL where
1527+ possible). This pre-filters the number of items returned from the
1528+ database when displaying results in an ``index`` template. This
1529+ filtering is usually faster than calling a ``check`` method (see
1530+ previous argument) on *each individual result*.
1531+
1532+ The ``filter`` method has the signature::
1533+
1534+ filter(db, userid, klass)
1535+
1536+ where ``db`` is the database handle, ``userid`` is the user attempting
1537+ access and ``klass`` is the ``Class`` in the schema.
1538+
1539+ The ``filter`` function must return a *list* of dictionaries of
1540+ parameters of the
1541+ `Class.filter <design.html#:~:text=search_matches>`_ call.
1542+ This includes filterspec, retired and exact_match_specs.
1543+ Note that sort and group parameters of the filter call should
1544+ not be set by filter method (they will be overwritten) and the
1545+ parameter search_matches must not be set.
1546+
1547+ The query executed by an index template is modified by the
1548+ parameters computed by the ``filter`` function. An
1549+ empty list of filter parameters (``[]``) indicates no access. When
1550+ using a filter, a check function is still needed to test each
1551+ individual item for visibility. When the filter function is defined
1552+ but a check function is not defined, a check function is
1553+ manufactured automatically from the ``filter`` function.
1554+
1555+ Note that the filter option is not supported for the Search
1556+ permission. Since the filter function is called *after* the search was
1557+ already performed a filter function does not make any sense.
1558+
1559+ An example ``filter`` function for the ``view_query`` check function
1560+ in the query checks above would look like::
1561+
1562+ def filter_query(db, userid, klass):
1563+ return [{'filterspec': {
1564+ 'private_for': ['-1', userid]
1565+ }}]
1566+
1567+ This would be called by the framework for all queries found when
1568+ displaying queries. It filters for all queries where the
1569+ ``private_for`` field is the userid or empty. This matches the
1570+ definition of the ``view_query`` function above where permission is
1571+ granted if the ``private_for`` field indicates the query is owned by
1572+ the user, or the ``private_for`` field is empty indicating that the
1573+ query is public. If we want to modify the check to also allow acess if
1574+ the user is the ``creator`` of a query we would change the filter
1575+ function to::
1576+
1577+ def filter_query(db, userid, klass):
1578+ f1 = {'filterspec': {'private_for': ['-1', userid]}}
1579+ f2 = {'filterspec': {'creator': userid}}
1580+ return [f1, f2]
1581+
1582+ This is an example where we need multiple filter calls to model an
1583+ "or" condition, the user has access if either the ``private_for``
1584+ check passes *or* the user is the creator of the query.
1585+
1586+ Consider an example where we have a class structure where both the
1587+ ``issue`` class and the ``user`` class include a reference to an
1588+ ``organization`` class. Users are permitted to view only those
1589+ issues that are associated with their respective organizations. A
1590+ check function or this could look like::
1591+
1592+ def view_issue(db, userid, itemid):
1593+ user = db.user.getnode(userid)
1594+ if not user.organisation:
1595+ return False
1596+ issue = db.issue.getnode(itemid)
1597+ if user.organisation == issue.organisation:
1598+ return True
1599+
1600+ The corresponding ``filter`` function::
1601+
1602+ def filter_issue(db, userid, klass):
1603+ user = db.user.getnode(userid)
1604+ if not user.organisation:
1605+ return []
1606+ return [{'filterspec': {
1607+ 'organisation': user.organisation
1608+ }}]
1609+
1610+ This filters for all issues where the organisation is the same as the
1611+ organisation of the user. Note how the filter fails early returning an
1612+ empty list (meaning "no access") if the user happens to not have an
1613+ organisation.
1614+
15231615**properties**
15241616 A sequence of property names that are the only properties to apply the
15251617 new Permission to (eg. ``... klass='user', properties=('name',
0 commit comments