[DRF]apiの作成（get）part2

# django-rest-frameworkの勉強中
[前回の記事]()の続きです。
drfでgetのAPIを作成をしたので、今回は

- listでの取得
- 汎用APIViewを使用しての取得

を行います。

Model情報は以下のとおりです。

``` python:models.py
from django.db import models

DISTRICT_CATEGORIES = [
    (1, "地区1"),
    (2, "地区2"),
    (3, "地区3"),
    (4, "地区4"),
]


class Student(models.Model):
    """生徒情報"""

    # 生徒ID
    student_id = models.CharField(max_length=4, primary_key=True)

    # クラス
    class_no = models.CharField(max_length=1)

    # 出席番号
    attendance_no = models.IntegerField()

    # 名前
    name = models.CharField(max_length=20)

    # 地区番号
    district_no = models.CharField(max_length=1, choices=DISTRICT_CATEGORIES)

    # フリーコメント
    comment = models.CharField(max_length=200,blank=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["class_no", "attendance_no"],
                name="class_attendance_unique"
            ),
        ]
```



|STUDENT ID|CLASS NO|ATTENDANCE NO|NAME|DISTRICT NO|COMMENT|
|---|---|---|---|---|---|
|0005|2|2|fugafuga|地区1|２組２番|
|0004|2|1|hogehoge|地区2|２組１番|
|0003|1|3|山田３番|地区4|１組３番|
|0002|1|2|山田花子|地区2|１組２番|
|0001|1|1|山田太郎|地区1|１組１番|

# 一覧の取得をします
前回は一件だけの取得だったので、次は一覧の取得をします。
たとえば、クラスの生徒全員を返すようなAPIとしましょうか。

## Serializer
ひとまず前回と同じでいきましょう。

```python:serializers.py
class StudentSerializer(serializers.ModelSerializer):
    """Studentクラスのシリアライザ"""

    class Meta:
        model = Student
        fields = '__all__'
```

## view
```python:views.py
class StudentListAPIView(APIView):

    def get(self, request, class_no, *args, **kwargs):
        """Student一覧をclass_noで取得"""

        # モデルのオブジェクト取得
        queryset = Student.objects.filter(class_no=class_no)

        # シリアライズ
        serializer = StudentSerializer(instance=queryset, many=True)

        # 返却
        return Response(serializer.data, status.HTTP_200_OK)
```

## url
```python:urls.py
path('<class_no>/', views.StudentListAPIView.as_view())
```

## 動かしてみました
`http://127.0.0.1:8000/student/1`

> [{"student_id":"0001","class_no":"1","attendance_no":1,"name":"山田太郎","district_no":"1","comment":"１組１番"},{"student_id":"0002","class_no":"1","attendance_no":2,"name":"山田花子","district_no":"2","comment":"１組２番"},{"student_id":"0003","class_no":"1","attendance_no":3,"name":"山田３番","district_no":"4","comment":"１組３番"}]

できましたね！
重要なところはViewsのなかでシリアライズをするときに`many=True`のオプションをつけるところでしょうか。また、リストの場合はModelSerializerでなくてListSerializerでシリアライズすることも可能そうです。

ちなみに条件によるフィルタリングについてだいぶ調べたんですが、`django-filter`というライブラリが良く見つかりました。今回、class_noで検索条件を書いていますが、クエリパラメータにより動的に検索条件を決めたいときとかは`django-filter`を使うと良さそうですね。
`http://127.0.0.1:8000/student/?attendance_no=2`
こんな感じに書くと検索条件が動的に出席番号になるイメージ。

## ListSerializerバージョン
一応やっておきます。更新系はこっち使っといたほうが良いらしい・・・？更新のときにまた調べようと思います。

### Serializer

```python:serializers.py
class StudentListSerializer(serializers.ListSerializer):
    child = StudentSerializer()
```

### view

シリアライズする部分が変わってます。

```diff_python:views.py
# シリアライズ
- serializer = StudentSerializer(instance=queryset, many=True)
+ serializer = StudentListSerializer(instance=queryset)
```

### 実行

> [{"student_id":"0001","class_no":"1","attendance_no":1,"name":"山田太郎","district_no":"1","comment":"１組１番"},{"student_id":"0002","class_no":"1","attendance_no":2,"name":"山田花子","district_no":"2","comment":"１組２番"},{"student_id":"0003","class_no":"1","attendance_no":3,"name":"山田３番","district_no":"4","comment":"１組３番"}]

