理解して使いこなすDjangoのForm機能
きゅうたつ(KyutatsuNishiura)
1
お前誰よ
きゅうたつ(Kyutatsu)
株式会社日本システム技研(JSL)所属
Webエンジニア
Python,Django,AWS
2
トークの対象者
Formは使ったことがあるが、既存コードをコピペして雰囲気でやっている。
いざ自分で実装しようとすると手が動かない.
公式DocumentのForm関連の項目を読むのが苦痛
3
こんな登録フォームがあったとします
お仕事の作業内容を、開始,終了時刻を登録するフォーム.
開始時刻の方には,6時〜23時までしか入力できないValidationがついている.
4
Q:こんな修正要望があったら?1/2
5
Q:こんな修正要望があったら?2/2
要望2.終了時刻の方にも、開始時刻と同じく6時〜23時の範囲チェックつけて!
要望3.開始時刻<終了時刻となってるようチェックして!
エラーメッセージはそれぞれのフィールドの上に出して!
6
目標
公式ドキュメントを読んで、手を動かせる.
作業量を見積もることができる.
そんなトークにしたいと思っています.
7
もくじ
まえおき
0章.準備編:FormをDjangoshellを使ってさわろう
1章.構造編:Formを構成する要素
2章.フロント編:HTMLをレンダリング
3章.バリデーション編:値をチェック
4章.便利な機能:FormSetでFormをたくさん並べよう! 8
9
初心者にとってFormの厄介なところ
複数のクラスが関わってくる。
modelやtemplate,validatorなど、複数の要素が絡んでくる。
パラメータなどを変更できるポイントが多い。
「ラベルを変えたい」だけでも、変更可能な場所が何箇所もある...
10
Formの構成要素をさわって動かしてみて理解する.
11
0章.Djangoshellを使おう
1.Djangoshellへの入り方
2.Templateへのレンダリングをshellから確認
12
0章.Djangoshellの入り方
$ python manage.py shell

...省略...

(InteractiveConsole)

>>> 

参照できるfield、ヘルプをその場で見れる。
テストコードを書くハードルが下がる.
13
0章:Templateへのレンダリングをshellから確認
formの定義
class NameForm(forms.Form):

name = forms.CharField()

インスタンス化
>>> form = NameForm()

14
0章:Templateへのレンダリングをshellから確認
DjangoTemplateLanguageでのレンダリング
カギカッコのアクセスはTemplateではドットで行う。
メソッドはコールされる。
最終的に__str__()される。
shellで出力結果を手軽に確認するにはprint()すればOK.
https://docs.djangoproject.com/ja/3.2/topics/templates/#variables

https://docs.djangoproject.com/ja/3.2/ref/templates/language/#variables
15
0章:Templateへのレンダリングをshellから確認
カギカッコのアクセスはドットで行う。
Template上でnameフィールドをレンダリング.
{{ form.name }}

↓
<input type="text" name="name" required id="id_name">

shell上で同じことをする場合.
最終的に__str__()される。->printを呼ぶ。
>>> print(form['name']) # []を使ったアクセス 

<input type="text" name="name" required id="id_name">
 16
0章:Templateへのレンダリングをshellから確認
メソッドはコールされる。
Template上でnameフィールドのラベルをレンダリング.
{{ form.name.label_tag }}
↓
<label for="id_name">Name:</label>

shellで同じことをする場合
最終的に__str__()される。->printを呼ぶ。
>>> print(form['name'].get_label()) # 関数呼び出し

<label for="id_name">Name:</label>
 17
1章.構造編:Formを構成する要素
18
1章.Formを構成する要素
以下のクラスが何者かをざっくり説明します。

詳細は後の方で解説します。
Form
Field
Widget
Validator
BoundField
19
1章.Formを構成する要素
以下のFormを例に説明します。
class MemberForm(forms.Form):

name = forms.CharField() # 名前

age = forms.IntegerField() # 年齢

