A to Bな変換系WebアプリをFlaskで作ろう!ゼロから…
最初に
Flaskを理解するために奮闘した記事です。
ここ間違ってるよ、こう解釈すると良いよなどのアドバイスがあればコメントいただけると無茶苦茶嬉しいです。
出来上がったものがこちらになります。
では、始めます。
概要
- A to B の形で文字列を変換する系のWebアプリを制作する
- NEUTRINOで作業するときに文字にスペースを入れる作業がめんどくさいので、これを利用してめんどくさくなくす
第一部 「Flaskを準備する」
使用する言語は僕がそれしか使えないのでPythonです。
Flaskというモジュールを使用しますが、実はこれが初体験です。
1から手順を追って説明していきましょう。
pipでFlaskをインストール
今回の開発にはpipenv
を使用ましたが、本記事はpip
で説明します。
以下コマンドを実行してFlaskを導入します。
pip install Flask
ディレクトリを準備する
-
project
のディレクトリにapp
,models
フォルダを作成します。 -
models
フォルダ内に__init__.py
-
app
フォルダ内にtemplates
,static
フォルダを作成します。 -
app
フォルダ内にapp.py
を作成します。 -
project
ディレクトリにrun.py
を作成します。
気がつくとこうなってると思います。
project
├app/
│ ├templates/
│ ├static/
│ └app.py
├models/
│ └__init__.py
└run.py
htmlを用意する
app/templates
フォルダにindex.html
を用意します。
だいたい中身はこんな感じとなっています。
これが出力されるページのモトとなります。
<!DOCTYPE html>
<html>
<head>
<title>Musescore流し込み用文字変換</title>
</head>
<body>
<h1>ここはindex.htmlだよ!</h1>
</body>
</html>
Pythonファイルを用意する
先程作った〜.py
の中身を組み立てていきます。
run.py
サーバーを立ち上げる際に実行します。
単純にappを呼び出してrunする、という書き方で。
from app.app import app
if __name__ == "__main__":
app.run()
app.py
Webアプリの根幹となる部分です。
とりあえずHello World.
を表示させときましょう。
from flask import Flask, render_template, request
# Flaskオブジェクトの生成
app = Flask(__name__)
@app.route('/')
def index():
# 単純に文字列を表示するだけ
return 'hello world.'
# 〜/index にアクセスがあった場合、index.htmlを描写する
@app.route('/index')
def post():
return render_template('index.html')
中身の解説については後ほど行います。
とりあえずこれで準備が整いました。
第二部 「とりあえず実行してみよう」
最小構成が出来上がりました。
ではこの構成で一度サーバーを立ち上げてみましょう。
立ち上げ方は、flaskが実行できる環境でrun.py
を実行する、です。
python run.py
実行するとターミナルにいろいろ文字が出てきますが、注視する点はこれです。
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
表示されているURLを開くと、このような画面が出てくるはずです。
この画面とPythonコードを見比べてみましょう。
@app.route('/')
def index():
return 'Hello World'
このコードの意味は以下の通りとなります。
-
/
、つまりhttp://127.0.0.1:5000/
にアクセスすると関数を呼び出す。 - 関数では単純に、文字列
Hello World
が返されているだけ。 - この関数の返り値である
Hello World
がページに反映される。
といった感じです、なんとなく理解はできましたでしょうか?
確か、こういったコードも入力しましたよね。
# 〜/index にアクセスがあった場合、index.htmlを描写する
@app.route('/index')
def post():
return render_template('index.html')
このコードの意味は以下の通りとなります。
-
/index
、つまりhttp://127.0.0.1:5000/index
にアクセスすると関数を呼び出す。 - 関数では、render_template('index.html')が返されている。
-
render
…描写する -
template
…テンプレート - つまり、
render_template('index.html')
は、テンプレートとしてindex.html
を描写するといった意味です。 - 即ち、
http://127.0.0.1:5000/index
にアクセスすると、index.html
が表示されます。
といった感じです。
なんとな〜く、仕組みが理解できてきたと思います。
なんとなくで良いのです、僕も詳しい意味はわからないので…
第三部 「ページを作っていこう」
おさらい
今回作りたいのはこんなページです。
このページに必要な要素を組んでいきましょう。
index.htmlを組む
さっきまで触っていたhtmlは普通のhtmlでしたが、ここから少し違ったhtmlの書き方をします。
<!DOCTYPE html>
<html>
<head>
<title>Musescore流し込み用文字変換</title>
</head>
<body>
<!-- form -->
<form action="/" method="POST">
<textarea name="before" rows="4" cols="40">ここにテキストを入れてね</textarea>
<input type="submit" value="変換">
{% if body %}
<textarea name="after" rows="4">{{body}}</textarea>
{% else %}
<textarea name="after" rows="4">ボタンを押すとこっちに出力されるよ</textarea>
{% endif %}
</form>
<!-- form -->
</body>
</html>
見慣れない文字が出てきましたね、順を追って説明しましょう。
-
{% if A %}
,{% endif %}
に挟まれたhtmlは、条件Aを満たさないと表示されない。
これの書き方がちょっとよくわからないんですけども、Webアプリ詳しい方いたら解説いただきたいです…
- Flask側から
body
という要素を受け取ると、{{body}}
にそれが挿入される。
といった感じです、プログラミングかじったことあるならば見慣れたような気がしますね。
では、以下の点に注目しましょう。
Flask側から
body
という要素を受け取ると、{{body}}
にそれが挿入される。
これはどういった感じなのでしょうか?
実際にFlask側、からbody
という要素を渡すapp.py
がこちらになります。
@app.route('/', methods=['post'])
def post():
body = 'hoge'
return render_template('index.html', body=body)
簡単な話ですね、返り値の引数にbody
を用意してあげればいいのです。
逆に、html側
からFlask側
に要素を渡す際はこうなります。
<form action="/" method="POST">
<textarea name="before" rows="4" cols="40">ここにテキストを入れてね</textarea>
<input type="submit" value="変換">
</form>
body = request.form['before']
-
POSTするform
を組み、このフォームを実行するとアクションが起こるページをaction
で指定します。 -
textarea
にname="before"
を設定します。 -
input
にtype="submit"
を設定します。 - この状態でボタンを押すと、Flask側で指定した要素をhtmlから取得した、という形でページが表示されます。
つまり、
これが
こうなります
…おや、なんか手順がすっ飛んでますね。
すっとんだ手順について追っていきましょう。
第四部 「文字列を変換する機構を作る」
そうです、文字列をどのように変換したかがすっ飛んでいます。
自作モジュールはmodelsフォルダへ入れる
自作のモジュールをFlaskで使用する場合、models
フォルダにまとめておきましょう。
フォルダの中身はこんな感じにしておきます。
models/
├__init__.py
└convert.py
__init__.py
を置くことで、modelsからimportできるようにします。
それでは、convert.py
をいじっていきましょう。
正規表現で文字列を変換する
今回は正規表現で攻めていきます。
各々関数にまとめたかったのですが、正直行数節約にもならなそうだったので一つにまとめちゃいました。
手順を説明するとこういった感じです。
-
(.)
で文字列を検索し、\1
で文字列に変更を加えないまま、スペースを後方に配置する。 -
\s(ゃ|ゅ|ょ|〜)
で拗音
となっている文字列を検索し、余分なスペースを削除する。
これについてもっとスマートな方法がある気がするのですが、知ってたら教えてください。
-
\s{2,}
で2つ以上連続しているスペースを検索し、余分なスペースを削除する。
import re
def convert(arg: str):
pattern_before = r'(.)'
pattern_after = r'\1 '
s = arg
s = re.sub(pattern_before, pattern_after, s)
pattern_before = r'\s(ゃ|ゅ|ょ|ぁ|ぃ|ぅ|ぇ|ぉ|ャ|ュ|ョ|ァ|ィ|ゥ|ェ|ォ)'
pattern_after = r'\1'
s = re.sub(pattern_before, pattern_after, s)
pattern_before = r'\s{2,}'
pattern_after = r' '
s = re.sub(pattern_before, pattern_after, s)
return s
convert.pyをapp.pyで呼び出す
それでは、app.py
からconvert.py
を呼び出して使用します。
from models import convert
@app.route('/', methods=['post'])
def post():
body = request.form['before']
body = convert.convert(body)
return render_template('index.html', body=body)
-
from models import convert
でconvertをimportする。 - フォームのボタンを押すと
before
要素を取得する -
before
に対してconvert
関数を実行する。 - 変換した文字列
body
を描写する
といった感じです。
つまり、これでようやく
これが
こうなる
とちゃんと説明できたわけです。
第五部 「機能ができたら次は外見」
このままだと見た目がちょっとダサいので、bootstrapを使って見た目を整えます。
app
フォルダ内にstatic
というフォルダがあります。
ここに、CSSやJSなどを入れていくわけです。
今回、CSSのみを使用するのでJSフォルダは用意しません。
app/
└static/
└css
└bootstrap.min.css
といった構成でいきましょう。
bootstrapをいじるわけですから…index.html
ですよね。
<!DOCTYPE html>
<html>
<head>
<title>Musescore流し込み用文字変換</title>
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="jumbotron text-center">
<h1 class="display-4">Lyrics -> Musescore</h1>
<p class="lead">for NEUTRINO</p>
<hr class="my-4">
<p>NEUTRINO用にMusescoreを用意する際に流し込む歌詞にスペースを自動で追加するやつです。</p>
<p>あいうえおきゃきゅきょ→あ い う え お きゃ きゅ きょ </p>
</div>
<!-- form -->
<form action="/" method="POST">
<div class="container-fluid">
<div class="row justify-content-around form-group">
<textarea class="col-11 col-md-4 form-control" name="before" rows="4" cols="40">ここにテキストを入れてね</textarea>
<div class="col-md-1 my-md-auto">
<input type="submit" class="btn btn-primary btn-lg" value="変換">
</div>
{% if body %}
<textarea class="col-11 col-md-4 form-control" name="after" rows="4">{{body}}</textarea>
{% else %}
<textarea class="col-11 col-md-4 form-control" name="after" rows="4">ボタンを押すとこっちに出力されるよ</textarea>
{% endif %}
</div>
</div>
</form>o
<!-- form -->
<footer class="text-muted ">
<div class="container">
<p class="text-center">
連絡はこちらへ:<a href="https://twitter.com/_Sotono">@_Sotono</a>
</p>
</div>
</footer>
</body>
</html>
これでようやく見た目がこうなりました。
長い旅路だった…
あとはこれを公開するなり煮るなり焼くなりして自由に取り扱うことができます。
番外編 「詰まったところと解決策」
-
Q.テストで
app.py
を実行してるのに、No module named app
とか表示されて動かない… -
A.
run.py
を実行しましょう、全部解決します。 -
Q.なんかよくわからない長文エラーを吐き出しています…
jinja2.exceptions.TemplateNotFound
とか末端に書いてある… -
A.
index.html
とapp.py
がうまく噛み合ってないかもしれないです、コードをちゃんと確認しましょう。存在しない要素呼び出してませんか?
おわりに
長い文章になりました。
今回これを作ったのは、趣味で作曲をしているときに「あーめんどくせ!!!」って思ったからです。
NEUTRINOはすごい技術だと思うので今後の発展に期待しています。
おわり。
Discussion