Start_python’s diary

ふたり暮らし

アラフィフ夫婦のフリーランスプラン

FlaskでWebアプリが完成しました!(全ソースコードはこちらです)

画像1

 

はじめに

Webアプリが完成したので全ソースコードを載せることにしました。

こちらから実際に操作できますのでお試しください。

http://startpython.pythonanywhere.com/blog/

ユーザー名とパスワードを何も入れずにログインするとゲストでログインできます。

※ユーザー名・パスワードは普段お使いのもの以外にしてください。セキュリティーは保証できません。

 

関連記事

 

動作環境

Windows10
Python 3.7.5
Flask 1.1.1

作業フォルダ/
 ├ blog/
 │  ├ templates/
 │  │  └ blog/
 │  │      ├ base.html
 │ │ ├ diary.html │ │ ├ edit.html │ │ ├ index.html │ │ ├ newcomoer.html │ │ └ top.html │ ├ models/ │ │ ├ __init__.py │ │ ├ database.py │ │ ├ models.py │ │ └ wiki.db │ └ server.py ├ static/ │ ├ images/ │ │ └ sample.jpg │ ├ back_image.jpg │ ├ base.css
 │ ├ diary.css
 │ ├ diary.js │ ├ edit.css │ ├ edit.js │ ├ image.jpg │ ├ index.css │ ├ login.css │ ├ login.js │ ├ newcomer.css │ └ newcomer.js ├ templates/ │ └ index.html ├ key.py └ main.py

 

ソースコード

「作業フォルダ/blog/templates/blog/」

base.html

<!DOCTYPE html>
<meta name="viewport" content="width=device-width,initial-scale=1">

<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 id="search1">
        <form action="/blog/search" method="post">
            <input id="search2" type="text" name="search">
            <input class="btn2" type="submit" value="検  索">
        </form>
        </div>

        <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><a href="/blog">ホーム</a></li>
            {% if name == "レイルーク" %}
            <li><a href="/blog/all_delete">全削除</a></li>
            {% else %}
            <li>

            <span onclick="document.getElementById('search1').style.display = 'block'; document.getElementById('search2').focus();">
            検 索</span>

            </li>
            {% endif %}
            <li><a href="/blog/create">新規作成</a></li>
            <li><a href="/blog/logout">ログアウト</a></li>
        </ul>
        <span id="c">(c)2020 ふたり暮らし</span>
    </footer>
</body>

 

diary.html

{% extends "blog/base.html" %}
{% block body %}

<link rel="stylesheet" type="text/css" href="/static/diary.css">
<script src="/static/diary.js"></script>


<title>更新画面</title>

<form action="/blog/diaryupdate" method="post" enctype="multipart/form-data">

    <div id="main">
        <input id="ID" type="text" name="ID" value="{{diary.id}}">
        <label class="label">タイトル</label>
        <input id="text1" type="text" name="title" value="{{diary.title}}">
        <br><br>
        <div id=image_box>
            {% if name == diary.user %}
            <label class="btn1" for="file-sample">ファイルを選択</label>
            {% else %}
            <label class="btn0">ファイルを選択</label>
            {% endif %}
            <input id="file-sample" type="file" name="image" value="{{diary.image_url}}">
            <img id="file-preview" src="{{diary.image_url}}">
        </div>
        <label class="label">コメント</label>
        <textarea rows="12" cols="100" name="comment">{{diary.comment}}</textarea>
        <div class="time">{{diary.date.strftime("%Y/%m/%d %H:%M")}}</div>

        {% if name == diary.user or name == "レイルーク" %}
        <input class="btn1" type="submit" value="  更  新  ">
        <button class="btn1" id="btn2" type="button" onclick="location.href='/blog/delete/{{diary.id}}'">  削  除  </button>
        {% endif %}
    </div>

</form>

{% endblock %}

 

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 class="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 class="label">コメント</label>
        <textarea rows="12" cols="100" name="comment"></textarea>
        <div class="time"></div>
        <input class="btn1" type="submit" value="  登  録  ">
    </div>

</form>

