6
9

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.

Flaskを用いたウェブアプリケーション作成①

Last updated at Posted at 2020-08-21

1. Flaskをセット

初めにウェブページをHTML/CSSで作成しました。
※Flaskの説明記事なので元になるHTMLファイル作成の説明はここでは行いません。
次に、PythonのFlaskを用いてルーティング処理を行いURLからアクセス出来るようにしました。

app.py
flask import Flask, render_template

# アプリケーション定義
app = Flask(__name__)

#ルーティング定義
@app.route('/')
def index():
#ルーティングと表示するウェブページの紐づけ
     return render_template('index.html', id="home")

@app.route('/menu')
def menu():
     return render_template('menu.html', id="menu")

@app.route('/news')
def news():
     return render_template('news.html', id="news")

@app.route('/contact')
def contact():
     return render_template('contact.html', id="contact")

# デバック時の処理
if __name__ == '__main__':
    app.debug = True
    app.run()

2. 入力フォームと出力フォーム

入力フォームと出力フォームの処理を実装しました。

app.py
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/')
def index():
     return render_template('index.html', id="home")

@app.route('/menu')
def menu():
     return render_template('menu.html', id="menu")

@app.route('/news')
def news():
     return render_template('news.html', id="news")

@app.route('/contact')
def contact():
     return render_template('contact.html', id="contact")

# フォーム入力処理
@app.route('/review', methods=['GET'])
def review():
     return render_template('review.html', id="review")

# フォーム出力処理
@app.route('/review', methods=['POST'])
def view_board():
# 3つの要素をreview.htmlに出力
     if request.form['username'] and request.form['star'] and request.form['review']:
          return render_template('review.html',  username=request.form['username'], star=request.form['star'], review=request.form['review'])


if __name__ == '__main__':
    app.debug = True
    app.run()
review.html
{% extends "theme.html" %}
{% block title %}Coffee House{% endblock %}
{% block head %}
  {{ super() }}
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
{% endblock %}
{% block content %}
    <div class="form-content wrapper">
        <h2 class="page-title">Review</h2>
        <form method="POST" action="/review">
            <div>
                <label for="name">お名前</label>
         <!-- ユーザーネーム出力先 -->
                <input placeholder="名前" name="username" type="text"/>
            </div>
            <div class="star-rating">
                <div class="star-rating__wrap">
          <!-- スターレーティング出力先 -->
                  <input class="star-rating__input" id="star-rating-5" type="radio" name="star" value="5">
                  <label class="star-rating__ico fa fa-star-o fa-lg" for="star-rating-5" title="5 out of 5 stars"></label>
                  <input class="star-rating__input" id="star-rating-4" type="radio" name="star" value="4">
                  <label class="star-rating__ico fa fa-star-o fa-lg" for="star-rating-4" title="4 out of 5 stars"></label>
                  <input class="star-rating__input" id="star-rating-3" type="radio" name="star" value="3">
                  <label class="star-rating__ico fa fa-star-o fa-lg" for="star-rating-3" title="3 out of 5 stars"></label>
                  <input class="star-rating__input" id="star-rating-2" type="radio" name="star" value="2">
                  <label class="star-rating__ico fa fa-star-o fa-lg" for="star-rating-2" title="2 out of 5 stars"></label>
                  <input class="star-rating__input" id="star-rating-1" type="radio" name="star" value="1">
                  <label class="star-rating__ico fa fa-star-o fa-lg" for="star-rating-1" title="1 out of 5 stars"></label>
                </div>
            </div>
            <div>
                <label for="message">メッセージ</label>
         <!-- レビュー出力先 -->
                <textarea id="message" placeholder="レビュー" name="review"></textarea>
            </div>
            <input type="submit" class="button" value="送信">
        </form>
    </div>
{% endblock %}
{% block content2 %}
    <div class="wrapper board">
    <!-- ユーザーネーム表示 -->
      <p>name : {{username}}</p>

   <!-- スターレーティング表示 -->
    {% if star == '1' %}
      <p>rating : <img src="/static/images/star.png" width="15" height="15"></p>
    {% endif %}
    {% if star == '2' %}
      <p>rating : <img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"></p>
    {% endif %}
    {% if star == '3' %}
    <p>rating : <img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"></p>
    {% endif %}
    {% if star == '4' %}
    <p>rating : <img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"></p>
    {% endif %}
    {% if star == '5' %}
    <p>rating : <img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"></p>
    {% endif %}
    <!-- レビュー表示 -->
      <p>message : {{review}}</p>
    </div>
{% endblock %}
{% block footer %}
    <footer>
        <div class="wrapper">
            <p><small>&copy; Coffee House</small></p>
        </div>
    </footer>
{% endblock %}

