DjangoとReactでPDCAアプリを作る その1

##まず私について
初めまして!

kenshoと申します。
2019年からプログラミングの勉強を本格的に始めて、web制作フリーランスを経験、その後はIT企業のインターン生として働きながら独学でwebエンジニアの勉強をしてきました。
2021年4月から新卒で上場企業のwebエンジニアとして働く予定です。

よかったらTwitterもやっておりますので気軽にフォローお願いします♪

Twitterアカウント↓
[健将@WEBエンジニア×明大生](https://twitter.com/kenshow_study)

##このアプリ開発記事
[DjangoとReactでPDCAアプリを作る その2](https://qiita.com/kenshow-blog/items/83d2d03e306bdecbc83b)

[DjangoとReactでPDCAアプリを作る その3](https://qiita.com/kenshow-blog/items/8e7c32eafc7ad87c3e0a)

[DjangoとReactでPDCAアプリを作る その4](https://qiita.com/kenshow-blog/items/c062312de96cbb084643)


##この記事はどんな人に参考になるのか

<strong>・これからweb系自社開発等に転職、就職を考えている人でポートフォリオを作成したいと考えている人</strong>

<strong>・python、reactの駆け出しのエンジニアの方</strong>

<strong>・他人のアプリ開発の流れを参考にしておきたい方</strong>



今回はdjango(pythonのwebフレームワーク)とreact、TypeScriptで作成したので、例えばDjango以外（railsなど)のフレームワークを使用している方でも、フロント側は参考になるかと思います。

また、reactは今、最も人気のあるフレームワークで、TypeScriptとの相性もとても良いので、ポートフォリオのための言語として採用するのは、とてもアリかと思います。


##アプリ概要

今回は、<strong>PDCAアプリ</strong>を作ってみました。

git⬇️
https://github.com/kenshow-blog/workapplication

1.ログイン画面

![スクリーンショット 2021-03-17 11.14.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1037585/30c112a1-86df-9e1f-85b1-9e7c4b8fcb9e.png)


2.登録画面
![スクリーンショット 2021-03-17 11.14.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1037585/b0e27bf0-afd4-da5c-6381-bf44d6c6af73.png)

3.HOME画面
今までのA(アクション)をまとめて表示することで、日々の記録の中で見えた、自分が改善するべき点をわかるようにしている。

![スクリーンショット 2021-03-17 11.16.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1037585/39b845ec-2006-3efe-d3b9-ecd3a4b709fe.png)


4.PDCA一覧画面
タイトルと日付を表示させている
![スクリーンショット 2021-03-17 11.16.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1037585/03f4dccf-4f81-7d6f-b38b-9dd8ec19ff39.png)

5.新規登録画面
![スクリーンショット 2021-03-17 11.17.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1037585/ce187a0b-4613-e6d9-d47d-bda3a7a2547d.png)


6.詳細画面
![スクリーンショット 2021-03-17 11.17.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1037585/1ce37bcc-d750-5699-1de4-ef9fa04f69db.png)

7.編集画面
![スクリーンショット 2021-03-17 11.17.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1037585/38b50014-ccea-a963-f959-6d7d94bb4ada.png)


###機能詳細
・ログイン機能
・登録機能
・プロフィール画像変更機能
・PDCAの新規作成、更新、削除機能

###使用言語、ライブラリ

```
python3
django(django-rest-frame-work)
react
redux-toolkit
TypeScript
```

###開発に至った経緯
３つあります

1.これから社会人として働くにあたって自身を向上させるためのアプリを開発したかったから
2.フルスタックなスキルを養うため
3.業務効率化アプリは、機能拡張しがいがあったため

##アプリ構造

バックエンド

```
.
├── auth_api
│   ├── __init__.py
│   ├── __pycache__
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   ├── models.py
│   ├── serializers.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── db.sqlite3
├── manage.py
├── media
│   └──avatar
├── pdca
│   ├── __init__.py
│   ├── __pycache__
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   ├── models.py
│   ├── serializers.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
└── workapp
    ├── __init__.py
    ├── __pycache__
    ├── asgi.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

```

フロントエンド

```
.
├── README.md
├── package-lock.json
├── package.json
├── public
├── src
│   ├── App.css
│   ├── App.test.tsx
│   ├── App.tsx
│   ├── app
│   │   └── store.ts
│   ├── features
│   │   ├── auth #認証部分
│   │   │   ├── Auth.module.css
│   │   │   ├── Auth.tsx
│   │   │   └── authSlice.ts
│   │   ├── core　#共通画面（ヘッダーなど）
│   │   │   ├── Core.module.css
│   │   │   └── Core.tsx
│   │   ├── home #ホーム画面
│   │   │   ├── Home.module.css
│   │   │   └── Home.tsx
│   │   ├── pdca　#PDCA部分
│   │   │   ├── DeleteDialog.tsx
│   │   │   ├── Pdca.module.css
│   │   │   ├── Pdca.tsx
│   │   │   ├── PdcaDetail.tsx
│   │   │   └── pdcaSlice.ts
│   │   └── types.ts
│   ├── index.css
│   ├── index.tsx
│   ├── logo.svg
│   ├── react-app-env.d.ts
│   ├── serviceWorker.ts
│   └── setupTests.ts
└── tsconfig.json

```


ここから早速どのように開発していったかを解説していく

##settingsを編集

```
django-admin startproject workapp
django-admin startapp auth_api
```
をしたらworkappのsettings.pyを編集する

```python:workapp/settings.py
# 追記したところのみ記述
INSTALLED_APPS = [
　　　　　　　　　・
　　　　　　　　　・
　　　　　　　　　・
    'corsheaders',
    'rest_framework',
    'auth_api.apps.AuthApiConfig',
    'djoser',#認証関係のものを保管してくれるため
]

#他のアプリからのリクエストを許可する
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
      .
      .
      .
]

#フロント側との通信を許可する
CORS_ORIGIN_WHITELIST = [
    'http://localhost:3000'
]


REST_FRAMEWORK = {
    'DEFAUT_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'#時刻を日本に合わせる

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/

STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL= '/media/'#画像の保存先を設ける

```

jwt認証をするためのパスと画像を保存先のパスを通しておく

```python:workapp/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('authen/', include('djoser.urls.jwt')),
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```
これで、ユーザーがログインする際にjwtトークンを付加しておくことができる


##DjangoでユーザーAPIを作成していく

###モデルの作成

プロフィールモデル

```python:auth_api/models.py
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator
# Create your models here.


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


class Profile(models.Model):
    user_profile = models.OneToOneField(
        User, related_name='user_profile',
        on_delete=models.CASCADE
    )
    #ユーザー登録の際は、画像が設定されてないため,nullをTrueにしておく
    img = models.ImageField(blank=True, null=True,upload_to=upload_avatar_path)


    def __str__(self):
        return self.user_profile.username

```

関数upload_avatar_pathは、プロフィール画像がアップロードされた時に、その画像の名前をそのユーザー情報と関連付けてavatarsディレクトリに保存できるようにしてくれている。

Profileとdjangoのデフォルトで用意されているユーザーモデルとを紐付けている。

adminに、作成したモデル情報を登録する

```python:auth_api/admin.py
from django.contrib import admin
from .models import Profile
# Register your models here.

admin.site.register(Profile)
```

makemigrationsとmigrateをした後、serializersとviewsを作成する

###ユーザー情報のシリアライザーを作成する

まずシリアライザーの役割について、、、、

---------------------------------------
クライアント側と、データベース側のやりとりを担っている。

例えばクライアント側からデータベースへ何か情報をリクエストした時に
データベースがリクエストに応じた情報を返すのだが、その際にserializerによって
それをjson形式に変換して返してあげることができる。

その際password等の他人に見られたくない情報はwrite onlyにしたりすることもできる(passwordをクライアント側に送らないために)

また、serializerはmodelsで設定した条件をvalidationを利用して審査してくれる
例えば「passwordを10文字以上にしないとあかんよ」という条件をmodelsで設定した場合
クライアント側が送ったpasswordがserializerを通る際に10文字以上かを確認してOKだった場合にデータベースにpassword情報を送るようにすることができる
ダメだった場合空の情報を送る

---------------------------------------

serializers.pyを作成する

```auth_api/serializers.py
from rest_framework import serializers
from .models import Profile
from django.contrib.auth.models import User


class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ['id', 'username', 'password']
        extra_kwargs = {'password': {'write_only': True, 'required': True}}

    def create(self, validated_data):
        user = User.objects.create_user(**validated_data)
        return user

class ProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = ['id', 'user_profile', 'img']
        extra_kwargs = {'user_profile': {'read_only': True}}   

```

パスワードはセキュリティ上公開はできないので<strong>write_only</strong>を設定しておく。
また、user_profileは、ユーザーモデルとの外部キーでユーザー側に依存している形なので、プロフィールモデル内で編集されては困るため、<strong>read_only</strong>を設定しておく

###ユーザー情報のviewsを作成
django-rest-frameworkを利用して、ユーザー情報APIの画面を作成する

```python:auth_api/views.py
from rest_framework import status, permissions, generics, viewsets
from .serializers import UserSerializer, ProfileSerializer
from rest_framework.response import Response
from .models import Profile


class CreateUserView(generics.CreateAPIView):
    serializer_class = UserSerializer
    #ユーザー登録画面であるためここは誰でもアクセスを許可させておく↓
    permission_classes = (permissions.AllowAny,)


class LoginUserView(generics.RetrieveUpdateAPIView):
    serializer_class = UserSerializer

    def get_object(self):
        return self.request.user
    
　　 #一度登録したユーザー情報は変更できないように設定しておく
    def update(self, request, *args, **kwargs):
        response = {'message': 'PUT method is not allowed'}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)

class ProfileViewSet(viewsets.ModelViewSet):
    queryset = Profile.objects.all()
    serializer_class = ProfileSerializer

    def perform_create(self, serializer):
        serializer.save(user_profile=self.request.user)

    def get_queryset(self):
        return self.queryset.filter(user_profile=self.request.user)

    def perform_create(self, serializer):
        serializer.save(user_profile=self.request.user)
    
    def destroy(self, request, *args, **kwargs):
        response = {'messeage': 'DELETE method is not allowed'}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)
    def partial_update(self, request, *args, **kwargs):
        response = {'message': 'PATCH method is not allowed'}
        return Response(response, status=status.HTTP_400_BAD_REQUEST)

```

###ユーザー情報APIのパスを通す
ここまできたらそれぞれのviewのパスを通してあげる

```python:auth_api/urls.py
from django.urls import include, path
from rest_framework import routers
from .views import CreateUserView, LoginUserView, ProfileViewSet

router = routers.DefaultRouter()
router.register('profile', ProfileViewSet)

urlpatterns = [
    path('', include(router.urls), name="profile"),
    path('loginuser/', LoginUserView.as_view(), name="loginuser"),
    path('create/', CreateUserView.as_view(), name="create"),
   
]
```

最後にwork_appプロジェクトでauth_api自体のパスを通してあげればユーザー情報API完成🎉

```python:work_app/urls.py

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    #今回追記したところ↓
    path('auth_api/', include('auth_api.urls')),
    path('authen/', include('djoser.urls.jwt')),
]

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

```

##ここまでの感想

ユーザー情報APIの部分は、どのアプリでも必ず作られる部分であるため、慣れてしまえば作成は容易だった。
次回から、PDCAのAPI部分を作成していきます！！

ここまで読んでくださりありがとうございました！🙇‍♂️🙇‍♂️

Twitterでも日々の積み上げや、プログラミング学習についてのツイートをしておりますので、よかったらフォローと応援の程よろしくお願いします！🙇‍♂️


Twitterアカウント↓
[健将@WEBエンジニア×明大生](https://twitter.com/kenshow_study)