LoginSignup
4
10

More than 5 years have passed since last update.

FlaskとHerokuでタイムズカープラス最安プラン選択アプリを作る

Last updated at Posted at 2018-08-26

はじめに

PythonではじめるHeroku 2018」で,Herokuのチュートリアルをやりました.

チュートリアルではあらかじめ用意されていたアプリ使ったので,今回は自分で書いたPythonコードを使って,Herokuにデプロイしてみたいと思います.具体的には,「タイムズカープラスで最安プランを選ぶ」で書いたPythonコードを動かします1

フレームワークはFlaskを使うことにします.

なぜFlask?

Flaskを使ったのは,以下のような思考によるものです.

  • 何かフレームワークを使ってみたい
  • でもシンプルなやつ,必要最小限で良い
  • なんかFlaskってのが良いらしい

参考にさせていただいたQiita記事

作業前のローカル環境

  • MacOSX 10.11.6
  • Python 3.6.3
  • HerokuCLI 7.7.10
  • Git 2.10.1

1. 準備

まずは準備です.ローカル環境を作っていきます.

1.1. Pipenvで環境を作る

PythonではじめるHeroku 2018」で学んだ通り,Pipenvを使います.

アプリ名はtcpopt(Times Car Plus Optimization)にしておきましょう.

terminal
$ mkdir tcpopt
$ cd tcpopt
$ pipenv --three

これでPipfileが作成されます.

1.2. Flaskインストール

Flaskをインストールします.あと,計算で使うPandasですね.

terminal
$ pipenv install flask
$ pipenv install pandas

この状態だと,Pipfileはこんな感じです.

terminal
$ more Pipfile
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
flask = "*"
pandas = "*"

[dev-packages]

[requires]
python_version = "3.6"

次はアプリの中身を作っていきます.

2. アプリの中身を作る

今回は(JavascriptもCSSも使わずに)フロントページで入力を促し,ボタンクリックで画面遷移して,遷移先で計算結果を表示する,というだけにしておきます.

2.1. Modelを作る

タイムズカープラスで最安プランを選ぶ」でほぼベタ書きだったコードを,ちょっと整理しておきます.

料金テーブルを保持するクラスFareTableと,条件に応じて料金を計算するクラスFareCalculatorを,1つのファイルmodel.pyにまとめます.

model.py
import math
import datetime as dt
import pandas as pd

class FareTable():
    def __init__(self):
        # plan names
        plans = ['short', 'hour6', 'hour12', 'hour24', 'early', 'late', 'double']

        # fare amount
        a = {'base':[0, 4020, 6690, 8230, 2060, 2060, 2580], 
             'extra':[206, 206, 206, 206, 206, 206, 206],
             'distance':[0, 0, 16, 16, 16, 16, 16]}
        amount = pd.DataFrame(data=a, index=plans)

        # fare condition
        c = {'type':['length', 'length', 'length', 'length', 'time', 'time', 'time'],
             'length':[0, 6, 12, 24, 6, 9, 15],
             'start':['', '', '', '', 18, 0, 18],
             'by':['', '', '', '', 24, 9, 24],
             'max':[72, 6, 6, 6, 6, 6, 6]}
        condition = pd.DataFrame(data=c, index=plans)
        self.__whole = pd.concat([amount, condition], axis=1)

    @property
    def whole(self):
        return self.__whole

class FareCalculator():
    def __init__(self, start, end, distance):
        self.s = start
        self.e = end
        self.d = distance
        self.__f = FareTable()

    @property
    def fare_table(self):
        return self.__f.whole

    def __calc_extra(self, plan):
        fare = self.__f.whole
        if fare.at[plan, 'type'] == 'length':
            extra = self.e - self.s - dt.timedelta(
                hours=int(fare.at[plan, 'length']))
        elif fare.at[plan, 'type'] == 'time':
            plan_start = dt.datetime(year=self.s.year, 
                                     month=self.s.month, 
                                     day=self.s.day, 
                                     hour=fare.at[plan, 'start'])
            plan_end = plan_start + dt.timedelta(
                hours=int(fare.at[plan, 'length']))
            extra = self.e - plan_end
        else:
            extra = math.inf
        return max(0, math.ceil((extra.total_seconds()/3600)*4))

    def calc_fare(self, plan):
        fare = self.__f.whole
        if (fare.at[plan, 'type'] == 'time') and (
                self.s.hour < fare.at[plan, 'start'] or
                self.s.hour >= fare.at[plan, 'by']):
            return math.inf
        else:
            extra_qnum = self.__calc_extra(plan)
        if extra_qnum > fare.at[plan, 'max']*4:
            return math.inf
        else:
            return (fare.at[plan, 'base'] + 
                    fare.at[plan, 'extra'] * extra_qnum + 
                    fare.at[plan, 'distance'] * self.d)

