Improved search between words.

        The original _search for MongoEngine dates back to November 12th, 2013 [1]_.
        In this ref it's stated that there is a bug with complex Q queries preventing
        multi-word searches. During this time, the MongoEngine version was earlier than
        0.4 (predating PyPI) [2]_. Since then, there have been multiple releases [3]_
        which appear to have fixed the query issue.

        Treats id (_id) impliticly as a member of column_searchable_list, except it's
        not computed in an OR/AND, a direct lookup is checked for.

        References
        ----------
        .. [1] Search for MongoEngine. 02b936b. November 23, 2013.
           https://git.io/fxf8C. Accessed September, 29th, 2018.
        .. [2] MongoEngine releases on PyPI.
           https://pypi.org/project/mongoengine/#history. Accessed September 29th, 2018.
        .. [3] MongoEngine release notes. http://docs.mongoengine.org/changelog.html.
           Accessed September 29th, 2018.

def _search(self, query, search_term):
        """
        Improved search between words.

        The original _search for MongoEngine dates back to November 12th, 2013 [1]_.
        In this ref it's stated that there is a bug with complex Q queries preventing
        multi-word searches. During this time, the MongoEngine version was earlier than
        0.4 (predating PyPI) [2]_. Since then, there have been multiple releases [3]_
        which appear to have fixed the query issue.

        Treats id (_id) impliticly as a member of column_searchable_list, except it's
        not computed in an OR/AND, a direct lookup is checked for.

        References
        ----------
        .. [1] Search for MongoEngine. 02b936b. November 23, 2013.
           https://git.io/fxf8C. Accessed September, 29th, 2018.
        .. [2] MongoEngine releases on PyPI.
           https://pypi.org/project/mongoengine/#history. Accessed September 29th, 2018.
        .. [3] MongoEngine release notes. http://docs.mongoengine.org/changelog.html.
           Accessed September 29th, 2018.
        """
        criterias = mongoengine.Q()
        rel_criterias = mongoengine.Q()
        terms = shlex.split(search_term)

        # If an ObjectId pattern, see if we can get an instant lookup.
        if len(terms) == 1 and re.match(RE_OBJECTID, terms[0]):
            q = query.filter(id=bson.ObjectId(terms[0]))
            if q.count() == 1:  # Note: .get doesn't work, they need a QuerySet
                return q

        for term in terms:
            op, term = parse_like_term(term)

            # Case insensitive by default
            if op == 'contains':
                op = 'icontains'

            criteria = mongoengine.Q()

            for field in self._search_fields:
                if isinstance(field, mongoengine.fields.ReferenceField):
                    rel_model = field.document_type
                    rel_fields = (
                        getattr(self, 'column_searchable_refs', {})
                        .get(field.name, {})
                        .get('fields', ['id'])
                    )

                    # If term isn't an ID, don't do an ID lookup
                    if rel_fields == ['id'] and not re.match(RE_OBJECTID, term):
                        continue

                    ids = [
                        o.id for o in search_relative_field(rel_model, rel_fields, term)
                    ]
                    rel_criterias |= mongoengine.Q(**{'%s__in' % field.name: ids})
                elif isinstance(field, mongoengine.fields.ListField):
                    if not isinstance(field.field, mongoengine.fields.ReferenceField):
                        continue  # todo: support lists of other types
                    rel_model = field.field.document_type_obj
                    rel_fields = (
                        getattr(self, 'column_searchable_refs', {})
                        .get(field.name, {})
                        .get('fields', 'id')
                    )
                    ids = [
                        o.id for o in search_relative_field(rel_model, rel_fields, term)
                    ]
                    rel_criterias |= mongoengine.Q(**{'%s__in' % field.name: ids})
                else:
                    flt = {'%s__%s' % (field.name, op): term}
                    q = mongoengine.Q(**flt)
                    criteria |= q

            criterias &= criteria

        # import pprint
        # pp = pprint.PrettyPrinter(indent=4).pprint
        # print(pp(query.filter(criterias)._query))
        return query.filter(criterias | rel_criterias)