NO IMAGE

【Python,Flask】FlaskでWebアプリケーション作成(How to creat Flask webapp)

FlaskでのWebアプリ作成方法(Windows)を解説します。作成するアプリは、同位体年代測定など、理系・研究者向けのちょっとした専門計算サイトです。

今回の題材は、その中の『同位体年代測定アプリ』です。要するに、回帰直線を引くアプリケーションです。笑

Point!

  • 環境構築
  • Anconda Promptの使用方法
  • Flaskのファイル構成
  • app.pyの基本形
  • テンプレートの作成と継承
  • 計算ロジックの組み込み

環境を構築する(Windows版)

Anacondaをインストール

Anacondaをインストールしてください。解説は、他のサイトを引用します。

👇Windows版Anacondaのインストール(Python Japan)

https://www.python.jp/install/anaconda/windows/install.html

👇Anacondaのインストール (Let’s プログラミング)

https://www.javadrive.jp/python/install/index5.html

👇Anaconda(Python3)インストール手順<Windows用>(IT入門書籍)

https://sukkiri.jp/technologies/ides/anaconda-win_install.html

Anaconda インストール

anaconda
Anaconda インストール画面

Anaconda Promptで仮想環境の作成とpip install Flask

1.Anaconda Promptを起動

Anaconda Promptを起動します。

Anaconda Prompt

2.Flask用のファイルを作成

どこでも良いので、Flask用のファイルを作成します。私は、今回『calcsite』という名前のフォルダ作りました。後ほどファイル構成については、解説します。

3.仮想環境の構築

3-1. 仮想環境の作成

まず、現在の状態を確認します。『conda env list』と入力します。

最初は、『base』という環境しかないはずです。

(base) C:\Users>conda env list
# conda environments:
#
base                  *  C:\Users\Anaconda3

次に仮想環境を構築します。以下のコマンドプロントを入力します。環境名はお好みで変更してください。私は、”flaskenv”とします。

(base) C:\Users>conda create -n 環境名 python

また、pythonのバージョンを指定する場合、以下のようにバージョンを指定します。

(base) C:\Users>conda create -n 環境名 python==3.6

これで、現在の状態を確認すると以下のよう『env/flaskenv』が作成できました。

以下、環境名=flaskenvとします。

(base) C:\Users>conda env list
# conda environments:
#
base                  *  C:\Users\Anaconda3
flaskenv                 C:\Users\Anaconda3\envs\flaskenv

3-2. 仮想環境の有効化

仮想環境を有効化します。以下のように『conda activate flaskenv』を入力します。

(base) C:\Users>conda activate flaskenv

先頭の(base)が(flaskenv)となれば、flaskenvの仮想環境が有効化されています。

(flaskenv) C:\Users>

3-3. 必要なモジュールインストール

今回は、以下のモジュールをインストールします。

  • Flask
  • numpy
  • matplotlib
  • gunicorn
  • Flask-SQLAlchemy (DBを使う場合のみ、今回は使用しません)

仮想環境で必要なモジュールをインストールには『pip install モジュール名』をAnacoda Promptに入力します。

以下、flaskを例にインストールします。

(flaskenv) C:\Users>pip install flask

他に必要なモジュールについても同様にインストールします。

以下の『pip list』でインストールしたモジュールを確認できます。以下は長くなるので、一部抜粋です。

(flaskenv) C:\Users>pip list
Package          Version
---------------- ---------
Flask            2.0.3
Flask-SQLAlchemy 2.5.1
gunicorn         20.1.0
Jinja2           3.0.3
matplotlib       3.5.1
numpy            1.22.3
pip              21.2.4
SQLAlchemy       1.4.32

ここまででFlaskを始めるための準備は終了です。

フォルダ構成

完成形のファイル構成は以下のようになります。

calcsite
│ app.py
│ Procfile
│ requirements.txt
│
├─static
│  ├─css
│  │   bootstrap.min.css
│  │   sidebars.css
│  │   style.css
│  │
│  └─js
│      bootstrap.bundle.min.js
│      form.js
│      sidebars.js
│
└─templates
    │ base.html
    │ error.html
    │ home.html
    │
    └─geochemi
          graph.html
          RbSr.html

アプリ内のロジック作成(app.pyの作成)と起動

app.pyの作成

アプリのロジックを作成します。app.pyをFlask用に作成したフォルダに作成し、以下のコードを記載します。

from flask import Flask

app = Flask(__name__)

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

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

app.pyの起動

次にAnaconda Promptで実行します。flask用のファイル(ここでは、calcsite)に移動して、『python app.py』を実行。以下のようにコードが出れば成功です。http://127.0.0.1:5000/へ移動すると、『Hello, World!』が出ます。

