Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add filters to a query dynamically in Django?

In my viewSet I am doing a query,

queryset= Books.objects.all();

Now from an ajax call I get my filter values from UI i.e. age,gender, etc. of auther.There will be a total of 5 filters.

Now the problem which I ran into is how am I going to add filters to my query(only those filters which have any value).

What I tried is I checked for individual filter value and did query, but that way it fails as if the user remove the filter value or add multiple filters. Any better suggestion how to accomplish this?

like image 866
Aman Gupta Avatar asked Jan 12 '16 09:01

Aman Gupta


People also ask

How can I filter a Django query with a list of values?

To filter a Python Django query with a list of values, we can use the filter method with in . to search Blog entries with pk set to 1,4 or 7 by calling Blog. objects. filter with the pk_in argument set to [1, 4, 7] .

What is filter () in Django?

The filter() method is used to filter you search, and allows you to return only the rows that matches the search term.

Can you filter a QuerySet?

Yes, you can reuse existing querysets. This is not really making anything faster though, in fact, this code block won't even execute a query against the database because Django QuerySets are lazily evaluated. What I means is that it won't send the query to the database until you actually need the values.


7 Answers

Here's a bit more generic one. It will apply filters to your queryset if they are passed as the GET parameters. If you're doing a POST call, just change the name in the code.

import operator
from django.db.models import Q


def your_view(self, request, *args, **kwargs):
    # Here you list all your filter names
    filter_names = ('filter_one', 'filter_two', 'another_one', )

    queryset = Books.objects.all(); 
    filter_clauses = [Q(filter=request.GET[filter])
                      for filter in filter_names
                      if request.GET.get(filter)]
    if filter_clauses:
        queryset = queryset.filter(reduce(operator.and_, filter_clauses))

    # rest of your view

Note that you can use lookup expressions in your filters' names. For example, if you want to filter books with price lower or equal to specified in filter, you could just use price__lte as a filter name.

like image 184
Alex Morozov Avatar answered Sep 26 '22 20:09

Alex Morozov


You haven't shown any code, so you haven't really explained what the problem is:

Start with the queryset Book.objects.all(). For each filter, check if there is a value for the filter in request.POST, and if so, filter the queryset. Django querysets are lazy, so only the final queryset will be evaluated.

queryset = Book.objects.all()
if request.POST.get('age'):
    queryset = queryset.filter(author__age=request.POST['age'])
if request.POST.get('gender'):
    queryset = queryset.filter(author__gender=request.POST['gender'])
...
like image 45
Alasdair Avatar answered Sep 27 '22 20:09

Alasdair


You can simply get the request.GET content as a dict (making sure to convert the values to string or a desired type as they'd be list by default i.e: dict(request.GET) would give you something like {u'a': [u'val']}.

Once you are sure you have a dictionary of keys matching your model fields, you can simply do:

filtered = queryset.filter(**dict_container)

like image 35
yusuf.oguntola Avatar answered Sep 23 '22 20:09

yusuf.oguntola


Maybe django-filter would help simplify the solutions others have given?

Something like:

class BookFilter(django_filters.FilterSet):
    class Meta:
        model = Book
        fields = ['author__age', 'author__gender', ...]

Then the view looks like:

def book_list(request):
    f = BookFilter(request.GET, queryset=Book.objects.all())
    return render_to_response('my_app/template.html', {'filter': f})

For more information see the documentation.

like image 22
Penguin Brian Avatar answered Sep 26 '22 20:09

Penguin Brian


this worked for me, I've merged Alex Morozov answer with Dima answer

import operator

def your_view(self, request, *args, **kwargs):
    # Here you list all your filter names
    filter_names = ('filter_one', 'filter_two', 'another_one', )

    queryset = Books.objects.all(); 
    filter_clauses = [Q(**{filter: request.GET[filter]})
                  for filter in filter_names
                  if request.GET.get(filter)]
    if filter_clauses:
    queryset = queryset.filter(reduce(operator.and_, filter_clauses))

    # rest of your view
like image 21
Bheid Avatar answered Sep 23 '22 20:09

Bheid


You can do something like that

class BooksAPI(viewsets.ModelViewSet):
    queryset = Books.objects.none()


def get_queryset(self):
    argumentos = {}
    if self.request.query_params.get('age'):
        argumentos['age'] = self.request.query_params.get('age')
    if self.request.query_params.get('gender'):
        argumentos['gender'] = self.request.query_params.get('gender')
    if len(argumentos) > 0:
        books = Books.objects.filter(**argumentos)
    else:
        books = Books.objects.all()
    return books
like image 36
Palomita Yañez Quiroz Avatar answered Sep 24 '22 20:09

Palomita Yañez Quiroz


For a very simple equality check, here is my solution using a helper function in a ModelViewSet.

  1. The helper function check_for_params creates a dictionary of request parameters passed in the URL.

  2. Alter the ModelViewSet get_queryset() method by filtering the Django QuerySet with a single filter clause which prevents multiple queries being called by chaining filters.

I could tried to use the Django Q() object but could not get it to only make a single call.

def check_for_params(request, param_check_list: List) -> dict:
    """
    Create a dictionary of params passed through URL.

    Parameters
    ----------
    request - DRF Request object.
    param_check_list - List of params potentially passed in the url.
    """
    if not param_check_list:
        print("No param_check_list passed.")
    else:
        param_dict = {}
        for p in param_check_list:
            param_dict[p] = request.query_params.get(p, None)
        return param_dict
    

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        """
        Return a queryset and apply filters, if applicable.
        
        Info
        ----
        Building the queryset.filter method by unpacking the key-value pairs this way,
        creates a single filter clause and prevents multiple queries from being called
        by chaining filters.
        """
        queryset = MyModel.objects.all()

        param_check_list = ['param1', 'param2', 'param3']
        params = check_for_params(self.request, param_check_list)
        filtered = {k: v for k, v in params.items() if v}

        # Calling filter() only once here prevents multiple queries. 
        return queryset.filter(**filtered)
like image 32
skbay2 Avatar answered Sep 23 '22 20:09

skbay2