geek開発日誌

超一流のプログラマーを目指してます!作成したポートフォリオを記録していく開発日誌です。

ドラマレビューサイト(Flask)②

こんにちは。nakatatsuです。

前回に引き続きドラマレビューサイトを作成します。前回の詳細についてはこのブログの過去記事「ドラマレビューサイト(Flask)」を参照してください。

nakatatsu-com.hatenablog.com


仕様

完成形の仕様です。途中で変更があるかもしれません。

・ドラマのレビューを閲覧、投稿できるサイトを作成

・おすすめのドラマを5個ランダムに表示

・レビューランキングを表示

・視聴率ランキングを表示

準備

Pipenv、Flaskのインストールについてはこのブログの過去記事「ドラマレビューサイト(Flask)」を参照してください。

nakatatsu-com.hatenablog.com



ここではFlask-SQLAlchemyとFlask-Scriptというライブラリをインストールします。


・Flask-SQLAlchemyのインストール

(drama) $ pipenv install Flask-SQLAlchemy

Flask-SQLAlchemyは簡単にデータベースを設定し扱うことのできるライブラリです。


・Flask-Scriptのインストール

(drama) $ pipenv install Flask-Script

Flask-Scriptはスクリプトを管理、実行するのに便利なライブラリです。

フォルダ構成

drama
├── Pipfile
├── Pipfile.lock
├── flask_drama
│   ├── __init__.py
│   ├── config.py
│   ├── flask_drama.db
│   ├── models
│   │   ├── posts.py
│   │   └── users.py
│   ├── scripts
│   │   └── db.py
│   ├── static
│   │   ├── coollogo_com-11407173.png
│   │   └── style.css
│   ├── templates
│   │   ├── index.html
│   │   ├── layout.html
│   │   ├── login.html
│   │   └── new.html
│   └── views.py
├── manage.py
└── server.py

フォルダ構成はこのようになっております。

ソースコード

server.py(起動ファイル)

from flask_drama import app


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


__init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)
app.config.from_object('flask_drama.config')

db = SQLAlchemy(app)

import flask_drama.views


views.py

from flask import render_template
from flask import redirect
from flask import session
from flask import flash
from flask import url_for
from flask import request
from flask_drama import app
from flask_drama import db
from flask_drama.models.users import User


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

@app.route('/login', methods=['GET', 'POST'])
def login():
    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('ログインしました')
            return redirect(url_for('index'))
    return render_template('login.html')

@app.route('/logout')
def logout():
    session.pop('logged_in', None)
    flash('ログアウトしました')
    return redirect(url_for('index'))

@app.route('/new', methods=['GET', 'POST'])
def new():
    if request.method == 'POST':
        user = User(
            username=request.form['username'],
            password=request.form['password']
        )
        db.session.add(user)
        db.session.commit()
        session['logged_in'] = True
        flash('新規登録しました')
        return redirect(url_for('index'))
    return render_template('new.html')


config.py(設定情報を記載したファイル)

DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///flask_drama.db'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = '\xbf,M\xe4\x89\x92C$H\xbeC\x0f\xad\xca\x04\xd5\xa5\xd00^\x8e\xee\xa0\x1c'


manage.py(モデルをデータベースに反映するための実行ファイル)

from flask_script import Manager
from flask_drama import app
from flask_drama.scripts.db import InitDB


if __name__ == '__main__':
    manager = Manager(app)
    manager.add_command('init_db', InitDB())
    manager.run()


db.py(モデルをデータベースに反映するためのスクリプトファイル)

from flask_script import Command
from flask_drama import db


class InitDB(Command):
    'create database'

    def run(self):
        db.create_all()


users.py(ユーザー情報のデータベースモデル)

from flask_drama import db


class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True)
    password = db.Column(db.String(50), unique=True)

    def __init__(self, username=None, password=None):
        self.username = username
        self.password = password


posts.py(レビュー情報のデータベースモデル)

from flask_drama import db


class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    star = db.Column(db.Integer)
    review = db.Column(db.text)

    def __init__(self, star=None, review=None):
        self.star = star
        self.review = review