(flaskenv) C:\Users\calcsite>python app.py

 * Serving Flask app 'app' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Restarting with stat

 * Debugger is active!
 * Debugger PIN: 519-676-480
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

メインページの作成(templates)

続いて、メインページを作っていきます。ここではhtml、css、javascriptについては詳しくは説明しません。app.pyがhtmlをどのように呼び出すのか、解説します。

render_template()の使い方(home.htmlの作成と表示)

👇こんな感じのサイトを作りました。

templates/home.html

このサイトを呼び出すように、以下のようにapp.pyを書き換えます。先ほどのHello worldを記述したapp.pyからの変更点は、以下の2点。

  1. render_templateをインストール
  2. def home()に変更し、return先をhome.htmlに変更。
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('home.html')

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

その後、先ほどと同様にAnconda Promptで実行。

(flaskenv) C:\Users\calcsite>python app.py

作成したhtmlが表示されはずです。

templates/home.html

テンプレートの継承(base.htmlの作成)

navbarやsidebarなど何度も使い回しする部分について、base.htmlに記載して、どのページでも使い回すことができます。具体的には、base.htmlには私は以下を記載しました。

  • htmlの基本構文
  • cssやjsなどのstaticファイルからの読み込むためのコード
  • navbarとsidebarなど、画面遷移しても常に出ている部分

テンプレートの継承は、base.htmlには以下を記載。

  • headに{% block head %}{% endblock %}を記載
  • {% block body %}{% endblock %}をページごとに入れ替えたい部分に記載

<html lang="ja" class=" js">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

    <!-- title -->
    <title>エンジニアリング計算</title>
    {% block head %}{% endblock %}
  <head>
  <body>
      <nav>
     ~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~
      </nav>
   <div id="sidebar">
   ~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~
   <div>

     {% block body %}{% endblock %}

      <script src="static/js/sidebars.js"></script>
      <script src="static/js/form.js"></script>
   </body>
</html>

引き継ぎ先のhome.htmlには、以下を記載します。

  • 先頭に{% extends ‘base.html’ %}を記載して、base.htmlから引き継ぐ。
  • {% block body %}と{% endblock %}の間に中身を記載する。

{% extends 'base.html' %}

{% block body %}
<div class="main">
        <div class="l-main">
          <h2 id="header2"><a href="/">技術計算ツール</a></h2>

     ~~~~~~~~~~省略~~~~~~~~~~~~~~~~~~
        </div>
</div>
{% endblock %}

app.pyはそのままです。

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('home.html')

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

Anaconda Promptで実行して、先ほどと同じ画面が出れば成功です。

計算ページの作成

データ入力画面の作成(RbSr.htmlの作成)

👇以下のページを作成します。これは、ポイントとしては以下になります。

  • <form> のactionとmethods属性の入力
  • <input>のname入力

増減ができるテーブルの作り方と数式の表示(MathJax)については、Javascriptの要素が強いので省略。別記事にします。

{% extends 'base.html' %}

{% block body %}
<div class="main">
    <div class="container">
        <h2 class="l-main-ttl"  id="header1"><a href="/">同位体年代測定 Rb-Sr法</a></h2>
        <div>
            <h3>同位体年代測定の基本式</h3>
            <div id="f">
                <div>
                    \[
                        \frac{87Sr}{86Sr} = 
                        \left(\frac{87Sr}{86Sr}\right)_0  + \frac{87Rb}{86Sr} \left(e^{λt}-1\right)
                    \]
                </div>
            </div>
        </div>
        <br>

        <h3>データベース</h3>
        <form action="/graph" method = "POST">
            <table class="table table-striped table-hover">
                <thread>
                    <tr class="">
                        <th scope="col">#</th>
                        <th scope="col">87Sr/86Sr</th>
                        <th scope="col">87Rb/86Sr</th>
                        <th scope="col"></th>

                    </tr>
                </thread>
                <tbody id="RbSr_table">
                        <tr  id="num1">
                            <td>0</td>
                            <td><input type="number" name="Sr1" id="" step="0.000001" class="form-control" max="1" min="0"></td>
                            <td><input type="number" name="Rb1" id="" step="0.000001" class="form-control" max="1" min="0"></td>
                            <td>削除不可</td>
                        </tr>
                        <tr id="num2">
                            <td>1</td>
                            <td><input type="number" name="Sr1" id="" step="0.000001" class="form-control" max="1" min="0"></td>
                            <td><input type="number" name="Rb1" id="" step="0.000001" class="form-control" max="1" min="0"></td>
                            <td>削除不可</td>
                        </tr>
                        <tr>
                            <input type="button" value="データ入力欄追加" onclick="addForm2()">
                        </tr>
                </tbody>
            </table>
            <p style="color: red;">※2点以上のデータを入力してください</p>
            <br>      
                <input type="submit" value="Create Graph & claculation">
        </form>
        
    </div>
