☆☆ 新着記事 ☆☆

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>

2019年7月26日金曜日

初めてPythonのクラスやメソッドを理解する為の説明

Pythonの基本を理解して、そろそろ小さなプログラムから脱皮したくなった人のための、Python 「クラス」、「インスタンス」や「メッソド」、また、これらの実際の「活用方法」の説明です。



◇ クラスの定義


1) クラスとは
Pythonではクラスの定義にclass文を使用します。class文を使った定義の仕方は以下のようになります。

class クラス名:

class ClassName():
 
class後にそのクラスの名前となるクラス名をつけて(大文字で始めるのが慣行)、最後に「:」を付けます。そのあとに一段落インデントを入れてクラス定義をしていきます。

class Test:
    pass  #何もしないクラスの場合はpassと記入する

2) クラス変数
クラスは、クラス内で定義されたメソッドに共有できる変数を持つことができます。
(詳細は、インスタンスと変数の後で説明)

「インスタンスを生成する(インスタンス化する)」、とは

1) インスタンスとは
Instanceとは、Classで記述した関数(メッソドという)などを実行するために生成するもの。

class Employee():
    pass
emp_1 = Employee()  #EmployeeというClassのemp_1 というインスタンス生成
emp_2 = Employee() #EmployeeというClassのemp_2 というインスタンス生成

print(emp_1)
> <__main__.Employee object at 0x0000000001E228D0>
print(emp_2)
> <__main__.Employee object at 0x0000000002856518>
Employee クラスの独立したオブジェクトがインスタンスとして、それぞれ生成された。

2) インスタンスと変数
インスタンスは、変数を属性としてもつことが出来る。 ここでは、ひとつずつ、マニュアルで属性値を与えた例を示しますが、コンストラクタを使うことで操作がもっと簡単になります。(後述)

emp_1 = Employee()
emp_1.first = 'Bruno'
emp_1.second = 'Mars'
emp_1.email = 'Bruno.Mars@test.com'
emp_1.pay = 4000

emp_2 = Employee()
emp_2.first = 'Charlie'
emp_2.second = 'Puth'
emp_2.email = 'Charlie.Puth@test.com'
emp_2.pay = 3000

print(emp_1.email)
>Bruno.Mars@test.com
print(emp_2.pay)
>3000
print('{} {}'.format(emp_1.first,emp_1.second))
>Bruno Mars


◇メソッドとは

メソッドとは、クラス内に定義された関数のことです。メソッドは定義されたクラスからしか呼び出すことが出来ないのも特徴。

class クラス名:
    def メソッド名(self):
        #メソッドの定義

メソッドは必ず1つ以上の引数を持ちます。また、引数のうち最初のものはselfという名前にすることになっています。メソッドを呼び出すには以下のようにします。

インスタンス.メソッド名()

class Test:
    def test_print(self):
        print('This is test')

test = Test()      #インスタンス生成
test.test_print()  #メソッド呼び出し
> This is test

◇ コンストラクタとデストラクタ (特殊関数)

メソッドの中でも、インスタンスが生成されるときに自動的に呼び出されるメソッドのことをコンストラクタと言います。デストラクタは、インスタンス消滅時に自動的に呼び出されるメソッドです。ただし、デストラクタは使わないことが、Python公式ドキュメントでも推奨されていないので、深堀りしない。

コンストラクタを定義するには「init」という名前のメソッドを作成します。

例)
class Test:
    def __init__(self, 引数1, 引数2, ….):
        #コンストラクタの定義

  例)
  Class Test():
         def __init__(self):
             print('Constructor is called!')
         def func(self):
              print('Function is called!')

  で、
  test_instance = Test()

  とインスタンスを生成すると、その時点で 
  print('Constructor is called!') が実行される。


例2)
インスタンスの例で、ここではコンストラクタを使用して、各インスタンスの変数に値を入れて見ます。

クラス Employee で、__init__ メソッドを使って、selfというインスタンスに、first, last,pay という引数を指定する記述が以下になります。