FormにはFieldを定義できる。
Fieldは、デフォルトのwidgetとvalidatorを持っています.
20
1章.Formを構成する要素
Form
Field
Widget
Validator
BoundField
21
1章.Formを構成する要素:Widget
HTMLを生成するためのクラス.
form = MemberForm())

form.fields['name'].widget

<django.forms.widgets.TextInput at 0x7fe0fb4b04f0>
renderメソッドで、inputタグのようなウィジェットのHTMLを作ります。
form.fields['name'].widget.render('これがname属性', 'これがvalue')

'<input type="text" name="これがname属性" value="これがvalue">'

22
1章.Formを構成する要素
Form
Field
Widget
Validator
BoundField
23
1章.Formを構成する要素:Validator
受け取った値が条件を満たすかをチェックする.
form = MemberForm()

form.fields['name'].validators # listに入ってる.
[<django.core.validators.ProhibitNullCharactersValidator at 0x7fe0fc0700a0>]

24
ここまででおぼえておくこと
FormにはFieldを定義することができる
Fieldは以下のオブジェクトを持っている.
HTMLをrenderするWidget
値のチェック(バリデーション)をおこなうValidator
25
1章.Formを構成する要素
Fieldが持っているwidget,validatorsは、Built-inFieldclassesで確認できます。
https://docs.djangoproject.com/en/3.2/ref/forms/fields/#built-in-field-classes
26
1章.Formを構成する要素
Form
BoundField
Field
Widget
Validator
↓テンプレートでアクセスしているこれです。
{{ form.field_name }}

27
1章.Formを構成する要素:BoundFieldとField
https://docs.djangoproject.com/ja/3.2/ref/forms/api/#django.forms.BoundField
Fieldクラス
Formのattribueとして定義する。
BoundField
テンプレート上でのField関係のレンダリングはこのクラスが取りまとめる
FormのField名に[]アクセスしたときに作成される.-> form['name']
28
1章.Formを構成する要素:BoundFieldとField
Field
form.fields['name']

<django.forms.fields.CharField at 0x7fecdcbd7fa0>

BoundField
form['name']

<django.forms.boundfield.BoundField at 0x7fecdc936100>

29
1.Formを構成する要素まとめ
Form
Field
Widget→<input>のようなHTMLをつくる
Validator→値のチェックを行う.
BoundField→各FieldのHTMLへのレンダリングとりまとめる.
30
2章.フロント編:HTMLをレンダリング
1.BoundFieldの役割とできること
2.Widgetの役割とできること
31
2章.フロント編:BoundField
Field単位でHTMLレンダリングのロジックをまとめている。
32
2章.フロント編:BoundField
{{ form.field_name}}

form.<field_name>
これはBoundField。
HTMLレンダリングに必要な要素は,BoundFieldから基本的に取得します.
33
2章.フロント編:BoundField
BoundFieldはHTMLレンダリングに必要なメソッドや属性を持ってい
る.
https://docs.djangoproject.com/ja/3.2/ref/forms/api/#attributes-of-boundfield

利用可能な属性一覧はこちらの公式リンクから確認できます。
# labelタグを生成するメソッド

print(form['name'].label_tag())

<label for="id_name">Name:</label>

print(form['name'].id_for_label)

id_name

print(form['name'].name)

name

34
2章.フロント編:BoundField
BoundFieldはWidgetやErrorListクラスにHTMLレンダリングを任せて
いる.
BoundFieldをprintすると,widgetによりinputタグがでている
print(form['name'])

<input type="text" name="name" required>

error自体はFormクラスがまとめている。( form.errors )
ul タグの作成は、ErrorListクラスが行っている。
BoundFieldは、Formクラスから自分のFieldに対応するErrorListを取り出すだけ。
print(form['name'].errors)

<ul class="errorlist"><li>この項目は必須です。</li></ul>

35
2章.フロント編:BoundField
BoundFieldはField,Formそれぞれのインスタンス化する時の引数で変更を入れられる.
class MemberForm(forms.Form):

name = forms.CharField(label='変更したラベル')

