【python】勉強メモ　速習Flask②


## 1. はじめに
今回は、**SQLite**を使い投稿したデーターをDBに保存するところまで行いたいと思います。


## 2. 全体の流れ
1.事前準備(SQLite3のインストール）
2.共通化
3.投稿内容のDB登録（今回のメイン）
4.DB登録処理（今回のメイン）

### 1. 事前準備　SQLite3のインストール
1.  [SQLite3 ダウンロードページ](https://www.sqlite.org/download.html)へ移動。

2. 「Precompiled Binaries for Windows」まで移動し、
「sqlite-tools-win32-x86-3360000.zip」をダウンロード
![SQL-1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/e031d10e-4d40-6ac9-bd36-b7dab17d1ee6.png)

3. ダウンロードしたら、任意の場所でZIPファイルを解凍。
4.  解凍すると、sqldiff.exe、sqlite3.exe、sqlite3_analyzer.exeのファイルが存在する。
5.  3つのファイルを設置するためのフォルダを作成する。私の場合は、以下にsqliteという
　　フォルダを作成する。
　　`C:\PC-Work\dockerenv\sqlite`
6. sqliteフォルダー内に、**sqldiff.exe**、**sqlite3.exe**、**sqlite3_analyzer.exe**をコピーします。
7. コマンドから実行出来るように、Pathを通す。
8. Pahtが通ったか、以下のコマンドを実行。
```
 $env:path.split(";")
``` 
sqlitへPATHが通ったことを確認
```
C:\PC-Work\dockerenv\sqlite
```
9. sqlalchemyのインストール（仮想環境内で実行すること）
```
pip install flask-sqlalchemy
```
10. slqalchmeyがインストールされたことを確認
```
pip list
```
```
Package          Version
---------------- -------
click            8.1.3
colorama         0.4.6
Flask            2.2.2
Flask-SQLAlchemy 3.0.2
greenlet         2.0.1
itsdangerous     2.1.2
Jinja2           3.1.2
MarkupSafe       2.1.1
pip              22.3
setuptools       65.5.0
SQLAlchemy       1.4.45
Werkzeug         2.2.2
```


前回の続きから
## ディレクトリ構造
```
pydir/
 ┣myproject/ ->このディレクトリで仮想環境有効化のコマンドを実行＆flask runコマンドもここで
   ┣venv/
   ┣instance/
    ┣blog.db ->今回作成
   ┣hello.py
   ┣app.py -> 今回作成
   ┣templates/
    ┣hello.html
    ┣index.html ->今回作成
    ┣base.html ->今回作成
    ┣create.html ->今回作成
```


仮想環境の有効化
```
.venv\Scripts\activate.ps1
```

app.pyファイル作成
```
New-Item app.py
```

app.py記述
```app.py
from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route("/")
def hello():
    return render_template('index.html')
```


templatesフォルダに移動して、index.htmlファイルを作成

```
New-Item index.html
```

index.html記述
```index.html
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
    <h1>ブログアプリケーション</h1>
    <article>
        <h2>記事1</h2>
        <p>作成日時： 2022/12/17 10:00</p>
        <p>記事1の内容</p>
    </article>
    <article>
        <h2>記事2</h2>
        <p>作成日時： 2022/12/17 11:00</p>
        <p>記事2の内容</p>
    </article>
</body>
</html>

```


環境変数設定
```
$env:FLASK_APP = "app"
```

デバッグモード
```
$env:FLASK_ENV = "development"
```
Flaskの起動
```
flask run
```
```
'FLASK_ENV' is deprecated and will not be used in Flask 2.3. Use 'FLASK_DEBUG' instead.'FLASK_ENV' is deprecated and will not be used in Flask 2.3. Use 'FLASK_DEBUG' instead.'FLASK_ENV' is deprecated and will not be used in Flask 2.3. Use 'FLASK_DEBUG' instead. * Serving Flask app 'app'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a 
production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
 * Restarting with stat
'FLASK_ENV' is deprecated and will not be used in Flask 2.3. Use 'FLASK_DEBUG' instead.'FLASK_ENV' is deprecated and will not be used in Flask 2.3. Use 'FLASK_DEBUG' instead.'FLASK_ENV' is deprecated and will not be used in Flask 2.3. Use 'FLASK_DEBUG' instead. * Debugger is active!
 * Debugger PIN: 261-206-630
```

ブラウザで`http://127.0.0.1:5000/`へアクセス
![Flask-1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/7e31c3c0-38f9-2c8f-5ea5-ae884a311e87.png)

ここまでが事前準備。
エラーが出た場合は、前回までのコードの見直しが必要。

---


### 2. 共通化

templatesフォルダ内に、HTMLで共通で利用するためのファイル（base.html）を作成する。
**{% block content %}** **{% endblock %}**　の間に、index.html内の **{% block content %}** から
**{% endblock %}** の内容が埋め込まれる。

base.html記載
```base.html
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
    <!-- ここに、index.htmlの共通化要素（記事1とか記事2などの内容）が埋め込まれる -->
    {% block content %}
    {% endblock %}
</body>
</html>

```

index.htmlが、base.htmlを読み込むための編集
**{% extends "base.html" %}**　で、base.htmlの<head>が読み込まれる。 

```index.html
{% extends "base.html" %} <!--追記 -->
{% block content %}　<!--追記 -->
<h1>ブログアプリケーション</h1>
<article>
    <h2>記事1</h2>
    <p>作成日時： 2022/12/17 10:00</p>
    <p>記事1の内容</p>
</article>
<article>
    <h2>記事2</h2>
    <p>作成日時： 2022/12/17 11:00</p>
    <p>記事2の内容</p>
</article>
{% endblock %}　<!--追記 -->

```
ブラウザで、`127.0.0.1:5000`にアクセスすると、前回と変わらない画面が表示されれば、共通化成功です。



### 3. 投稿内容のDB登録
投稿は、HTTPの**POST**というメソッドを使い、DBに投稿（送信）される。

```app.py
from flask import Flask
from flask import render_template
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import pytz

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db" # 今回作成するDB名
db = SQLAlchemy(app)

# 以下の部分がClassを使ってDBへPOSTするデーターを定義している。
# db.String()は、入力可能な文字数を定義
# nullable=Falseは、nullでPOSTはできないように定義
class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title= db.Column(db.String(50), nullable=False)
    body = db.Column(db.String(300), nullable=False)
    created_at = db.Column(db.DateTime, nullable=False,
                    default=datetime.now(pytz.timezone('Asia/Tokyo')))

@app.route("/")
def hello():
    return render_template('index.html')
```


---

#### DB作成（SQLiteを使用）


仮想環境内で以下のコマンドを実行すること。
**対話モード**で実施（app.pyがあるところで）

```
python
```
```
Python 3.11.0 (main, Oct 24 2022, 18:26:48) [MSC v.1933 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
```

```
>>> from app import app, db
```
```
>>> with app.app_context():
```
```
...     db.create_all()
```

```
...
```

```
>>> exit()
```


上記コマンドは分かりにくいのでキャプチャーも載せておきます。
![キャプチャ-1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/cc5150ac-4bb8-a56e-b8a5-fe06f1717d1d.png)

成功すると、dbが作成されます。
![SQL-2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/d954c94e-0fd7-55ff-4878-fe6d1f339ba3.png)


新規登録画面を作成するため、**create.html**を作成する。
```create.html
{% extends "base.html" %}
{% block content %}
<h1>新規登録</h1>
<form method="POST">
    <label for="title">タイトル</label>
    <input type="text" name="title">
    <label for="body">内容</label>
    <input type="text" name="body">
    <input type="submit" value="新規登録">
</form>
{% endblock %}
```
index.htmlでは、「新規作成画面」へ遷移するためのボタンを作成する。
`<a href="/create" role="button">新規作成画面</a>`を追加。 

```index.html
{% extends "base.html" %}
{% block content %}
<h1>ブログアプリケーション</h1>
<!-- ボタン作成 -->
<a href="/create" role="button">新規作成画面</a> 
<article>
    <h2>記事1</h2>
    <p>作成日時： 2022/12/17 10:00</p>
    <p>記事1の内容</p>
</article>
<article>
    <h2>記事2</h2>
    <p>作成日時： 2022/12/17 11:00</p>
    <p>記事2の内容</p>
</article>
{% endblock %}
```

```app.py
from flask import Flask
from flask import render_template
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import pytz

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db"
db = SQLAlchemy(app)

# 以下の部分がClassを使ってDBへPOSTするデーターを定義している。
# db.String()は、入力可能な文字数を定義
# nullable=Falseは、nullでPOSTはできないように定義
class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title= db.Column(db.String(50), nullable=False)
    body = db.Column(db.String(300), nullable=False)
    created_at = db.Column(db.DateTime, nullable=False,
                    default=datetime.now(pytz.timezone('Asia/Tokyo')))

@app.route("/")
def hello():
    return render_template('index.html')

# 新規登録画面へ遷移するためのルーティング追加
@app.route("/create")
def create():
    return render_template('create.html')
```


base.htmlは、修正なし。
```base.html
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
    <!-- ここに、HTMLの共通化が埋め込まれる -->
    {% block content %}
    {% endblock %}
</body>
</html>
```


`127.0.0.1:5000`にアクセス
ブログのTop画面に「新規作成画面」ボタンが表示される。「新規作成画面」をクリック。
![Flask-2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/5898dfdc-474b-58e0-c304-e3513b97b83d.png)

![Flask-4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/cbe8dd4c-0e50-2c23-0330-b713b66d8969.png)

新規作成画面でタイトルと内容を入力し、新規登録ボタンをクリック
![Flask-5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/dac873d2-9c21-e796-7ab6-90ebe0a8d3bc.png)

エラーとなる
![Flask-6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/acdf6488-4380-d149-eed6-d40c196d080c.png)

app.py側でPOSTを受け付ける仕組みを作っておかなければならない。上記では、POSTで送信しているが、HTTPはデフォルトでGET送信するため。

app.py側で、GETとPOSTの両方を受け付ける様に変更を行う。
```app.py
from flask import Flask
from flask import render_template
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import pytz

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db"
db = SQLAlchemy(app)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title= db.Column(db.String(50), nullable=False)
    body = db.Column(db.String(300), nullable=False)
    created_at = db.Column(db.DateTime, nullable=False,
                    default=datetime.now(pytz.timezone('Asia/Tokyo')))

@app.route("/")
def hello():
    return render_template('index.html')


@app.route("/create", methods=['GET', 'POST']) # ここを追加
def create():
    return render_template('create.html')
```
再度、「新規作成画面」から新規登録を行う。
![Flask-7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/e6a9ee62-e347-3071-97b5-e8dfc97b71d7.png)
以下の画面が表示されれば成功です。無事、POST（登録内容がDBへ送信）される。
ただし、DBには、送信したデーターは登録されていない。
![Flask-8.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/a05f9d56-429c-c272-6c81-5303360bfa91.png)


### 4. DB登録処理
以下からDB登録処理
処理内容としては、POST後に、**db.session.commit()**でDBへコミットし、**return redirect('/')**で
トップページにリダイレクトを行う。
上記を**if文**内に記載している。

```app.py
from flask import Flask
from flask import render_template, request, redirect # 追加
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import pytz

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db"
db = SQLAlchemy(app)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title= db.Column(db.String(50), nullable=False)
    body = db.Column(db.String(300), nullable=False)
    created_at = db.Column(db.DateTime, nullable=False,
                    default=datetime.now(pytz.timezone('Asia/Tokyo')))

@app.route("/")
def hello():
    return render_template('index.html')


@app.route("/create", methods=['GET', 'POST']) # ここを追加
def create():
    # requestがPOSTだったら
    if request.method == 'POST':

        # request.form.get()でフォームで入力した情報を取得（事前に、requestのimport必要）
        title = request.form.get('title')
        body = request.form.get('body')
     
        post = Post(title=title, body=body)

        # DBにpostされてきたデーターを追加（add）する
        db.session.add(post)

        # 変更を保存
        db.session.commit()

        # DB登録後、トップページにリダイレクト
        return redirect('/')

    # POSTではない場合、つまり、GETでアクセスした場合の処理
    else:
        return render_template('create.html')
```

```create.html
{% extends "base.html" %}
{% block content %}
<h1>新規登録</h1>
<form method="Post">
    <label for="title">タイトル</label>
    <input type="text" name="=title">
    <label for="body">内容</label>
    <input type="text" name="=body">
    <input type="submit" value="新規登録">
</form>
{% endblock %}
```
```index.html
{% extends "base.html" %}
{% block content %}
<h1>ブログアプリケーション</h1>
<!-- ボタン作成 -->
<a href="/create" role="button">新規作成画面</a> 
<article>
    <h2>記事1</h2>
    <p>作成日時： 2022/12/17 10:00</p>
    <p>記事1の内容</p>
</article>
<article>
    <h2>記事2</h2>
    <p>作成日時： 2022/12/17 11:00</p>
    <p>記事2の内容</p>
</article>
{% endblock %}
```

ブラウザで、`127.0.0.1:5000`にアクセス。
![Flask-7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/2601383c-f848-283b-04b2-95a07d6b2e55.png)

**新規作成画面**をクリック
![Flask-8.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/be7ad906-98ee-f0a9-e968-4b79bea18826.png)

新規登録画面に遷移するので、「タイトル」「内容」を入力し、**新規登録**ボタンをクリックするとDBへ登録が行われ
最初の画面に戻る。エラーがでなければOK。
登録した内容が表示されない理由は、まだ、登録したデーターを取得して、表示する処理を追加していないため。

![Flask-9.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3014502/d2f80a89-770e-bb44-ffdc-32a1ca9474cc.png)


DB登録の状況確認については、次回。