layout.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>あなたにおすすめのドラマ ドラマのレビューを閲覧・投稿</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.1/css/all.css">
  </head>
  <body>
    <header>
      <div class="container">
        <div id="header-left">
          <a id="top" href="{{ url_for('index') }}">
            <img src="{{ url_for('static', filename='coollogo_com-11407173.png') }}">
          </a>
        </div>
        <div id="header-right">
          {% if not session.logged_in %}
          <a id="new" href="{{ url_for('new') }}">新規登録</a>
          <a id="login" href="{{ url_for('login') }}">ログイン</a>
          {% else %}
          <a id="logout" href="{{ url_for('logout') }}">ログアウト</a>
          {% endif %}
        </div>
        <div id="clear"></div>
      </div>
    </header>
    <div id="main">
      <div id="title">
        <h1>あなたにおすすめのドラマ</h1>
      </div>
      {% for message in get_flashed_messages() %}
      <div id="alert">
        <p>{{ message }}</p>
      </div>
      {% endfor %}
      <div class="container">
        {% block body %}{% endblock %}
      </div>
    </div>
  </body>
</html>


index.html

{% extends 'layout.html' %}
{% block body %}
<div id="reviews">
  <h2>おすすめのドラマ</h2>
  <div class="review">
    <div class="review-title">
      <h3>スパイラル~町工場の奇跡~</h3>
      <p>出演:玉木宏、貫地谷しほり、戸塚純貴、福士誠治</p>
    </div>
    <ul>
      <li>
        なんか勿体ないんだよなぁ…。
        前に仲村トオルさをが主役のドラマと似ていてそれを強く感じる。
      </li>
      <li>
        毎週、今週こそはスッキリする展開が来るかと思って見ていたけどずーーーっとやられっぱなし。本当につまらない。もう見なくていいや。
      </li>
      <li>
        ホライズンキャピタルが英興技巧を買収。
        さらに、マジテックの債権を取得。
        英興技巧・マジテックの特許をどう守るのか。
        普通だったら訴訟するしかないのでは?
      </li>
    </ul>
    <a class="all" href="{{ url_for('index') }}"><i class="fas fa-list"></i><span>すべてのレビューを見る</span></a>
  </div>
  <div class="review">
    <div class="review-title">
      <h3>白い巨塔</h3>
      <p>出演:岡田准一、松山ケンイチ、沢尻エリカ、寺尾聰</p>
    </div>
    <ul>
      <li>
        岡田准一さん内面を魅せれる俳優さんでない事をこれで感じた。
        時代劇SP、動の演技が向いてるようだな!
        視聴率が大事だからとてこれを白い巨塔ですと見せられても(泣)
        細かい批評はもう皆さんたくさん書かれてるので省略。
        つまらなかったです!
      </li>
      <li>
        やっとこさ 全話観終わりました
        とてもよかったです!!
        またゆっくり 細部を見なおします
        ちなみに ジャニーズとか好きではないし
        岡田さんて 映画永遠のゼロくらいしか
        見たことなかった
        小説の映画化は また見たいです
      </li>
      <li>
        岡田准一は変な日本語だけど時代劇がかった時代物向き。現代物は重すぎてうざい
      </li>
    </ul>
    <a class="all" href="{{ url_for('index') }}"><i class="fas fa-list"></i><span>すべてのレビューを見る</span></a>
  </div>
  <div class="review">
    <div class="review-title">
      <h3>グッドワイフ</h3>
      <p>出演:常盤貴子、小泉孝太郎、水原希子、北村匠海</p>
    </div>
    <ul>
      <li>
        常盤貴子に一点。
      </li>
      <li>
        面白かったです。続編あったらいいな!
      </li>
      <li>
        悪いドラマじゃないかけど絶賛する程のドラマじゃない
        分かるわ!
        中身がうーん😔
      </li>
    </ul>
    <a class="all" href="{{ url_for('index') }}"><i class="fas fa-list"></i><span>すべてのレビューを見る</span></a>
  </div>
  <div class="review">
    <div class="review-title">
      <h3>なつぞら</h3>
      <p>出演:広瀬すず、岡田将生、草刈正雄、松嶋菜々子</p>
    </div>
    <ul>
      <li>
        山口智子がカスタネットを使い出した。フラメンコ踊る準備?
        山口智子の演技が良かったのは野際陽子との嫁姑ドラマ、ダブルキッチンと、
        布施博とのお受験ドラマ、スウィートホーム。個人的にはこちらの方が好きかな。泣いたり笑ったり怒ったり、一人息子翼君の小学校受験に一生懸命な母親役が素敵だった。こちらにも塾講師役で野際陽子が出てた。懐かしいな。
        29歳のクリスマスからは強気でガサツな役回りが続いてワンパターン化してしまった。なつぞらもそれを続けてしまってる。こればっかりは脚本家と監督の意向もあるから仕方ないとしても、もう55になるのだから少し違った山口智子が見たいな。
      </li>
      <li>
        青のカーディガンの先輩がちょっといいなと思った。
        「変わってるって主張したいんじゃないの?」「いいのよ!絵を描く人間なんて、そういうの、いっぱい、いるから」って、マルッとなっちゃんを肯定してくれて、頼もしかった。
      </li>
      <li>
        一応1500円の家賃払ってますよね。
        食事と衣装つきで。
        上げ膳据え膳は別に責められることでもないと思う。
      </li>
    </ul>
    <a class="all" href="{{ url_for('index') }}"><i class="fas fa-list"></i><span>すべてのレビューを見る</span></a>
  </div>
  <div class="review">
    <div class="review-title">
      <h3>パーフェクトワールド</h3>
      <p>出演:松坂桃李、山本美月</p>
    </div>
    <ul>
      <li>
        フジと日テレを応援して他をけなす人
        やめてよ。
        ちゃんと関テレ制作ってエンドロールにあるでしょ!ん書かれてるので省略。
      </li>
      <li>
        カンテレさんは丁寧にドラマを制作します。
      </li>
      <li>
        関テレさんも良いがフジテレもグッドだよ。
      </li>
    </ul>
    <a class="all" href="{{ url_for('index') }}"><i class="fas fa-list"></i><span>すべてのレビューを見る</span></a>
  </div>