form = MemberForm(auto_id='変更したID_%s')

print(form['name'].label_tag())

<label for="変更したID_name">変更したラベル:</label>

36
2章.フロント編:BoundField
Formのインスタンス化時に設定.

https://docs.djangoproject.com/ja/3.2/ref/forms/api/#configuring-form-elements-html-id-
attributes-and-label-tags
Fieldの引数

https://docs.djangoproject.com/ja/3.2/ref/forms/fields/#core-field-arguments
37
2章.フロント編:BoundFieldまとめ
BoundFieldは form['フィールド名'] にアクセスすることで作成される。
BoundFieldはField単位でのHTML出力をとりまとめている。
BoundFieldは、Field,Formそれぞれのインスタンス化時の引数によって変更を加えられ
る。
38
2章.フロント編:HTMLをレンダリング:Widget
https://docs.djangoproject.com/ja/3.2/ref/forms/widgets/
class MemberForm(forms.Form):

name = forms.CharField()

↓と同じ.
class MemberForm(forms.Form):

name = forms.CharField(

widget=forms.TextInput,

)

39
2章.フロント編:Widgetの差し替え
<input> などの要素をレンダリングします。
組み込みのwidgetが複数用意されています。
差し替えることで生成されるHTMLタグを簡単に変更できます.
自分でカスタムwidgetを定義することも可能です。
40
2章.フロント編:Widgetの差し替え
Formの定義
class MemberForm(forms.Form):

name = forms.CharField()

form['フィールド名'] はBoundFieldです。

これをprintすると、BoundFieldからWidgetのrenderがよばれ,HTMLが作られます。
form = MemberForm()

print(form['name'])

<input type="text" name="name" required id="id_name">

CharFieldのデフォルトwidget(TextInput)によりinputタグが作成される.
41
2章.フロント編:Widgetの差し替え
Formの定義
class MemberForm(forms.Form):

name = forms.CharField(widget=forms.Textarea) # TexInput-> Textarea

form['フィールド名'] はBoundFieldです。
form = MemberForm()

print(form['name'])

<textarea name="name" cols="40" rows="10" required id="id_name">

</textarea>

input->textareaタグに変更された。
42
2章.フロント編:Widgetの差し替え
HTMLタグのattributeを差し替えることができます。
widgetにはattr引数でdictを渡します。
class MemberForm(forms.Form):

name = forms.CharField(

# inputタグにclassを設定.

widget=forms.TextInput(attrs={'class': 'user-defined-attr'}))

form = MemberForm()

print(form['name'])

# クラスが設定された!

<input type="text" name="name" class="user-defined-attr" required id="id_name">

TextInputwidgetに渡したattrにより、printしたHTMLタグにclass属性が追加されてい
る.
43
2章.フロント編:Widgetはテンプレートを持つ
例:TextInputクラスのソースコード上の定義
class TextInput(Input):

input_type = 'text'

template_name = 'django/forms/widgets/text.html'

django/forms/widgets.py より抜粋.
{% include "django/forms/widgets/input.html" %}

/django/forms/templates/django/forms/widgets/text.html より。
44
2章.フロント編:Widgetはテンプレートを持つ
<input type="{{ widget.type }}"

name="{{ widget.name }}"i

{% if widget.value != None %}

value="{{ widget.value|stringformat:'s' }}"

{% endif %}

{% include "django/forms/widgets/attrs.html" %}>

/django/forms/templates/django/forms/widgets/input.html より改行などを加えて
抜粋。
45
2章.フロント編:Widgetまとめ
Widgetクラスはテンプレート+レンダリングのロジックを再利用可能な形でまとめてい
る。
WidgetのrenderメソッドはBoundFieldから呼ばれて利用される。
46
2章のまとめ
Widgetは <input> のようなFormの部品を定義している。
BoundFieldはField単位のHTMLレンダリングの取りまとめ役.
BoundFieldはwidgetやErrorListクラスにおねがい して、タグを丸ごとレンダリングす
ることもある.
47
3章.バリデーション編:値をチェック
1.validationの結果:データorエラー
2.エラーを構成するクラス
i.ErrorDict
ii.ErrorList
iii.ValidationError例外
3.validationの方法
i.Form.cleanで複数Fieldのチェック
ii.validatorを使ってひとつのFieldをチェック
iii.ModelFormのvalidation
48
3章.バリデーション編:validationの結果
class JslMemberForm(forms.Form):