上記のテンプレートはjinja2を用いて作成しました。

3. applicationフォルダ構成

様々な機能を処理していく上で、機能ごとにファイルを分けた方が可読性、判読性、アップデートのしやすさなどのメリットがある為、機能ごとにファイル構成を分けて作成することにしました。

3-1. アプリケーション本体ファイル作成

rootディレクトリ内にcafe_siteフォルダを作成し、その中に__init__.pyファイルを作成します

./cafe_site/__init__.py
from flask import Flask

app=Flask(__name__)

import cafe_site.views

3-2. viewsファイル作成

次にcafe_siteフォルダ内にviewsフォルダを作成し、その中にviews.pyファイルとして先ほど作成したウェブページを出力するスクリプトを保存します。

./cafe_site/views.py
from flask import request, redirect, url_for, render_template, flash, session
from cafe_site import app


@app.route('/')
def index():
     return render_template('cafe_site/index.html', id="home")


@app.route('/menu')
def menu():
     return render_template('cafe_site/menu.html', id="menu")


@app.route('/news')
def news():
     return render_template('cafe_site/news.html', id="news")


@app.route('/contact')
def contact():
     return render_template('cafe_site/contact.html', id="contact")


@app.route('/review', methods=['GET'])
def review():
     return render_template('review.html', id="review")


@app.route('/review', methods=['POST'])
def view_board():
     if request.form['username'] and request.form['star'] and request.form['review']:
          return render_template('review.html',  username=request.form['username'], star=request.form['star'], review=request.form['review'])

3-3. 起動ファイル作成

最後に起動ファイルとなるserver.pyをrootディレクトリ直下に作成します。

./server.py
from cafe_site import app

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

これで、とりあえずFlaskアプリケーションを起動する事ができました。

3-4. 設定ファイル作成

設定ファイルとしてconfig.pyファイルを作成しました。

./cafe_site/config.py
DEBUG = True

今回のアプリケーションでは、設定情報としてデバッグモードをONにしています。
※server.pyに記載したdebug=Trueは削除しています。

3-4-1. 設定ファイル有効化

設定ファイルを有効化する為に__init__.pyの中でconfig.pyの内容を設定ファイルとして扱うと宣言します。

./__init__.py
app.config.from_object('cafe_site.config')

4. MTVフレームワーク

ここでは、FlaskをModel, Template, Viewの3つに分けて各機能を処理していきたいと思います。

4-1. ユーザーからのアクセスがあった場合、以下の様に処理します。

1. ユーザーがURLに対してアクセスする

2. アクセスされたURLを読み取り、あらかじめ定義されたURLに紐付いた処理を実行する(View)

3. 処理の中で必要に応じてモデルと呼ばれるオブジェクトを通してデータベースにアクセスする(Model)

4. 処理の最後にユーザーに表示するHTMLなどのテンプレートを返す(Template)

5. Template

cafe_siteフォルダ以下にtemplatesフォルダを置き、その中にユーザーに表示するHTMLファイルを並べていきます。

./cafe_site/templates/theme.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        {% block head %}
        <meta charset="utf-8">
        <title>{% block title %}{% endblock %}</title>
        <meta name="description" content="ワーキングスペースを提供するカフェ">
        <link rel="icon" type="image/png" href="/static/images/favicon.png">
        <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSS -->
        <link rel="stylesheet" href="https://unpkg.com/ress/dist/ress.min.css">
        <link href="https://fonts.googleapis.com/css?family=Philosopher" rel="stylesheet">
        <link href="/static/style.css" rel="stylesheet">
        {% endblock %}
    </head>

    <body>
        <div id="{{ id }}" class="big-bg">
            <header class="page-header wrapper">
                <h1><a href="/"><img class="logo" src="/static/images/logo.svg" alt="Coffee House ホーム"></a></h1>
                <nav>
                    <ul class="main-nav">
                        <li><a href="/news">News</a></li>
                        <li><a href="/menu">Menu</a></li>
                        <li><a href="/contact">Contact</a></li>
                        <li><a href="/review">Review</a></li>
                    </ul>
                </nav>
            </header>
            {% block content %}
            {% endblock %}
        </div>
        {% block content2 %}
        {% endblock %}
        {% block footer %}
        {% endblock %}
    </body>
