akatak blog

プログラム初心者の50代ビジネスマンがセカンドキャリアを目指して働きながらPythonを中心に独学していましたが、昨年IT系企業に転職。新規事業開発の仕事をすることになりました。自らの覚え書きや成果物、感じたことなどを脈絡もなく書き連ねるブログです。

React HooksとDjango REST Frameworkを使ってTo Do Listを作ってみた(後編)〜バックエンド編

前回に引き続き、React(フロントエンド)+Django(バックエンド)によるTo Do Listアプリを作っていきます。

今回はバックエンド編です。

環境構築

Djangoを利用しますので、任意の名前の仮想環境を構築しておきます。

当該仮想環境を立ち上げて、以下をインストールします。

pip install django
pip install djangorestframework
pip install django-cors-headers

djangorestframeworkDjango REST Frameworkを利用するためのもので、django-cors-headersは、ReactアプリのアドレスとDjangoのアドレスが異なっているため、通常ではブラウザは通信できません。ReactアプリとDjangoが適切にAPI通信を行うために必要なモジュールとなります。

私の環境における各バージョンは以下の通りです。

Django             3.0
djangorestframework 3.11.1
django-cors-headers 3.4.0

Djangoの設定

Django プロジェクト及びアプリの作成

まずは、Djangoプロジェクトを作成します。任意のプロジェクト名をつけますが、ここではtodoprojとしています。

そしてtodoprojフォルダに移動し、そこでdjangoアプリを作成します。ここではアプリ名をtodoappとしています。

django-admin startproject todoproj
cd todoproj
python manage.py startapp todoapp

settings.pyの設定

todoprojフォルダ傘下のsettings.pyを以下の通り修正します。

INSTALLED_APPS = [
    ...
    'rest_framework', # <- 追加
    'corsheaders', # <- 追加
 'todoapp.apps.TodoappConfig' # <- todoappを追加
]

## django-cors-headersを利用する場合
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', 
    'django.middleware.common.CommonMiddleware',
    .....
]

# Djangoが通信する相手であるReactアプリのポートを指定
CORS_ORIGIN_WHITELIST = [
    'http://localhost:3000'
]

Djangoアプリの書き方

それでは、Django REST Frameworkを利用したアプリを具体的に記述していきます。

モデル(models.py)

Django REST Frameworkを利用する場合であっても、このモデルの作成は通常のDjangoと同様となります。したがって、models.pyにdjango.db.models.Modelを継承したクラスとして定義します。

Reactアプリで使っているfieldをここで定義します。idはReact側でuuidを自動的に付与しますので、ここではCharField(文字列)とします。また、primary_key=Trueとしないとmakemigration時に警告が出てきます。その他titleを文字列としてisCompletedをBool値として設定します。

from django.db import models

class Task(models.Model):
    id = models.CharField(max_length=128, primary_key=True)
    title = models.CharField(max_length=256)
    isCompleted = models.BooleanField(default=False)

    def __str__(self):
        return self.title

リアライザ(serializers.py)

リアライザは、データを保持しておくための入れ物で、JSON文字列とモデルオブジェクトの相互変換をしてくれるものです(「現場で使えるDjango REST Frameworkの教科書」)。

今回は、Taskモデルでの定義に基づき、JSONの入出力が行われるため、ModelSerializerを継承したシリアライザを利用することができます。これにより、モデルのフィールド定義が内部的に再利用されるため、このsirializers.pyでの記述が非常に簡単になります。

具体的には以下の通り。

from rest_framework import serializers
from .models import Task

class TaskSerializer(serializers.ModelSerializer):
    class Meta:
        model = Task
        fields = ['id', 'title', 'isCompleted']

ビュー(views.py)

このビューの役割は、JSONデータが入ったリクエストオブジェクトを受け取り、APIの種類に応じた処理を実行し、JSON形式のレスポンスオブジェクトを返すことです。

今回のモデルは、Taskのみの単一モデルなので、ModelViewSetを利用すれば、CRUDを処理するAPIを簡単に実装することができます。

具体的には、views.pyを以下のとおり最低限記述するだけで利用可能ととなります。

from rest_framework import viewsets
from .serializers import TaskSerializer
from .models import Task

