自動化無しに生活無し

WEBとかAIとかLinux関係をひたすら書く備忘録系ブログ

【Restful】Django+Reactビギナーが40分で掲示板アプリ(SPA)を作る方法【axios】

thumbnail

Djangoビギナーが40分で掲示板アプリを作る方法 』を終え、DjangoとReactを組み合わせ、SPAを作りたいと思った方向け。

40分はあくまでも目安なので、ご了承ください。

内部構造に関しては、『DjangoとReactを組み合わせる方法論と問題の考察 』に解説がある。

2023年5月3日現在、編集機能の実装を考慮中。近日追記予定。

流れ

  1. React、Djangoの各プロジェクトを作る
  2. Djangoの必要なライブラリをインストール
  3. Djangoのsettings.pyの編集
  4. Djangoのmodels.pyの編集
  5. Djangoのserializers.pyの編集
  6. Djangoのviews.pyの編集
  7. Djangoのurls.pyの編集
  8. Reactの必要なライブラリをインストール
  9. Reactのpackage.jsonの編集
  10. Reactのindex.jsの編集
  11. ReactのApp.jsの編集
  12. DjangoとReactの開発用サーバーをそれぞれ起動

React、Djangoの各プロジェクトを作る

mkdir django_react_todo
cd django_react_todo

mkdir backend

npx create-react-app frontend

Djangoの必要なライブラリをインストール

cd backend
virtualenv venv 

source ./venv/bin/activate 

pip install django djangorestframework django-cors-headers
django-admin startproject config .

Djangoのsettings.pyの編集

アプリを作る。

python3 manage.py startapp bbs

インストールする。

"""
Django settings for config project.

Generated by 'django-admin startproject' using Django 4.1.5.

For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-8%$blv$7^^$pm(s#r(!tr94k9ch7p@wissbgbz!jep%@xlsced'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    "bbs.apps.BbsConfig",
    'corsheaders',
    'rest_framework',

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

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',
]
CORS_ORIGIN_WHITELIST = [
     'http://localhost:3000'
]


ROOT_URLCONF = 'config.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'config.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


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

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

Djangoのmodels.pyの編集

from django.db import models

class Topic(models.Model):

    comment     = models.CharField(verbose_name="コメント",max_length=2000)

マイグレーションしておく

python3 manage.py makemigrations 
python3 manage.py migrate

Djangoのserializers.pyの編集

from rest_framework import serializers
from .models import Topic

class TopicSerializer(serializers.ModelSerializer):
    class Meta:
        model = Topic
        fields = ("id","comment")

Djangoのviews.pyの編集

from django.shortcuts import render
from rest_framework import viewsets
from .serializers import TopicSerializer
from .models import Topic

class TopicView(viewsets.ModelViewSet):
    serializer_class    = TopicSerializer
    queryset            = Topic.objects.all()

Djangoのurls.pyの編集

"""config URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path,include


from rest_framework import routers
from bbs import views

router = routers.DefaultRouter()
router.register(r"topics", views.TopicView, "topic")

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

Reactの必要なライブラリをインストール

npm install axios@1.4.0
npm install bootstrap@5.2.3
npm install reactstrap@9.1.9

Reactのpackage.jsonの編集

proxyを追加する。

{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:8000",
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.4.0",
    "bootstrap": "^5.2.3",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

Reactのindex.jsの編集

bootstrapを読み込むように編集する。

import React from 'react';
import ReactDOM from 'react-dom/client';
import 'bootstrap/dist/css/bootstrap.css'; // ←追加
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

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

// 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();

ReactのApp.jsの編集

import React, { Component } from "react";
import axios from "axios";

class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            topicList: [],
            activeItem: {
                comment: "",
            },
        };
    }

    componentDidMount() {
        this.refreshList();
    }

    //ここでaxiosを使ってデータを取得する。jsonで返ってくるのでStateに入れる。
    refreshList = () => {
        axios
            .get("/api/topics/")
            .then((res) => this.setState({ topicList: res.data }))
            .catch((err) => console.log(err));
    };


    // モーダルダイアログを表示させ、idがあれば編集処理のダイアログを、なければ新規作成のダイアログを表示させる
    handleSubmit = (item) => {

        axios
            .post("/api/topics/", item)
            .then((res) => {
                this.refreshList();
            });
    };

    // 削除処理。axiosを使ってDELETEメソッドの送信。refreshListを使ってデータを取得する。
    handleDelete = (item) => {
        axios
            .delete(`/api/topics/${item.id}/`)
            .then((res) => this.refreshList());
    };


    handleChange = (e) => {
        let { name , value }    = e.target;
        const activeItem        = { ...this.state.activeItem, [name]: value };
        this.setState({ activeItem });
    }


    // タブの中身 Topicの数だけレンダリングする。
    renderItems = () => {

        return this.state.topicList.map((item) => (
            <div className="border">
                <div>{item.id}:{item.comment}</div>

                <div className="text-end">
                    <input type="button" className="btn btn-danger" value="削除" onClick={ () => this.handleDelete(item) } />

                </div>

            </div>
        ));

    };

    // ページに表示させる内容
    render() {
        return (
            <main className="container">
                <textarea className="form-control" name="comment" onChange={this.handleChange}>{this.state.activeItem.comment}</textarea>
                <input type="button" value="送信" onClick={ () => this.handleSubmit(this.state.activeItem) }/>
                {this.renderItems()}
            </main>
        );
    }
}

export default App;

DjangoとReactの開発用サーバーをそれぞれ起動

python3 manage.py runserver
npm start

動かすとこうなる。

テキストエリアに入力した内容が残ってしまうが、これは別途対応が必要なようだ。

スポンサーリンク

シェアボタン

Twitter LINEで送る Facebook はてなブログ