</div>
<div id="main-right">
  <div id="review-ranking">
    <h3>ドラマレビューランキングTOP10</h3>
    <div id="review-ranking-list">
      <ol>
        <li>坂の途中の家</li>
        <li>のの湯</li>
        <li>きのう何食べた?</li>
        <li>デジタル・タトゥー</li>
        <li>インハンド</li>
        <li>向かいのバズる家族</li>
        <li>わたし、定時で帰ります。</li>
        <li>賭ケグルイ season 2</li>
        <li>やじ×きた 元祖・東海道中膝栗毛</li>
        <li>大草原の小さな家</li>
      </ol>
      <a class="all" href="{{ url_for('index') }}"><i class="fas fa-list"></i><span>すべてのレビューを見る</span></a>
    </div>
  </div>
  <div id="rate-ranking">
    <h3>視聴率ランキングTOP10</h3>
    <div id="rate-ranking-list">
      <ol>
        <li>緊急取調室 第3シリーズ</li>
        <li>特捜9 season2</li>
        <li>ラジエーションハウス</li>
        <li>科捜研の女 Season19</li>
        <li>集団左遷!!</li>
        <li>いだてん~東京オリムピック噺~</li>
        <li>インハンド</li>
        <li>わたし、定時で帰ります。</li>
        <li>俺のスカート、どこ行った?</li>
        <li>白衣の戦士!</li>
      </ol>
    </div>
  </div>
</div>
{% endblock %}


new.html

{% extends 'layout.html' %}
{% block body %}
<div id="form-group">
  <form action="{{ url_for('new') }}" method=post>
    <label for="username">ユーザ名ー</label><br>
    <input id="username" type="text" name="username"><br>
    <label for="password">パスワード</label><br>
    <input id="password" type="password" name="password"><br>
    <input id="login-btn" type="submit" value="新規登録">
  </form>
