how to use django rest filtering with mongoengine for list filtering

1k views Asked by At

views.py

from __future__ import unicode_literals

from rest_framework_mongoengine.viewsets import ModelViewSet as MongoModelViewSet

from app.serializers import *

from rest_framework_mongoengine.generics import * 

from rest_framework import filters    


def index_view(request):
    context = {}
    return TemplateResponse(request, 'index.html', context)


class ToolViewSet(MongoModelViewSet):
    serializer_class = ToolSerializer
    my_filter_fields = ('crop', 'district','taluka','circle','year',) 

    def get_kwargs_for_filtering(self):
        filtering_kwargs = {} 

        for field in  self.my_filter_fields: # iterate over the filter fields
            field_value = self.request.query_params.get(field) # get the value of a field from request query parameter
            if field_value: 
                filtering_kwargs[field] = field_value
        return filtering_kwargs 

    def get_queryset(self):
        queryset = Tool.objects.all() 
        filtering_kwargs = self.get_kwargs_for_filtering() # get the fields with values for filtering 
        if filtering_kwargs:
            queryset = Tool.objects.filter(**filtering_kwargs) # filter the queryset based on 'filtering_kwargs'
        return queryset

This is my code.

it's work for filtering exactly what we want to serach.

for example : http://api/tool/?district=Nasik&crop=banana

But, it's not working for list fileds for example : http://api/tool/?district=Nasik&district=Pune this gives output: [] (empty)

How to get this filter working to get all district "Nasik" OR district "Pune"

Can you please help me what I have to changes in my code for get data if I search http://api/tool/?crops=guava,banana Or http:../tool/?district=Nasik,Pune
gives me correct output?

2

There are 2 answers

8
Remi Smirra On BEST ANSWER

I recommend taking a look at django-rest-framework-filters. You can then use stuff like __in in your query params...

But if you want to proceed with your approach, I would add the logic in your get_kwargs_for_filtering method somehow like this:

def get_kwargs_for_filtering(self):
    filtering_kwargs = {} 

    for field in  self.my_filter_fields: # iterate over the filter fields
        field_value = self.request.query_params.get(field) # get the value of a field from request query parameter
        if field_value: 
            if ',' in field_value: # put your queryParams into an array and use the built-in django filter method '__in'
                filtering_kwargs[field + '__in'] = field_value.split(',')
            else:
                filtering_kwargs[field] = field_value
    return filtering_kwargs

With this, a call to http://api/tool/?crops=guava,banana should produce the desired behavior. I can't test if it works now, but this should lead you to the right direction.

0
Ouss On

Although the question is somewhat old. This is a question that is still important and valid in later versions of Django, DRF and MongoEngine.

At the time of this answer I am running

Django==4.1.3
djangorestframework==3.14
django-rest-framework-mongoengine==3.4.1
mongoengine==0.24.2
django-filter==22.1
# patched version of django-mongoengine-filter to support Django 4.0
# https://github.com/oussjarrousse/django-mongoengine-filter 
# Pull request https://github.com/barseghyanartur/django-mongoengine-filter/pull/16 
django-mongoengine-filter>=0.3.5

The idea in this answer is to add filtering support to django-rest-framework-mongoengine using django-mongoengine-filter that is an replacement or an extension to django-filter and should work the same way as django-filter.

First let's edit the project/settings.py file. Find the INSTALLED_APPS variable and make sure the following "Django apps" are added:

# in settings.py:

INSTALLED_APPS = [
    # ...,
    "rest_framework",
    "rest_framework_mongoengine",
    "django_filters",
    # ...,
]

the app django_filters is required to add classes related to filtering infrastructure, and other things including html templates for DRF.

Then in the variable REST_FRAMEWORK we need to edit the values associated with the key: DEFAULT_FILTER_BACKENDS

# in settings.py:

REST_FRAMEWORK = {
    # ...
    "DEFAULT_FILTER_BACKENDS": [
        "filters.DjangoMongoEngineFilterBackend",
        # ...
    ],
    # ...
}

DjangoMongoEngineFilterBackend is a custom built filter backend that we need to add to the folder (depending on how you structure your project) in the file filters

# in filters.py:
from django_filters.rest_framework.backends import DjangoFilterBackend

class DjangoMongoEngineFilterBackend(DjangoFilterBackend):
    # filterset_base = django_mongoengine_filter.FilterSet
    """
    Patching the DjangoFilterBackend to allow for MongoEngine support
    """

    def get_filterset_class(self, view, queryset=None):
        """
        Return the `FilterSet` class used to filter the queryset.
        """
        filterset_class = getattr(view, "filterset_class", None)
        filterset_fields = getattr(view, "filterset_fields", None)

        if filterset_class:
            filterset_model = filterset_class._meta.model

            # FilterSets do not need to specify a Meta class
            if filterset_model and queryset is not None:
                element = queryset.first()
                if element:
                    queryset_model = element.__class__
                    assert issubclass(
                        queryset_model, filterset_model
                    ), "FilterSet model %s does not match queryset model %s" % (
                        filterset_model,
                        str(queryset_model),
                    )

            return filterset_class

        if filterset_fields and queryset is not None:
            MetaBase = getattr(self.filterset_base, "Meta", object)

            element = queryset.first()
            if element:
                queryset_model = element.__class__
                class AutoFilterSet(self.filterset_base):
                    class Meta(MetaBase):
                        model = queryset_model
                        fields = filterset_fields

            return AutoFilterSet

        return None

This custom filter backend will not raise the exceptions that the original django-filter filter backend would raise. The django-filter DjangoFilterBackend access the key model in QuerySet as in queryset.model, however that key does not exist in MongoEngine.

Maybe making it available in MongoEngine should be considered: https://github.com/MongoEngine/mongoengine/issues/2707 https://github.com/umutbozkurt/django-rest-framework-mongoengine/issues/294

Now we can add a custom filter to the ViewSet:

# in views.py
from rest_framework_mongoengine.viewsets import ModelViewSet

class MyModelViewSet(ModelViewSet):
    serializer_class = MyModelSerializer
    filter_fields = ["a_string_field", "a_boolean_field"]
    filterset_class = MyModelFilter

    def get_queryset(self):
        queryset = MyModel.objects.all()
        return queryset

Finally let's get back to filters.py and add the MyModelFilter

# in filters.py
from django_mongoengine_filter import FilterSet, StringField, BooleanField
class MyModelFilter(FilterSet):
    """
    MyModelFilter is a FilterSet that is designed to work with the django-filter.
    However the original django-mongoengine-filter is outdated and is causing some troubles
    with Django>=4.0.
    """

    class Meta:
        model = MyModel
        fields = [
            "a_string_field",
            "a_boolean_field",
        ]

    a_string_field = StringFilter()
    a_boolean_field = BooleanFilter()

That should do the trick.