- Python 3.11.4 / Django 4.2.4
目次
チュートリアル②:Requests and Responses
- Request objects RESTフレームワークは、通常のHttpRequestを拡張するRequestオブジェクトを導入していて、そして、より柔軟なリクエストの解析を提供する。Requestオブジェクトの核となる機能は、request.data属性で、これはrequest.POSTに似ているが、Web APIを使用する際にはより便利。
request.POST # Only handles form data. Only works for 'POST' method. request.data # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods.
- Response objects RESTフレームワークはまた、Responseオブジェクトも導入している。これは未レンダリングのコンテンツを取り、コンテンツネゴシエーションを使用してクライアントに返す正しいコンテンツタイプを決定する種類のTemplateResponse。
return Response(data) # クライアントが要求したコンテンツタイプとしてレンダリングする。
- Status codes ビューで数値のHTTPステータスコードを使用すると、常に明確に読み取れるわけではなく、エラーコードを間違えた場合に気づきにくいことがある。RESTフレームワークは、statusモジュールのHTTP_400_BAD_REQUESTのように、各ステータスコードのためのより明確な識別子を提供している。数値の識別子を使用するのではなく、これらを使用するほうが良い!
- Wrapping API views REST framework provides two wrappers you can use to write API views.
- The
@api_view
decorator for working with function based views. - The
APIView
class for working with class-based views.
Request
instances in your view, and adding context toResponse
objects so that content negotiation (Webサーバーとクライアント間で最も適切なコンテンツの表現や形式を決定するプロセス) can be performed. また、ラッパーは適切な場合に**405 Method Not Allowed
のレスポンスを返すといった動作や、不正な入力でrequest.data
にアクセスした際に発生するParseError
**例外の処理も提供する。 - The
- Pulling it all together Let’s go ahead and start using these new components to refactor our views slightly.
views.py
を以下のコードに書き換える。from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response from snippets.models import Snippet from snippets.serializers import SnippetSerializer @api_view(['GET', 'POST']) # 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)
Our instance view is an improvement over the previous example. It’s a little more concise, and the code now feels very similar to if we were working with the Forms API. We’re also using named status codes, which makes the response meanings more obvious. Here is the view for an individual snippet, in theviews.py
module.@api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ try: # 例外処理の開始を示す。 snippet = Snippet.objects.get(pk=pk) # データベースから指定されたプライマリーキーpkを持つSnippetオブジェクトを取得する。 except Snippet.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = SnippetSerializer(snippet) # 取得したSnippetオブジェクトをシリアル化する。 return Response(serializer.data) # シリアル化されたデータをResponseオブジェクトに滞納し返す。 elif request.method == 'PUT': serializer = SnippetSerializer(snippet, data=request.data) # 既存のSnippetオブジェクトに、送られてきた新しいデータ(request.data)を適用してシリアル化する。 if serializer.is_valid(): # シリアライザーがデータを正しく検証した場合の処理を開始する。 serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': snippet.delete() # データベースから指定されたSnippetオブジェクトを削除する。 return Response(status=status.HTTP_204_NO_CONTENT) # スニペットの削除に成功した場合、204 No Contentを返す。
This should all feel very familiar – it is not a lot different from working with regular Django views. 私たちはもう明示的にリクエストやレスポンスを特定のコンテンツタイプに結びつけていない。**request.data
**は、入ってくるJSONリクエストを処理できるだけでなく、他のフォーマットも処理できる。同様に、私たちはデータを持つレスポンスオブジェクトを返しているが、RESTフレームワークに正しいコンテンツタイプでレスポンスをレンダリングさせている。 - Adding optional format suffixes to our URLs レスポンスが単一のコンテンツタイプにハードワイヤードされていないという事実を利用するために、APIエンドポイントにフォーマットサフィックスのサポートを追加する。フォーマットサフィックスは、APIのエンドポイントの末尾に特定のフォーマット(例:.json)を追加することを指し、これを使用すると、APIがさまざまなフォーマットのデータを返すことができるようになる。例えば、**http://example.com/api/items/4.json** というURLは、アイテムの情報をJSON形式で要求していることを意味する。これにより、必要なデータ形式を簡単に指定して取得することができる。 Start by adding a
format
keyword argument to both of the views, like so.def snippet_list(request, format=None):
anddef snippet_detail(request, pk, format=None):
formatキーワード引数をAPIビュー関数に追加することで、クライアントが特定のレスポンスフォーマットを要求できるようになる。具体的には、URLのサフィックス(.jsonや.xml)を使って要求されるデータのフォーマットを指定できるようになる。 Now update thesnippets/urls.py
file slightly, to append a set offormat_suffix_patterns
in addition to the existing URLs.from django.urls import path from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets/<int:pk>/', views.snippet_detail), ] urlpatterns = format_suffix_patterns(urlpatterns)
これらの追加のURLパターンを必ずしも追加する必要はないが、特定のフォーマットを指定するためのシンプルでクリーンな方法を提供してくれる。 - How’s it looking? チュートリアル①で行ったように、コマンドラインからAPIをテストしてみると、すべてがかなり似たように動作しているが、無効なリクエストを送信した場合のエラーハンドリングがより良くなっている。 We can get a list of all of the snippets, as before.
http <http://127.0.0.1:8000/snippets/> HTTP/1.1 200 OK ... [ { "id": 1, "title": "", "code": "foo = \\"bar\\"\\n", "linenos": false, "language": "python", "style": "friendly" }, { "id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly" } ]
We can control the format of the response that we get back, either by using theAccept
header:http <http://127.0.0.1:8000/snippets/> Accept:application/json # Request JSON http <http://127.0.0.1:8000/snippets/> Accept:text/html # Request HTML
Or by appending a format suffix:http <http://127.0.0.1:8000/snippets.json> # JSON suffix http <http://127.0.0.1:8000/snippets.api> # Browsable API suffix
Similarly, we can control the format of the request that we send, using theContent-Type
header.# POST using form data http --form POST <http://127.0.0.1:8000/snippets/> code="print(123)" { "id": 3, "title": "", "code": "print(123)", "linenos": false, "language": "python", "style": "friendly" } # POST using JSON http --json POST <http://127.0.0.1:8000/snippets/> code="print(456)" { "id": 4, "title": "", "code": "print(456)", "linenos": false, "language": "python", "style": "friendly" }
If you add a--debug
switch to thehttp
requests above, you will be able to see the request type in request headers. Now go and open the API in a web browser, by visiting http://127.0.0.1:8000/snippets/.- Browsability Because the API chooses the content type of the response based on the client request, it will, by default, return an HTML-formatted representation of the resource when that resource is requested by a web browser. This allows for the API to return a fully web-browsable HTML representation. Having a web-browsable API is a huge usability win, and makes developing and using your API much easier. It also dramatically lowers the barrier-to-entry for other developers wanting to inspect and work with your API.
チュートリアル③:Class-based Views
We can also write our API views using class-based views, rather than function based views. As we’ll see this is a powerful pattern that allows us to reuse common functionality, and helps us keep our code DRY.
- Rewriting our API using class-based views We’ll start by rewriting the root view as a class-based view. All this involves is a little bit of refactoring of
views.py
.from snippets.models import Snippet from snippets.serializers import SnippetSerializer from django.http import Http404 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status 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)
So far, so good. It looks pretty similar to the previous case, but we’ve got better separation between the different HTTP methods. We’ll also need to update the instance view inviews.py
.class SnippetDetail(APIView): """ Retrieve, update or delete a snippet instance. """ def get_object(self, pk): try: return Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: raise Http404 def get(self, request, pk, format=None): snippet = self.get_object(pk) serializer = SnippetSerializer(snippet) return Response(serializer.data) def put(self, request, pk, format=None): snippet = self.get_object(pk) serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, pk, format=None): snippet = self.get_object(pk) snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT)
That’s looking good. Again, it’s still pretty similar to the function based view right now. We’ll also need to refactor oursnippets/urls.py
slightly now that we’re using class-based views.from django.urls import path from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = [ path('snippets/', views.SnippetList.as_view()), path('snippets/<int:pk>/', views.SnippetDetail.as_view()), ] urlpatterns = format_suffix_patterns(urlpatterns)
Okay, we’re done. If you run the development server everything should be working just as before. - Using mixins クラスベースのビューを使用する大きな利点の一つは、再利用可能な振る舞いの部分を簡単に組み合わせることができること。 これまで使用してきた作成/取得/更新/削除の操作は、私たちが作成する任意のモデル裏打ちのAPIビューにとって非常に類似している。これらの共通の振る舞いの部分は、RESTフレームワークのミックスインクラスに実装されている。 Let’s take a look at how we can compose the views by using the mixin classes. Here’s our
views.py
module again.from snippets.models import Snippet from snippets.serializers import SnippetSerializer from rest_framework import mixins from rest_framework import generics 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)
- **
ListModelMixin
は、オブジェクトのリストを取得するためのlist
**メソッドを提供する。 - **
CreateModelMixin
は、新しいオブジェクトを作成するためのcreate
**メソッドを提供する。 - **
GenericAPIView
は、基本的なAPIビューの機能(例えば、get_serializer_class
やget_queryset
**など)を提供する。
GenericAPIView
, and adding inListModelMixin
andCreateModelMixin
. 基本クラスはコア機能を提供し、ミックスインクラスは**.list()
と.create()
**のアクションを提供する。その後、getメソッドとpostメソッドを適切なアクションに明示的に結び付けている。class SnippetDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer def get(self, request, *args, **kwargs): return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def delete(self, request, *args, **kwargs): return self.destroy(request, *args, **kwargs)
Pretty similar. Again we’re using theGenericAPIView
class to provide the core functionality, and adding in mixins to provide the.retrieve()
,.update()
and.destroy()
actions. - **
- Using generic class-based views Using the mixin classes we’ve rewritten the views to use slightly less code than before, but we can go one step further. REST framework provides a set of already mixed-in generic views that we can use to trim down our
views.py
module even more.from snippets.models import Snippet from snippets.serializers import SnippetSerializer from rest_framework import generics class SnippetList(generics.ListCreateAPIView): # generics.ListCreateAPIViewを継承している。一覧表示と新しいオブジェクトの作成の機能を持つAPIビューを提供。 queryset = Snippet.objects.all() # このビューが操作するデータのセットを定義。ここではSnippetモデルのすべてのオブジェクトが対象。 serializer_class = SnippetSerializer # SnippetSerializerを使用して、データのシリアライズとデシリアライズを行う。 class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): # 特定のオブジェクトの取得、更新、削除の機能を持つAPIビューが提供される。 queryset = Snippet.objects.all() serializer_class = SnippetSerializer
Wow, that’s pretty concise. We’ve gotten a huge amount for free, and our code looks like good, clean, idiomatic Django.
コメント