☆☆ 新着記事 ☆☆

2019年8月10日土曜日

1つのアプリファイルからDB関連だけを分割して記述する

このファイルを分割してみる。


#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os
from flask import Flask, render_template,url_for,flash, redirect, request
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, Length
from flask_migrate import Migrate

app = Flask(__name__)
project_dir = os.path.dirname(os.path.abspath(__file__))
database_file = "sqlite:///{}".format(os.path.join(project_dir, "msg.db"))
app.config["SQLALCHEMY_DATABASE_URI"] = database_file
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] ='you-will-never-know'
db = SQLAlchemy(app)
db.init_app(app)
migrate = Migrate(app, db)

#DB Model
class Tw_msg (db.Model):
     __tablename__ = 'tw_msg'
     id = db.Column(db.Integer, unique=True, primary_key=True)
     time = db.Column(db.String(6))
     message = db.Column(db.String(140))
     hashtag =db.Column(db.String(140))
     url = db.Column(db.String(140))
     length = db.Column(db.String(10))
     conditions = db.Column(db.String(5))
#WTF Model(入力フィールド定義)
class Message_Add(FlaskForm):
    
     id = StringField('ID',validators=[DataRequired(), Length(min=1, max=3) ])
     time = StringField('Time',
                        validators=[DataRequired(), Length(min=2, max=60) ])
     message = StringField('Message')
     hashtag = StringField('Hashtag')
     url = StringField('URL')
     length = StringField('Length')
     conditions = StringField('Conditions')

     submit = SubmitField('Submit')

#ルーティングと処理
@app.route('/',methods=['GET', 'POST'])
def message():
     db.create_all()
     form = Message_Add()
     #新規データ受付時処理
     try:
          if form.validate_on_submit():
               Char_len = len(form.message.data+form.hashtag.data)
      
               message = Tw_msg( time = form.time.data,  message = form.message.data ,
                                 hashtag = form.hashtag.data, url = form.url.data,
                                 length=Char_len, conditions=form.conditions.data)
               db.session.add(message)
               db.session.commit()
               flash('New message has been created!', 'success')
               return redirect(url_for('message'))
     except:
          flash('Failed at somewhere!', 'Unsuccess')

     #登録データ一覧表示    
     messages = Tw_msg.query.all()
     return render_template("message.html",form=form, messages=messages)

@app.route("/<int:message_id>/edit", methods=['GET', "POST"])
def edit_message(message_id):
     msg = Tw_msg.query.get(message_id)
     form = Message_Add()
     if form.validate_on_submit():
          Char_len = len(form.message.data+form.hashtag.data)
          print(Char_len)
 
          msg.id = form.id.data
          msg.time = form.time.data
          msg.message = form.message.data
          msg.hashtag = form.hashtag.data
          msg.url = form.url.data
          msg.length = Char_len
          msg.conditions = form.conditions.data
         
          db.session.commit()
          flash('Your post has been updated!', 'success')
          return redirect(url_for('message'))
     elif request.method == 'GET':
          form.id.data = msg.id
          form.time.data = msg.time
          form.message.data = msg.message
          form.hashtag.data = msg.hashtag
          form.url.data = msg.url
          form.url.data = msg.length
          form.conditions.data = msg.conditions
      
     return render_template("edit_message.html",form=form)

@app.route("/<int:message_id>/delete", methods=['GET', "POST"])
def delete_message(message_id):
    
    msg = Tw_msg.query.get_or_404(message_id)
    db.session.delete(msg)
    db.session.commit()
    flash('Your post has been deleted!', 'success')
    return redirect(url_for('message'))

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




1. Step1

admin.pyを作って、その配下にadminフォルダにを作って、__init__.pyを作って、上のアプリを__init__.pyに全部コピー。

admin.py

from admin import app

/admin/__init_.py

__init__.py
上のアプリを全コピー。

すると、DBファイルがこのディレクトリに作られる。

Step2. DB関連をmodel.pyに分割する。

model.py

