Django REST Frameworkで素早く簡単にバックエンドのAPIを開発してみる

Django REST Frameworkとは、Djangoを拡張してRESTfulなwebAPIを開発することができるライブラリです。(以下DRFと呼びます。)

RESTful APIとは「REST」の設計思想に基づいて開発されたAPIの事です。

例えばwebフレームワークでも「MVCモデル」や「MVTモデル」みたいな設計思想がありますが、APIの世界では一般的にこの「REST」という設計思想が採用されています。

この記事を最後まで読めば簡単にアプリやサービスのAPIが開発できるようになるので是非一緒に作りながら進めてみてください。

環境

Python3.8.2
Django3.2.4

開発環境のセットアップ

DRFはあくまでDjangoのライブラリですので、基本は同じです。

仮想環境の構築や、プロジェクトを作成する箇所、設定ファイルを変更するなどのDjangoの開発環境を作成するまでの手順は過去記事で詳しく解説しているので、下記記事も参考にしてみてください。

【Python】Django3.2でHelloWorldまでを初学者向けに丁寧に解説していく!

bash
# 仮想環境を作成
$ pipenv install

# 仮想環境に入る
$ pipenv shell

# 必要ライブラリをインポート
(hgoe)$ pipenv install django
(hgoe)$ pipenv install djangorestframework

# Djangoプロジェクトを作成(今回プロジェクト名は例としてconfig)
(hgoe)$ django-admin startproject config

# ディレクトリ移動
(hgoe)$ cd config

# 開発用サーバーを起動
(hgoe)$ python manage.py runserver

ついでに言語設定と利用するタイムゾーンを変更しておきます。

~/config/config/settings.py
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

http://localhost:8000/にアクセスして、Djangonの初期画面が表示されていればセットアップ完了です。

アプリを作成する

次にアプリを作成していきます。

bssh
(hoge)$ python manage.py startapp blog

アプリが作成できたらsettings.pyに作成したアプリ(blog)と先程インポートしたdjangorestframeworkを登録しておきます。

~/config/config/settings.py
# Application definition
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',  # 追加
    'rest_framework',  # 追加
]

モデルを作成する

データベースをマイグレートする前にモデルを作成しておきます。

今回はブログサイトのバックエンドをAPIで作成する想定でカスタムユーザーモデル(UserProfile)ブログモデル(Blog)を定義していきます。

~/config/blog/models.py
from django.db import models
from django.conf import settings

# AbstractBaseUserを利用してUserモデルをカスタマイズ
from django.contrib.auth.models import AbstractBaseUser

# PermissionsMixinを用いてUserの認証を行う
from django.contrib.auth.models import PermissionsMixin

# BaseUserManager利用してUserManagerモデルをカスタマイズ
from django.contrib.auth.models import BaseUserManager


class UserProfileManager(BaseUserManager):
    """ カスタムユーザーマネージャー """

    # ユーザを作成するメソッド
    def create_user(self, email, name, password=None):
        """ ユーザー作成 """

        if not email:
            raise ValueError('User must have an email address')

        # emailのドメインを小文字に変換
        email = self.normalize_email(email)

        # UserProfileモデルを参照してuserを定義
        user = self.model(email=email, name=name)

        # userが入力したパスワードをハッシュ化
        user.set_password(password)

        # settings.pyでdefaultに設定されているDBに保存
        user.save(using=self._db)

        return user

    def create_superuser(self, email, name, password):
        """ スーパーユーザー作成 """

        # 上記create_userを利用
        user = self.create_user(email, name, password)

        # superuserの権限を適用
        user.is_superuser = True
        user.is_staff = True
        user.save(using=self._db)

        return user


class UserProfile(AbstractBaseUser, PermissionsMixin):
    """ カスタムユーザーモデル """

    email = models.EmailField(max_length=255, unique=True)
    name = models.CharField(max_length=255)

    # ユーザが退会したらfalseにして論理削除
    is_active = models.BooleanField(default=True)

    # 管理画面にアクセスを許可するか
    is_staff = models.BooleanField(default=False)

    # UserProfileManagerのメソッドを使えるようにする
    objects = UserProfileManager()

    # emailを利用したログイン認証に変更
    USERNAME_FIELD = 'email'

    # 必須項目追加
    REQUIRED_FIELDS = ['name']

    # 1つのnameフィールドで表示したいので、既存のメソッドをオーバーライド
    def get_full_name(self):
        return self.name

    def get_short_name(self):
        return self.name

    # 管理画面などで表示される文字列を定義
    def __str__(self):
        return self.name


