【PyBegi29 30分ハンズオン】Flaskで認証なしの簡易掲示板を爆速で作ってみる

# はじめに

この記事はPython Beginners沖縄というコミュニティの初心者向けPython勉強会のために作った資料ですが、せっかく作った資料をpcに眠らせるのはもったいないと思ったため一部修正・加筆したものを公開します。
ある程度初心者向けの内容を想定して、出来る限り専門用語を省きざっくりとした説明を書いていますが、もしわかりにくい箇所・もう少し別の言い方したほうがいいかもしれないどあればアドバイスいただけると助かります。


# 参考文献

大変参考にさせていだたきました。ありがとうございます。

PythonのFlaskでデータベースを利用する方法(Flask-SQLAlchemy)
https://note.com/junyaaa/n/n9eab953c73c9#zdJy9

Python+Flask環境をDockerで構築する
https://qiita.com/kai_kou/items/e78b546b9820c7d8f1f9

# そもそもPythonとは?
プログラミング言語の1つ。文法がシンプルで機械学習やデータ分析・webアプリ開発など幅広い分野で使える。

# Flaskとは?

Python用の軽量なWebアプリケーションフレームワーク。
Djangoなどの他のフレームワークと比べて、学習コストが低いというメリットもある。
ちなみに、フレームワークとは開発のための枠組みを作ってくれるもの。

# flask_sqlalchemyとは？

Flaskから**データベース**を利用する際に、**SQL文**を書かずにPythonコード上からデータベースを操作できるようになるライブラリ。

## データベースとは?
データを1つの場所に集めて、必要な時に必要なデータを集められるようにしたもの。
例えば、SNSでは、アカウント情報(メアド、パスワード、ユーザ名など)や投稿した内容などをデータベースに記録して管理している。

## SQLとは？
データベースを操作するための言語。データベースにデータを挿入したり、検索するといったことが可能。


# 準備

## pythonのインストール

環境構築の参考になるサイト

https://qiita.com/oyan29/items/832f0616589c9f0fcf9d


## Flaskのインストール

```
$ pip install flask
```

## flask_sqlalchemyのインストール

```
$ pip install flask_sqlalchemy
```

## 環境構築めんどくさいかつ初心者ではない方向け(Docker)

Flaskの環境構築をDockerで構築できるようにサンプルと同梱してあるので、ローカルで環境構築したくない方、環境構築めんどくさい方などはそちらをお使いください。

# 今回作るもの

こんな感じの某掲示板に若干寄せた感じの掲示板。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508580/8d5dd903-bb28-4f71-8033-24a5f53744c4.png)


名前とメッセージを打って送信するだけのとてもシンプルな掲示板。

## 完成形のリポジトリ

https://github.com/helloworld753315/PyBegi29_FLask

# フォルダ構成

この通りにファイルを作成・配置する。画像は写真ACというサイトからお借りしたものを使う。

https://www.photo-ac.com/main/detail/3537893?title=%E6%B5%B7_%E6%B3%A2%E7%B4%8B%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3_20&searchId=3728906341

app/
　├ static/
　│　└ css/
　│　　　└ style.css
　│　└ images/ 
　│　　　└ backgroud.jpg
　├ templates/
　│　└ index.html
　└ app.py

# 完成したコード

だらだら説明しても、ねむくなるので完成形(｀・ω・´)

```py:app.py
from flask_sqlalchemy import SQLAlchemy
from flask import Flask, render_template, request, redirect, url_for
import datetime

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.chat'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

class Chat(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_name = db.Column(db.String(16), nullable=False)
    message = db.Column(db.String(128), nullable=False)
    timestamp = db.Column(db.String(28), nullable=False)

@app.route('/')
def index():
    data = Chat.query.all()
    return render_template('index.html',data=data)

#以下追加↓	
@app.route('/add', methods=['POST'])
def add():
    user_name = request.form['user_name']
    message = request.form['message']
    dt_now = datetime.datetime.now()
    timestamp = dt_now.strftime('%Y/%m/%d %H:%M:%S')
    if not(user_name):
        user_name = "名無しの人"
    new_message = Chat(message=message,user_name=user_name,timestamp=timestamp)
    db.session.add(new_message)
    db.session.commit()
    return redirect(url_for('index'))

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

```

```html:index.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat.html</title>
    <link rel="stylesheet" href="{{ url_for('static',filename='css/style.css') }}">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Hachi+Maru+Pop&display=swap" rel="stylesheet"> 
  </head>
  <body>
    <h1>29ちゃっと</h1>
    <form action="/add" method="POST" class="form">
      <input type="text" name="user_name" placeholder="名無しさん"/>
      <br>
      <input type="text" name="message" placeholder="input chat!!"/>
      <input type="submit" value="送信"/>
    </form>
    <!--以下追加-->
    <div class="contents">
      <br>
      {% for d in data %}
        <p>{{d.id}}. 名前: <span class="name">{{d.user_name}}</span> {{d.timestamp}}</p>
        <p>　{{d.message}}</p>
      {% endfor %}
      <!---------->
    </div>
  </body>
</html>
```

