Webアプリケーション作成 | 環境構築 | ログ | Flask

参照先

Installation — Flask Documentation (3.0.x)

Installation

Create an environment
% pwd
~/Desktop/guri_flask/myproject
$ mkdir myproject
$ cd myproject
$ python3 -m venv .venv
Activate the environment
$ . .venv/bin/activate
Install Flask
$ pip install Flask
Collecting Flask
  Downloading flask-3.0.3-py3-none-any.whl.metadata (3.2 kB)
Collecting Werkzeug>=3.0.0 (from Flask)
  Downloading werkzeug-3.0.3-py3-none-any.whl.metadata (3.7 kB)
Collecting Jinja2>=3.1.2 (from Flask)
  Downloading jinja2-3.1.4-py3-none-any.whl.metadata (2.6 kB)
Collecting itsdangerous>=2.1.2 (from Flask)
  Downloading itsdangerous-2.2.0-py3-none-any.whl.metadata (1.9 kB)
Collecting click>=8.1.3 (from Flask)
  Downloading click-8.1.7-py3-none-any.whl.metadata (3.0 kB)
Collecting blinker>=1.6.2 (from Flask)
  Downloading blinker-1.8.2-py3-none-any.whl.metadata (1.6 kB)
Collecting MarkupSafe>=2.0 (from Jinja2>=3.1.2->Flask)
  Downloading MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl.metadata (3.0 kB)
