WTForms TextAreaFiledに初期値をどう入れるか?

はじめに

wtformsを使っていて気が付いた。Templates側からTextAreaFiledへの初期値の入れ方が分からない。結構ハマったのでメモしておく。

コード

formの定義

from wtforms import Form
from wtforms.fields import (
    HiddenField, StringField, IntegerField, TextAreaField, SubmitField
)
class UpdateForm(Form):
    id = HiddenField()
    name = StringField('名前は:')
    age = IntegerField('年齢は:')
    comment = TextAreaField('コメント:')
    submit = SubmitField('更新')

Templates側の定義

<form method="POST">
        {{form.csrf_token}}
        {{form.id(value=member.id)}}
        {{form.name.label}}{{form.name(value="john")}}<br>
        {{form.age.label}}{{form.age(value=16)}}<br>
        {{form.comment.label}}{{form.comment(value="aaaa")}}<br>    ←これがうまくいかない
        {{form.submit()}}
</form>

問題

上記form.comment()に初期値としてaaaaを入力したいが実行すると

こんな風にcommentのところだけ初期値が入ってない。いろいろ調べたらTextAreaFieldがvalue属性をもっていない。だからStringFieldやIntegerFieldと同じようにvalue=で値を代入できない。

解決方法

<form method="POST">
        {{form.csrf_token}}
        {{form.id(value=member.id)}}
        {{form.name.label}}{{form.name(value=member.name)}}<br>
        {{form.age.label}}{{form.age(value=member.age)}}<br>

        {% set f = form.comment.process_data(member.comment) %}  ←こうする
        {{form.comment.label}}  {{form.comment()}}<br>

        {{form.submit()}}
</form>

参考にしたサイト

python — WTForms-textareaフィールドに事前入力する方法は?

flask_migrateの使い方

はじめに

flask_migrateの使い方が良くわからなかったのでメモ。説明できるまで理解が追いついてないので完全に自分用のメモ。

構造

MigrateTEST
└─migrate_model.py

migrate_model.pyの作成

import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

base_dir = os.path.dirname(__file__)  # 作成しているmodel.pyを配置しているファイルのパス

app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(base_dir, 'migrate_data.sqlite')  # データベース接続先
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # ログ出力を無効に

db = SQLAlchemy(app)
Migrate(app, db)

class Person(db.Model):
    __tablename__ = 'persons'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"id = {self.id}, name={self.name}, age={self.age}"

このpythonファイルは特に実行はしないでそのまま置いておく。flask_Alchemyだとpythonファイルを実行しなければデータベースが作成できなかった。

コマンドプロンプト側での操作

①migrate_model.pyがおいてあるフォルダまで移動。

 C:\Users\ProgMemo\PythonProject\MigrateTEST>

環境変数を設定する。

C:\Users\ProgMemo\PythonProject\MigrateTEST>set FLASK_APP=migrate_model.py

set FLASK_APP=ファイル名は一時的に環境変数を設定する命令。ここで設定した値はそのセッションのみ有効。つまりコマンドラインを閉じない限り有効。これでflaskに対してファイルの場所を教えた。>flask runってやるとwebサーバーが立ち上がるらしい。今回はrunしない。

③ migrationの初期化

C:\Users\ProgMemo\PythonProject\MigrateTEST>flask db init

migrationの初期化って言い方が正しいか分からないが、とにかくflask_migrateを使用するためには最初に>flask db initを実行して初期化する必要があるらしい。この時点でmigrate_model.pyと同階層にmigrationsというフォルダが作成される。この中にmigrationの設定ファイルが入る。

MigrateTEST
├─migrations
│ ├─versions
│ │ └─1eaea977438f_add_person.py
│ ├─almbic.ini
│ ├─env.py
│ ├─README
│ └─script.py.mako
└─migrate_model.py

④ migrateする

C:\Users\ProgMemo\PythonProject\MigrateTEST>flask db migrate -m "add Person"

こうするとversionsフォルダの中に新たにファイルができる。
MigrateTEST
├─migrations
│ ├─versions
│ │ ├─1eaea977438f_add_person.py
│ │ └─513990528038_add_person.py
│ ├─almbic.ini
│ ├─env.py
│ ├─README
│ └─script.py.mako
└─migrate_model.py

