Django REST Frameworkの動作確認 QuerySetのfilterを試す

https://programming-gogogogo.hatenablog.com/entry/2023/03/24/054507?_ga=2.11575732.1429117217.1679603968-1930150346.1678444632

上記の記事の続きです。

今回はQuerySetで用意されているAPIを動作確認してみたいと思います。

https://docs.djangoproject.com/en/4.1/ref/models/querysets/

先にデータを用意したいので以下のloop.shスクリプトを作成しました。

#!/bin/bash

for i in `seq 1 20`; do
    echo $i
    username="testuser$i"
    age="$i"
    echo $username
    echo $age
    curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"$username\", \"age\":$age}" http://localhost:8000/blog/user_viewset/
done

sh script/loop.shで実行するとユーザーのデータを作成することができました。

sh script/list.sh
[{"id":5,"name":"testuser1","age":1},{"id":6,"name":"testuser2","age":2},{"id":7,"name":"testuser3","age":3},{"id":8,"name":"testuser4","age":4},{"id":9,"name":"testuser5","age":5},{"id":10,"name":"testuser6","age":6},{"id":11,"name":"testuser7","age":7},{"id":12,"name":"testuser8","age":8},{"id":13,"name":"testuser9","age":9},{"id":14,"name":"testuser10","age":10},{"id":15,"name":"testuser11","age":11},{"id":16,"name":"testuser12","age":12},{"id":17,"name":"testuser13","age":13},{"id":18,"name":"testuser14","age":14},{"id":19,"name":"testuser15","age":15},{"id":20,"name":"testuser16","age":16},{"id":21,"name":"testuser17","age":17},{"id":22,"name":"testuser18","age":18},{"id":23,"name":"testuser19","age":19},{"id":24,"name":"testuser20","age":20}]

これまでにidを指定した1件のデータ取得の動作確認を行なっていなかったのでスクリプトを追加してみます。

touch script/retrieve.sh

retrieve.shは以下の内容にします。

#!/bin/bash