name = forms.CharField() # 名前

age = forms.IntegerField() # 年齢

1.Validation成功->form.cleaned_dataに整形された値が入る.
2.Validation失敗->エラーがform.errorsにまとめられる.
49
3章.バリデーション編:validation成功
form.cleaned_data (dict)に「cleanされた」値が入る。
form = JslMemberForm({'name': 'たろう ', 'age': '20'})

form.is_valid()

Out[69]: True

form.cleaned_data

Out[70]: {'name': 'たろう', 'age': 20}

nameは後ろのスペースが除去されている
ageはint型に変更されている.
50
3章.バリデーション編:validation失敗
validationに失敗(エラーが発生)すると、 form.errors にエラーがまとめられる.
form = JslMemberForm({'name': '', 'age': 'にじゅっさい'})

form.is_valid()

Out[72]: False

form.errors

Out[73]: {'name': ['This field is required.'], 'age': ['Enter a whole number.']}

51
3章.バリデーション編:エラーを構成するクラス
1.ErrorDict
2.ErrorList
3.ValidationError例外
52
3章.バリデーション編:エラーを構成するクラス
form.errors

Out[73]: {'name': ['This field is required.'], 'age': ['Enter a whole number.']}

53
3章.バリデーション編:ErrorDictクラス
print(form.errors)

<ul class="errorlist">

<li>name<ul class="errorlist"><li>This field is required.</li></ul></li>

<li>age<ul class="errorlist"><li>Enter a whole number.</li></ul></li>

</ul>

__str__ メソッドでas_ul()を呼ぶようになっており,エラーメッセージのHTMLを作る
※ 一部改行とインデントを加えて表示.
54
3章.バリデーション編:ErrorListクラス
再掲
form.errors

Out[73]: {'name': ['This field is required.'], 'age': ['Enter a whole number.']}

ErrorDictのvalue部分はlistに見えるが、実際はErrorListと言うクラス.
form.errors['name']

Out[74]: ['This field is required.']

print(form.errors['name'])

<ul class="errorlist"><li>This field is required.</li></ul>

__str__ メソッドでas_ul()を呼ぶようになっており,エラーメッセージのHTMLを作る
55
3章.バリデーション編:ErrorListクラス
ポイント: form['name'] はBoundField.
Form._error から自分のFieldに属するエラーを引き出してきている.
print(form['name'].errors)

<ul class="errorlist"><li>この項目は必須です。</li></ul>

BoundFieldのソースコード↓
django/forms/boundfield.py
56
3章.バリデーション編:ValidationErrorr例外
form.errors['name']

Out[74]: ['This field is required.']

ErrorListクラスはValidationError例外を保持している.
Validationの失敗でraiseされるのは、全てこのValidationError例外.
form.errors['name'].data

Out[84]: [ValidationError(['This field is required.'])]

