Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django filter on values of child objects

Tags:

I have the following (simplified) data model:

class Article(Model):
    uuid = models.CharField(primary_key=True, max_length=128)

class Attribute(Model):
    uuid = models.CharField(primary_key=True, max_length=128)
    article = models.ForeignKey(Article, related_name='attributes')
    type = models.CharField(max_length=256)
    value = models.CharField(max_length=256)

An example usage would be an article with an attribute attached to it with type="brand" and value="Nike". Now I want to write an API which can get all articles with a certain brand, but I can't seem to write the filter for it. This is what I have so far:

class PhotobookFilter(df.FilterSet):
    brand = df.CharFilter(method='filter_brand')

    class Meta:
        model = Article

    def filter_brand(self, queryset, name, value):
        return queryset.filter('order__attributes')

class PhotobookViewSet(AbstractOrderWriterViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticlePhotobookSerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filter_class = PhotobookFilter

The line with queryset.filter is obviously not correct yet. I need to create a filter here that returns all articles that contain an attribute with type="brand" and value=value. How would I do this?

like image 664
physicalattraction Avatar asked Mar 01 '17 08:03

physicalattraction


2 Answers

Are you sure you want to condense both lookups (type and value of Attribute) into one filter? Why not allow filtering on both fields separately?

E.g.

class PhotobookFilter(df.FilterSet):
    type = df.CharFilter(method='filter_type')
    value = df.CharFilter(method='filter_value')

    class Meta:
        model = Article

    def filter_type(self, queryset, name, value):
        return queryset.filter(**{'attributes__type': value}) 

    def filter_value(self, queryset, name, value):
        return queryset.filter(**{'attributes__value': value}) 

And now a query like ?type=brand&value=Nike should work.

Obviously you could condense both conditions into one filter and for example hard code the band part:

class PhotobookFilter(df.FilterSet):
    brand = df.CharFilter(method='filter_brand')

    def filter_brand(self, queryset, name, value):
        return queryset.filter(**{'attributes__type': 'brand', 'attributes__value': value}) 

But keeping them separate feels way more flexible.

like image 163
arie Avatar answered Sep 25 '22 11:09

arie


You could also filter in reverse like this:

class PhotobookFilter(df.FilterSet):
    brand = df.CharFilter(method='filter_brand')

    class Meta:
        model = Article

    def filter_brand(self, queryset, name, value):
        articles = Attribute.objects.filter(type="brand", value=value).values_list('article_id', flat=True)
        return queryset.filter(id__in=articles)

This will create subquery for Attribute, which will still be one sql request in the end

like image 24
Sardorbek Imomaliev Avatar answered Sep 24 '22 11:09

Sardorbek Imomaliev