Django2で動的にformを作る

# 背景

djangoのformは色々処理してくれて便利なんですが基本的には決まったフォームを事前に定義する形式。
わたしはDB等でマスター設定されたデータから動的にformが作りたくて色々勉強しました。
つまりアンケートフォームのような指定したアンケートの設問をDBから取得してフォームを作るといった事がしたかったのです。

# 動的にformオブジェクトを生成される

ネットの海を徘徊しているといい文献がありました。
[Dynamic Forms In Django](https://zerowithdot.com/django-dynamic-forms/)
これを参考（ほぼ丸パクリ）しつつ動的フォームを作成したいと思います。

## form.pyはpassのみ!?

マスタ値から動的に生成するのでコーディング時に要素は何も決まっていません。
よって`EnqForm`は`pass`のみ(笑)

```python:form.py
from django import forms

class EnqForm(forms.Form):
    pass
```

## views.pyが肝になってきます

先ほど`pass`のみのform classを定義したので、そこに動的にFieldを割り当てていきます。
今回は簡単な紹介のためマスター値はDBからとってこず、事前に定義しています。

```python:views.py
from django.shortcuts import render
from .forms import EnqForm
from django import forms

# Create your views here.
def dynamic(request):
    context = {}
    content = {}
    form_item = {}

    # define enquete fields
    qs = []
    qs.append( {'title':'title1', 'description': 'note1', 'type': 'text' , 'required': False} )
    qs.append( {'title':'title2', 'description': 'note2', 'type': 'text' , 'required': True} )
    qs.append( {'title':'title3', 'description': 'note3', 'type': 'choice' , 'required': False} )

    # create enquete form objects
    no = 0
    for q in qs:
        if q['type'] == 'text':
            form_item.update( { ('ans%d' % no): forms.CharField(label=q['title'], label_suffix=q['description'], required=q['required'], max_length=256) } )
        elif q['type'] == 'choice':
            form_item.update( { ('ans%d' % no): forms.ChoiceField(label=q['title'], label_suffix=q['description'], required=q['required'], choices=(('a','a'),('b','b'),('c','c'),('d','d'))) } )
        no += 1

    DynamicEnqForm = type('DynamicEnqForm', (EnqForm,), form_item )

    # get enquete answers
    if request.method == 'POST':
        for key in request.POST.keys():
            if key != 'csrfmiddlewaretoken':
                content[key] = request.POST[key]

    # draw enquete form
    DynamicForm = DynamicEnqForm(content)
    context['enq_form'] = DynamicForm
    return render(request, "dynamic.html", context)
```
`# define enquete fields`の`qs`定義部がマスター値だと思ってください。
本番ではもちろんDBからModelを経由して値を取得します。

`# create enquete form objects`が実際にフォームオブジェクトを定義している部分になります。
`qs`の`type`に応じて適切なfieldオブジェクトを`form_item`代入して行きます。
今回は説明のためtextとchoiceだけに絞ってます。しかもchoiceの選択肢も適当です。

```python
DynamicEnqForm = type('DynamicEnqForm', (EnqForm,), form_item )
```
の部分で`pass`だけ定義したform class`EnqForm`が登場します。
先ほど作成した`form_item`を要素として`EnqForm`が`DynamicEnqForm`として生成されます。


`# get enquete answers`の部分はPOSTされた場合に値を取得する部分になります。
ここで次の処理を行えばOKですね。

`# draw enquete form`は動的に作成された`DynamicEnqForm`にPOSTされた値をセットしなおして描画しています。

## templateについて

```html:dynamic.html
<form action="" method="POST">
  {% csrf_token %}
  {% for field in enq_form %}
  <div>
    <div>
      <label for="{{ field.id_for_label }}">{{ field.label }}</label>
      <p>{{ field.field.label_suffix }}</p>
      {{ field }}
    </div>
  </div>
  {% endfor %}
  <input type="submit">
</form>
```
すいません。本当に必要な部分しか書いてません(笑)


## url.pyでパスをつなぐ

```python:url.py
from django.urls import path

from . import views

urlpatterns = [
    path('', views.dynamic, name='index'),
]
```
最後にパスをつないで完了です。

この例では永久ループで処理自体は行われませんが、マスター相当のデータを変えることで動的にform生成できるようになりました(^^♪

# 後日談
このソースではMultipleChoiceFieldには対応できません。
ので、対応しました(笑)
- [Django2で動的にformを作る2-MultipleChoiceFieldに対応させる-](https://qiita.com/imp555sti/items/ed3a577e8138918718a4)

# 参考文献
- [Dynamic Forms In Django](https://zerowithdot.com/django-dynamic-forms/)
- [Django、フォームの表示方法まとめ](https://narito.ninja/blog/detail/98/)