</html>
./cafe_site/templates/index.html
{% extends "theme.html" %}
{% block title %}Coffee House{% endblock %}
{% block head %}
  {{ super() }}
{% endblock %}
{% block content %}
    <div class="home-content wrapper">
        <h2 class="page-title">We Make Your Relax Space </h2>
        <p>仕事や勉強をするスペースに落ち着いたカフェを使いませんか?リラックスできる空間を提供します。</p>
        <a class="button" href="/menu">メニューを見る</a>
    </div><!-- /.home-content -->
{% endblock %}
./cafe_site/templates/menu.html
{% extends "theme.html" %}
{% block title %}Coffee House{% endblock %}
{% block head %}
  {{ super() }}
{% endblock %}
{% block content %}
    <div class="menu-content wrapper">
        <h2 class="page-title">Menu</h2>
        <p>
            集中力をあげる地中海食を提供するCoffee House。魚介類の食材を利用したメニューが特徴です。
            ブレンドコーヒーには厳選されたモカ豆を使用。地中海食、美味しいコーヒー、落ち着いた空間でより質の高い仕事や勉強が出来るでしょう。
        </p>
    </div><!-- /.menu-content -->
{% endblock %}
{% block content2 %}
    <div class="wrapper grid">
        <div class="item">
            <img src="/static/images/menu1.jpg" alt="">
            <p>コーヒーセット</p>
        </div>
        <div class="item">
            <img src="/static/images/menu2.jpg" alt="">
            <p>カフェラテ</p>
        </div>
        <div class="item">
            <img src="/static/images/menu3.jpg" alt="">
            <p>パンケーキ</p>
        </div>
        <div class="item">
            <img src="/static/images/menu4.jpg" alt="">
            <p>スープ</p>
        </div>
        <div class="item">
            <img src="/static/images/menu5.jpg" alt="">
            <p>サラダ</p>
        </div>
        <div class="item">
            <img src="/static/images/menu6.jpg" alt="">
            <p>牡蠣</p>
            </div>
        <div class="item">
            <img src="/static/images/menu7.jpg" alt="">
            <p>トムヤムクン</p>
        </div>
        <div class="item">
            <img src="/static/images/menu8.jpg" alt="">
            <p>海鮮パスタ</p>
        </div>
        <div class="item">
            <img src="/static/images/menu9.jpg" alt="">
            <p>ラズベリーケーキ</p>
        </div>
    </div><!-- /.grid -->
{% endblock %}
{% block footer %}
    <footer>
        <div class="wrapper">
            <p><small>&copy; Coffee House</small></p>
        </div>
    </footer>
{% endblock %}
./cafe_site/templates/news.html
{% extends "theme.html" %}
{% block title %}Coffee House{% endblock %}
{% block head %}
  {{ super() }}
{% endblock %}
{% block content %}
    <div class="wrapper">
        <h2 class="page-title">News</h2>
    </div><!-- /.wrapper -->
{% endblock %}
{% block content2 %}
    <div class="news-contents wrapper">
        <article>
            <header class="post-info">
                <h2 class="post-title">店内の内装を新しくしました。</h2>
                <p class="post-date">1/1 <span>2020</span></p>
                <p class="post-cat">カテゴリー:お店の紹介</p>
            </header>
            <img src="/static/images/wall.jpg" alt="店内の様子">
            <p>
                店内の内装を少し変えました。
            </p>
            <p>
                前よりも爽やかな空間になっています。
            </p>
            <p>地中海食とブレンドコーヒを提供するWCB CAFE。</p>
        </article>

        <aside>
            <h3 class="sub-title">カテゴリー</h3>
            <ul class="sub-menu">
                <li><a href="#">お店の紹介</a></li>
                <li><a href="#">期間限定メニュー</a></li>
                <li><a href="#">イベント</a></li>
                <li><a href="#">お客様との会話</a></li>
            </ul>

            <h3 class="sub-title">このお店について</h3>
            <p>
                仕事や勉強の場にリラックスした空間を使いませんか?
                私たちは、それを提供します。
            </p>
        </aside>
    </div><!-- /.news-contents -->
{% endblock %}
{% block footer %}
    <footer>
        <div class="wrapper">
            <p><small>&copy; Coffee House</small></p>
        </div>
    </footer>
{% endblock %}
./cafe_site/templates/contact.html
{% extends "theme.html" %}
{% block title %}Coffee House{% endblock %}
{% block head %}
  {{ super() }}
{% endblock %}
{% block content %}
    <div class="wrapper">
        <h2 class="page-title">Contact</h2>
        <form action="#">
            <div>
                <label for="message">メッセージ</label>
                <textarea id="message" name="your-message"></textarea>
            </div>

            <input type="submit" class="button" value="送信">
        </form>
    </div><!-- /.wrapper -->
{% endblock %}
{% block content2 %}
    <section id="location">
        <div class="wrapper">
            <div class="location-info">
                <h3 class="sub-title">Coffee House 那覇店</h3>
                <p>
                    住所: 沖縄県那覇市<br>
                    〇〇〇〇〇〇〇 900-0000<br>
                    〇〇〇〇<br>
                    電話: 11-1111-1111<br>
                    営業時間: 13:00〜20:00<br>
                    休日:水曜
                </p>
            </div><!-- /.location-info -->
            <div class="location-map">
                <iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d1789.7269103206745!2d127.6771741081368!3d26.21443990219555!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x34e5699c49922617%3A0xda1a9bb8fcac1213!2z44CSOTAwLTAwMTUg5rKW57iE55yM6YKj6KaH5biC5LmF6IyC5Zyw77yR5LiB55uu!5e0!3m2!1sja!2sjp!4v1585272464198!5m2!1sja!2sjp" width="800" height="400" frameborder="0" style="border:0;" allowfullscreen="" aria-hidden="false" tabindex="0"></iframe>
            </div><!-- /.location-map -->
        </div><!-- /.wrapper -->
    </section><!-- /#location -->

    <section id="sns">
        <div class="wrapper">
            <div class="sns-box">
                <h3 class="sub-title">Facebook</h3>
                <iframe src="https://www.facebook.com/plugins/page.php?href=https%3A%2F%2Fwww.facebook.com%2Ffacebook&tabs=timeline&width=340&height=315&small_header=false&adapt_container_width=true&hide_cover=false&show_facepile=false&appId" width="340" height="315" style="border:none;overflow:hidden" scrolling="no" frameborder="0" allowTransparency="true" allow="encrypted-media"></iframe>
            </div>

            <div class="sns-box">
                <h3 class="sub-title">Twitter</h3>
                <a class="twitter-timeline" data-height="315" href="https://twitter.com/CoffeeH95724918?ref_src=twsrc%5Etfw">Tweets by CoffeeH95724918</a> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
            </div>

            <div class="sns-box">
                <h3 class="sub-title">Youtube</h3>
                <iframe width="560" height="315" src="https://www.youtube.com/embed/VDoTkxg9Rac" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
            </div>
        </div><!-- /.wrapper -->
    </section><!-- /#sns -->
{% endblock %}
{% block footer %}
    <footer>
        <div class="wrapper">
            <p><small>&copy; Coffee House</small></p>
        </div>
    </footer>
{% endblock %}
review.html
{% extends "theme.html" %}
{% block title %}Coffee House{% endblock %}
{% block head %}
  {{ super() }}
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
{% endblock %}
{% block content %}
    <div class="form-content wrapper">
        <h2 class="page-title">Review</h2>
        <form method="POST" action="/review">
            <div>
                <label for="name">お名前</label>
                <input placeholder="名前" name="username" type="text"/>
            </div>
            <div class="star-rating">
                <div class="star-rating__wrap">
                  <input class="star-rating__input" id="star-rating-5" type="radio" name="star" value="5">
                  <label class="star-rating__ico fa fa-star-o fa-lg" for="star-rating-5" title="5 out of 5 stars"></label>
                  <input class="star-rating__input" id="star-rating-4" type="radio" name="star" value="4">
                  <label class="star-rating__ico fa fa-star-o fa-lg" for="star-rating-4" title="4 out of 5 stars"></label>
                  <input class="star-rating__input" id="star-rating-3" type="radio" name="star" value="3">
                  <label class="star-rating__ico fa fa-star-o fa-lg" for="star-rating-3" title="3 out of 5 stars"></label>
                  <input class="star-rating__input" id="star-rating-2" type="radio" name="star" value="2">
                  <label class="star-rating__ico fa fa-star-o fa-lg" for="star-rating-2" title="2 out of 5 stars"></label>
                  <input class="star-rating__input" id="star-rating-1" type="radio" name="star" value="1">
                  <label class="star-rating__ico fa fa-star-o fa-lg" for="star-rating-1" title="1 out of 5 stars"></label>
                </div>
            </div>
            <div>
                <label for="message">メッセージ</label>
                <textarea id="message" placeholder="レビュー" name="review"></textarea>
            </div>
            <input type="submit" class="button" value="送信">
        </form>
    </div>
{% endblock %}
{% block content2 %}
    <div class="wrapper board">
      <p>name : {{username}}</p>
    {% if star == '1' %}
      <p>rating : <img src="/static/images/star.png" width="15" height="15"></p>
    {% endif %}
    {% if star == '2' %}
      <p>rating : <img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"></p>
    {% endif %}
    {% if star == '3' %}
    <p>rating : <img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"></p>
    {% endif %}
    {% if star == '4' %}
    <p>rating : <img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"></p>
    {% endif %}
    {% if star == '5' %}
    <p>rating : <img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"><img src="/static/images/star.png" width="15" height="15"></p>
    {% endif %}
      <p>message : {{review}}</p>
    </div>
{% endblock %}
{% block footer %}
    <footer>
        <div class="wrapper">
            <p><small>&copy; Coffee House</small></p>
        </div>
    </footer>
{% endblock %}

