Noronha
Noronha

Reputation: 89

Django FilterSet AND(?) condition

I am creating an application where food has several components, and each component has a specific value. I want to be able to filter food for the value of a specific component.

Here are my models:

class Food(models.Model):
    id = models.AutoField(primary_key=True, db_column="id")
    name = models.CharField(max_length=150)
    product_type = models.CharField(max_length=150)

class Component(models.Model):

    id = models.AutoField(primary_key=True, db_column="id")
    name = models.CharField(max_length=150)
    value = models.FloatField()
    food = models.ForeignKey(Food, related_name='food_components', related_query_name='food_components')

Then I define filter sets and the view

import rest_framework_filters as filters

class ComponentFilter(filters.FilterSet):

    name = filters.CharFilter(name="name")
    val = filters.NumberFilter(name="value", distinct=True)
    minval = filters.NumberFilter(name="value", lookup_type="gte", distinct=True)
    maxval = filters.NumberFilter(name="value", lookup_type="lte", distinct=True)

    class Meta:
        model = Component

class FoodFilter(filters.FilterSet):

    name = filters.CharFilter(name='name')
    product_type = filters.CharFilter(name='product_type')
    components = filters.RelatedFilter(ComponentFilter, name='food_components')

    class Meta:
        model = Food

class FoodViewSet(viewsets.ModelViewSet):

    queryset = Food.objects.all()
    serializer_class = FoodSerializer
    filter_class = FoodFilter

I want to be able to filter a viewset using more than one field in a related model (combining both parameters). Something like http://whatever/foods/components__name=X&components__value=1 would get me all foods with the element X of value 1. I am using django-rest-framework-filters (https://github.com/philipn/django-rest-framework-filters)

From what I see, since all foods have all the elements, the components__name is irrelevant, and I will get all the foods that have any component with the value 1. How can I combine both?

Thanks in advance!

Upvotes: 3

Views: 1467

Answers (1)

Ross Rogers
Ross Rogers

Reputation: 24228

You'll need to create a new filter that handles the name, val pair on a single Food object that is related to a given Component:

class FoodComponentFilter(django_filters.Filter):
    def filter(self, qs, value):
        strs = value.split(',')
        if len(strs) != 2:
            raise Exception
        return qs.filter(
            food_components__in = Component.objects.filter(
                name=strs[0],
                value=float(strs[1])
            )
        )

Instantiate this in the FoodFilter:

class FoodFilter(filters.FilterSet):
   component_food_pair = FoodComponentFilter(name='dummy_field')
   [...]
   class Meta:
        model = Food
        fields = ('component_food_pair',[...])

Then, use it like:

?component_food_pair=Foo,42.0

Upvotes: 4

Related Questions