django restframework 2周め。 一回目 でまったくわからなかったけど、今ならわかりそうな気がする。
tutorial
先にまとめ
DRFでは複数のviewがあって、それぞれ使えるmethodが違う。この比較表がいまいち見つからない。
まず、Viewの種類は↓
APIView, GenericAPIView, various Mixins, and Viewsets
それぞれのviewは継承関係にある。これ大事。
ざっとした違いは使えるメソッドの違い。
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の進化
-
serializers.Serializer
不便- fieldの属性を全部書く
-
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という引数が不思議な力で来るっぽい
- 関数(引数=request)
if request.method == 'GET':
serializer = SnippetSerializer()
- return JsonResponse()
-
@api_view(['GET', 'POST'])
- return以外変わらず
- return が
JsonResponse()
からResponse(serializer.data)
に - statusがわかりやすい
return Response(serializer.data, status=status.HTTP_201_CREATED)
- class化 -
APIView
- classにしたら、
if GET
のところがdef get()
という関数になった。 def get(self, request, format=None):
- 引数のformatは、json/htmlを帰れるよ的なやつ。使わない
- classにしたら、
- 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)
-
- class =
generics.ListCreateAPIView
,generics.RetrieveUpdateDestroyAPIView
-
classの中が2行。劇的に短くなる。もはや理解不能。
-
CRUD全部これだけでいける。DRF使う人の99%はこれがやりたくて使ってる(わたし調べ
queryset = Snippet.objects.all() serializer_class = SnippetSerializer
-
- class = read系
generics.ListAPIView, RetrieveAPIView
- mixins と特に変わらず
- 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)
-
- 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 の進化
-
views.snippet_list
- viewが関数時代は、関数を直接呼び出す
path('snippets/', views.snippet_list),
-
<class APIView>.as_view()
- viewがclass
APIView
になったら、classに対してas_view()
を呼ぶ。 path('snippets/', views.SnippetList.as_view()),
- viewがclass
-
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