django model search form

django, forms, model, search

Solution

I had a similar case like yours (real estate project), I ended up with the following approach, you can refine this to your needs...I removed select_related and prefetch_related models for easier reading

properties/forms.py:

class SearchPropertyForm(forms.Form):

    property_type = forms.ModelChoiceField(label=_("Property Type"), queryset=HouseType.objects.all(),widget=forms.Select(attrs={'class':'form-control input-sm'}))
    location = forms.ModelChoiceField(label=_('Location'), queryset=HouseLocation.objects.all(), widget=forms.Select(attrs={'class':'form-control input-sm'}))

Then in the properties/views.py

# Create a Mixin to inject the search form in our context 

class SeachPropertyMixin(object):
    def get_context_data(self, **kwargs):
        context = super(SeachPropertyMixin, self).get_context_data(**kwargs)
        context['search_property_form'] = SearchPropertyForm()
        return context

In your actual view (I apply the search form as a sidebar element in my detailview only:

# Use Class Based views, saves you a great deal of repeating code...
class PropertyView(SeachPropertyMixin,DetailView):
    template_name = 'properties/view.html'
    context_object_name = 'house'
    ...
    queryset = HouseModel.objects.select_related(...).prefetch_related(...).filter(flag_active=True, flag_status='a')

Finally your search result view (this is performed as GET request, since we are not altering any data in our DB, we stick to the GET method):

# Search results should return a ListView, here is how we implement it:
class PropertySearchResultView(ListView):
    template_name = "properties/propertysearchresults.html"
    context_object_name = 'houses'
    paginate_by = 6
    queryset = HouseModel.objects.select_related(...).prefetch_related(...).order_by('-sale_price').filter(flag_active=True, flag_status='a')

    def get_queryset(self):
        qs = super(PropertySearchResultView,self).get_queryset()
        property_type = self.request.GET.get('property_type')
        location = self.request.GET.get('location')
        '''
        Start Chaining the filters based on the input, this way if the user has not 
        selected a filter it wont be used.
        '''
        if property_type != '' and property_type is not None:
            qs = qs.filter(housetype=property_type)
        if location != '' and location is not None:
            qs = qs.filter(location=location)
        return qs

    def get_context_data(self, **kwargs):
        context = super(PropertySearchResultView, self).get_context_data()
        ''' 
        Add the current request to the context 
        '''
        context['current_request'] = self.request.META['QUERY_STRING']
        return context

Problem

Firstly, I did my homework and looked around before posting! My question seems like a very basic thing that must’ve been covered before. I'm now looking at Django-filter as a potential solution, but would like some advice on if this is the right way to go and if there any other solutions. I have a Django app wit 10 models, each model has a few fields. Most fields are `ChoiceField` that users populate using forms with the default `select` widget. There is a separate form for each model. I want to create a separate form for each model (in separate views) that users will use to search the database. The search form will contain only drop-down boxes (the `select` widgets) with the same choices as the forms used to populate the database with the addition of the “any” option. I know how to use `.object.filter()`, however the “any” option would correspond to not include specific fields in the filter and I'm not sure how to add model fields to the filter based on users’ selection I briefly looked at Haystack as an option but it seems to be made for full text search rather than “model filed search” I'm after. Sample model (simplified): ``` class Property(): TYPE_CHOICES = (‘apartment’, ‘house’, ‘flat’) type = charfield(choices=TYPE_CHOICES) LOC_CHOICES = (‘Brussels’, ‘London’, ‘Dublin’, ‘Paris’) location = charfield(choices=LOC_CHOICES) price = PostivieInteger() ``` Users can select only “type”, only “location” or both (not making selection is equal to ANY) in which case I end up with 3 different filters: ``` Property.objects.filter(type=’apartment’) Property.objects.filter(location=’Dublin’) Property.objects.filter(type=’apartment’, location=’Dublin’) ``` The main question: django-filter the best option? ``` Question 1: what’s the best option of accomplishing this overall? Question 2: how do I add model fields to the filter based on user’s form selection? Question 3: how do I do the filter based on user selection? (I know how to use .filter(price_lt=).exclude(price_gt=) but again how do I do it dynamically based on selection as “ANY” would mean this is not included in the query) ```

Original source