{% endblock %}

 

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] %}
        {% if search == "" or search in diary.user 
            or search in diary.title or search in diary.comment %}

    <form action="/blog/diary/{{diary.id}}">
        <label id="menu" for="{{diary.id}}">

    <div class="table">
        {% if diary.user != "" %}
        <h3>{{diary.title}} @{{diary.user}}</h3>
        {% else %}
        <h3>{{diary.title}}</h3>
        {% endif %}
        <p><img src={{diary.image_url}}></p>
        <p>{{diary.comment}}</p>
        <div class="time">{{diary.date.strftime("%Y/%m/%d %H:%M")}}</div>
    </div>

        </label>
        <input type="submit" id="{{diary.id}}" class="update">
    </form>

        {% endif %}
    {% endfor %}
</div>

{% endblock %}

 

newcomoer.html

<!DOCTYPE html>
<meta name="viewport" content="width=device-width,initial-scale=1">

<link rel="stylesheet" type="text/css" href="/static/newcomer.css">
<script src="/static/newcomer.js"></script>

<title>新規登録画面</title>

<div class="frame">
    <img src="/static/image.jpg">
    <h2>アカウントを作成</h2>
    {% if status == "exist_user" %}
    <p>そのユーザーは既に登録されています。</p>
    {% elif status == None %}
    <p>ユーザ名を入力してください。</p>
    {% endif %}
    <form action="/blog/registar" method="post">
        <div class="box1">
            <label for="text1" id="l_text1">名前</label>
            <input id="text1" type="text" name="user_name" placeholder="user name">
        </div>
        <div class="box1">
            <label for="text2" id="l_text2">パスワード</label>
            <input id="text2" type="password" name="password" placeholder="password">
        </div>
        <p>
            アカウントを作成すると、利用規約、およびCookieの使用を含むプライバシーポリシーに同意したことになります。
        </p>
        <button class="btn1" type="submit">登録する</button>
    </form>
    <p>
      <br>
      <a href="/blog/top">ログイン画面に戻る</a>
    </p>
</div>

 

top.html

<!DOCTYPE html>
<meta name="viewport" content="width=device-width,initial-scale=1">

<link rel="stylesheet" type="text/css" href="/static/login.css">
<script src="/static/login.js"></script>

<title>ログイン画面</title>

<div class="frame">
  <div class="box0">
    <img src="/static/image.jpg">
    <h3>ふたり暮らしにログイン</h3>

    {% if status == "user_notfound" %}
    <p>ユーザーが見つかりません。アカウント作成してください</p>
    {% elif status == "wrong_password" %}
    <p>パスワードが間違っています</p>
    {% elif status == "logout" %}
    <p>ログアウト完了</p>
    {% endif %}
  </div>
  <form action="/blog/login" method="post">
    <div class="box1">
      <label for="text1" id="l_text1">ユーザー名</label>
      <input id="text1" type="text" name="user_name" placeholder="user name">
    </div>
    <div class="box1">
      <label for="text2" id="l_text2">パスワード</label>
      <input id="text2" type="password" name="password" placeholder="password">
    </div>
    <button class="btn1" type="submit">ログイン</button>
  </form>

  <p>
    <br>
    <a href="/blog/newcomer">パスワードをお忘れですか?</a>
 
    <a href="/blog/newcomer">アカウント作成</a>
  </p>
</div>

 

「作業フォルダ/blog/models/」

__init__.py

 

 

database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import os

#パスを定義してwiki.dbを作成します。
databese_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'wiki.db')
engine = create_engine('sqlite:///' + databese_file, convert_unicode=True)

#データベースにアクセスするためにセッションを作成します。
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
#Baseオブジェクトを作った後、query_property()を使ってBaseオブジェクトに検索クエリを持たせます。
#後でデータベースから検索する時に楽らしい。
Base = declarative_base()
Base.query = db_session.query_property()

#データベースの初期化用です。初期化したいときに呼びます。
def init_db():
    import models.models
    Base.metadata.create_all(bind=engine)

 

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)
    user = Column(String(128))
    title = Column(String(128))
    comment = Column(Text)
    image_url = Column(String(128))
    date = Column(DateTime, default=datetime.now())
 
    def __init__(self, user=None, title=None, image_url=None, comment=None, date=None):
        self.user = user
        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)

 

