LoginSignup
1
1

More than 1 year has passed since last update.

🔰HTMLかHTLMかすら知らなかった初心者による、Webアプリとの格闘の痕跡(後半)🔰

Last updated at Posted at 2021-12-05

本記事の構成は以下のようになっています。

0. 開発前の下準備「環境構築」 仮想環境はなくてもなんとかなる。
1. Webアプリの基本的な構造  URLに頼りまくる。
2. とりあえず画像表示してみる もしかして、パスってとんでもなく面倒くさい?
3. ファイルのアップロード   もしかして、パスってとんでもなく面倒くさい?
4. @の外側に変数を連れていく sessionは神だが万能ではなかった、、、
5. Pythonでの抽選処理     例外パターンが意外と多い
6. 関数を外から連れてくる   もしかして、パスってとんでもなく面倒くさい?
7. 結果の出力         csvかexcelか。Pandasのおかげで意外と簡単
8. 見た目を整える       ここで登場HTLM

後半では、4番から取り組んでいきます。前半はこちら→HTMLかHTLMかすら知らなかった初心者による、Webアプリとの格闘の痕跡(前半)

4. @の外側に変数を連れていく

アップロードできたファイルや、入力された文字などの情報は、保存するだけでなく利用したいものです。
例えば先ほどのfilepathなどは、この後ガンガン使っていきたいものの一つですが、実はそのままでは使い勝手が非常に悪いのです。
故郷である@app.route("/uploaded")の中であれば当然、filepathは立派な生きた変数です。しかし、一度別のURLに飛び別の@分岐に行くと、filepathは変数として認めてもらえません。非常に悲しいですね。

そこで使いたいのがflaskのsessionというものです。
これは簡単なデータベースのようなもので、sessionに登録されている変数なら別の分岐にいても使えます。
しかも使い方は滅茶滅茶簡単なので、filepathを例に早速見ていきましょう

参考にした記事→【Flask】Sessionについて

sessionへの登録

始めに、シークレットキーというのを設定します。何でもいいらしいです。

app.secret_key = 'yeahyeah'

一度シークレットキーを設定出来たら、個々の登録はこのように行います。

session["filepath"] = filepath

これで、故郷の@app.route()の外に出られるようになります。

sessionからの読み込み

filepath = session["filepath"]
#または
filepath = session.get("filepath")

故郷の@app.route()の外でも、このように呼び出せば普通の変数としてその土地に馴染めるのです。

今回はsessionを使い、アップロードされたファイルを別の分岐でpandasのDataFrameに加工したいと思います。
(さしあたりexcelファイルを想定して進めていきます。)
DataFrameに加工できた証明のため、保存完了の次の画面で、DataFrameの行数を表示してみましょう。
uploaded.htmlの改変と、gyousuu.htmlの新規作成を行います。

uploaded.html
<!DOCTYPE html>
<html>
    <body>
        保存が完了しました!処理を行います。
        <form action="/gyousuu"><input type="submit" value="行数はこちら"></form>
    </body>
</html>

ページ送りのためのボタンを追加しました。
続いて行数表示用のhtmlファイルですが、とりあえず以下のようにしてみて下さい。

gyousuu.html
<!DOCTYPE html>
<html>
    <body>
        アップロードされたExcelファイルは{% print(session.get("gyousuu")) %}行でした!
    </body>
</html>

驚くほどありがたい仕組みなのですが、実は{%%}で囲うことによって、Pythonが使えます。
ここではprint文の()の中に、sessionから参照してきたgyousuuという変数が入るため、適切な行数が表示される仕組みです。print文は簡易表現が用意されているため、{{変数名}}だけでも表示できます。
sessionに保存されている変数ならば、htmlファイルからでもPythonファイルからでも参照できるわけです。

というわけで、やってみましょう!

main.py
from flask import Flask,render_template,request,session
import pandas as pd

app = Flask(__name__)
app.secret_key = 'yeahyeah'

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

