4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PythonAdvent Calendar 2022

Day 23

Flask+SQLiteでSQL Alchemy構築+CRUD手順

Last updated at Posted at 2022-12-22

前提

DB 新規作成手順

①プロジェクト直下にapp.pyを作成
②app.pyに以下を記載

app.py
from datetime import datetime
# pythonでtimezone扱う場合のインポート
import pytz

from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy

# アプリケーションをインスタンス化
app = Flask(__name__)

# "sqlite:///blog.db"の部分を "sqlite:///hogehoge.db"と任意のDB名に変更
# 下記はinstanceというfolderが作られて、更にその中にblogというDBが作成されるという意味
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db"

# インスタンス化したアプリケーションをSQLAlchemyに渡す
db = SQLAlchemy(app)

# modelを作成(別途modelsフォルダに切り出してもよい)
class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(30), nullable=False)
    body = db.Column(db.String(300), nullable=False)
    # 世界協定時から日本の時刻に変更してdefault日時を登録するためにpytz.timezone使用する
    created_at = db.Column(db.DateTime, nullable=False, default=datetime.now(pytz.timezone("Asia/Tokyo")))

# 一覧画面へのルーティング
@app.route("/")
def index():
    return render_template("index.html")

③環境変数にFlskコマンドを使用するためのパスを通す

# Linuxの場合

$ export FLASK_APP=app    # app.pyを作成している場合はapp
$ export FLASK_ENV=development    # debug_modeがonになり、編集したファイルがサーバーを再起動せずに反映されるようになる
$ flask run
 * Running on http://127.0.0.1:5000/
# Win(CMD)の場合

> set FLASK_APP=app   # app.pyを作成している場合はapp
> set FLASK_ENV=development    # debug_modeがonになり、編集したファイルがサーバーを再起動せずに反映されるようになる
> flask run
 * Running on http://127.0.0.1:5000/

※この手順がうまくいくと flask ~ のコマンドが使用可能になる(例)flask shell など

④app.pyがおいてある階層にて下記実行

$ flask shell
>>> db.create_all()

または

>>> from app import db
>>> db.create_all()

※ db.create_all()にて[RuntimeError: Working outside of application context.]エラーが吐かれた際は下記参照

https://stackoverflow.com/questions/73961938/flask-sqlalchemy-db-create-all-raises-runtimeerror-working-outside-of-applicat

⑤カレントディレクトリにinstanceフォルダが作成され、その中にblog.dbが作成されていればDB作成完了
※sqliteの中身の確認方法については前提の部分を参照のこと

SQL Alchemyを使用した新規登録(CREATE)

①app.pyに createルーティングを追加

app.py
from flask import Flask, render_template, request, redirect
.
.  省略
.

# /create のルーティングに GETかPOSTメソッドで来たら下記関数に入る
@app.route("/create", methods=["GET", "POST"])
def create():
    # POSTメソッドの場合の処理
    if request.method == "POST":
        # formから渡された各データを取得
        title = request.form.get("title")
        body = request.form.get("body")

        # 先で作成したmodelのクラス内にフォームから持ってきた各データをそれぞれ設定する
        post = Post(title=title, body=body)
        
        # セッションを設定したデータで追加してコミットを行う
        db.session.add(post)
        db.session.commit()
        return redirect("/")

    # GETメソッドの場合の処理
    else:
        return render_template("create.html")

app.pyに渡されるフォーム実装(参考)

templates/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 %}

SQL Alchemyを使用した参照(READ)

一覧画面(/index)のルーティングを追加

app.py
@app.route("/", methods=["GET", "POST"])
def index():
    # "/"にGETメソッドで渡された場合の処理
    if request.method == "GET":
        # Postクラスからの全データをリスト形式で取得するORM
        posts = Post.query.all()
        # 取得したpostsという全データをindex.htmlに渡す
        return render_template("index.html", posts=posts)

③一覧画面のhtmlをDBからのデータを表示するように追加

templates/index.html
{% extends "base.html" %}
{% block content %}
<h1>ブログアプリケーション</h1>
<a href="/create" role="button">新規作成画面</a>
<!-- app.pyから渡ってきたpostsをfor文で回し、一つずつ中のデータを取得・設定する -->
{% for post in posts %}
<article>
    <h2>{{ post.title }}</h2>
    <p>{{ post.created_at }}</p>
    <p>{{ post.body }}</p>
</article>
{% endfor %}
{% endblock %}

④flask run で確認すると記事を新規登録後に一覧画面で反映されていることが確認できる。

SQL Alchemyを使用した更新(UPDATE)

①一覧画面の各記事内に編集のリンク先を追加する

templates/index.html
{% extends "base.html" %}
{% block content %}
<h1>ブログアプリケーション</h1>
<a href="/create" role="button">新規作成画面</a>
{% for post in posts %}
<article>
    <h2>{{ post.title }}</h2>
    <!-- それぞれの記事内でidのルーティングをさせるように追記 -->
    <a href="/{{ post.id }}/update" role="button">編集</a>
    <p>{{ post.created_at }}</p>
    <p>{{ post.body }}</p>
</article>
{% endfor %}
{% endblock %}

②app.pyに updateルーティングを追加