from admin import db
#DB Model
class Tw_msg (db.Model):
     __tablename__ = 'tw_msg'
     id = db.Column(db.Integer, unique=True, primary_key=True)
     time = db.Column(db.String(6))
     message = db.Column(db.String(140))
     hashtag =db.Column(db.String(140))
     url = db.Column(db.String(140))
     length = db.Column(db.String(10))
     conditions = db.Column(db.String(5))

これでも動く。

DBの変更は、FLASK-Migrateで。


2019年8月8日木曜日

FLASK - MIGRATE


Flask_Migrateは、SQLAlchemyベースのDBをマイグレーションする為のFlask Extensionです。

Alembic is a database migration tool for SQLAlchemy.
https://flask-migrate.readthedocs.io/en/latest/

 ◇ Step 1 :
 pip install flask-migrate

  Step 2 : modify main.py
 import os
 from flask import Flask
 from flask_migrate import Migrate  // Imported Flask Migrate
 from api.db import db
 from api.config import config
 ......
 db.init_app(app)
 migrate = Migrate(app, db) // It will allow us to run migrations
 ......
 @app.before_first_request
 def create_tables():
     db.create_all()
 if __name__ == '__main__':
     app.run()


==以下はコマンドライン操作

  Step 3 :Creation of Migration Repository (Directory).

flask dbの変更記録を保存するレポジトリーを作成する。

> export FLASK_APP=run.py (Windowsは set FLASK_APP=run.py)
flask db init (<- これでレポジトリ用のフォルダが作成される)

 Flask-Migrate exposes its commands through the flask command. You have already seen flask run, which
 is a sub-command that is native to Flask. The flask db sub-command is added by Flask-Migrate to manage
 everything related to database migrations. So let's create the migration repository for microblog by
 running flask db init:

  Step 4 :(generate an initial migration:)
 flask db migrate -m "コメント"
-m ”コメント”は、file名に付加される任意情報。なくても良い。

(Alembicで認識できるDBの変更には制限がある。)
Alembic is currently unable to detect table name changes, column name changes, or anonymously named  constraints.

既にあるDBにFlask_Migrateを適用して後から管理しようとすると、この段階で
No changes in schema detected. 等のMessageがでる。

対応方法:How To Add Flask-Migrate To An Existing Project (2020/04/10)
https://youtu.be/IxCBjUapkWk



  Step 5 :
 flask db upgrade
 or
 flask db downgrade

 Because this application uses SQLite, the upgrade command will detect that a database does not exist
 and will create it (you will notice a file named app.db is added after this command finishes, that is
 the SQLite database). When working with database servers such as MySQL and PostgreSQL, you have to  create the database in the database server before running upgrade.
You also have a flask db downgrade command, which undoes the last migration.

Flask dbで使えるその他のオプション: flask db とタイプしてリターンすると表示される。

Commands:
  branches   Show current branch points
  current    Display the current revision for each database.
  downgrade  Revert to a previous version
  edit       Edit a revision file
  heads      Show current available heads in the script directory
  history    List changeset scripts in chronological order.
  init       Creates a new migration repository.
  merge      Merge two revisions together, creating a new revision file
  migrate    Autogenerate a new revision file (Alias for 'revision...
  revision   Create a new revision file.
  show       Show the revision denoted by the given symbol.
  stamp      'stamp' the revision table with the given revision; don't run...
  upgrade    Upgrade to a later version


ということでやってみる。

tw_msg.py
import os
from flask import Flask, render_template,url_for,flash, redirect, request
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, Length
from flask_migrate import Migrate

app = Flask(__name__)
project_dir = os.path.dirname(os.path.abspath(__file__))
database_file = "sqlite:///{}".format(os.path.join(project_dir, "msg.db"))
app.config["SQLALCHEMY_DATABASE_URI"] = database_file
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] ='you-will-never-know'

db = SQLAlchemy(app)
db.init_app(app)
migrate = Migrate(app, db)

#DB Model
class Tw_msg (db.Model):
     __tablename__ = 'tw_msg'
     id = db.Column(db.Integer, unique=True, primary_key=True)
     time = db.Column(db.String(6))
     message = db.Column(db.String(140))
     hashtag =db.Column(db.String(140))
     url = db.Column(db.String(140))