class TaskViewSet(viewsets.ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer

URLconf(Urls.py)

URLconfとはURLのパターンの集まりで、適切なビューを見つけるために、DjangoがリクエストされたURLと照合するものです。

API用のURLパターンを新たに設定する場合、通常のDjangoと同様にurlpatternsリストにpath関数等を利用して、URLパターンとビューのセットを記載します。

# todoproj/settings.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('todoapp.urls'))

なお、ModelViewSetを継承した場合には、Django REST Framework独自のRouterクラスを使って設定します。

# todoapp/settings.py

from django.urls import path, include
from rest_framework import routers
from .views import TaskViewSet

router = routers.DefaultRouter()
router.register('tasks', TaskViewSet)

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

これでdjangoを起動して、ブラウザのアドレスバーにhttp://127.0.0.1:8000/api/tasksを入力すると、ブラウザ上にtasks(task一覧)が表示されるようになります。

なお、DjangoのAdmin管理画面でTaskの登録状況が一応見られるように、以下のとおり登録しておきます。

from django.contrib import admin
from .models import Task

admin.register(Task)

以上で準備は完了です。

それでは、仮想環境になっていることを確認して、python manage.py rumserverDjangoを起動させます。

そしてアドレスバーにhttp://127.0.0.1:8000/api/tasksを入力すると以下の画面が立ち上がりました。まだ、データは登録していないので、ブランクリストが表示されていますね。

f:id:akatak:20200901160822p:plain

データをいくつか登録しておきましょう。

下側のhtml formの記載のあるボックスに「id」「title」「isCompleted」が入力できるようになっているので、適当に入力してPOSTを押すと登録できます。

idは文字列で他のtaskと重ならないように以下のとおり入力してみました。

id: a001
title: Reactを学習する
isCompleted: チェック

id: a002
title: お米を買う
isCompleted: チェックしない

id: a003
title: お酒を買う
isCompleted: チェックをしない

上段のボックスのGET をクリックします。すると以下のとおり、Task Listが表示されました。

f:id:akatak:20200901162138p:plain

なお、入力済みの個別taskの修正や削除を行いたい場合は、アドレスバーに http://127.0.0.1:8000/api/tasks/a001と最後にidを入力します。すると以下のとおり、個別明細とPUTDELETE表示が出ますので、適宜修正や削除が可能です。

f:id:akatak:20200901162541p:plain

ここでは、「学習する」を「勉強する」に修正しておきました。

Reactアプリと繋げる

さて、いよいよReactアプリとDjangoを接続します。まずはReactアプリを修正して、json-serverではなく、Djangoに接続できるように設定します。

Reactアプリの修正

baseUrlを以下のとおり修正します。

細かいところですが、addの場合とupdateの場合には、${baseUrl}/${baseUrl}/${id}/と最後に/をつけないとエラーとなってしまいます。

import axios from 'axios';

// const baseUrl = "http://localhost:3001/tasks"; 
const baseUrl = "http://localhost:8000/api/tasks";

async function getAll () {
  const response = await axios.get(baseUrl);
  return response.data;
}

async function add (newTask) {
  const response = await axios.post(`${baseUrl}/`, newTask)
  return response.data;
}

async function update(id, updatedTask) {
  const response = await axios.put(`${baseUrl}/${id}/`, updatedTask)
  return response.data;
}

async function _delete(id) {
  await axios.delete(`${baseUrl}/${id}`);
  return id;
}

export default { getAll, add, update, delete: _delete }

これで準備が整いました。

React アプリのあるフォルダ(ここではmytodo)に移動し、npm startでアプリを立ち上げます。

f:id:akatak:20200901164029p:plain

無事にDjangoと接続できました。


React-to-do-sample

djangoプロジェクトのtodoprojの下にfrontendというフォルダを作成し、その下にmytodoを移動しておくと、djangoとの関係性が明確で良いかと思います。

今回は単純なアプリでしたが、それでもHooksの概念がなかなか理解できず(副作用って一体何?だったり、Hooksの種類がいろいろあったりで)、手間取ったところもありましたが、何とかイメージしたことは実装できました。

今後は、ユーザー登録機能だったり、順番を入れ替える、期日管理を行うなど実装にチャレンジしたいなとは思っています。ただ、やりたいことが結構増えてしまっていますので、いつになるか分かりませんが...