app.py
# 文字列は受け付けずint型のみ受け付けるようにする
@app.route("/<int:id>/update", methods=["GET", "POST"])
def update(id):
    # idを指定してデータを取得するORM
    post = Post.query.get(id)
    # GETの場合の処理
    if request.method == "GET":
        return render_template("update.html", post=post)
    # POSTの場合の処理(後程追加)
    else:
        return redirect("/")

③app.pyからレンダリングされるupdate.htmlを作成

templates/update.html
{% extends "base.html" %}
{% block content %}
<h1>編集画面</h1>
<form method="POST">
    <label for="title">タイトル</label>
    <!-- ページを開いたときにデフォルトで既に登録されている値を表示する為 -->
    <input type="text" name="title" value={{ post.title }}>

    <label for="body">内容</label>
    <!-- ページを開いたときにデフォルトで既に登録されている値を表示する為 -->
    <input type="text" name = "body" value={{ post.body }}>

    <input type="submit" value="更新">
</form>
{% endblock %}

④先ほどのapp.pyの/updateルーティングにPOST時の処理を追加する

app.py
# 文字列は受け付けずint型のみ受け付けるようにする
@app.route("/<int:id>/update", methods=["GET", "POST"])
def update(id):
    # idを指定してデータを取得するORM
    post = Post.query.get(id)
    # GETの場合の処理
    if request.method == "GET":
        return render_template("update.html", post=post)
    # POSTの場合の処理
    else:
        # インスタンス化しているデータにformから受け取った各データを上書き
        post.title = request.form.get("title")
        post.body = request.form.get("body")

        # updateの場合はdb.session.add()は不要
        db.session.commit()
        return redirect("/")

⑤flask run で確認すると記事毎の編集ボタンクリック→更新後に一覧画面で更新が反映されていることが確認できる。

SQL Alchemyを使用した削除(DELETE)

①一覧画面の各記事内に削除のリンク先を追加する

templates/index.html
{% extends "base.html" %}
{% block content %}
<h1>ブログアプリケーション</h1>
<a href="/create" role="button">新規作成画面</a>
{% for post in posts %}
<article>
    <h2>{{ post.title }}</h2>
    <a href="/{{post.id}}/update" role="button">編集</a>
    <a href="/{{post.id}}/delete" role="button">削除</a>
    <p>{{ post.created_at }}</p>
    <p>{{ post.body }}</p>
</article>
{% endfor %}
{% endblock %}

②app.pyにルーティングを追加

app.py
# 文字列は受け付けずint型のみ受け付けるようにする
@app.route("/<int:id>/delete", methods=["GET"])
def delete(id):
    # idを基にデータを取得するORM
    post = Post.query.get(id)
    # idを指定して取ってきた投稿をdeleteしてcommit
    db.session.delete(post)
    db.session.commit()
    # htmlを返すわけではないのでリダイレクトでOK
    return redirect("/")

③flask run で確認すると削除ボタンクリック時に記事が削除され一覧画面が再表示されることが確認できる。

最終ソースコード

app.py
from datetime import datetime
import pytz

from flask import Flask, render_template, request, redirect
from flask_sqlalchemy import SQLAlchemy

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(30), 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("/", methods=["GET", "POST"])
def index():
    if request.method == "GET":
        posts = Post.query.all()
        return render_template("index.html", posts=posts)

@app.route("/create", methods=["GET", "POST"])
def create():
    if request.method == "POST":
        title = request.form.get("title")
        body = request.form.get("body")

        post = Post(title=title, body=body)
        
        db.session.add(post)
        db.session.commit()
        return redirect("/")

    else:
        return render_template("create.html")

@app.route("/<int:id>/update", methods=["GET", "POST"])
def update(id):
    post = Post.query.get(id)
    if request.method == "GET":
        return render_template("update.html", post=post)
    else:
        post.title = request.form.get("title")
        post.body = request.form.get("body")

        db.session.commit()
        return redirect("/")

@app.route("/<int:id>/delete", methods=["GET"])
def delete(id):
    post = Post.query.get(id)
    db.session.delete(post)
    db.session.commit()
    return redirect("/")
templates/base.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>Document</title>
</head>
<body>
    {% block content %}
    {% endblock %}
</body>
</html>
templates/index.html
{% extends "base.html" %}
{% block content %}
<h1>ブログアプリケーション</h1>
<a href="/create" role="button">新規作成画面</a>
{% for post in posts %}
<article>
    <h2>{{ post.title }}</h2>
    <a href="/{{post.id}}/update" role="button">編集</a>
    <a href="/{{post.id}}/delete" role="button">削除</a>
    <p>{{ post.created_at }}</p>
    <p>{{ post.body }}</p>
</article>
{% endfor %}
{% endblock %}
templates/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 %}
templates/update.html
{% extends "base.html" %}
{% block content %}
<h1>編集画面</h1>
<form method="POST">
    <label for="title">タイトル</label>
    <input type="text" name="title" value={{ post.title }}>

    <label for="body">内容</label>
    <input type="text" name = "body" value={{ post.body }}>

    <input type="submit" value="更新">
</form>
{% endblock %}

参考

今西さんのflask解説のyoutube(超わかりやすい)
flask-alchemyの公式document
flaskの公式document

4
6
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
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?