class Blog(models.Model):
    title = models.CharField(blank=False, null=False, max_length=150)
    text = models.TextField(blank=True)
    created_datetime = models.DateTimeField(auto_now_add=True)
    updated_datetime = models.DateTimeField(auto_now=True)

    # 外部キー
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='blog', on_delete=models.CASCADE, default=1)

    def __str__(self):
        return self.title

この際に定義したカスタムユーザーモデルの認証をsettings.pyに記載しておきます。

AbstractUserを継承したモデルを作成した際にはこれを指定しないとエラーになってしまいます。

~/config/config/settings.py
# ユーザ認証を変更
AUTH_USER_MODEL = 'blog.UserProfile'

記述したらデータベースをマイグレートします。

bash
(hoge)$ python manage.py makemigrations
(hoge)$ python manage.py migrate

スーパーユーザーを作成

この段階でスーパーユーザーを作成しておきます。

bash
(hoge)$ python manage.py createsuperuser

先程models.pyでUserProfileを定義したのでアカウントの登録方法がEメールと名前とパスワードに設定されているはずです。

管理サイトからテストデータを登録

アカウントが作成できたら、管理画面で先程作成したモデルを扱えるようにadmin.pyを編集します。

~/config/api/admin.py
from django.contrib import admin
from .models import Blog, UserProfile


class UserProfileAdmin(admin.ModelAdmin):
    # 一覧表示画面のフィールド
    list_display = ('name', 'email', 'is_active', 'is_staff')
    # フィールドをリンク化
    list_display_links = ('name', 'email')


class BlogAdmin(admin.ModelAdmin):
    list_display = ('title', 'text', 'created_datetime',
                    'updated_datetime', 'author')
    list_display_links = ('title', 'text')


# 管理画面に登録
admin.site.register(Blog, BlogAdmin)
admin.site.register(UserProfile, UserProfileAdmin)

admin.pyを編集したら実際に管理画面にログインしてみましょう。

localhost:8000/adminにアクセスしてみてください。

カスタムユーザーモデルを作成しているので認証がEmailとパスワードに変更されています。

DRFでは通常のDjangoと同様にデフォルトで用意されている管理画面から、モデルの操作などがブラウザ上で行うことができます。

後ほどAPIの動作確認をするので、「+追加」ボタンを押して何でも良いのでBlogsとUser profilesにテストデータを事前に登録しておきましょう。

▼Blogs

▼User profiles

ここまでが実際にAPI実装前までの準備段階になります。

APIの実装

ここから実際にapiのエンドポイントを実装していきます。

今回新規API実装の一連の流れは下記の順番で進めていきます。

  1. Viewの作成
  2. ルーティングの作成
  3. シリアライザーの作成

Viewの作成

始めにViewを作成していきます。

DRFにおけるViewとは、ユーザーからのアクセス(リクエスト)に対して、どのAPIを使用するかを決定、処理を行って返却する役割を担っています。

今回例として実装するAPIの機能は下記になります。

  • ブログの一覧表示処理
  • ブログの詳細取得処理
  • ユーザーのCRUD処理

Viewはアプリのviews.pyに記述します。

~/config/blog/views.py
from blog import serializers
from .models import Blog, UserProfile
from rest_framework import generics
from rest_framework import viewsets


# ListAPIView メソッド:GET 一覧取得
class BlogView(generics.ListAPIView):
    queryset = Blog.objects.all()
    serializer_class = serializers.BlogSerializer


# RetrieveAPIView メソッド:GET 単一取得
class BlogDetailView(generics.RetrieveAPIView):
    queryset = Blog.objects.all()
    serializer_class = serializers.BlogSerializer


# ModelViewSet 1つのエンドポイントでmodelに紐付いたCRUD処理を実装してくれる
class UserProfileViewSet(viewsets.ModelViewSet):
    queryset = UserProfile.objects.all()
    serializer_class = serializers.UserProfileSerializer

DRF側がよく使う機能のViewをデフォルトで提供してくれているので、それらを継承する事で開発者は短い記述量で簡単によく使う処理を実装できるようになっています。フレームワークらしい便利な機能ですね。

