FlaskでWebアプリを作成 続き1(インデックス画面と入力画面を機能させてアップデートまで)
はじめに
インデックス(見出し)画面と入力画面のレイアウトが出来上がったので、以前にログイン画面を作成したWebサイトに追加・変更していきます。
以前の内容はこちら
動作環境
Windows10
Python 3.7.5
Flask 1.1.1
作業フォルダ/
├ blog/
│ ├ templates/
│ │ └ blog/
│ │ ├ base.html ・・・(1)
│ │ ├ edit.html ・・・(4)
│ │ ├ index.html ・・・(3)
│ │ ├ newcomoer.html
│ │ └ top.html
│ ├ models/
│ │ ├ __init__.py
│ │ ├ database.py
│ │ ├ models.py ・・・(2)
│ │ └ wiki.db
│ ├ __init__.py
│ └ server.py ・・・(5)
├ static/
│ ├ images/
│ │ └ sample.jpg
│ ├ back_image.jpg
│ ├ base.css
│ ├ edit.css
│ ├ edit.js
│ ├ image.jpg
│ ├ index.css
│ ├ login.css
│ ├ login.js
│ ├ newcomer.css
│ └ newcomer.js
├ templates/
│ └ index.html
├ key.py
└ main.py
1.ヘッダーにユーザー名を表示する
base.html
<link rel="stylesheet" type="text/css" href="/static/base.css">
<body>
<header>
{% if name == "" %}
<h3 id="user_name">ようこそ ゲスト さん</h3>
{% elif name %}
<h3 id="user_name">{{name}}</h3>
{% else %}
<h3 id="user_name">ログインしてください</h3>
{% endif %}
<div class="menu_box">
<label id="menu" for="toggle">メニュー</label>
<input type="checkbox" id="toggle">
<ul class="dropdown_menu">
<li><a href="/blog">ホーム</a></li>
<li><a href="/blog/create">新規作成</a></li>
<li><a href="/blog/logout">ログアウト</a></li>
</ul>
</div>
</header>
{% block body %}{% endblock %}
<footer>
<ul>
<li>ホーム</li>
<li>検 索</li>
<li><a href="/blog/create">新規作成</a></li>
<li><a href="/blog/logout">ログアウト</a></li>
</ul>
<span id="c">(c)2020 ふたり暮らし</span>
</footer>
</body>
変更点:ヘッダーに「ユーザー名」を表示します。ユーザー名が無い場合には「ようこそ ゲスト さん」と表示します。
2.SQLiteでデータベースを追加・修正
models.py
from sqlalchemy import Column, Integer, String, Text, DateTime #from models.database import Base # データベースの初期化用 from blog.models.database import Base # 初期化が終わればこちらへ変更 from datetime import datetime class WikiContent(Base): __tablename__ = 'wikicontents' id = Column(Integer, primary_key=True) title = Column(String(128))
image_url = Column(String(128)) comment = Column(Text) date = Column(DateTime, default=datetime.now()) def __init__(self, title=None, image_url=None, comment=None, date=None): self.title = title self.image_url = image_url self.comment = comment self.date = date def __repr__(self): return '<Title %r>' % (self.title) #Userクラスを追加。WikiContentの使い回し class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) user_name = Column(String(128)) hashed_password = Column(String(128)) def __init__(self, user_name=None, hashed_password=None): self.user_name = user_name self.hashed_password = hashed_password def __repr__(self): return '<Name %r>' % (self.user_name)
追加・変更点:「id, title, image_url, comment, date」の5つのデータを扱います。
image_url・・画像のファイル名のみを保存します。(画像データは「作業フォルダ/static/images/」に保存します)
※データベースに保存する順番が「読み込む時の順番」になりますので、順番には注意が必要です。
データベースの初期化
「wiki.db」を作成します。コマンドプロンプトから下記を実行します。
cd 作業フォルダ
cd blog
python
from models.database import init_db
init_db()
quit()
modelsフォルダ内に「wiki.db」ファイルが出来ます。確認してみましょう。
※重要:初期化する時は「models.py」の2行目「#from models.database import Base」のコメント化を解除して、3行目「from blog.models.database import Base」をコメント化してください。
「wiki.db」が作成されたら元に戻してください。(他にいい方法があればいいのですが、、、最初だけなので。)
3.インデックス(見出し)画面の修正
index.html
{% extends "blog/base.html" %}
{% block body %}
<link rel="stylesheet" type="text/css" href="/static/index.css">
<title>インデックス画面</title>
<div id="main">
{% for diary in all_diary[::-1] %}
<div class="table">
<h3>サンプル1</h3>
<h3>{{diary.title}}</h3>
<p><img src={{diary.image_url}}></p>
<p>{{diary.comment}}</p>
<div class="time">{{diary.date.strftime("%Y/%m/%d %H:%M")}}</div>
</div>
{% endfor %}
</div>
{% endblock %}
{% for diary in all_diary[::-1] %}
Pythonでfor文を逆順で使います。(インデックスを新しい順に表示するため)
※データベースを「読み込む時の順番」に要素を配置できるように注意が必要です。
4.入力画面の修正
edit.html
{% extends "blog/base.html" %}
{% block body %}
<link rel="stylesheet" type="text/css" href="/static/edit.css">
<script src="/static/edit.js"></script>
<title>入力画面</title>
<form action="/blog/diarysave" method="post" enctype="multipart/form-data">
<div id="main">
<label>タイトル</label>
<input id="text1" type="text" name="title">
<br><br>
<div id=image_box>
<label class="btn1" for="file-sample">ファイルを選択</label>
<input id="file-sample" type="file" name="image">
<img id="file-preview">
</div>
<label>コメント</label>
<textarea rows="12" cols="100" name="comment"></textarea>
<div class="time"></div>
<input class="btn1" type="submit" value=" 登 録 ">
</div>
</form>
{% endblock %}
<form action="/blog/diarysave" method="post" enctype="multipart/form-data">
入力フォームを作成します。送信ボタンをクリックすると転送処理されます「action」で送信先、「method」で転送方法、「enctype」でデータ形式、フォーム内の要素の「name」でPythonで使用する変数を設定します。
5.メインプログラムの追加・修正
server.py(長いので全文は後で載せます)
追加部分
#「/diarysave」へアクセスがあった場合「/blog/index」ページへ移動
@app.route('/diarysave', methods=['post'])
def diarysave(): #保存
image_url = ""
# 画像ファイルを保存
if request.files['image']:
f = request.files['image']
filepath = 'static/images/' + secure_filename(f.filename)
f.save(filepath)
filepath = '/' + filepath
image_url = filepath
# データベースに保存
title = request.form["title"]
comment = request.form["comment"]
# JSTタイムゾーンを作成
jst = timezone(timedelta(hours=9), 'JST')
date = datetime.now(jst)
content = WikiContent(title,image_url,comment,date)
db_session.add(content)
db_session.commit()
return redirect(url_for("blog.index"))
画像ファイルが選択されていた場合は「static/images/」に画像データを保存します。変数「image_url」に「/static/images/画像ファイル名」を設定します。
「pythonanywhere」では現在時刻が「日本時間の9時間前」になってしまうので、日本の現在時刻を「date」に設定しています。
「server.py」の全文です。
# Blueprint(pyファイルを分割するための関数)をインポート
from flask import Blueprint
#「app」を「Blueprint()」を使って定義
#「blog」の部分は、url_for("blog.top")で使用
app = Blueprint('blog', __name__, template_folder='templates')
# 必要なモジュールをインポート
import os
from datetime import datetime, timedelta, timezone
from flask import Flask, session, redirect, url_for, render_template,request
#「/blog/models/models.py」の「WikiContent」と「User」を呼び出す
from blog.models.models import WikiContent,User
#「/blog/models/database.py」の「db_session」を呼び出す
from blog.models.database import db_session
from werkzeug.utils import secure_filename
# ハッシュ化するための関数をインポート(パスワードの暗号化用)
# 今回はsha256という暗号化方式を使用
import key
from hashlib import sha256
#「/」へアクセスがあった場合
@app.route('/')
def index(): # 一覧画面
if "user_name" in session:
# セッションがログイン状態であれば「blog/index.html」を返す
name = session["user_name"]
all_diary= WikiContent.query.all()
return render_template('blog/index.html',name=name, all_diary=all_diary)
else:
# ログイン状態でなければ「/blog/top」ページへ移動
return redirect(url_for("blog.top"))
#「/logout」へアクセスがあった場合「/blog/top」ページへ移動
@app.route('/logout')
def logout():
session.pop("user_name", None)
return redirect(url_for("blog.top",status="logout"))
#「/create」へアクセスがあった場合「blog/edit.html」を返す
@app.route('/create')
def create(): #新規作成
return render_template('blog/edit.html')
#「/diarysave」へアクセスがあった場合「/blog/index」ページへ移動
@app.route('/diarysave', methods=['post'])
def diarysave(): #保存
image_url = ""
# 画像ファイルを保存
if request.files['image']:
f = request.files['image']
filepath = 'static/images/' + secure_filename(f.filename)
f.save(filepath)
filepath = '/' + filepath
image_url = filepath
# データベースに保存
title = request.form["title"]
comment = request.form["comment"]
# JSTタイムゾーンを作成
jst = timezone(timedelta(hours=9), 'JST')
date = datetime.now(jst)
content = WikiContent(title,image_url,comment,date)
db_session.add(content)
db_session.commit()
return redirect(url_for("blog.index"))
@app.route('/all_delete')
def all_delete(): #全削除
WikiContent.query.delete()
db_session.commit()
return redirect(url_for("blog.index"))
# top
#「/login」へアクセスがあった場合
@app.route("/login",methods=["post"])
def login():
# 入力された「ユーザー名」が既に存在するか確認
user_name = request.form["user_name"]
user = User.query.filter_by(user_name=user_name).first()
if user:
#「ユーザー名」が存在した場合
#「パスワード」が一致するか確認(「key.SALT」で暗号化)
password = request.form["password"]
hashed_password = sha256((user_name + password + key.SALT).encode("utf-8")).hexdigest()
if user.hashed_password == hashed_password:
#「パスワード」が一致した場合「/blog/index」ページへ移動
session["user_name"] = user_name
return redirect(url_for("blog.index"))
else:
#「パスワード」が一致しない場合「/blog/top」ページへ移動
return redirect(url_for("blog.top",status="wrong_password"))
else:
#「ユーザー名」が存在しなかった場合「/blog/top」ページへ移動
return redirect(url_for("blog.top",status="user_notfound"))
#「/registar」へアクセスがあった場合
# 新規登録画面で「新規登録」ボタンが押された時
@app.route("/registar",methods=["post"])
def registar():
# 入力された「ユーザー名」が既に存在するか確認
user_name = request.form["user_name"]
user = User.query.filter_by(user_name=user_name).first()
if user:
#「ユーザー名」が存在した場合「/blog/newcomer」ページへ移動
return redirect(url_for("blog.newcomer",status="exist_user"))
else:
#「ユーザー名」が存在しなかった場合
#「ユーザー名」「パスワード」(「key.SALT」で暗号化)を
password = request.form["password"]
hashed_password = sha256((user_name + password + key.SALT).encode("utf-8")).hexdigest()
user = User(user_name, hashed_password)
# データベースに追加して保存
db_session.add(user)
db_session.commit()
session["user_name"] = user_name
#「/blog/index」ページへ移動
return redirect(url_for("blog.index"))
#「/top」へアクセスがあった場合「blog/top.html」を返す
@app.route("/top")
def top():
status = request.args.get("status")
return render_template("blog/top.html",status=status)
#「/newcomer」へアクセスがあった場合「blog/newcomer.html」を返す
@app.route("/newcomer")
def newcomer():
status = request.args.get("status")
return render_template("blog/newcomer.html",status=status)
まとめ
http://startpython.pythonanywhere.com/blog/
こちらから実際に操作できますのでお試しください。(ユーザー名・パスワードは普段お使いのもの以外にしてください。セキュリティーは保証できません)
ユーザー名とパスワードを何も入れずにログインするとゲストでログインできます。
次回は編集と削除の機能を付けたいと思います。
↓よかったらポチッとしていってください。