LoginSignup
6
8

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-07-30

django restframework 2周め。 一回目 でまったくわからなかったけど、今ならわかりそうな気がする。

tutorial

先にまとめ

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

APIView, GenericAPIView, various Mixins, and Viewsets

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

image.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をまとめて1つにすることができる。

メソッドの違い

APIView / @api_view(['GET', 'POST'])

これが基本。

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

GericAPIView

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

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

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

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

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は勝手にやる
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通す、の行がなくなる。
        def get(self, request, *args, **kwargs):
            return self.list(request, *args, **kwargs)
    
  5. class = generics.ListCreateAPIView, generics.RetrieveUpdateDestroyAPIView

    • classの中が2行。劇的に短くなる。もはや理解不能。
    • CRUD全部これだけでいける。DRF使う人の99%はこれがやりたくて使ってる(わたし調べ
        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だろう。
        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~

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

6
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
8