はじめに
こんにちは。
株式会社m-Labでインターン生をやらしてもらっている@1millionです。
普段は大学院に通ってます。
インターン開始して、3〜4ヶ月ほど経過しました。
この3〜4ヶ月でDjangoチュートリアルを触って、Flaskを使ってアプリを開発をしました。
1ヶ月目・・・ django チュートリアル
2ヶ月目・・・ Flask 学習&開発着手
3ヶ月目・・・ リファクタリング 等
今回、人生初のアプリを記事にします!
暖かい目で見守っていただけると幸いです。
もちろん誤字脱字や、変なこと言ってるなと思ったら遠慮なく指摘してください。
読んで欲しい人
プログラミングを初心者の同志
気持ちの余裕がある人にお勧めします笑
僕のインターン開始時のスペック
◇ 現在、大学院で触覚の表現について研究中
◇ 今年の4月から弊社でインターン開始、週3勤務
◇ プログラミング経歴は他企業が主催の Python の講習を4日間受けたことがあるw
◇ ディレクトリって何??という質問をしてしまうほどに初心者ですw
アプリを作成した背景
1.インターン生の質問力を高めたい
弊社では、インターン生の課題の1つに良い質問ができるようになることがあります。
まず、ここでいう良い質問とは、、、
いい感じの頻度で質問→多すぎず少なすぎず!
いい感じに調べてから質問→考える力を養おう!
良い質問というより、最低限できて欲しいことに近い気がしますね笑
しかし、このいい感じにというのは、曖昧で人や環境によって異なると思います。
どうにか良い質問を可視化できないか。いい感じに笑
そんな時、弊社の代表がこんなqiitaの記事を見つけてくれました。
『新卒からの質問をソシャゲっぽい仕組みにしたら捗った話』
なんと、この記事では良い質問の頻度をいい感じに可視化していました。
そして筆者は日頃からAIアクセラレータ等で弊社がお世話になってるバイトルドットコム♫でお馴染みの、ディップ株式会社の記事!
やるしかないでしょ!
ということで、上記の記事を簡単に説明すると
・ 1回質問すると、1枚消費される質問チケットを新入社員に配布
・ 一定時間でチケット枚数が回復する
・ チケットに保有上限を設定する
機能を設ける、これにより
・ 質問チケットという大義名分があることで安心して質問できる環境
・ 1回の質問の精度向上
・ 新入社員がつまずいているところを解決する先輩のスゲー感
・ 質問チケット使い方を考えるゲームの戦略性
等々が得られたそうです。
弊社でも同様の効果を期待して、質問チケットをslack上で管理するアプリを開発することにしました。
2.ついでにslack上で勤怠を打刻できる機能も欲しい
上記のチケットアプリを作成している最中に質問チケットの回復のために勤怠記録が必要になりました。
勤怠の記録が必要なら、
『各々で記録をとっている出退勤の記録をslack上でできたら楽だし便利だよね』
という意見が出始め、slack上で使える勤怠管理アプリの開発にも着手しました。
本記事で「Django」ではなく「Flask」を使用しているのは、開発に着手した時は機能要件が少なかったという理由です。
質問チケットアプリ
まずは、タイトル前半のインターン生の質問をスタミナ制にするアプリに関して記述します。
機能
・ ユーザーの登録を行う
・ 出勤すると、質問チケットが2つ付与される
・ 質問チケットは1時間に2つ追加される
・ 質問をすると、質問チケットを1つ消費する
・ 退勤時に質問チケットを0にする
各機能はslackのSlash Commandsで登録したコマンドがPOSTされたときに動作するようにしました。
質問チケットの上限枚数を5枚とし、インターン生の質問チケットが1時間ごとに2枚付与されるように設計しています。
使用技術
言語
Python 3
インフラ
GAE
フレームワーク
Flask
テンプレートエンジン
Jinja2
ORM
SQLAlchemy
DB
MySQL 5.7(cloud SQL)
Slack から Flask へのデータの受け渡し
/create
/attendance
Slackに 上記のようにコマンドを入力することにより、あらかじめ設定しておいたエンドポイントにPOSTする様な仕組みが欲しい。
使用するのは slack api の slach commands
https://api.slack.com/custom-integrations/slash-commands
画像の様に
A というコマンドが来たら B というエンドポイント(URL)にPOSTを行う
という設定が可能
POSTデータの例はこんな具合
token=xxxxxxxxxxxxxxxxxxxxxxxx
&team_id=T0001
&team_domain=example
&enterprise_id=E0001
&enterprise_name=Globular%20Construct%20Inc
&channel_id=C2147483705
&channel_name=test
&user_id=U2147483697
&user_name=Steve
&command=/weather
&text=94070
&response_url=https://hooks.slack.com/commands/1234/5678
&trigger_id=11111111111.111111111.1111111111d88f008e0
###開発時に重宝したサービス
Postmanを使って、slackのPOSTデータを自作してテストしました。
Postmanはテストの他にも認証、ドキュメント作成、バージョン管理ができるそうです。
###質問チケットアプリのテーブル設計
カラム名 | 説明 |
---|---|
id | レコード番号 |
username | ユーザーのslackの名前 |
count | 質問チケットの残り枚数 |
attendance | 出勤か否か |
is_intern | インターン生と社員の識別 |
class User(Base):
__tablename__ = 'slack_question'
id = Column(String(100), primary_key=True)
username = Column(String(100), index=True, unique=True)
count = Column(Integer)
attendance = Column(Integer, nullable=False)
is_intern = Column(Integer, nullable=True)
###ユーザー登録
Slash Commandsで登録した任意のコマンド(/create)がPOSTされたときに先述の質問チケットアプリテーブルにslackのuser_nameをユーザーとして登録できるようにしました。その際に、社員の場合は登録したコマンドの後ろに『emp』を含ませてコマンド入力することでインターン生と識別できるようにしました。
・ インターン生としての登録コマンド例 →/create
・ 社員としての登録コマンド例 →/ceate emp
コード:
@app.route('/create', methods=['POST'])
def create():
session = Session()
created = request.form
created_name = created["user_name"]
created_id = created["user_id"]
created_emp = created["text"]
usernames = [name for name, in session.query(User.username)]
session.close()
if created_emp == "emp":
newname = User(id=created_id, username=created_name,
count=2, attendance=False, is_intern=False)
session.add(newname)
session.commit()
session.close()
return created_name + "さんを登録しました!"
elif not created_name in usernames:
newname = User(id=created_id, username=created_name,
count=2, attendance=False, is_intern=True)
session.add(newname)
session.commit()
session.close()
return created_name + "さんを登録しました!"
else:
return "もうメンバーですよ!"
SQL操作はsqlalchemyの基本的なクエリ(参考ページ)を使いました。
###質問チケットの消費、回復
質問チケットの保有上限を5枚として、インターン生の場合は1時間ごとに2枚回復。
質問チケットの消費は、質問を答えてくれた人が質問をした人の質問チケットの消費をコマンド(/q)として登録したコマンドの後ろにslackの名前を打つことで質問チケットが1枚消費されるようにしました。
・ 質問チケットの消費コマンドの例 →/q @millon
質問チケットの消費コード:
@app.route('/question', methods=["POST"])
def question():
session = Session()
posted = request.form
posted_name = posted['text']
posted_name = posted_name.strip("@")
if posted_name[-1:] in " ":
posted_name = posted_name.strip(" ")
usernames = [name for name, in session.query(User.username)]
session.close()
if posted_name in usernames:
filtered = session.query(User).filter(
User.username == posted_name).first()
filtered_count = filtered.count
if 0 < filtered.count < 5:
filtered.count -= 1
session.commit()
session.close()
return "残りの質問回数は" + str(filtered_count) + "回です!"
else:
return '質問回数が不足してます!'
else:
return "出勤を記録してください!"
質問チケットの回復コード:
下記の質問チケットの回復コードが時間毎に動作するように元ホストのエンジニアさん@from_hostにGCEのcronを作っていただきました。
@app.route("/counter")
def add_question():
session = Session()
users = session.query(User).filter(User.is_intern ==
True, User.attendance).all()
for i in users:
if i.count < 4:
i.count += 2
session.commit()
else:
i.count = 5
session.commit()
session.close()
#勤怠管理アプリ
上記の質問チケットアプリを開発したあと、質問チケットアプリの追加機能勤怠管理アプリの開発しました。
##機能
1.出退勤の記録
2.管理画面へのログイン画面
3.全ユーザーの出退勤の表示画面
4.検索機能
5.編集機能
出勤・退勤の記録はslackの社内チャンネルで登録したコマンド(例/att,/fin )を打つことで動くようにしました。
機能の2.以降はFlaskのテンプレートエンジンであるjinja2とCSSフレームワークの
Bootstrap4を使って開発をしました。
##テーブル設計
class WorkTime(Base):
__tablename__ = 'work_time'
id = Column(Integer, primary_key=True)
user_id = Column(String(100), index=True)
username = Column(String(100), index=True)
attendance_time = Column(DateTime(),
default=datetime.now(pytz.timezone('Asia/Tokyo')))
finish_time = Column(DateTime(),
onupdate=datetime.now(pytz.timezone('Asia/Tokyo')))
カラム名 | 説明 |
---|---|
id | レコード番号 |
user_id | ユーザ固有のslackのid |
username | ユーザーの名前 |
attendance_time | 出勤時間 |
finish_time | 退勤時間 |
MySQL内のTimeZoneをUTC(協定世界時)に設定しました。UTCはJST(日本標準時)と基本的には、9時間の時差があるみたいです。しかし、注意しなければいけない点にうるう年や日本にはないサマータイムを考慮する必要がありました。結果として、時間整形に関して大変勉強になりました。
##勤怠管理アプリ開発で使った技術
勤怠管理アプリ作成時に面白かった or 苦労した箇所に関して記述します。
###1.ログイン画面
from flask import session as cook
from flask import flash
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
if request.form["loginname"] == "mlab" and
request.form["password"] == "password":
cook['logged_in'] = True
return show_entries()
else:
flash("ログイン名、パスワードを正しく入力してください")
return render_template("login.html")
Flaskのsessionを用いて、認証機能をつけました。
また、Flaskのflashを使うことでログインが失敗したときにメッセージを送ることができました。
###2.出退勤記録の処理
Pythonの標準ライブラリdatetimeを使って、日時(日付や時間・時刻)の処理をしました。
・ datetime型とstring型を相互に変換するメソッド:`strftime()`と`strptime()`
・ aware(TimeZone情報を持つ)とnaive(TimeZone情報を持たない)の変換:サードパーティのライブラリ`pytz`
・ TimeZoneの変更:`astimezone()`
3.sqlalchemyのSQL操作
SQL操作はsqlalchemyの基本的なクエリ(参考ページ)を使いました。
sqlalchemyのSQL操作の中で難しかったこと
- ある日付(start_date)からある日付(end_date)の間のデータの抽出:betweenの利用
例:
period_date = session.query(Work_time).filter(Work_time.attendance_time.
between(start_date, end_date)).all()
###4.Bootstrap4
・ 大体の機能を実装できいるようにしたけれど、HTMLだけでは味気ないなー
・ サクッと体裁を整えられたらいいなー
こんな気持ちでBootstrap4を導入してみることにしました。
結果、Bootstrapは体裁を整える点で、テンプレートなどがネット上に転がっているのでそれを真似することでざっくり体裁を整えられるので、導入してよかったと思っております。
導入方法
Bootstrapを導入したいHTMLファイルのヘッドにBootstrapの公式HPのBootstrapCDNをコピペしてあげることでできました。
最初、Bootstrapを導入する方法がわからなくて困り果てました笑
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/
css/bootstrap.min.css"integrity="sha384ggOyR0iXCbMQv3Xipma34MD+dH/
1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
GAE&CloudSQL
GAE&CloudSQLに関しては、先輩社員さんに甘えさせていただいちゃいました-。
聞くところによると、『弊社のサービスのインフラはGCPのため、わざわざ他のインフラサービスを利用する事もない。さらに、社内用で、しかもリクエスト回数も少ないため、今回は最小限のコストで運用できる様にGAEのスタンダードを選定した。』だそうです!
設定
公式ドキュメントを参考に、2ファイルを作成
残念ながら python3.7 用のドキュメントは無いが、動作確認できた
https://cloud.google.com/appengine/docs/standard/python/getting-started/python-standard-env
runtime: python37
handlers:
- url: /static
static_dir: static
- url: /.*
redirect_http_response_code: 301
script: auto
以下のファイルは、他のGCPのサービスを使用する際に必要になる。
from google.appengine.ext import vendor
# Add any libraries installed in the "lib" folder.
vendor.add('lib')
##リファクタリング
可読性をあげるために必要なそうです。
今回、リファクタリングに多くの時間を費やしました。
というのも、最初に出来上がったものは、HTMLファイルを除いた全ての機能を1つのファイルに実装していました。加えて、関数化すらしていませんでした。
結果、長い、長い巻物みたいなコードが出来上がってしまいました。可読性はもちろん皆無です。
どうにか、可読性を高めるために、下記のことを行いました。
1.質問チケットアプリと勤怠管理アプリにファイルを分割
2.変数名の改善
3.同じ動作をする箇所を抽出して関数化
4.冗長なコードを簡潔なコードにする
上記4つをした結果、コードがみるみる簡潔になっていくことに感動しました笑
最終的には、コードの長さが半分くらいになりました!
リファクタリングをする上で、基礎、ファイルの構成、わかりやすい変数名の命名の大切さを思い知りました。
まとめ
Flaskの選定には、小さなアプリを作る上でスピーディーな開発ができるそうなので、開発環境として採用しました。開発初期、質問チケットをスタミナ制にする機能だけの開発予定だったので、Djangoと比較してFlaskは、ファイルを行き来する回数がものすごく少なくなって、『お手頃だな』と思っていました。しかし、勤怠管理機能を追加することになり割と大きなアプリになってしまいました。アプリの機能が増えて行くに伴い、Djangoのファイル構成のありがたみを感じるようになりました。結果、Django,Flask双方の良し悪しを感じることができてよかったと思ってます。
総じて、『楽しくアプリ開発をできた』の一言に尽きると思います!
この素敵な環境に日々、感謝感激をしている次第です🙇♂️
早く、恩返しをできるようになりたいですね!
次は、面白い成果物を作れたらいいなーなんて思ってます!
最近、だんだんと機能を増やしてslackのincoming webhookを使って質問コマンドが打刻された時にチャンネルメンバーに通知を送れるようにしました。
#Git
https://github.com/yoshiyasugimoto/qa_caounter/tree/master