LoginSignup
2
0

FlaskとReactでToDoリストを作ってみた!

Posted at

はじめに

お久しぶりです。Tomita Kentaroです。今回は、過去に作ったFlask(python)とReact(JavaScript)で作ったToDoリストを紹介したいと考えております。Flaskをサーバーとして、Reactをフロントとして動かしてあります。

Flask(バックエンド)の紹介

まずは、ディレクトリ構造の紹介です。

.
├── TodoDB.db
├── main.py
├── mecab-test
│   └── ...
├── requirements.txt
└── utils.py

続いて、main.pyの紹介です。
使用できる、ユーザは "a" , "b" , "c" の3人を用意しました。

main.py
from flask import Flask
from flask import request, make_response, jsonify
from flask_httpauth import HTTPBasicAuth
from flask_cors import CORS
import sqlite3
import webbrowser

app = Flask(__name__, static_folder="./build/static", template_folder="./build")
auth = HTTPBasicAuth()
CORS(app, supports_credentials=True)

users = {
    "a":"1",
    "b":"2",
    "c":"3"
}

@auth.get_password
def get_pw(username):
    if username in users:
        return users.get(username)
    return None

@app.route('/')
@auth.login_required
def index():
    webbrowser.open('http://127.0.0.1:3000/')
    con = sqlite3.connect('TodoDB.db', detect_types=sqlite3.PARSE_DECLTYPES)
    con.execute("DROP TABLE user")
    con.execute("CREATE TABLE user(id TEXT)")
    con.execute("INSERT INTO user(id) values(:id)", {"id": auth.username()})
    con.commit()
    return "こんにちは、%sさん。このページは閉じてください。 " % auth.username()

@app.route('/reload', methods=['GET', 'POST'])
def reload():
    con = sqlite3.connect('TodoDB.db', detect_types=sqlite3.PARSE_DECLTYPES)
    cur = con.execute("SELECT * FROM user")
    login_user = str(cur.fetchone()[0])
    print(login_user)
    if login_user == 'a':
        cur = con.execute("SELECT * FROM a")
        db_col = ['id', 'name', 'completed']
        text = cur.fetchall()
        data_list = []
        for str_data in text:
            data = dict(zip(db_col, str_data))
            data_list.append(data)
        response = {'result': data_list}
        print(response)
        return make_response(jsonify(response))
    elif login_user == 'b':
        cur = con.execute("SELECT * FROM b")
        db_col = ['id', 'name', 'completed']
        text = cur.fetchall()
        data_list = []
        for str_data in text:
            data = dict(zip(db_col, str_data))
            data_list.append(data)
        response = {'result': data_list}
        print(response)
        return make_response(jsonify(response))
    elif login_user == 'c':
        cur = con.execute("SELECT * FROM c")
        db_col = ['id', 'name', 'completed']
        text = cur.fetchall()
        data_list = []
        for str_data in text:
            data = dict(zip(db_col, str_data))
            data_list.append(data)
        response = {'result': data_list}
        print(response)
        return make_response(jsonify(response))
    else:
        text = []
        response = {'result': text}
        return make_response(jsonify(response))

@app.route('/post', methods=['GET', 'POST'])
def parse():
    data = request.get_json()
    con = sqlite3.connect('TodoDB.db', detect_types=sqlite3.PARSE_DECLTYPES)
    cur = con.execute("SELECT * FROM user")
    login_user = str(cur.fetchone()[0])
    print(login_user)
    if login_user == 'a':
        cur = con.execute("SELECT COUNT(*) FROM sqlite_master WHERE TYPE='table' AND name='a'")
        table_a = int(cur.fetchone()[0])
        print(table_a)
        if (table_a == 1):
            con.execute("DROP TABLE a")
        con.execute("CREATE TABLE a(id TEXT PRIMARY KEY, name TEXT, completed INTEGER)")
        text = data['post_text']
        print(text)
        for str_data in text:
            id_data = str_data['id']
            name = str_data['name']
            completed = str_data['completed']
            if completed == False:
                completed_data = 0
            else:
                completed_data = 1
            con.execute("INSERT INTO a(id, name, completed) values(:id, :name, :completed)", {"id": id_data, "name": name, "completed": completed_data})
            con.commit()
        response = {'result': text}
        print(response)
        return make_response(jsonify(response))
    elif login_user == 'b':
        cur = con.execute("SELECT COUNT(*) FROM sqlite_master WHERE TYPE='table' AND name='b'")
        table_b = int(cur.fetchone()[0])
        print(table_b)
        if (table_b == 1):
            con.execute("DROP TABLE b")
        con.execute("CREATE TABLE b(id TEXT PRIMARY KEY, name TEXT, completed INTEGER)")
        text = data['post_text']
        print(text)
        for str_data in text:
            id_data = str_data['id']
            name = str_data['name']
            completed = str_data['completed']
            if completed == False:
                completed_data = 0
            else:
                completed_data = 1
            con.execute("INSERT INTO b(id, name, completed) values(:id, :name, :completed)", {"id": id_data, "name": name, "completed": completed_data})
            con.commit()
        response = {'result': text}
        print(response)
        return make_response(jsonify(response))
    elif login_user == 'c':
        cur = con.execute("SELECT COUNT(*) FROM sqlite_master WHERE TYPE='table' AND name='c'")
        table_c = int(cur.fetchone()[0])
        print(table_c)
        if (table_c == 1):
            con.execute("DROP TABLE c")
        con.execute("CREATE TABLE c(id TEXT PRIMARY KEY, name TEXT, completed INTEGER)")
        text = data['post_text']
        print(text)
        for str_data in text:
            id_data = str_data['id']
            name = str_data['name']
            completed = str_data['completed']
            if completed == False:
                completed_data = 0
            else:
                completed_data = 1
            con.execute("INSERT INTO c(id, name, completed) values(:id, :name, :completed)", {"id": id_data, "name": name, "completed": completed_data})
            con.commit()
        response = {'result': text}
        print(response)
        return make_response(jsonify(response))
    else:
        text = []
        response = {'result': text}
        return make_response(jsonify(response))

        