57
3章.バリデーション編:ValidationErrorr例外
formでvalidationをするときにraiseすることになっている例外.
formには、発生したValidationErrorをまとめる仕組みが実装されている。
58
3章.バリデーション編:ValidationError例外
https://docs.djangoproject.com/en/3.2/ref/forms/validation/#raising-validationerror
書き方のベストプラクティスという項目があるので、一読しておくと良いともいます.
59
3章.バリデーション編:エラーを構成するクラスまとめ
この二つは自らをHTMLとしてレンダリングすることができる.
ErrorDict: form.errors に入っている。
ErrorList:ErrorDictのvalue.
3.ValidationError例外
formでのvalidation失敗時にraiseする例外.
60
3章.バリデーション編:validationの方法
1.Form.cleanで複数Fieldのチェック
2.validatorを使ってひとつのFieldをチェック
61
3章.Validationは複数のステップで呼ばれる
https://docs.djangoproject.com/ja/3.2/ref/forms/validation/
Form
clean()
clean_<fieldname>()
Field
to_python()
validate()
run_validators()
Field.validators:listのvalidatorを順番に実行...
62
3章.Form.clean():複数Fieldをvalidateする
https://docs.djangoproject.com/ja/3.2/ref/forms/validation/#cleaning-and-validating-
fields-that-depend-on-each-other
開始時刻(start_at)と終了時刻(finish_at)をsubmitするFormを考える。
開始時刻<終了時刻となるようチェック。
このような場合、Validationはcleanメソッドに記述する.
63
3章.Form.clean():複数Fieldをvalidateする
class WorkTimeForm(forms.Form):

start_at = forms.DateTimeField()

finish_at = forms.DateTimeField()

def clean(self):

cleaned_data = super().clean()

start_at = cleaned_data.get("start_at")

finish_at = cleaned_data.get("finish_at")

if finish_at and start_at and finish_at <= start_at:

raise forms.ValidationError("終了時刻は開始時刻より後にしてください",

code='work_time_order')

64
3章.Form.clean():複数Fieldをvalidateする
form = WorkTimeForm({"start_at": "2020-01-02 08:00:00",

"finish_at": "2020-01-01 17:30:00"})

form.errors

{'__all__': ['終了時刻は開始時刻より後にしてください']}

finish_at<start_atとなるようなデータを渡してerrorsを見ると、エラーが出ている.
特定のFieldに結びついていないため、ErorrsDictのkeyは** __all__ **となっている.
65
3章. __all__ keyのエラーはform.non_field_errors()
で取得できる.
form.non_field_errors()

Out[101]: ['終了時刻は開始時刻より後にしてください']

print(form.non_field_errors())

<ul class="errorlist nonfield"><li>終了時刻は開始時刻より後にしてください</li></ul>

66
3章.key: __all__ のエラーはform.non_field_errors()
で取得できる.
テンプレートでは下のように書くと同じ意味になる.
{{ form.non_field_error }}

↓
<ul class="errorlist nonfield"><li>終了時刻は開始時刻より後にしてください</li></ul>

67
3章.form.add_errorで各Fieldにエラーメッセージをセッ
トする
https://docs.djangoproject.com/ja/3.2/ref/forms/validation/#cleaning-and-validating-
fields-that-depend-on-each-other
Form.add_errorsメソッドを呼ぶ.
class WorkTimeForm(forms.Form):

# .........(省略)...........

def clean(self):

# .......(省略).......

if finish_at <= start_at:

self.add_error('start_at', forms.ValidationError('開始時刻のエラー'))

self.add_error('finish_at', forms.ValidationError('終了時刻のエラー'))

68
3章.form.add_errorで各Fieldにエラーメッセージをセッ
トする
再掲.raiseValidationErrorした場合.
form.errors

{'__all__': ['終了時刻は開始時刻より後にしてください']}

修正後:add_errorを使った場合.
form = WorkTimeForm({"start_at": "2020-01-02 08:00:00",

"finish_at": "2020-01-01 17:30:00"})

form.errors

Out[105]: {'start_at': ['開始時刻が×'], 'finish_at': ['終了時刻が×']}

69
3章.form.add_errorで各Fieldにエラーメッセージをセッ
トする
それぞれのFieldに属しているので、レンダリングでエラーメッセージをFieldの隣に配置
するのに役立つ.
form['start_at'].errors

Out[107]: ['開始時刻が×']

