Djangoでフォームの入力欄を動的に追加する

## はじめに
独学でプログラミングを学習中の大学3年生です。

以前、WebアプリケーションをPHPで作成したときに[フォームの入力欄を動的に追加した][Qiita]ことがありました。現在、WebアプリをDjangoに移行中なのですが、タイトルの問題に当たってなかなか解決しなかったためここに残しておきます。

ドキュメントにはこれしか書かれてなかった…
（2020/08/26 追記: [ここ](https://docs.djangoproject.com/ja/3.1/topics/forms/formsets/#formset-prefix)にちゃんと書かれてました笑）

> **empty_form**
> BaseFormSet provides an additional attribute empty_form which returns a form instance with a prefix of __prefix__ for easier use in dynamic forms with JavaScript.


初学者にも分かりやすいように、できるだけ簡潔なコードになるよう心がけています。


## 以前の記事
この記事だけで分からないときは、以前の記事を見てもらえると分かるかもしれません。

- [フォームの入力欄を動的に追加する][Qiita]
- [CodePen][CodePen]（動作を確認できます。）

[Qiita]:https://qiita.com/Yuji-207/items/0b0bd72330974186be8c
[CodePen]:https://codepen.io/yuji-207/pen/OJNMYgv


## コード

Djangoでフォームを動的に扱う場合には、Formsetsの**empty_form**を使います。

Djangoが生成する一般的なフォームの`input`には、`id_form-{No.}-place_name`のようなidが自動で挿入されます。ただし、**empty_form**を使った場合はすべての`input`のidが`id_form-__plefix__-place_name`となるため、`__plefix__`の部分を数字に書き換える必要があります。

```template.html
<form method="POST" id="form">
    {% csrf_token %}
    {{ form.management_form }}
    <div class="text">
        {{ form.empty_form.as_p }}
    </div>
    <button type="button" id="btn-clone">clone</button>
    <button type="submit">submit</button>
</form>
```

実際には、このように表示されます。

```template.html
<form method="POST" id="form">
    <!-- ...... -->
    <input type="hidden" name="form-TOTAL_FORMS" value="2" id="id_form-TOTAL_FORMS">
    <!-- ...... -->
    <div class="text">
        <p>
            <label for="id_form-__prefix__-place_name">Input:</label>
            <input type="text" name="form-__prefix__-place_name" id="id_form-__prefix__-place_name">
            <input type="hidden" name="form-__prefix__-id" id="id_form-__prefix__-id">
        </p>
    </div>
    <button type="button" id="btn-clone">clone</button>
    <button type="submit">submit</button>
</form>
```


`__prefix__`を書き換えます。
また、`input[id="id_form-TOTAL_FORMS"]`のvalueで`input`の数を指定します。

```script.js
$(function() {


    // clone
    $('#btn-clone').click(function() {
        var text = $('.text').last();  // 最後尾にあるinput
        clone = text.clone().insertAfter(text);  // inputを最後尾に追加
        clone.find("input[type='text']").val('');  // valueもクローンされるので削除する
    });


    // ここからDjango用のidなどを操作する
    $('#form').submit(function() {  // フォームを送信する直前

        // フォームの入力欄の数を指定する
        const text = $('.text');
        $('[name=form-TOTAL_FORMS]').val(text.length);

        // それぞれの入力欄の__prefix__をindexで置換する
        text.each(function(index, element){
            html = $(element).html().replace(/__prefix__/g, index);
            value = $(element).find("input[type='text']").val();  // valueが消えるので保存しておく
            $(element).html(html);
            $(element).find("input[type='text']").val(value);
        });

    });
});
```

フォームの削除機能を実装したい場合は、[以前の記事][Qiita]をご覧ください。ここでは見やすいようにフォームの追加だけを実装しています。なお、最終的にフォームの削除機能の実装を想定しているため、コードが冗長になっています。フォームの削除機能が必要ない場合は修正してください。


## 参考

- [Formsets（Django documentation）](https://docs.djangoproject.com/en/3.1/topics/forms/formsets/#empty-form)

- [動的なフィールド数を持つDjangoフォームクラスを作成する（it-swarm.dev）](https://www.it-swarm.dev/ja/python/%E5%8B%95%E7%9A%84%E3%81%AA%E3%83%95%E3%82%A3%E3%83%BC%E3%83%AB%E3%83%89%E6%95%B0%E3%82%92%E6%8C%81%E3%81%A4django%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B/971177164/)

- [正しい方法でjavascriptを使用してdjango formsetに動的フォームを追加してください（stackoverrun）
](https://stackoverrun.com/ja/q/5808297)