from sqlalchemy import or_


class BaseFilter:
    """
    Base filter that all filter classes must implement.
    """

    def filter_query(self, request, query, view):
        """
        Return the filter query.
        """
        raise NotImplementedError('.filter_query() must be implemented.')  # pragma: no cover


class FieldFilter(BaseFilter):
    """
    Filters a query based on the filter_fields set on the view.
    filter_fields should be a list of SQLAlchemy Model columns.
    Supports filtering a comma separated list using OR statements.
    Supports relationship filter using the . path to attribute. WARNING: Every relationship in a . path is joined.
    """

    def filter_query(self, request, query, view):
        if not view.filter_fields or not request.params:
            return query

        filter_list = []
        querystring_params = self.parse_query_string(request.params)
        available_fields = list(map(lambda x: '{}.{}'.format(x.parent.class_.__name__, x.name), view.filter_fields))

        for key, val in querystring_params.items():
            attrs = key.split('.')  # Every item in the resulting array must be a relationship except for the last
            related_model = view.model
            join_models = []

            for attr in attrs[:-1]:
                # Loop through all the relationships and build the list of tables that need to be joined
                # to the query. The final value of related_model will be the model representing the table
                # with the attribute to be filtered on.

                relationship = getattr(related_model, attr, None)

                if relationship is None:
                    related_model = None
                    break

                related_model = relationship.mapper.class_
                join_models.append(related_model)

            attr = attrs[-1]

            if related_model and hasattr(related_model, attr):
                try:
                    i = available_fields.index('{}.{}'.format(related_model.__name__, attr))
                except ValueError:
                    continue

                joined_tables = [mapper.class_ for mapper in query._join_entities]  # accessing protected attribute?

                for join_model in join_models:
                    if join_model not in joined_tables:
                        query = query.join(join_model)

                # If there is only one comparison in or_ it is just AND'd on the query
                filter_list.append(or_(*[view.filter_fields[i] == v for v in val.split(',')]))

            query = query.filter(*filter_list)

        return query

    def parse_query_string(self, params):
        """
        Override this method if you need to support query string filter keys other than
        the names of the fields being filtered.

        For example, if your filter querystring param looks like filter[<field_name>], you will
        just want to return a dictionary with just the field_name, value pair.

        :return: dict
        """

        return params

@@ -326,13 +326,21 @@
 ass 
-Field
+AttributeBase
 Filt
@@ -371,105 +371,48 @@
     
-Filters a query based on the filter_fields set on the view.%0A    filter_fields should be a list of
+A base class for implementing filters on
  SQL
@@ -423,20 +423,23 @@
 emy 
-M
+m
 odel 
-column
+attribute
 s.%0A 
@@ -502,30 +502,20 @@
 atements
-.%0A    Supports
+ and
  relatio
@@ -532,16 +532,20 @@
 er using
+%0A   
  the . p
@@ -613,23 +613,698 @@
 joined.%0A
-    %22%22%22
+%0A    query_string_lookup: The key to use when parsing the requests query string. The %3Ckey%3E in %3Ckey%3E%5B%3Cfield_name%3E%5D=%3Cval%3E.%0A    %22%22%22%0A%0A    query_string_lookup = None%0A%0A    def parse_query_string(self, params):%0A        %22%22%22%0A        Override this method if you need to support query string filter keys other than those in the%0A        format of filter%5B%3Cfield_name%3E%5D.%0A%0A        :return: dict%0A        %22%22%22%0A        results = %7B%7D%0A%0A        for key, val in params.items():%0A            lookup_len = len(self.query_string_lookup) + 1%0A%0A            if key%5B0:lookup_len%5D == '%7B%7D%5B'.format(self.query_string_lookup) and key%5B-1%5D == '%5D':%0A                results%5Bkey%5Blookup_len:-1%5D%5D = val%0A%0A        return results
 %0A%0A    de
@@ -3037,611 +3037,673 @@
     
-# If there is only one comparison in or_ it is just AND'd on the query%0A                filter_list.append(or_(*%5Bview.filter_fields%5Bi%5D == v for v in val.split(',')%5D))%0A%0A            query = query.filter(*filter_list)%0A%0A        return query%0A%0A    def parse_query_string(self, params):%0A        %22%22%22%0A        Override this method if you need to support query string filter keys other than%0A        the names of the fields being filtered.%0A%0A        For example, if your f
+filter_list.append(self.build_comparision(view.filter_fields%5Bi%5D, val))%0A%0A            query = query.filter(*filter_list)%0A%0A        return query%0A%0A    def build_comparision(self, field, value):%0A        %22%22%22%0A        Given the model field and the value to be filtered, this should return the statement to be appended%0A        as a filter to the final query.%0A        %22%22%22%0A%0A        raise NotImplementedError%0A%0A%0Aclass FieldFilter(AttributeBaseFilter):%0A    %22%22%22%0A    F
 ilter
+s a
  query
-string param looks like
+ based on the filter_fields set on the view.%0A   
  filter
-%5B%3C
+_
 field
-_name%3E%5D, you will%0A        just want to return a dictionary with just the
+s should be a list of SQLAlchemy Model columns.%0A    %22%22%22%0A%0A    query_string_lookup = 'filter'%0A%0A    def build_comparision(self,
  field
-_name
 , value
- pair.%0A
+):
 %0A   
@@ -3711,53 +3711,89 @@
     
-:return: dict%0A        %22%22%22%0A%0A        return params
+# Support %22IN%22 filtering%0A        return or_(%5Bfield == v for v in value.split(',')%5D)%0A
 %0A

break out the common filter functionality