Published Date : 2020年11月29日12:02

これまでに作ったFlaskの二つのアプリを組み合わせてHEROKUにアップする
Combine the two Flask apps we've created and upload them to HEROKU


This blog has an English translation


YouTubeにアップした動画、「【日本語解説】これまでに作ったFlaskの二つのアプリを組み合わせる」の補足説明の記事です。

Here's a little more about the 「【Python】​Combine the two Flask apps we've created」 video I uploaded to YouTube.

字幕を付けるなら、英語音声と日本語音声の二つに分けたほうが良いと思ったので、今回から同じ動画だが音声が日本語と英語に分かれたものをアップしました。

I thought it would be better to separate the video into English audio and Japanese audio instead of adding subtitles to the video, so I uploaded the same video with Japanese and English audio separately.

ページの頭に日本語解説動画、ページの最後に英語解説動画のリンクが貼られてます。

There is a Japanese explanation video at the top of the page, and an English explanation video link at the end of the page.

【日本語解説】これまでに作ったFlaskの二つのアプリを組み合わせる


目次

Table of Contents




① 動画の説明
① Video Description



今まで作った二つのアプリを一つにまとめて、Herokuにアップしてみましょう。

​Let's combine the two apps we've created so far and upload them to Heroku.

前回の時と同じように、適当なフォルダを作成します。

Create a folder as you did last time.

そのフォルダ内にpythonファイル、templatesフォルダ内にHTMLファイル、staticとjsフォルダ内にjsファイルを作成します。

Create a python file in that folder, an HTML file in the templates folder, and a js file in the static and js folders.

新しいフォルダ/
    static/
        js/
            script.js
    templates/
        cp.html
        ev.html
        index.html
    application.py

まず二つのアプリのハブとなるインデックスページを作成します。

First, create an index page to serve as a hub for the two apps.

仕組みは単純です。二つのアプリへのリンクボタンを作成し、Jinja2のblockを利用して、二つのアプリのページにこのインデックスページを継承させます。

The mechanism is simple. Create a link button to the two apps and use the Jinja2's block to have the pages of the two apps inherit this index page.

application.py
from flask import Flask, render_template, request
import string
app = Flask(__name__)

@app.route('/')
def index():
    description = """
    連続する確率の計算と期待値の計算を求めるアプリ 
    """
    # ​calculation of successive probabilities
    btn_text_cp = '連続する確率の計算'
    # ​Calculation of the expected value
    btn_text_ev = '期待値の計算'

    cp_link = '/cp'
    ev_link = '/ev'

    index_content = {
        'description': description,
        'btn_text_cp': btn_text_cp,
        'btn_text_ev': btn_text_ev,
        'cp_link': cp_link,
        'ev_link': ev_link,
    }

    return render_template('index.html', **index_content)
if __name__ == "__main__":
    app.run()
templates/index.html
<!doctype html>
<html lang="ja">


<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href=""
        integrity="" crossorigin="anonymous">

    <title>成功確率計算と期待値計算</title>
</head>

<body>
    <div class="container">
        <h3 class="text-center p-3">{{ description }}</h3>

        <div class="text-center p-3">
            <a href={{ cp_link }}><button class="btn btn-primary">{{ btn_text_cp }}</button></a>
        </div>
        <div class="text-center p-3">
            <a href={{ ev_link }}><button class="btn btn-primary">{{ btn_text_ev }}</button></a>
        </div>
        {% block cp %}
        {% endblock %}
        {% block ev %}
        {% endblock %}


        <!-- Optional JavaScript -->
        <!-- jQuery first, then Popper.js, then Bootstrap JS -->
        <script src=""
            integrity=""
            crossorigin="anonymous"></script>
        <script src=""
            integrity=""
            crossorigin="anonymous"></script>
        <script src=""
            integrity=""
            crossorigin="anonymous"></script>

        <script src="../static/js/script.js"></script>
</body>

