.. _custom-filters:

Custom Filters
==============

The basic requirements for a custom filter are to supply the operators, the query modifier
for applying the filter, and a search expression for single-search. A few examples are
here.

Simple custom filter::

    class ActivityStatusFilter(FilterBase):
        # operators are declared as Operator(<key>, <display>, <field-type>)
        operators = (
            Operator('all', 'all', None),
            Operator('pend', 'pending', None),
            Operator('comp', 'completed', None),
        )

        def get_search_expr(self):
            status_col = sa.sql.case(
                [(Activity.flag_completed == sa.true(), 'completed')],
                else_='pending'
            )
            # Could use ilike here, depending on the target DBMS
            return lambda value: status_col.like('%{}%'.format(value))

        def apply(self, query):
            if self.op == 'all':
                return query
            if self.op == 'comp':
                return query.filter(Activity.flag_completed == sa.true())
            if self.op == 'pend':
                return query.filter(Activity.flag_completed == sa.false())
            return super().apply(self, query)


Options filter for INT foreign key lookup::

    class VendorFilter(OptionsIntFilterBase):
        def options_from(self):
            # Expected to return a list of tuples (id, label).
            # In this case, we're retrieving options from the database.
            return db.session.query(Vendor.id, Vendor.label).select_from(
                Vendor
            ).filter(
                Vendor.active_flag == sa.true()
            ).order_by(
                Vendor.label
            ).all()


Aggregate filters, i.e. those using the HAVING clause instead of WHERE, must be marked with the
`is_aggregate` flag. Single-search via expressions will only address aggregate filters if all
search filters are aggregate. Using an aggregate filter will require a GROUP BY clause be set.

    class AggregateTextFilter(TextFilter):
        is_aggregate = True
