def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
            can_reuse=None, process_extras=True):
        arg, value = filter_expr
        parts = arg.split(LOOKUP_SEP)
        if not parts:
            raise FieldError("Cannot parse keyword query %r" % arg)

        # Work out the lookup type and remove it from 'parts', if necessary.
        if len(parts) == 1 or parts[-1] not in self.query_terms:
            lookup_type = 'exact'
        else:
            lookup_type = parts.pop()

        # By default, this is a WHERE clause. If an aggregate is referenced
        # in the value, the filter will be promoted to a HAVING
        having_clause = False

        # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
        # uses of None as a query value.
        if value is None:
            if lookup_type != 'exact':
                raise ValueError("Cannot use None as a query value")
            lookup_type = 'isnull'
            value = True
        elif (value == '' and lookup_type == 'exact' and
              connection.features.interprets_empty_strings_as_nulls):
            lookup_type = 'isnull'
            value = True
        elif callable(value):
            value = value()
        elif hasattr(value, 'evaluate'):
            # If value is a query expression, evaluate it
            value = SQLEvaluator(value, self)
            having_clause = value.contains_aggregate

        for alias, aggregate in self.aggregates.items():
            if alias == parts[0]:
                entry = self.where_class()
                entry.add((aggregate, lookup_type, value), AND)
                if negate:
                    entry.negate()
                self.having.add(entry, AND)
                return

        opts = self.get_meta()
        alias = self.get_initial_alias()
        allow_many = trim or not negate

        try:
            field, target, opts, join_list, last, extra_filters = self.setup_joins(
                    parts, opts, alias, True, allow_many, can_reuse=can_reuse,
                    negate=negate, process_extras=process_extras)
        except MultiJoin, e:
            self.split_exclude(filter_expr, LOOKUP_SEP.join(parts[:e.level]),
                    can_reuse)
            return

        if (lookup_type == 'isnull' and value is True and not negate and
                len(join_list) > 1):
            # If the comparison is against NULL, we may need to use some left
            # outer joins when creating the join chain. This is only done when
            # needed, as it's less efficient at the database level.
            self.promote_alias_chain(join_list)

        # Process the join list to see if we can remove any inner joins from
        # the far end (fewer tables in a query is better).
        col, alias, join_list = self.trim_joins(target, join_list, last, trim)

        if connector == OR:
            # Some joins may need to be promoted when adding a new filter to a
            # disjunction. We walk the list of new joins and where it diverges
            # from any previous joins (ref count is 1 in the table list), we
            # make the new additions (and any existing ones not used in the new
            # join list) an outer join.
            join_it = iter(join_list)
            table_it = iter(self.tables)
            join_it.next(), table_it.next()
            table_promote = False
            join_promote = False
            for join in join_it:
                table = table_it.next()
                if join == table and self.alias_refcount[join] > 1:
                    continue
                join_promote = self.promote_alias(join)
                if table != join:
                    table_promote = self.promote_alias(table)
                break
            self.promote_alias_chain(join_it, join_promote)
            self.promote_alias_chain(table_it, table_promote)


        if having_clause:
            self.having.add((Constraint(alias, col, field), lookup_type, value),
                connector)
        else:
            self.where.add((Constraint(alias, col, field), lookup_type, value),
                connector)

        if negate:
            self.promote_alias_chain(join_list)
            if lookup_type != 'isnull':
                if len(join_list) > 1:
                    for alias in join_list:
                        if self.alias_map[alias][JOIN_TYPE] == self.LOUTER:
                            j_col = self.alias_map[alias][RHS_JOIN_COL]
                            entry = self.where_class()
                            entry.add((Constraint(alias, j_col, None), 'isnull', True), AND)
                            entry.negate()
                            self.where.add(entry, AND)
                            break
                elif not (lookup_type == 'in'
                            and not hasattr(value, 'as_sql')
                            and not hasattr(value, '_as_sql')
                            and not value) and field.null:
                    # Leaky abstraction artifact: We have to specifically
                    # exclude the "foo__in=[]" case from this handling, because
                    # it's short-circuited in the Where class.
                    # We also need to handle the case where a subquery is provided
                    entry = self.where_class()
                    entry.add((Constraint(alias, col, None), 'isnull', True), AND)
                    entry.negate()
                    self.where.add(entry, AND)

        if can_reuse is not None:
            can_reuse.update(join_list)
        if process_extras:
            for filter in extra_filters:
                self.add_filter(filter, negate=negate, can_reuse=can_reuse,
                        process_extras=False)
Mocks the effects of get_hits using global data instead of Elasticsearch.
dataset/ETHPy150Open CollabQ/CollabQ/vendor/django/db/models/sql/query.py/BaseQuery.add_filter--crossover with--dataset/ETHPy150Open Yelp/elastalert/elastalert/test_rule.py/MockElastAlerter.mock_hits