後ほど設定するルーティングによってリクエストされたエンドポイント毎にそれぞれのViewが割り当てられます。

それぞれのViewでは、各自でmodelのクエリセットと、シリアライザーを定義しています。

このシリアライザーについては後ほど解説します。

今回使用したViewについて簡単に解説します。

Generic View

Generic ViewとはDRFがデフォルトで提供している汎用的なViewの総称です。

よく使う機能毎にViewを提供してくれているので、自分が実装したい機能に合わせて適切なViewを継承をしましょう。

今回は

  • ブログの一覧表示機能:ListAPIViewを継承
  • ブログの一覧表示機能:ListAPIViewを継承

といった感じでGeneric Viewを利用しています。

よく使うGeneric Viewから提供されているViewとしては下記を抑えておけば問題ないでしょう。

▼View名▼メソッド▼機能
CreateAPIViewPOST登録
ListAPIViewGET一覧取得
RetrieveAPIViewGET単一取得
UpdateAPIViewPUT
PATCH
更新
DestroyAPIViewDELETE削除

提供されているViewの種類については公式リファレンスに一覧で載っているので確認してみてください。

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

ViewSet

ViewSetもDRFからデフォルトで提供されているDBとのやりとりに特化したViewで、取得, 一覧, 登録, 更新, 削除を一括で実装してくれます。

今回の機能の例だと、modelに紐付いたCRUD処理を一括で実装してくれるModelViewSetを継承して利用しています。

ViewSetを利用するメリットは複数機能を一括で実装してくれるのでコード記述量が減るというのが大きいと思います。

先程のGeneric Viewではよく使う機能毎にViewを設定していたので必然的にコード量が増えていましたが、ViewSetを利用するとリクエストされたアクションメソッドによって自動的に機能を判別して実行してくれるので、コード量が減って管理しやすくなります。

提供されているViewSetの種類に関しても公式リファレンスに載っているので確認してみてください。

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

ルーティングの作成

次にユーザーからのリクエストに対して先程作成したViewに紐付けを行います。

ルーティングはプロジェクト側とアプリ側のurls.pyに定義します。

まずはプロジェクトファイルのurls.pyを編集します。

~/config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    # 管理画面を表示
    path('admin/', admin.site.urls),
    # APIへの入り口
    path('api/', include('blog.urls')),
]

プロジェクトディレクトリのurls.pyでは管理画面用のadmin/とAPI用のapi/にアクセスされた際の紐付けを定義しています。

api/にアクセスされた時はアプリ側のurls.pyを参照するように記述しています。

通常のDjangoと同様にアプリ内に新規にurls.pyを作成します。

~/config/blog/urls.py
from django.urls import path, include
from blog import views
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
# ユーザーのCRUD処理用
router.register('profile', views.UserProfileViewSet)

urlpatterns = [
    # ブログ一覧取得用
    path('blog/', views.BlogView.as_view(), name='blog'),
    # ブログ詳細取得用
    path('blog/<str:pk>/', views.BlogDetailView.as_view(), name='blog_detail'),
    # router接続用
    path('', include(router.urls)),
]

今回ユーザー関連とブログ関連のリクエストでルーティングの方法を分けています。

通常はurlpatternsにそれぞれのViewへのルーティングを定義する必要があるのですが、ViewSetを継承したViewに紐付けをする場合、routerという機能を使ってルーティングを作成することができます。

routerとはユーザーからのリクエストに応じて自動的にURLを生成する機能で、例えばapi/profileにリクエストが来た場合はユーザーの一覧取得api/profile/1にリクエストが来た場合はユーザーの詳細取得という感じにURLパターンを自動的に生成します。

routerを使う際にはurlpatternsの最後に記述したinclude(router.urls)が無いと紐付けできないので注意してください。

シリアライザーの作成

最後にシリアライザーを定義します。

これは通常のDjangoには存在しないDRF独自の概念で、データの入出力の際にmodelとの橋渡しをして、バリデーションを実施したり、APIの返却値のフォーマットを最適化するなどの役割があります。

serializers.ModelSerializerを継承することによってモデルに紐付いたシリアライザーが簡単に作成可能です。

アプリ内にserializers.pyを新しく作成して記述をします。