#WTF Model(入力フィールド定義)
class Message_Add(FlaskForm):
    
     id = StringField('ID',validators=[DataRequired(), Length(min=1, max=3) ])
     time = StringField('Time',
                        validators=[DataRequired(), Length(min=2, max=60) ])
     message = StringField('Message')
     hashtag = StringField('Hashtag')
     url = StringField('URL')

     submit = SubmitField('Submit')

#ルーティングと処理
@app.route('/',methods=['GET', 'POST'])
def message():
     # db.create_all()
    #db.create_all()は、windows環境ではあっても動作するが(Migrateが認識しない空白のオペレー
   ションが出来てしまうことになるが)、CentOS環境では動作しない。(DBは自動生成されない。)
  従って削除して、wtfで作成した方が良い。

   #テーブルを新しく追加する時も、既に既存のtableがある場合、エラーになる。


     form = Message_Add()
     #新規データ受付時処理
     try:
          if form.validate_on_submit():
      
               message = Tw_msg( time = form.time.data,  message = form.message.data ,
                                 hashtag = form.hashtag.data, url = form.url.data )
               db.session.add(message)
               db.session.commit()
               flash('New message has been created!', 'success')
               return redirect(url_for('message'))
     except:
          flash('Failed at somewhere!', 'Unsuccess')

     #登録データ一覧表示    
     messages = Tw_msg.query.all()
     return render_template("message.html",form=form, messages=messages)

@app.route("/<int:message_id>/edit", methods=['GET', "POST"])
def edit_message(message_id):
     msg = Tw_msg.query.get(message_id)
     form = Message_Add()
     if form.validate_on_submit():
          msg.id = form.id.data
          msg.time = form.time.data
          msg.message = form.message.data
          msg.hashtag = form.hashtag.data
          msg.url = form.url.data
          db.session.commit()
          flash('Your post has been updated!', 'success')
          return redirect(url_for('message'))
     elif request.method == 'GET':
          form.id.data = msg.id
          form.time.data = msg.time
          form.message.data = msg.message
          form.hashtag.data = msg.hashtag
          form.url.data = msg.url
         
     #return "I'm doin' fine."
     return render_template("edit_message.html",form=form)

@app.route("/<int:message_id>/delete", methods=['GET', "POST"])
def delete_message(message_id):
    
    msg = Tw_msg.query.get_or_404(message_id)
    db.session.delete(msg)
    db.session.commit()
    flash('Your post has been deleted!', 'success')
    return redirect(url_for('message'))


==さて、実行してみる。


C:\Users\[ユーザ名]\Desktop\top>set FLASK_APP=tw_msg.py
C:\Users\[ユーザ名]\Desktop\top>flask db init

Creating directory C:\Users\[ユーザ名]\Desktop\top\migrations ... done
Creating directory C:\Users\[ユーザ名]\Desktop\top\migrations\versions ... done
Generating C:\Users\[ユーザ名]\Desktop\top\migrations\alembic.ini ... done
Generating C:\Users\[ユーザ名]\Desktop\top\migrations\env.py ... done
Generating C:\Users\[ユーザ名]\Desktop\top\migrations\README ... done
Generating C:\Users\[ユーザ名]\Desktop\top\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'C:\\Users\\[ユーザ名]\\Desktop\\top\\migrations\\alembic.ini' bef
ore proceeding.

で、Current Directoryにmigrationsというフォルダができて、以下のようなファイルが格納されている。

次に、現状を確認
>>> from tw_msg import db
>>> from tw_msg import Tw_msg
>>> a = Tw_msg.query.all()
>>> a[0].__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x0000000004E9D940>, 'hashtag': '# ハッシュタグ', 'time': '12:20', 'url': 'https://mossymob.net', 'message': 'メッセージ1', 'id': 1}
 
こんな感じ。
 
#DB Model
class Tw_msg (db.Model):
     __tablename__ = 'tw_msg'
     id = db.Column(db.Integer, unique=True, primary_key=True)
     time = db.Column(db.String(6))
     message = db.Column(db.String(140))
     hashtag =db.Column(db.String(140))
     url = db.Column(db.String(140))
     length = db.Column(db.String(10))
 