復習:↑はBoundFieldが、formの_errorsから自分のfieldに出ているエラーを取得することで
表示している。
70
3章.バリデーション編
3.validationの方法
i.Form.cleanで複数Fieldのチェック
ii.validatorを使ってひとつのFieldをチェック
iii.ModelFormのvalidation
71
3章.バリデーション編:validatorでひとつのFieldをチェッ
ク
Form
clean()
clean_<field名>()
Field
to_python()
validate()
run_validators()
Field.validators:listのvalidatorを順番に実行...
72
3章.validatorの定義1/4
https://docs.djangoproject.com/en/3.2/ref/validators/
ドキュメントがFormとは別の場所にある。
Djangoでのvalidatorは以下のようなものです。
callableである.
引数を一つとる.
特定の条件を満たさない時、ValidationError例外をraiseする.
73
3章.validatorの定義2/4
from django.core.exceptions import ValidationError
def validate_even(value):

if value % 2 != 0:

raise ValidationError('これは偶数ではありません!')

https://docs.djangoproject.com/ja/3.2/ref/validators/を参考に記述.
単なる関数(など)なので、再利用性が高い。
74
3章.validatorの定義3/4
>>> validate_even(2)

# 何も返さない。(None)

奇数(3)を与えると、ValidationErrorをraiseする。
>>> validate_even(3)

...省略...

django.core.exceptions.ValidationError: ['これは偶数ではありません!']

75
3章.validatorの定義4/4
class NumberForm(forms.Form):

even = forms.IntegerField(validators=[validate_even])

form = NumberForm({'even': '3'})

form.errors

Out[37]: {'even': ['これは偶数ではありません!']}

76
3章.build-inのValidatorクラス
https://docs.djangoproject.com/en/3.2/ref/validators/#built-in-validators
built-inのvalidatorは一度見ておくと良い。
パラメータを渡し、インスタンス化して使うことができる。
from django.core.validators import RegexValidator

start_with_jsl = RegexValidator(regex='^jsl.*', message='「jsl」から始めてください')

start_with_jsl('jsl00000') # OK

start_with_jsl('AAA00000') # NG!

# django.core.exceptions.ValidationError: ['「jsl」から始めてください']

77
3章.バリデーション編
3.validationの方法
i.Form.cleanで複数Fieldのチェック
ii.validatorを使ってひとつのFieldをチェック
iii.ModelFormのvalidation
78
3章.バリデーション編:値をチェック
1.validationの結果:データorエラー
2.エラーを構成するクラス
i.ErrorDict
ii.ErrorLis
iii.ValidationError例外
3.validationの方法
i.Form.cleanで複数Fieldのチェック
ii.validatorを使ってひとつのFieldをチェック
iii.ModelFormのvalidation
79
4章.便利な機能:FormSetでFormをたくさん並べよう!
80
4章.FormSet
https://docs.djangoproject.com/ja/3.2/topics/forms/formsets/

https://docs.djangoproject.com/ja/3.2/topics/forms/modelforms/#model-formsets
81
4章.FormSet
同じ種類のFormを複数並べる時に便利!
factory関数を使って簡単にFormSetインスタンスを作成できる.
通常のFormとほぼ同じように扱える。
82
4章.FormSet
formset_factoryとmodelformset_factoryがある。
既存のModelFormから、modelformset_factoryを使ってFormSetを作る例を紹介しま
す。
83
4章.FormSet
class Member(models.Model):

name = models.CharField(max_length=100)

age = models.IntegerField(null=True, blank=True)

class MemberModelForm(forms.ModelForm):

class Meta:

model = Member

fields = ('name',)

84
4章.FormSet
from django.forms import modelformset_factory

MemberFormSet = modelformset_factory(

model=Member,

form=MemberModelForm,
extra=5, # いくつ空のFormを並べるか。

)

これだけ!
85
4章.FormSet:Viewでの使い方
通常のモデルフォーム
# 空

form = MemberModelForm()

# データあり

form = MemberModelForm(request.POST)

↓
# 空

form_set = MemberFormSet(queryset=Member.objects.none())

# データあり

form_set = MemberFormSet(request.POST, queryset=Member.objects.none()) 

