Djangoで動的にフォームクラスを作成する

Djangoのフォーム機能は通常、 django.forms.Form クラスを継承して定義をします。

フォームを使う | Django ドキュメント | Django

リクエスト時に動的にフォームクラスを生成したい場合もあるので、今回はそれをやってみます。

フォームクラスを動的に生成する関数

myapp/forms.py:

from django import forms


def create_form(form_schema_dict):
    """
    動的にフォームを作成する

    :form_schema_dict: フォームのスキーマを定義した辞書
    :return: フォームクラス

    例:
    form_class = create_form({
        "name": {"max_length": 100},
        "email": {"max_length": 100},
    })
    """
    form_schema = {}
    for field_name, options in form_schema_dict.items():
        form_schema[field_name] = forms.CharField(label=field_name, **options)
    return type("DynamicForm", (forms.Form,), form_schema)

この create_form 関数では、指定されたフィールド名とオプションでCharFieldを持つフォームクラスを生成して返します。

type関数は、Pythonの組み込み関数で、引数を3つ指定する場合は、新たな型を生成して返却します。

参考: 組み込み関数 - Python 3.12.3 ドキュメント

動作確認

Django shellからインポートして、フォームクラスが動作するか試します。

>>> from myapp import forms
>>> form_class = forms.create_form({
...     'name': {'max_length': 100},
...     'email': {'max_length': 100},
... })
>>> form = form_class()
>>> form
<DynamicForm bound=False, valid=Unknown, fields=(name;email)>

Djangoのビューで表示してみる

Djangoのビューから呼び出して、テンプレートファイル上でのレンダリングも試します。

myapp/views.py:

from django.shortcuts import render
from django import forms
from .forms import create_form


def index(request):
    form_class = create_form(
        {
            "name": {"max_length": 100},
            "email": {"max_length": 100},
            "message": {"widget": forms.Textarea},
        }
    )
    form = form_class()
    return render(request, "index.html", {"form": form})

このビュー関数では、単純にフォームを表示するだけにしていますが、やろうと思えば is_valid() を呼び出してフォーム内容を検証するようなコードにも変更できます。

myapp/templates/index.html:

<html>
  <head>
    <title>form test</title>
  </head>
  <body>
    <form>
      {{ form }}
    </form>
  </body>
</html>

サンプルコード全体は、GitHubに置いてます。

https://github.com/tokibito/sample_nullpobug/tree/main/django/dynamic-form