Downloading flask-3.0.3-py3-none-any.whl (101 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.7/101.7 kB 1.0 MB/s eta 0:00:00
Downloading blinker-1.8.2-py3-none-any.whl (9.5 kB)
Downloading click-8.1.7-py3-none-any.whl (97 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97.9/97.9 kB 548.9 kB/s eta 0:00:00
Downloading itsdangerous-2.2.0-py3-none-any.whl (16 kB)
Downloading jinja2-3.1.4-py3-none-any.whl (133 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.3/133.3 kB 557.8 kB/s eta 0:00:00
Downloading werkzeug-3.0.3-py3-none-any.whl (227 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 227.3/227.3 kB 435.1 kB/s eta 0:00:00
Downloading MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl (18 kB)
Installing collected packages: MarkupSafe, itsdangerous, click, blinker, Werkzeug, Jinja2, Flask
Successfully installed Flask-3.0.3 Jinja2-3.1.4 MarkupSafe-2.1.5 Werkzeug-3.0.3 blinker-1.8.2 click-8.1.7 itsdangerous-2.2.0

[notice] A new release of pip is available: 24.0 -> 24.2
[notice] To update, run: pip install --upgrade pip
Flaskのバージョン確認
$ flask --version
Python 3.12.4
Flask 3.0.3
Werkzeug 3.0.3

Webサーバーを構築

プロジェクト用にワークスペースを作成
myprojectディレクトリ内にapp.pyを作成
Quickstartを参考にWebサーバーを構築
Quickstart — Flask Documentation (3.0.x)
①プログラム作成
$ cat app.py 
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

if __name__ == "__main__":
    app.run()
②プログラムの実行
  •  Flaskを実行するために環境変数を設定

仮想環境をactivate化した状態で

$ export FLASK_APP=app
$ export FLASK_ENV=development
$ export FLASK_DEBUG=1
$ flask run
 * Serving Flask app 'app'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 244-907-730



$ export FLASK_DEBUG=1
がない場合は以下。
 * Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
127.0.0.1 - - [12/Aug/2024 17:39:28] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [12/Aug/2024 17:39:28] "GET /favicon.ico HTTP/1.1" 404 -

(確認するには…

$ echo $FLASK_APP
app
$ echo $FLASK_ENV
development
http://127.0.0.1:5000

にアクセスすると

Routing

URLごとに違うページに遷移させる。

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

@app.route("/guri")
def hello_guri():
    return "<p>Hello, Guri!</p>"

@app.route("/gura")
def hello_gura():
    return "<p>Hello, Gura!</p>"


if __name__ == "__main__":
    app.run()

可変URL

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

@app.route("/<name>")
def hello(name):
    return f"<p>Hello, {name}!</p>"


if __name__ == "__main__":
    app.run()

HTMLファイルを返す

URLごとのHTMLファイルを返す。(別の画面を表示)

$ cat app.py
from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route("/")
def hello_world():
    return render_template('index.html')


if __name__ == "__main__":
    app.run()

Top画面作成

base.htmlを作成

今後、Flaskで画面作成する際のベース(雛形)となるテンプレートとしてbase.htmlを作成する。

templatesディレクトリ内にbase.htmlを作成する。

```base.html```
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>メモアプリ</title>
</head>
<body>
    {% block content %}
    {% endblock %}
</body>
</html>

```index.html```
{% extends "base.html" %}
{% block content %}
<h1>メモアプリ</h1>
    <article>
        <h2>メモ1</h2>
        <p>メモ1の内容</p>
    </article>
    <article>
        <h2>メモ2</h2>
        <p>メモ2の内容</p>
    </article>
{% endblock %}
```app.py```
from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route("/")
def top():
    return render_template('index.html')


if __name__ == "__main__":
    app.run()

app.py側でメモリストを受け取り、画面側に出力する機能の追加

```app.py```
from flask import Flask
from flask import render_template

app = Flask(__name__)

memo_list = [
    {'title':"test01", 'body':"ぐり主任です。"},
    {'title':"test02", 'body':"ぐらです。"}
]

@app.route("/")
def top():
    return render_template('index.html', memo_list=memo_list)


if __name__ == "__main__":
    app.run()
```index.html```
{% extends "base.html" %}
{% block content %}
<h1>メモアプリ</h1>
    {% if memo_list == [] %}
        <p>メモは登録されていません</p>
    {% else %}
        {% for memo in memo_list %}
            <article>
                <h2>{{ memo.title }}</h2>
                <p>{{ memo.body }}</p>
            </article>
        {% endfor %}
    {% endif %}
{% endblock %}

top画面を表形式で表示させるように修正

```index.html```
{% extends "base.html" %}
{% block content %}
<h1>メモアプリ</h1>
    {% if memo_list == [] %}
        <p>メモは登録されていません</p>
    {% else %}
        <table border="1">
            <tr>
                <th>タイトル</th>
                <th>メモ</th>
            </tr>
            {% for memo in memo_list %}
                <tr>
                    <td>{{ memo.title }}</td>
                    <td>{{ memo.body }}</td>
                </tr>
            {% endfor %}
        </table>
    {% endif %}
{% endblock %}

DBを使用したtop画面の作成

DB(:DataBase)に接続

$ pwd
/Users/koji/Desktop/guri_flask/myproject

% sqlite3 flaskmemo.db
SQLite version 3.43.2 2023-10-10 13:08:14
Enter ".help" for usage hints.
sqlite>

Ctrl+Cキーで接続解除できる。

テーブルを作成

DBに接続した状態で、テーブルを作成。

$ sqlite3 flaskmemo.db
SQLite version 3.43.2 2023-10-10 13:08:14
Enter ".help" for usage hints.
sqlite> CREATE TABLE memo(
(x1...> id INTEGER PRIMARY KEY AUTOINCREMENT,
(x1...> title TEXT NOT NULL,
(x1...> body TEXT NOT NULL
(x1...> );
sqlite> 

作成済みのテーブル一覧を表示して確認。

sqlite> .tables
memo

.tablesで今定義しているテーブルの一覧を表示する。

memoというテーブル名で作成したものを確認。

また、.schemaでテーブルの構造(schema)を表示する。

sqlite> .schema
CREATE TABLE memo(
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL
);
CREATE TABLE sqlite_sequence(name,seq);

INSERTでレコードを作成する

sqlite> INSERT INTO memo (title, body)
   ...> VALUES ("First message", "Hello world");
sqlite> INSERT INTO memo (title, body)
   ...> VALUES ("Second message", "Hello Guri");

UPDATEでレコードを更新する

DBに接続した状態で、テーブル一覧表示して確認。

$ sqlite3 flaskmemo.db
SQLite version 3.43.2 2023-10-10 13:08:14
Enter ".help" for usage hints.
sqlite> .tables
memo

テーブル(テーブル名がmemo)のレコードを確認する。

sqlite> SELECT * FROM memo;
1|First message|Hello World
2|Second message|Hello Guri
3|Third message|Hello Gura

続いて、テーブル(テーブル名がmemo)のレコードを更新する。

qlite> UPDATE memo 
   ...> SET body="Hello World!"
   ...> WHERE id=1;

テーブル(テーブル名がmemo)のレコードを確認する。

sqlite> SELECT * FROM memo;
1|First message|Hello World!
2|Second message|Hello Guri
3|Third message|Hello Gura

DELETEでテーブルからレコードを削除

DBに接続。

$ sqlite3 flaskmemo.db 
SQLite version 3.43.2 2023-10-10 13:08:14
Enter ".help" for usage hints.
sqlite> 

テーブル一覧を表示。

sqlite> .tables
memo

テーブルの構造を表示。

sqlite> .schema
CREATE TABLE memo(
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL
);
CREATE TABLE sqlite_sequence(name,seq);

テーブル名がmemoのテーブルのレコードを表示。

sqlite> SELECT * FROM memo;
1|First message|Hello World!
2|Second message|Hello Guri
3|Third message|Hello Gura

id=3のレコードを削除してみる。

sqlite> DELETE FROM memo
   ...> WHERE id=3;

# 確認
sqlite> SELECT * FROM memo;
1|First message|Hello World!
2|Second message|Hello Guri

BEGIN; ROLLBACK;あるいはBEGIN; COMMIT;でトランザクション処理

top画面でDBを利用した実装

```app.py```
from flask import Flask
from flask import render_template, g
import sqlite3
DATABASE = "flaskmemo.db"

app = Flask(__name__)

@app.route("/")
def top():
    memo_list = get_db().execute("SELECT id, title, body FROM memo").fetchall()
    return render_template('index.html', memo_list=memo_list)


# DBと接続
def connect_db():
    con = sqlite3.connect(DATABASE)
    con.row_factory = sqlite3.Row
    return con

def get_db():
    if not hasattr(g, 'sqlite_do'):
        g.sqlite_db = connect_db()
    return g.sqlite_db


if __name__ == "__main__":
    app.run()

top画面から登録画面への遷移

top画面に新規登録のボタンを追加。

```app.py```
{% extends "base.html" %}
{% block content %}
<h1>メモアプリ</h1>
    <a href="/regist" role="button">新規登録</a>
    {% if memo_list == [] %}
        <p>メモは登録されていません</p>
    {% else %}
        <table border="1">
            <tr>
                <th>タイトル</th>
                <th>メモ</th>
            </tr>
            {% for memo in memo_list %}
                <tr>
                    <td>{{ memo.title }}</td>
                    <td>{{ memo.body }}</td>
                </tr>
            {% endfor %}
        </table>
    {% endif %}
{% endblock %}

登録画面へのルーティングを追加。

```app.py```
from flask import Flask
from flask import render_template, g
import sqlite3
DATABASE = "flaskmemo.db"

app = Flask(__name__)

@app.route("/")
def top():
    memo_list = get_db().execute("SELECT id, title, body FROM memo").fetchall()
    return render_template('index.html', memo_list=memo_list)

@app.route("/regist", methods=['GET', 'POST'])
def regist():
    return render_template('regist.html')


# DBと接続
def connect_db():
    con = sqlite3.connect(DATABASE)
    con.row_factory = sqlite3.Row
    return con

def get_db():
    if not hasattr(g, 'sqlite_do'):
        g.sqlite_db = connect_db()
    return g.sqlite_db


if __name__ == "__main__":
    app.run()

登録画面regist.htmlを作成

```regist.html```
{% extends "base.html" %}
{% block content %}
<h1>新規登録</h1>
<form method="POST">
    <table border="1">
        <tr>
            <th>タイトル</th>
            <th>メモ</th>
        </tr>
        <tr>
            <td>
                <input type="text" name="title">
            </td>
            <td>
                <input type="text" name="body">
            </td>
        </tr>
    </table>
    <input type="submit" value="新規登録">
</form>
<a href="/" role="buttun">一覧へ戻る</a>
{% endblock %}

新規登録画面で新規登録機能の実装

index.html

{% extends "base.html" %}
{% block content %}
<h1>メモアプリ</h1>
    <a href="/regist" role="button">新規登録</a>
    {% if memo_list == [] %}
        <p>メモは登録されていません</p>
    {% else %}
        <table border="1">
            <tr>
                <th>タイトル</th>
                <th>メモ</th>
            </tr>
            {% for memo in memo_list %}
                <tr>
                    <td>{{ memo.title }}</td>
                    <td>{{ memo.body }}</td>
                </tr>
            {% endfor %}
        </table>
    {% endif %}
{% endblock %}

regist.html

{% extends "base.html" %}
{% block content %}
<h1>新規登録</h1>
<form method="POST">
    <table border="1">
        <tr>
            <th>タイトル</th>
            <th>メモ</th>
        </tr>
        <tr>
            <td>
                <input type="text" name="title">
            </td>
            <td>
                <input type="text" name="body">
            </td>
        </tr>
    </table>
    <input type="submit" value="新規登録">
</form>
<a href="/" role="buttun">一覧へ戻る</a>
{% endblock %}

app.py

from flask import Flask
from flask import render_template, g, redirect, request
import sqlite3
DATABASE = "flaskmemo.db"


app = Flask(__name__)

@app.route("/")
def top():
    memo_list = get_db().execute("SELECT id, title, body FROM memo").fetchall()
    return render_template('index.html', memo_list=memo_list)

@app.route("/regist", methods=['GET', 'POST'])
def regist():
    if request.method == 'POST':
        # 画面からの登録情報の取得
        title = request.form.get('title')
        body = request.form.get('body')
        # DBに登録
        db = get_db()
        db.execute('INSERT into memo (title, body) VALUES (?, ?)', [title, body])
        db.commit()
        return redirect('/')
    return render_template('regist.html')


# DBと接続
def connect_db():
    con = sqlite3.connect(DATABASE)
    con.row_factory = sqlite3.Row
    return con

def get_db():
    if not hasattr(g, 'sqlite_do'):
        g.sqlite_db = connect_db()
    return g.sqlite_db


if __name__ == "__main__":
    app.run()

メモの編集画面を作成

top画面(index.html)の編集

新しいカラムを右に追加。

{% extends "base.html" %}
{% block content %}
<h1>メモアプリ</h1>
    <a href="/regist" role="button">新規登録</a>
    {% if memo_list == [] %}
        <p>メモは登録されていません</p>
    {% else %}
        <table border="1">
            <tr>
                <th>タイトル</th>
                <th>メモ</th>
                <th></th>
            </tr>
            {% for memo in memo_list %}
                <tr>
                    <td>{{ memo.title }}</td>
                    <td>{{ memo.body }}</td>
                    <td>
                        <a href="/{{memo.id}}/edit" role="button">編集</a>
                    </td>
                </tr>
            {% endfor %}
        </table>
    {% endif %}
{% endblock %}

Python側(app.py)を編集

from flask import Flask
from flask import render_template, g, redirect, request
import sqlite3
DATABASE = "flaskmemo.db"


app = Flask(__name__)

@app.route("/")
def top():
    memo_list = get_db().execute("SELECT id, title, body FROM memo").fetchall()
    return render_template('index.html', memo_list=memo_list)

@app.route("/regist", methods=['GET', 'POST'])
def regist():
    if request.method == 'POST':
        # 画面からの登録情報の取得
        title = request.form.get('title')
        body = request.form.get('body')
        # DBに登録
        db = get_db()
        db.execute('INSERT into memo (title, body) VALUES (?, ?)', [title, body])
        db.commit()
        return redirect('/')
    return render_template('regist.html')

@app.route("/<id>/edit", methods=['GET', 'POST'])
def edit(id):
    if request.method == 'POST':
        # 画面からの登録情報の取得
        title = request.form.get('title')
        body = request.form.get('body')
        # DBに登録
        db = get_db()
        db.execute('UPDATE memo SET title=?, body=? WHERE id=?', [title, body, id])
        db.commit()
        return redirect('/')

    post = get_db().execute(
        "SELECT id, title, body FROM memo WHERE id=?", (id, )
    ).fetchone()
    return render_template('edit.html', post=post)

# DBと接続
def connect_db():
    con = sqlite3.connect(DATABASE)
    con.row_factory = sqlite3.Row
    return con

def get_db():
    if not hasattr(g, 'sqlite_do'):
        g.sqlite_db = connect_db()
    return g.sqlite_db


if __name__ == "__main__":
    app.run()

編集画面(edit.html)の作成

{% extends "base.html" %}
{% block content %}
<h1>編集</h1>
<form method="POST">
    <table border="1">
        <tr>
            <th>タイトル</th>
            <th>メモ</th>
        </tr>
        <tr>
            <td>
                <input type="text" name="title" value="{{post.title}}">
            </td>
            <td>
                <input type="text" name="body" value="{{post.body}}">
            </td>
        </tr>
    </table>
    <input type="submit" value="編集">
</form>
<a href="/" role="buttun">一覧へ戻る</a>
{% endblock %}

削除画面の作成

トップ画面(index.html)を修正

削除ボタンを追加。

{% extends "base.html" %}
{% block content %}
<h1>メモアプリ</h1>
    <a href="/regist" role="button">新規登録</a>
    {% if memo_list == [] %}
        <p>メモは登録されていません</p>
    {% else %}
        <table border="1">
            <tr>
                <th>タイトル</th>
                <th>メモ</th>
                <th></th>
            </tr>
            {% for memo in memo_list %}
                <tr>
                    <td>{{ memo.title }}</td>
                    <td>{{ memo.body }}</td>
                    <td>
                        <a href="/{{memo.id}}/edit" role="button">編集</a>
                        <a href="/{{memo.id}}/delete" role="button">削除</a>
                    </td>
                </tr>
            {% endfor %}
        </table>
    {% endif %}
{% endblock %}

Python側の修正①(delete.htmlを返す機能のみ実装)

from flask import Flask
from flask import render_template, g, redirect, request
import sqlite3
DATABASE = "flaskmemo.db"


app = Flask(__name__)

@app.route("/")
def top():
    memo_list = get_db().execute("SELECT id, title, body FROM memo").fetchall()
    return render_template('index.html', memo_list=memo_list)

@app.route("/regist", methods=['GET', 'POST'])
def regist():
    if request.method == 'POST':
        # 画面からの登録情報の取得
        title = request.form.get('title')
        body = request.form.get('body')
        # DBに登録
        db = get_db()
        db.execute('INSERT into memo (title, body) VALUES (?, ?)', [title, body])
        db.commit()
        return redirect('/')
    return render_template('regist.html')

@app.route("/<id>/edit", methods=['GET', 'POST'])
def edit(id):
    if request.method == 'POST':
        # 画面からの登録情報の取得
        title = request.form.get('title')
        body = request.form.get('body')
        # DBに登録
        db = get_db()
        db.execute('UPDATE memo SET title=?, body=? WHERE id=?', [title, body, id])
        db.commit()
        return redirect('/')

    post = get_db().execute(
        "SELECT id, title, body FROM memo WHERE id=?", (id, )
    ).fetchone()
    return render_template('edit.html', post=post)


@app.route("/<id>/delete", methods=['GET', 'POST'])
def delete(id):
    post = get_db().execute(
        "SELECT id, title, body FROM memo WHERE id=?", (id, )
    ).fetchone()
    return render_template('delete.html', post=post)


# DBと接続
def connect_db():
    con = sqlite3.connect(DATABASE)
    con.row_factory = sqlite3.Row
    return con

def get_db():
    if not hasattr(g, 'sqlite_do'):
        g.sqlite_db = connect_db()
    return g.sqlite_db


if __name__ == "__main__":
    app.run()

削除画面(delete.html)の作成

{% extends "base.html" %}
{% block content %}
<h1>削除</h1>
<p>以下のメモを削除しますか?</p>
<form method="POST">
    <table border="1">
        <tr>
            <th>タイトル</th>
            <th>メモ</th>
        </tr>
        <tr>
            <td>
                <input type="text" name="title" value="{{post.title}}" disabled>
            </td>
            <td>
                <input type="text" name="body" value="{{post.body}}" disabled>
            </td>
        </tr>
    </table>
    <input type="submit" value="削除">
</form>
<a href="/" role="buttun">一覧へ戻る</a>
{% endblock %}

Python側のapp.pyの編集②(削除ボタンで削除機能を追加

from flask import Flask
from flask import render_template, g, redirect, request
import sqlite3
DATABASE = "flaskmemo.db"


app = Flask(__name__)

@app.route("/")
def top():
    memo_list = get_db().execute("SELECT id, title, body FROM memo").fetchall()
    return render_template('index.html', memo_list=memo_list)

@app.route("/regist", methods=['GET', 'POST'])
def regist():
    if request.method == 'POST':
        # 画面からの登録情報の取得
        title = request.form.get('title')
        body = request.form.get('body')
        # DBに登録
        db = get_db()
        db.execute('INSERT into memo (title, body) VALUES (?, ?)', [title, body])
        db.commit()
        return redirect('/')
    return render_template('regist.html')

@app.route("/<id>/edit", methods=['GET', 'POST'])
def edit(id):
    if request.method == 'POST':
        # 画面からの登録情報の取得
        title = request.form.get('title')
        body = request.form.get('body')
        # DBに登録
        db = get_db()
        db.execute('UPDATE memo SET title=?, body=? WHERE id=?', [title, body, id])
        db.commit()
        return redirect('/')

    post = get_db().execute(
        "SELECT id, title, body FROM memo WHERE id=?", (id, )
    ).fetchone()
    return render_template('edit.html', post=post)


@app.route("/<id>/delete", methods=['GET', 'POST'])
def delete(id):
    if request.method == 'POST':
        # 画面から登録情報の取得
        db = get_db()
        db.execute('DELETE FROM memo WHERE id=?', (id,))
        db.commit()
        return redirect('/')

    post = get_db().execute(
        "SELECT id, title, body FROM memo WHERE id=?", (id, )
    ).fetchone()
    return render_template('delete.html', post=post)


# DBと接続
def connect_db():
    con = sqlite3.connect(DATABASE)
    con.row_factory = sqlite3.Row
    return con

def get_db():
    if not hasattr(g, 'sqlite_do'):
        g.sqlite_db = connect_db()
    return g.sqlite_db


if __name__ == "__main__":
    app.run()

ログイン画面の作成

flask-loginライブラリのインストール。

$ pip install flask-login

確認。

$ pip list | grep Flask
Flask              3.0.3
Flask-Login        0.6.3

コメント

タイトルとURLをコピーしました