序章

このチュートリアルでは、DjangoとReactを使用してTo-Doアプリケーションを構築します。

Reactは、SPA(シングルページアプリケーション)を開発するためのJavaScriptフレームワークです。 堅実なドキュメントとその周りの活気に満ちたエコシステムがあります。

Djangoは、Web開発の一般的な方法を簡素化するPythonWebフレームワークです。 Djangoは信頼性が高く、一般的な開発ニーズをサポートする安定したライブラリの活気に満ちたエコシステムも備えています。

このアプリケーションでは、Reactはフロントエンドまたはクライアント側のフレームワークとして機能し、ユーザーインターフェースを処理し、Django RESTフレームワーク(DRF)を使用して構築されたAPIであるDjangoバックエンドへのリクエストを介してデータを取得および設定します。

このチュートリアルを終了すると、完全に機能するアプリケーションができあがります。

Animated gif of a user interacting with the application to create a new task and toggle it between complete and incomplete statuses.

注:このチュートリアルのソースコードはGitHubで入手できます。

警告:このチュートリアルで提供されるコードは教育目的であり、本番環境での使用を目的としたものではありません。

このアプリケーションを使用すると、ユーザーはタスクを作成して、それらを完了または未完了としてマークできます。

前提条件

このチュートリアルに従うには、次のことを行う必要があります。

  1. Python3のローカルプログラミング環境をインストールしてセットアップします
  2. Node.jsをインストールし、ローカル開発環境を作成します

このチュートリアルは、Python v3.9.1、pip v20.2.4、Django v3.1.6、djangorestframework v3.12.2、django-cors-headers v3.7.0、ノードv15.8.0、 npm v7.5.4、React v17.0.1、およびaxiosv0.21.0。

ステップ1—バックエンドを設定する

このセクションでは、新しいプロジェクトディレクトリを作成し、Djangoをインストールします。

新しいターミナルウィンドウを開き、次のコマンドを実行して新しいプロジェクトディレクトリを作成します。

  1. mkdir django-todo-react

次に、ディレクトリに移動します。

  1. cd django-todo-react

次に、pipを使用してPipenvをインストールします。

  1. pip install pipenv

注:インストールによっては、pipの代わりにpip3を使用する必要がある場合があります。

そして、新しい仮想環境をアクティブ化します。

  1. pipenv shell

Pipenvを使用してDjangoをインストールします。

  1. pipenv install django

次に、backendという名前の新しいプロジェクトを作成します。

  1. django-admin startproject backend

次に、新しく作成されたバックエンドディレクトリに移動します。

  1. cd backend

todoという新しいアプリケーションを起動します。

python manage.py startapp todo

移行を実行します。

python manage.py migrate

そして、サーバーを起動します。

python manage.py runserver

Webブラウザでhttp://localhost:8000に移動します。

Screenshot of the default Django application running successfully.

この時点で、Djangoアプリケーションのインスタンスが正常に実行されていることがわかります。 終了したら、サーバーを停止できます(CONTROL+CまたはCTRL+C)。

todoアプリケーションの登録

バックエンドのセットアップが完了したので、todoアプリケーションをインストール済みアプリとして登録し、Djangoが認識できるようにすることができます。

コードエディタでbackend/settings.pyファイルを開き、todoINSTALLED_APPSに追加します。

backend / settings.py
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'todo',
]

次に、変更を保存します。

Todoモデルの定義

Todoアイテムをデータベースに保存する方法を定義するモデルを作成しましょう。

コードエディタでtodo/models.pyファイルを開き、次のコード行を追加します。

todo / models.py
from django.db import models

# Create your models here.

class Todo(models.Model):
    title = models.CharField(max_length=120)
    description = models.TextField()
    completed = models.BooleanField(default=False)

    def _str_(self):
        return self.title

上記のコードスニペットは、Todoモデルの3つのプロパティについて説明しています。

  • title
  • description
  • completed

completedプロパティは、タスクのステータスです。 タスクはいつでも完了するか、完了しないかのいずれかです。 Todoモデルを作成したので、移行ファイルを作成する必要があります。

  1. python manage.py makemigrations todo

そして、変更をデータベースに適用します。

  1. python manage.py migrate todo

Djangoがデフォルトで提供する管理インターフェースを使用して作成したTodoモデルでCRUD操作が機能することをテストできます。

