目次
Djangoの1行入力サンプル
一番使う頻度の高い「一行テキスト入力」とバリデーションのサンプルです。
環境は Windows + anaconda です。
Djangoの一行入力には「DateField」・「DecimalField」などの種類があります。
ですが。
djangoで用意されているFieldを使うと、型チェックがサイバーサイドになります。
そのため、max-length等のチェックとそれ以外のチェックで、エラーメッセージの出方が変わってしまいます。
それをさけるため、今回は、これを「CharField」一択でやる前提で説明します。
HTML5から<input>タグで「pattern=」で正規表現を指定して入力形式制限できるので、これを利用して「数値」「カナ」「日付」など任意のデータ形式に絞ることで、、エラーメッセージの出し方の統一感がだす方法をとります。
1行テキスト入力1:基本形
labels = ['テキスト','パスワード','整数','カタカナ','Email','西暦日付'] one = forms.CharField( label=labels[0], required=True, disabled=False, max_length=30, min_length=1, widget=forms.TextInput(attrs={ 'id': 'one', 'placeholder':'特に入力文字に制限ありません',}))
patternは使ってません。
「id」と、入力例や注意事項を初期表示する「placeholer」を追加してます。
1行テキスト入力2:パスワード
labels = ['テキスト','パスワード','整数','カタカナ','Email','西暦日付'] two = forms.CharField( label=labels[1], required=True, disabled=False, max_length=20, min_length=1, widget=forms.PasswordInput(attrs={ 'id': 'two', 'placeholder':'半角英数字_-@のみ入力可能です。', 'pattern':'^[A-Za-z0-9_@-]+$'}))
パスワードに利用可能な文字種の制限を、patternに正規表現で行っています。
1行テキスト入力3:整数
labels = ['テキスト','パスワード','整数','カタカナ','Email','西暦日付'] three = forms.CharField( label=labels[2], required=False, disabled=False, max_length=10, min_length=1, widget=forms.TextInput(attrs={ 'id': 'three', 'placeholder':'数字のみ入力可能です。', 'pattern':'^[0-9]+$'}))
数字のみに制限をかけるpatternを設定しています。
1行テキスト入力4:カタカナ(全角・半角)
labels = ['テキスト','パスワード','整数','カタカナ','Email','西暦日付'] four = forms.CharField( label=labels[3], required=False, disabled=False, max_length=30, min_length=1, widget=forms.TextInput(attrs={ 'id': 'four', 'placeholder':'カタカナのみ入力可能です。', 'pattern':'^[ヲ-゚ァ-ヴ]+$'}))
半角カナと全角カナ両方のみ許可するpatternを設定しています。
全角カナだけにするときは、「ァ-ヴ」だけでいいです。
1行テキスト入力5:EMailアドレス
labels = ['テキスト','パスワード','整数','カタカナ','Email','西暦日付'] five = forms.CharField( label=labels[4], required=False, disabled=False, max_length=100, min_length=1, widget=forms.EmailInput(attrs={ 'id': 'five', 'placeholder':'Eメールアドレス(ex:xxxxxx@xxx.xx.jp)',}))
patternではなく、DjangoのEmailInputを使ってバリデーションをしている例です。
1行テキスト入力6:日付
labels = ['テキスト','パスワード','整数','カタカナ','Email','西暦日付'] six = forms.CharField( label=labels[5], required=False, disabled=False, max_length=10, min_length=8, widget=forms.TextInput(attrs={ 'id': 'five', 'placeholder':'西暦4ケタ月日(ex:2019/1/1)', 'pattern':'(19[0-9]{2}|20[0-9]{2})/([1-9]|1[0-2])/([1-9]|[12][0-9]|3[01])'}))
一応、ある程度日付らしい入力しか受け付けないようにpatternを組んでます。
ですが、完璧ではありません。
うるう年とかもあるし、日付を正規表現だけでチェックするのは不可能です。
考え方としては、ある程度はじければよくて、例えば、2018/2/31なんてのは通ってしまうのですが、その辺はサーバーサイドでチャックすればいいや・・って、感覚です。
patternの正規表現を工夫すれば色々やれますが、サンプルなので、このへんで。
一行テキスト入力を使った入力画面サンプル
上記で作成したFieldを表示するHTMLです。
django-widget-tweaksインストール・設定されている必要があります。
まだの場合は
でインストールして、settings.pyのINSTALLED_APPSブロックに追加しておきます。
INSTALLED_APPS = [
'widget_tweaks',
]
サンプルHTML
exsample.htmlという名前にしました。
{% load static %} {% load widget_tweaks %} <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="https://fonts.xz.style/serve/inter.css" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css" /> </head> <body> <table> <tr> <td><h1>Hello World!!</h1></td> </tr> <tr> <td><img src="{% static "images/k.png" %}" /></td> </tr> <form action="" method="post"> {% csrf_token %} <h1>ビルトインフォームサンプル</h1> {% if ret %} {% for key,val in results.items %} <tr> <td><h4>{{ key }}</h4></td> <td><h4>{{ val }}</h4></td> </tr> {% endfor %} {% else %} {% for key,val in errors.items %} <tr> <td> <h2>{{ key }} : {{ val }}</h2> </td> </tr> {% endfor %} {% for field in form %} <tr> <td><h4>{{ field.label_tag }}</h4></td> <td>{{ field|add_class:"form-control font-weight-bold" }}</td> </tr> {% endfor %} <tr> <td colspan="2"> <button type="submit" class="btn btn-primary">送信する</button> </td> </tr> {% endif %} </form> </table> </body> </html> {% endblock %}
フィールドの数が増えたり減ったりしたときにHTMLを書き直さなくてよいように、formからfieldを取り出して、ループでまわして処理してます。
{% for key,val in results.items %} <tr> <td><h4>{{ key }}</h4></td> <td><h4>{{ val }}</h4></td> </tr> {% endfor %}
同様に、サーバー側でエラーチェックやった結果は、「errors」という辞書にセットしていて、入力された結果は「results」という辞書にセットしているので、こちらも数が増えてもHTMLを修正しなくて良いように、ループで処理してます。
{% for key,val in errors.items %} <tr> <td> <h2>{{ key }} : {{ val }}</h2> </td> </tr> {% endfor %}
Views.py:サーバー側バリデーションと表示の切り分け
自分流ですが、バリデーションはviews.pyの中でまとめてやってます。
views.pyのソース全体です。
from django.shortcuts import render from . import forms from django.template.context_processors import csrf import datetime def demo(request): # エラーメッセージをセットする辞書 errors = {} # 入力結果を格納する辞書 results = {} # 入力がすべてOKなら「OK」をセットする。HTML側の判断でも使う。 ret = '' # エラーがひとつでもあればTrueにする is_error = False # Submitボタンからの遷移の場合 if request.method == 'POST': # 入力されたデータの受取 results['one'] = request.POST["one"] results['two'] = request.POST["two"] results['three'] = request.POST["three"] results['four'] = request.POST["four"] results['five'] = request.POST["five"] results['six'] = request.POST["six"] # 入力データを初期値にしてformを生成。 form = forms.UserForm(initial={ 'one': results['one'], 'two': results['two'], 'three': results['three'], 'four': results['four'], 'five': results['five'], 'six': results['six'] }) # サーバー側バリデーションの例 if _is_password_error(results['two']): errors['パスワード'] = '認証できませんでした。' is_error = True if _is_date_error(results['six']): errors['西暦日付'] = 'ただしい日付ではありません。' is_error = True # エラーがあればエラーメッセージをだして再入力 if is_error: ret = '' c = {'form': form, 'errors': errors, 'ret': ret, } c.update(csrf(request)) # エラーがなければ、入力内容を表示しておしまい。 else: ret = 'OK' c = {'results': results, 'ret': ret, } return render(request, 'exsample.html', c) # 初期表示の場合(GET) else: # 初期表示の時にセッションもクリアする request.session.clear() # フォームの初期化 form = forms.UserForm() c = {'form': form, 'ret': ret, } # CFRF対策(必須) c.update(csrf(request)) return render(request, 'exsample.html', c) # サーバー側バリデーション。認証のかわりのつもり def _is_password_error(value): # 認証チェックの代わりにngがはいってればエラーにする if 'ng' in value: return True else: return False # サーバー側バリデーション。日付に変換できるかを試している def _is_date_error(datestring): try: tdatetime = datetime.datetime.strptime(datestring, '%Y/%m/%d') datetime.date(tdatetime.year, tdatetime.month, tdatetime.day) return False except BaseException: return True
ちょっと長いですが、複数のフォームを一度に書いているのと、バリデーションのロジックが追加されているだけなので、特に補足しません。
URL生成の設定
今回は「http://localhost:8000.exp/」のURLでアクセスするようにしました。
htmlが「exsample」メソッドがviews.pyのdemoですから、以下をurls.pyに追加します。
path('exp/', views.demo, name='exsample'),
実行結果イメージ
「python manage.py runserver」でサーバーを実行し「http://localhost:8000.exp/」でアクセスします。
さっくりとハードコピーだけ貼り付けておきます。
初期表示です。
プレースフォルダが表示されてます。
クライアントサイドの型チェックエラーはこんな感じ。
数字の正規表現エラーになってます。
patternでひっかかると、このメッセージになります。
requireとかと同じ感じなので、なんとなく統一感はでます。
サーバーサイドエラーは以下のようなメッセージになってます。
エラーメッセージを赤にするの忘れてました(笑)
入力OK時の画面です。
まあ。こんなもんかな。
参考:forms.pyのソース全文
from django import forms class UserForm(forms.Form): labels = ['テキスト', 'パスワード', '整数', 'カタカナ', 'Email', '西暦日付'] one = forms.CharField( label=labels[0], required=True, disabled=False, max_length=30, min_length=1, widget=forms.TextInput(attrs={ 'id': 'one', 'placeholder': '特に入力文字に制限ありません', })) two = forms.CharField( label=labels[1], required=True, disabled=False, max_length=20, min_length=1, widget=forms.PasswordInput(attrs={ 'id': 'two', 'placeholder': '半角英数字_-@のみ入力可能です。', 'pattern': '^[A-Za-z0-9_@-]+$'})) three = forms.CharField( label=labels[2], required=False, disabled=False, max_length=10, min_length=1, widget=forms.TextInput(attrs={ 'id': 'three', 'placeholder': '数字のみ入力可能です。', 'pattern': '^[0-9]+$'})) four = forms.CharField( label=labels[3], required=False, disabled=False, max_length=30, min_length=1, widget=forms.TextInput(attrs={ 'id': 'four', 'placeholder': 'カタカナのみ入力可能です。', 'pattern': '^[ヲ-゚ァ-ヴ]+$'})) five = forms.CharField( label=labels[4], required=False, disabled=False, max_length=100, min_length=1, widget=forms.EmailInput(attrs={ 'id': 'five', 'placeholder': 'Eメールアドレス(ex:xxxxxx@xxx.xx.jp)', })) six = forms.CharField( label=labels[5], required=False, disabled=False, max_length=10, min_length=8, widget=forms.TextInput(attrs={ 'id': 'five', 'placeholder': '西暦4ケタ月日(ex:2019/1/1)', 'pattern': '(19[0-9]{2}|20[0-9]{2})/([1-9]|1[0-2])/([1-9]|[12][0-9]|3[01])'}))
ではでは。