LoginSignup
1
1

More than 1 year has passed since last update.

Python / Flaskでしりとり機能を持たせた「しりとり掲示板」を作ってみた

Posted at

対象

  • PythonとFlaskの基礎はなんとなく学習したから、何か成果物を作ってみたい
  • ただの掲示板ではなく、何か機能を付け加えた掲示板を作ってみたい

実装環境

  • Windows 10 Home 64bit
  • Python 3.9.6

ライブラリ

  • Flask==2.0.2
  • Flask-SQLAlchemy==2.5.1
  • Jinja2==3.0.3
  • SQLAlchemy==1.4.35
  • sqlite3

ディレクトリ構成

flask_bbs
┝ app.py
┝ templates
│ ┝ index.html
│ ┝ layout.html
│ ┝ result.html
│ ┝ delete.html
│ ┝ error.html

app.pyの作成

必要なライブラリのインポート

app.py
import re
from flask import Flask, request, render_template
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import validates

app = Flask(__name__)

SQLALCHEMY_TRACK_MODIFICATIONS = False
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# SQLAlchemyでデータベースに接続する
db_uri = 'sqlite:///test.db'
app.config['SQLALCHEMY_DATABASE_URI'] = db_uri
db = SQLAlchemy(app)

しりとり機能を持たせるために、正規表現ライブラリreをインポートします。
また、validationが必要になるので、sqlalchemy.ormからvalidatesを利用しましょう。

テーブルの作成

app.py
class Comment(db.Model):
    """[テーブルの定義を行うクラス]
    Arguments:
        db {[Class]} -- [ライブラリで用意されているクラス]
    """
    __tablename__ = 'Comment'

    id_ = db.Column(db.Integer, primary_key=True, autoincrement=True)
    pub_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    name = db.Column(db.Text())
    comment = db.Column(db.Text())

    def __init__(self, pub_date, name, comment):
        """[テーブルの各カラムを定義する]
        [Argument]
            id_ -- 投稿番号(プライマリキーなので、自動で挿入される)
            pub_date -- 投稿日時
            name -- 投稿者名
            comment -- 投稿内容
        """

        self.pub_date = pub_date
        self.name = name
        self.comment = comment

    @validates('name')
    def validate_name(self, key, name):
        if len(name) <= 1:
            raise ValueError("名前を2文字以上で入力してください")
        
        name_start = re.compile(r'^[a-zA-Z].*').search(name)
        name_end = re.compile(r'.*[a-zA-Z]$').search(name)
        if name_start == None or name_end == None:
            raise ValueError("名前の先頭と末尾は半角アルファベットで入力してください")
        
        last_name = Comment.query.order_by(Comment.id_.desc()).first()
        if last_name != None:
            if str(name[0]) != str(last_name.name)[-1]:
                raise ValueError("前の投稿の名前の末尾の文字=名前の先頭文字になるよう入力してください") 
        return name


    @validates('comment')
    def validate_comment(self, key, comment):
        if len(comment) <= 1:
            raise ValueError("投稿内容を2文字以上で入力してください")
        comment_start = re.compile(r'^[あ-わ].*').search(comment)
        comment_end = re.compile(r'.*[あ-わ]$').search(comment)
        if comment_start == None or comment_end == None:
            raise ValueError("投稿内容の先頭と末尾は、「ん」と小文字以外のひらがなで入力してください")

        last_comment = Comment.query.order_by(Comment.id_.desc()).first()
        if last_comment != None:
            if str(comment[0]) != str(last_comment.comment)[-1]:
                raise ValueError("前の投稿内容の末尾の文字=投稿内容の先頭文字になるよう入力してください") 

        return comment


try:
    db.create_all()
except Exception as e:
    print(e.args)
    pass

名前は2文字以上の半角アルファベット、投稿内容は2文字以上の全角ひらがなという制限をつけました(正確には先頭と末尾のみ制限をつけています)。
また、前回の投稿を参照するために、降順に並べたときの最初の一行を
Comment.query.order_by(Comment.id_.desc()).first()で取り出しています。
そこにif last_name(last_comment) != None:の条件をつけることで、最初の投稿にはしりとりの制限をつけないようにしています。

ルーティングの設定

app.py
@app.route("/")
def index():
    # テーブルから投稿データをSELECT文で引っ張ってくる
    text = Comment.query.all()
    return render_template("index.html", lines=text)