templatesフォルダと同じくstaticフォルダも作成し、その中にhtmlで出力する画像ファイルとcssファイルを配置します。

./cafe_site/static/style.css
@charset "UTF-8";

/* ********************
    共通部分
******************** */
html {
    font-size: 100%;
}
body{
    font-family: "Yu Gothic Medium", "游ゴシック Medium", YuGothic, "游ゴシック体", "ヒラギノ角ゴ Pro W3", sans-serif;
    line-height: 1.7;
    color: rgb(0, 0, 0);
}
a {
    text-decoration: none;
}
img {
    max-width: 100%;
}
.wrapper {
    max-width: 1100px;
    margin: 0 auto;
    padding: 0 4%;
}

/* 背景画像 */
.big-bg {
    background-size: cover;
    background-position: center top;
    background-repeat: no-repeat;
}

/* 見出し */
.page-title {
    font-size: 5rem;
    font-family: 'Philosopher', serif;
    text-transform: uppercase;
    font-weight: normal;
}
.sub-title {
    font-size: 1.375rem;
    padding: 0 8px 8px;
    border-bottom: 2px #0bd solid;
    font-weight: normal;
}

/* ボタン */
.button {
    font-size: 1.375rem;
    background: #0bd;
    color: #fff;
    border-radius: 5px;
    padding: 18px 32px;
}
.button:hover {
    background: #0090aa;
}

