I am trying to filter queryset for Many To Many Relationship. But its not working. I am trying to get objects of TestTag document with keyword name.
models :
class TestKeyword(Document):
name = StringField(required=True)
class TestTag(Document):
tag = StringField(max_length=100, null=True)
keywords = ListField(ReferenceField(TestKeyword), null=True)
Filter:
import django_mongoengine_filter as filters
from app.models import TestTag
class TestTagFilter(filters.FilterSet):
class Meta:
model = TestTag
fields = ['tag', 'keywords__name']
class TestTag(ModelViewSet):
queryset = TestTag.objects.all()
serializer_class = TestTagSerializer
# override filter_queryset function
def filter_queryset(self, queryset):
filter = TestTagFilter(self.request.query_params, queryset=queryset)
return filter.qs
A general solution that worked for me is described bellow:
First make sure you have the following packages in your environment:
Django
djangorestframework
django-rest-framework-mongoengine
mongoengine
django-filter
# 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 or download the original if you are using Django 3.x
django-mongoengine-filter
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.