@app.route("/result", methods=["POST"])
def result():
    # 現在時刻 投稿者名 投稿内容を取得
    date = datetime.now()
    name = request.form["name"]
    comment = request.form["comment_data"]
    # テーブルに格納するデータを定義する
    comment_data = Comment(pub_date=date, name=name, comment=comment)
    # テーブルにINSERTする
    db.session.add(comment_data)
    # テーブルへの変更内容を保存
    db.session.commit()
    return render_template("result.html", comment=comment, name=name, now=date)

@app.route('/delete', methods=["POST"])
def all_delete():
    DleteDataset = db.session.query(Comment).all()
    for DleteData in DleteDataset:
        db.session.delete(DleteData)
        db.session.commit()

    date = datetime.now()
    comment_ = 'しりとり'
    name_ = 'start'
    start_data = Comment(pub_date=date, name=name_, comment=comment_)
    db.session.add(start_data)
    db.session.commit()

    return render_template('delete.html')

@app.errorhandler(500)
def valueerror(error):
    return render_template('error.html'), 500

if __name__ == "__main__":
    # app.run(debug=True)にpython app.pyではなく、flask run --debugger --reload で起動すると、自動リロードが走る
    app.run(debug=True)

4つのhtmlファイルを用意し、ルートを振り分けています。
ValueErrorはerrorhandlerを使い、error.htmlを表示するよう設定しています。
deleteを実行すると、名前がstart、投稿内容がしりとりの投稿が自動で生成されるようにしています。
このapp.pyを実行する際は、python app.pyではなく flask run --debugger --reloadで起動すると、ファイルの編集等がしやすいと思います。

htmlファイルの用意

index.html
{% extends 'layout.html' %}
{% block content %}
<h1>しりとり掲示板へようこそ!</h1>
    <form action="/result" method="post">
        <input type="hidden" name="comment_box">
        <label for="comment_">投稿</label>
        <textarea name="comment_data" rows="6" cols="100"></textarea>
        <p></p>
        <label for="name">名前</label>
        <input type="text" name="name">
        <button type="submit">送信する</button>
    </form>
    <h2>投稿一覧</h2>
    <form method="post" action="">
        <table class="table">
            <thead class="thead-dark">
                <tr>
                    <th>ID</th>
                    <th>日付</th>
                    <th>名前</th>
                    <th>投稿内容</th>
                    </tr>
                </thead>
            <tbody>
                {% for th in lines | reverse %}
                <tr>
                    <td>{{ th.id_ }}</td>
                    <td>{{ th.pub_date }}</td>
                    <td>{{ th.name }}</td>
                    <td>{{ th.comment }}</td>
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </form>
    <p></p>
    <form action="/delete" method="post">
        <input type="hidden" name="comment_box">
        <label for="comment_">データ削除ボタン</label>
        <p></p>
        <button type="submit">データを全て削除する</button>
    </form>

{% endblock %}
layout.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>しりとり掲示板</title>

        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

        <!-- <style>body {padding: 10px;}</style> -->
    </head>
    <body>
        {% block content %}
        {% endblock %}
        <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.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
        </body>
</html>
result.html
{% extends "layout.html" %}
{% block content %}
    <h1>コメントを書き込みました</h1>
    <br>
    <form action="/" method="get">
        <button type="submit">戻る</button>
    </form>
{% endblock %}
delete.html
{% extends "layout.html" %}
{% block content %}
    <h1>コメントを全て削除しました</h1>
    <br>
    <form action="/" method="get">
        <button type="submit">戻る</button>
    </form>
{% endblock %}
error.html
{% extends "layout.html" %}
{% block content %}
    <h1>入力エラー</h1>
    <h2>入力ルールをもう一度ご確認ください</h2>
    <br>
    <form action="/" method="get">
        <button type="submit">戻る</button>
    </form>
{% endblock %}

実際に作成するときは、cp templates/result.html templates/delete.html などのコマンドを用いて、コピーして作成すると効率が上がります。

実際に遊んでみた

しりとり掲示板new.gif

感想

しりとり機能を付け加えるのは、そこまで難しくないことがわかりました。
validatesを使いこなせば、他にも色々な制限を設けることが簡単にできます。
ぜひvalidationで遊んでみてください。

1
1
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
1
1