Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django REST framework foreign keys and filtering

I have following models in django app:

models.py:

class Make(BaseModel):
    slug = models.CharField(max_length=32) #alfa-romeo
    name = models.CharField(max_length=32) #Alfa Romeo

    def __unicode__(self):
        return self.name

class Model(BaseModel):
    make = models.ForeignKey(Make)  #Alfa Romeo
    name = models.CharField(max_length=64) # line[2]
    engine_capacity = models.IntegerField()
    trim = models.CharField(max_length=128) # line[4]

And serializers.py:

from .models import Make,Model
from rest_framework import serializers


class MakeSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Make
        fields = ('url', 'slug', 'name')


class ModelSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Model
        fields = ('url', 'make', 'name', 'trim', 'engine_capacity')

and also views.py:

from rest_framework import viewsets
from rest_framework import filters
from rest_framework import generics

from .models import Make, Model
from .serializers import MakeSerializer, ModelSerializer


class MakeViewSet(viewsets.ModelViewSet):
    queryset = Make.objects.all()
    serializer_class = MakeSerializer
    filter_backends = (filters.DjangoFilterBackend,)

class ModelViewSet(viewsets.ModelViewSet):
    make = MakeSerializer
    queryset = Model.objects.all()
    serializer_class = ModelSerializer
    filter_backends = (filters.DjangoFilterBackend,)

What I need to to, I want to fetch all Models manufactured by specific make. How can I get all models with particular make foreign key using query params? And my 2nd question - can I filter results using queryparams to get models with specific engine_capacity?

One comment: It would be perfect, if I can to query results using something like this in url: /api/models/?make=ford where make is slug field in Make model

like image 675
dease Avatar asked Jul 21 '14 09:07

dease


People also ask

What is filter in Django REST framework?

Prerequisite: Adding Filtering in APIs – Django REST Framework [lin needed article on published yet] Django filters facilitate filtering the queryset to retrieve the relevant results based on the values assigned to the filter fields.

What is a foreign key in Django?

What is ForeignKey in Django? ForeignKey is a Field (which represents a column in a database table), and it’s used to create many-to-one relationships within tables. It’s a standard practice in relational databases to connect data using ForeignKeys. What’s the difference between foreign key and primary key?

Is it possible to explicitly name the filter argument in Django?

This is nice, but it exposes the Django's double underscore convention as part of the API. If you instead want to explicitly name the filter argument you can instead explicitly include it on the FilterSet class:

Are there any third party filters available for Django?

The following third party packages provide additional filter implementations. The django-rest-framework-chain package works together with the DjangoFilterBackend class, and allows you to easily create filters across relationships, or create multiple filter lookup types for a given field.


4 Answers

You can specify filter_fields = ('make__slug', ) in your view set. Don't forget to include filter_backends = (DjangoFilterBackend, ) as well. Also you will need to add django-filter dependency.

class ModelViewSet(viewsets.ModelViewSet):
    queryset = Model.objects.all()
    serializer_class = ModelSerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filter_fields = ('make__slug',)

Then you query like /api/models/?make__slug=ford. Note double underscore symbol.

Docs.

If you don't like make__slug keyword argument in the URL, then you can create a filter class:

import django_filters

from myapp.models import Make


class ModelFilter(django_filters.FilterSet):
    make = django_filters.ModelChoiceFilter(field_name="make__slug",
                                            queryset=Make.objects.all())

    class Meta:
        model = Model
        fields = ('make',)

and then

class ModelViewSet(viewsets.ModelViewSet):
    make = MakeSerializer
    queryset = Model.objects.all()
    serializer_class = ModelSerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filter_class = ModelFilter

/api/models/?make=ford should work.

like image 160
Vladimir Prudnikov Avatar answered Oct 21 '22 11:10

Vladimir Prudnikov


urls.py

url('^model/by/(?P<make>\w+)/$', ModelByMakerList.as_view()),

views.py

class ModelByMakerList(generics.ListAPIView):
    serializer_class = ModelSerializer

    def get_queryset(self):
        """
        This view should return a list of all models by
        the maker passed in the URL
        """
        maker = self.kwargs['make']
        return Model.objects.filter(make=maker)

For more info checkout the docs.

You can also use filtering with QUERY_PARAMS, but IMHO this looks better.

like image 35
danielcorreia Avatar answered Oct 21 '22 13:10

danielcorreia


To expand on @vladimir-prudnikov's answer:

Things changed a bit in recent versions of django-filter. You probably want:

class ModelFilter(django_filters.FilterSet):
    make = django_filters.ModelChoiceFilter(field_name='make__slug',
                                            to_field_name='slug',
                                            queryset=Make.objects.all())

    class Meta:
        model = Model
        fields = ('make',)

See https://django-filter.readthedocs.io/en/master/ref/filters.html#field-name and https://django-filter.readthedocs.io/en/master/ref/filters.html#to-field-name

like image 3
felixhummel Avatar answered Oct 21 '22 13:10

felixhummel


What you need to do in your view is something like this: It is called "Lookups that span relationships"

queryset = Model.objects.filter(make__name__exact='Alfa Romeo')

the filtering of models with specific engine capacity is similar

queryset = Model.objects.filter(engine_capacity__exact=5)

if you want both filters combined, you can chain them:

queryset = Model.objects.filter(make__name__exact='Alfa Romeo').filter(engine_capacity__exact=5)

more examples can be found here django query making

like image 1
AHaberl Avatar answered Oct 21 '22 13:10

AHaberl