コードエディタでtodo/admin.pyファイルを開き、次のコード行を追加します。

todo / admin.py
from django.contrib import admin
from .models import Todo

class TodoAdmin(admin.ModelAdmin):
    list_display = ('title', 'description', 'completed')

# Register your models here.

admin.site.register(Todo, TodoAdmin)

次に、変更を保存します。

管理インターフェースにアクセスするには、「スーパーユーザー」アカウントを作成する必要があります。 ターミナルで次のコマンドを実行します。

  1. python manage.py createsuperuser

スーパーユーザーのユーザー名、電子メール、およびパスワードを入力するように求められます。 管理ダッシュボードにログインするために必要になるため、覚えておくことができる詳細を必ず入力してください。

サーバーをもう一度起動します。

  1. python manage.py runserver

Webブラウザでhttp://localhost:8000/adminに移動します。 そして、以前に作成したユーザー名とパスワードでログインします。

Screenshot of the admin interface for the Django application.

次のインターフェイスを使用して、Todoアイテムを作成、編集、および削除できます。

Screenshot of the admin interface for the Django application displaying todo items.

このインターフェースを試した後、サーバーを停止できます(CONTROL+CまたはCTRL+C)。

ステップ2—APIを設定する

このセクションでは、DjangoRESTフレームワークを使用してAPIを作成します。

Pipenvを使用してdjangorestframeworkおよびdjango-cors-headersをインストールします。

  1. pipenv install djangorestframework django-cors-headers

インストールされているアプリケーションのリストにrest_frameworkcorsheadersを追加する必要があります。 コードエディタでbackend/settings.pyファイルを開き、INSTALLED_APPSセクションとMIDDLEWAREセクションを更新します。

backend / settings.py
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'rest_framework',
    'todo',
]

MIDDLEWARE = [
    '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',
    'corsheaders.middleware.CorsMiddleware',
]

次に、次のコード行をbackend/settings.pyファイルの最後に追加します。

backend / settings.py
CORS_ORIGIN_WHITELIST = [
     'http://localhost:3000'
]

django-cors-headersは、CORSルールが原因で通常発生するエラーを防ぐPythonライブラリです。 CORS_ORIGIN_WHITELISTコードでは、アプリケーションのフロントエンド(そのポートで提供される)がAPIと対話するようにするため、localhost:3000をホワイトリストに登録しました。

serializersを作成しています

フロントエンドが受信したデータを処理できるように、モデルインスタンスをJSONに変換するシリアライザーが必要になります。

コードエディタでtodo/serializers.pyファイルを作成します。 serializers.pyファイルを開き、次のコード行で更新します。

todo / serializers.py
from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Todo
        fields = ('id', 'title', 'description', 'completed')

このコードは、操作するモデルとJSONに変換するフィールドを指定します。

ビューの作成

todo/views.pyファイルにTodoViewクラスを作成する必要があります。

コードエディタでtodo/views.pyファイルを開き、次のコード行を追加します。

todo / views.py
from django.shortcuts import render
from rest_framework import viewsets
from .serializers import TodoSerializer
from .models import Todo

# Create your views here.

class TodoView(viewsets.ModelViewSet):
    serializer_class = TodoSerializer
    queryset = Todo.objects.all()

viewsets基本クラスは、デフォルトでCRUD操作の実装を提供します。 このコードは、serializer_classquerysetを指定します。

コードエディタでbackend/urls.pyファイルを開き、内容を次のコード行に置き換えます。

backend / urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from todo import views

router = routers.DefaultRouter()
router.register(r'todos', views.TodoView, 'todo')

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

このコードは、APIのURLパスを指定します。 これは、APIの構築を完了する最後のステップでした。

TodoモデルでCRUD操作を実行できるようになりました。 routerクラスを使用すると、次のクエリを実行できます。

  • /todos/-すべてのTodoアイテムのリストを返します。 CREATEおよびREAD操作はここで実行できます。
  • /todos/idid主キーを使用して、単一のTodoアイテムを返します。 UPDATEおよびDELETE操作はここで実行できます。

サーバーを再起動しましょう:

  1. python manage.py runserver

Webブラウザでhttp://localhost:8000/api/todosに移動します。

Screenshot of the API results for the Todo items.

次のインターフェイスを使用して、新しいTodoアイテムをCREATEできます。

Screenshot of the API tool for creating new Todo items.

