質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Flask

FlaskはPython用のマイクロフレームワークであり、Werkzeug・Jinja 2・good intentionsをベースにしています。

Heroku

HerokuはHeroku社が開発と運営を行っているPaaSの名称です。RubyやNode.js、Python、そしてJVMベース(Java、Scala、Clojureなど)の複数のプログラミング言語をサポートしている。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

1191閲覧

Flaskで作成したWebアプリがvenv環境では動くが、heroku環境では動作が不安定

baboo

総合スコア8

Flask

FlaskはPython用のマイクロフレームワークであり、Werkzeug・Jinja 2・good intentionsをベースにしています。

Heroku

HerokuはHeroku社が開発と運営を行っているPaaSの名称です。RubyやNode.js、Python、そしてJVMベース(Java、Scala、Clojureなど)の複数のプログラミング言語をサポートしている。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/02/02 22:53

#実現したいこと
自身の学習の一環で、目標の時間にかける時間を可視化するWebアプリを作成しています。
利用者の大まかな流れとしては、

①目標の個数を記入
②目標の内容と重みを記入
③目標にかけられる時間を算出するため、その他の時間(仕事や睡眠、食事など)を入力
④平日22日、休日8日とした上で入力情報から可処分時間を計算
⑤可処分時間を重みに基づいて配分し、結果として表示

になります。

#発生している不具合
現在質問させていただきたい内容として、「実行する状況によって発生するエラーやエラー発生有無が異なる」といったことになります。
具体的な状況として、

【状況1】 ローカルのMacOS上に構築した仮想環境(venv)
【状況2】 herokuを利用して公開したWebアプリを開発に使用しているPCで利用した場合
【状況3】 herokuを利用して公開したWebアプリをスマートフォンで利用した場合

の3つがあるのですが、

【状況1】のときはエラーは発生せずにうまく動作します。

【状況2】のときは主に
エラー2_1: NameError: name 'count_list' is not defined とcount_listが未定義の状態になってしまう
エラー2_2: 目標の個数を3つ以上にすると、目標の記入欄は4つ出現するが、結果は2つもしくは3つしか表示されない
エラー2_3: 目標の個数を3つにすると再度目標の個数入力画面へ遷移する(これは個数が入力されていなかった場合に想定している処理です)

といったエラーが発生します。
エラー2_1に関してはリロードしたり、一度TOP画面へ戻り、再度入力を行うと解消されます。
またこれは時間をあけた場合にのみ観測され、一度再入力し、エラーが発生しなくなるとその直後は何度試してもエラーが発生しません。

エラー2_2、2_3に関しては目標の内容記入欄の個数は入力値と一致しているものの、結果の表示では値が一致しない、といった状況です。
なおこれらの発生頻度は

エラー2_1: テストの初回のみ(時間をあけると再度発生)
エラー2_2: 100%エラーが発生
エラー2_3: 50%程度エラーが発生

【状況3】のときは
エラー3_1: 目標の個数を2つにしたが、結果では3つ表示される。
エラー3_2: 目標の個数を3つにしたが、結果は2つで表示される。
エラー3_3: 目標値記入後に。

2021-02-02T22:25:30.899761+00:00 app[web.1]: File "/app/app.py", line 60, in f_work 2021-02-02T22:25:30.899761+00:00 app[web.1]: impsum += float(importances[i - 1]) 2021-02-02T22:25:30.899769+00:00 app[web.1]: IndexError: list index out of range

といったエラーが発生します。
発生頻度は

エラー3_1: スマホアクセス時初回のみ(ただしエラー3_2と類似している)
エラー3_2: 50%程度エラーが発生
エラー3_3: 50%程度エラーが発生

になります。

#試したこと
herokuへのアップロードはvenvをactivateした状態で、pip freeze > requirements.txtを作成し、
.gitignoreを下記内容で作成。

# Virtualenv # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ .Python [Bb]in [Ii]nclude [Ll]ib [Ll]ib64 [Ll]ocal [Ss]cripts pyvenv.cfg .venv pip-selfcheck.json

herokuへコミットする際はgit add .で下記のディレクトリをコミットしています。

- Procfile - __pycache__ - app.py - myfunc.py - requirement.py - static └styles.css └img  └imgファイル - templates └htmlファイル - venv

#ソースコード

[app.py]

python