class Employee():
    def __init__(self, first,last,pay):
        self.first = first
        self.second = second
        self.pay = pay
        self.email = first + '.' + 'test.com'

    #Full Nameをprint outする関数を作成
    def  fullname(self):
        print('{} {}'.format(self.first, self.second))

メンバー変数を指定してみます。メンバー変数はインスタンス間をまたぎません。
emp_1 = Employee('Bruno','Mars',4000) 
emp_2 = Employee('Charlie','Puth',3000)
 

print(emp_1.email)
>Bruno.Mars@test.com
print(emp_2.pay)
>3000

♯Instanceを使って関数を呼び出し。fullname( )メソッドにemp2の属性を再記述する必要なし。

print(emp_2.fullname() #()を付けることで、属性ではなく関数を呼びだし。
>Charlie Puth

#Class名を使って関数を呼び出し。fullname( )メソッドにemp2の属性を再記述する必要あり。

print( Employee.fullname(emp_2) )
>Charlie Puth


(挙動の解説)
*コンストラクタである__init__() で、このように第一属性として self を定義すると、emp_1が読まれた際に、Employee()にパスされて、selfとして理解されて、各属性が self(emp_1) = first ,self(emp_1) = secondと解釈される。 したがって、

self.first = firstは、emp_1.first = と記述していることと同じになる。


*emp_1 や emp_2 は、Employeeというクラスで定義されているので、このEmployee()クラスで定義されているメソッド fullname()を利用することができる。

 *   def  fullname(self): に引数 selfの記述をしないで、以下のprint outを実行した場合、
print(emp_2.fullname())
TypeError: fullname() takes 0 positional arguments but 1 was given
という、「引数が1つしか与えられていない」といいうエラーになる。

それは、emp_2.fullname() で、fullname()に引数を与えていないように見えるが、実際には、emp_2というインスタンスが自動的に引数としてfullname()に渡されているので、selfで受け取る必要があるから。



クラス変数
(クラスとは、ちょっと書きましたが)クラス変数は、クラス内で記述されたメソッドで共有できる変数です。インスタンス間で同じ値を参照します。

class Employee():

    raise_amount = 1.04  # クラス変数の定義

    def __init__(self, first,last,pay):
        self.first = first
        self.second = second
        self.pay = pay
        self.email = first + '.' + 'test.com'

    def  fullname(self):
        print('{} {}'.format(self.first, self.second))

    def  apply_raise(self):
        self.pay = int (self.pay * self.raise_amount)

   #クラス変数を各メソッド内で参照する場合にも、self. や、クラス名そのもの Employee.変数
    を明記して、Employee.raise_amount と記述する必要がある。これらの記述を明示しない
    と、「raise_amountは定義されていない」というエラーになる。


emp_1 = Employee('Bruno','Mars',4000)
emp_2 = Employee('Charlie','Puth',3000)


print(emp_1.pay)
>4000
emp_1.apply_raise()
print(emp_1)
>4160

*(解説)
クラス変数を使用する際にインスタンス名、又はクラス名を明示しなければならない理由。
print(Employee.raise_amount)
>1.04
print(emp_1.raise_amount)
>1.04
print(emp_2.raise_amount)、
>1.04

クラス変数は、最初、インスタンスに引数が定義されていないかチェック。なければクラスで定義されいないかチェックする挙動となります。

この例では、
最初、emp_1 の属性を参照
print(emp_1.__dict__)
{'first': 'Bruno', 'second': 'Mars', 'pay': 4000, 'email': 'Bruno.test.com'}

raise_amountが定義されていないので、Classの属性を参照。
print(Employee.__dict__)

{'__module__': '__main__', 'raise_amount': 1.04, '__init__': <function Employee.__init__ at 0x0000000002F026A8>, 'fullname': <function Employee.fullname at 0x0000000002F02730>, 'apply_raise': <function Employee.apply_raise at 0x0000000002F027B8>, '__dict__': <attribute '__dict__' of 'Employee' objects>, '__weakref__': <attribute '__weakref__' of 'Employee' objects>, '__doc__': None}

で、変数の値を参照することができる。


コンストラクタとは逆に、インスタンスが不要になり削除される時に呼ばれるメソッドをデストラクタと言います。デストラクタは「del」という名前のメソッドで定義されます。

class Test:
    def __init__(self):
        print('コンストラクタが呼ばれました')
    def __del__(self):
        print('デストラクタが呼ばれました')

test = Test()  #インスタンスを生成
del test      #インスタンスを削除

実行結果

コンストラクタが呼ばれました
 デストラクタが呼ばれました

◇ 継承
Pythonのクラスも他のクラスを継承し、拡張することができます。クラスを継承する場合はクラス文に親となるクラスを指定します。親のクラスのメソッドを呼び出す場合はsuper()を使います。

class Test( ):
    def __init__(self, num):
        self.num = num;
    def print_num(self):
        print('引数で渡された数字は{}です。'.format(self.num))

class Test2(Test):  #Testクラスを継承
    def print_test2_info(self):
        print('このクラスはTestクラスを継承しています。')
        super().print_num()  #親クラスのprint_num()を呼び出す

test = Test2(10)
test.print_test2_info()

実行結果

このクラスはTestクラスを継承しています。
 引数で渡された数字は10です。

参考)
https://www.youtube.com/watch?v=ZDa-Z5JzLYM