「作業フォルダ/blog/」

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()
        search = ""
        return render_template('blog/index.html',name=name, all_diary=all_diary,search=search)
    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():  #新規作成
    name = session["user_name"]
    return render_template('blog/edit.html',name=name)


#「/diary/<ID>」へアクセスがあった場合「blog/diary.html」を返す
@app.route('/diary/<ID>')
def diary(ID):  #参照
    name = session["user_name"]
    diary = WikiContent.query.get(ID)
    return render_template('blog/diary.html',name=name,diary=diary)


#「/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
    # データベースに保存
    name = name = session["user_name"]
    title = request.form["title"]
    comment = request.form["comment"]
    # JSTタイムゾーンを作成
    jst = timezone(timedelta(hours=9), 'JST')
    date = datetime.now(jst)
    content = WikiContent(name,title,image_url,comment,date)
    db_session.add(content)
    db_session.commit()
    return redirect(url_for("blog.index"))



#「/diaryupdate」へアクセスがあった場合「/blog/index」ページへ移動
@app.route('/diaryupdate', methods=['post'])
def diaryupdate():  #更新
    content = WikiContent.query.get(request.form.get("ID"))
    # 画像ファイルを更新
    if request.files['image']:
        f = request.files['image']
        filepath = 'static/images/' + secure_filename(f.filename)
        f.save(filepath)
        filepath = '/' + filepath
        content.image_url = filepath
    # データベースを更新
    content.title = request.form["title"]
    content.comment = request.form["comment"]
    db_session.commit()
    return redirect(url_for("blog.index"))


#「/delete/<ID>」へアクセスがあった場合「/blog/index」ページへ移動
@app.route('/delete/<ID>')
def delete(ID):  #削除
    content = WikiContent.query.get(ID)
    db_session.delete(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"))


#「/search」へアクセスがあった場合「blog/index.html」を返す
@app.route('/search', methods=['post'])
def search():  # 検索
    name = session["user_name"]
    all_diary= WikiContent.query.all()
    search = request.form["search"]
    return render_template('blog/index.html',name=name, all_diary=all_diary,search=search)


# 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)

 

「作業フォルダ/static/」

base.css

header{
    position: fixed; /*絶対位置で固定*/
    top: 0px; /*上に配置*/
    left: 0px; /*左の隙間をなくす*/
    padding: 0px 30px; /*範囲内の余白(上下、左右)*/
    width: 100%; /*幅*/
    height: 60px; /*高さ*/
    background-image: url("back_image.jpg");
    /*画像を背景に指定*/
}

#user_name{
    position: absolute; /*絶対位置で表示*/
    margin: 20px 0; /*範囲外の余白*/
}

#search1{
    display: none; /*非表示にする*/
    position: absolute; /*絶対位置で表示*/
    left: 0;
    right: 0;
    width: 90%; /*幅*/
    margin: 10px auto; /*中央寄せ*/
    max-width: 700px; /*最大の幅*/
}

#search2{
    padding: 8px; /*範囲内の余白*/
    width: calc(100% - 200px); /*幅*/
    font-size: 18px; /*文字の大きさ*/
    background-color: white; /*背景色*/
}

.btn2{
    padding: 5px 30px; /*余白*/
    color:#fff; /*文字の色*/
    font-size: 16px; /*文字のサイズ*/
    text-align: center; /*文字の位置*/
    background:#1B73BA; /*背景色*/
    border-radius: 10px; /*角の半径*/
    cursor: pointer; /*カーソル(指のかたち)*/
}

.btn2:hover{
    opacity: 0.8; /*透明度*/
}

.menu_box{
    margin: 30px 60px 0 calc(100% - 180px); /*範囲外の余白*/
    text-align: right; /*右に寄せる*/
}

#toggle{
    display: none; /*非表示にする*/
}

.dropdown_menu{
    display: none; /*非表示にする*/
}

/* .menu_box:hover .dropdown_menu{ */
#toggle:checked+.dropdown_menu{
    display: block; /*リストを表示する*/
}