@app.route("/upload", methods=["POST"])
def upload():
    f = request.files["uploaded_file"] 
    filepath = 'app/static/uploaded/' + f.filename
    f.save(filepath)

    #sessionにfilepathを登録
    session["filepath"] = filepath

    return render_template("uploaded.html")

@app.route("/gyousuu")
def gyousuu():

    #sessionからfilepathを参照
    filepath = session.get("filepath")

    #Pandasを用いてエクセルファイルを、扱いやすいdataframeに加工。
    df = pd.read_excel(filepath)
    gyousuu = len(df)

    #sessionにgyousuuを登録
    session["gyousuu"] = gyousuu

    return render_template("gyousuu.html")

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

もちろん、何でもかんでもsessionに入れられるわけではありません。
例えばdataframeなどはsessionに入らないので、別の方法で連れて行かなくてはならないことになります。
ここではその一つとして、「直通」を紹介します。
「次に表示するHTMLファイル」に変数を連れていく場合しか使えないのですが、render_template()の引数として直接指定してしまえば、htmlファイル内でもその変数が使えます。

render_template("gyousuu.html", gyousuu=gyousuu)

ここまでできれば、基本的なアプリが作れるような気分になっていると思います。

5. Pythonでの抽選処理

だいぶ長い道のりでしたが、実はこれは抽選アプリの作成なので、そのメイン部分を作らなくてはなりません。後々main.pyに組み込むために、関数の形で抽選処理をしていきます。
(必要のない人は読み飛ばしてください。)
本アプリは15ずつ余ってしまったTシャツとトートバッグを希望者に捌く抽選アプリなので、希望者の名前と希望の商品のExcel(のパス)を引数にし、当選者のデータフレームを返す関数を作ります。

Chusen.py
import pandas as pd

def choose(pas):

    #データの読み込み
    df = pd.read_excel(pas) 
    df.dropna(how='any')

    #希望ごとにグループ分け
    bag = df[df["希望"]=="トートバッグ"]
    tst = df[df["希望"]=="Tシャツ"]
    kibou_bag = len(bag)
    kibou_tst = len(tst)

    #不本意な方で当選する人数
    huhonni_bag = 0
    huhonni_tst = 0
    if kibou_bag > 15 and kibou_tst < 15:
        h情報onni_tst = min((15-kibou_tst), (kibou_bag-15))#余ったTシャツと、バッグ貰えなかった人数を比較
    elif kibou_tst > 15 and kibou_bag < 15:
        huhonni_bag = min((15-kibou_bag), (kibou_tst-15))#余ったバッグと、Tシャツ貰えなかった人数を比較

    #抽選
    if huhonni_bag == 0 and huhonni_tst == 0:
        bag_get = bag.sample(min([15,kibou_bag]))
        tst_get = tst.sample(min([15,kibou_tst]))
    elif huhonni_bag >0 :
        tst_get = tst.sample(15+huhonni_bag)
        bag_get = pd.concat([bag, tst_get.tail(huhonni_bag)])
        tst_get = tst_get.head(15)
    elif huhonni_tst >0 :
        bag_get = bag.sample(15+huhonni_tst)
        tst_get = pd.concat([tst, bag_get.tail(huhonni_tst)])
        bag_get = bag_get.head(15)

    bag_get.reset_index(inplace=True, drop=True)
    tst_get.reset_index(inplace=True, drop=True)
    kekka = pd.concat([bag_get["名前"], tst_get["名前"]], axis=1)
    kekka.columns=["トートバッグ","Tシャツ"]

    return kekka

記事の長さにくじけかけているので、ここの解説は省かせてください。
とりあえず書き上げることが大切です。

6. 関数を外から連れてくる

さて、話をアプリ開発っぽいところに戻します。
現状使っているPythonのファイルは殆どapp.pyだけですが、中身が複雑になるとそれではやっていけなくなります。実働部分を他の.pyファイルに任せて、なるべくapp.pyを簡略化したいという欲求が出てきたそのとき、我々はまたもや因縁のパスと対峙することになるのです。
面倒だ!という方は、app.py内で関数の定義も行ってしまってください。