2019年7月21日日曜日

Flaskで、Login ページを作ってDBを操作する(1)

このポストの範囲:

◇ 全体の流れを考える
・ ログインできるユーザを管理する。(html)
  1) ユーザ登録ページの作成
  2)  ログインページ(ログイン認証要求ページ)を作成


・必要となるFunction
    1) app.pyで、これらhtmlのルーティングを管理
  2) forms.pyで、htmlから送られてくる値のセキュリティー・チェック
  3) 送信されてきたユーザデータをDBに登録。 ログイン認証時に参照できるようにする。



作業の流れとしては、

Step1 :
 forms.pyで、各htmlに設置する「入力フィールドの定義」と送信されてくる値の「validation」の方法を定義する。 => FLASK-WTFを利用。

Step2:
各htmlに入力フィールドを記述

Step3:
DBを作成する。

◇出来上がりのCode

<forms.py> 

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo

class RegistrationForm(FlaskForm):
    username = StringField('Username',
                           validators=[DataRequired(), Length(min=2, max=20)])
    email = StringField('Email',
                        validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password',
                                     validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Sign Up')

class LoginForm(FlaskForm):
    email = StringField('Email',
                        validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    remember = BooleanField('Remember Me')
    submit = SubmitField('Login')


</templates/register.html>
  < form method="POST" action="">
            {{ form.hidden_tag() }}

    <legend>Join Today</legend>

    {% 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 %}
           
                    {{ form.username.label }}
                    {% if form.username.errors %}
                        {{ form.username }}
                     
                            {% for error in form.username.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                     
                    {% else %}
                        {{ form.username }}
                    {% endif %}
                </div>
                <div class="form-group">
                    {{ form.email.label }}
                    {% if form.email.errors %}
                        {{ form.email }}
                        <div class="invalid-feedback">
                            {% for error in form.email.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                        </div>
                    {% else %}
                        {{ form.email }}
                    {% endif %}
                </div>
                <div class="form-group">
                    {{ form.password.label }}
                    {% if form.password.errors %}
                        {{ form.password }}
                        <div class="invalid-feedback">
                            {% for error in form.password.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                        </div>
                    {% else %}
                        {{ form.password }}
                    {% endif %}
                </div>
                <div class="form-group">
                    {{ form.confirm_password.label(class="form-control-label") }}
                    {% if form.confirm_password.errors %}
                        {{ form.confirm_password(class="form-control form-control-lg is-invalid") }}
                        <div class="invalid-feedback">
                            {% for error in form.confirm_password.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                        </div>
                    {% else %}
                        {{ form.confirm_password }}
                    {% endif %}
                </div>
            </fieldset>
            <div class="form-group">
                {{ form.submit }}
            </div>
        </form>
    </div>
    <div class="border-top pt-3">
        <small class="text-muted">
            Already Have An Account? <a  href="{{ url_for('login') }}">Sign In</a>
        </small>
    </div>
           

</templates/login.html>

                 <legend>Log In</legend>


    {% 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 %}
       

    <!-- Input email -->
                    {{ form.email.label}}
                    {% if form.email.errors %}
                        {{ form.email }}
                        <div class="invalid-feedback">
                            {% for error in form.email.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                        </div>
                    {% else %}
                        {{ form.email }}
                    {% endif %}
                </div>
               <!-- Input password -->
                    {{ form.password.label }}
                    {% if form.password.errors %}
                        {{ form.password(class="form-control form-control-lg is-invalid") }}
                        <div class="invalid-feedback">
                            {% for error in form.password.errors %}
                                <span>{{ error }}</span>
                            {% endfor %}
                        </div>
                    {% else %}
                        {{ form.password }}
                    {% endif %}
                </div>
     <!-- Remember me -->
                <div class="form-check">
                    {{ form.remember }}
                    {{ form.remember.label}}
                </div>
            <div class="form-group">
                {{ form.submit }}
            </div>
            <small class="text-muted ml-2">
                <a href="#">Forgot Password?</a>
            </small>
        </form>
      </div>
      <div class="border-top pt-3">
            <small class="text-muted">
              Need An Account? <a class="ml-2" href="{{ url_for('register') }}">Sign Up Now</a>
            </small>
   </div>
  
  

<app.py>


from flask import Flask, render_template, url_for, flash, redirect
from forms import RegistrationForm, LoginForm
app = Flask(__name__)
app.config['SECRET_KEY'] = '5791628bb0b13ce0c676dfde280ba245'


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

@app.route("/register", methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit(): 

        flash(f'Account created for {form.username.data}!', 'success')
        return redirect(url_for('home')) ♯ユーザが正しく作成できた場合home にリダイレクト

    return render_template('register.html', title='Register', form=form)

@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():

        if form.email.data == 'admin@blog.com' and form.password.data == 'password':
     ♯データベースを作る前なので仮にテスト用

            flash('You have been logged in!', 'success')
            return redirect(url_for('home')) ♯ユーザが正しく作成できた場合home にリダイレクト
        else:
            flash('Login Unsuccessful. Please check username and password', 'danger')

    return render_template('login.html', title='Login', form=form)
    #form を htmlにpass することで, htmlからLoginFormにアクセス可能になる。

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

* flash(f'Account created for {form.username.data}!', 'success')
  flashは、2つめの引数にBootstrapのカテゴリーを指定することが可能。指定しなくても良い。
 指定した場合は、HTML側に

{% with messages = get_flashed_messages() %}に、引数として以下のように指定する。
{% with messages = get_flashed_messages(with_categories=true) %}
      {% if messages %}
         {% for category, message in messages %}
        <div class="alert alert-{{ category }}">
        {{ message }}
        </div>
         {% endfor %} 
      {% endif %}
などと記入する。


<app.py>

2019年7月14日日曜日

FLASK - SQLAlchemy 1対1 1対多 リレーション・データベース

【(最も参照中) FLASK SQLAlchemyを使ってみる】

のスピンオフの投稿。
投稿のページが、書きすぎて重くなったので分割して書きます。




 Simple Relationships

SQLAlchemy は、relational databases の連携を強力にサポートする(らしい)のでやってみる。

上記のapp.pyに以下のDBを追加する。

 from datetime import datetime
 class Post(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     title = db.Column(db.String(80), nullable=False)
     body = db.Column(db.Text, nullable=False)
     pub_date = db.Column(db.DateTime, nullable=False,
         default=datetime.utcnow)
     category_id = db.Column(db.Integer, db.ForeignKey('category.id'),
         nullable=False)
     category = db.relationship('Category',
         backref=db.backref('posts', lazy=True))
     def __repr__(self):
         return '<Post %r>' % self.title

 class Category(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String(50), nullable=False)
     def __repr__(self):
         return '<Category %r>' % self.name

 First let’s create some objects:
>>> from app import Post
>>> from app import Category

 >>> py = Category(name='Python')
 >>> Post(title='Hello Python!', body='Python is pretty cool', category=py)
<Post 'Hello Python!'>  <-- 何故かprintされる。
 >>> p = Post(title='Snakes', body='Ssssssss')
 >>> py.posts.append(p)
 >>> db.session.add(py)


*最初にやった、やり方は

 (*インスタンスの生成)
 >>> admin = User(username='admin', email='admin@example.com')
 >>> db.session.add(admin)
 >>> db.session.commit()


As you can see, there is no need to add the Post objects to the session. Since the Category is part of the session all objects associated with it through relationships will be added too. It does not matter whether db.session.add() is called before or after creating these objects. The association can also be done on either side of the relationship - so a post can be created with a category or it can be added to the list of posts of the category.

 Let’s look at the posts. Accessing them will load them from the database since the relationship is lazy-loaded, but you will probably not notice the difference - loading a list is quite fast:

 >>> py.posts
 [<Post 'Hello Python!'>, <Post 'Snakes'>]

While lazy-loading a relationship is fast, it can easily become a major bottleneck when you end up triggering extra queries in a loop for more than a few objects. For this case, SQLAlchemy lets you override the loading strategy on the query level. If you wanted a single query to load all categories and their posts, you could do it like this:

 >>> from sqlalchemy.orm import joinedload
 >>> query = Category.query.options(joinedload('posts'))
 >>> for category in query:
 ...     print category, category.posts
 <Category u'Python'> [<Post u'Hello Python!'>, <Post u'Snakes'>]

 If you want to get a query object for that relationship, you can do so using with_parent(). Let’s exclude that post about Snakes
 for example:

 >>> Post.query.with_parent(py).filter(Post.title != 'Snakes').all()
 [<Post 'Hello Python!'>]


 One-to-Many Relationships

 The most common relationships are one-to-many relationships. Because relationships are declared before they are established you can use strings to refer to classes that are not created yet (for instance if Person defines a relationship to Address which is declared later in the file).

 Relationships are expressed with the relationship() function. However the foreign key has to be separately declared with the ForeignKey class:

 class Person(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String(50), nullable=False)
     addresses = db.relationship('Address', backref='person', lazy=True)
 class Address(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     email = db.Column(db.String(120), nullable=False)
     person_id = db.Column(db.Integer, db.ForeignKey('person.id'),
         nullable=False)

 What does db.relationship() do? That function returns a new property that can do multiple things. In this case we told it to point to the Address class and load multiple of those. How does it know that this will return more than one address? Because SQLAlchemy guesses a useful default from your declaration. If you would want to have a one-to-one relationship you can pass uselist=False to
 relationship().

 Since a person with no name or an email address with no address associated makes no sense, nullable=False tells SQLAlchemy to create the column as NOT NULL. This is implied for primary key columns, but it’s a good idea to specify it for all other columns to make it clear to other people working on your code that you did actually want a nullable column and did not just forget to add it.

So what do backref and lazy mean? backref is a simple way to also declare a new property on the Address class. You can then also use my_address.person to get to the person at that address. lazy defines when SQLAlchemy will load the data from the database:
 *'select' / True (which is the default, but explicit is better than implicit) means that SQLAlchemy will load the data as necessary in one go using a standard select statement.

 *'joined' / False tells SQLAlchemy to load the relationship in the same query as the parent using a JOIN statement.
 *'subquery' works like 'joined' but instead SQLAlchemy will use a subquery.
 *'dynamic' is special and can be useful if you have many items and always want to apply additional SQL filters to them. Instead of loading the items SQLAlchemy will return another query object which you can further refine before loading the items. Note that this cannot be turned into a different loading strategy when querying so it’s often a good idea to avoid using this in favor of lazy=True. A query object equivalent to a dynamic user.addresses relationship can be created using Address.query.with_parent(user)
 while still being able to use lazy or eager loading on the relationship itself as necessary.
 How do you define the lazy status for backrefs? By using the backref() function:

 class User(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String(50), nullable=False)
     addresses = db.relationship('Address', lazy='select',
         backref=db.backref('person', lazy='joined'))

 Many-to-Many Relationships
 If you want to use many-to-many relationships you will need to define a helper table that is used for the relationship. For this
 helper table it is strongly recommended to not use a model but an actual table:

 tags = db.Table('tags',
     db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True),
     db.Column('page_id', db.Integer, db.ForeignKey('page.id'), primary_key=True)
 )
 class Page(db.Model):
     id = db.Column(db.Integer, primary_key=True)
     tags = db.relationship('Tag', secondary=tags, lazy='subquery',
         backref=db.backref('pages', lazy=True))
 class Tag(db.Model):
     id = db.Column(db.Integer, primary_key=True)

 Here we configured Page.tags to be loaded immediately after loading a Page, but using a separate query. This always results in two queries when retrieving a Page, but when querying for multiple pages you will not get additional queries.
 The list of pages for a tag on the other hand is something that’s rarely needed. For example, you won’t need that list when retrieving the tags for a specific page. Therefore, the backref is set to be lazy-loaded so that accessing it for the first time will trigger a query to get the list of pages for that tag. If you need to apply further query options on that list, you could either switch to the 'dynamic' strategy - with the drawbacks mentioned above - or get a query object using Page.query.with_parent(some_tag) and then use it exactly as you would with the query object from a dynamic relationship.




Step1 : Creating Models
A Model is a Python class which represents the database table and its attributes map to the column of the table. A model class
inherits from db.Model and defines columns as an instance of db.Column class.
プロジェクト・ディレクトリ/main2.py

#...
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
#...
class Category(db.Model):
    __tablename__ = 'categories'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), nullable=False)
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    def __repr__(self):
        return "<{}:{}>".format(id, self.name)
class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer(), primary_key=True)
    title = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text(), nullable=False)
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)
    def __repr__(self):
        return "<{}:{}>".format(self.id, self.title[:10])

__tablename__ は、テーブル名を指定するクラス変数。 指定しなければ、SQLAlchemyは、modelの名前を自動的に使うが、複数系の名前はつけられない。

・db.Columnの代表的なConstraints.

nullable  When set to False makes the column required. Its default value is True.
index      A boolean attribute. If set to True creates an indexed column.
primary_key  A boolean attribute. If set to True marks the column as the primary key of the table.
unique  A boolean attribute. If set to True each value in the column must be unique.

You might have noticed that we have set the default value of created_on and updated_on to a method name ( datetime.utcnow ) instead of calling the method ( datetime.utcnow() ). This is because we don’t want to call datetime.utcnow() method when the code is executed. Instead, we want it to call it when an actual record is added or updated.
Defining Relationship
In the previous section, we have created a Post model with a couple of fields. In the real world, however, model classes seldom
exist on their own. Most of the time they are connected with other models through various relationships like one-to-one, one-to-many
and many-to-many.
Let’s expand on the analogy of a blog site. Generally, a blog post belongs to a category and one or more tags. In other words,


there is a one-to-many relationship between a category and a post and a many-to-many relationship between a post and a tag. The following figure demonstrates this relationship.

flask_app/models.py
#...
class Category(db.Model):
    __tablename__ = 'categories'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), nullable=False)
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    posts = db.relationship('Post', backref='category')
    def __repr__(self):
        return "<{}:{}>".format(id, self.name)

class Post(db.Model):
__tablename__ = 'posts'
    id = db.Column(db.Integer(), primary_key=True)
    title = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), nullable=False)
    content = db.Column(db.Text(), nullable=False)
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)
category_id = db.Column(db.Integer(), db.ForeignKey('categories.id'))

