【Django】バリデーションチェックを使いこなす

バリデーションはフォームなどの入力項目に条件を設定し、その条件を満たしているかどうかを確認する機能です。

forms.Formのバリデーション

まずはモデルを使わない一般的なフォームでのバリデーションについて見てみましょう。
hello/forms.py

class HelloForm(forms.Form):
  name = forms.CharField(label='Name', \
    widget=forms.TextInput(attrs={'class':'form-control'}))
  mail = forms.EmailField(label='Email', \
    widget=forms.EmailInput(attrs={'class':'form-control'}))
  gender = forms.BooleanField(label='Gender', required=False, \
    widget=forms.CheckboxInput(attrs={'class':'form-check'}))
  age = forms.IntegerField(label='Age', \
    widget=forms.NumberInput(attrs={'class':'form-control'}))
  birthday = forms.DateField(label='Birth', \
    widget=forms.DateInput(attrs={'class':'form-control'}))

gender部分の引数に下記の設定があります。
これこそがバリデーションの設定なのです。Falseにすることでバリデーション必須ではなくなります。

required=False

それでは、チェック用のテンプレート check.htmlを作成して使ってみましょう。
/hello/templates/hello/check.html

{% load static %}
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>{{title}}</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css" 
  rel="stylesheet" crossorigin="anonymous">
  </head>
<body class="container">
  <h1 class="display-4 text-primary">
    {{title}}</h1>
  <p>{{message|safe}}</p>
  <form action="{% url 'check' %}" method="post">
    {% csrf_token %}
    {{ form.as_table }}
    <input type="submit" value="click"
        class="btn btn-primary mt-2">
  </form>
</body>
</html>

urlpatternsの追記
/hello/urls.py

from django.urls import path
from . import views

urlpatterns = [
・・
  path('check', views.check, name='check'), #追加
]

CheckFormの作成
/hello/forms.py

from django import forms
from.models import Contact

class ContactForm(forms.ModelForm):
  class Meta:
    model = Contact
    fields = ['name','mail','gender','age','birthday']

class FindForm(forms.Form):
  find = forms.CharField(label='Find', required=False, \
    widget=forms.TextInput(attrs={'class':'form-control'}))
  
class HelloForm(forms.Form):
  name = forms.CharField(label='Name', \
    widget=forms.TextInput(attrs={'class':'form-control'}))
  mail = forms.EmailField(label='Email', \
    widget=forms.EmailInput(attrs={'class':'form-control'}))
  gender = forms.BooleanField(label='Gender', required=False, \
    widget=forms.CheckboxInput(attrs={'class':'form-check'}))
  age = forms.IntegerField(label='Age', \
    widget=forms.NumberInput(attrs={'class':'form-control'}))
  birthday = forms.DateField(label='Birth', \
    widget=forms.DateInput(attrs={'class':'form-control'}))

class CheckForm(forms.Form): #追加
  str = forms.CharField(label='Name',\
    widget=forms.TextInput(attrs={'class':'form-control'}))

views.pyにCheck関数を作成します。
/hello/views.py

from .forms import CheckForm    #☆

def check(request):
  params = {
    'title': 'Hello',
    'message':'check validation.',
    'form': CheckForm(),
  }
  if (request.method == 'POST'):
    form = CheckForm(request.POST)
    params['form'] = form
    if (form.is_valid()):
      params['message'] = 'OK!'
    else:
      params['message'] = 'no good.'
  return render(request, 'hello/check.html', params)

コードの以下の部分にてバリデーションチェックを行っています。

    if (form.is_valid()):
      params['message'] = 'OK!'
    else:
      params['message'] = 'no good.'

webブラウザにてアクセスします。
Nameが未入力だとエラーメッセージが表示されます。

半角スペースだけを入力すると、「This field is required.」のエラーメッセージが表示されます。

どんなバリデーションがあるか?

CharFieldのバリデーション

最も基本となるテキスト入力フィールド「CharField」に用意されているバリデーションには下記のものがあります。

#必須項目チェック
require

#最小文字数、最大文字数
min_length, max_length

#空の入力許可するか
empty_value

それでは、min_length, max_lengthのバリデーションを試してみましょう。
/hello/forms.py

class CheckForm(forms.Form):
  empty = forms.CharField(label='Empty', empty_value=True, \
    widget=forms.TextInput(attrs={'class':'form-control'}))
  min = forms.CharField(label='Min', min_length=10, \
    widget=forms.TextInput(attrs={'class':'form-control'}))
  max = forms.CharField(label='Max', max_length=10, \
    widget=forms.TextInput(attrs={'class':'form-control'}))

MinもMaxも10文字を指定しました。Maxは10文字以下しか入力できなくなります。
webブラウザにてアクセスします。

IntegerField/FloatFieldのバリデーション

「IntegerField」に用意されているバリデーションには下記のものがあります。

required
min_value, max_value

それでは、min_value, max_valueのバリデーションを試してみましょう。
forms.pyのCheckFormクラスを修正します。
min_value=100、max_value=1000を指定してあります。
/hello/forms.py