.dropdown_menu li{
    display: block; /*要素をインライン表示にする*/
    width: 120px; /*幅*/
    text-align: center; /*中央に表示する*/
    background-color: white; /*背景色*/
    border: outset 2px; /*外枠を作る(スタイル、太さ)*/
    cursor: pointer; /*カーソル(指のかたち)*/
}

footer{
    position: fixed; /*絶対位置で固定*/
    bottom: 0px; /*下に配置*/
    left: 0px; /*左の隙間をなくす*/
    width: 100%; /*幅*/
    height: 60px; /*高さ*/
    font-weight: bold; /*文字を太字にする*/
    text-align: center; /*中央に表示する*/
    background-image: url("back_image.jpg");
    /*画像を背景に指定*/
}

ul{
    margin: 0; /*上の余白をなくすため*/
    padding: 5px 0; /*左の余白をなくすため*/
}

li{
    display: inline-block; /*要素をインライン表示にする*/
    width: 120px; /*幅*/
    text-align: center; /*中央に表示する*/
    border: outset 2px; /*外枠を作る(スタイル、太さ)*/
    cursor: pointer; /*カーソル(指のかたち)*/
}

a {
    color: #000; /*文字の色*/
    text-decoration: none; /*下線を消す*/
}

@media screen and (max-width: 600px) {
    li{
        width: 80px; /*スマホなどの時のボタン幅*/
    }
}

#c{
    position: absolute; /*絶対位置(rightのために必要)*/
    right: 30px; /*右に配置*/

 

diary.css

header{
    position: fixed; /*絶対位置で固定*/
    top: 0px; /*上に配置*/
    left: 0px; /*左の隙間をなくす*/
    padding:0px 30px; /*範囲内の余白(上下、左右)*/
    width: 100%; /*幅*/
    height: 60px; /*高さ*/
    background-image: url("back_image.jpg");
    /*画像を背景に指定*/
}

#main{
    margin: auto; /*中央寄せ*/
    padding:60px 20px; /*範囲内の余白(上下、左右)*/
    width: 90%; /*幅*/
    max-width: 800px; /*最大の幅*/
}

#ID{
    display: none; /*非表示にする*/
}

.label{
    font-size: 24px; /*文字のサイズ*/
}

#text1{
    padding: 8px; /*範囲内の余白*/
    width: 100%; /*幅*/
    font-size: 18px; /*文字の大きさ*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
}

textarea{
    padding: 10px; /*範囲内の余白*/
    width: calc(100% - 300px); /*幅*/
    font-size: 17px; /*文字の大きさ*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
    resize: vertical;
}

#image_box{
    float: right; /*右に寄せる*/
    width: 300px;
}

#file-sample {
     display: none; 
}

.btn1{
    display: inline-block;
    padding: 5px 30px; /*余白*/
    color:#fff; /*文字の色*/
    font-size: 16px; /*文字のサイズ*/
    text-align: center; /*文字の位置*/
    background:#1B73BA; /*背景色*/
    border-radius: 10px; /*角の半径*/
    cursor: pointer; /*カーソル(指のかたち)*/
}

.btn1:hover{
    opacity: 0.8; /*透明度*/
}

.btn0{
    display: inline-block;
    padding: 5px 30px; /*余白*/
    font-size: 16px; /*文字のサイズ*/
    opacity: 0; /*透明*/
}

#btn2{
    background:red; /*背景色*/
    float: right; /*右に寄せる*/
}

img{
    margin: 2px 0;
    width: 100%;
}

@media screen and (max-width: 600px) {
    #image_box{
        width: 100%; /*スマホなどの時の画像サイズ*/
    }
    textarea{
        width: 100%;
    }
}

.time{
    clear: both; /*「float」を解除*/
}

footer{
    position: fixed; /*絶対位置で固定*/
    bottom: 0px; /*下に配置*/
    left: 0px; /*左の隙間をなくす*/
    width: 100%; /*幅*/
    height: 60px; /*高さ*/
    font-weight: bold; /*文字を太字にする*/
    text-align: center; /*中央に表示する*/
    background-image: url("back_image.jpg");
    /*画像を背景に指定*/
}

ul.footer-menu li {
    display: inline; /*一行で表示する*/
}