Todoアイテムが正常に作成されると、正常な応答が表示されます。

Screenshot of the API response for a successful Todo item creation.

id主キーを使用して、特定のTodoアイテムに対してDELETEおよびUPDATE操作を実行することもできます。 アドレス構造/api/todos/{id}を使用し、idを提供します。

URLに1を追加して、idが「1」のTodoアイテムを調べます。 Webブラウザでhttp://localhost:8000/api/todos/1に移動します。

Screenshot of API tools for DELETE and PUT.

これで、アプリケーションのバックエンドの構築が完了しました。

ステップ3—フロントエンドの設定

アプリケーションのバックエンドが完成したので、フロントエンドを作成し、作成したインターフェイスを介してバックエンドと通信させることができます。

まず、新しいターミナルウィンドウを開き、django-todo-reactプロジェクトディレクトリに移動します。

フロントエンドを設定するために、このチュートリアルはCreateReactAppに依存します。 create-react-appを使用するにはいくつかのアプローチがあります。 1つのアプローチは、npxを使用してパッケージを実行し、プロジェクトを作成することです。

  1. npx create-react-app frontend

Create React Appを使用してReactプロジェクトをセットアップする方法を読むと、このアプローチの詳細を学ぶことができます。

プロジェクトが作成されたら、新しく作成されたfrontendディレクトリに変更できます。

  1. cd frontend

次に、アプリケーションを起動します。

  1. npm start

Webブラウザがhttp://localhost:3000を開き、デフォルトのCreateReactApp画面が表示されます。

Screenshot of landing page for the default Create React App application.

次に、bootstrapreactstrapをインストールして、ユーザーインターフェイスツールを提供します。

  1. npm install bootstrap@4.6.0 reactstrap@8.9.0 --legacy-peer-deps

注: React、Bootstrap、およびReactstrapのバージョンによっては、unable to resolve dependency treeエラーが発生する場合があります。

改訂の時点で、popper.jsの最新バージョンは非推奨になっており、React17+と競合します。 これは既知の問題であり、インストール時に--legacy-peer-depsオプションを使用することができます。

コードエディタでindex.jsを開き、bootstrap.min.cssを追加します。

フロントエンド/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

この手順で問題が発生した場合は、ブートストラップを追加するための公式ドキュメントを参照してください。

コードエディタでApp.jsを開き、次のコード行を追加します。

フロントエンド/src/App.js
import React, { Component } from "react";

