２回目の Django Restframework 〜 進化の過程を追え

django restframework ２周め。 [一回目](https://qiita.com/uturned0/items/6ecf37d815304bad86bc) でまったくわからなかったけど、今ならわかりそうな気がする。

### tutorial

https://www.django-rest-framework.org/tutorial/1-serialization/



# 先にまとめ

DRFでは複数のviewがあって、それぞれ使えるmethodが違う。この比較表がいまいち見つからない。
まず、Viewの種類は↓

> APIView, GenericAPIView, various Mixins, and Viewsets

それぞれのviewは継承関係にある。これ大事。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24737/505bcd52-38b1-0d68-21fc-2c2395adf810.png)
> https://www.programmersought.com/article/10293784454/

ざっとした違いは使えるメソッドの違い。

> **APIView** allow us to define functions that match standard HTTP methods like **GET, POST, PUT, PATCH**, etc.
**Viewsets** allow us to define functions that match to common API object actions like : **LIST, CREATE, RETRIEVE, UPDATE**, etc.

後で話すが、Viewsetsでは LIST/Detail のclassをまとめて１つにすることができる。

# メソッドの違い

## APIView / @api_view(['GET', 'POST'])
https://www.django-rest-framework.org/api-guide/views/

これが基本。

```py
.get()
.post()
put()
patch() 
 .delete().
```

## GericAPIView
https://www.django-rest-framework.org/api-guide/generic-views/

APIViewに加えて、以下のメソッドが使えるようだ。違いはなんなんだろう。

```py
perform_create(self, serializer) - Called by CreateModelMixin when saving a new object instance.
perform_update(self, serializer) - Called by UpdateModelMixin when saving an existing object instance.
perform_destroy(self, instance) - Called by DestroyModelMixin when deleting an object instance.
```

GenericAPIViewは、扱うリクエストを制限するために派生したviewが用意されている。

- CreateAPIView
- ListAPIView
- RetrieveAPIView
- DestroyAPIView
- UpdateAPIView
- ListCreateAPIView
- RetrieveUpdateAPIView
- RetrieveDestroyAPIView
- RetrieveUpdateDestroyAPIView




## ViewSets
https://www.django-rest-framework.org/api-guide/viewsets/

もっとも抽象化されたview. 細かいことをするには向いていないようだ。

これは使えない → .get() or .post(), .list() and .create().

```py
class UserViewSet(viewsets.ViewSet):
    def list(self, request):
    def create(self, request):
    def retrieve(self, request, pk=None):
    def update(self, request, pk=None):
    def partial_update(self, request, pk=None):
    def destroy(self, request, pk=None):
```


これを踏まえてtutorialから進化の過程を見ていく。


# serializerの進化

1. `serializers.Serializer` 不便
	- fieldの属性を全部書く
2. `serializers.ModelSerializer` いきなり最終形
	- fieldはmodelのをそのまま使う
	- create/updateは勝手にやる

```py
1. modelだけでなくシリアライザにも全部 field を書く
class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.title = validated_data.get('title', instance.title)
        ...
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

2. Modelから定義をimportする感じ。劇的に短いcode
class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ['id', 'title', 'code', 'linenos', 'language', 'style']    	
```




# viewの進化
djangoのurls.pyを通ると、呼び出される関数にrequestという引数が不思議な力で来るっぽい

1. 関数(引数=request)
	- `if request.method == 'GET':`
	- `serializer = SnippetSerializer()`
	- return JsonResponse()
2. @api_view(['GET', 'POST'])
    - return以外変わらず
    - return が `JsonResponse()` から `Response(serializer.data)` に
    - statusがわかりやすい `return Response(serializer.data, status=status.HTTP_201_CREATED)`
3. class化 - `APIView`
    - classにしたら、 `if GET` のところが `def get()` という関数になった。
    - `    def get(self, request, format=None):`
    - 引数のformatは、json/htmlを帰れるよ的なやつ。使わない
4. classを `mixins` にする
    - `class SnippetList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):`
    - defとreturnの2行になる。 **DB呼ぶ、serializer通す、の行がなくなる。** 

      ```py
	    def get(self, request, *args, **kwargs):
	        return self.list(request, *args, **kwargs)
      ```
5. class = `generics.ListCreateAPIView`, `generics.RetrieveUpdateDestroyAPIView`
	- classの中が２行。劇的に短くなる。もはや理解不能。
	- CRUD全部これだけでいける。DRF使う人の99%はこれがやりたくて使ってる（わたし調べ
	
	  ```py
	    queryset = Snippet.objects.all()
	    serializer_class = SnippetSerializer
	  ```
6. class = read系 `generics.ListAPIView, RetrieveAPIView` 
	- mixins と特に変わらず
7. class = `ListCreateAPIView` のままで、 list なのに、 `perform_create()` で POST受信処理 を書き換え
	- この owner= は、models.ForeignKey として model relation されているもの。
	- relationする場合は引数でForeignKeyに対応するもの **だけ** を渡す
	- ↓は親要素を新たに作っている。子はread onlyだろう。
	
	  ```py
		class SnippetList(generics.ListCreateAPIView):
		    queryset = Snippet.objects.all()
		    serializer_class = SnippetSerializer      <---- この中で Model=Snippet して DB につながっている
		    def perform_create(self, serializer):
		        serializer.save(owner=self.request.user)
	  ```
8. ViewSet 
	- ListとDetailの APIView class が一つになる！
	- retrieve, perform_create, update, partial_updateなど。 get, put はない。
	- **Routerと組み合わせて使う**
	- 





コード 1~


```py
1. 関数基本形。 GET は if　文
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)


2. 関数 + デコレータ基本形 GET は if　文
@api_view(['GET', 'POST'])
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

3. class化 APIView 。 GET/POST は if から関数になる。
class SnippetList(APIView):
    """
    List all snippets, or create a new snippet.
    """
    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

4. class = mixins 短くなる 
class SnippetList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

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

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

5. class = GenericAPIView 劇的に短くなる
class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

6. ListAPIView も同じ
class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

7. GET/POSTを上書き `perform_create` relationする場合は引数でForeignKeyに対応するものを渡す
class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

8. ViewSetはlist/detailのclassがひとつに！
class SnippetViewSet(viewsets.ModelViewSet):
    """
    This viewset automatically provides `list`, `create`, `retrieve`,
    `update` and `destroy` actions.
    """
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)
```



# urls.py の進化
1. `views.snippet_list`
	- viewが関数時代は、関数を直接呼び出す
	- `    path('snippets/', views.snippet_list),`
2. `<class APIView>.as_view()`	
    - viewがclass `APIView` になったら、classに対して `as_view()` を呼ぶ。
    - `    path('snippets/', views.SnippetList.as_view()),`
3. `DefaultRouter()`
	- `router.register()` で登録したらそれを urlpatternsに入れるだけ。

	```
	router = DefaultRouter()
	router.register(r'snippets', views.SnippetViewSet)

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


> Trade-offs between views vs viewsets
Using viewsets can be a really useful abstraction. It helps ensure that URL conventions will be consistent across your API, minimizes the amount of code you need to write, and allows you to concentrate on the interactions and representations your API provides rather than the specifics of the URL conf.
That doesn't mean it's always the right approach to take. There's a similar set of trade-offs to consider as when using class-based views instead of function based views. Using viewsets is less explicit than building your views individually.




うし、明日はpart 5

https://www.django-rest-framework.org/tutorial/5-relationships-and-hyperlinked-apis/