</html>
templates/cp.html
{% extends "index.html" %}
{% block cp %}
<div class="container">

    <div class="container" style="padding: 15px;">
        <h1>成功確率計算機です。</h1>
        <p>成功する確率と試行回数を入力してくだちぃ。</p>
    </div>

    <div class="container" style="padding: 15px;">
        <form method="POST">
            <div class="form-group">
                <label for="probability">成功する確率は?(%) : probability of success</label>
                <input type="number" step="0.0001" class="form-control" name="probability"
                    placeholder="1%なら1と入力、0.1%なら0.1">
            </div>
            <div class="form-group">
                <label for="count">何回挑戦する?または個数をいくつ上げる? : Number of attempts</label>
                <input type="number" step="1" class="form-control" name="count" placeholder="500">
            </div>
            <button type="submit" class="btn btn-primary">Calculate</button>
        </form>
    </div>
</div>
</div>

<div class="container" style="padding: 15px;">
    {% if error_message %}
    <p class="text-danger"><b>{{ error_message }}</b></p>
    {% else %}
    <p>{{ possibility_message | safe }}</p>
    <p>{{ count_required_message | safe }}</p>
    {% endif %}
</div>
{% endblock %}
templates/ev.html
{% extends "index.html" %}
{% block ev %}
<div class="container">
    <div class="container" style="padding: 15px;">
        <h1>期待値計算機です。</h1>
        <p>得られるお金とその確率を入力してくだちぃ。</p>
    </div>

    <div class="container" style="padding: 15px;">
        <form method="post">
            <div id="prob-form" class="form-group">
                <label for="probabilities">確率は?(%)</label>
                <input type="number" step="any" class="form-control" name="probability" placeholder="1%なら1と入力、0.1%なら0.1"
                    min="0.000001" max="100">
            </div>
            <div style="padding: 1px 0px 30px;">
                <input id="add-prob" class="btn btn-primary" type="button" value="add">
                <input id="rm-prob" class="btn btn-secondary" type="button" value="remove">
            </div>
            <div id="earnings-form" class="form-group">
                <label for="earnings">得られるお金は?</label>
                <input type="number" step="1" class="form-control" name="earnings" placeholder="円、マイナスの値はダメ、30億円以上はダメ"
                    min="0" max="3000000000">
            </div>
            <div style="padding: 1px 0px 30px;">
                <input id="add-earnings" class="btn btn-primary" type="button" value="add">
                <input id="rm-earnings" class="btn btn-secondary" type="button" value="remove">
            </div>
            <button type="submit" class="btn btn-success" id="calculate-btn">Calculate</button>
            <input type="reset" class="btn btn-danger" id="reset-btn" value="Reset">
        </form>
    </div>

    <div class="container" style="padding: 15px;">
        {% if error_message != '' %}
        <p class="text-danger"><b>{{ error_message }}</b></p>
        {% else %}
        <table class="table">
            <thead class="thead-dark">
                <tr>
                    <th scope="col">期待値計算表</th>
                    {% for name in names %}
                    <th scope="col"></th>
                    {% endfor %}
                    <th scope="col">期待値合計</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <th scope="row">イベント</th>
                    {% for name in names %}
                    <td>{{ name }}</td>
                    {% endfor %}
                    <td>/</td>
                </tr>
                <tr>
                    <th scope="row">確率(%)</th>
                    {% for text in probability_text %}
                    <td>{{ text }}</td>
                    {% endfor %}
                    <td>/</td>
                </tr>
                <tr>
                    <th scope="row">儲け(円)</th>
                    {% for text in earnings_text %}
                    <td>{{ text }}</td>
                    {% endfor %}
                    <td>/</td>
                </tr>
                <tr>
                    <th scope="row">期待値(円)</th>
                    {% for ev in expected_values %}
                    <td>{{ ev }}</td>
                    {% endfor %}
                    <td><b class="text-danger">{{ expected_value }}円</b></td>
                </tr>

            </tbody>
        </table>
        <div class="container" style="padding: 15px;">
            <h3 class="text-center">1回あたり<b class="text-danger">{{ expected_value }}円未満</b>でやり続けるならプラスになるかも。</h3>
        </div>
        {% endif %}
    </div>
</div>
{% endblock %}