</div>
{% endblock %}

home.htmlからRbSr.htmlへの移動

app.pyにロジックを追加します。『/RbSr』へ遷移したとき、geochemi/RbSr.htmlを返すように記載を追加(9~11行目)。

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('home.html')

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

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

base.htmlやhome.htmlのRb-Sr法と書かれた部分に<a>タグで遷移先『/RbSr』を指定します。

以下、記載例です。

<span class="ss"><a href="/RbSr">Rb-Sr法</a></span>

計算結果表示ページ(RbSr.html→graph.html)への遷移

ここは要素が多いので、以下のように区切ろうと思います。

  1. formからの値をリストとして受け取る
  2. formから受け取った値をグラフ化
  3. graph.htmlへの結果の受け渡し
  4. エラーハンドリング

<form>からの値をリストとして受け取る

先ほどRbSr.htmlの<form>には以下のように属性を与えています。また、<input> に”Sr1”と”Rb1”というname属性を与えています

<form action="/graph" method = "POST">
  <table class="table table-striped table-hover">
~~~~~~~~~~省略~~~~~~~~~~~~~~~
   <tbody id="RbSr_table">
    <tr  id="num1">
          <td>0</td>
          <td><input type="number" name="Sr1" id="" step="0.000001" class="form-control" max="1" min="0"></td>
          <td><input type="number" name="Rb1" id="" step="0.000001" class="form-control" max="1" min="0"></td>
          <td>削除不可</td>
       </tr>
~~~~~~~~~~省略~~~~~~~~~~~~~~~
</form>

これを受けるapp.pyのロジックを作成します。先ほどまでのapp.pyに『/graph』を受けるロジックを追加します。さらに、<form>内の<input>をrequest.form.getlist(name属性)でリストとして取得します。

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('home.html')

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

@app.route('/graph',methods = ['POST', 'GET'])
def graph():
    if request.method == 'POST':
     Rb_list = request.form.getlist('Rb1')
        Sr_list = request.form.getlist('Sr1')

        return render_template('geochemi/graph.html')

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

formから受け取った値をグラフ化

/graphを受けるロジックをコードに沿って説明します。流れとしては、以下です。

  • モジュールをimport
  • <form>から受け取ったstrをfloatへ変換と行数のカウント
  • グラフを作成する(fig)
  • figでは表示できないので、base64に変換
  • 計算値と図をgraph.htmlへ引き渡す

全体のコードは以下のようになります。

from flask import Flask, render_template, redirect, request,
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg')
import io
import numpy as np
from io import BytesIO
import base64

def fig_to_base64_img(fig):
    """画像を base64 に変換する。
    """
    # png 形式で出力する。
    io = BytesIO()
    fig.savefig(io, format="png")
    # base64 形式に変換する。
    io.seek(0)
    base64_img = base64.b64encode(io.read()).decode()


@app.route('/graph',methods = ['POST', 'GET'])
def graph():
    if request.method == 'POST':
        try:
            Rb_list = request.form.getlist('Rb1')
            Sr_list = request.form.getlist('Sr1')
            Rb_list = [float(n) for n in Rb_list ]
            Sr_list = [float(n) for n in Sr_list ]
            n = len(Rb_list)

            x = Rb_list
            y = Sr_list

            fig = plt.figure(figsize=(15,6))
            # ax = plt.axes()
            ax = fig.add_subplot(111)

            cf1 = np.polyfit(x,y,1)
            func1 = np.poly1d(cf1)
            fig, ay = plt.subplots()
            ay.invert_yaxis()
            plt.plot(x, func1(x),label='d=1')
            plt.xlabel('87Rb/86Sr',fontsize=18)
            plt.ylabel("87Sr/86Sr",fontsize=18)
            # plt.ylim()
            plt.grid()
            plt.tick_params(labelsize=18)
            plt.tight_layout()
            plt.scatter(x,y)

            a = round(cf1[0],4)
            b = round(cf1[1],4) 
            slope = "87Sr/86Sr = " + str(a)+'(87Rb/86Sr) + ' +str(b)
            t = round(((np.log(1+a))/ (1.42*10**(-11))/100000000),3)

            img = fig_to_base64_img(fig)
            return render_template('geochemi/graph.html',img= img ,a=a,b=b,Rb_list=Rb_list,Sr_list=Sr_list,t=t,n=n)

        except ValueError:
            return render_template('error.html')