と、1つコラムを追加。
 
>flask db migrate "add length column" "コメントをつけるとエラー。なしで実行。
 
(venv) C:\Users\Toshiro\Desktop\top>flask db migrate
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'tw_msg.length'
Generating C:\Users\Toshiro\Desktop\top\migrations\versions\9da06fe9a5aa_.py ... done
 
migrationsフォルダの中に\versionsフォルダができて
9da06fe9a5aa_.pyが作成された。
 
中身は、
"""empty message
Revision ID: 9da06fe9a5aa
Revises:
Create Date: 2019-08-08 01:43:33.173202
"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = '9da06fe9a5aa'
down_revision = None
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('tw_msg', sa.Column('length', sa.String(length=10), nullable=True))
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('tw_msg', 'length')
    # ### end Alembic commands ###
良さそうなので、
 
(venv) C:\Users\ユーザ名\Desktop\top> flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 9da06fe9a5aa, empty message
 
>>> from tw_msg import db
>>> from tw_msg import Tw_msg
>>> a= Tw_msg.query.all()
>>> a[0].__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x0000000004ABAA58>, 'length': None, 'hashtag': '#
ハッシュタグ', 'time': '12:20', 'url': 'https://mossymob.net', 'message': 'メッセージ1', 'id': 1}
 
ということで、既存のデータを変更せずに、コラムの追加に成功した。
 



(( トラブル対応))
migrationsフォルダーをdeleteしても、upgrade/downgradeが出来ない時がある。


alembic_version という名前のテーブルが残ってしまって、バージョン管理に齟齬が出てしまっているから。 layerが違うらしうく、通常のテーブル検索では表示されないが、普通のテーブルの消去の仕方でテーブルを消せる。

dropTableStatement = "DROP TABLE alembic_version"
cursor.execute(dropTableStatement)

等、

baseに戻ってみるのもあるかも。
from flask_migrate import downgrade
downgrade(revision='base')

自分の事象は、これでは上手くいかなかったけど。

(追記)
あれ、alembic tableを取得できる場合もあるね。 混乱中。

>>> cursor.execute("select * from sqlite_master where type='table'")
<sqlite3.Cursor object at 0x7f6a1f5e01f0>
>>> for x in cursor.fetchall():
...     print(x)
...
('table', 'alembic_version', 'alembic_version', 2, 'CREATE TABLE alembic_version (\n\tversion_num VARCHAR(32) NOT NULL, \n\tCONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)\n)')

2019年8月3日土曜日

FLASK-WTF でセキュリティー対策をして、html経由でデータベースを操作する。(CRUDサンプルコード)

FLASKを使ったhtmlで、データベースのCRUD(Create, Read, Update, Delete)をする。

html経由なので、セキュリティー対策として、FLask-WTFormsを使います。

データベース操作は、FLASK-SQLAlchemyで記述していますが、他のDB言語にでも良いです。(SQLを直接でも、SQLALchemyなどのORMでも、ご自由にどうぞ)

尚、FLASKのアプリファイルは一括して1つのファイルにアプリファイルに書いています。分かり易いので。 

今回のTIPは、データ内容を更新する場合フィールドに、最初から修正対象のデータがインプット(プレ・ポピュレイティド)されているようにします。

ある種のplaceholderの値を動的に作成する感じです。 <submit>ではなく、<a href>を使うのがポイントです。

【参考】
もっと詳しく、各記述の理解をしたい場合、

Flask (1) WTForms でHTMLの入力フォームのセキュリティー対策

も、参考にしてみてください。 



基本の構成

<1.   データベースへの新規追加と一覧表示>


(ファイル構成)
--- tw_msg.py
 |---templates/
   |---message.html


◇tw.message.py


(tw_msg.py)

import os
from flask import Flask, flash,render_template, request,url_for, redirect
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, Length

app = Flask(__name__)
project_dir = os.path.dirname(os.path.abspath(__file__))
database_file = "sqlite:///{}".format(os.path.join(project_dir, "msg.db"))
app.config["SQLALCHEMY_DATABASE_URI"] = database_file
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] ='you-will-never-know'
db = SQLAlchemy(app)


#DB Model
class Tw_msg (db.Model):
     __tablename__ = 'tw_msg'
     id = db.Column(db.Integer, unique=True, primary_key=True)
     time = db.Column(db.String(6))
     message = db.Column(db.String(140))
     hashtag =db.Column(db.String(140))
     url = db.Column(db.String(140))

#WTF Model(入力フィールド定義)
class Message_Add(FlaskForm):
    
     id = StringField('ID')
     time = StringField('Time',
                        validators=[DataRequired(), Length(min=2, max=60)])
     message = StringField('Message')
     hashtag = StringField('Hashtag')
     url = StringField('URL')
     submit = SubmitField('Submit')

#ルーティングと処理
@app.route('/',methods=['GET', 'POST'])
def message():
     db.create_all()
     form = Message_Add()

     #新規データ受付時処理
     try:
          if form.validate_on_submit():
      
               message = Tw_msg(id= form.id.data, time = form.time.data,  message = form.message.data , hashtag = form.hashtag.data, url = form.url.data )
               db.session.add(message)
               db.session.commit()
               flash('New message has been created!', 'success')
               return redirect(url_for('message'))
     except:
          flash('Failed at somewhere!', 'Unsuccess')

     #登録データ一覧表示    
     messages = Tw_msg.query.all()
     return render_template("message.html",form=form, messages=messages)


(message.html)

<body>
   <!-- Flash Message -->
    {% with messages = get_flashed_messages(with_categories=true) %}
        {% if messages %}
           {% for category, message in messages %}
              <div class="alert alert-{{ category }}">
              {{ message }}
              </div>
           {% endfor %}
        {% endif %}
     {% endwith %}

 <h3>新規追加</h3>
  <div class="box1">
  <form method="POST" action="{{ url_for('message')}}">

  {{ form.hidden_tag() }} #これを忘れると、送信が受付られません。

  <p>{{ form.time.label }}  : {{ form.time(size=10) }}</p>
  <p>{{ form.message.label }}: {{ form.message(size=100) }}</p>
  <p>{{ form.hashtag.label}} : {{ form.hashtag(size=50) }}</p>
  <p>{{ form.url.label}}    : {{ form.url(size=100) }} </p>
  <p>{{ form.submit() }}</p>
  </form>
    </div>
 

 <h3>登録済メッセージ一覧</h3>
  {% for message in messages %}
  <div class="box1">
  <p>id: {{message.id}}
     Time: {{message.time}} :</p>
  <p>Message: {{message.message }} :</p>
  <p>Hashtag:{{message.hashtag}} </p>
  <p>URL: {{message.url}}:</p>
  <a href="{{ url_for('edit_message',message_id = message.id)}}">Update</a>
  </div>
   {% endfor %}


message.idで、リストからイテレイトして表示させた各レコードのうち、どれを選択したかを特定します。 Flask-wtfのform.submit( ) で、この種の、レコードを特定させるファンクションは供えられていないので、<a href=>のリンクで、URLを指定してeditファンクションにアクセスします。



この状態で 'Update'をクリックすると、
にアクセスすることになります。 ここは、後で綺麗にします。
<a href>タグでのアクセスになりますから、このリクエストは'GET'です。

< 2. 登録済みデータベースの更新>

(ファイル構成)
--- tw_msg.py
 |---templates/
   |---message.html

   |---edit_message.html

(処理の流れ)

Step①: 最初のアクセスとして、message.htmlで一覧表示で、UPDATEをクリックすると
 <a href="{{ url_for('edit_message',message_id = message.id)}}">Update</a>
で一緒に送られてくる、message.idを取得し、DBのQueryでレコードを特定

Step②:特定されたレコードの各要素を、FORMの値として代入し、form = form で、edit_message.htmlにpassして、表示させる。

Step③:edit_message.htmlで各form itemを修正し、submitボタンでsubmitされてくる値をDBに登録。



(tw_msg.py:該当部のみ記載)

@app.route("/<int:message_id>/edit", methods=['GET', "POST"]) 

def edit_message(message_id):

♯ ① 初回アクセス時に送信されてきたidで、対象のレコードを特定
     msg = Tw_msg.query.get(message_id)
     form = Message_Add()
♯ ③ 表示された内容を変更し、データがsubmitされてきた時の処理。
     if form.validate_on_submit():
          msg.id = form.id.data
          msg.time = form.time.data
          msg.message = form.message.data
          msg.hashtag = form.hashtag.data
          msg.url = form.url.data
          db.session.commit()
          flash('Your post has been updated!', 'success')
          return redirect(url_for('message'))

♯ ② 初回アクセス時。 データ・フォーム内に値を事前挿入(プレ・ポピュレイト)
     elif request.method == 'GET':  
          form.id.data = msg.id
          form.time.data = msg.time
          form.message.data = msg.message
          form.hashtag.data = msg.hashtag
          form.url.data = msg.url

     return render_template("edit_message.html",form=form)

* @app.route("/<int:message_id>/)は、FlaskでURLに変数を使う時の書き方。



edit_message.html


  <form action="" method="post">
  {{ form.hidden_tag() }}
 
  <div class="box1">
  <p>{{form.id.label}}:{{form.id()}}</p>
     <p>{{form.time.label}}:{{form.time()}}</p>
     <p>{{form.message.label}}:{{form.message()}}</p>
  <p>{{form.hashtag.label}}:{{form.hashtag()}}</p>
  <p>{{form.url.label}}:{{form.url()}}</p>
  <p>{{ form.submit() }}</p>
  </form>
  </div>

*action=" "を空欄にしておくのがミソ。 url_for などで特定しない。 ここが分からなくて、かなり苦労した。



< 3. 登録済みデータベースの削除>

これは簡単。 一覧ページに以下のようにDeleteボタンを作ります。


<message.htm>
  <a href="{{ url_for('edit_message',message_id = message.id)}}">Update</a>
# Deleteを追加
  <a href="{{ url_for('delete_message',message_id = message.id)}}">Delete</a>


<tw_msg.py>
# Delete処理を追加
@app.route("/<int:message_id>/delete", methods=['GET', "POST"])
def delete_message(message_id):
    
    msg = Tw_msg.query.get_or_404(message_id)
    db.session.delete(msg)
    db.session.commit()
    flash('Your post has been deleted!', 'success')
    return redirect(url_for('message'))

これで削除もできるようになりました。

以上です。

以下、全体。

tw_msg.py

import os
from flask import Flask, render_template,url_for,flash, redirect, request
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, Length

app = Flask(__name__)
project_dir = os.path.dirname(os.path.abspath(__file__))
database_file = "sqlite:///{}".format(os.path.join(project_dir, "msg.db"))
app.config["SQLALCHEMY_DATABASE_URI"] = database_file
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] ='you-will-never-know'
db = SQLAlchemy(app)
#DB Model
class Tw_msg (db.Model):
     __tablename__ = 'tw_msg'
     id = db.Column(db.Integer, unique=True, primary_key=True)
     time = db.Column(db.String(6))
     message = db.Column(db.String(140))
     hashtag =db.Column(db.String(140))
     url = db.Column(db.String(140))
#WTF Model(入力フィールド定義)
class Message_Add(FlaskForm):
    
     id = StringField('ID',validators=[DataRequired(), Length(min=1, max=3) ])
     time = StringField('Time',
                        validators=[DataRequired(), Length(min=2, max=60) ])
     message = StringField('Message')
     hashtag = StringField('Hashtag')
     url = StringField('URL')

     submit = SubmitField('Submit')

#ルーティングと処理
@app.route('/',methods=['GET', 'POST'])
def message():
     db.create_all()
     form = Message_Add()
     #新規データ受付時処理
     try:
          if form.validate_on_submit():
      
               message = Tw_msg( time = form.time.data,  message = form.message.data ,
                                 hashtag = form.hashtag.data, url = form.url.data )
               db.session.add(message)
               db.session.commit()
               flash('New message has been created!', 'success')
               return redirect(url_for('message'))
     except:
          flash('Failed at somewhere!', 'Unsuccess')

     #登録データ一覧表示    
     messages = Tw_msg.query.all()
     return render_template("message.html",form=form, messages=messages)

@app.route("/<int:message_id>/edit", methods=['GET', "POST"])
def edit_message(message_id):
     msg = Tw_msg.query.get(message_id)
     form = Message_Add()
     if form.validate_on_submit():
          msg.id = form.id.data
          msg.time = form.time.data
          msg.message = form.message.data
          msg.hashtag = form.hashtag.data
          msg.url = form.url.data
          db.session.commit()
          flash('Your post has been updated!', 'success')
          return redirect(url_for('message'))
     elif request.method == 'GET':
          form.id.data = msg.id
          form.time.data = msg.time
          form.message.data = msg.message
          form.hashtag.data = msg.hashtag
          form.url.data = msg.url
         
     #return "I'm doin' fine."
     return render_template("edit_message.html",form=form)

@app.route("/<int:message_id>/delete", methods=['GET', "POST"])
def delete_message(message_id):
    
    msg = Tw_msg.query.get_or_404(message_id)
    db.session.delete(msg)
    db.session.commit()
    flash('Your post has been deleted!', 'success')
    return redirect(url_for('message'))


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


<message.html>
<html>
<head>
<style type="text/css">
.box1 {
    padding: 0.5em 1em;
    margin: 2em 0;
    font-weight: bold;
    color: #6091d3;/*文字色*/
    background: #FFF;
    border: solid 3px #6091d3;/*線*/
    border-radius: 10px;/*角の丸み*/
}
.box2 p {
    margin: 0;
    padding: 0;
}
</style>
</head>
<body>
   <!-- Flash Message -->
    {% with messages = get_flashed_messages(with_categories=true) %}
        {% if messages %}
           {% for category, message in messages %}
              <div class="alert alert-{{ category }}">
              {{ message }}
              </div>
           {% endfor %}
        {% endif %}
     {% endwith %}
 <h3>新規追加</h3>
  <div class="box1">
  <form method="POST" action="{{ url_for('message')}}">
  {{ form.hidden_tag() }}
  <p>{{ form.id.label }}  : {{ form.id(size=3) }}</p>
  <p>{{ form.time.label }}  : {{ form.time(size=10) }}</p>
  <p>{{ form.message.label }}: {{ form.message(size=100) }}</p>
  <p>{{ form.hashtag.label}} : {{ form.hashtag(size=50) }}</p>
  <p>{{ form.url.label}}    : {{ form.url(size=100) }} </p>
  <p>{{ form.submit() }}</p>
  </form>
    </div>
 
 
 <h3>登録済メッセージ一覧</h3>
  {% for message in messages %}
  <div class="box1">
  <p>id: {{message.id}}
     Time: {{message.time}} :</p>
  <p>Message: {{message.message }} :</p>
  <p>Hashtag:{{message.hashtag}} </p>
  <p>URL: {{message.url}}:</p>
  <a href="{{ url_for('edit_message',message_id = message.id)}}">Update</a>
  <a href="{{ url_for('delete_message',message_id = message.id)}}">Delete</a>
delete_message
  </div>
     {% endfor %}

 
</body>
</html>

<edit_message.html>

<html>
<head>
<style type="text/css">
.box2 {
    padding: 0.5em 1em;
    margin: 2em 0;
    font-weight: bold;
    color: #6091d3;/*文字色*/
    background: #FFF;
    border: solid 3px #6091d3;/*線*/
    border-radius: 10px;/*角の丸み*/
}
.box2 p {
    margin: 0;
    padding: 0;
}
</style>
</head>
<body>
  <form action="" method="post">
  {{ form.hidden_tag() }}
 
  <div class="box1">
  <p>{{form.id.label}}:{{form.id()}}</p>
     <p>{{form.time.label}}:{{form.time()}}</p>
     <p>{{form.message.label}}:{{form.message(size=100)}}</p>
  <p>{{form.hashtag.label}}:{{form.hashtag(size=30)}}</p>
  <p>{{form.url.label}}:{{form.url(size=30)}}</p>
  <p>{{ form.submit() }}</p>
  </form>
  </div>

 
</body>
</html>