if [ $# -eq 0 ]; then
  echo "please specify id."
  exit 1
fi

curl -H "Content-Type: application/json" http://localhost:8000/blog/user_viewset/$1/

以下のようにデータが取得できました。

sh script/retrieve.sh 5
{"id":5,"name":"testuser1","age":1}

更新処理も確認するためのスクリプトを作成します。

touch script/update.sh

update.shは以下のようにしました。

#!/bin/bash

if [ $# -eq 0 ]; then
  echo "please specify id."
  exit 1
fi

curl -X PUT -H "Content-Type: application/json" -d '{"name":"testuser1updated", "age":"100"}' http://localhost:8000/blog/user_viewset/$1/

id5のtestuser1でupdate処理ができることを確認しました。

sh script/update.sh 5
{"id":5,"name":"testuser1updated","age":100}

sh script/retrieve.sh 5
{"id":5,"name":"testuser1updated","age":100}

次にQuerySetをフィルタリングできるか確認してみます。

Django REST Frameworkでは公式ドキュメントは下記だと思います。 https://www.django-rest-framework.org/api-guide/filtering/

The default behavior of REST framework's generic list views is to return the entire queryset for a model manager. Often you will want your API to restrict the items that are returned by the queryset.

REST frameworkのgeneric list viewのデフォルトの挙動はmodel managerの全体のquerysetを返すことだが、返される値を制御したい場合もあるでしょう、的な感じに解釈しました。

The simplest way to filter the queryset of any view that subclasses GenericAPIView is to override the .get_queryset() method.

Overriding this method allows you to customize the queryset returned by the view in a number of different ways.

GenericAPIViewのサブクラスのquerysetをフィルタリングするシンプルな方法はget_queryset()メソッドをoverrideすることです。このメソッドをorverrideすることでviewが返すquerysetをカスタマイズできます、的な感じに解釈しました。

ドキュメントに書かれているquery parameterを使ってフィルタリングするためのサンプルコードは下記です。

https://www.django-rest-framework.org/api-guide/filtering/#filtering-against-query-parameters

class PurchaseList(generics.ListAPIView):
    serializer_class = PurchaseSerializer

    def get_queryset(self):
        """
        Optionally restricts the returned purchases to a given user,
        by filtering against a `username` query parameter in the URL.
        """
        queryset = Purchase.objects.all()
        username = self.request.query_params.get('username')
        if username is not None:
            queryset = queryset.filter(purchaser__username=username)
        return queryset

まずは真似してコードを書いてみます。 フィルタリングする処理を書くためのファイルを新規に作成しました。

touch drfproject/blog/filter_view.py

filter_view.pyを以下のようにしました。

from .models import User
from .serializers import UserSerializer
from rest_framework import generics


class FilteredUsersList(generics.ListAPIView):
    serializer_class = UserSerializer

    def get_queryset(self):
        queryset = User.objects.all()
        username = self.request.query_params.get('name')
        if username is not None:
            queryset = queryset.filter(name=username)
        return queryset

上記コードを書く際、以下の画像のようにVSCodeで補完が動作していました。

drfproject/blog/urls.pyは以下のように編集しサーバーを起動することができました。 FilteredUsersListはgenerics.ListAPIViewを継承しているのでas_view()でpath()に渡すことができるみたいです。

from django.urls import path, include
from blog import views
from .viewsets import UserViewSet
from .filter_view import FilteredUsersList
from rest_framework.routers import DefaultRouter


router = DefaultRouter()
router.register(r'user_viewset', UserViewSet, basename='user_viewset')

urlpatterns = [
    path('', include(router.urls)),
    path('users_list', views.users_list),
    path('filter_view', FilteredUsersList.as_view())
]

filter_viewにリクエストを送るためのスクリプトを作成します。

touch script/filter_view.sh

filter_view.shは以下のようにしました。

#!/bin/bash

if [ $# -eq 0 ]; then
  echo "please specify name."
  exit 1
fi

curl -H "Content-Type: application/json" http://localhost:8000/blog/filter_view?name=$1

以下のようにフィルタリングされたQuerySetが返ってくることが確認できました。

sh ../script/filter_view.sh  testuser
3
[{"id":7,"name":"testuser3","age":3}]

filter以外にもいろいろQuerySetのAPIがあるようです。 https://docs.djangoproject.com/en/4.1/ref/models/querysets/#annotate

試しにexclude()を使ってみます。 https://docs.djangoproject.com/en/4.1/ref/models/querysets/#exclude

filter_view.pyを以下のように編集しました。

from .models import User
from .serializers import UserSerializer
from rest_framework import generics


class FilteredUsersList(generics.ListAPIView):
    serializer_class = UserSerializer

    def get_queryset(self):
        queryset = User.objects.all()
        username = self.request.query_params.get('name')
        if username is not None:
            queryset = queryset.exclude(name=username)
        return queryset

再度リクエストを投げるとnameがtestuser3以外のものが取得できることが確認できました。

sh ../script/filter_view.sh  testuser3
[{"id":5,"name":"testuser1updated","age":100},{"id":6,"name":"testuser2","age":2},{"id":8,"name":"testuser4","age":4},{"id":9,"name":"testuser5","age":5},{"id":10,"name":"testuser6","age":6},{"id":11,"name":"testuser7","age":7},{"id":12,"name":"testuser8","age":8},{"id":13,"name":"testuser9","age":9},{"id":14,"name":"testuser10","age":10},{"id":15,"name":"testuser11","age":11},{"id":16,"name":"testuser12","age":12},{"id":17,"name":"testuser13","age":13},{"id":18,"name":"testuser14","age":14},{"id":19,"name":"testuser15","age":15},{"id":20,"name":"testuser16","age":16},{"id":21,"name":"testuser17","age":17},{"id":22,"name":"testuser18","age":18},{"id":23,"name":"testuser19","age":19},{"id":24,"name":"testuser20","age":20}]