モジュールをimport

以下のモジュールをインストール。そのままコピペで良いです。

from flask import Flask, render_template, redirect, request,
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg')
import io
import numpy as np
from io import BytesIO
import base64

<form>から受け取ったstrをfloatへ変換と行数のカウント

request.form.getlistの中身がstr(文字列)として認識されているので、float型に変換します。

また、受け取った先でfor文を回すので行数をlen()でカウントしておきます。

Rb_list = request.form.getlist('Rb1')
Sr_list = request.form.getlist('Sr1')
Rb_list = [float(n) for n in Rb_list ]
Sr_list = [float(n) for n in Sr_list ]
n = len(Rb_list)

グラフを作成する(fig)&引き渡す数字を定義

ここはmatplotlib固有のものなので、解説は省略します。

傾きa、切片b、年代tをそれぞれ定義しておます。(16~18行目)

fig = plt.figure(figsize=(15,6))
ax = fig.add_subplot(111)

cf1 = np.polyfit(x,y,1)
func1 = np.poly1d(cf1)
fig, ay = plt.subplots()
ay.invert_yaxis()
plt.plot(x, func1(x),label='d=1')
plt.xlabel('87Rb/86Sr',fontsize=18)
plt.ylabel("87Sr/86Sr",fontsize=18)
plt.grid()
plt.tick_params(labelsize=18)
plt.tight_layout()
plt.scatter(x,y)

a = round(cf1[0],4)
b = round(cf1[1],4) 
t = round(((np.log(1+a))/ (1.42*10**(-11))/100000000),3)

figでは表示できないので、base64に変換

定義したfig_to_base64_imgでfigをbase64に変換します。

img = fig_to_base64_img(fig)
def fig_to_base64_img(fig):
    """画像を base64 に変換する。
    """
    # png 形式で出力する。
    io = BytesIO()
    fig.savefig(io, format="png")
    # base64 形式に変換する。
    io.seek(0)
    base64_img = base64.b64encode(io.read()).decode()

計算値と図をgraph.htmlへ引き渡す

render_templateでgeochemi/graph.htmlに計算値と図を引き渡します。

return render_template('geochemi/graph.html',img= img ,a=a,b=b,Rb_list=Rb_list,Sr_list=Sr_list,t=t,n=n)

データ結果出力画面の作成(graph.htmlの作成)

引き渡されたimg、a、b、Rb_list、Sr_list、t、nをそれぞれ以下のようにgraph.htmlで受けます

~~~~省略~~~~
     <tbody id="RbSr_table">
                {% for i in range(0,n) %}
                    <tr>
                        <td> {{ i }}</td>
                        <td>{{ Sr_list[i] }}</td>
                        <td>{{ Rb_list[i] }}</td>
                    </tr>
                {% endfor %}
           </tbody>

~~~~省略~~~~
            <div id="isochron">
                <h5>アイソクロン</h5>
                <div>
                    \[
                        \frac{87Sr}{86Sr} = 
                        {{ b }}  + \frac{87Rb}{86Sr} \left({{ a }}\right)
                    \]
                </div>
                <br>
                <div style="margin-left: 150px;">
                    <img src="data:image/png;base64,{{ img }}" class="figure-img img-fluid rounded"/>
                </div>
            </div>

            <h5 class="">年代(傾き)</h5>
                <div id="a">
                    \[
                        \left(e^{λt}-1\right) = {{ a }}
                    \]
                    \[
                        t = {{ t }} 億年
                    \]
                </div>
~~~~省略~~~~

これで完成です。

Anaconda Promptを実行すると、以下のように画面が変わっていきます。

home.html

値を入力して、『Create Graph&calculation』をクリック。

以下のようにデータが表示されました。

HerokuにDeploy

最後にHerokuにDeployします。これは、他のサイトに載っているの今回は省略します。簡単な手順だけ書いておきます

  • requirements.txtの作成
  • Procfileの作成
  • Githubにアップデート
  • HerokuとGithubを連携

こちらの記事もご参考にしてください。

まとめ

これができれば、一通りの簡単な計算サイトはFlaskで作成できます。

プログラミングの勉強に悩んだら、、

プログラミングの勉強に悩んだら、『Udemy』をオススメします。

オススメのポイント
・全ての講座がオンライン
・リーズナブルな価格で、実用的なスキル
・購入した講座を何度でも見直せる
・プログラミングの講座が豊富

プログラミング言語の人気オンラインコース 開発の人気オンラインコース

NO IMAGE
最新情報をチェックしよう!