LoginSignup
1
3

More than 1 year has passed since last update.

バックエンド編: Django REST frameworkとReact(TypeScript)でInstagramのクローンアプリを作成

Last updated at Posted at 2021-03-24

事前知識

  • anaconda navigatorはPython開発のためのディストリビューション(コンパイル済みのソフトウェアの集まり)
  • jwtという認証トークンを使用する
  • djoserとはjwtトークンを簡単に使用するためのサードパーティー
  • django-cors-headersはReactからのアクセスを許可するために必要になってくる
  • Pillowは画像データを扱うために使用する

仮想環境を構築する

  1. Environments項目のCreateを選択
  2. Nameを入力し、Packages(Python 3.7)を選択
  3. 再生ボタンをクリックし、ターミナルを開く
  4. anaconda navigatorにて仮想環境(python3.7)の作成し、必要なものをインストール(下記コマンド)
$ pip install django==3.0.7
$ pip install djangorestframework==3.10
$ pip install djangorestframework-simplejwt==4.6.0
$ pip install djoser
$ pip install django-cors-headers
$ pip install Pillow

上記のコマンドにより各ライブラリ?のインストールが完了したら、anaconda navigatorとターミナルを閉じて、anaconda navigatorで作成した仮想環境とPycharmのプロジェクトに紐付ける

仮想環境とPycharmを紐付ける

  1. 空のディレクトリ(筆者はapi_insta)を作成し、Openを選択し、Pycharmで開く
  2. Preferences...を選択し、Projectの中のPython Interpreterを選択
  3. 右上の設定ボタン(歯車マーク)を選択し、Addを選択
  4. Existing envirionmentにチェックを入れ、右にある...を選択
  5. /Users/ホームディレクトリ/opt/anaconda3/envs/API_insta/bin/pythonを選択しOK

プロジェクトを作成する

$ django-admin startproject api_insta .

アプリを作成する

$ django-admin startapp api

現時点でのローカル環境を起動する

  1. manage.pyを右クリックし、Run 'manage'を選択
  2. 右上のmanageをクリックし、Edit Configulations...を選択
  3. Parametersという項目にrunserverを追加してOK
  4. 右上の再生ボタンをクリックで、ローカル環境を起動

モデルの作成

UserManagerではデフォルトではuser nameをpasswordで認証する様になっているのでemailとpasswordで認証するようにオーバーライドする必要がある

models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.conf import settings

def upload_avatar_path(instance, filename):
    ext = filename.split('.')[-1]
    return '/'.join(['avatars', str(instance.userProfile.id)+str(instance.nickName)+str(".")+str(ext)])

def upload_post_path(instance, filename):
    ext = filename.split('.')[-1]
    return '/'.join(['posts', str(instance.userPost.id)+str(instance.title)+str(".")+str(ext)])

class UserManager(BaseUserManager):
    def create_user(self, email, password=None):
        if not email:
            raise ValueError('email is must')

        user = self.model(email=self.normalize_email(email))
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password):
        user = self.create_user(email, password)
        user.is_staff = True
        user.is_superuser = True
        user.save(using= self._db)
        return user

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(max_length=50, unique=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'

    def __str__(self):
        return self.email

class Profile(models.Model):
    nickName = models.CharField(max_length=20)
    userProfile = models.OneToOneField(
        settings.AUTH_USER_MODEL, related_name='userProfile',
        on_delete=models.CASCADE
    )
    created_on = models.DateTimeField(auto_now_add=True)
    img = models.ImageField(blank=True, null=True, upload_to=upload_avatar_path)

    def __str__(self):
        return self.nickName

class Post(models.Model):
    title = models.CharField(max_length=100)
    userPost = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='userPost',
        on_delete=models.CASCADE
    )
    created_on = models.DateTimeField(auto_now_add=True)
    img = models.ImageField(blank=True, null=True, upload_to=upload_post_path)
    liked = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='liked', blank=True)

    def __str__(self):
        return self.title

class Comment(models.Model):
    text = models.CharField(max_lengt=100)
    userComment = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='userComment',
        on_delete=models.CASCADE
    )
    post = models.ForeignKey(Post, on_delete=models.CASCADE)

    def __str__(self):
        return self.text
  • normalze_email(email)はemailを正規化してuserをいう変数を作成している
  • set_password(password)はpasswordをハッシュ化している
  • データベースに保存
  • UserManagerをオーバーライドした場合はcreateSuperUserもオーバーライドする必要がある
  • Userモデルもemailバージョンにオーバーライドする

