1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Django Ninjaを使ってみた Vol.2 (User認証編)

Last updated at Posted at 2023-03-13

環境

MacOS Ventura
Python 3.10.7
Poetry 1.3.2

はじめに

この記事はこちらの続きです
https://qiita.com/ps0317ix/items/344471dd89a10ab520cd

早速始めよう

まずは、user用のディレクトリを作っていきます

terminal
python manage.py startapp user

models.pyを以下の内容に変更します。
今回はDjango-ninjaがメインなので、Djangoのモデルなどに関する詳しい説明は割愛

user/models.py
from django.db import models
from django.contrib.auth.models import PermissionsMixin, Group, Permission
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.core.mail import send_mail
from django.utils.translation import gettext_lazy as _


class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, username, email, password, **extra_fields):
        if not email:
            raise ValueError('Emailを入力して下さい')
        email = self.normalize_email(email)
        username = self.model.normalize_username(username)
        user = self.model(username=username, email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self.db)
        return user

    def create_user(self, username, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(username, email, password, **extra_fields)

    def create_superuser(self, username, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        if extra_fields.get('is_staff') is not True:
            raise ValueError('is_staff=Trueである必要があります。')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('is_superuser=Trueである必要があります。')
        return self._create_user(username, email, password, **extra_fields)

    def update_or_create_user(self, username=None, email=None, password=None, username_new=None, **extra_fields):
        try:
            user = self.get(email=email)
            updated = False
            if username_new:
                user.username = username_new
                updated = True
            for key, value in extra_fields.items():
                if getattr(user, key) != value:
                    setattr(user, key, value)
                    updated = True
            if updated:
                user.save(using=self.db)
        except self.model.DoesNotExist:
            user = self.create_user(username, email, password, **extra_fields)
        return user


class User(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(_("username"), max_length=50, blank=True)
    email = models.EmailField(_("email"), unique=True)
    avatar = models.ImageField(_("avatar"), upload_to='avatars', blank=True)
    avatar_url = models.URLField(_("avatar_url"), max_length=1500, blank=True)
    router = models.JSONField(_("router"), blank=True, null=True)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    groups = models.ManyToManyField(Group, verbose_name=_("groups"), blank=True, related_name="users")
    user_permissions = models.ManyToManyField(Permission, verbose_name=_("user permissions"), blank=True, related_name="users")
    created_at = models.DateTimeField(_("created_at"), auto_now_add=True)
    updated_at = models.DateTimeField(_("updated_at"), auto_now=True)

    objects = UserManager()
    USERNAME_FIELD = "email"
    EMAIL_FIELD = "email"
    REQUIRED_FIELDS = ['username']

    class Meta:
        db_table = 'user_user'
        verbose_name = "user"
        verbose_name_plural = "users"

    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def email_user(self, subject, message, from_email=None, **kwargs):
        send_mail(subject, message, from_email, [self.email], **kwargs)

続いてスキーマを定義します。

user/schema.py
from pydantic.types import SecretStr
from ninja import Schema
from ninja import ModelSchema
from django.contrib.auth import get_user_model


class UserSchema(ModelSchema):
    class Config:
        model = get_user_model()
        model_fields = ['id', 'username', 'email', 'is_staff', 'is_active', 'is_superuser']


class UseLogin(Schema):
    username: str
    email: str
    password: SecretStr


class UserIn(Schema):
    id: str
    username: str
    email: str
    openai_api_key: str


class UserOut(Schema):
    id: int
    username: str
    email: str
    roles: list[str] = ['client']

上記ができたらapiを作成していきます

user/api.py
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model

from ninja_extra import (
    http_get, http_post, http_generic, http_put, http_delete,
    api_controller, status, ControllerBase, pagination
)
from ninja_extra.controllers.response import Detail
from ninja_jwt.authentication import JWTAuth

from .schema import UserSchema, UseLogin, UserIn, UserOut,

@api_controller('user', tags=['User'], auth=[JWTAuth()])
class UserController(ControllerBase):
    user_model = get_user_model()

    @http_get("/me", response=UserOut)
    def get_user(self, request):
        roles = []
        if request.user.is_superuser:
            roles.append('admin')
        elif request.user.is_staff:
            roles.append('staff')
        else:
            roles.append('client')

        if len(request.user.avatar_url) > 0:
            avatar = request.user.avatar_url
        else:
            avatar = request.user.avatar
        return {
            'id': request.user.id,
            'username': request.user.username,
            'email': request.user.email,
            'avatar': avatar,
            'roles': roles
        }

    @http_post('/create', response=UserSchema)
    def create_user(self, payload: UseLogin):
        return User.objects.create_user(**payload.dict())

    @http_put('/update', response=UserSchema)
    def update_user(self, request, payload: UserIn):
        user = self.user_model.objects.update_or_create_user(
            username=request.user.username,
            email=request.user.email
        )
        return user

    @http_delete('/{int:user_id}', response=Detail(status_code=status.HTTP_204_NO_CONTENT))
    def delete_user(self, user_id: int):
        user = self.get_object_or_exception(self.user_model, id=user_id)
        user.delete()
        return self.create_response('', status_code=status.HTTP_204_NO_CONTENT)

DJANGO_NINJA_TEMPLETEディレクトリに戻り、上記のController, appsを登録

DJANGO_NINJA_TEMPLETE/api.py
from ninja_jwt.controller import NinjaJWTDefaultController
from ninja_extra import NinjaExtraAPI

from user.api import UserController # 追加

api = NinjaExtraAPI()
api.register_controllers(NinjaJWTDefaultController)
api.register_controllers(UserController) # 追加
DJANGO_NINJA_TEMPLETE/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user', # 追加
]

migrationファイルを生成

terminal
python manage.py makemigrations
# ImageFieldを入れる場合、pip install Pillowしてください

そして、migrateを生成

terminal
python manage.py migrate

http://127.0.0.1:8000/api/docs
に遷移し、鍵マーク付きのエンドポイントが生成されていたら成功
スクリーンショット 2023-03-13 19.13.46.png

確認

以下のコマンドでsuperuserを作成

terminal
python manage.py createsuperuser

/api/token/pairのエンドポイントで、登録したユーザー情報を入力し、tokenが生成されたら成功!!
スクリーンショット 2023-03-13 19.17.00.png

最後に

圧倒的に少ないコード量で実装できて、速度もDjangoより早くて、docsなども自動生成されて、革命的に感じるのは私だけですかね。

こういったサービス運営やってるので、気になる方はぜひ
https://data-lab.project-g.co.jp?utm_source=Qiita&utm_medium=qiita&utm_campaign=qiita_20230313

参考

※2023/05/17追記
デプロイ編を公開しました
https://qiita.com/ps0317ix/items/07af7a863ff63ad3dc81

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?