1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3 4#モジュールのインポート 5from flask import Flask, render_template, request 6from myfunc import my_remove 7 8#クラスの定義 9class Conditions: 10 def __init__(self, work = 0, sleep = 0, breakfast = 0, lanch = 0, dinner = 0,\ 11 bath = 0, housework = 0, others = 0): 12 self.work = float(work) 13 self.sleep = float(sleep) 14 self.breakfast = float(breakfast) 15 self.lanch = float(lanch) 16 self.dinner = float(dinner) 17 self.bath = float(bath) 18 self.housework = float(housework) 19 self.others = float(others) 20 21 def sum_condition(self): 22 sum_wd = self.work + self.sleep + self.breakfast + self.lanch + self.dinner + self.bath + self.housework + self.others 23 sum_hd = self.sleep + self.breakfast + self.lanch + self.dinner + self.bath + self.housework + self.others 24 return sum_wd, sum_hd 25 26#インスタンス化 27app = Flask(__name__) 28cond = Conditions() 29 30#ルーティング 31@app.route('/') 32def f_top(): 33 return render_template("index.html") 34 35@app.route('/count') 36def f_count(): 37 return render_template('count.html') 38 39@app.route('/goal', methods = ['POST']) 40def f_goal(): 41 if request.form['count']: 42 global count 43 global count_list 44 count = int(request.form['count']) 45 count_list = range(1, count + 1, 1) 46 return render_template("input_goal.html", count = count_list) 47 else: 48 return render_template("count.html") 49 50@app.route('/work', methods = ['POST']) 51def f_work(): 52 if request.form.getlist('goal') and request.form.getlist('importance'): 53 global goals 54 global importances 55 global impsum 56 goals = my_remove(request.form.getlist('goal'), "") 57 importances = my_remove(request.form.getlist('importance'), "") 58 impsum = 0 59 for i in count_list: 60 impsum += float(importances[i - 1]) 61 if len(goals) == count and len(importances) == count: 62 return render_template("input_work.html") 63 else: 64 return render_template('count.html') 65 66@app.route('/sleep', methods = ['POST']) 67def f_sleep(): 68 if request.form['work-hours'] and request.form['work-minutes']: 69 cond.work = int(request.form['work-hours']) * 60 + int(request.form['work-minutes']) 70 return render_template("input_sleep.html") 71 72@app.route('/breakfast', methods = ['POST']) 73def f_breakfast(): 74 if request.form['sleep-hours'] and request.form['sleep-minutes']: 75 cond.sleep = int(request.form['sleep-hours']) * 60 + int(request.form['sleep-minutes']) 76 return render_template("input_breakfast.html") 77 78@app.route('/lanch', methods = ['POST']) 79def f_lanch(): 80 if request.form['breakfast-hours'] and request.form['breakfast-minutes']: 81 cond.breakfast = int(request.form['breakfast-hours']) * 60 + int(request.form['breakfast-minutes']) 82 return render_template("input_lanch.html") 83 84@app.route('/dinner', methods = ['POST']) 85def f_dinner(): 86 if request.form['lanch-hours'] and request.form['lanch-minutes']: 87 cond.lanch = int(request.form['lanch-hours']) * 60 + int(request.form['lanch-minutes']) 88 return render_template("input_dinner.html") 89 90@app.route('/bath', methods = ['POST']) 91def f_bath(): 92 if request.form['dinner-hours'] and request.form['dinner-minutes']: 93 cond.dinner = int(request.form['dinner-hours']) * 60 + int(request.form['dinner-minutes']) 94 return render_template("input_bath.html") 95 96@app.route('/housework', methods = ['POST']) 97def f_housework(): 98 if request.form['bath-hours'] and request.form['bath-minutes']: 99 cond.bath = int(request.form['bath-hours']) * 60 + int(request.form['bath-minutes']) 100 return render_template("input_housework.html") 101 102@app.route('/others', methods = ['POST']) 103def f_others(): 104 if request.form['housework-hours'] and request.form['housework-minutes']: 105 cond.housework = int(request.form['housework-hours']) * 60 + int(request.form['housework-minutes']) 106 return render_template("input_others.html") 107 108@app.route('/time_allocate', methods = ['POST']) 109def f_time_allocate(): 110 if request.form['others-hours'] and request.form['others-minutes']: 111 cond.others = int(request.form['others-hours']) * 60 + int(request.form['others-minutes']) 112 113 #可処分時間の計算 114 freetime_wd = 24 * 60 - cond.sum_condition()[0] 115 freetime_hd = 24 * 60 - cond.sum_condition()[1] 116 117 #時間配分 118 global allocated_times 119 allocated_times = [] 120 time_goal = [] 121 for i in count_list: 122 all_minutes = freetime_wd * (float(importances[i - 1]) / impsum) * 22 + freetime_hd * (float(importances[i - 1]) / impsum) * 8 123 minutes = all_minutes % 60 124 hours = all_minutes // 60 125 time_goal = [hours, minutes, goals[i - 1]] 126 allocated_times.append(time_goal) 127 return render_template("result.html", count = count_list, goals = goals,allocated_times = allocated_times)

htmlファイルは数が多くなっているので、不具合が発生しているcount.htmlとresult.htmlのみ載せます

[count.html]

html

1<!DOCTYPE html> 2<html lang="ja"> 3 <head> 4 <meta charset="utf-8"> 5 <link rel="stylesheet" href="../static/styles.css"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> 7 <title>Time Allocator</title> 8 </head> 9 <body> 10 <main id="count-main"> 11 <h1>目標の数</h1> 12 <div id="count-lead"> 13 <p>あなたがこの1ヶ月で取り組もうと<br> 14 している目標の数を教えてください</p> 15 </div> 16 <div> 17 <form method="POST" action="/goal"> 18 <p>タスクの個数を記入してください</p> 19 <div class="div-input"> 20 <input id="input-count" name="count" type="number" step="1" required> 21 <p>個の目標達成を目指す</p> 22 </div> 23 <div class="div-img"> 24 <img src="../static/img/busy.png" id="img-count"> 25 </div> 26 <div class="div-button"> 27 <button type="submit">つぎへ</button> 28 </div> 29 </form> 30 </div> 31 </main> 32 <footer> 33 </footer> 34 </body> 35</html>

