MENU

rest_framework クイックスタート / チュートリアル①について

  • Python 3.11.4 / Django 4.2.4
目次

rest_framework

We’re going to create a simple API to allow admin users to view and edit the users and groups in the system.

  • Project setup Create a new Django project named tutorial, then start a new app called quickstart. # Create the project directory mkdir tutorial cd tutorial # Create a virtual environment to isolate our package dependencies locally python -m venv env env\\Scripts\\activate # Install Django and Django REST framework into the virtual environment pip install django pip install djangorestframework # Set up a new project with a single application django-admin startproject tutorial . # 最後の'.'忘れないで cd tutorial django-admin startapp quickstart cd .. The project layout should look like: cd <some path>\\tutorial tree /f C:. │ asgi.py │ settings.py │ urls.py │ wsgi.py │ __init__.py │ └─quickstart │ admin.py │ apps.py │ models.py │ tests.py │ views.py │ __init__.py │ └─migrations __init__.py アプリケーションがプロジェクトディレクトリ内に作成されているのは一見珍しく見えるかもしれないが、プロジェクトの名前空間を使用することで、外部モジュールとの名前の衝突を避けることができる。 Now sync your database for the first time: python manage.py migrate We’ll also create an initial user named admin with a password pf password123. 後でそのユーザーとして認証を行う。 python manage.py createsuperuser --email admin@example.com --username admin Once you’ve set up a database and the initial user is created and ready to go, open up the app’s directory and we’ll get coding…
  • Serializers : Serializersは、Djangoのデータ(例えば、QuerySetやモデルのインスタンス)を、Pythonのデータ形式(例:辞書)に変換するためのツール。この変換を行うことで、Pythonのデータを簡単にJSONやXMLのようなフォーマットに出力することができる。しかし、Serializersの役割はこれだけではなく、PythonのデータをDjangoのデータ形式に戻す「デシリアライズ」もサポートしている。これにより、例えば、JSONからDjangoのモデルにデータを変換することも可能。 First up we’re going to define some serializers. Let’s create a new module named tutorial/quickstart/serializers.py that we’ll use for our data representations. from django.contrib.auth.models import User, Group from rest_framework import serializers class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User fields = ['url', 'username', 'email', 'groups'] # シリアライズする際に取り出すフィールドを指定。この場合url, username, email, groupsがJSONやXMLに変換される際に出力される class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Group fields = ['url', 'name'] # Groupモデルのシリアライズ時に取り出すフィールドを指定。この場合urlとnameの2つのフィールドが出力される Notice that we’re using hyperlinked relations in this case with HyperlinkedModelSerializer. You can also use primary key and various other relationships, but hyperlinking is good RESTful design.
  • Views Open tutorial/quickstart/views.py and get typing. from django.contrib.auth.models import User, Group from rest_framework import viewsets from rest_framework import permissions from tutorial.quickstart.serializers import UserSerializer, GroupSerializer class UserViewSet(viewsets.ModelViewSet): # Userモデルに対するAPIエンドポイントを定義するクラス """ API endpoint that allows users to be viewed or edited. """ queryset = User.objects.all().order_by('-date_joined') # APIエンドポイントで返すUserモデルのオブジェクトのリストを定義。この場合すべてのUserオブジェクトをdate_joinedフィールドの降順で取得する。 serializer_class = UserSerializer # APIのレスポンスやリクエストのデータを変換するためのシリアライザクラスを指定。 permission_classes = [permissions.IsAuthenticated] # APIエンドポイントへのアクセス権限を定義。この場合認証済みのユーザーのみがアクセスできるようになっている。 class GroupViewSet(viewsets.ModelViewSet): # Groupモデルに対するAPIエンドポイントを定義するクラス """ API endpoint that allows groups to be viewed or edited. """ queryset = Group.objects.all() # APIエンドポイントで返すGroupモデルのオブジェクトのリストを定義。 serializer_class = GroupSerializer # APIのレスポンスやリクエストのデータを変換するためのシリアライザクラスを指定。 permission_classes = [permissions.IsAuthenticated] # APIエンドポイントへのアクセス権限を定義。 Rather than write multiple views we’re grouping together all the common behavior into classes called ViewSets. We can easily break these down into individual views if we need to, but using viewsets keeps the view logic nicely organized as well as being very concise(簡潔).
  • URLs Okay, now let’s wire up the API URLs. On to tutorial/urls.pyfrom django.urls import include, path from rest_framework import routers from tutorial.quickstart import views router = routers.DefaultRouter() router.register(r'users', views.UserViewSet) router.register(r'groups', views.GroupViewSet) # 自動URLルーティングを使用してAPIを設定する。 # Additionally, we include login URLs for the browsable API. urlpatterns = [ path('', include(router.urls)), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) ] この例では、viewsではなくviewseteを使用しているため、viewsetsをルータークラスに登録するだけで、APIのURL設定を自動的に生成することができる。 さらに、APIのURLに対してもっと制御が必要な場合は、通常のクラスベースのviewsを使用して、URL設定を明示的に書くこともできる。 最後に、ブラウザブルなAPIを使用するためのデフォルトのログインおよびログアウトのviewを含めている。これはオプションだが、APIが認証を必要としており、ブラウザブルなAPIを使用したい場合には便利。
  • Pagination Pagination allows you to control how many objects per page are returned. To enable it add the following lines to tutorial/settings.py REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10 }
  • Settings Add 'rest_framework' to INSTALLED_APPS. The settings module will be in tutorial/settings.py INSTALLED_APPS = [ ... 'rest_framework', ]
  • Testing our API We’re now ready to test the API we’ve built. Let’s fire up the server from the command line. python manage.py runserver We can now access our API, both from the command-line, using tools like curlbash: curl -H 'Accept: application/json; indent=4' -u admin:password123 <http://127.0.0.1:8000/users/> { "count": 2, "next": null, "previous": null, "results": [ { "email": "admin@example.com", "groups": [], "url": "<http://127.0.0.1:8000/users/1/>", "username": "admin" }, ] }
    • 私の場合 コマンドプロンプトからサーバーを起動したら、windows powershellで以下のコマンドを実行 cd tutorial $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("admin:password123"))) $response = Invoke-WebRequest -Headers @{"Accept"="application/json; indent=4"; "Authorization"="Basic $base64AuthInfo"} -Uri "<http://127.0.0.1:8000/users/>" -UseBasicParsing $response.Content # ここでユーザー名とパスワードの入力を要求するダイアログが表示される # 入力すると下のような結果が表示される { "count": 1, "next": null, "previous": null, "results": [ { "url": "<http://127.0.0.1:8000/users/1/>", "username": "admin", "email": "admin@example.com", "groups": [] } ] } http://127.0.0.1:8000/users/ に移動すると以下のようなページが表示されているはず。 quickstart.png

