Django - フォームを作成する (初歩)

概要

  • ドキュメントのコードを参考に初歩的なフォームを作成する
  • チュートリアルで作成した polls アプリケーションを改造していく方針で
  • (Djangoのドキュメントはドキュメントであって、チュートリアルではありませんという声が聞こえた気がした)
  • 低勾配の私的チュートリアルを目指す

作りたいもの

フォームに名前を入力すると、"Hello, <名前>!" と表示してくれるページ

はじめに URL ありき

まずは URL を定義する。
/polls/your-name/ にユーザー名をPOSTする。

# polls/urls.py
from django.urls import path


# 名前空間を設定する
app_name = "polls"

urlpatterns = [

    # その他の URL 定義
    # ...

    # ex. /polls/your-name/
    path("your-name/", views.your_name, name="your_name"),
]

コントローラ (views.pyのコードのこと) からレスポンスを返させる

urls.py にて、views.your_name() を呼び出すことになっているので、views.py において定義する必要がある。

# polls/views.py
from django.http import HttpResponse

# その他のコード
# ..

def your_name(request):
    """'polls/your-name/'にアクセスするとこのメソッドが呼び出される"""
    msg = "Hello, %s" % request.POST["your_name"]
    return HttpResponse(msg)

ビューを作成する

views.py の your_name() において、request.POST["your_name"] という
キーでアクセスしているので、input フォームの name 属性を "your_name" でそろえる。また、urls.py においてすでに polls/your-name/ という URL を定義したので form タグの action 属性の値も揃える。

<!-- templates/polls/index.html -->

<div id="sample-form">
    <form action="/polls/your-name/" method="POST">
        {% csrf_token %}
        <label for="your name">Your name:</label>
        <input type="text" name="your_name">
        <input type="submit" value="OK">
    </form>
</div>

(開発|学習)時に意識(してい|すべきと思われ)ること

WEBフレームワークを学ぶコツは、必然性を意識することだと思う。

福岡から東京に行きたいならその中間の過程を端折ってくれるのがフレームワークだが、
逆にどこかに行こうとしているということは(必然的に)方角くらいの情報はいるよな、というようなところから攻めの理解ができる。

あと、URLを先に定義してしまうこと。
機能を考える → URLを考える → urls.py でコントローラのメソッド名を指定 →
views.py でメソッド定義 → ビューにおけるタグ属性名が決まる ... (必ずしもこの通りに開発しないが)
という流れが自然と思われる。

平滑化 (移動平均) フィルタ

概要

  • 画素の色の違いを滑らかにする (周囲の画素と平均する)

イメージ

黒板の文字を黒板消しで消そうとしたが、完全には消えていない状態 (= チョークの粉を引き延ばした状態?)

分かった部分

カーネル適用部分の中心部分を注目画素という。畳み込みを行ったときの
合計値が注目画素 (カーネルで処理しようとしている部分全体ではない!) の画素値になる。3x3カーネルを1回適用しても画素値が変化するのは1マスだけ

実装してみる (Python)

import numpy as np
from matplotlib import pyplot as plt


def smooth(img, i, j, kernel):
    """
    カーネルの左上のインデックスが (i, j) となるよう
    カーネルを適用する
    """ 
    
    # カーネルのサイズ
    rows, cols = kernel.shape

    # カーネルで平滑化する
    kerneled = img[i:i+rows, j:j+cols] * kernel
        
    # 注目画素の位置 (i, jからのギャップ)
    interest_gap = int(rows / 2)    # 切り捨て

    # 注目画素の色を変える
    img[i+interest_gap, j+interest_gap] = np.sum(kerneled)

    return img


def walk(img, kernel):
    """画像全体にカーネル (フィルタ) を適用する"""

    img_rows, img_cols = img.shape
    k, _ = kernel.shape

    # 画像よりも大きなフィルタは適用できない
    # (フィルタの縦、横いずれかが画像より大きい場合はエラー)
    if img_rows < k or img_cols < k:
        emsg = "kernel size must be less than the size of the image"
        raise ValueError(emsg)

    # 画像全体にカーネルを適用する
    # カーネルは縦、横にそれぞれ、縦(横) - k + 1だけ動ける
    for i in range(img_rows - (k - 1)):
        for j in range(img_cols - (k - 1)):
            img = smooth(img, i, j, kernel)

    return img


def main():
    """平滑化フィルタを適用する"""

    # 画像データ
    img = np.array([
        [1, 1, 1, 0, 0, 0],
        [1, 1, 1, 0, 0, 0],
        [1, 1, 1, 0, 0, 0],
        [1, 1, 1, 0, 0, 0],
        [1, 1, 1, 0, 0, 0],
    ]).astype("float32")

    # 元の画像をコピーしておく
    img_original = img.copy()

    # カーネル
    kernel3 = np.ones((3, 3), np.float32) / 9
    kernel5 = np.ones((5, 5), np.float32) / 25

    # カーネルを選択する
    kernel = (kernel3, kernel5)[0]

    # カーネルの左上の座標 (インデックス) が
    # (i, j) の位置になるようカーネルを適用する
    img = walk(img, kernel)

    # 確認用
    check = False
    if check:
        rnd = np.vectorize(lambda bit: np.round(bit, 1))
        print(rnd(img))

    # 画像を表示する
    plot_images = True
    if plot_images:
        titles = ("Original", "Averaging")
        for i, image in enumerate((img_original, img)):
            plt.subplot(1, 2, i + 1)
            plt.imshow(image, cmap=plt.cm.gray)
            plt.title(titles[i])

        plt.tight_layout()
        plt.show()


if __name__ == "main":
    main()

動作結果

境界線をぼかそうとしているのが分かる。

f:id:zdassen:20170808140259p:plain