class CheckForm(forms.Form):
  required = forms.IntegerField(label='Required', \
    widget=forms.NumberInput(attrs={'class':'form-control'}))
  min = forms.IntegerField(label='Min', min_value=100, \
    widget=forms.NumberInput(attrs={'class':'form-control'}))
  max = forms.IntegerField(label='Max', max_value=1000, \
    widget=forms.NumberInput(attrs={'class':'form-control'}))

webブラウザにてアクセスします。

日付関連のバリデーション

DateField、TimeField、DateTimeFieldというった日付関連のフィールドには、requiredの他にフォーマットに関するバリデーションが設定されています。この日時のフォーマットは「input_formats」という引数で指定することができます。

input_formats = [フォーマット1, フォーマット2, ・・]

フォーマットの書き方

%y年を表す数字
%m月を表す数字
%d日を表す数字
%H時を表す数字
%M分を表す数字
%S秒を表す数字

forms.pyのCheckForm関数を下記のように修正します。
dateは1~31の数値、Timeは「時:分」、DateTimeは「年-月-日 時:分」というフォーマットにして指定します。
/hello/forms.py

class CheckForm(forms.Form):
  date = forms.DateField(label='Date', input_formats=['%d'], \
    widget=forms.DateInput(attrs={'class':'form-control'}))
  time = forms.TimeField(label='Time', \
    widget=forms.TimeInput(attrs={'class':'form-control'}))
  datetime = forms.DateTimeField(label='DateTime', \
    widget=forms.DateTimeInput(attrs={'class':'form-control'}))

webブラウザにてアクセスします。

全てエラーになる場合

入力OKの場合

バリデーションを追加する

デフォルトのバリデーション以外に、「こういうときにバリデーションエラーになってほしい」ということがあります。このような場合は、Formクラスにメソッドを追加します。

class クラス名(forms.Form)

 def clean(self):
   変数=super().clean()

cleanというメソッドは、用意された値の検証を行う際に呼び出されます。
このメソッドでは最初にsuper().clean()というものを呼び出して、基底クラス(継承元のクラス)のcleanを呼び出します。戻り値にはチェック済みの値が返されます。また下記の定義により任意にエラーを発生させることができます。

raise ValidationError(エラーメッセージ)

それでは実際にエラーを発生させてみます。
forms.pyのCheckFormクラスを下記にように修正します。
/hello/forms.py

from django import forms    #☆

class CheckForm(forms.Form):
  str = forms.CharField(label='String', \
    widget=forms.TextInput(attrs={'class':'form-control'}))
  
  def clean(self):
    cleaned_data = super().clean()
    str = cleaned_data['str']
    if (str.lower().startswith('no')):
      raise forms.ValidationError('You input "NO"!')

「NO」と入力すると’You input “NO”!’というエラーメッセージが表示されます。

ModelFormでのバリデーション

Djangoには、forms.Formを使った一般的なフォームのバリデーション以外に、ModelFormを使ったバリデーションがあります。
/hello/forms.py

class ContactForm(forms.ModelForm):
  class Meta:
    model = Contact
    fields = ['name','mail','gender','age','birthday']

/hello/models.py

class Friend(models.Model):
  name = models.CharField(max_length=100)
  mail = models.EmailField(max_length=200)
  gender = models.BooleanField()
  age = models.IntegerField(default=0)
  birthday = models.DateField()

このバリデーションは「save」をクリックしたときに、バリデーションチェックが行われます。

/hello/views.py

def create(request):
  if (request.method == 'POST'):
    obj = Contact()
    contact= ContactForm(request.POST, instance=obj)
    contact.save()
    return redirect(to='/hello')
  params = {
    'title': 'Hello',
    'form': ContactForm(),
  }
  return render(request, 'hello/create.html', params)

「save」時以外でチェックを行わせる場合、「is_valid」メソッドを使ってチェックを行うことができます。
/hello/views.py

def check(request):
  params = {
    'title': 'Hello',
    'message':'check validation.',
    'form': ContactForm(),
  }
  if (request.method == 'POST'):
    obj = Contact()
    form = ContactForm(request.POST, instance=obj)
    params['form'] = form
    if (form.is_valid()):
      params['message'] = 'OK!'
    else:
      params['message'] = 'no good.'
  return render(request, 'hello/check.html', params)

/hello/templates/hello/check.html

{% load static %}
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>{{title}}</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css" 
  rel="stylesheet" crossorigin="anonymous">
  </head>
  <body class="container">
    <h1 class="display-4 text-primary">
      {{title}}</h1>
    <p>{{message|safe}}</p>
    <form action="{% url 'check' %}" method="post">
      {% csrf_token %}
      <table class="table">
      {{ form.as_table }}
      <tr><th></th><td>
        <input type="submit" value="click"
          class="btn btn-primary mt-2">
      </td></tr>
      </table>
    </form>
  </body>
</html>

