【Django】The Xxxx could not be created because the data didn’t validate. エラーが発生した時の原因と対処法

The Xxxx could not be created because the data didn't validate.エラーの原因と対処法の解説ページアイキャッチ

Django の勉強をしていて下記のエラーが出て困ったので、備忘録の意味も含めてこのエラーの原因と対処法について解説しておきます。

ValueError: The Xxxx could not be created because the data didn't validate.

Xxxx の部分には models.py で定義したクラス名が入ります。

エラーの原因と対処法

最初に結論として、上記エラーの原因と対処法について解説します。

エラーの原因

このエラーが出る原因は単純明快で、あなたがデータベースに反映しようとしてフォームに入力した値が「妥当でない」ことが原因です。

おそらく、初めて ModelForm を使って試しにデータベースへのレコードの保存を行おうとしたときに発生しやすいエラーになると思います。

例えば、メールアドレスの入力フォームでお試しで a@b.c 等のてきとうなものを入力して送信すると、このエラーが発生します。これは、入力した文字列がメールアドレスとして妥当でないからです(そんなメールアドレスあり得ないよね…と判断された)。

また、日付の入力フォームでも日付の指定形式が間違っていたりすると、このエラーが発生します。

ちなみに、エラーメッセージの最後にある validate とは、コンピュータ用語としては「妥当性を検証する」と言う意味で使用されることの多い単語です。

エラーメッセージで validatevalidationバリデーション などの用語が出てきた場合は、エラーの原因が「データが妥当でない」である場合が多いです。例えば、メールアドレスを表すデータを期待しているのにメールアドレスとして妥当でない(@ がない、@ の後ろ側に . がない等)、みたいな感じです。

今回の場合は「フォームに入力されるデータとして期待しているもの」に対し、実際に入力されたデータが妥当でないために発生するエラーとなります。

スポンサーリンク

エラーの対処法

あなたが Django の学習中であったり、お試しでレコードのデータベースへの保存を行なっているだけであれば、とりあえずフォームに入力する値を妥当にすることで対処できるはずです。

例えばメールアドレスの入力フォームであれば、a@example.com などのように、ドメイン(@ よりも後ろの部分)に妥当そうなものを指定すれば良いです。メールアドレスの場合、おそらくトップドメイン(最後の . よりも後ろの部分)に comjp などの実在するものを指定するのが一番無難です。

そもそもエラーが出ないようにしっかり対処したい場合は、データベースにレコードを保存する前に is_valid メソッド等を利用して入力されたデータ(値や文字列)の妥当性を検証し、問題ない場合のみデータベースへの保存を行うように制御する必要があります。

ただ、もしあなたが Django 学習中なのであれば、おそらく必要なタイミングでバリデーションや妥当性の検証について、さらにはそれらを行う方法について学ぶ機会があるはずです。

なので、前述のように、とりあえず妥当な値をフォームに入力してエラーを回避しておき、学習を進めて行けば良いと思います。

エラーが発生する際の Django の動作

ここからは、どういう動作によって下記のエラーが発生するのかについて解説していきます。上記の内容でエラーの回避法は分かると思いますので、ここからは読みたい方だけ読み進めてください。

ValueError: The Xxxx could not be created because the data didn't validate.

妥当でない値があるのに ModelForm クラスの save メソッドを実行するとエラーになる

このエラーは、下記のように models.pyforms.pyviews.py を作成した場合に発生する可能性があります(その他の htmlcss 等については省略します)。

models.py
from django.db import models

class Test(models.Model):
    name = models.CharField(max_length=100)
    mail = models.EmailField(max_length=200)
     
    def __str__(self):
        return '<test:id=' + str(self.id) + ', ' + self.name + '(' + str(self.mail) + ')>'
forms.py
from django import forms
from.models import Test

class TestForm(forms.ModelForm):
    class Meta:
        model = Test
        fields = ['name','mail']
views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from .models import Test
from .forms import TestForm

def index(request):
    data = Test.objects.all()
    params = {
        'title': 'Hello',
        'data': data,
    }
    return render(request, 'hello/index.html', params)

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

おそらく、Django やデータベースに詳しい人なら、すぐにエラーが発生する原因や、このソースコードが良くない理由は直ぐに分かるのではないかと思います。

このように各スクリプトを作成してページにアクセスすれば、下の図のような入力フォームが表示されます。

表示される画面

入力後に画面下側のボタンを押せば、views.pycreate 関数が実行され(request.method'POST')、入力内容に応じたレコードが作成されてデータベースに保存されることになります(そうなるように html ファイルや urls.py を作成しています)。