できました。

# 汎用APIViewを用いる
汎用APIViewを用いると、getのAPIがもっと簡単に記述できそうです。
APIの用途に合わせてクラスを継承したViewを作るともっとコード量が少なく書けそうです。
例えば一覧ではなく一件だけ取得するようなAPIを考えてみます。RetrieveAPIViewを使ってみましょう。

## url

```python:urls.py
path('<pk>/retrieve/', views.StudentRetrieveAPIView.as_view())
```

## view

```python:views.py
from rest_framework import generics

from .models import Student
from .serializers import StudentSerializer


class StudentRetrieveAPIView(generics.RetrieveAPIView):
    queryset = Student.objects.all()
    serializer_class = StudentSerializer
```

## 実行

> {"student_id":"0001","class_no":"1","attendance_no":1,"name":"山田太郎","district_no":"1","comment":"１組１番"}

できました。

えっこれだけ！？
viewの実装が意味わからないくらい少なくなってる・・・。

```diff_python:views.py
- from django.shortcuts import get_object_or_404
- from rest_framework import status
- from rest_framework.views import APIView
- from rest_framework.response import Response
+ from rest_framework import generics

from .models import Student
from .serializers import StudentSerializer


- class StudentRetrieveAPIView(APIView):
+ class StudentRetrieveAPIView(generics.RetrieveAPIView):

-     def get(self, request, pk, *args, **kwargs):
-         """Studentをpkで取得"""
-         # モデルのオブジェクト取得
-         instance = get_object_or_404(Student, pk=pk)
-         # シリアライズ
-         serializer = StudentSerializer(instance)
-         # 返却
-         return Response(serializer.data, status.HTTP_200_OK)

+         queryset = Student.objects.all()
+         serializer_class = StudentSerializer
```

コード量がすごく少なくなりましたね。。
RetrieveAPIViewでいろいろとやってくれてるって感じですね。pkでの取得も裏でやってくれていそうです。url.pyでpkと書いてあるところを勝手に読み込んでくれています。
RetrieveAPIViewで継承している`RetrieveModelMixin`、`GenericAPIView`がいろいろやってくれていそうです。まぁ確かに取得してシリアライズするだけだから汎用的な処理に落とし込めるやろってことですね。

## RetrieveAPIViewで複数条件検索
今度はpkではなくclass_no、attendance_noから検索をしてみます。

### url

```python:url.py
path('<class_no>/<int:attendance_no>/retrieve/', views.StudentRetrieveAPIView.as_view())
```

### view

```python:view.py
class StudentRetrieveAPIView(generics.RetrieveAPIView):

    serializer_class = StudentSerializer
    queryset = Student.objects.all()

    def retrieve(self, request, class_no, attendance_no, *args, **kwargs):
        instance = get_object_or_404(self.queryset, class_no=class_no, attendance_no=attendance_no)
        serializer = StudentSerializer(instance)

        return Response(serializer.data)
```

今回は、検索条件がpkではないので独自の実装が必要になります。
RetrieveViewのアクションメソッドであるretrieveをオーバーライドして実装しています。
（ハンドラメソッド・・・get、アクションメソッド・・・retrieve）

汎用APIViewを用いるときはクラスフィールドに`serializer_class`と`queryset`という変数が必要になってくるようです。

### 実行
> {"student_id":"0002","class_no":"1","attendance_no":2,"name":"山田花子","district_no":"2","comment":"１組２番"}

無事取得できました。

### うーん・・・
なかなか難しい・・・というか理解がしづらい。
例えば`queryset = Student.objects.all()`のところ。最初に全件とってきてるけど、今回したいのってフィルタリングだから全件とかいらないんだけどって思って調べました。
→queryの遅延評価というものがあるようです。実際にORMでSQLが流れるのは、queryの値を使うときだけのよう。今度記事にしよっと。

あとは
model.object.all()とか、model.object.filter(xx=xx)で戻ってくるのはqueryset型で、それはserializeできない（リストのときにやるやつ）とか、
serializeするときはget_objectで取得（retrieveのとき）とか、いろいろ気にすることはありそうですね。

とりあえずgetはここまでにしようかな。
次はpostあたりをやろうと思います。