class Tag(db.Model):
    __tablename__ = 'tags'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    slug = db.Column(db.String(255), nullable=False)
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    def __repr__(self):
        return "<{}:{}>".format(id, self.name)
#...


One to Many Relationship (between a category and a post)


*db.relationship():2つのテーブルが関連していることを明示。model classと関連するobjectsを指定。

*db.ForeignKey() : one to manyのmany側に記述する。参照するテーブルのcolumn名を指定する。db.relationship()は、one側、many側のどちらに記述しても良い。
db.relationship()がある方を親クラス、db.ForeignKey()があるクラスを子クラスと呼ぶことがある。

*backref='category'
Now if we have a Category object (say c) then we can access all posts under it as c.posts. What if you want to access the data from the other side of the relationship i.e get category from a post object? This is where backref comes into play. So the code:
 posts = db.relationship('Post', backref='category')
adds a category attribute to the Post object. That means if we have a Post object (say p) then we can access its category as
p.category.
The category and posts attributes on Post and Category object only exist for your convenience they are not actual columns in the
table.

Many to Many Relationship

many-to-many relationship を作る際には、もう一つ、 association table と呼ばれるテーブルを作る。
以下の例では、post.id と tag.id を含む、post_tagsという名前のアソシエーション・テーブルを作る。