[result.html]

html

1<!DOCTYPE html> 2<html lang="ja"> 3 <head> 4 <meta charset="utf-8"> 5 <link rel="stylesheet" href="../static/styles.css"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> 7 <title>Time Allocator</title> 8 </head> 9 <body> 10 <main id="result-main"> 11 <h1>計算結果</h1> 12 <div id="result-div"> 13 <p>計算結果が出ました<br> 14 それでは結果を見てみましょう</p> 15 </div> 16 <div id="result"> 17 <p class="title-sentence"><b>あなたは1ヶ月の中で</b></p> 18 {% for lists in allocated_times %} 19 <div class="result-detail"> 20 <p>「{{lists[0]}}時間 {{lists[1]}}分」かけて<br>「{{lists[2]}}」</p> 21 </div> 22 {% endfor %} 23 <p>という目標を達成しようとしてます</p> 24 <div class="div-img"> 25 <img src="../static/img/result.png" id="img-others"> 26 </div> 27 </div> 28 <button onclick="location.href='/'">Topへもどる</button> 29 </main> 30 </body> 31</html> 32

#環境
マシン: MacBook Pro(M1)
Python: 3.8.2
Flask: 1.1.2
Jinja2: 2.11.3
Werkzeug: 1.0.1

長くなってしまい恐縮ですが、お知恵お借りできますと幸いです。
何卒よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

長いので詳しく読んでいませんが、エラー2_1の動作と、count_listの定義の仕方などからWebアプリが正しく動作するプログラムがされていないように感じました。

以下を参考にプログラムを作り直してみてください。

  1. Webアプリは性悪説で作ります。
    「性悪説の使い方がおかしい」とかそういう突っ込みはしないでください(笑)
    Webアプリは、悪い人間が使うという前提に設計・実装する必要があります。
    コードをぱっと見た感じだと、/goalcount_listに何か入力し、そのあと/workとか/time_allocatecount_listを使用することを想定しているように見えます。(つまり、正しい手順が存在する)
    Webアプリでは、逆順や同じ場所への連続アクセスされた場合にも正しく動作するように作る必要があります。
    count_listが空の場合(定義されていない場合)は、入力を促すページに遷移して問題が起こらないようにするなどが必要です。

  2. 入力は正しく無い
    [1]の概念から、入力をそのまま使う(request.form['count']をそのまま使う)のはNGです。
    なんか凄く悪いものが入っているかもしれません。
    エスケープしたりして安全な入力に変換してから使う必要があります。

  3. デバッグ環境と本番環境は別
    デバッグ環境は1個だけプロセスが動作し、自分で正しく入力するのでたいてい動作します。
    本番環境は、プロセスが2つ以上動作したり、勝手に再起動したり、正しい順番でアクセスされなかったり、別の悪い人が勝手にアクセスしたりして、デバッグ環境では起こらなかった問題が発生する可能性があります。
    「同時に2以上のアクセスがある」可能性を考慮に入れて設計・実装してください。
    つまり、globalは使わないでください。
    そういうのを処理するために、Webではセッションという概念が用いられます。
    Flaskにもsessionという機能があるのでglobalを置き換えると意図した動作になるかもしれません。
    ※sessionに入れた値は自分で入力した値なので信じたいかもしれませんが、悪いものが入っている可能性があるので信用しないでチェックしてくださいね。

  4. データは変数ではなく記録媒体へ
    プログラムの中身がよく分かっていませんが、もしもデータを保存する必要があるならDBの仕様を検討してください。
    herokuはSQliteが使えなかった気がするのでPostgreSQLとかMySQLとかMariaDBとかを使う必要がありそうですが、できることが格段に増えます。
    DBが絡んで来ると、sessionだけでなくcookieとかユーザの管理も絡んできますが、できることが増えるので設計しやすくなるかもしれません。

番外.
request.form['key']で値が取得できますが、値が取得できない可能性があります。
値が取得できない場合はExceptionになってしまうので、とりあえずNoneを取得するということもできます。

python

1 2value = request.form.get('key', None) 3 4if value is None: 5 return 値を再入力して貰ったり・・・ 6

第2引数Noneは、第1引数のkeyが存在しなかった場合に取得できる値です。
第2引数が指定されなかった場合のデフォルト値はNoneなので指定しなくても同じです。

投稿2021/02/03 06:19

FiroProchainezo

総合スコア2424

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

baboo

2021/02/03 08:51

ご回答いただきありがとうございます! 独学でやっていると上記のようなご指摘を自身の書いたコードに対してもらえることはないのでとても有益でした。 一度大きく書き直してみようと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問