```css:style.css
body {
  background-image: url("/static/images/background.jpg");
  background-repeat: repeat-y;
}

h1 {
  font-family: 'Hachi Maru Pop', cursive;
  font-size: 40px;
  margin: 20px auto;
}

.form {
  /*background-color: blueviolet;*/
}

.form input {
  height: 25px;
  border-radius: 4px;
  margin: 2px auto;
}

.form input:nth-child(1) {
  width: 200px;
}

.form input:nth-child(3) {
  width: 600px;
}

.form input:nth-child(4) {
  height: 32px;
  width: 80px;
}

.contents {
  background-color: rgb(255, 255, 255);
  border-radius: 10px;
  margin: 20px 15px;
  border: 1px solid #3c3c3c;
  padding: 10px 5px;
  box-shadow: 2px 2px 6px rgb(157, 157, 157);
}

.contents .name {
  color: rgb(31, 91, 181);
}
```

`app = Flask(__name__)` でFlaskのインスタンスを作成。

```py
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.chat'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
```

こちらの行でデータベースの設定をしている。今回は、簡単にサクッと実装したいのでsqliteを使用する。
ちなみに、`app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False`はリソースの節約のための設定なので、特に気にしなくていい。

## テーブルのクラスを定義

```py
class Chat(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_name = db.Column(db.String(16), nullable=False)
    message = db.Column(db.String(128), nullable=False)
    timestamp = db.Column(db.String(28), nullable=False)
```

データベースにどういう値を記録・管理するのかをここに定義する。

今回は以下のようにテーブルを定義する。

1. id
    - 重複した値を区別するためのキー(プライマリーキー)。
2. user_name
    - 投稿者名。
3. message
    - 投稿内容。
4. timestamp
    - 投稿時間。

### 例

| id  | user_name | message      | timestamp           | 
| --- | --------- | ------------ | ------------------- | 
| 1   | 村人1     | 初めての投稿 | 2021/08/10 20:28:59 | 
| 2   | 村人2     | 2番目        | 2021/08/11 10:28:59 | 
| 3   | 村人3     | 3番目        | 2021/08/11 21:28:59 | 


ちなみに、db.chatというデータベースがまだないので作成する。
今回は、リポジトリにinit_db.pyというファイルを用意しているのでそれを実行すれば作成できる。

## ルーティングを作成

```py
@app.route('/')
def index():
    data = Chat.query.all()
    return render_template('index.html',data=data)
```

`/`にアクセスすると、index.htmlを返す、つまり表示させるようにしている。
このとき、データベースのChatモデルのデータを`data = Chat.query.all()`で全て取得し、それをindex.htmlに渡すようにしている。

今回は、`/`で投稿と表示を行い、`/add`でデータベースに追加する処理を書く。

```py
<input type="text" name="user_name" placeholder="名無しさん"/>
```

このように、nameを指定してたinputタグからPOSTされた値を

```py
user_name = request.form['user_name']
```

このように書いて受け取ることができる。

また、それらの値を以下のように書いてデータベースに渡す。

```py
new_message = Chat(message=message,user_name=user_name,timestamp=timestamp)
db.session.add(new_message)
db.session.commit()
```

そして、値を渡した後、index.htmlにリダイレクトするようにしている。

```py
return redirect(url_for('index'))
```

## データを表示する

先程の処理でidnex.htmlにdataが渡されたので今度はそれを表示したい。

```html
{% for d in data %}
        <p>{{d.id}}. 名前: <span class="name">{{d.user_name}}</span> 
        {{d.timestamp}}</p>
        <p>　{{d.message}}</p>
{% endfor %}
```

html内でpythonコードを書きたい場合、{{}}に変数名を入れることで中身を表示できる。
また、for文(繰り返し処理)を書く際は、{% for d in data %}のように書き始めて、{% endfor %}でfor文を閉じる。

今回やっていることを軽く説明すると、dataで渡された値をfor文でループさせてd.id, d.user_nameのように一つずつ値を取り出して表示している。

## 動作確認

以下のコマンドをターミナル(コマンドプロンプト)から入力しよう。

```
$ cd app
$ python app.py
```


localhost:5000にアクセスしてみて、次の画面が表示されたら完成!!

画面が表示されたら、適当にメッセージを送信してきちんと表示されるか確認してみよう。