チュートリアル①:Serialization

  • Setting up a new enviroment Before we do anything else we’ll create a new virtual environment. This will make sure our package configuration is kept nicely isolated from any other projects we’re working on. python -m venv tutorial_env tutorial_env\\Scripts\\activate Now that we’re inside a virtual environment, we can install our package requirements. pip install django pip install djangorestframework pip install pygments # We'll be using this for the code highlighting Note: To exit the virtual environment at any time, just type deactivate.
  • Getting started To get started, let’s create a new project to work with. cd %USERPROFILE% django-admin startproject tutorial1 cd tutorial1 Once that’s done we can create an app that we’ll use to create a simple Web API. python manage.py startapp snippets 上のコマンドはsnippetsという新しいディレクトリを作成し、そのディレクトリ内にDjangoアプリケーションを構築するための基本的なファイルセットを生成する。 We’ll need to add our new snippets app and the rest_framework app to INSTALLED_APPS. Let’s edit the tutorial1/settings.py file: INSTALLED_APPS = [ ... 'rest_framework', 'snippets', ]
  • Creating a model to work with For the purposes of this tutorial we’re going to start by creating a simple Snippet model that is used to store code snippets. Go ahead and edit the snippets/models.py file. Note: Good programming practices include comments. Although you will find them in our repository version of this tutorial code, we have omitted them here to focus on the code itself. from django.db import models from pygments.lexers import get_all_lexers # pygmentsというシンタックスハイライトのライブラリから全ての利用可能なレキサーを取得する関数get_all_lexersをインポートしている。 from pygments.styles import get_all_styles # 同じくpygmentsから、全ての利用可能なスタイルを取得する関数get_all_stylesをインポートしている。 LEXERS = [item for item in get_all_lexers() if item[1]] # 利用可能なレキサーを取得して、そのリストから有効なレキサー(item[1]がTrueのもの)だけをフィルタリングしてLEXERSリストに格納している。 LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) # LEXERSから言語の選択肢を作成している。これはDjangoのモデルフィールドのchoicesオプションに使用するタプルのリストで、リストはソートされている。 STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()]) # 利用可能なスタイルの選択肢を作成している。これもDjangoのモデルフィールドのchoicesオプションに使用する。 class Snippet(models.Model): created = models.DateTimeField(auto_now_add=True) # スニペットが作成された日時を保存するためのフィールドで、auto_now_add=Trueはこのスニペットが初めて保存される時に現在の日時が自動的に設定されることを意味する。 title = models.CharField(max_length=100, blank=True, default='') # スニペットのタイトルを保存するための文字列フィールド。 code = models.TextField() # スニペットの実際のコードを保存するためのテキストフィールド。 linenos = models.BooleanField(default=False) # 行番号を表示するかどうかを示す真偽値フィールド。デフォルトはFalse。 language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100) # スニペットの言語を示す文字列フィールド。選択肢はLANGUAGE_CHOICESに定義されており、デフォルト言語はpython。 style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100) # シンタックスハイライトのスタイルを示す文字列フィールド。選択肢はSTYLE_CHOICESに定義されており、デフォルトスタイルはfriendly。 class Meta: # Djangoモデルのメタデータを定義する内部クラス。 ordering = ['created'] # Snippetモデルのオブジェクトをデフォルトでcreatedフィールドの昇順に並べ替えることを指定している。
    • シンタックスハイライト(syntax highlighting)は、ソースコードやマークアップ言語のテキストを表示する際に、その言語の構文(syntax)に基づいてテキストの一部を色分けしたりスタイルを変えたりする技術のこと。
    • レキサー(lexer/lexical analyzer)はソースコードを入力として受け取り、それをトークンと呼ばれる小さな単位に分割する。トークンはプログラムの意味を理解するための基本的な単位となる。 x = 10 + 5 # 上のコードをレキサーに渡すと以下のようなトークンのリストに分割される。 # x (変数名) # = (代入演算子) # 10 (整数) # + (加算演算子) # 5 (整数) **pygmentsライブラリの文脈で言及される「レキサー」とは、異なるプログラミング言語のコードを正しくハイライトするために、その言語の構文を理解する部分を指す。つまり、pygments**のレキサーは、特定の言語のコードをトークンに分割し、それに応じて色分けやスタイリングを行う。
    We’ll also need to create an initial migration for our snippet model, and sync the database for the first time. python manage.py makemigrations snippets python manage.py migrate snippets
  • Creating a Serializer class The first thing we need to get started on our Web API is to provide a way of serializing(変換) and deserializing(元に戻す) the snippet instances into representations such as json. これはDjangoのフォームと非常に似ている方法で動作するシリアライザを宣言することで実現できる。 Create a file in the snippets directory named serializers.py and add the following. from rest_framework import serializers from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES 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): """ Create and return a new `Snippet` instance, given the validated data. """ return Snippet.objects.create(**validated_data) def update(self, instance, validated_data): """ Update and return an existing `Snippet` instance, given the validated data. """ instance.title = validated_data.get('title', instance.title) instance.code = validated_data.get('code', instance.code) instance.linenos = validated_data.get('linenos', instance.linenos) instance.language = validated_data.get('language', instance.language) instance.style = validated_data.get('style', instance.style) instance.save() return instance The first part of the serializer class defines the fields that get serialized / deserialized. The create() and update() methods define how fully fledged(完全な) instances are created or modified when calling serializer.save() A serializer class is very similar to a Django Form class, and includes similar validation flags on the various fields, such as requiredmax_length and default. The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. 上述の {'base_template': 'textarea.html'} フラグは、Djangoの**Form**クラスで widget=widgets.Textarea を使用するのと同等。This is particularly useful for controlling how the browsable API should be displayed, as we’ll see later in the tutorial.
  • Working with Serializers Before we go any further we’ll familiarize ourselves with using our new Serializer class. Let’s drop into the Django shell. python manage.py shell Once we’ve got a few imports out of the way, let’s create a couple of code snippets to work with. >>>from snippets.models import Snippet >>>from snippets.serializers import SnippetSerializer >>>from rest_framework.renderers import JSONRenderer >>>from rest_framework.parsers import JSONParser >>>snippet = Snippet(code='foo = "bar"\\n') # foo = "bar"\\n:fooという名前の変数に文字列barを代入。\\n は文字列の末尾に改行を追加している。 >>>snippet.save() # 作成したsnippetインスタンスをデータベースに保存 >>>snippet = Snippet(code='print("hello, world")\\n') >>>snippet.save() We’ve now got a few snippet instances to play with. Let’s take a look at serializing one of those instances. >>>serializer = SnippetSerializer(snippet) >>>serializer.data {'id': 2, 'title': '', 'code': 'print("hello, world")\\n', 'linenos': False, 'language': 'python', 'style': 'friendly'} At this point we’ve translated the model instance into Python native datatypes. To finalize the serialization process we render the data into json. (json: JavaScript Object Notation / データ交換形式の一つ。テキストベースで、データを構造化するための軽量なフォーマット。) >>>content = JSONRenderer().render(serializer.data) >>>content b'{"id": 2, "title": "", "code": "print(\\\\"hello, world\\\\")\\\\n", "linenos": false, "language": "python", "style": "friendly"}' Deserialization is similar. First we parse a stream into Python native datatypes… >>>import io >>>stream = io.BytesIO(content) >>>data = JSONParser().parse(stream) …then we restore those native datatypes into a fully populated object instance. >>>serializer = SnippetSerializer(data=data) >>>serializer.is_valid() True >>>serializer.validated_data OrderedDict([('title', ''), ('code', 'print("hello, world")\\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]) >>>serializer.save() <Snippet: Snippet object> フォームを使用する場合のAPIとの類似性に注目!シリアライザーを使用するビューを書き始めると、その類似性がさらに明らかになるはず。 モデルのインスタンスの代わりにクエリセットもシリアライズすることができる。シリアライザの引数にmany=Trueフラグを追加する。 >>>serializer = SnippetSerializer(Snippet.objects.all(), many=True) >>>serializer.data [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print("hello, world")\\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print("hello, world")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
  • Using ModelSerializers SnippetSerializerクラスは、Snippetモデルにも含まれている多くの情報を複製している。コードをもう少し簡潔にしたい。 DjangoがFormクラスとModelFormクラスの両方を提供するのと同様に、RESTフレームワークにはSerializerクラスとModelSerializerクラスの両方が含まれている。 ModelSerializerクラスを使用してシリアライザーをリファクタリングする方法を見てみる!Open the file snippets/serializers.py again, and replace the SnippetSerializer class with the following. class SnippetSerializer(serializers.ModelSerializer): class Meta: model = Snippet fields = ['id', 'title', 'code', 'linenos', 'language', 'style'] One nice property that serializers have is that you can inspect all the fields in a serializer instance, by printing its representation. Open the Django shell with python manage.py shell, then try the following: >>>from snippets.serializers import SnippetSerializer >>>serializer = SnippetSerializer() >>>print(repr(serializer)) SnippetSerializer(): id = IntegerField(label='ID', read_only=True) title = CharField(allow_blank=True, max_length=100, required=False) code = CharField(style={'base_template': 'textarea.html'}) linenos = BooleanField(required=False) language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')... style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')... It’s important to remember that ModelSerializer classes don’t do anything particularly magical, they are simply a shortcut for creating serializer classes:
    • 自動で決定されるフィールドのセット
    • create() と update() メソッドのためのシンプルなデフォルト実装
  • Writing regular Django views using our Serializer Let’s see how we can write some API views using our new Serializer class. For the moment we won’t use any of REST framework’s other features, we’ll just write the views as regular Django views. Edit the snippets/views.py file, and add the following. from django.http import HttpResponse, JsonResponse from django.views.decorators.csrf import csrf_exempt from rest_framework.parsers import JSONParser from snippets.models import Snippet from snippets.serializers import SnippetSerializer APIのルートは、すべての既存のスニペットをリスト表示するか、新しいスニペットを作成するためのビューになる。 @csrf_exempt # このデコレータは、このビューでCSRF保護を無効にする。 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) Note that because we want to be able to POST to this view from clients that won’t have a CSRF token we need to mark the view as csrf_exempt. This isn’t something that you’d normally want to do, and REST framework views actually use more sensible behavior than this, but it’ll do for our purposes right now. We’ll also need a view which corresponds to an individual snippet, and can be used to retrieve(取得), update or delete the snippet. @csrf_exempt def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: return HttpResponse(status=404) if request.method == 'GET': serializer = SnippetSerializer(snippet) return JsonResponse(serializer.data) elif request.method == 'PUT': data = JSONParser().parse(request) serializer = SnippetSerializer(snippet, data=data) if serializer.is_valid(): serializer.save() return JsonResponse(serializer.data) return JsonResponse(serializer.errors, status=400) elif request.method == 'DELETE': snippet.delete() return HttpResponse(status=204) Finally we need to wire these views up. Create the snippets/urls.py file: from django.urls import path from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets/<int:pk>/', views.snippet_detail), ] We also need to wire up the root urlconf, in the tutorial1/urls.py file, to include our snippet app’s URLs. from django.urls import path, include urlpatterns = [ path('', include('snippets.urls')), ] It’s worth noting that there are a couple of edge cases we’re not dealing with properly at the moment. If we send malformed json, or if a request is made with a method that the view doesn’t handle, then we’ll end up with a 500 “server error” response. Still, this’ll do for now.
  • Testing our first attempt at a Web API Now we can start up a sample server that serves our snippets. Quit out of the shell… quit() …and start up Django’s development server. python manage.py runserver In another terminal window, we can test the server. We can test our API using curl or httpie. Httpie is a user friendly http client that’s written in Python. Let’s install that. You can install httpie using pip: pip install httpie Finally, we can get a list of all of the snippets:
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次