/* iframe */
iframe {
    width: 100%;
}


/* ********************
    HEADER
******************** */
.page-header {
    display: flex;
    justify-content: space-between;
}
.logo {
    width: 210px;
    margin-top: 14px;
}
.main-nav {
    display: flex;
    font-size: 1.25rem;
    text-transform: uppercase;
    margin-top: 34px;
    list-style: none;
}
.main-nav li {
    margin-left: 36px;
}
.main-nav a {
    color: rgb(0, 0, 0);
}
.main-nav a:hover {
    color: #0bd;
}

/* ********************
    HOME
******************** */
#home {
    background-image: url(./images/main-bg.jpg);
    min-height: 100vh;
}
#home .page-title {
    text-transform: none;
}
.home-content {
    text-align: center;
    margin-top: 10%;
}
.home-content p {
    font-size: 1.125rem;
    margin: 10px 0 42px;
}

/* ********************
    NEWS
******************** */
#news {
    background-image: url(./images/news-bg.jpg);
    height: 270px;
    margin-bottom: 40px;
}
#news .page-title {
    text-align: center;
}
.news-contents {
    display: flex;
    justify-content: space-between;
    margin-bottom: 50px;
}

/* ********************
    記事部分
******************** */
article {
    width: 74%;
}
.post-info {
    position: relative;
    padding-top: 4px;
    margin-bottom: 40px;
}
.post-date {
    background: #0bd;
    border-radius: 50%;
    color: #fff;
    width: 100px;
    height: 100px;
    font-size: 1.625rem;
    text-align: center;
    position: absolute;
    top: 0;
    padding-top: 10px;
}
.post-date span {
    font-size: 1rem;
    border-top: 1px rgba(255,255,255,.5) solid;
    padding-top: 6px;
    display: block;
    width: 60%;
    margin: 0 auto;
}
.post-title {
    font-family: "Yu Mincho", "YuMincho", serif;
    font-size: 2rem;
    font-weight: normal;
}
.post-title,
.post-cat {
    margin-left: 120px;
}
article img {
    margin-bottom: 20px;
}
article p {
    margin-bottom: 1rem;
}