簡易版 手順

  1. appフォルダ内に、pythonファイルを新規作成する。
  2. そのファイル内で、実働部分の関数を作る。
  3. 相対パスでそのファイルを指定し、そこから目的の関数をimportしてくる。
  4. 適宜使う。

1.appフォルダ内に、pythonファイルを新規作成する。

 今回はappフォルダ内に、Chusen.pyというファイルを作りました。

2.そのファイル内で、実働部分の関数を作る。

 これはひとつ前の章で既に行っています。
今回は、希望調査のExcelファイルの相対パスをpasという引数にしていますが、引数はあってもなくても全然大丈夫です。

3.相対パスでそのファイルを指定し、そこから目的の関数をimportしてくる。

 結論から言うと、app.pyで次のようなインポートを行えばOKです。

app.py
from app.chusen import choose

この"app.chusen"というのは、実行ファイルであるrun.py目線の相対パスです。
appファイルの中のchusenというファイルから、chuooseという関数を持ってきてね、という指示なわけです。

4.適宜使う。

それでは使っていきましょう。先にapp.pyの方を改変します。
/chusenというURLに、以下の処理を対応させます。
 chusen()の戻り値をkekkaに格納し、抽選結果保存のためExcelで出力しています。
 残念ながらdataframeはsessionに保存できないので、直通方式でいきます。

app.py
@app.route("/chusen")
def chusen():
    filepath = session.get("filepath")
    kekka = choose(filepath)
    session["kekka"] = kekka
    kekka.to_excel("kekka.xlsx")
    return render_template("kekka.html", kekka=kekka)

次に、uploaded.htmlを改変していきます。

uploaded.html
<!DOCTYPE html>
<html>
    <body>
        <title>アップロード成功</title>
        <form action="/chusen"><input type="submit" value="抽選開始"></form>
    </body>
</html>

これで、アップロード完了後、抽選を行うところまで出来ました。
あとはkekka.html上で結果を表示できれば完成です。

ちゃんとやりたい方は、sys.path.append()で検索

上記の方法は、相対パスが分かっている場合の方法です。
しかし実際の開発環境では、この部分だけを実行したい!等の理由で実行ファイル自体が変わってしまうこともあるかと思います。
そうなると、毎回相対パスを書き替える必要が出てきてしまい、それだけミスも増えてしまいそうです。
余計な手間を増やさないためには、相対パスを自動で取得してくれるsys.path.appendとやらが超絶便利なようなので、気になる方は是非調べてみて下さい!

7. 結果の出力

結果の表示は、以下のような感じでやってみて下さい!
複数表示したいので、for文を使っています。
普通のPythonと異なる点は2つです。
まず、{% print(name) %}の代わりに{{name}}という簡易表現を使っているという点。
そして、for文の最後にendforを書いているという点です。

kekka.html
<!DOCTYPE html>
<html>
    <body>
        Tシャツ当選者発表
        <br>
        {% for name in kekka["トートバッグ"] %}
        {{name}}<br>
        {% endfor %}
    </body>
</html>

見栄えにこだわらなければ、意外と簡単に表示できます。

8. 見た目を整える

ここからはHTMLの話になってきます。
最低限必要な改行や中央寄せのあたりは解説をします。
そしてその後、全く理解せずに使っている画面分割、背景の色変えの運よく成功した例も載せておきます。

改行

使うタグは<br>です。殆どのタグが2つのタグで該当箇所を挟むのに対し、改行タグは改行を入れて欲しいところで1つだけ使います。逆に言うと、html上での改行には殆ど意味がないということです。
見やすくするために、ガンガン改行してきましょう。行の最初のスペースなども調整して、見やすいHTMLを心がけたいと思います。

