DjangoとReactで自分用日報アプリを作る~①Django編~

#はじめに
Django Rest FrameworkとReactで自分用の日報アプリを作りました:grinning:
その手順をまとめておきたいと思います。 
長くなると思うので ①Django編 ②React編 の2つの記事に分けて書きます。
この記事は①Django編になります。
②React編は[こちら](https://qiita.com/Piyopanman/items/0a757a7b2dd412e07135)


#完成させたもの
頑張って作ったのでよかったら[完成品](https://piyopanman.com/)見てください:kissing_heart:
![完成品.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/671055/333e4725-bbd5-739e-ae4e-2f1aeda961dd.png)

#この記事で説明すること
この日報アプリはDjango Rest Framework(DRF)でAPIを作ってReactでそのAPIを叩く、という形にしてあり、今回は「Django Rest FrameworkでAPIを作る」部分を説明します。さらに、マークダウン記法で書きたいので、そこの実装も紹介しようと思います。

#環境
- django 2.2.16 
- djangorestframework 3.12.1
- django-cors-headers 2.4.0
- django-markdownx 3.0.1
- node 14.11.0
- npm 6.14.8
- react 17.0.1
- marked 1.2.7
- react-router-dom 5.2.0



#Djangoプロジェクト・アプリケーションの作成

1. プロジェクトやアプリケーションをいれるフォルダを作る(ここではbackendという名前)
2. backendに移動
3. djangoをインストール
4. djangorestframeworkをインストール
5. markdownxをインストール  
6. shellを起動
7. daily_apiという名前のプロジェクトを作る
8. dailyという名前のアプリケーションを作る
9. マイグレートしてデータベースを作る

```bash
$ mkdir backend
$ cd backend
$ pipenv install django
$ pipenv install djangorestframework
$ pipenv install django-markdownx
$ pipenv shell
(backend) $ django-admin startproject daily_api .
(backend) $ python manage.py startapp daily
(backend) $ python manage.py migrate
```

#settings.py
次にプロジェクトのsettings.pyの編集をします。
ここのINSTALLED_APPSの追加で、

- DRFを使うよ！
- markdownxを使うよ！
- dailyという名前のアプリケーションを管理するよ！

ってことをプロジェクトに教えています。

REST_FRAMEWORKの部分はDRFの設定です。

```python:daily_api/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',  #これと
    'markdownx', #これと
    'daily',  #これ
]

REST_FRAMEWORK = { #これも追加
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
}

```

DEFAULT_PERMISSION_CLASSESは「誰にアクセスを許可するか」を指定しています。
今回はgetリクエストしかない(後ほどviews.pyのところで作る) & 誰にでも日報を見て欲しいのでAllowAnyで誰でもアクセスできるようにしてありますが、postとかdeleteとかがあるのにAllowAnyにしてると誰でも日報を追加したり削除できたりしちゃって大変なことになります:scream:
なのでpostリクエストなどがある場合は AllowAny を IsAuthenticatedOrReadOnly に変えるといいと思います。これを指定すると、get,head,optionsリクエストは誰にでも許可されますが、それ以外のpost,deleteなどのリクエストは認証済みのユーザにしか許可されなくなります。
詳しくは[こちらのDRFのドキュメント](https://www.django-rest-framework.org/api-guide/permissions/)で確認できます。

ここまでで一旦うまく行っているか確認します。そのために、

```bash

(backend)$ python manage.py runserver
```
とターミナルで打ってhttp://127.0.0.1:8000/ にアクセスしてみましょう。なんかロケットの画像が表示されてたら成功です:thumbsup:

#models.py
日報のモデルを定義していきます。モデルはデータベースに格納されます。
また、ここで先ほどインストールしたmarkdownxというライブラリを使います。MarkdownxFieldをインポートしてマークダウン記法で書きたいところにMarkdownxFieldを指定するだけでオッケーです。そうするとこの後に設定するadminページでリアルタイムプレビューを見ることができるようになります。

```python:daily/models.py
from django.db import models
from markdownx.models import MarkdownxField


class Daily(models.Model):
    date = models.DateField() #日付
    univ = MarkdownxField() #大学の課題とか
    study = MarkdownxField() #勉強したこと
    other = MarkdownxField() #その他
    first_meet = MarkdownxField() #初めて知ったこと
    wanna_do = MarkdownxField() #やりたいこと
    summary = MarkdownxField() #1日のまとめ
    evaluation = models.ForeignKey('Evaluation', on_delete=models.PROTECT) #1日の評価(外部キー)
    isOpen = models.BooleanField(default=True)　#公開/非公開

    def __str__(self):
        date_str = self.date.strftime('%Y/%m/%d')
        return date_str


class Evaluation(models.Model):
    evaluation = models.CharField(max_length=255)

    def __str__(self):
        return self.evaluation

```

次に以下のコマンドを入力してマイグレーションファイルを作り、データベースに反映させます。

 ```brew
$ python manage.py makemigrations daily
$ python manage.py migrate daily
```

#admin.py

Djangoにはadminページがあります。そこで日報の追加・編集・削除を行えるようにするためにadmin.pyを編集します。

```python:daily/admin.py

from django.contrib import admin
from .models import Daily, Evaluation
from markdownx.admin import MarkdownxModelAdmin

admin.site.register(Evaluation)
admin.site.register(Daily, MarkdownxModelAdmin)
```
これでadminページで日報を管理できるようになりました。
また、adminページにログインするにはsuperuserの設定も必要なのでやっておきます。

```bash
$ python manage.py createsuperuser
```

ユーザー名、メールアドレス、パスワードを設定したらhttp://127.0.0.1:8000/admin/ にアクセス・ログインできることを確認しましょう。
ここで適当に日報のデータを2,３個作っておくと、APIいい感じに作れてるかな？ってあとでチェックするときに役立つので作っておくといいです。この時にマークダウン記法でかくとプレビューが表示されると思います。便利！
あと私の場合、evaluationには"perfect","good","soso","bad" というデータを入れました。

![admin1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/671055/e76b2f04-1c04-2c7e-f76c-6d662f10847d.png)

![admin2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/671055/552da5dc-fd88-8441-e26f-cb5efb66c457.png)

ここまではほぼDjangoと同じ流れでした。次からはDjango Rest Frameworkに則った開発をしていきます。

#urls.py
まずプロジェクトの中のurls.pyの編集をします。

```python:daily_api/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('daily/', include('daily.urls')),  #これと
    path('markdownx/', include('markdownx.urls')),  #これを追加
]
```

これは、http://127.0.0.1:8000/daily/ ってURLがきたら、dailyというアプリケーションのurls.pyの方を確認してね、という意味です。
（Djangoプロジェクトは複数のアプリケーションを管理できるのでdaily以外のアプリケーションが増えた場合にもここを編集することになります。）
その下の行はmarkdownxの設定です。
dailyのurls.pyの方を確認してね、としたのでそちらも編集しましょう。

```python:daily/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.ListDaily.as_view()), #日報一覧
    path('<int:pk>/', views.DetailDaily.as_view()), #1日の詳細
    path('<str:cat>/', views.CategoryDairy.as_view()), #カテゴリ別一覧
]
```
これは

- http://127.0.0.1:8000/daily/ というURLがきたらviewsのListDailyをみてね
- http://127.0.0.1:8000/daily/1/ とか http://127.0.0.1:8000/daily/2/ というURLがきたらviewsのDetailDailyをみてね
- http://127.0.0.1:8000/daily/study/ とか http://127.0.0.1:8000/daily/wanna_do/ というURLがきたらviewsのCategoryDairyをみてね

ということです。
つまりここでは http://127.0.0.1:8000/daily/ 以降のパスを指定しているということです。

#views.py
続いてviews.pyを編集していきます。ここではJSONを返す処理を書いていきます。

```python:views.py

from django.shortcuts import render
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status, viewsets, filters

from .models import Daily


class ListDaily(APIView):
    def get(self, request):
        try:
            daily = Daily.objects.filter(isOpen=True).order_by('-date')
            res_list = [
                {
                    'id': d.id,
                    'date': d.date,
                    'evaluation': d.evaluation.evaluation,
                }
                for d in daily
            ]
            return Response(res_list)
        except:
            return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)

class DetailDaily(APIView):
    def get(self, request, pk):
        try:
            try:
                daily = Daily.objects.get(id=pk)
            except:
                error_msg = "そんなidの日報はないよ！"
                return Response(error_msg, status=status.HTTP_404_NOT_FOUND)
            res = {
                'id': daily.id,
                'date': daily.date,
                'study': daily.study,
                'other': daily.other,
                'first_meet': daily.first_meet,
                'wanna_do': daily.wanna_do,
                'summary': daily.summary,
            }
            return Response(res)
        except:
            return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class CategoryDairy(APIView):
    def get(self, request, cat):
        try:
            daily = Daily.objects.filter(isOpen=True).values_list(
                'date', cat).order_by('-date')

            res_list = [
                {
                    'date': d[0],
                    'content': d[1],
                }
                for d in daily
            ]

            return Response(res_list) 
        except:
            return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
```

どれもAPIViewを継承して作っています。GerericsViewというやつを継承するとスッキリ書けたりしますが、他にserializers.pyというファイルを作る必要が出てくるので、今回は採用しませんでした。
ListDaily, DetailDaily, CategoryDairyの３つのクラスについて少しずつ説明を加えておきます。

- ListDaily

日報の一覧を返します。
isOpenがTrueになっているものを日付が新しい順にして全ての日報オブジェクトをdailyという変数に入れています。その後リスト内包表記でres_listに入れていき、Responseでいい感じにして返してます。

- DetailDaily

1日の詳細情報を返します。
引数にある変数pkはさっきのurls.pyで出てきたアレです。idがpkと一致するオブジェクト１つだけを取り出してます。もしpkに存在しないidの値が渡されていたら404を返します。

- CategoryDairy

カテゴリ別の一覧を返します。
getの引数にある変数catはさっきのurls.pyで出てきたアレです。例えばcatにunivが入っていたら日付とunivのデータだけ取り出してdailyに入れることになります。dailyにはタプルのリストの形でデータが入っているので、インデックスを指定して取り出してます。

ここまででAPIは作れたので、確認してみましょう！
例えば、

- 日報の一覧を取得したいときは http://127.0.0.1:8000/daily/
![daily.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/671055/868d4651-6d4a-ae52-dad2-7f6fd4c74382.png)


- idが２の日報の詳細情報を取得したいときは http://127.0.0.1:8000/daily/2/ 
![daily_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/671055/fe7a5b7a-dafc-de0e-b09b-b62d4bcd5a9b.png)


- univというカテゴリの一覧を取得したいときは http://127.0.0.1:8000/daily/univ/
![daily_univ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/671055/9242769f-16b9-eaf4-cc7e-9772aaf47c3d.png)



でチェックできます。
ここまで出来たら、次はReactとやり取りするための設定をしていきます。

#CORSの設定
今回のようにバックエンドをDjango、フロントをReactでやる場合、CORS(オリジン間リソース共有)の設定をする必要があります。DjangoとReactは異なるオリジンで動作するので、その間のHTTPリクエストを許可する必要があります。そのために使うのがDRFが推奨しているdjango-cors-headersというライブラリです。これをインストールしましょう。

```bash
$ pipenv install django-cors-headers
```
これをインストールしたことによりsettings.pyを書き換える必要が出てきました。
以下のように編集しましょう。

```python:daily_api/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders',  # これ
    'daily',
    'markdownx',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # これ
    'django.middleware.common.CommonMiddleware', # これ
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

CORS_ORIGIN_WHITELIST = (  # これ
    'http://localhost:3000', 
)
```
Reactのデフォルトのポートの http://localhost:3000 を設定しています。

#終わりに
ここまででDjango側の開発は完成です！
次からはReactでフロント側の開発をしていきましょう:laughing:
では次の[②React編](https://qiita.com/Piyopanman/items/0a757a7b2dd412e07135)でお会いしましょう！終わり！

#参考
- [Django Rest Framework with React Tutorial](https://wsvincent.com/django-rest-framework-react-tutorial/)
- [Django Girls](https://tutorial.djangogirls.org/ja/)
- [Django Rest Framework 公式](https://www.django-rest-framework.org/)
- [DRFのすゝめ](https://www.takurinton.com/post/32)
- [Narito Blog](https://blog.narito.ninja/detail/102)