/* サイドバー */
aside {
    width: 22%;
}
.sub-menu {
    margin-bottom: 60px;
    list-style: none;
}
.sub-menu li {
    border-bottom: 1px #ddd solid;
}
.sub-menu a {
    color: rgb(0, 0, 0);
    padding: 10px;
    display: block;
}
.sub-menu a:hover {
    color: #0bd;
}
aside p {
    padding: 12px 10px;
}

/* ********************
    MENU
******************** */
#menu {
    background-image: url(./images/menu-bg.jpg);
    min-height: 100vh;
}
.menu-content {
    max-width: 560px;
    margin-top: 10%;
}
.menu-content .page-title {
    text-align: center;
}
.menu-content p {
    font-size: 1.125rem;
    margin: 10px 0 0;
}

.grid {
  display: grid;
  gap: 26px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  margin-top: 6%;
  margin-bottom: 50px;
}

/* ********************
    CONTACT
******************** */
#contact {
    background-image: url(./images/contact-bg.jpg);
    min-height: 100vh;
}

/* フォーム */
form div {
    margin-bottom: 14px;
}
label {
    font-size: 1.125rem;
    margin-bottom: 10px;
    display: block;
}
input[type="text"],
input[type="email"],
textarea {
    background: rgba(255,255,255,.5);
    border: 1px #fff solid;
    border-radius: 5px;
    padding: 10px;
    font-size: 1rem;
}
input[type="text"],
input[type="email"] {
    width: 100%;
    max-width: 240px;
}
textarea {
    width: 100%;
    max-width: 480px;
    height: 6rem;
}
input[type="submit"] {
    border: none;
    cursor: pointer;
    line-height: 1;
}

/* 店舗情報・地図 */
#location {
    padding: 4% 0;
}
#location .wrapper {
    display: flex;
    justify-content: space-between;
}
.location-info {
    width: 22%;
}
.location-info p {
    padding: 12px 10px;
}
.location-map {
    width: 74%;
}

/* SNS */
#sns {
    background: #FAF7F0;
    padding: 4% 0;
}
#sns .wrapper {
    display: flex;
    justify-content: space-between;
}
#sns .sub-title {
    margin-bottom: 30px;
}
.sns-box {
    width: 30%;
}

/* ********************
    review
******************** */
#review {
    background-image: url(./images/review-bg.jpg);
    min-height: 100vh;
}

.form-content {
    max-width: 560px;
    margin-top: 10%;
}
.form-content .page-title {
    text-align: center;
}
form div {
    margin-bottom: 14px;
}
label {
    font-size: 1.125rem;
    margin-bottom: 10px;
    display: block;
}
input[type="text"],
textarea {
    background: rgba(255,255,255,.5);
    border: 1px #fff solid;
    border-radius: 5px;
    padding: 10px;
    font-size: 1rem;
}
input[type="text"] {
    width: 100%;
    max-width: 240px;
}
textarea {
    width: 100%;
    max-width: 480px;
    height: 6rem;
}
input[type="submit"] {
    border: none;
    cursor: pointer;
    line-height: 1;
}
.star-rating{
	font-size: 0;
}
.star-rating__wrap{
	display: inline-block;
	font-size: 1rem;
}
.star-rating__wrap:after{
	content: "";
	display: table;
	clear: both;
}
.star-rating__ico{
	float: right;
	padding-left: 2px;
	cursor: pointer;
	color: #FFB300;
}
.star-rating__ico:last-child{
	padding-left: 0;
}
.star-rating__input{
	display: none;
}
.star-rating__ico:hover:before,
.star-rating__ico:hover ~ .star-rating__ico:before,
.star-rating__input:checked ~ .star-rating__ico:before
{
	content: "\f005";
}
.board {
    display: grid;
    gap: 26px;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    margin-top: 6%;
    margin-bottom: 50px;
}