#copy{
    position: absolute; /*絶対位置(rightのために必要)*/
    right: 30px; /*右に配置*/
}

 

diary.js

window.onload = function () {
    document.getElementById('file-sample').addEventListener('change', function (e) {
        var file = e.target.files[0];

        var blobUrl = window.URL.createObjectURL(file);

        var img = document.getElementById('file-preview');
        img.src = blobUrl;
    });
}

 

edit.css

header{
    position: fixed; /*絶対位置で固定*/
    top: 0px; /*上に配置*/
    left: 0px; /*左の隙間をなくす*/
    padding:0px 30px; /*範囲内の余白(上下、左右)*/
    width: 100%; /*幅*/
    height: 60px; /*高さ*/
    background-image: url("back_image.jpg");
    /*画像を背景に指定*/
}

#main{
    margin: auto; /*中央寄せ*/
    padding:60px 20px; /*範囲内の余白(上下、左右)*/
    width: 90%; /*幅*/
    max-width: 800px; /*最大の幅*/
}

.label{
    font-size: 24px; /*文字のサイズ*/
}

#text1{
    padding: 8px; /*範囲内の余白*/
    width: 100%; /*幅*/
    font-size: 18px; /*文字の大きさ*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
}

textarea{
    padding: 10px; /*範囲内の余白*/
    width: calc(100% - 300px); /*幅*/
    font-size: 17px; /*文字の大きさ*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
    resize: vertical;
}

#image_box{
    float: right; /*右に寄せる*/
    width: 300px;
}

#file-sample {
     display: none; 
}

.btn1{
    display: block;
    padding: 5px 30px; /*余白*/
    color:#fff; /*文字の色*/
    font-size: 16px; /*文字のサイズ*/
    text-align: center; /*文字の位置*/
    background:#1B73BA; /*背景色*/
    border-radius: 10px; /*角の半径*/
    cursor: pointer; /*カーソル(指のかたち)*/
}

.btn1:hover{
    opacity: 0.8; /*透明度*/
}

img{
    margin: 2px 0;
    width: 100%;
}

@media screen and (max-width: 600px) {
    #image_box{
        width: 100%; /*スマホなどの時の画像サイズ*/
    }
    textarea{
        width: 100%;
    }
}

.time{
    clear: both; /*「float」を解除*/
}

footer{
    position: fixed; /*絶対位置で固定*/
    bottom: 0px; /*下に配置*/
    left: 0px; /*左の隙間をなくす*/
    width: 100%; /*幅*/
    height: 60px; /*高さ*/
    font-weight: bold; /*文字を太字にする*/
    text-align: center; /*中央に表示する*/
    background-image: url("back_image.jpg");
    /*画像を背景に指定*/
}

ul.footer-menu li {
    display: inline; /*一行で表示する*/
}

#copy{
    position: absolute; /*絶対位置(rightのために必要)*/
    right: 30px; /*右に配置*/
}

 

edit.js

window.onload = function () {
    document.getElementById('file-sample').addEventListener('change', function (e) {
        var file = e.target.files[0];

        var blobUrl = window.URL.createObjectURL(file);

        var img = document.getElementById('file-preview');
        img.src = blobUrl;
    });
}

 

index.css

#main{
    width: 100%; /*幅*/
    margin: auto; /*中央寄せ*/
    max-width: 800px; /*最大の幅*/
    padding:60px 0; /*範囲内の余白(上下、左右)*/
}

.table{
    border: outset 2px; /*外枠を作る(スタイル、太さ)*/
    padding:10px 20px; /*範囲内の余白(上下、左右)*/
    margin: 10px 15px; /*範囲外の余白(上下、左右)*/
}

img{
    float: right; /*右に寄せる*/
    width: 300px; /*画像サイズを固定*/
}

@media screen and (max-width: 600px) {
    img{
        width: 100%; /*スマホなどの時の画像サイズ*/
    }
}

.time{
    clear: both; /*「float」を解除*/
}

.update{
    display: none; /*非表示にする*/
}

 

login.css

@media (max-width: 600px) {
    p {
        color: red;
        font-size: 80%;
    }
}

