🚀

Django REST Framework の導入から開発スタート地点に立つまでの手順 (トークン認証方式)

2021/08/17に公開

はじめに

Django REST Framwork の導入にあたって、開発環境で実際に使い始めるまでの手順がわかりにくかったのでまとめました。

ゴール

  • 開発初期にすぐに使い始めることができる状態になっている
    • カスタムユーザーモデルを定義している
    • トークン認証できる
    • ページネーションが有効になっている
    • URI設計を考慮してある
    • ユニットテストが実施できる
    • Django管理サイトにアクセスできる
    • WEBブラウザでAPIを表示できる
    • オリジン間リソース共有(CORS)許可設定ができる
  • Django, Django REST Framework のインストール手順になるべく準拠する

ゴール時点のサンプルソースコード

drf-example

環境

  • Windows 10
  • Anaconda インストール済み

バージョン

  • Python==3.9.6
  • Django==3.2.6
  • djangorestframework==3.12.4
  • markdown==3.3.4
  • django-filter==2.4.0
  • django-cors-headers==3.8.0

事前準備

Python環境は Anaconda を使った仮想環境を利用します。

Python 仮想環境

ここでは、仮想環境の名前を drf-example とし、Anaconda Prompt で以下のコマンドを実行します。

conda create -n drf-example python=3.9.6

対話モードになるので適宜 "y" を入力して進め、作成が完了したら、仮想環境をアクティベートします。

conda activate drf-example

以降は、この仮想環境を使った作業になります。

Django のインストール

Django本体をインストールします。

pip install Django==3.2.6

プロジェクトの作成

ソースコードを格納するフォルダーを用意します。

プロジェクトをGit管理する前提で、先にリモートリポジトリが作成されているなら、それをクローンしたフォルダーを使うことになります。そうでなければ、任意のフォルダーを作成します。

ここではフォルダー名を drf-example 、Django プロジェクト名を myproject とします。

用意したフォルダー drf-example に cdコマンド で移動したあと、以下のコマンドを実行します。現在の(カレント)フォルダーにプロジェクトを作成するので "." を最後につけます。

django-admin startproject myproject .

この時点で、次のファイル構成になります。リポジトリからクローンした場合は他のファイルも含まれるかもしれません。

tree /f

...

:.
│  manage.py
│
└─myproject
        asgi.py
        settings.py
        urls.py
        wsgi.py
        __init__.py

初期設定

言語コードとタイムゾーンを日本向けに変更します。

myproject/settings.py
...
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

開発用サーバー起動確認

以下を実行し、表示されたURLにWEBブラウザでアクセスします。

python manage.py runserver

...
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

次の画面が表示されることを確認します。

確認ができたら、[Ctrl] + [c] で開発用サーバーを停止します。

Django REST Framework のインストール

続いて、Django REST Framework をインストールします。

Python パッケージのインストール

公式サイトのインストール手順と同様にパッケージをインストールします。

pip install djangorestframework
pip install markdown
pip install django-filter

オリジン間リソース共有(CORS)許可設定のためのパッケージをインストールします。

pip install django-cors-headers

各ファイルを編集します。
開発環境構築向けなのでセキュリティに関する制限は緩めます。

myproject/settings.py
...
~~~

ALLOWED_HOSTS = ['*'] # 追記 サイトをローカルPC以外で配信できるようにする

~~~

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # ↓追記
    'rest_framework',
    'rest_framework.authtoken',
    'corsheaders',
]

MIDDLEWARE = [
    ~~~
    'corsheaders.middleware.CorsMiddleware', # 追記 CommonMiddlewareの前(上)(公式のインストール手順にその旨記載あり)
    'django.middleware.common.CommonMiddleware', # ← 初期状態で設定がある
    ~~~
]

~~~
# ↓追記
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 100,
}

# ↓追記 全許可が嫌なら個別に指定する
CORS_ALLOW_ALL_ORIGINS = True
#CORS_ALLOWED_ORIGINS = [
#    'http://localhost:3000',
#]

myproject/urls.py
~~~
from django.contrib import admin
from django.urls import path
from rest_framework.authtoken import views as auth_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/auth/', auth_views.obtain_auth_token, name="auth"),
]

アカウントアプリの作成

Django では プロジェクトの開始時にカスタムのユーザーモデルを使用することが強く推奨されています。

そこで accounts というアプリを作成し、このアプリにカスタムユーザーモデルを定義することにします。設計方針として、このアプリではアカウントに関することだけを扱い、業務的な事柄は適宜別にアプリを作成してその中で扱うことを想定します。

これは、アカウント(ユーザー情報)は通常、業務的な事柄から参照されるが、アカウント側は業務的な事柄を参照すべきでないとの考えによります。

以下はプロジェクトとアプリ構成の概念を示しています。

  • プロジェクト (myproject)
    • アカウントアプリ (accounts)
    • 業務的な事柄を扱うアプリA (accounts を参照する)
    • 業務的な事柄を扱うアプリB (accounts を参照する)
    • ...