if __name__=='__main__':
    app.run(debug=True, port=5000, threaded=True)

続いて、utils.pyの紹介です。

utils.py
import MeCab

def wakati(text):
    wakatext = text
    m = MeCab.Tagger('-0wakati')
    return m.parse(wakatext).resplace("¥n","")

最後に、requirements.txtの紹介です。

requirements.txt
flask
flask_cors
requests
flask_httpauth
mecab-python3
unidic-lite

React(フロントエンド)の紹介

まずは、ディレクトリ構造の紹介です。

.
├── package-lock.json
├── package.json
├── public
│   └── ...
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── Todo.jsx
    ├── Todolist.jsx
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── reportWebVitals.js
    └── setupTests.js

続いて、package.jsonの紹介です。

package.json
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://127.0.0.1:5000",
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.1.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"
    ]
  }
}

続いて、App.jsの紹介です。

App.js
import { useState, useRef } from "react";
import TodoList from "./Todolist";
import { v4 as uuidv4 } from "uuid";
import Axios from "axios";

function App() {
  const [todos, setTodos] = useState([]);
  const todoNameRef = useRef();

  const posting = (text) => {
    Axios.post('http://127.0.0.1:5000/post', {
      post_text: text
    }).then(res => {
        console.log(res.data.result);
        setTodos(res.data.result);
    });
  };

  const reload = () => {
    Axios.get('http://127.0.0.1:5000/reload')
    .then(res => {
      console.log(res.data.result);
      setTodos(res.data.result);
    });
  };

  const handleAddTodo = () => {
    const name = todoNameRef.current.value;
    if (name === "") return;
    const newTodos = [...todos, { id: uuidv4(), name: name, completed: false }];
    setTodos(newTodos);
    posting(newTodos);
    todoNameRef.current.value = null;
  };

  const toggleTodo = (id) => {
    const newTodos = [...todos];
    const todo = newTodos.find((todo) => todo.id === id);
    todo.completed = !todo.completed;
    setTodos(newTodos);
    posting(newTodos);
  };

  const handleClear = () => {
    const newTodos = todos.filter((todo) => !todo.completed);
    setTodos(newTodos);
    posting(newTodos);
  };

  return (
    <div>
      <TodoList todos={todos} toggleTodo={toggleTodo} />
      <input type="text" ref={todoNameRef} />
      <button onClick={handleAddTodo}>タスクを追加</button>
      <button onClick={handleClear}>完了したタスクの削除</button>
      <button onClick={reload}>更新</button>
      <div>残りのタスク:{todos.filter((todo) => !todo.completed).length}</div>
    </div>
  );
}

export default App;

続いて、Todo.jsxの紹介です。

Todo.jsx
import React from 'react'

const Todo = ({ todo, toggleTodo }) => {
    const handleTodoClick = () => {
        toggleTodo(todo.id);
    };
  return (
    <div>
        <label>
            <input 
            type="checkbox" 
            checked={todo.completed} 
            readOnly 
            onChange={handleTodoClick} 
            />
        </label>
        {todo.name}
    </div>
  );
};

export default Todo;

最後に、Todolist.jsxの紹介です。

Todolist.jsx
import React from 'react'
import Todo from './Todo'

const Todolist = ({ todos, toggleTodo }) => {
  return todos.map((todo) => <Todo todo={todo} key={todo.id} toggleTodo={toggleTodo} />);
};

export default Todolist

実行画面

localhost:5000にアクセスすると、ユーザー名とパスワードが要求されます。
スクリーンショット 2024-05-02 8.02.33.png

ログインすると、ToDoリストが利用できます。
スクリーンショット 2024-05-02 8.10.46.png

最後に

 このプログラムは、1年以上前に作成したものであったため、仕様を思い出すのに苦労いたしました。このプログラムは、自分でも何とか動かすのに成功していたため、かなり雑だったり冗長だったりすると思います。今回の反省点です。今後、自分が作成したプログラムで、面白そうなものを見つけたら投稿したいと思います。また、間違えているところがあるかもしれませんので、何かあれば、コメントなどで教えていただけると嬉しいです。よろしくお願いいたします。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0