ウェブブラウザによる妥当性の検証

ここで下側の入力フォームに注目すれば、このフォームは models.py の下記に関連づけられて作成されたものということになります。

mail = models.EmailField(max_length=200)

で、上記でフィールドとして EmailField を指定しているため、このフォームに「”メールアドレスの形式でない” 文字列」が入力された場合、フォームの値が送信されないよう、ウェブブラウザで妥当性の検証が行われるようになっています。

例えば @ が含まれない文字列を入力してボタンを押そうとしても、フォームからメッセージが表示されボタンが押せません。これは、フォームに入力された文字列に対してウェブブラウザによって妥当性の検証が行われているからです。妥当でない入力はそもそも送信できないようになっています。

妥当でないメールアドレスを入力した時の画面

なので、例えば a@b.c などのように、@ を含むメールアドレスとして妥当な文字列変更してやる必要があります。そうすればウェブブラウザが入力された値が妥当であると判断し、ボタンを押した際にフォームに入力した情報をサーバーに送信することができるようになります。

ウェブブラウザで値の妥当性が行われる様子

データベース保存前の妥当性の検証

ただ、この例のように、a@b.c などのような「てきとうなメールアドレスを入力して送信すること」こそが、下記のエラーが発生する原因となります。

The Test could not be created because the data didn't validate.

実は、妥当性の検証はウェブブラウザだけでなく、Django 側でも行われます。そして、妥当かどうかの判断がウェブブラウザと Django では異なるようです。

この妥当性の検証が行われるのが、今回の views.py の場合、下記の save メソッド実行時になります。

妥当性の検証
test = TestForm(request.POST, instance=obj)
test.save()

TestForm クラスは forms.py で定義した ModelForm クラスのサブクラスです。

この ModelForm の save メソッドはデータベースへのレコードの保存を行うメソッドですが、その保存を行う前にレコードの各値の妥当性の検証が行われます。

この時の妥当性の検証の仕方はウェブブラウザとは異なります。ですので、ウェブブラウザが “妥当である” と判断した場合でも、save メソッド内で行われる妥当性の検証で “妥当でない” と判断される場合があります。

そして、妥当であると判断された場合はレコードがデータベースに保存されますが、妥当でないと判断された場合に例外が発生します。

Djangoで値の妥当性の検証が行われる様子

その例外が発生した際に出力されるエラーの文言が、ページの題名になっているエラーになります。

具体的には、save メソッド実行直後に下記の処理が動作し、if self.errors が成立して例外が発生してエラーになるようになっているようです(django/forms/models.pysave メソッド)

models.py
    def save(self, commit=True):
        """
        略
        """
        if self.errors:
            raise ValueError(
                "The %s could not be %s because the data didn't validate." % (
                    self.instance._meta.object_name,
                    'created' if self.instance._state.adding else 'changed',
                )
            )

つまり、このエラーは、フォームから送信された値が妥当でないのに、ModelForm の save メソッドを利用してデータベースに保存しようとした場合に発生するということになります。

ですので、フォームに入力する値を妥当なものに変更することで、このエラーは解決できると思います。

入力値を変更してDjangoの妥当性の検証で妥当であると判断されるようになる様子

ちょっとどういう仕組みで妥当性の検証が行われるかまでは理解していないですが、例えばメールアドレスであれば a@b.c などを送信するとエラーになりますが、a@b.jp などであればエラーになりませんので、最後の . の後ろ側にはそれっぽいものを選ばないとダメな気がしますね。

また、メールアドレスだけでなく、日付なども適した形式でないと妥当でないと判断されるので注意が必要です。

妥当性の検証を行なってから save メソッドを実行すれば解決

ただ、ウェブブラウザで妥当性の検証が行われるといっても、ユーザーが妥当でない値をフォームに入力して送信されることは普通にあり得る話です。

その度にページの題名のようなエラーを表示するのはイマイチです。なので、save メソッドの中ではなく、事前に妥当性の検証を行い、妥当である場合のみ save メソッドを実行するようにする必要があります。

このフォームの入力値の妥当性の検証は、例えば ModelForm クラスの is_valid メソッドにより行うことができます。is_valid メソッドが True を返却した場合、フォームの入力値は妥当であると判断できますし、False を返却した場合は妥当でないと判断することができます。

ですので、False を返却した場合は save メソッドを実行しなければ、本ページの題名のエラーの発生を防ぐことができます。

イメージとしては、save メソッド実行箇所を下記のように変更すれば良いです。

