🍣

[DRF]RetrieveAPIViewがgetリクエストされて、オブジェクトを一意に特定するまでの流れ

2023/02/24に公開

書くこと

  • RetrieveAPIViewがgetリクエストされて、オブジェクトを一意に特定するまでの流れ
  • queryset, get_queryset, get_objectsの内部挙動

利用する技術

  • Django REST Framework
  • rest_framework.generics

想定シチュエーション

以下のようなRetrieveAPIViewがあるとする。

class ArticleView(RetrieveAPIView):
    """Articleの参照APIクラス"""
    queryset = Article.objects.all()

    # 以下をオーバーライドしない想定    
    # def get_queryset(self):
    # def get_object(self):

集合の絞り込み観点

def get_object(self):def get_queryset(self):queryset = Article.objects.all()

参照/呼び出し順序

  1. def get_object(self):
  2. def get_queryset(self):
  3. queryset = Article.objects.all()

内部コードを知る

1.RetrieveAPIView#get

class RetrieveAPIView(mixins.RetrieveModelMixin, GenericAPIView):
    """
    Concrete view for retrieving, updating a model instance.
    """

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

2.RetrieveModelMixin#retrieve

class RetrieveModelMixin:
    def retrieve(self, request, *args, **kwargs):
        # 呼び出し1: get_object()
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

3. GenericAPIView#get_object

class GenericAPIView(views.APIView):
    def get_object(self):
        # 呼び出し2: get_queryset()
        queryset = self.filter_queryset(self.get_queryset())

        # Perform the lookup filtering.
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

        assert lookup_url_kwarg in self.kwargs, (
                'Expected view %s to be called with a URL keyword argument '
                'named "%s". Fix your URL conf, or set the `.lookup_field` '
                'attribute on the view correctly.' %
                (self.__class__.__name__, lookup_url_kwarg)
        )

        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
        obj = get_object_or_404(queryset, **filter_kwargs)

        # May raise a permission denied
        self.check_object_permissions(self.request, obj)

        return obj

4. GenericAPIView#get_queryset

class GenericAPIView(views.APIView):
    def get_queryset(self):
        assert self.queryset is not None, (
                "'%s' should either include a `queryset` attribute, "
                "or override the `get_queryset()` method."
                % self.__class__.__name__
        )

        # 呼び出し3(参照): self.queryset
        queryset = self.queryset
        if isinstance(queryset, QuerySet):
            # Ensure queryset is re-evaluated on each request.
            queryset = queryset.all()
        return queryset

Discussion