レイアウトを確認したら、ボタンを押した際に、アプリのページに移動できるようにします。

After checking the layout, ​Create a script that takes you to the app's page by pressing a button.

そうしたら、後は前回作成したスクリプトをコピペしていくだけです。

Then all you have to do is copy and paste the script you created last time.

static/js/script.js
$(function () {
    $("input#add-prob").click(function () {
        var prob_ = $("div#prob-form label");
        if (prob_.length < 9) {
            $("div#prob-form").append('<label>確率は?(%)</label><input>');
            $('div#prob-form label').each(function (i) {
                $(this).attr('id', 'probability' + i)
                    .attr('for', 'probabilities');
            });
            $('div#prob-form input').each(function (i) {
                $(this).attr('id', 'probability' + i)
                    .attr('type', 'number')
                    .attr('step', 'any')
                    .attr('class', 'form-control')
                    .attr('name', 'probability')
                    .attr('placeholder', '1%なら1と入力、0.1%なら0.1')
                    .attr('min', '0.000001')
                    .attr('max', '100.0');
            });
        } else {
            var note = "注意:9個以下にしてね!"
            alert(note)
        }
    });
    $("input#rm-prob").click(function () {
        var prob_label = $('div#prob-form label');
        if (prob_label.length > 1) {
            $('div#prob-form label:last').remove();
        }
        var prob_input = $('div#prob-form input');
        if (prob_input.length > 1) {
            $('div#prob-form input:last').remove();
        }
    });

    $("input#add-earnings").click(function () {
        var earnings_ = $("div#earnings-form label");
        if (earnings_.length < 9) {
            $("div#earnings-form").append('<label>得られるお金は?</label><input>');
            $('div#earnings-form label').each(function (i) {
                $(this).attr('id', 'earnings' + i)
                    .attr('for', 'earnings');
            });
            $('div#earnings-form input').each(function (i) {
                $(this).attr('id', 'earnings' + i)
                    .attr('type', 'number')
                    .attr('step', '1')
                    .attr('class', 'form-control')
                    .attr('name', 'earnings')
                    .attr('placeholder', '円、マイナスの値はダメ、30億円以上はダメ')
                    .attr('min', '0')
                    .attr('max', '3000000000');
            });
        } else {
            var note = "注意:9個以下にしてね!"
            alert(note)
        }
    });
    $("input#rm-earnings").click(function () {
        var earnings_label = $('div#earnings-form label');
        if (earnings_label.length > 1) {
            $('div#earnings-form label:last').remove();
        }
        var earnings_input = $('div#earnings-form input');
        if (earnings_input.length > 1) {
            $('div#earnings-form input:last').remove();
        }
    });

});
application.py
from flask import Flask, render_template, request
import string
app = Flask(__name__)


@app.route('/')
def index():
    description = """
    連続する確率の計算と期待値の計算を求めるアプリ 
    """
    # ​calculation of successive probabilities
    btn_text_cp = '連続する確率の計算'
    # ​Calculation of the expected value
    btn_text_ev = '期待値の計算'

    cp_link = '/cp'
    ev_link = '/ev'

    index_content = {
        'description': description,
        'btn_text_cp': btn_text_cp,
        'btn_text_ev': btn_text_ev,
        'cp_link': cp_link,
        'ev_link': ev_link,
    }

    return render_template('index.html', **index_content)


