Rasiel
Rasiel

Reputation: 2913

Advanced search for a specific Django model

I'm aware of full text search applications like Django Solr and solango, etc.

What I'm looking to built is more akin to an advanced search for a real estate site. E.g they can choose location, price, etc. similar to www.viewr.com advanced search.

What I have done so far is this, under the models custom manager:

def advanced_search(self, district, location, type, facilities, features, not_permitted):
        q_objects = []
        l_objects = []
        t_objects = []
        fc_objects = []
        ft_objects = []
        np_objects = []

        if district:
            if location:
                for loc in location:
                    l_objects.append(Q(location__exact=loc))
            else:
                l_objects.append(Q(location__district=district))


        if type:    
            for ty in type:
                t_objects.append(Q(listing_type__exact=ty))

        if facilities:    
            for fc in facilities:
                fc_objects.append(Q(property_facilities__exact=fc))

        if features:
            for ft in features:
                ft_objects.append(Q(property_features__exact=ft))
                ft_objects.append(Q(community_features__exact=ft))

        if not_permitted:    
            for np in not_permitted:
                np_objects.append(Q(not_permitted__exact=np))                              

        # Start with a bare QuerySet
        qs = self.get_query_set()

        if location:
            qs = qs.filter(reduce(operator.or_, l_objects))
        if type:
            qs = qs.filter(reduce(operator.or_, t_objects))
        if facilities:
            qs = qs.filter(reduce(operator.or_, fc_objects))
        if features:
            qs = qs.filter(reduce(operator.or_, ft_objects))
        if not_permitted:
            qs = qs.filter(reduce(operator.or_, np_objects))
        # Use operator's or_ to string together all of your Q objects.
        return qs

Right now I'm not getting very predictable results. Is there something I might be doing wrong? Is there a better way of doing the various OR searches/joins?

Upvotes: 2

Views: 5608

Answers (4)

Tunji Abioye
Tunji Abioye

Reputation: 69

I know this post is a bit old but I came across this, and I assume someone else might when looking for a simple and quick solution,

from django.db.models import Q
from .models import RealEstateListing

def advanced_search(self, community_features, property_features, price, listing, location):
    query_dictionary = {
            'community_features': community_features,
            'property_features': property_features,
            'price__lte': price,
            'listing_type': listing,
            'location': location,
        }
        # filter out None values
        not_none_parameters = {single_query: query_dictionary.get(single_query) for single_query in query_dictionary if query_dictionary.get(single_query) is not None}
        filter_list = Q()
        for item in not_none_parameters:
            filter_list |= Q(**{item:not_none_parameters.get(item)})
        qs = RealEstateListing.objects.filter(filter_list)

Upvotes: 1

Rasiel
Rasiel

Reputation: 2913

The closest I've come to this scenario is this post by Jacob:

http://www.djangosnippets.org/snippets/32/

What I've done is:

def advanced_search(self, form):
        # It's easier to store a dict of the possible lookups we want, where
        # the values are the keyword arguments for the actual query.
        qdict = {'district': 'location__district',
          'location': 'location',
          'property_type': 'listing_type',
          'max_price': 'price__lte',
          'property_features': 'property_features',
          'community_features': 'community_features',
        }

        # Then we can do this all in one step instead of needing to call
        # 'filter' and deal with intermediate data structures.
        q_objs = [Q(**{qdict[k]: form.cleaned_data[k]}) for k in qdict.keys() if form.cleaned_data.get(k, None)]        
        search_results = RealEstateListing.objects.select_related().filter(*q_objs)
        return search_results

It works fine when I pass single choices, but when passing multiple choices as for property facilities it chokes and says:

OperationalError: Subquery returns more than 1 row

Upvotes: 2

Mikhail Korobov
Mikhail Korobov

Reputation: 22248

Just a couple of ideas.

It is very useful to use kwargs unpacking to provide arguments to filter method in such cases. Something like this can make the code simplier:

kwargs = {'not_permitted':np,'property_features': ft}
return qs.filter(**kwargs)

Maybe you should take a look at django's filtering code in admin. In django admin parameters like not_permitted__exact are passed via GET. And then, after some filtering, the whole GET dict can be passed as unpacked kwargs argument to filter method. This make things really simple when you have a lot of filter options.

Upvotes: 2

dan
dan

Reputation: 552

http://code.google.com/p/djapian/ can index the fields of your choice and allow you build complex searches though combination, boolean logic, etc.

Upvotes: 1

Related Questions