Alfred Huang Alfred Huang - 25 days ago 15
reST (reStructuredText) Question

Django Rest Framework OneToOneField related BooleanFilter

I'm using django with rest-framework.

Now I have models with OneToOne relations:

class ModelA(models.Model):
pass

class ModelB(models.Model):
a = models.OneToOneField(
A, related_name='b')


And I have a ViewSet applied on A:

class ModelAViewSet(viewsets.ModelViewSet):
queryset = ModelA.objects.all()
serializer_class = ModelASerializer


Then, I want to add a
BooleanFilter
on
ModelAViewSet
to filter whether the
ModelA
object has a relating
ModelB
object.

And I tried the below:

class ModelAViewSet(viewsets.ModelViewSet):
class Filter(FilterSet):
has_b = filters.BooleanFilter('b')
queryset = ModelA.objects.all()
serializer_class = ModelASerializer
filter_fields = ['has_b']


So, if it works, we got:


  1. ?has_b=1
    : returning all records without filtering;

  2. ?has_b=2
    : returning records which have relating
    ModelA
    object;

  3. ?has_b=3
    : returning records which NO NOT have relating
    ModelA
    object;



It almost worked, except the case 3 above.

So what's the correct way to do this?

Answer
from django_filters.rest_framework import FilterSet, filters

class Filter(FilterSet):
    has_b = filters.BooleanFilter(name='b', lookup_expr='isnull', exclude=True)

There are three changes here:

  1. By importing from the rest_framework sub-package, an API-friendly BooleanWidget is used, so you can query ?has_b=true instead of ?has_b=1.

  2. The filter is using an isnull lookup, which allows you to test for existence of a related model.

  3. b__isnull=True is testing for the lack of a related B, which is the opposite of what you want. Using exclude=True reverses the logic such that you instead filter for As that have a related B instead.

The equivalent .filter() calls:

# before, incorrect
A.objects.filter(b=True)

# after, correct
A.objects.exclude(b__isnull=True)
Comments