@app.route('/cp', methods=['GET', 'POST'])
def calculate_cp():
    description = """
    連続する確率の計算 
    """
    # ​return to index
    btn_text_cp = 'インデックスページに戻る'
    # ​Calculation of the expected value
    btn_text_ev = '期待値の計算'

    cp_link = '/'
    ev_link = '/ev'

    prob = request.form.get('probability')
    count = request.form.get('count')

    possibility_message = ''
    count_required_message = ''
    error_message = ''

    if prob and count:

        prob = float(prob)
        count = int(count)

        if prob < 0 or count < 0:
            error_message = 'マイナスの値は入れないでください!'
        elif prob > 100:
            error_message = '成功確率に100より大きい数字を入れないでください!'
        else:
            possibility = 1 - (1 - (prob/100)) ** count

            possibility_message = f'''
            成功確率<b><{prob}%></b>の場合、<br>
            <b><{count:,}(回or個)></b>試行すると、<br>
            成功する可能性は<b><{possibility * 100:.3f}%></b>になります。<br>
            '''
            error_message = ''

            # success rate of more than 99.9%
            success_rate = 0.0
            # Number of attempts required
            count_required = 0
            while success_rate < 0.999:
                success_rate = 1 - (1 - (prob/100)) ** count_required
                count_required += 1

            count_required_message = f'''また、成功確率<b><{prob}%></b>の場合、<br>
            <b><{count_required:,}(回or個)></b>試行することで、<br>
            成功確率を<b><99.9%></b>まで上げることができます。<br>'''

    cp_content = {
        'possibility_message': possibility_message,
        'count_required_message': count_required_message,
        'error_message': error_message,
        'description': description,
        'btn_text_cp': btn_text_cp,
        'btn_text_ev': btn_text_ev,
        'cp_link': cp_link,
        'ev_link': ev_link,
    }

    return render_template('cp.html', **cp_content)


@app.route('/ev', methods=['GET', 'POST'])
def calculate_ev():
    description = """
    期待値の計算 
    """
    # ​calculation of successive probabilities
    btn_text_cp = '連続する確率の計算'
    # ​return to index
    btn_text_ev = 'インデックスページに戻る'

    cp_link = '/cp'
    ev_link = '/'

    names = ''
    probability_text = ''
    earnings_text = ''
    expected_values = ''
    expected_value = ''
    error_message = ''

    if request.method == 'POST':

        probabilities = request.form.getlist('probability')
        earnings = request.form.getlist('earnings')

        if not '' in probabilities and not '' in earnings:
            if len(probabilities) == len(earnings):
                names = [string.ascii_uppercase[i]
                            for i in range(len(probabilities))]
                probability_text = [p+'%' for p in probabilities]
                earnings_text = [e+'円' for e in earnings]
                expected_values = [str(round(float(p) * (float(earnings[i])/100))) +
                                    '円' for i, p in enumerate(probabilities)]
                expected_value = round(
                    sum([float(p) * (float(earnings[i])/100) for i, p in enumerate(probabilities)]))
            else:
                error_message = '数を合わせてください!'

        else:
            error_message = '全て入力してください!'

    ev_content = {
        'names': names,
        'probability_text': probability_text,
        'earnings_text': earnings_text,
        'expected_values': expected_values,
        'expected_value': expected_value,
        'error_message': error_message,
        'description': description,
        'btn_text_cp': btn_text_cp,
        'btn_text_ev': btn_text_ev,
        'cp_link': cp_link,
        'ev_link': ev_link,
    }
    return render_template('ev.html', **ev_content)


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

それではHerokuにこのアプリをアップロードしていきましょう。

​Let's upload this application to Heroku.

Heroku CLIやgitのインストール、Herokuアプリの登録等の説明は以前の動画を参考にしてくだちぃ。

​See my previous video for instructions on installing Heroku CLI, git, and registering Heroku apps.

いつものようにProcfileにアプリの起動方法を記述します。

As usual, you write how to start the application in the Procfile.

Procfile
web: gunicorn application:app --log-file=-

requirements.txtは以前のアプリのものを再利用します。

The content of requirements.txt is reused from the previous application.

requirements.txt
click==7.1.2
Flask==1.1.2
gunicorn==20.0.4
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
python-dateutil==2.8.1
pytz==2020.1
Werkzeug==1.0.1

そして、いつものようにHeroku CLIとGitコマンドを使ってHerokuアプリをデプロイします。

​Then deploy the Heroku app using the Heroku CLI and Git commands as usual.

後はHerokuが用意したアプリのURLにアクセスして色々と試してみてください。

Then, Visit the application URL provided by Heroku, and please try various things and play.



以上です。お疲れ様です。

That's all. Thank you for your hard work.


【Python】​Combine the two Flask apps we've created