.frame{
    width: 90%; /*幅*/
    margin: 30px auto; /*上の位置と中央寄せ*/
    max-width: 600px; /*最大の幅*/
}

img{
    width: 80px; /*幅*/
    height: auto; /*縦横比を維持する高さを自動計算*/
}

.box0{
    text-align: center; /*中央寄せ*/
}

.box1{
    width: 100%; /*幅*/
    height: 60px; /*高さ*/
    margin: 15px 0px; /*領域外のスペース*/
    background-color: #eee; /*背景色*/
    border-bottom: 2px solid #c2c2c2; /*text1の下線*/
}

#l_text1{
    display: block; /*要素の表示形式*/
    font-size: 14px; /*文字の大きさ*/
    padding: 5px 10px; /*領域内のスペース*/
}

#text1{
    font-size: 18px; /*文字の大きさ*/
    padding: 0 0 7px 10px; /*ボックスを大きくする*/
    width: 100%; /*幅*/
    background-color: #eee; /*背景色*/
    border: none; /*外枠の線*/
    outline: none; /*外枠の線*/
    border-bottom: 2px solid #c2c2c2; /*text1の下線*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
}

#text1:focus{
    border-bottom: 2px solid #1B73BA; /*下線の太さと色*/
}

#l_text2{
    display: block; /*要素の表示形式*/
    font-size: 13px; /*文字の大きさ*/
    padding: 5px 10px; /*領域内のスペース*/
}

#text2{
    font-size: 18px; /*文字の大きさ*/
    padding: 2px 0 7px 10px; /*ボックスを大きくする*/
    width: 100%; /*幅*/
    background-color: #eee; /*背景色*/
    border: none; /*外枠の線*/
    outline: none; /*外枠の線*/
    border-bottom: 2px solid #c2c2c2; /*text1の下線*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
}

#text2:focus{
    border-bottom: 2px solid #1B73BA; /*下線の太さと色*/
}

.btn1{
    position:relative; /*相対位置(topのために必要)*/
    width:100%; /*幅*/
    height:50px; /*高さ*/
    top: 10px; /*上の位置*/
    color:#fff; /*文字の色*/
    border:none; /*外枠*/
    -webkit-border-radius: 25px;
       -moz-border-radius: 25px;
            border-radius: 25px; /*角の半径*/
    background:#1B73BA; /*背景色*/
    display: block; /*要素の表示形式*/
    -webkit-appearance: none;
    outline: 0; /*外枠(押した後)*/
    cursor: pointer; /*カーソル(指のかたち)*/
}
.btn1:hover{
    opacity: 0.8; /*透明度*/
}

a {
    text-decoration: none; /*下線を消す*/
}
a:hover{
    text-decoration: underline; /*下線を引く*/
}

p {
    text-align: center; /*中央寄せ*/
}

 

login.js

window.onload = function () {
  document.getElementById( "text1" ).onfocus = function(){
    document.getElementById("l_text1").style.color = '#1B73BA';
  };
  document.getElementById( "text1" ).onblur = function(){
    document.getElementById("l_text1").style.color = '';
  };

  document.getElementById( "text2" ).onfocus = function(){
    document.getElementById("l_text2").style.color = '#1B73BA';
  };
  document.getElementById( "text2" ).onblur = function(){
    document.getElementById("l_text2").style.color = '';
  };
}

 

newcomer.css

@media screen and (max-width: 600px) {
    p {
        font-size: 80%;
    }
}

.frame{
    width: 90%; /*幅*/
    margin: 30px auto; /*上の位置と中央寄せ*/
    max-width: 600px; /*最大の幅*/
}

img{
    display: block; /*要素の表示形式*/
    margin: auto; /*中央寄せ*/
    width: 40px; /*幅*/
    height: auto; /*縦横比を維持する高さを自動計算*/
}

.box1{
    width: 100%; /*幅*/
    height: 60px; /*高さ*/
    margin: 15px 0px; /*領域外のスペース*/
    background-color: #eee; /*背景色*/
    border-bottom: 2px solid #c2c2c2; /*text1の下線*/
}

#l_text1{
    display: block; /*要素の表示形式*/
    font-size: 14px; /*文字の大きさ*/
    padding: 5px 10px; /*領域内のスペース*/
}