/* ********************
    フッター
******************** */
footer {
    background: rgb(0, 0, 0);
    text-align: center;
    padding: 26px 0;
}
footer p {
    color: #fff;
    font-size: 0.875rem;
}

/* ********************
    スマホ版
******************** */
@media (max-width: 600px) {
    .page-title {
        font-size: 2.5rem;
    }
    .page-header {
        flex-direction: column;
        align-items: center;
    }

    /* HEADER */
    .main-nav {
        font-size: 1rem;
        margin-top: 10px;
    }
    .main-nav li {
        margin: 0 20px;
    }

    /* HOME */
    .home-content {
        margin-top: 20%;
    }

    /* NEWS */
    .news-contents {
        flex-direction: column;
    }
    #news .page-title {
        margin-top: 30px;
    }
    article,
    aside {
        width: 100%;
    }
    aside {
        margin-top: 60px;
    }
    .post-info {
        margin-bottom: 30px;
    }
    .post-date {
        width: 70px;
        height: 70px;
        font-size: 1rem;
    }
    .post-date span {
        font-size: 0.875rem;
        padding-top: 2px;
    }
    .post-title {
        font-size: 1.375rem;
    }
    .post-cat {
        font-size: 0.875rem;
        margin-top: 10px;
    }
    .post-title,
    .post-cat {
        margin-left: 80px;
    }

    /* MENU */
    .menu-content {
        margin-top: 20%;
    }

    /* CONTACT */
    #contact .page-title {
        margin-top: 40px;
    }

    /* フォーム */
    input[type="text"],
    input[type="email"],
    textarea {
        max-width: 100%;
    }

    /* 店舗情報・地図 / SNS */
    #location .wrapper,
    #sns .wrapper {
        flex-direction: column;
    }
    .location-info,
    .location-map,
    .sns-box {
        width: 100%;
    }
    .sns-box {
        margin-bottom: 30px;
    }
}

5-1. ログインフォーム作成

ここからはログインフォームを作成していきます。今回は、ログインすることでブログを投稿することが出来る機能を作成します。誰もがブログを投稿・編集・削除できたら困ってしまうので、ユーザIDとパスワードを知っている人だけがアクセスできるよう、ユーザ認証機能を処理するHTMLを作成します。

./cafe_site/templates/login.html
{% extends "theme.html" %}
{% block title %}Coffee House{% endblock %}
{% block head %}
  {{ super() }}
{% endblock %}
{% block content %}
{% for message in get_flashed_messages() %}
<div class="alert">
  <font color="red">
    <p>※{{ message }}</p>
  </font>
</div>
{% endfor %}
{% endblock %}

{% block content2 %}
<form action="{{ url_for('login') }}" method=post>
  <div>
    <label for="InputTitle">ユーザ名</label>
    <input type="text" id="InputTitle" name=username>
  </div>

  <div>
    <label for="InputPassword">パスワード</label>
    <input type="password" id="InputPassword" name=password>
  </div>
    <input type="submit" class="button" value="ログイン">
</form>
{% endblock %}

ログインとログアウトはナビゲーションバーで操作出来るよう設定します。

6. View

次にURLへのアクセスがあった場合の処理を追加して行きます。
views.pyでは、ルーティング処理とそれに紐付く処理メソッドを記載します。
ここでは、ログインとログアウトのViewを追加しセッション処理とログインとログアウトをユーザーに知らせるメッセージ機能の追加行います。

6-1. セッション追加

以下の処理を実装します。

1. ログイン後、サーバからsセッション情報をブラウザ(クライアント)側に返します。

2. クライアントはセッション情報をクッキーに保存します。