views.py
def create(request):
    if (request.method == 'POST'):
        obj = Test()
        test = TestForm(request.POST, instance=obj)
        if test.is_valid():
            test.save()
            return redirect(to='/hello')
        else:
            # フォーム入力値が妥当でない場合の処理
            # test.save() は実行しない
    params = {
        'title': 'Hello',
        'form': TestForm(),
    }
    return render(request, 'hello/create.html', params)

例えば is_validFalse の際の処理を下記のようにすれば、妥当でない値が送信された際に同じページを表示して再度入力を受け付けるようなこともできます。is_validFalse の場合は save メソッドを実行していないのでエラーが発生しません。

views.py
def create(request):
    if (request.method == 'POST'):
        obj = Test()
        test = TestForm(request.POST, instance=obj)
        if test.is_valid():
            test.save()
            return redirect(to='/hello')
        else:
            params = {
                'title': 'Hello',
                'form': test,
            }
            return render(request, 'hello/create.html', params)

    params = {
        'title': 'Hello',
        'form': TestForm(),
    }
    return render(request, 'hello/create.html', params)

上記は1つの対処策の例です。詳しくは Django のバリデーションの解説を読んでみると良いと思います。

スポンサーリンク

Model クラスの save メソッドでは妥当性の検証が行われない

なぜ、このエラーで私がハマったかというと、それは、同じフォームの入力値であったのにも関わらず、ModelForm を利用するようにしただけでエラーが発生するようになってしまったからです。

それまでは通常通りデータベースに保存できていた入力値が、ModelForm を利用するだけでエラーが出るようになってしまう理由が分かりませんでした。

ちなみに、ModelForm を利用する前は下記のように forms.pyviews.py を書いていました(models.py は前述の例と同じ)。この場合だと、メールアドレスのフォームに a@b.c などのてきとうな文字列を入力してもエラーが発生しません。

forms.py
from django import forms

class TestForm(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'}))
views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from .models import Test
from .forms import TestForm

def index(request):
    data = Test.objects.all()
    params = {
        'title': 'Hello',
        'data': data,
    }
    return render(request, 'hello/index.html', params)

def create(request):
    params = {
        'title': 'Hello',
        'form': TestForm(),
    }
    if (request.method == 'POST'):
        name = request.POST['name']
        mail = request.POST['mail']
        test = Test(name=name,mail=mail)
        test.save()
        return redirect(to='/hello')
    return render(request, 'hello/create.html', params)

この場合は、views.py では ModelForm のサブクラスである TestForm は利用せず、models.py で定義した Model のサブクラスの Test のオブジェクトが save メソッドを実行しています。

Django の動きをいろいろ確認してみたのですが、どうも Model クラスの save メソッドでは、save メソッドの中で妥当性の検証は行われないようです。

ですので、ModelForm クラスの save メソッドだと妥当でないと判断されてエラーになるような場合でも、Model クラスの save メソッドだとそもそも妥当性の検証が行われないのでエラーにならず、データベースにレコードの保存ができてしまいます。

ModelとModelFormのsaveメソッドの違い

そして、この違いがあるので、今までエラーが発生しなかったフォームの入力値が突然エラーになるようになってしまったようです。私と一緒で、ここで戸惑う人もいるんじゃないかなぁと考え、このページを作成することにしました。

まとめ

このページでは、下記のエラーが発生する原因およびエラーの対処法について解説しました!

ValueError: The Xxxx could not be created because the data didn't validate.

フォームに妥当でない値を入力して送信すると、このエラーが発生する場合があります。

特に妥当性の検証について理解していない状態で ModelForm クラスの save メソッドを実行した際に、このエラーが発生しやすいのではないかと思います。

Django 学習中の方であれば、今後妥当性の検証については学ぶ機会があると思いますので、まずはフォームに妥当な値を入力するようにしてエラーを回避すれば良いと思います。

下記のあたりを頭に入れておけば、このエラーが発生した際にも戸惑わなくなると思いますので、とりあえずここだけ覚えておきましょう!

  • ウェブブラウザでも妥当性の検証が行われるが、ModelForm クラスの save メソッドでも妥当性の検証が行われる
  • ModelForm クラスの save メソッドの中で妥当でないと判断されるとエラーが発生してしまう
  • Model クラスの save メソッドでは妥当性の検証が行われない

逆に Django を使いこなしているつもりでこのエラーが発生してしまったという方は、妥当性の検証(バリデーターなど)について学習するのが良いと思います!

同じカテゴリのページ一覧を表示

コメントを残す

メールアドレスが公開されることはありません。