読者です 読者をやめる 読者になる 読者になる

似非プログラマのうんちく

「似非プログラマの覚え書き」出張版

巳年じゃないけど Python やろうぜ(その 12)

Python Django

フォームとビューの作成

まずはビューの作成に必要なフォームから作ります。既に Django バリバリ使ってる人なら「ModelForm 使えば楽やろ」って言うと思いますが、実は ModelForm には「(自動生成される id などの)AutoField はフォーム部品化の対象外」だったりするのと、今回はいろいろと属性をいじりたいということもあって、「それだったら一から作った方がいいんじゃね ?」ということで見送りとなりました… orz

というわけで forms.py から。

from django import forms
from .models import Customer


class CustomerForm(forms.Form):
    customer_id = forms.IntegerField(
        label='顧客ID',
        widget=forms.TextInput(attrs={'size': 5}),
        min_value=1,
    )
    customer_name = forms.CharField(
        required=False,
        label='顧客名',
        widget=forms.TextInput(attrs={'size': 20, 'readonly': True}),
    )
    customer_phone = forms.CharField(
        required=False,
        label='連絡先',
        widget=forms.TextInput(attrs={'size': 20, 'readonly': True}),
    )

label は以前ご説明の通りですが、それ以外は初お目見えですね。

IntegerField は整数値を入力するべきフォームになります。widget はデフォルトでは NumberInput*1なのですが、ご指名(?)で TextInput に登場していただきます。attrs には辞書形式で input 要素に指定する属性と属性値の組み合わせを指定します。このコードの例だと

<input id="id_customer_id" name="customer_id" size="5" type="text" required />

と同等になります。min_value は後でバリデーションチェックに使われます。テーブルの ID なので正の数でないとおかしいですよね。だから最小値は 1 です。

customer_namecustomer_phonelabel 以外は同じですね。widget はデフォルトの TextInput なのですが、属性を付与したいので敢えて指定します。required はデフォルトは True なのですが、この二つは検索結果を埋め込むためのものなので、送信時に空になっていても良いように False にします。また readonly 属性を付けて、このフォームからは編集できない*2ようにします。


次は views.py です。

from django.shortcuts import get_object_or_404, render
from .forms import CustomerForm
from .models import Customer

# Create your views here.


def search(request):
    if request.method == 'POST':
        form = CustomerForm(request.POST, label_suffix='')
        if form.is_valid():
            customer_id = form.cleaned_data['customer_id']
            customer = get_object_or_404(Customer, pk=customer_id)
            data = {
                'customer_id': customer.id,
                'customer_name': customer.name,
                'customer_phone': customer.phone,
            }
            form = CustomerForm(data, label_suffix='')
    else:
        form = CustomerForm(label_suffix='')
    return render(request, 'customer_search/search.html', {
        'form': form,
    })

get_object_or_404 は一連のよく使われる流れを簡略化したショートカットです。これは

try:
    customer = Customer.objects.get(pk=customer_id)
except Customer.DoesNotExist:
    raise Http404("Customer does not exist")

を簡略した書き方です。入力された ID を元に検索をかけて、拾った値をフォームにセットしていきます。label_suffix は今回ちょっとお遊びで付けてみました。デフォルトだとコロンが付きますが、何も付けないように空文字をセットしてあります。

最後はテンプレート。

<!doctype html>
<html>
  <head>
    <title>顧客検索</title>
  </head>
  <body>
    <h1>顧客検索</h1>
    <form method="post" action=".">
      {% csrf_token %}
      <table>{{ form }}</table>
      <input type="submit" value="検索">
    </form>
  </body>
</html>

form はデフォルトだと as_table()レンダリングされる*3ので、table 要素の中身として記述します。それ以外は特に変わったところはありません。

django_test/urls.py

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^hello/', include('hello.urls')),
    url(r'^customer_search/', include('customer_search.urls')),  # 追加
]

customer_search/urls.py (新規作成)

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.search),
]

次回、ロギングの話をさらっとしてから実際に動かしてみます。

*1:localize がデフォルトで False のため。localize を True にすると TextInputになるようです。

*2:あくまでもユーザーインターフェース上の話であって、これによってデータが完全に守られるというわけではないことには注意が必要です。

*3:公式ドキュメントには「form.as_table() は print(form) と完全に同じです」と書いてある。