A many-to-many relationship between the post and tags is implemented as a two one-to-many relationship. The first one-to-one relationship is between
①posts and post_tags table and the second is between
②tags and post_tags table.

flask_app/main2.py
# ...
class Category(db.Model):
    # ...
① post_tags = db.Table('post_tags',
    db.Column('post_id', db.Integer, db.ForeignKey('posts.id')),
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'))
)
class Post(db.Model):
__tablename__ = 'posts'
    id = db.Column(db.Integer(), primary_key=True)
    # ...
class Tag(db.Model):
    # ...
    created_on = db.Column(db.DateTime(), default=datetime.utcnow)
    posts = db.relationship('Post', ②secondary=post_tags, backref='tags')
#...
① db.Table(): post_tagsという名前のアソシエーション・テーブルをdb.Table()のオブジェクトとして定義する。(Syntaxは、SQLAlchemyのもの
で、通常のclassの定義とは異なる。) db.Table()の最初の引数はテーブル名。次の引数は、参照されるテーブル Columnのインスタンス。.
②secondary=post_tags
モデル・classが使用するアソシエーション・テーブルの指定する。
Let’s say we have a Post object p, then we can access all its tags as p.tags. Similarly, Given a Tag object t, we can access all
the posts under it as t.posts.



One to One relationship


One to Manyモデルとの唯一の違いは、以下のように、argument uselist=False をdb.relationship() をディレクティブの中に指定する。
(従業員と免許書IDを関連づけたモデルの例)

class Employee(db.Model):
    __tablename__ = 'employees'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(255), nullable=False)
    designation = db.Column(db.String(255), nullable=False)
    doj = db.Column(db.Date(), nullable=False)
    dl = db.relationship('DriverLicense', backref='employee', uselist=False)

class DriverLicense(db.Model):
    __tablename__ = 'driverlicense'
    id = db.Column(db.Integer(), primary_key=True)
    license_number = db.Column(db.String(255), nullable=False)
    renewed_on = db.Column(db.Date(), nullable=False)
    expiry_date = db.Column(db.Date(), nullable=False)
    employee_id = db.Column(db.Integer(), db.ForeignKey('employees.id'))

Now if we have an Employee object e then e.dl would return a DriverLicense object. If we hadn’t passed uselist=False to the db.relationship() directive then the relationship between Employee and DriverLicense would be one-to-many and e.dl would return a list of DriverLicense objects instead of a single object. The uselist=False argument doesn’t have any effect on the employee attribute of the DriverLicense object. As usual, it will return a single object.
Now its time to create our database and tables inside it.