</div>
{% endblock %}


login.html

{% extends 'layout.html' %}
{% block body %}
<div id="form-group">
  <form action="{{ url_for('login') }}" method=post>
    <label for="username">ユーザー名</label><br>
    <input id="username" type="text" name="username"><br>
    <label for="password">パスワード</label><br>
    <input id="password" type="password" name="password"><br>
    <input id="login-btn" type="submit" value="ログイン">
  </form>
</div>
{% endblock %}


style.css

* {
  box-sizing: border-box;
  margin: 0;
}

.container {
  width: 100%;
  padding: 0 20px;
}

a {
  text-decoration: none;
}

span {
  margin-left: 5px;
}

#alert {
  color: #ff0000;
  padding-left: 20px;
}

header {
  border-bottom: solid 1px #c0c0c0;
  box-shadow: 0 2px 4px #c0c0c0;
  width: 100%;
  height: 60px;
  position: fixed;
  top: 0px;
  z-index: 10;
  background-color: #ffffff;
}

#clear {
  clear: right;
}

#header-left img {
  float: left;
  height: 40px;
  margin-top: 10px;
}

#header-right {
  float: right;
}

#header-right a {
  display: inline-block;
  line-height: 60px;
  transition: all 0.5s;
  padding: 0 10px;
}

#header-right a, #header-right a:visited {
  color: #000000;
}

#header-right a:hover {
  background-color: rgba(192, 192, 192, 0.2);
}

#logout {
  margin: 0 20px;
}

#title {
  padding: 100px 0 20px 0;
  text-align: center;
  text-shadow: 4px 4px 1px rgb(192, 192, 192);
}

#reviews {
  float: left;
  width: 70%;
}

#reviews h2 {
  padding-bottom: 20px;
}

.review {
  border: solid 1px #c0c0c0;
  margin-bottom: 20px;
  border-radius: 5px;
  padding: 10px;
}

.review li {
  padding-top: 10px;
}

.review h3, .review p {
  display: inline;
}

.review p {
  margin-left: 10px;
  color: #6b6b6b;
}

.review-title {
  border-bottom: solid 1px #c0c0c0;
  padding-bottom: 10px;
}
#main-right {
  float: right;
  width: 30%;
}

#main-right h3 {
  border-bottom: solid 1px #c0c0c0;
  padding: 20px 0;
  margin-bottom: 20px;
  font-size: 18px;
  color: #6b6b6b;
}

#review-ranking, #rate-ranking {
  border: solid 1px #f5f5f5;
  border-radius: 5px;
  padding: 10px;
  background-color: #f5f5f5;
  margin-left: 20px;
}

#review-ranking {
  margin-top: 80px;
  margin-bottom: 30px;
}

.all {
  display: inline-block;
  padding-top: 10px;
  color: #4387e9;
}

.all:visited {
  color: #4387e9;
}

.all:hover {
  text-decoration: underline;
}

#username, #password {
  width: 30%;
  height: 25px;
  margin-bottom: 20px;
}

#login-btn {
  color: #ffffff;
  background-color: #4387e9;
  padding: 5px 10px;
  opacity: 1;
  cursor: pointer;
  font-size: 15px;
}

#login-btn:hover {
  opacity: 0.8;
}


起動ファイルであるserver.pyを実行するとアプリケーションが起動します。
HTMLを分割し共通部分はlayout.htmlにまとめました。ユーザー情報、レビュー情報をデータベースで処理できるようにモデルを作成しました。

結果

今回は主に新規登録ページとログインページを作成しました。新規登録、ログインが完了するとトップページに遷移します。

f:id:nakatatsu_com:20190607002240p:plain
f:id:nakatatsu_com:20190607002238p:plain

まとめ

・ドラマのレビューを閲覧、投稿できるサイトを作成した

・今回は主に新規登録ページとログインページを作成した

参考文献

参考文献です。