2.2. フロントページを作る

見た目は考えずに作ります.templateディレクトリの中に入れます.

template/index.html
<!doctype html>
<html>
  <head>
    <title>TCPOPT: Times Car Plus OPTimization</title>
  </head>
  <body>
    <h1>TCPOPT: Times Car Plus OPTimization</h1>
    <form method="post" action="/result">
      from:
      <input type="datetime-local" value="2018-08-26T19:30" name="start" /><br />
      to:
      <input type="datetime-local" value="2018-08-27T08:30" name="end" /><br />
      distance:
      <input type="number" value="200" min="0" max="500" step="1" name="distance" />km<br />
      <button>Calculate</button>
    </form>
  </body>
</html>

2.3. Viewを作る

とりあえず書いていきます.

datetime-localをパースする部分はこちらを参考にしました.きっともっと良いやり方があるのでしょうが,今は細かいことは気にしません.

view.py
from flask import Flask, render_template, request
import datetime as dt
import pandas as pd
from model import FareCalculator, FareTable

app = Flask(__name__)

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

@app.route('/result', methods=['POST'])
def result():
    # need some improvement
    start = request.form['start']
    start = start.replace('T', '-').replace(':', '-').split('-')
    start = [int(v) for v in start]
    start = dt.datetime(*start)
    end = request.form['end']
    end = end.replace('T', '-').replace(':', '-').split('-')
    end = [int(v) for v in end]
    end = dt.datetime(*end)

    distance =(request.form['distance'])
    calculator = FareCalculator(start, end, distance)

    plans = calculator.fare_table.index
    result = pd.DataFrame(columns=[], index=plans)
    for plan in plans:
        result.at[plan, 'amount'] = calculator.calc_fare(plan)

    return ('Plan:' + str(result.amount.idxmin()) 
            + ', Amount: ' + str(result.amount.min())
           )

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

2.4. アプリを起動する

ここまででアプリは動くようになりました.ローカルで起動してみます.

terminal
$ pipenv shell
$ python view.py

この状態で,http://localhost:5000/をWEBブラウザで開くと,index.htmlが表示されます.

20180826_tcpopt_index.png

そのままcalculateボタンを押すと,http://localhost:5000/resultにページ遷移し,計算結果

Plan:double, Amount: 5780.0

が表示されます.

また,利用開始時刻,返却予定時刻,距離を変更してボタンを押すと,計算結果も変わります.

ターミナルでCtrl+Cを押せば,サーバが停止します.

3. Herokuにデプロイする

最後にHerokuで動かします.

3.1. ローカルで動かす

gunicornをインストールします.

terminal
$ pipenv install gunicorn

次に,Procfileを作ります.

Procfile
web: gunicorn view:app

この時点で,ディレクトリ構成はこんな感じになっています.

terminal
$ tree -A
.
├── Pipfile
├── Pipfile.lock
├── Procfile
├── model.py
├── templates
│   └── index.html
└── view.py

Herokuを使ってローカルで動かします.

terminal
$ heroku local web

http://localhost:5000/をWEBブラウザで開くと,さっきFlaskで動かしたのと同じように,index.htmlが表示されます.

3.2. Heroku上にデプロイする

まずはHerokuにログインして,アプリをcreateします.名前が重複してなければできるはず.

terminal
$ heroku login
$ heroku create tcpopt

Gitのリポジトリを作ってpushします.

terminal
$ git init
$ git add .
$ git commit -m "first commit"
$ git push heroku master
$ heroku open

これで,作成したWEBアプリ「TCPOPT: Times Car Plus Optimization」が動いているのが確認できます.

(開発中のものなので,止めたり消したりすることがあります.あと当然ながら計算結果の正確性は保証しません.テスト全くしてないし.)

おわりに

これでタイムズカープラスの最安プランがWEBで計算できるようになりました.

ただ,中身は粗だらけなので,これから改良していく予定です.


  1. タイムズカープラスについては公式サイトを参照してください 

4
10
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
4
10