#text1{
    font-size: 18px; /*文字の大きさ*/
    padding: 0 0 7px 10px; /*ボックスを大きくする*/
    width: 100%; /*幅*/
    background-color: #eee; /*背景色*/
    border: none; /*外枠の線*/
    outline: none; /*外枠の線*/
    border-bottom: 2px solid #c2c2c2; /*text1の下線*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
}

#text1:focus{
    border-bottom: 2px solid #1B73BA; /*下線の太さと色*/
}

#l_text2{
    display: block; /*要素の表示形式*/
    font-size: 13px; /*文字の大きさ*/
    padding: 5px 10px; /*領域内のスペース*/
}

#text2{
    font-size: 18px; /*文字の大きさ*/
    padding: 2px 0 7px 10px; /*ボックスを大きくする*/
    width: 100%; /*幅*/
    background-color: #eee; /*背景色*/
    border: none; /*外枠の線*/
    outline: none; /*外枠の線*/
    border-bottom: 2px solid #c2c2c2; /*text1の下線*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
}

#text2:focus{
    border-bottom: 2px solid #1B73BA; /*下線の太さと色*/
}

.btn1{
    position:relative; /*相対位置(topのために必要)*/
    width:100%; /*幅*/
    height:50px; /*高さ*/
    top: 10px; /*上の位置*/
    color:#fff; /*文字の色*/
    border:none; /*外枠*/
    -webkit-border-radius: 25px;
       -moz-border-radius: 25px;
            border-radius: 25px; /*角の半径*/
    background:#1B73BA; /*背景色*/
    display: block; /*要素の表示形式*/
    -webkit-appearance: none;
    outline: 0; /*外枠(押した後)*/
    cursor: pointer; /*カーソル(指のかたち)*/
}

.btn1:hover{
    opacity: 0.8; /*透明度*/
}

a {
    text-decoration: none; /*下線を消す*/
}
a:hover{
    text-decoration: underline; /*下線を引く*/
}

 

newcomer.js

window.onload = function () {
  document.getElementById( "text1" ).onfocus = function(){
    document.getElementById("l_text1").style.color = '#1B73BA';
  };
  document.getElementById( "text1" ).onblur = function(){
    document.getElementById("l_text1").style.color = '';
  };

  document.getElementById( "text2" ).onfocus = function(){
    document.getElementById("l_text2").style.color = '#1B73BA';
  };
  document.getElementById( "text2" ).onblur = function(){
    document.getElementById("l_text2").style.color = '';
  };
}

 

「作業フォルダ/templates/」

index.html

<!DOCTYPE html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>ふたり暮らし メニュー画面</title>
  </head>
  <body>

    <h2>メインメニュー</h2>
    <p><a href= "/blog" >ブログ(登録とログイン)</a></p>

  </body>
</html>

 

「作業フォルダ/」

key.py

SECRET_KEY = "内緒:好きなランダムの英数字"
SALT = "内緒:好きなランダムの英数字"

 

main.py

# Flaskとrender_template(HTMLを表示させるための関数)をインポート
from flask import Flask, render_template

# Flaskオブジェクトの生成
app = Flask(__name__)

# セキュリティーキーの設定(なりすましなどを防ぐため)
import key
app.secret_key = key.SECRET_KEY

# 前回作った基本的なWebページ(今回は使いません)
#from test.test_app import app1
#app.register_blueprint(app1)

# 今回使用するWebアプリ
from blog.server import app as app2
app.register_blueprint(app2, url_prefix="/blog")


#「/」へアクセスがあった場合に「index.html」を返す
@app.route('/')
def index():
    return render_template('index.html')


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

 

まとめ

以上です。お疲れ様でした。すごく長くなりました。

ここまで見てくれている人はどれくらいいるのでしょうか。これでゼロから始める人も掲示板アプリを作成することができると思います。ここのソースコードはこのままコピペして使っていただいても構いません。著作権フリーです。

少しでもこれからWebアプリを作られる方のお役に立てればうれしいです。

 

 

↓よかったらポチッとしていってください。

にほんブログ村 IT技術ブログ Pythonへ
にほんブログ村