~/config/blog/serializers.py
from rest_framework import serializers
from .models import Blog, UserProfile


class UserProfileSerializer(serializers.ModelSerializer):
    class Meta:

        # Serializersに紐付けるmodelを定義
        model = UserProfile

        # APIとして出力したいカラムを定義(タプル形式)
        fields = ('id', 'email', 'name', 'password')

        # fieldsの制限設定(定義したカラム名をkeyに取る)
        extra_kwargs = {
            'password': {

                # セキュリティの関係上パスワードは書き込むだけ。
                'write_only': True,

                # パスワード入力の際に「・・・」となるようにstyleを指定
                'style': {'input_type': 'password'}
            }
        }

    # ModelSerializerにデフォルトで実装されているメソッドをオーバーライド(パスワードのハッシュ化処理)
    def create(self, validated_data):
        user = UserProfile.objects.create_user(
            email=validated_data['email'],
            name=validated_data['name'],
            password=validated_data['password'],
        )
        return user

    def update(self, instance, validated_data):
        if 'password' in validated_data:
            password = validated_data.pop('password')
            instance.set_password(password)
        return super().update(instance, validated_data)


class BlogSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Blog
        fields = ('id', 'text', 'created_datetime',
                  'updated_datetime', 'author')

これで基本となる一連の新規APIの作成手順は終わりです。

動作確認

ここまでやったら動作検証をしてみましょう。

DRFではデフォルトでAPIの返却値をブラウザ上に表示してくれる機能も備えてくれています。

それではまず登録したブログ一覧を取得するエンドポイントのhttp://localhost:8000/api/blog/にアクセスしてみます。

すると先程serializers.pyで定義したフィールドがブラウザ上にAPIからの返却値として表示できていると思います。

続いて先程のブログ一覧取得のエンドポイントの後ろに個別のIDをつけると個別にブログを取得することができます。

例えばhttp://localhost:8000/api/blog/1/にアクセスするとブログ一覧のIDが「1」のブログを表示することができます。

続いてユーザー一覧を取得するエンドポイントのhttp://localhost:8000/api/profile/にアクセスしてみます。

登録されているユーザーの一覧が取得できているのが分かると思います。

先程のブログの結果画面と違う点は、API出力値の下にフォームが設置されている点です。

ユーザー用のViewにはViewSetによってCRUD処理が一括で実装されているので、この確認用の画面からでも新規でデータを作成することができるようになっています。

ブログの詳細取得と同様にhttp://localhost:8000/api/profile/1/という感じにユーザー一覧取得のエンドポイントの後ろにIDを付けると、ユーザー詳細を取得するエンドポイントになります。

ここでポイントなのが、ユーザーの詳細取得用のルーティングはurls.pyでは定義していませんが、routerが自動的に詳細用のエンドポイントを生成してくれている点です。

この様に、一覧取得のエンドポイントの後ろにIDが付いていれば詳細取得をするという判断を自動でやってくれるのがViewSetを使うことで得られるrouter機能の利点です。

また、詳細取得の結果確認画面ではデータの削除と編集ができるようになっています。

ここまでのまとめとしてDRFのリクエストからレスポンスの返却までの流れを簡単に表すと↓のイラストの様な感じになります。

おわりに

長くなりましたがDRFでのAPI開発の基本を解説してきました。

正直な話、わざわざフロントエンドとバックエンドで処理を分けて個々で開発するのは非常にめんどくさいです。

ただ、それぞれの領域で分業をすることで、やれることの幅の広いアプリやサービスを作成可能になります。

例えばフロントエンドはNext.js、バックエンドはDRFと別々で実装すればユーザー体験が優れるSPA(シングルページアプリケーション)で画面テンプレートを作りながら、機能として機械学習を利用した質の高いサービスなどが提供できるようになりますよね。

DRFはPythonで開発されていますので、機械学習系の処理を自然に実装できるのも強みです。

他にもモバイルアプリフレームワークのFlutter+DRFなんて組み合わせも可能です。

活用方法は無限大ですので是非皆さんもアプリ等のバックエンドを開発する際はDjangonのrest frameworkを採用してみてはどうでしょうか。

この記事を書くに当たって大変参考になった書籍を貼っておくので是非見てみてください!

(AmazonではKindle版のみになります。)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です