querysetを指定すると、DB上のデータを投入したFormSetを表示できる(編集)
Model.objects.non()を指定すると、空のformsetができます(新規登録)
86
4章.テンプレートでは
https://docs.djangoproject.com/ja/3.2/topics/forms/modelforms/#using-the-formset-in-
the-template
<form method="POST">

{% csrf_token %}

<!-- 補助フィールド -->

{{ form_set.management_form }}

{% for f in form_set %}

{{ f }}

{% endfor %}

<input type="submit" value="SUBMIT!">

</form>

87
まとめ
とっつきにくいDjangoのForm機能ですが、個人的にはいくつかキーとなるポイントを知って
おくと、怖くなくなるのではないかと思います
djangoshellを使ってFormクラスやModelクラスと仲良くなるのはオススメ
Formクラスが含むそれぞれのオブジェクトの役割をイメージすると、ドキュメントが読
みやすくなる。
88
しゃべることのできなかったModelFormのvalidation.
89
3章.バリデーション編
3.validationの方法
i.Form.cleanで複数Fieldのチェック
ii.validatorを使ってひとつのFieldをチェック
iii.ModelFormのvalidation
90
3章.ModelFormとは
https://docs.djangoproject.com/ja/3.2/topics/forms/modelforms/
model定義
class Member(models.Model):

name = models.CharField(verbose_name='名前', max_length=100)

age = models.IntegerField(verbose_name='年齢')

Form定義
class MemberModelForm(forms.ModelForm):

class Meta:

model = Member

fields = ('name', 'age')

91
3章.Modelにもvalidatiorを設定できる.
class ProjectCode(models.Model):

jsl_code = models.CharField(

verbose_name='プロジェクトコード', max_length=7,

validators=[RegexValidator(regex='^jsl[0-9]{4}$')])

project_code = ProjectCode(jsl_code='jsl0')

project_code.full_clean()
django.core.exceptions.ValidationError: {'jsl_code': ['Enter a valid value.']}

92
Modelにもvalidatorを設定できる.
https://docs.djangoproject.com/ja/3.2/ref/models/instances/#validating-objects
https://docs.djangoproject.com/ja/3.2/ref/models/fields/#validators
Model:.full_clean()で以下を実行.
clean_fields()->Fieldsのvalidation
clean()
validate_unique()
93
ModelとModelFormにvalidatorを設定すると...
class ProjectCode(models.Model):

jsl_code = models.CharField(

max_length=7,

validators=[RegexValidator(regex='^jsl[0-9]{4}$', message='model側')])

class ProjectCodeModelForm(forms.ModelForm):

class Meta:

model = ProjectCode

fields = ('jsl_code', )

# Form由来は末尾1としてみる.

jsl_code = forms.CharField(

validators=[RegexValidator(regex='1$', message='Form側')])

94
ModelとModelFormにvalidatorを設定すると...
form = ProjectCodeModelForm({'jsl_code': 'jsl0000'})

form.errors

Out[14]: {'jsl_code': ['Form側']}

form = ProjectCodeModelForm({'jsl_code': '0001'})

form.errors

Out[12]: {'jsl_code': ['model側']}

95
ModelとModelFormにvalidatorを設定すると...
Model,Formどちらも満たさない場合.

Form側のメッセージしか表示されません.
form = ProjectCodeModelForm({'jsl_code': '00'})

form.errors

Out[16]: {'jsl_code': ['Form側']}

Model側でやっていたチェック(jsl...となること)のメッセージも出したければ

Form側に同じvalidatorを設定する必要があります.
class ProjectCodeModelForm(forms.ModelForm):

# ...省略....

validators=[

RegexValidator(regex='1$', message='Form側'),

RegexValidator(regex='^jsl[0-9]{4}$', message='Form側その2')])

96
ModelとModelFormにvalidatorを設定すると...
ModelForm側で設定した2つのバリデーション結果がerrorsに入っている。
form = ProjectCodeModelForm({'jsl_code': '00'})

form.errors

Out[28]: {'jsl_code': ['Form側', 'Form側その2']}

97

理解して使いこなすDjangoのForm機能(2021 Django Congress発表資料)