Active, Staff, Superuserの違いについて

Active: is_activeに指定する真偽値でアカウントの有効無効の切り替え(無効にするとログインできなくなる)
Staff: is_staggに指定する真偽値でstaff権限の有効無効の切り替え(staff権限はadminのダッシュボードにログインする権限)
Superuser: is_superuserに指定する真偽値で有効無効の切り替え(superusreはstaff権限に加え、データベースの内容の変更などの全権限を付与する)

settings.pyを編集

settings.py
from datetime import timedelta               #追記

INSTALLED_APPS = [
    #省略
    'rest_framework',                        #追記
    'djoser',                                #追記
    'api.apps.ApiConfig',                    #追記
    'corsheaders',                           #追記
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', #追記
    #省略
]

#以下を追記
CORS_ORIGIN_WHITELIST = [
    "http://localhost:3000"
]

WSGI_APPLICATION = 'api_insta.wsgi.application'

#以下を追記
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}

#以下を追記
SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('JWT',),
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
}

TIME_ZONE = 'Asia/Tokyo'                     #UTCから変更
AUTH_USER_MODEL = 'api.User'                 #追記
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') #追記
MEDIA_URL = '/media/'                        #追記
  • jwtトークンの有効期限を設定するためにtimedeltaを導入
  • INSTALLED_APPに導入したアプリを追加
  • djangoは.(ドット)で繋ぐことができるため、api.apps.ApiConfigとはapiディレクトリのapps.pyのApiConfigのことを指す
  • MIDDLEWAREにcorsに関するコードを追記
  • reactからのアクセスを許可するためにCORS_ORIGIN_WHITELISTを追加する
  • djangoのデフォルトの認証関連を設定する
  • views.pyを特定のユーザーだけに見せるようにするためにDAFAULT_PERMISSION_CLASSEを設定する
  • jwtを使用した認証をしたいため、DEFAULT_AUTHENTICATION_CLASSES
  • SIMPLE_JWTによってトークンの有効期限を60分に指定している
  • djangoにデフォルトのUserモデルではなく、カスタマイズしたUserモデルを使用するように明示的にする必要がある
  • プロジェクト直下にmediaディレクトリを作成し、画像の保存先を明示的にする(MEDIA_ROOTとMEDIA_URL)
  • migrateコマンドはデフォルトでsqlite3に変換してくれる

データベースに反映させる

$ python manage.py makemigrations
$ python manage.py migrate

ダッシュボードで追加したモデル内容を確認する

admin.py
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.translation import gettext as _
from . import models

class UserAdmin(BaseUserAdmin):
    ordering = ['id']
    list_display = ['email']
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal Info'), {'fields': ()}),
        (
            _('Permissions'),
            {
                'fields': (
                    'is_active',
                    'is_staff',
                    'is_superuser',
                )
            }
        ),
        (_('Important dates'), {'fields': ('last_login',)}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2')
        }),
    )

admin.site.register(models.User, UserAdmin)
admin.site.register(models.Profile)
admin.site.register(models.Post)
admin.site.register(models.Comment)
  • オーバーライドせずに作成したモデル(PostやProfileモデルなど)はadmin.site.registerによってadmin dashboardで確認したい項目を追加できる
$ python manage.py createsuperuser
  • 管理者権限をもったユーザーの作成

シリアライザーの作成

シリアライザーとは

データベースからクライアント側にオブジェクトを渡す時にjsonに変換してくれたり、クライアント側からemailやpasswordなどをデータベースに渡す時に精査(validation)してくれたりするもの。
又、新規でモデルを作成した際にセットで作るもの。(User, Profile, Post, Commentの4つのシリアライザーを作成する)

  1. アプリケーションディレクトリ(startappコマンドで作成したディレクトリ)直下にserializers.pyを作成
serializers.py
from django.contrib.auth import get_user_model
from rest_framework import serializers
from .models import Profile, Post, Comment

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = get_user_model()
        fields = ('id', 'email', 'password')
        extra_kwargs= {'password': {'write_only': True}}

    def create(self, validated_data):
        user = get_user_model().objects.create_user(**validated_data)
        return user
1
3
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
3