简体   繁体   中英

Filtering by Foreign Key in ViewSet, django-rest-framework

I want my api to return certain objects from a database based on the foreign key retrieved from the url path. If my url looks like api/get-club-players/1 I want every player object with matching club id (in this case club.id == 1 ). I'm pasting my code down below:

models.py

class Club(models.Model):
    name = models.CharField(max_length=25)
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True)

    def __str__(self):
        return self.name

class Player(models.Model):
    name = models.CharField(max_length=30)
    club = models.ForeignKey(Club, on_delete=models.SET_NULL, blank=True, null=True)

    
    def __str__(self):
        return self.name

serialziers.py

class ClubSerializer(serializers.ModelSerializer):
    class Meta:
        model = Club
        fields = 'id', 'owner', 'name'

class PlayerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Player
        fields = 'id', 'name', 'offense', 'defence', 'club', 'position'

views.py, This is the part where I get the most trouble with:

class ClubViewSet(viewsets.ModelViewSet):
    queryset = Club.objects.all()
    serializer_class = ClubSerializer

class PlayerViewSet(viewsets.ModelViewSet):
    queryset = Player.objects.all()
    serializer_class = PlayerSerializer

class GetClubPlayersViewSet(viewsets.ViewSet):
    def list(self, request):
        queryset = Player.objects.all()
        serializer = PlayerSerializer(queryset, many=True)
        

    def retrieve(self,request, clubId):
        players = Player.objects.filter(club=clubId, many=True)
        if not players:
            return JsonResponse({'error': "No query found!"})
        else:
            serializer = PlayerSerializer(players)
            return Response(serializer.data)

urls.py

from rest_framework import routers
from django.urls import path, include
from .views import (GameViewSet, PlayerViewSet, ClubViewSet,
 GetClubPlayersViewSet, create_club, set_roster)

router = routers.DefaultRouter()
router.register(r'clubs', ClubViewSet, basename="clubs")
router.register(r'players', PlayerViewSet, basename="players")
router.register(r'get-club-players', GetClubPlayersViewSet, basename="club-players")

urlpatterns = [
    path('', include(router.urls)),
]

EDIT: Now views.py looks like that:

class GetClubPlayersViewSet(viewsets.ViewSet):
    queryset = Player.objects.all()

    def list(self, request):
        serializer = PlayerSerializer(self.queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, *args, **kwargs):
        clubId = kwargs['get-club-players']
        players = Player.objects.filter(club=clubId, many=True)
        if not players:
            return JsonResponse({'error': "No query found!"})
        else:
            serializer = PlayerSerializer(players)
            return Response(serializer.data)

http://127.0.0.1:8000/api/get-club-players/ returns all of the player objects, but when I ad a clubId into url I get this error: 在此处输入图像描述 在此处输入图像描述

EDIT 2 :

class GetClubPlayersViewSet(viewsets.ViewSet):
    queryset = Player.objects.all()
    
    def retrieve(self, request, *args, **kwargs):
        queryParams = self.request.GET.get('abc')
        if queryParams is None:
            queryset = Player.objects.none()
        else:
            queryset = Player.objects.filter(club = queryParams)
            serializer = PlayerSerializer(queryset)
        return Response(serializer.data)
    
    def list(self, request):
        serializer = PlayerSerializer(self.queryset, many=True)
        return Response(serializer.data)

You can get url parameters using kwargs attribute. You will need to modify the signature of your retrieve method for it.

def retrieve(self, request, *args, **kwargs):
    clubId = self.kwargs['get-club-players']
    players = Player.objects.filter(club=clubId, many=True)
    ....

EDIT

For the queryset error, it is due to DRF requiring either the queryset class attribute or implementation of get_queryset() function. In your case, you can get around it like this:

class GetClubPlayersViewSet(viewsets.ViewSet):    
    queryset = Player.objects.all()

    def list(self, request):
        serializer = PlayerSerializer(self.queryset, many=True)

So you can define your queryset like -

def get_queryset(self):
    queryParams == self.request.GET.get('abc') # get queryparameter from url
    if queryParams is None:
        #queryset = anyModel.objects.all()
        queryset = anyModel.objects.none()
    else:
        queryset = anyModel.objects.filter(anyProperty = queryParams)
     return queryset

and your url will be like -

api/get-club-players/?abc=1

this abc can be id or any other property from the model.

Use this get_queryset logic in your retrieve method.

rest_framework.viewsets.ViewSet has an attribute named lookup_field which you can override. By default the value of lookup_field is id .

When adding the viewset in router, the lookup_field is added as the argument name in the url (eg /api/get-club-players/:id/ ).

You can either override the lookup_field of GetClubPlayersViewSet or access the correct kwargs key by changing clubId = kwargs['get-club-players'] to clubId = kwargs['id']

Or a bit of both:

class GetClubPlayersViewSet(viewsets.ViewSet):
    lookup_field = "clubId"
    queryset = Player.objects.all()

    # ....

    def retrieve(self, request, *args, **kwargs):
        clubId = kwargs[self.lookup_field]
        players = Player.objects.filter(club=clubId, many=True)
        if not players:
            return JsonResponse({'error': "No query found!"})
        else:
            serializer = PlayerSerializer(players)
            return Response(serializer.data)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM