Python + Flaskを使ったWebアプリ作成①(環境構築〜画面追加編)



Pythonのフレームワークのひとつである Flask を使って、ブログのWebアプリケーションを作成してみました。今回の記事では「環境構築からテンプレートを使った画面の作成」までの手順です。


*目次

  • 仮想環境の作成
  • Flaskのインストール
  • Webサーバーの起動
  • 画面の追加
  • テンプレートの作成
  • 共通テンプレートの作成
  • Bootstrapの適用
  • CSSの追加


*参考



*環境

  • MacOS
  • Python 3.6.3
  • Flask 1.0.2


*仮想環境の作成

python3で仮想環境を作成するコマンドは python2とは異なり、envコマンドを使います。
# 作業ディレクトリを作成
$ mkdir Flask_Blog
$ cd Flask_Blog/

# 仮想環境を作成
$ python3 -m venv env
$ source env/bin/activate

# バージョンの確認
(env)$ python -V
Python 3.6.3


*Flaskのインストール

下記コマンドを実行してFlaskをインストールします。
$ pip install flask


*Webサーバーの起動

作業ディレクトリ直下にhome.pyを新規作成します。

<home.py>
from flask import Flask  
app = Flask(__name__)  
  
  
@app.route('/')  
def hello_world():  
    return 'Hello, World!'

ターミナルで下記コマンドを実行します。
(環境変数を設定してから、Webサーバーを起動します。)
$ export FLASK_APP=home.py

# デバッグモードがONになって変更がすぐに反映される
$ export FLASK_ENV=development

$ flask run

 * Serving Flask app "home.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 322-114-333

下記URLにアクセスします。
http://127.0.0.1:5000/
http://localhost:5000/ でも同様のページにとびます)












*画面の追加

他の画面も表示できるよう、home.pyにメソッドを追加します。
Flaskは@app.route()によってルーティングを行い、URLと処理を結びつけているので、今回は新しく@app.route('/about')のデコレーターを付けたメソッドを追加します。
また、pythonファイルを直接実行してWebサーバーを起動させるために、if __name__ == '__main__':を追加します。

<home.py>
from flask import Flask
app = Flask(__name__)  
  
  
@app.route('/')  
@app.route('/home')  
def home():  
    return '<h1>Home Page</h1>'
  
  
@app.route('/about')  
def about():  
    return '<h1>About Page</h1>'  
  
  
if __name__ == '__main__':  
    app.run(debug=True)

下記コマンドを実行してWebサーバーを起動します。
$ python home.py

下記URLにアクセスするとAbout Pageと表示された画面が表示されます。
http://localhost:5000/about



*テンプレートの作成

画面レイアウトを作成するために、templateを作っていきます。
指定URLにアクセスされたら HTMLファイルが開くようにするため、home.pyの各メソッドでrender_template()を返却するよう修正します。render_template()の第1引数にHTMLファイルを指定し、第2引数以降にHTMLファイルに渡す値を指定します。

<home.py>
from flask import Flask, render_template  
app = Flask(__name__)  

# home.htmlに渡すデータ
posts = [  
    {  
        'author': 'Corey Schafer',  
        'title': 'Blog Post 1',  
        'content': 'First post content',  
        'date_posted': 'April 20, 2018'  
    },  
    {  
        'author': 'Jane Doe',  
        'title': 'Blog Post 2',  
        'content': 'Second post content',  
        'date_posted': 'April 21, 2018'  
    }  
]  
  
  
@app.route('/')  
@app.route('/home')  
def home():
 # 引数にpostsを追加
    return render_template('home.html', posts=posts)  
  
  
@app.route('/about')  
def about():  
    return render_template('about.html', title='About')  
  
  
if __name__ == '__main__':  
    app.run(debug=True)

作業ディレクトリ直下にtemplateフォルダを作成し、その中にhome.htmlabout.htmlを作成します。
画面ごとに動的な値を表示させる方法として、Flaskは デフォルトで Jinja2 というテンプレートエンジンを使うことができます。
HTMLファイルに{{ }}を使って変数名を指定することで、Python側で渡した値を表示させることができます。条件式を付けたい場合は{% if .. %}とすることで、表示非表示を分岐させることができます。

<templates/home.html>
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <!-- home.htmlの場合はelseになる -->
    {% if title %}  
        <title>Flask Blog - {{ title }}</title>  
    {% else %}  
        <title>Flask Blog</title>  
    {% endif %}  
</head>  
<body>  
    <!-- 引数で渡されたpostsのデータを表示 -->
    {% for post in posts %}  
        <h1>{{ post.title }}</h1>  
        <p>By {{ post.author }} on {{ post.date_posted }}</p>  
        <p>{{ post.content }}</p>  
    {% endfor %}  
</body>  
</html>

<templates/about.html>
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    <!-- about.htmlの場合はtitleが表示される -->
    {% if title %}  
        <title>Flask Blog - {{ title }}</title>  
    {% else %}  
        <title>Flask Blog</title>  
    {% endif %}  
</head>  
<body>  
    <h1>About Page</h1>  
</body>  
</html>











*共通テンプレートの作成

home.htmlabout.htmlを作成しましたが、タイトルの分岐処理など同じレイアウトがあったかと思います。こういった全画面に共通するようなレイアウトは、ベースとなるHTMLファイルに抜き出して、各画面では変更がある部分のみを実装するようにします。

templateフォルダにlayout.htmlを新規作成します。
{% block content %}{% endblock %}の部分に、各画面の実装内容が差し込まれます。

<templates/layout.html>
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <meta charset="UTF-8">  
    {% if title %}  
    <title>Flask Blog - {{ title }}</title>  
    {% else %}  
    <title>Flask Blog</title>  
    {% endif %}  
</head>  
<body>  
    <div class="container">  
        <!-- ここに各画面ごとのコンテンツが差し込まれる -->
        {% block content %}{% endblock %}  
    </div>  
</body>  
</html>

各画面では、共通化した部分は削除し、{% block content %}{% endblock content %}の中に独自の実装を書くようにします。最初の行で共通化したlayout.htmlextendsすることで、共通化したファイルを読み込めるようになります。

<templates/home.html>
{% extends "layout.html" %}  
{% block content %}  
    {% for post in posts %}  
        <h1>{{ post.title }}</h1>  
        <p>By {{ post.author }} on {{ post.date_posted }}</p>  
        <p>{{ post.content }}</p>  
    {% endfor %}  
{% endblock content %}

<templates/about.html>
{% extends "layout.html" %}  
{% block content %}  
    <h1>About Page</h1>  
{% endblock content %}



*Bootstrapの適用

Bootstrapはレスポンシブデザインに対応したフレームワークで、これを使うことで簡単で綺麗な画面レイアウトにすることができます。
下記サイトのStarter templateという項目のコードをコピーしてHTMLファイルに貼り付けることで、Bootstrapを使うことができるようになります。
https://getbootstrap.com/docs/4.2/getting-started/introduction/

また、navbarを使ったレイアウトにするためにスニペット使って今回の実装に合わせて修正します。

<templates/layout.html>
<!DOCTYPE html>  
<html lang="en">  
<head>  
<!-- ↓ここからサイトのコードを貼り付け -->
    <!-- Required meta tags -->  
  <meta charset="utf-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">  
  
    <!-- Bootstrap CSS -->  
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">  
<!-- ↑ここまで -->
  
    <meta charset="UTF-8">  
    {% if title %}  
    <title>Flask Blog - {{ title }}</title>  
    {% else %}  
    <title>Flask Blog</title>  
    {% endif %}  
</head>  
<body>  
<!-- ↓ここからスニペットのコードを貼り付け(navbar) -->
    <header class="site-header">  
        <nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">  
            <div class="container">  
                <a class="navbar-brand mr-4" href="/">Flask Blog</a>  
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">  
                    <span class="navbar-toggler-icon"></span>  
                </button>  
                <div class="collapse navbar-collapse" id="navbarToggle">  
                    <div class="navbar-nav mr-auto">  
                        <a class="nav-item nav-link" href="/">Home</a>  
                        <a class="nav-item nav-link" href="/about">About</a>  
                    </div>  
                    <!-- Navbar Right Side -->  
  <div class="navbar-nav">  
                        <a class="nav-item nav-link" href="/login">Login</a>  
                        <a class="nav-item nav-link" href="/register">Register</a>  
                    </div>  
                </div>  
            </div>  
        </nav>  
    </header>  
<!-- ↑ここまで -->
<!-- ↓ここからスニペットのコードを貼り付け(main) -->
    <main role="main" class="container">  
        <div class="row">  
            <div class="col-md-8">  
                {% block content %}{% endblock %}  
            </div>  
            <div class="col-md-4">  
                <div class="content-section">  
                    <h3>Our Sidebar</h3>  
                    <p class='text-muted'>You can put any information here you'd like.  
                    <ul class="list-group">  
                        <li class="list-group-item list-group-item-light">Latest Posts</li>  
                        <li class="list-group-item list-group-item-light">Announcements</li>  
                        <li class="list-group-item list-group-item-light">Calendars</li>  
                        <li class="list-group-item list-group-item-light">etc</li>  
                    </ul>  
                    </p>  
                </div>  
            </div>  
        </div>  
    </main>  
<!-- ↑ここまで -->
<!-- ↓ここからサイトのコードを貼り付け -->
    <!-- Optional JavaScript -->  
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>  
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>  
<!-- ↑ここまで -->
</body>  
</html>


*CSSの追加

レイアウトの色やフォントサイズを変えるためにCSSを追加します。
新規にstaticディレクトリを作成し、その中にmain.cssを作成します。