<!DOCTYPE html>
<html>
    <body>初心者の皆さん<br>こんにちゎ</body>
</html>
<!DOCTYPE html>
<html>
    <title>ファイルをアップロードしてね!</title>
    <body>
        <br>
        <form action="/upload" method="POST" enctype="multipart/form-data">
        <input type="file" name="excel" accept=".xls,.xlsx">
        <br>
        <input type="submit" value="送信">
        </form>
        ※ファイル形式はexcelでお願いします。
    </body>
</html>

中央寄せとブロック分割

文字表示は初期設定だと左端に寄ってしまいます。
そのため、中央に寄せたい場合は該当箇所を<center>と</center>で囲う必要があります。
また、ここからここまではひとまとめにしたい、という場合は該当箇所を<div>と</
div>で囲ってブロックにします。正直に言うとブロック分けの存在意義はまだ分からないのですが、大事な気がするので載せておきます。

<!DOCTYPE html>
<html>
    <title>ファイルをアップロードしてね!</title>
    <body>
        <br>
        <center>
        <form action="/upload" method="POST" enctype="multipart/form-data">
            <div>
                <input type="file" name="excel" accept=".xls,.xlsx">
            </div>
            <br>
            <div>
                <input type="submit" value="送信">
            </div>
        </form>
        ※ファイル形式はexcelでお願いします。
        </center>
    </body>
</html>

これを実行すると、このような画面になるはずです。
html_ex.PNG

画面分割と背景の色変え

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>結果発表</title>
  <style>
    #child1 {
      background-color: rgb(243, 181, 162);
    }
    #child2 {
      background-color: rgb(148, 195, 233);
    }

    @media (min-width: 600px) {
      #parent {
        display: flex;
      }
      #child1 {
        flex-grow: 1;
      }
      #child2 {
        flex-grow: 1;
      }
    }
  </style>
</head>
<body>
   <div id="parent">
    <div id="child1">
    <center>
        <table>
            <p><font size="5">トートバッグ当選者</font></p>
            <br>
            {% for name in kekka["トートバッグ"] %}
            {% print(name) %}<br>
            {% endfor %}
        </table>
        <img src="/static/images/bag.jpeg" alt="トートバッグ" width="200" height="200">
        </div>
    </center>
    <div id="child2">
    <center>
        <table>
            <p><font size="5">Tシャツ当選者</font></p>
            <br>
            {% for name in kekka["Tシャツ"] %}
            {{name}}<br>
            {% endfor %}
        </table>
        <img src="\static\images\tst.png" alt="Tシャツ" width="200" height="200">
        </div>
    </center>
   </div>
   <div id="parent">
        <center>
        <form action="/fin">
        <input type="submit" value="次へ">
        </form>
        </center>
    </div>
</body>
</html>

もう何が何を表しているのか分かりませんが、childとparentに分かれているのは分かります。
実行するとこのような画面になります。
html_ex2.PNG

まとめとお礼と感想メモ

ここまで読んで下さり、本当にありがとうございました。
前後編の合計8章で、最低限アプリを作るのに必要な知識には触れられたかと思います。
アプリ制作という高い垣根の、向こうとこちらを結ぶ記事になれていたら幸いです。

本記事の執筆中、大学の授業でPythonとC言語を交互に学ぶという経験をしました。
比較の意図の強い講義だったこともあり、Pythonの忖度スキルに気付かされ、自分がとんでもなく甘やかされているという自覚が芽生えました。dataframeがsessionに保存できない!と嘆いていた自分に教えてあげたいです、型宣言もなくintもdoubleもstrも受け入れてくれるsessionがいかに有難い存在か。

当初は、「自分の味わった苦しみを他の誰かが繰り返さないように」と始めた記事でしたが、書いている内に新たな発見も多数あり、別の種類の苦しみと成長も味わえました。友人にはサクラダファミリアと呼ばれていた記事ですが、拙いながらもなんとか書き上げることが出来てよかったです。

1
1
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
1
1