const todoItems = [
  {
    id: 1,
    title: "Go to Market",
    description: "Buy ingredients to prepare dinner",
    completed: true,
  },
  {
    id: 2,
    title: "Study",
    description: "Read Algebra and History textbook for the upcoming test",
    completed: false,
  },
  {
    id: 3,
    title: "Sammy's books",
    description: "Go to library to return Sammy's books",
    completed: true,
  },
  {
    id: 4,
    title: "Article",
    description: "Write article on how to use Django with React",
    completed: false,
  },
];

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      todoList: todoItems,
    };
  }

  displayCompleted = (status) => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }

    return this.setState({ viewCompleted: false });
  };

  renderTabList = () => {
    return (
      <div className="nav nav-tabs">
        <span
          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
          onClick={() => this.displayCompleted(true)}
        >
          Complete
        </span>
        <span
          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
          onClick={() => this.displayCompleted(false)}
        >
          Incomplete
        </span>
      </div>
    );
  };

  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.todoList.filter(
      (item) => item.completed == viewCompleted
    );

    return newItems.map((item) => (
      <li
        key={item.id}
        className="list-group-item d-flex justify-content-between align-items-center"
      >
        <span
          className={`todo-title mr-2 ${
            this.state.viewCompleted ? "completed-todo" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            className="btn btn-secondary mr-2"
          >
            Edit
          </button>
          <button
            className="btn btn-danger"
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };

  render() {
    return (
      <main className="container">
        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="mb-4">
                <button
                  className="btn btn-primary"
                >
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush border-top-0">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
      </main>
    );
  }
}

export default App;

このコードには、4つのアイテムのハードコードされた値が含まれています。 これらは、アイテムがバックエンドからフェッチされるまでの一時的な値になります。

renderTabList()関数は、表示されるアイテムのセットを制御するのに役立つ2つのスパンをレンダリングします。 完了タブをクリックすると、完了したタスクが表示されます。 不完全タブをクリックすると、不完全なタスクが表示されます。

変更を保存し、Webブラウザでアプリケーションを確認します。

Screenshot of the application currently displaying tasks for Study and Article.

タスクの追加や編集などのアクションを処理するには、モーダルコンポーネントを作成する必要があります。

まず、srcディレクトリにcomponentsフォルダを作成します。

  1. mkdir src/components

次に、Modal.jsファイルを作成し、コードエディタで開きます。 次のコード行を追加します。

フロントエンド/src/components/Modal.js
import React, { Component } from "react";
import {
  Button,
  Modal,
  ModalHeader,
  ModalBody,
  ModalFooter,
  Form,
  FormGroup,
  Input,
  Label,
} from "reactstrap";

export default class CustomModal extends Component {
  constructor(props) {
    super(props);
    this.state = {
      activeItem: this.props.activeItem,
    };
  }

  handleChange = (e) => {
    let { name, value } = e.target;

    if (e.target.type === "checkbox") {
      value = e.target.checked;
    }

    const activeItem = { ...this.state.activeItem, [name]: value };

    this.setState({ activeItem });
  };

  render() {
    const { toggle, onSave } = this.props;

    return (
      <Modal isOpen={true} toggle={toggle}>
        <ModalHeader toggle={toggle}>Todo Item</ModalHeader>
        <ModalBody>
          <Form>
            <FormGroup>
              <Label for="todo-title">Title</Label>
              <Input
                type="text"
                id="todo-title"
                name="title"
                value={this.state.activeItem.title}
                onChange={this.handleChange}
                placeholder="Enter Todo Title"
              />
            </FormGroup>
            <FormGroup>
              <Label for="todo-description">Description</Label>
              <Input
                type="text"
                id="todo-description"
                name="description"
                value={this.state.activeItem.description}
                onChange={this.handleChange}
                placeholder="Enter Todo description"
              />
            </FormGroup>
            <FormGroup check>
              <Label check>
                <Input
                  type="checkbox"
                  name="completed"
                  checked={this.state.activeItem.completed}
                  onChange={this.handleChange}
                />
                Completed
              </Label>
            </FormGroup>
          </Form>
        </ModalBody>
        <ModalFooter>
          <Button
            color="success"
            onClick={() => onSave(this.state.activeItem)}
          >
            Save
          </Button>
        </ModalFooter>
      </Modal>
    );
  }
}

このコードはCustomModalクラスを作成し、reactstrapライブラリから派生したModalコンポーネントをネストします。

このコードは、次の形式で3つのフィールドも定義しました。

  • title
  • description
  • completed

これらは、バックエンドのTodoモデルのプロパティとして定義したものと同じフィールドです。

CustomModalは、activeItemtoggle、およびonSaveを小道具として受け取ります。

  1. activeItemは、編集するTodoアイテムを表します。
  2. toggleは、モーダルの状態を制御するために使用される関数です(つまり、モーダルを開いたり閉じたりします)。
  3. onSaveは、Todoアイテムの編集された値を保存するために呼び出される関数です。

次に、CustomModalコンポーネントをApp.jsファイルにインポートします。

コードエディタでsrc/App.jsファイルに再度アクセスし、内容全体を次のコード行に置き換えます。

フロントエンド/src/App.js
import React, { Component } from "react";
import Modal from "./components/Modal";

const todoItems = [
  {
    id: 1,
    title: "Go to Market",
    description: "Buy ingredients to prepare dinner",
    completed: true,
  },
  {
    id: 2,
    title: "Study",
    description: "Read Algebra and History textbook for the upcoming test",
    completed: false,
  },
  {
    id: 3,
    title: "Sammy's books",
    description: "Go to library to return Sammy's books",
    completed: true,
  },
  {
    id: 4,
    title: "Article",
    description: "Write article on how to use Django with React",
    completed: false,
  },
];

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      todoList: todoItems,
      modal: false,
      activeItem: {
        title: "",
        description: "",
        completed: false,
      },
    };
  }

  toggle = () => {
    this.setState({ modal: !this.state.modal });
  };

  handleSubmit = (item) => {
    this.toggle();

    alert("save" + JSON.stringify(item));
  };

  handleDelete = (item) => {
    alert("delete" + JSON.stringify(item));
  };

  createItem = () => {
    const item = { title: "", description: "", completed: false };

    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  editItem = (item) => {
    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  displayCompleted = (status) => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }

    return this.setState({ viewCompleted: false });
  };

  renderTabList = () => {
    return (
      <div className="nav nav-tabs">
        <span
          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
          onClick={() => this.displayCompleted(true)}
        >
          Complete
        </span>
        <span
          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
          onClick={() => this.displayCompleted(false)}
        >
          Incomplete
        </span>
      </div>
    );
  };

  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.todoList.filter(
      (item) => item.completed === viewCompleted
    );

    return newItems.map((item) => (
      <li
        key={item.id}
        className="list-group-item d-flex justify-content-between align-items-center"
      >
        <span
          className={`todo-title mr-2 ${
            this.state.viewCompleted ? "completed-todo" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            className="btn btn-secondary mr-2"
            onClick={() => this.editItem(item)}
          >
            Edit
          </button>
          <button
            className="btn btn-danger"
            onClick={() => this.handleDelete(item)}
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };

  render() {
    return (
      <main className="container">
        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="mb-4">
                <button
                  className="btn btn-primary"
                  onClick={this.createItem}
                >
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush border-top-0">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
        {this.state.modal ? (
          <Modal
            activeItem={this.state.activeItem}
            toggle={this.toggle}
            onSave={this.handleSubmit}
          />
        ) : null}
      </main>
    );
  }
}

export default App;

変更を保存し、Webブラウザでアプリケーションを確認します。

Screenshot of the Modal component displaying a new task with a title of - Study - and a description of - Read Algebra and History textbook for the upcoming test

Todoアイテムを編集して保存しようとすると、Todoアイテムのオブジェクトを表示するアラートが表示されます。 保存または削除をクリックすると、Todoアイテムに対してそれぞれのアクションが実行されます。

注::ReactおよびReactstrapのバージョンによっては、コンソールエラーが発生する場合があります。 改訂時点では、Warning: Legacy context API has been detected within a strict-mode tree.Warning: findDOMNode is deprecated in StrictMode.既知の問題です。

次に、前のセクションで作成したDjangoAPIと相互作用するようにアプリケーションを変更します。 最初のターミナルウィンドウに戻り、サーバーが実行されていることを確認します。 実行されていない場合は、次のコマンドを使用します。

  1. python manage.py runserver

注:このターミナルウィンドウを閉じた場合は、backendディレクトリに移動して、仮想Pipenvシェルを使用する必要があることに注意してください。

バックエンドサーバー上のAPIエンドポイントにリクエストを送信するには、axiosというJavaScriptライブラリをインストールします。

2番目のターミナルウィンドウで、frontendディレクトリにいることを確認し、axiosをインストールします。

  1. npm install axios@0.21.1

次に、コードエディタでfrontend/package.jsonファイルを開き、proxyを追加します。

フロントエンド/package.json
[...]
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:8000",
  "dependencies": {
    "axios": "^0.18.0",
    "bootstrap": "^4.1.3",
    "react": "^16.5.2",
    "react-dom": "^16.5.2",
    "react-scripts": "2.0.5",
    "reactstrap": "^6.5.0"
  },
[...]

プロキシは、Djangoアプリケーションが処理するhttp://localhost:8000へのAPIリクエストのトンネリングに役立ちます。 このproxyがないと、フルパスを指定する必要があります。

axios.get("http://localhost:8000/api/todos/")

proxyを使用すると、相対パスを提供できます。

axios.get("/api/todos/")

注:プロキシをアプリケーションに登録するには、開発サーバーを再起動する必要がある場合があります。

frontend/src/App.jsファイルに再度アクセスし、コードエディタで開きます。 この手順では、ハードコードされたtodoItemsを削除し、バックエンドサーバーへのリクエストからのデータを使用します。 handleSubmitおよびhandleDelete

App.jsファイルを開き、次の最終バージョンに置き換えます。

フロントエンド/src/App.js
import React, { Component } from "react";
import Modal from "./components/Modal";
import axios from "axios";

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewCompleted: false,
      todoList: [],
      modal: false,
      activeItem: {
        title: "",
        description: "",
        completed: false,
      },
    };
  }

  componentDidMount() {
    this.refreshList();
  }

  refreshList = () => {
    axios
      .get("/api/todos/")
      .then((res) => this.setState({ todoList: res.data }))
      .catch((err) => console.log(err));
  };

  toggle = () => {
    this.setState({ modal: !this.state.modal });
  };

  handleSubmit = (item) => {
    this.toggle();

    if (item.id) {
      axios
        .put(`/api/todos/${item.id}/`, item)
        .then((res) => this.refreshList());
      return;
    }
    axios
      .post("/api/todos/", item)
      .then((res) => this.refreshList());
  };

  handleDelete = (item) => {
    axios
      .delete(`/api/todos/${item.id}/`)
      .then((res) => this.refreshList());
  };

  createItem = () => {
    const item = { title: "", description: "", completed: false };

    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  editItem = (item) => {
    this.setState({ activeItem: item, modal: !this.state.modal });
  };

  displayCompleted = (status) => {
    if (status) {
      return this.setState({ viewCompleted: true });
    }

    return this.setState({ viewCompleted: false });
  };

  renderTabList = () => {
    return (
      <div className="nav nav-tabs">
        <span
          onClick={() => this.displayCompleted(true)}
          className={this.state.viewCompleted ? "nav-link active" : "nav-link"}
        >
          Complete
        </span>
        <span
          onClick={() => this.displayCompleted(false)}
          className={this.state.viewCompleted ? "nav-link" : "nav-link active"}
        >
          Incomplete
        </span>
      </div>
    );
  };

  renderItems = () => {
    const { viewCompleted } = this.state;
    const newItems = this.state.todoList.filter(
      (item) => item.completed === viewCompleted
    );

    return newItems.map((item) => (
      <li
        key={item.id}
        className="list-group-item d-flex justify-content-between align-items-center"
      >
        <span
          className={`todo-title mr-2 ${
            this.state.viewCompleted ? "completed-todo" : ""
          }`}
          title={item.description}
        >
          {item.title}
        </span>
        <span>
          <button
            className="btn btn-secondary mr-2"
            onClick={() => this.editItem(item)}
          >
            Edit
          </button>
          <button
            className="btn btn-danger"
            onClick={() => this.handleDelete(item)}
          >
            Delete
          </button>
        </span>
      </li>
    ));
  };

  render() {
    return (
      <main className="container">
        <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
        <div className="row">
          <div className="col-md-6 col-sm-10 mx-auto p-0">
            <div className="card p-3">
              <div className="mb-4">
                <button
                  className="btn btn-primary"
                  onClick={this.createItem}
                >
                  Add task
                </button>
              </div>
              {this.renderTabList()}
              <ul className="list-group list-group-flush border-top-0">
                {this.renderItems()}
              </ul>
            </div>
          </div>
        </div>
        {this.state.modal ? (
          <Modal
            activeItem={this.state.activeItem}
            toggle={this.toggle}
            onSave={this.handleSubmit}
          />
        ) : null}
      </main>
    );
  }
}