3. 以後、クライアントはそのセッション情報を付与してリクエストし、サーバはそのsセッション情報が正しいか確認することで、ログインしているかどうかを判別します。

6-2. サーバ側でのメッセージ追加

views.pyにて、各アクションに応じて表示させたい結果をflashに保存し、クライアントに返せるよう変更します。

views.py
from flask import request, redirect, url_for, render_template, flash, session
from cafe_site import app


@app.route('/')
def index():
     return render_template('cafe_site/index.html', id="home")


@app.route('/menu')
def menu():
     return render_template('cafe_site/menu.html', id="menu")


@app.route('/news')
def news():
     return render_template('cafe_site/news.html', id="news")



@app.route('/contact')
def contact():
     return render_template('cafe_site/contact.html', id="contact")


@app.route('/review', methods=['GET'])
def review():
     return render_template('cafe_site/review.html', id="review")


@app.route('/review', methods=['POST'])
def view_board():
     if request.form['star'] and request.form['title'] and request.form['review']:
          return render_template('cafe_site/review.html',  name=app.config['USERNAME'], star=request.form['star'], title=request.form['title'], review=request.form['review'])


@app.route('/review')
def show_entries():
# ログアウト状態であればログインページへ遷移
    if not session.get('logged_in'):
        return redirect(url_for('login'))
# ログイン状態であればレビューページへ遷移
    return render_template('cafe_site/review.html')


###########################
### ログインページの処理 ###
###########################
@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
# ユーザー名照合失敗時の処理
        if request.form['username'] != app.config['USERNAME']:
            flash('ユーザ名が異なります')
# パスワード照合失敗時の処理
        elif request.form['password'] != app.config['PASSWORD']:
            flash('パスワードが異なります')
# ログイン成功時の処理
        else:
            session['logged_in'] = True
            flash('ログインしました')
# show_entries関数の呼び出し
            return redirect(url_for('show_entries'))
    return render_template('login.html')

# ログアウト時の処理
@app.route('/logout')
def logout():
# ログアウト状態でshow_entries関数の呼び出し
    session.pop('logged_in', None)
    flash('ログアウトしました')
    return redirect(url_for('show_entries'))

6-3. セッション処理

Flaskでは、session変数を用いることでセッション情報を扱うことができます。
/loginでは、ログイン後にsession['logged_in']=Trueとすることでセッション中のlogged_inという値がTrueにセットされます。以後、この値をリクエストのたびにチェックすることで、ログインしているかどうかを判別することができます。
ログアウトしたらセッション情報を削除します。session.pop('logged_in',None)として、削除するようにします。

6-4. メッセージ処理

メッセージ機能はそれぞれ、ログインが成功したときとログアウトした場合で、flash('ログインしました')flash('ログアウトしました')と1行追加しています。これで、flash領域にメッセージが追加され、ブラウザにメッセージを渡すことができます。

6-5. シークレットキー設定

config.pyにて、シークレットキーを設定します。このシークレットキーを使って、セッション情報が暗号化されます。

./cafe_site/config.py
DEBUG = True
SECRET_KEY = 'bjgc5ejrwgwk'
USERNAME = 'syuuhei'
PASSWORD = 'abc123'

6-6. セッション情報によって、メニュー表示を切り替える

theme.htmlを編集して、ログインしてるかどうかによってナビゲーションバーに表示させるリンクを変更させます。

./cafe_site/templates/theme.html
<nav>
    <ul class="main-nav">
       {% if not session.logged_in %}
        <li><a href="/news">News</a></li>
        <li><a href="/menu">Menu</a></li>
        <li><a href="/contact">Contact</a></li>
        <li><a href="/login">login</a></li>
       {% else %}
        <li><a href="/news">News</a></li>
        <li><a href="/menu">Menu</a></li>
        <li><a href="/contact">Contact</a></li>
        <li><a href="/review">Review</a></li>
        <li><a href="/logout">logout</a></li>
       {% endif %}
    </ul>
</nav>
./cafe_site/templates/theme.html
{% if not session.logged_in %}
ログインしていない時の処理
{% else %}
ログインしている時の処理
{% endif %}

ログインしていないときはログインリンクを表示させ、ログインしているときはログアウトリンクを表示させています。

続きはFlaskを用いてのウェブアプリケーション作成②で説明いたします。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?