備忘録:簡易Youtubeクローン作成[バックエンド]

#概要
- [DjangoとReactで簡易Youtubeを作る](https://qiita.com/enuii3/items/d0621e6029b7e9b30709)

- [Anacondaでプロジェクトを作成](https://qiita.com/ezaqiita/items/c109d9b8e21a1db42085)

- [バックエンドの作成](https://qiita.com/enuii3/items/1a9da78fdc080fe2adc5)👈今ここ

- [フロントエンドの作成](https://qiita.com/drafts/0e9078b6f7ba81a90a53/edit)

#バックエンド(Django)の作成
```
$ pip install Django
$ pip install djangorestframework
$ pip install djangorestframework-simplejwt==4.1.2  (バージョン4.1.2のjwtを導入)
$ pip install djoser  (トークン関係)
$ pip install pillow  (画像関係)
$ pip install django-cors-headers （クロスオリジンで繋げられるようにしたいから）
$ Django-admin startproject プロジェクト名 (プロジェクト作成される？)
$ Django-admin startapp アプリケーション名 (アプリケーションが作成される？)
```
- Manage.pyを右クリックで”run manage”を選択
- parametersをrunserverに変更すると、再生ボタンを押下した時にローカル環境が立ち上がるように修正


####INSTALLED_APPSに以下を追加
```settings.py
INSTALLED_APPS = [
    （省略） #以下の4行を追加
    'rest_framework', 
    'api.apps.ApiConfig', (apiディレクトリの中にある、apps.pyの中にある、ApiConfigをさす)
    'corsheaders' ,
    'djoser',
]
```


####MIDDLEWAREの箇所に以下を追加 
```settings.py
'conrsheaders.middleware.CorsMiddleware' (フロントエンドとの連携のため？)
```


####フロントエンドからのアクセスを許可するために以下を追加
```
 CORS_ORIGIN_WHITELIST = ['http://localhost:3000']
```


####認証の権限を指定
```settings.py
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}
```
- 通常はview事に権限を書くが、settings.pyに書いておくとベースができる、ベースの権限に対してカスタマイズした権限を与えたい場合の差分のみ各viewに書く


####jwtの設定(今回の認証はjwtの認証を使いたいので以下を追加)
```settings.py
from datetime import timedelta

SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('JWT',),
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
}
```
- 認証が通ったユーザーがviewを見ることができるというパーミッションをデフォルトで定義
- 認証にはJWTを使うことを定義

- ヘッダーのタイプがjwt
- トークンの有効期限（ex timedeltaで有効期限を30分に指定している)


####画像データを扱えるようにする
```settings.py
(省略) #以下の2行を追加
MEDIA_ROOT = BASE_DIR / 'media'
MEDIA_URL = '/media/'
```
- プロジェクトの直下に画像データの保管場所としてmediaディレクトリを作成


####カスタマイズされたモデルを使用することを明示する
```
AUTH_USER_MODEL = 'api.User'
```
- apiディレクトリの中のUserモデルを参照するように明示する


####DjangoとReactのパスを紐付ける
```プロジェクト名/urls.py
(省略) #以下の1行を追加
from django.conf.urls import include

urlpatterns = [
    #以下の2行を追加
    path('api/', include('api.urls')),
    path('authen/', include('djoser.urls.jwt')),
]
```
- 'api/'にアクセスがあった際に'api/urls'を参照する (アプリケーション名ディレクトリにurls.pyを作成)
- 'authen/'にpostメソッドでメールアドレスとパスワードを渡すとjwtのトークンを返す

```アプリケーション名/urls.py
from rest_framework import routers
from django.urls import path
from django.conf.urls import include

router = routers.DefaultRouter()

urlpatterns = [
   path('', include(router.urls)),
]
```

####MEDIA_URLとMEDIA_ROOTを追加
```settings.py
from django.conf.urls.static import static
from django.conf import settings

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```


####データベースの作成
```models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
import uuid

def load_path_video(instance, filename):
    return '/'.join(['video', str(instance.title)+str(".mp4")])

def load_path_thum(instance, filename):
    ext = filename.split('.')[-1]
    return '/'.join(['thum', str(instance.title)+str(".")+str(ext)])
 
class UserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError('Email address is must')

        user = self.model(email=self.normalize_email(email), **extra_fields)
        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, Permissions Mixin):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    email = modelsEmailField(max_length=255, unique=True)
    username = modelsCharField(max_length=255, blank=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 Video(models.Model):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    title = models.CharField(max_length=255, unique=True)
    video = models.FileField(blank=False, upload_to=load_path_video)
    thum = models.ImageField(blank=False, upload_to=load_path_thum)
    like = models.IntegerField(defualt=0)
    dislike = models.IntegerField(defualt=0)
 
    def __str__(self):
        return self.title
```
- AbstractBaseUserとBaseUserManagerはuseModelの編集に必要
- uuidはuniversal unique idの略で自動で生成される数字のpkを文字列に変える？
- 継承元のBaseUserManagerにはcreateuserやcreatesuperuserが予め定義されているがuser.nameをベースとして認証になっているので, user.emailをベースに認証するようにオーバーライドする
- normalize_emailは渡されたメールアドレスを正規化して小文字にしている
- primary_key=Trueはuuidで生成される文字列をpkとして扱う宣言になり、editableは編集を不可にする宣言
- mediaディレクトリの中に'video'というサブディレクトリを作成し、選択された動画タイトル名と拡張子(.mp4)をつけて保存する
- サムネイル(thum)は拡張子が今後変わる可能性があるので、拡張子を抜き出す(split関数で引数に渡されたfilenameの後ろから初めにヒットする.(ドット)までを取り出し、ext変数に代入)
- mediaディレクトリにサブディレクトリとして'thum'を作成し、動画タイトル名と.(ドット)とext(取り出した拡張子)を結合して保存

####admin siteの構築
```admin.py
(省略) #以下の3行を追記
from .models import Video, User 
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.translation import gettext as _

class UserAdmin(BaseUserAdmin):
    ordering = ['id']
    list_display = ['email', 'password']
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal Info'), {'fields': ('username',)}),
        (
            _('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(User, UserAdmin)
admin.site.register(Video)
```
- admin.pyにadmin siteで管理したいモデルをimportし、admin.site.registerの引数に管理したいモデルを渡すとadmin siteで管理可能になる
- UserAdminだとuser.nameを元に認証するのでuser.emailを元に認証するようにオーバーライドさせる

####シリアライザーの作成
```serializers.py
from rest_framework import serializers
from django.contrib.auth import get_user_model
from .models import User, Video

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

    #渡されたパスワードをハッシュ化して保存している
    def create(self, validated_data):
        user = get_user_model().objects.create_user(**validated_data)

        return user

class VideoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Video
        fields = ['id', 'title', 'video', 'thum', 'like', 'dislike']
```

- アプリケーションディレクトリ？の直下にserializer.pyを作成
- metaでserializerを装飾することができる？
- create_userはmodels.pyで定義していたUserManagerのcreate_userメソッドの事
- ser_passwordメソッドは引数に渡されたパスワードをハッシュ化している

####views.pyの作成
```views.py

from rest_framework import viewsets
from rest_framework import generics
from .models import Video
from .serializers import VideoSerializer, UserSerializer

class CreateUserView(generics.CreateAPIView):
    serializer_class = UserSerializer
    permission_classes = (AllowAny, )

class VideoViewSet(viewsets.ModelViewSet):
    queryset = Video.objects.all()
    serializer_class = VideoSerializer
```
- Userを新規で作成するviewを作成する
- 動画の一覧の取得や作成、削除などを取り扱うviewを作成する
- genetics(汎用api)のCreateAPIViewを継承したCreateUserViewを作成

####viewsとurlsを紐づける
```urls.py
(省略) #以下を追加
from .views import VideoViewSet, CreateUserView

router = routers.DefaultRouter()
router.register('videos', VideoViewSet)

urlpatterns = [
    path('create', CreateUserView.as_view(), name='create'),
    path('', include(router.urls)),
]
```
- VideoViewSetをrouterに追加