export default App;

refreshList()関数は再利用可能であり、APIリクエストが完了するたびに呼び出されます。 Todoリストを更新して、追加されたアイテムの最新のリストを表示します。

handleSubmit()関数は、作成操作と更新操作の両方を処理します。 パラメータとして渡されたアイテムにidがない場合は、作成されていない可能性があるため、関数が作成します。

この時点で、バックエンドサーバーが最初のターミナルウィンドウで実行されていることを確認します。

  1. python manage.py runserver

注:このターミナルウィンドウを閉じた場合は、backendディレクトリに移動して、仮想Pipenvシェルを使用する必要があることに注意してください。

そして、2番目のターミナルウィンドウで、frontendディレクトリにいることを確認し、フロントエンドアプリケーションを起動します。

  1. npm start

これで、Webブラウザでhttp://localhost:3000にアクセスすると、アプリケーションでREADCREATEUPDATE、およびDELETEのタスクを実行できるようになります。 。

Animated gif of a user interacting with the application to create a new item and toggle it between complete and incomplete statuses.

これで、Todoアプリケーションのフロントエンドとバックエンドが完成しました。

結論

この記事では、DjangoとReactを使用してTo-Doアプリケーションを構築しました。 これは、djangorestframeworkdjango-cors-headersaxiosbootstrap、およびreactstrapライブラリで実現しました。

Djangoについて詳しく知りたい場合は、Djangoトピックページで演習とプログラミングプロジェクトを確認してください。

Reactの詳細については、 React.js シリーズのコーディング方法をご覧になるか、Reactトピックページで演習やプログラミングプロジェクトを確認してください。