問題がなければ「OK」と表示される。

モデルで使えるバリデータ

MinValueValidator/MaxValueValidator

数値の最大値、最小値をチェック

MinValidator(値)
MaxValidator(値)

MinLengthValidator/MaxLengthValidator

テキストの最大文字数、最小文字数をチェック

MinLengthValidator(値)
MaxLengthValidator(値)

下記のようにコーディングします。
/hello/models.py

from django.db import models
from django.core.validators import MinLengthValidator  #☆

class Contact(models.Model):
  name = models.CharField(max_length=100, \
    validators=[MinLengthValidator(10)])
  mail = models.EmailField(max_length=200, \
    validators=[MinLengthValidator(10)])
  gender = models.BooleanField()
  age = models.IntegerField()
  birthday = models.DateField()

  def __str__(self):
    return '<Contact:id=' + str(self.id) + ', ' + \
      self.name + '(' + str(self.age) + ')>'

nameとmailは10文字以内だとバリデータエラーになります。

EmailValidator/URLValidator

メールアドレスやURLのバリデーションチェックを行うことができます。
nameにバリデータを設定してみます。
/hello/models.py

from django.db import models
from django.core.validators import URLValidator  #☆

class Contact(models.Model):
  name = models.CharField(max_length=100, \
    validators=[URLValidator()])
  mail = models.EmailField(max_length=200)
  gender = models.BooleanField()
  age = models.IntegerField()
  birthday = models.DateField()

  def __str__(self):
    return '<Contact:id=' + str(self.id) + ', ' + \
      self.name + '(' + str(self.age) + ')>'

NameにURL以外を入力するとエラーになります。

ProhibitNullCharactersValidator

null文字を禁止するためのバリデータです。

RegexValidator

これは正規表現パターンを使って、パターンに合致する値かどうかをチェックするためのものです。

validators = [RegexValidator(r'^[a-z]*$')]

フォームとエラーメッセージを個別に表示

check,htmlを修正することで、フォームとエラーメッセージを個別に表示させることができます。
/hello/check.html

{% load static %}
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>{{title}}</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css" 
  rel="stylesheet" crossorigin="anonymous">
  </head>
  <body class="container">
    <h1 class="display-4 text-primary">
      {{title}}</h1>
    <p>{{message|safe}}</p>
    <ol class="list-group">
    {% for item in form %}
    <li class="list-group-item py-2">
      {{ item.name }} ({{ item.value }})
      :{{ item.errors.as_text }}</li>
    {% endfor %}
    </ol>
    <table class="table mt-4">
      <form action="{% url 'check' %}" method="post">
      {% csrf_token %}
      <tr><th>名前</th><td>{{ form.name }}</td></tr>
      <tr><th>メール</th><td>{{ form.mail }}</td></tr>
      <tr><th>性別</th><td>{{ form.gender }}</td></tr>
      <tr><th>年齢</th><td>{{ form.age }}</td></tr>
      <tr><th>誕生日</th><td>{{ form.birthday }}</td></tr>
      <tr><td></td><td>
        <input type="submit" value="click"
          class="btn btn-primary">
      </td></tr>
      </form>
    </table>
  </body>  
</html>

ウィジェット設定した場合

ウィジェットの概要と、使い方を解説
https://codor.co.jp/django/how-to-use-widget

/hello/forms.py

class ContactForm(forms.ModelForm):
  class Meta:
    model = Contact
    fields = ['name','mail','gender','age','birthday']
    widgets = {
      'name': forms.TextInput(attrs={'class':'form-control'}),
      'mail': forms.EmailInput(attrs={'class':'form-control'}),
      'age': forms.NumberInput(attrs={'class':'form-control'}),
      'birthday': forms.DateInput(attrs={'class':'form-control'}),
    }

/hello/check.html

{% load static %}
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>{{title}}</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.css" 
  rel="stylesheet" crossorigin="anonymous">
  </head>
  <body class="container">
    <h1 class="display-4 text-primary">
      {{title}}</h1>
    <p>{{message|safe}}</p>
    <ol class="list-group mb-4">
    {% for item in form %}
    <li class="list-group-item py-2">
      {{ item.name }} ({{ item.value }})
      :{{ item.errors.as_text }}</li>
    {% endfor %}
    </ol>
    <form action="{% url 'check' %}" method="post">
      {% csrf_token %}
      <div class="form-group">名前{{ form.name }}</div>
      <div class="form-group">メール{{ form.mail }}</div>
      <div class="form-group">性別</th><td>{{ form.gender }}</div>
      <div class="form-group">年齢</th><td>{{ form.age }}</div>
      <div class="form-group">誕生日</th><td>{{ form.birthday }}</div>
      <div class="form-group">
        <input type="submit" value="click"
          class="btn btn-primary">
      </div>
    </form>
  </body>  
</html>

この記事は役に立ちましたか?

もし参考になりましたら、下記のボタンで教えてください。

関連記事

コメント

この記事へのコメントはありません。