<static/main.css>
body {  
  background: #fafafa;  
  color: #333333;  
  margin-top: 5rem;  
}  
  
h1, h2, h3, h4, h5, h6 {  
  color: #444444;  
}  
  
.bg-steel {  
  background-color: #5f788a;  
}  
  
.site-header .navbar-nav .nav-link {  
  color: #cbd5db;  
}  
  
.site-header .navbar-nav .nav-link:hover {  
  color: #ffffff;  
}  
  
.site-header .navbar-nav .nav-link.active {  
  font-weight: 500;  
}  
  
.content-section {  
  background: #ffffff;  
  padding: 10px 20px;  
  border: 1px solid #dddddd;  
  border-radius: 3px;  
  margin-bottom: 20px;  
}  
  
.article-title {  
  color: #444444;  
}  
  
a.article-title:hover {  
  color: #428bca;  
  text-decoration: none;  
}  
  
.article-content {  
  white-space: pre-line;  
}  
  
.article-img {  
  height: 65px;  
  width: 65px;  
  margin-right: 16px;  
}  
  
.article-metadata {  
  padding-bottom: 1px;  
  margin-bottom: 4px;  
  border-bottom: 1px solid #e3e3e3  
}  
  
.article-metadata a:hover {  
  color: #333;  
  text-decoration: none;  
}  
  
.article-svg {  
  width: 25px;  
  height: 25px;  
  vertical-align: middle;  
}  
  
.account-img {  
  height: 125px;  
  width: 125px;  
  margin-right: 20px;  
  margin-bottom: 16px;  
}  
  
.account-heading {  
  font-size: 2.5rem;  
}

main.cssを読み込めるようにするために、ベースとなるlayout.html<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">を追加します。

<templates/layout.html>
<!DOCTYPE html>  
<html lang="en">  
<head>  
    <!-- Required meta tags -->  
  <meta charset="utf-8">  
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">  
  
    <!-- Bootstrap CSS -->  
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">  
    <!-- main.cssの読込 -->
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">  
  
    <meta charset="UTF-8">  
    {% if title %}  
    <title>Flask Blog - {{ title }}</title>  
    {% else %}  
    <title>Flask Blog</title>  
    {% endif %}  
</head>  
<body>  
    <header class="site-header">  
        <nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">  
            <div class="container">  
                <a class="navbar-brand mr-4" href="/">Flask Blog</a>  
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">  
                    <span class="navbar-toggler-icon"></span>  
                </button>  
                <div class="collapse navbar-collapse" id="navbarToggle">  
                    <div class="navbar-nav mr-auto">  
                        <a class="nav-item nav-link" href="/">Home</a>  
                        <a class="nav-item nav-link" href="/about">About</a>  
                    </div>  
                    <!-- Navbar Right Side -->  
  <div class="navbar-nav">  
                        <a class="nav-item nav-link" href="/login">Login</a>  
                        <a class="nav-item nav-link" href="/register">Register</a>  
                    </div>  
                </div>  
            </div>  
        </nav>  
    </header>  
    <main role="main" class="container">  
        <div class="row">  
            <div class="col-md-8">  
                {% block content %}{% endblock %}  
            </div>  
            <div class="col-md-4">  
                <div class="content-section">  
                    <h3>Our Sidebar</h3>  
                    <p class='text-muted'>You can put any information here you'd like.  
                    <ul class="list-group">  
                        <li class="list-group-item list-group-item-light">Latest Posts</li>  
                        <li class="list-group-item list-group-item-light">Announcements</li>  
                        <li class="list-group-item list-group-item-light">Calendars</li>  
                        <li class="list-group-item list-group-item-light">etc</li>  
                    </ul>  
                    </p>  
                </div>  
            </div>  
        </div>  
    </main>  
    <!-- Optional JavaScript -->  
 <!-- jQuery first, then Popper.js, then Bootstrap JS -->  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>  
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>  
</body>  
</html>

home.htmlにBootstrapのスニペットを追加します。
<templates/home.html>
{% extends "layout.html" %}  
{% block content %}  
    {% for post in posts %}  
<!-- ↓ここからスニペットのコードを貼り付け(article) -->
     <article class="media content-section">  
        <div class="media-body">  
            <div class="article-metadata">  
                <a class="mr-2" href="#">{{ post.author }}</a>  
                <small class="text-muted">{{ post.date_posted }}</small>  
            </div>  
            <h2><a class="article-title" href="#">{{ post.title }}</a></h2>  
            <p class="article-content">{{ post.content }}</p>  
        </div>  
    </article>  
<!-- ↑ここまで -->
{% endfor %}  
{% endblock content %}


ブラウザで画面を再読み込みしてレイアウトを確認します。










*所感

参考にした動画は英語ですが、非常にわかりやすい手順になっていたので大変参考になりました。まだ続きがあるので、引き続き参考にさせていただき理解を深めたいと思います。

Previous
Next Post »

人気の投稿