Helmi
Helmi

Reputation: 539

Django Rest Framework: Respond with 404 if no result found

With the help of some others here I managed to get my DRF driven API to a good point to work with. I'm now trying to mingle out some details. Currently.

Here's the view:

class EpisodeViewSet(viewsets.ModelViewSet):
    queryset = Episode.objects.all().order_by('-published_at')
    filter_backends = (DjangoFilterBackend,)
    filter_fields = ('show_id', 'number')

    def get_serializer_class(self):
        if self.action == 'retrieve':
            return EpisodeDetailSerializer
        if self.request.GET.get('show_id') and self.request.GET.get('number'):
            return EpisodeDetailSerializer
        return EpisodeSerializer

and here's my serializer:

class EpisodeDetailSerializer(serializers.ModelSerializer):
    chapters = ChapterMarkSerializer(source='chaptermark_set', many=True)
    media = MediaClipSerializer(source='mediaclip_set', many=True)
    show = ShowSerializer()
    guest = GuestSerializer(source='guest_set', many=True, read_only=True)
    topic = TopicSerializer(source='topic_set', many=True, read_only=True)

    # def get_queryset(self):
    #    show = self.request.query_params.get('show')
    #    number = self.request.query_params.get('number')
    #    queryset = self.objects.get(show_id=show, number=number)
    #    return queryset

    class Meta:
        model = Episode
        fields = ('id', 'title', 'number', 'subtitle', 'show', 'published_at', 'updated_at', 'description_title',
                  'description', 'show_notes', 'supporters_title', 'supporters_text', 'supporters_button',
                  'forum_title', 'forum_text', 'forum_link', 'cover_image', 'updated_at', 'chapters', 'media',
                  'guest', 'topic')
        depth = 1

I'm not sure anymore where the get_queryset function came from but the results weirdly are the same with and without, that's why it's commented out at the moment.

Background is I'm querying episodes in two different ways:

  1. through their primary key api/episodes/123
  2. through a filter by show and number api/episodes?show_id=1&number=2

In the first case the api returns one object if found or a 404 with an object only containing

{
    "detail": "Not found."
}

in case an episode with that ID doesn't exist.

In the second case it returns a list of objects (sidenote: I'd prefer to also only get one object as there will never be more than one result but that's probably a different topic). and it returns an empty result still with HTTP 200 if nothing is found.

For cleaner handling on the frontend side, I'd prefer to raise an HTTP 404 if nothing was found in both cases. I already found that this may work with a try/except in Django but I didn't yet succeed in finding where to place it. I'M still only scratching the surface with DRF.

Any help is appreciated.

Upvotes: 3

Views: 6296

Answers (2)

neverwalkaloner
neverwalkaloner

Reputation: 47364

You can override viewset's list method for this:

from rest_framework import status
from rest_framework.response import Response

def list(self, request, *args, **kwargs):
    queryset = self.filter_queryset(self.get_queryset())
    if not queryset.exists():
        return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)

    page = self.paginate_queryset(queryset)
    if page is not None:
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)

    serializer = self.get_serializer(queryset, many=True)
    return Response(serializer.data)

Upvotes: 4

eran
eran

Reputation: 6921

I think the easiest way for you (and maybe the most general) will be to override the list() method of ModelViewSet. This is the code (from mixins.py):

def list(self, request, *args, **kwargs):
    queryset = self.filter_queryset(self.get_queryset())

    page = self.paginate_queryset(queryset)
    if page is not None:
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)

    serializer = self.get_serializer(queryset, many=True)
    return Response(serializer.data)

Above is the original code. You can then override it in your class, for example, you can add the following lines (after queryset = and before page =)

  ...
 if queryset.count():
    raise exceptions.NotFound()
 ...

Actually, you can do what ever you want now. You can also change the function to return single object.

Upvotes: 4

Related Questions