以下が新しくできたファイルの中身。

"""add Person

Revision ID: 513990528038
Revises: 1eaea977438f
Create Date: 2022-07-27 01:56:21.597348

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '513990528038'
down_revision = '1eaea977438f'
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('persons', 'age')
    op.drop_column('persons', 'gender')
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('persons', sa.Column('gender', sa.TEXT(), nullable=True))
    op.add_column('persons', sa.Column('age', sa.INTEGER(), nullable=True))
    # ### end Alembic commands ###

⑤migrationの内容をデータベースに反映させる。

C:\Users\ProgMemo\PythonProject\MigrateTEST>flask db upgrade

こうするとmigrate_mode.pyの同階層に新たにデータベースファイルmigrate_data.sqliteが作成される。

MigrateTEST
├─migrations
│ ├─versions
│ │ ├─1eaea977438f_add_person.py
│ │ └─513990528038_add_person.py │ ├─almbic.ini
│ ├─env.py
│ ├─README
│ └─script.py.mako
├─migrate_model.py
└─migrate_data.sqlite

データベースの中身を確認すると、personsテーブルと、alembic_versionテーブルが作成されている。

personsテーブル
migrate_model.pyで定義したテーブル。まだなにも値が入ってない。

id name age

alembic_versionテーブル こっちは現在のバージョンを格納するテーブルのようだ。

version_num
513990528038

参考にしたサイト

flask_install01

flask_sqlalchemyのコンストラクタについて

はじめに

flask_sqlalchemyのチュートリアルをやっていたらコンストラクタの記述でちょっと気になったので調べてみた。

コード

これが今回テストに使うコード。これを実行すると、pythonファイルと同階層に"TestPerson.db"というsqliteデータベースができる。

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///TestPerson.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_ECHO'] = False

db = SQLAlchemy(app)

class Person(db.Model):
    __tablename__ = 'person'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)
    age = db.Column(db.Integer)

    def __init__(self, name, age):
        self.name = name
        self.age = age

if __name__ == '__main__':
    db.create_all()

    man1 = Person('john', 29)
    man2 = Person('adam', 19)
    man3 = Person('David', 55)

    db.session.add(man1)
    db.session.add(man2)
    db.session.add(man3)

    db.session.commit()

データベースの中身を確認すると

id name age
1 john 29
2 adam 19
3 David 55

こうなってる。

気になった個所

Personクラスのコンストラクタ(init)の部分ってどう解釈すればいい?

class Person(db.Model):
    __tablename__ = 'person'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)
    age = db.Column(db.Integer)

    def __init__(self, name, age):  # ここが凄い気になる
        self.name = name
        self.age = age

ほかのチュートリアル見るとコンストラクタを書かないパターンもあった。
name = db.Column(db.Text), age = db.Column(db.Integer)はクラス変数として定義されている。よってコンストラクタを書かなければ、
man1 = Person(name='john', age = 29) でクラス変数に値が入るはず。ところがコンストラクタでインスタンス変数self.name, self.ageを定義しているため、man1 = Person(name='john', age = 29)インスタンス変数に値が入ってしまう。これはSQLAlchemyの処理的に問題ないのか?

このへんに関してはSQLAlchemy公式サイトにちらっと記載があった。

公式サイトの説明

以下公式サイトから抜粋

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    def __repr__(self):
        return '<User %r>' % self.username

Note how we never defined a __init__ method on the User class? That’s because SQLAlchemy adds an implicit constructor to all model classes which accepts keyword arguments for all its columns and relationships. If you decide to override the constructor for any reason, make sure to keep accepting **kwargs and call the super constructor with those **kwargs to preserve this behavior:

class Foo(db.Model):
    # ...
    def __init__(self, **kwargs):
        super(Foo, self).__init__(**kwargs)
        # do custom stuff

参考にしたサイト

Quickstart — Flask-SQLAlchemy Documentation (2.x)

python - Flask-SQLAlchemy Constructor - Stack Overflow

thead、tbody、tfootタグについて

はじめに

HTMLを勉強中、Tableタグの中にthead, tbody, tfootタグを見つけたので意味をメモしておく

簡単な説明

thead, tbody, tfootとは、それぞれ表のヘッダ部分、ボディ部分、フッタ部分をグループ化するタグ。これを書くことでブラウザがヘッダとフッタを先読みして表示したり、ヘッダとフッタを固定表示したままボディ部分だけスクロールしたりできるようになる。

サンプルコード

<table border="1">
    <thead>
        <tr>
            <th>ID</th>
            <th>商品名</th>
            <th>値段</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th>1</th>
            <th>ケーキ</th>
            <th>480</th>
        </tr>
        <tr>
            <th>2</th>
            <th>アイス</th>
            <th>250</th>
        </tr>
        <tr>
            <th>3</th>
            <th>パフェ</th>
            <th>760</th>
        </tr>
    </tbody>
    <tfoot>
        <tr>
            <th>~</th>
            <th>~~</th>
            <th>~~~</th>
        </tr>
    </tfoot>
</table>
ID 商品名 値段
1 ケーキ 480
2 アイス 250
3 パフェ 760
~~ ~~~

備考

スクロールのやり方は下記のサイト様参照

ヘッダを固定してスクロールする方法 | CSS解説

参考にしたサイト

theadタグとは|コーディングのプロが作るHTMLタグ辞典

ヘッダを固定してスクロールする方法 | CSS解説

pykakasiの使い方

はじめに

ファイル操作などをする際、ファイル名に日本語が使えないという場面に多々出くわす。
そういう時に使用する「pykakasi」についてメモ。

pykakasiとは

ひらがな、カタカナ、漢字、もしくはこれらの複合文をローマ字に変換するpythonライブラリ。

ソースコード

以下のようにして使う。

import pykakasi

kakasi = pykakasi.kakasi()  # インスタンスの作成
kakasi.setMode('H', 'a')  # ひらがなをローマ字に変換するように設定
kakasi.setMode('K', 'a')  # カタカナをローマ字に変換するように設定
kakasi.setMode('J', 'a')  # 漢字をローマ字に変換するように設定
conversion = kakasi.getConverter()  # 上記モード設定の適用

print(conversion.do('ねこ'))
print(conversion.do('ネコ'))
print(conversion.do('猫'))
print(conversion.do('猫 ねこ ネコ neko'))

実行結果は以下となる。

neko
neko
neko
neko neko neko neko

注意点

例えばローマ字を漢字にしたりはできない。これを指定するとエラーになる。

OKパターン
ひらがな or カタカナ or 漢字→ローマ字
カタカナ or 漢字 → ひらがな
ひらがな or 漢字 → カタカナ
NGパターン
ひらがな or カタカナ or ローマ字 → 漢字
ローマ字 → ひらがな
ローマ字 → カタカナ

参考にしたサイト

RURUK BLOG

Flaskを使って画像データをアップロードする方法

はじめに

Flaskを使って画像データをアップロードして、特定のフォルダに保存するまでのプログラムをメモしておく。
upload.htmlに任意の画像をアップロードすると、サーバー側のstatic/imageフォルダに保存される。

フォルダ構成

まずSampleフォルダを作成し、直下にapp.pyを作成する。
staticフォルダを作成し、中にimageフォルダを作成。
templatesフォルダを作成し、中にupload.htmlとuploaded_file.htmlを作成する。

Sample
  ├─static
  │  └─image
  ├─templates
  │  ├─upload.html
  │  └─uploaded_file.html
  └─app.py

アップロード用ページ「upload.html」の作成

アップロードは<form>~</form>で実行する。
actionに指定している{{url_for('upload')}}はapp.py内のuploadメソッドに対応するURLという意味。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>アップロードページ</title>
</head>
<body>
    <h1>アップロード</h1>
    <form action="{{url_for('upload')}}" method="POST" enctype="multipart/form-data">
        <input type="file" name="example">
        <input type="submit" value="アップロード">
    </form>

</body>
</html>

app.pyの作成

def uploadメソッドでアップロードページからの画像を受け取る。

from flask import Flask, render_template, request, url_for, redirect
import os

app = Flask(__name__)

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    # URLでhttp://127.0.0.1:5000/uploadを指定したときはGETリクエストとなるのでこっち
    if request.method == 'GET':
        return render_template('upload.html')
    # formでsubmitボタンが押されるとPOSTリクエストとなるのでこっち
    elif request.method == 'POST':
        file = request.files['example']
        file.save(os.path.join('./static/image', file.filename))
        return redirect(url_for('uploaded_file', filename=file.filename))


@app.route('/uploaded_file/<string:filename>')
def uploaded_file(filename):
    return render_template('uploaded_file.html', filename=filename)

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

upload()メソッドの解説

  1. @app.route('/upload')はURLと処理を対応づける処理を担っている。これはルーティングと呼ばれる。記載したURL「/upload」にアクセスするとこのメソッドが呼ばれる。
  2. methods=['GET', 'POST']の部分は、このURLが受け入れられるHTTPリクエストはGETリクエストとPOSTリクエストですよという意味。HTTPメソッド名をリストで渡すことで指定する。これを書かないとGETリクエストしか受け付けない。今回は画像をアップロードするので、POSTリクエストを使用する。
  3. request.methodで何のHTTPリクエストが来たかが確認できる。もしGETリクエストだった場合は、 URL 「/upload」を直接指定してアクセスしてきたということであるから、アップロード用ページを表示する。POSTリクエストだった場合は、アップロード用ページから送信ボタンが押され、画像が送られてきたということなので、画像保存処理を実行する。
  4. return render_template('upload.html') は同階層のtemplatesフォルダ内のupload.htmlをクライアント側へ返すという意味。render_templateメソッドはflaskの機能で、templatesフォルダ内のhtmlファイルを引用することができる。print(render_template('upload.html'))とやってみると```<!DOCTYPE html>から始まるhtmlファイルがそのまま出力されていることが確認できる。
  5. request.files['example']でPOSTリクエストで送られてきたファイルを参照できる。 'example'というのはupload.htmlの画像入力用の<input type="file" name="example">にて指定したname属性に対応する。request.files['example']はFileStorage型であり、辞書型を継承しているので同じように要素を指定する。
  6. `redirect()メソッドは、url_for('uploaded_file')メソッドに遷移しろという命令。uploaded_fileメソッドに対応するURLは以下に続けてstring型の文字列filenameを指定する必要がある。これはurl_forの第2引数で指定する。

uploaded_file()メソッドの解説

  1. 上記のuploadメソッドで画像保存後、redirectでこのuploaded_fileメソッドが呼ばれる。 これは単純にrender_templateメソッドでuploaded_file.htmlを返しているだけ。

アップロード完了時に表示するページを作成

アップロード完了と表示するのと、画像名を表示している。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>アップロード完了</title>
</head>
<body>
    <h1>ファイルアップロード完了</h1>
    <h2>{{filename}}</h2>
</body>
</html>
  1. {{ }}っていうのはflaskのjinja2という機能で、pythonプログラムから変数を受け取ることができる。
    受け渡す変数は本htmlファイルを呼び出す際の、render_template()メソッドの第2引数以下に指定する。(第1引数はhtmlファイル名)。例えばrender_template('uploaded_file.html', var1="hoge", var2=111)と書くと、uploaded_file.html内でvar1とvar2が表示できるようになる。使うときは二重中括弧で囲う。

まとめ

忘れないうちにメモしておいた。

参考にしたページ

[python]Flask入門 Jinja2を用いてhtmlを動的に表示 | しげっちBlog

いまさらながら Flask についてまとめる 〜Routing〜 - 適当おじさんの適当ブログ

Flaskでファイルアップロード - iMind Developers Blog

GETリクエスト、POSTリクエストとは何なのか

はじめに

GETリクエスト、POSTリクエストって何ぞやとなったのでメモ

HTTPリクエストとは

webにおいて、サーバとクライアント間の通信はHTTPというプロトコルを用いている。
クライアントからサーバへの要求は「HTTPリクエスト」、サーバからクライアントへの応答は「HTTPレスポンス」と呼ばれる。

HTTPリクエス

HTTPリクエストはテキストデータで、
 1. HTTPリクエスト部 (常に1行、リクエストの種類をメソッドで指定する)
 2. HTTPヘッダー部 (リクエストの詳細情報。複数行)
 3. HTTPボディ部 (必要なときだけ)
という構成になっている。

HTTPリクエスト部ではサーバーに送るリクエストの種類を指定する。情報を取り出したいのか、情報を送りたいのか。 リクエストの種類はメソッドの形で指定する。
HTTPヘッダー部はリクエストに関連する各種の補足情報を指定する。サーバー名、ブラウザーの種類、接続維持の指定など。
HTTPボディ部は主に「情報を送る」リクエストの際、送りたい情報を格納する目的で使う。

HTTPリクエストメソッドの種類

メソッドは全部で8種類。そのうち良く使われるのがGETメソッドとPOSTメソッド。
これらを使用したリクエストをGETリクエスト、POSTリクエストと呼ぶ。

メソッド 意味
GET サーバ上のファイル本体を取り出す
POST サーバ上のプログラムにデータを送付する

リクエストパラメータの付与

クライアントからサーバへリクエストするとき、商品IDなどの付加情報(リクエストパラメータ)を送る必要がある。 この方法がGETリクエストとPOSTリクエストで異なる。

GETリクエス

Webサーバに対してクライアントからGETメソッドでリクエストパラメータを送信するには、
URLに「キー=値」で指定する。リクエストパラメータが複数存在する場合は「&」で連結する。
http://localhost/SamplePHP/http_get_result.php?id=111&name=test

このようにURLを指定してGETで送った際のHTTPリクエストを見てみる。
一行目のHTTPリクエスト部にリクエストパラメータが付与されていることがわかる。

GET /SamplePHP/http_get_result.php?id=111&name=test HTTP/1.1
Host: localhost
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
Sec-Fetch-Dest: document
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Referer: http://localhost/SamplePHP/http_get_index.php
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8

GETリクエストはリクエストパラメータがURLに付与されるため、送信内容が丸見えとなる。URLはコピーしてどこかに張り付ければ簡単に共有できてしまう。個人情報やパスワードをGETリクエストで送ってしまうとURL上に記載されてしまうため良くない。
更に、URLには長さの制限があるため、長い文章や画像データのような膨大なデータをURLにくっ付けて送ることはできない。
GETリクエストは本来、リソースを取り出すことが目的なメソッドなので、パラメータの送信は簡易的な実装になっている。

POSTリクエス

Webサーバに対してクライアントからPOSTメソッドでリクエストパラメータを送信するには、HTMLのform要素から送信する。

<form action="http_post_result.php" method="post">
    id:<input type="text" name="id" /><br />
    名前:<input type="text" name="name"/><br />
    <input type="submit" value="送信" />
</form>


ブラウザで表示すると以下のようになる。

id:
名前:

送信ボタンを押すとPOSTリクエストが送られる。

idを「111」、名前を「test」にして送信ボタンを押した際のHTTPリクエストは次のようになる。

POST /SamplePHP/http_post_result.php HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 16
Cache-Control: max-age=0
Origin: http://localhost
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
Sec-Fetch-Dest: document
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Referer: http://localhost/SamplePHP/http_post_index.php
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.9,en;q=0.8

id=111&name=test

最後のHTTPボディ部にリクエストパラメータが格納されている。 このPOSTリクエストどこに送られるのかというと、HTMLにてaction=で指定した「http_post_result.php」に送られる。

Webサーバ側の「http_post_result.php」内にて、下記のようにして要素を取り出すことができる。

$_POST['id']
$_POST['name']

ちなみにこれは受け取る側がphpだった場合。pythonだとまた違う表記となる。 また、上記のformをGETリクエストとして送信することができる。その場合はHTMLでmethod="get"と指定する。

まとめ

GETリクエスト、POSTリクエストとはHTTPリクエストの一種。
GETリクエストはページを取得するためのリクエストであり、表示したい商品や動画のid、ページ番号等を送りたいときに使用する。
POSTリクエストは情報を送信するためのリクエストであり、ログインIDや個人情報などを送りたいときに使用する。

参考にしたサイト

Web初心者のためのHTTP、REST入門 - MyEnigma

「HTTP」の仕組みをおさらいしよう(その1):リトライ! 触って学ぶTCP/IP(2)(3/3 ページ) - @IT

HTTP GETとPOSTの違い - ITを分かりやすく解説

GET/POSTの違い&実際の使い分け方

zenn.devPOSTパラメータを扱えるようにする|伸び悩んでいる3年目Webエンジニアのための、Python Webアプリケーション自作入門

HTMLのフォームからPOSTで送信されたデータの受け取り方を現役エンジニアが解説【初心者向け】 | TechAcademyマガジン