☆☆ 新着記事 ☆☆

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>

0 件のコメント:

コメントを投稿