なお、Django の アプリの単位には正解がなく、アプリ1つにすべての事柄を含める場合もあれば、複数のアプリに分ける場合もあります。また、上記の概念において、現実的には アプリA と アプリB の間にも参照が必要になるケースはありえます。(最初は必要なくとも、後で必要になるといったことも)

アカウントアプリ作成とカスタムユーザーモデル定義

以下のコマンドを実行してアプリを作成します。

python manage.py startapp accounts

カスタムユーザーモデルを定義します。

accounts/models.py
from django.contrib.auth.models import AbstractUser


class User(AbstractUser):
    pass
accounts/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .models import User

admin.site.register(User, UserAdmin)

users API に関する定義を行います。ユーザーモデルは get_user_model() で取得します。

accounts/serializers.py (新規)
from django.contrib.auth import get_user_model
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = get_user_model()
        fields = ('id', 'username', 'email', 'is_staff')
accounts/views.py
from django.contrib.auth import get_user_model
from rest_framework import viewsets

from accounts.serializers import UserSerializer


class UserViewSet(viewsets.ModelViewSet):
    queryset = get_user_model().objects.all()
    serializer_class = UserSerializer

カスタムユーザー作成時にトークン生成するハンドラを新規ファイルに定義します。ユーザーモデルは settings.AUTH_USER_MODEL で指定します。

accounts/signals.py (新規)
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token


@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)
accounts/apps.py
from django.apps import AppConfig


class AccountsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'accounts'

    def ready(self) -> None:
        import accounts.signals
        return super().ready()

アカウントアプリ、カスタムユーザーモデルをプロジェクト設定に追加します。

myproject/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    # ↓追記
    'accounts.apps.AccountsConfig',
]
~~~
# ↓追記
AUTH_USER_MODEL = 'accounts.User'

URL定義に users API を追加します。

myproject/urls.py
from accounts.views import UserViewSet
from django.contrib import admin
from django.urls import include, path
from rest_framework import routers
from rest_framework.authtoken import views as auth_views

router = routers.DefaultRouter()
router.register(r'users', UserViewSet)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/auth/', auth_views.obtain_auth_token, name='auth'),
    path('api/v1/', include(router.urls)),
]

テストコードも用意します。

accounts/tests.py
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase


class AuthTestCase(APITestCase):

    def setUp(self):
        user = get_user_model().objects.create_user("user", "user@example.com", "password")
        user.save()

    def test_auth(self):
        url = reverse("auth")
        data = { "username": "user", "password": "password" }
        response = self.client.post(url, data, format="json")

        self.assertEqual(response.status_code, status.HTTP_200_OK)

マイグレーション

カスタムユーザーモデルを使用するため以下の手順でマイグレーションを実施します。

python manage.py makemigrations accounts
python manage.py migrate accounts
python manage.py migrate

以下は実行結果の表示を含めたものです。

python manage.py makemigrations accounts

Migrations for 'accounts':
  accounts\migrations\0001_initial.py
    - Create model User
python manage.py migrate accounts

Operations to perform:
  Apply all migrations: accounts
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying accounts.0001_initial... OK
python manage.py migrate

Operations to perform:
  Apply all migrations: accounts, admin, auth, authtoken, contenttypes, sessions
Running migrations:
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying authtoken.0001_initial... OK
  Applying authtoken.0002_auto_20160226_1747... OK
  Applying authtoken.0003_tokenproxy... OK
  Applying sessions.0001_initial... OK

ユニットテスト実施

成功することを確認します。

python manage.py test

~~~

Ran 1 test in 0.326s

OK
Destroying test database for alias 'default'...

スーパーユーザー作成

ここでは、以下を設定することとします。(状況により適切な値を設定してください)

python manage.py createsuperuser

ユーザー名: admin
メールアドレス: admin@example.com
Password: password
Password (again): password
このパスワードは一般的すぎます。
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

WEBブラウザで users APIを確認

開発用サーバーを起動し、表示されたURLに "api/v1/users" を付与したURLにWEBブラウザでアクセスします。

python manage.py runserver
~~~
Starting development server at http://127.0.0.1:8000/

次の画面が表示されることを確認します。
ページネーションを有効にしているので、レスポンスが次の項目で構成されています。

  • count
  • next
  • previous
  • results

Django 管理サイトを確認

開発用サーバーを起動したままで、表示されたURLに "admin" を付与したURLにWEBブラウザでアクセスします。

ログイン画面が表示されたら、スーパーユーザーのユーザー名とパスワードでログインします。

次の画面が表示されることを確認します。

補足: 開発用サーバーがすべてのIPからのリクエストを受け付けるようにする

開発用サーバー起動時にコマンドライン引数を指定します。
前述の ALLOWED_HOSTS = ['*'] (settings.py) の設定がされていることも確認してください。
詳細は開発用サーバーの"ポート番号の変更"項目を参照してください。

python manage.py runserver 0:8000

おわりに

ここを開発スタート地点として、業務的な事柄を扱うアプリを作成し、APIを追加していくことができます。

Discussion