ℹ️

Django Ninjaでアプリケーションを作成

に公開

アプリケーションの立ち上げ

今回は、CustomUserをもとにしたユーザー管理用のシステムを作成します。

poetry run python manage.py startapp accounts

アプリケーションの登録

プロジェクトのsettings.pyにアプリケーションを登録します。

backend/project/settings.py
INSTALLED_APPS = [
    'accounts.apps.AccountsConfig', # add
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

モデルの設定

accountsのmodels.pyとadmin.pyはすでに作成していますので、こちらの記事を参考にしてください。
https://zenn.dev/keita_f/articles/6ce5cc265d3f1f

マイグレーションを実行していない場合は、下記により実行してください。

poetry run python manage.py makemigrations
poetry run python manage.py migrate

Django Ninjaの設定

今回はDjango Ninjaを使用してAPIを開発していきます。

Djangoのモデルからスキーマを生成

Django NinjaはDjnagoで設定したモデルからAPI用のschemaを生成することができます。

Django Ninjaの公式ドキュメントはこちらです。
https://django-ninja.dev/guides/response/django-pydantic/

APIのコア部分を作る

それではDjango Ninjaを使用して実際にAPIを作成していきます。

routerの設定

Django NinjaでもFastAPIと同様にrouterの概念があり、複数のモジュールでAPIを持つことができるようになっています。
それでは実際にAPIを作成していきます。

backend/accounts/api.py
from ninja import Router
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from accounts.models import CustomUser
from accounts.schemas import UserOut, UserCreate, TokenSchema
from django.contrib.auth.hashers import make_password  # パスワードハッシュ化のため
from typing import List, Optional


from .utils import create_jwt_token
from .auth import JWTAuth

router = Router(tags="Auth API")


@router.post("/register/", response=UserOut, summary="ユーザー登録")
def register_user(request, user_data: UserCreate):
    """
    ユーザー登録を行うAPIエンドポイント。
    """
    # パスワードをハッシュ化
    hashed_password = make_password(user_data.password)

    # usernameが既に存在するか確認
    if CustomUser.objects.filter(username=user_data.username).exists():
        raise Exception("Username already exists")  # エラーメッセージを返す

    # emailが既に存在するか確認
    if CustomUser.objects.filter(email=user_data.email).exists():
        raise Exception("Email already exists")  # エラーメッセージを返す

    user = CustomUser.objects.create(
        username=user_data.username,
        email=user_data.email,
        password=hashed_password,  # ハッシュ化されたパスワードを保存
        is_staff=user_data.is_staff,
        is_superuser=user_data.is_superuser,
    )
    return UserOut.from_orm(user)


@router.post("/login/", response=TokenSchema, summary="ログイン")
def user_login(request, email: str, password: str):
    """
    ログインAPIエンドポイント。
    """
    user = authenticate(request, email=email, password=password)
    if user:
        login(request, user)
        token = create_jwt_token(user)
        return {"access_token": token}
    else:
        raise Exception("Invalid credentials")  # エラーメッセージを返す


@router.post("/logout/", summary="ログアウト")
def user_logout(request):
    """
    ログアウトAPIエンドポイント。
    """
    logout(request)
    return {"message": "Logged out"}


@router.get(
    "/users/me/", response=UserOut, auth=JWTAuth(), summary="ログインユーザー情報"
)
def get_me(request):
    """
    ログインしているユーザーの情報を返すAPIエンドポイント。
    """
    user = request.auth
    return UserOut.model_validate(user)


@router.get(
    "/users/{user_uuid}",
    response=UserOut,
    auth=JWTAuth(),
    summary="ユーザーIDでユーザー取得",
)
def get_user(request, user_uuid: str):
    """
    指定されたユーザーIDのユーザー情報を返すAPIエンドポイント。
    """
    user = get_object_or_404(CustomUser, uuid=user_uuid)  # uuidでユーザーを取得
    return UserOut.model_validate(user)


@router.get(
    "/users/", response=List[UserOut], auth=JWTAuth(), summary="ユーザー一覧取得"
)
def get_users(request, is_staff: Optional[bool] = None):
    """
    ユーザー一覧を取得するAPIエンドポイント。
    is_staffでフィルタリングも可能。
    """
    users = CustomUser.objects.all()
    if is_staff is not None:
        users = users.filter(is_staff=is_staff)
    user_list = []  # UserOutのリストを格納する
    for user in users:
        user_info = UserOut(
            uuid=user.uuid,
            username=user.username,
            email=user.email,
            is_active=user.is_active,
            is_staff=user.is_staff,
            date_joined=user.date_joined,
        )
        user_list.append(user_info)
    return user_list

routerに関するドキュメントはこちらです。
https://django-ninja.dev/guides/routers/

APIエンドポイントの作成

Django Ninjaの場合、apiのエンドポイントは単一のエンドポイントとなります。
NinjaAPIのインスタンスを作成し、この後設定する各API routerを読み込みます。

backend/backend/urls.py
from django.contrib import admin
from django.urls import path
from ninja import NinjaAPI
from accounts.api import router as accounts_router

api = NinjaAPI(
    description="Admin page: [Admin](http://127.0.0.1:8000/admin)",
)

api.add_router("/accounts/", accounts_router, tags=["Accounts"])

urlpatterns = [path("admin/", admin.site.urls), path("api/", api.urls)]

urls.pyでaccountsに関するrouterを読み込み、アクセスできるようにしています。
Django NinjaではOpenAPIの使用しているため、単一のエンドポイントから複数のエンドポイントにアクセス、実行できます。

APIの確認

poetry run python manage.py runserver

サーバーを立ち上げて、http://0.0.0.0:8000/api/docs/にアクセスし、最終的にこのような画面が表示されれば完成です。
最終的なAPIの画面の例

今回のAPI作成はこちらの記事も参考にしています。
https://testdriven.io/blog/django-and-pydantic/#django-ninja

Discussion