Djangoで、フォームセットにフォームの追加ボタンを実装する

2018-10-18 / PythonDjangoJavaScript

概要

Djangoでフォームセットを使うシリーズの1つです。Djangoの管理画面でインラインフォームを利用すると、下側に「○○の追加」というリンクがあり、好きな数だけ行を追加できます。これを自分で実装していきます。

管理画面でインラインフォームを利用しています。下側に「Fileの追加」とありますね。 下側にファイルの追加がある

これを押すと、1行追加されます。
1行追加された

この機能を、自作のテンプレートでもつけ加えます。Django、インラインフォームセットの基本的な使い方のモデルやフォーム等を元に説明していきます。インラインフォームセットを例にしていますが、他のフォームセットも流れは同様です。

フォーム

わかりやすいように、extra=1として、最初は1件しか表示されないようにしておきます。

FileFormset = forms.inlineformset_factory(
    Post, File, fields='__all__',
    extra=1, can_delete=False
)

モデルやビューは、Django、インラインフォームセットの基本的な使い方のものをそのまま使います。

テンプレートファイル

base.htmlを編集します。Bootstrap4のスターターテンプレートそのままですが、後で追加のJavaScriptを書くため、下側で{% block extrajs %}と定義しておきます。

<!doctype html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

    <title>フォームセット</title>
  </head>
  <body>
    <div class="container mt-3">
        {% block content %}{% endblock %}
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
    {% block extrajs %}{% endblock %}
  </body>
</html>

そして、post_form.htmlを編集します。

{% extends 'app/base.html' %}

{% block content %}
<form action="" method="post" enctype="multipart/form-data">
    <h2>記事</h2>
    {{ form.as_p }}

    <h2>添付ファイル</h2>
    {{ formset.management_form }}
    <div id="file-area">
        {% for file_form in formset %}
            {{ file_form.as_p }}
            <hr>
        {% endfor %}
    </div>

    {% csrf_token %}
    <button type="submit" class="btn btn-primary">送信</button>
    <button id="add" type="button" class="btn btn-primary">ファイルの追加</button>
</form>

{% endblock %}

{% block extrajs %}
<script>
$(function(){
    var totalManageElement = $('input#id_file_set-TOTAL_FORMS');
    var currentFileCount = parseInt(totalManageElement.val());
    $('button#add').on('click', function(){
        var nameElement = $('<input>', {
            type: 'name',
            name: 'file_set-' + currentFileCount + '-name',
            id: 'id_file_set-' + currentFileCount + '-name',
        });
        var fileElement = $('<input>', {
            type: 'file',
            name: 'file_set-' + currentFileCount + '-src',
            id: 'id_file_set-' + currentFileCount + '-src',
        });
        $('div#file-area').append(nameElement);
        $('div#file-area').append(fileElement);
        currentFileCount += 1;
        totalManageElement.attr('value', currentFileCount);
    });
});
</script>
{% endblock %}

{% block content %}内はインラインフォームセットのときと同様ですが、要素を追加しやすくするため、<div id="file-area">という要素で囲んでいます。{% block extrajs %}内で、ファイルを追加しています。

追加するボタンを押してみると...
追加するボタンを押す

見た目がちょっと雑ですが、入力欄が追加されました。送信しても、きちんと動きます。
追加された

この記事の関連記事

Djangoでフォームセットを使うシリーズ

2018-10-17 / PythonDjangoシリーズ・まとめ

- Djangoにはフォームセットという、複数のフォームを一括で扱うための機能があります。いくつか種類があり、様々な機能があるので、それらを紹介していきます。

Django、インラインフォームセットの基本的な使い方

2018-10-18 / PythonDjango

- Djangoでフォームセットを使うシリーズの1つです。今回はインラインフォームセットについてです。記事に添付するファイルがあったとして、記事の作成時に一緒にファイルを複数作ることができるようになります。

Django、保存してもう一つ追加・保存して編集を続けるボタン

2018-11-07 / PythonDjango

- Djangoで、「保存してもう一つ追加」「保存して編集を続ける」機能を作成します。管理サイトにある機能ですが、通常のページにも簡単につけることができます。

コメント欄

記事にコメントする

Django6ヶ月目

naritoさん、こんばんは。 いつもDjangoのWebアプリ開発の際に勉強させていただいております、ありがとうございます。

本記事を拝見し、実際に追加可能な画像フォームセットを作りましたが、 下記2点質問がございます。

・追加できる最大数(上限フィールド数)はどこで定義するのが最適でしょうか?

・追加したフィールドを削除する仕様は、forms.pyのcan_delete=Trueで制御できなかったのですが、どのように実現できますでしょうか?

恐れ入りますが、お手すきでご確認いただけますと幸いです。 よろしくお願いいたします!

コメントに返信する

なりと

・追加できる最大数(上限フィールド数)はどこで定義するのが最適でしょうか?

フォームセットの定義時にmax_num=10としておくと、HTML側に<input type="hidden" name="file_set-MAX_NUM_FORMS" value="10" id="id_file_set-MAX_NUM_FORMS">のように最大フォーム数がセットされた要素ができます。valueにmax_numの値があります。

JavaScriptでフォームを作成する際に、この値を超えそうなら作成しない、といった感じの処理になります。

・追加したフィールドを削除する仕様は、forms.pyの「can_delete=True」で制御できなかったのですが、どのように実現できますでしょうか?

具体的にどういった意味でしょうか。

Django6ヶ月目

ご回答いただき誠にありがありがとうございます。 上限フィールド数のコントロール無事に実装できました!

・追加したフィールドを削除する仕様は、forms.pyの「can_delete=True」で制御できなかったのですが、どのように実現できますでしょうか?

具体的にどういった意味でしょうか。

上記ありがありがとうございます。 formsetをforで取り出しておらず、ひとつずつ取り出していたのでdeleteのチェックボックスが表示されなかったのですが、 https://narito.ninja/blog/detail/30/ の記事を拝見し、{{ form.DELETE }}で取り出せるということがわかりました。

名無し

こちらの記事と組み合わせて質問させていただきます。 (Djangoで、プレビュー付き画像アップロード欄を作る) [https://narito.ninja/blog/detail/139/]

javaScriptで動的に生成したフォームに対し、画像プレビュー用のwidgetsの機能を組み合わせようとしているのですが、なかなかうまくいきません。 js上で擬似的なフォームをつくっているので、forms.pyで定義したものと別物だとは理解していますが、そこから詰まってしまいました。

どうかお力添えをお願いいたします。

コメントに返信する

名無し

jQueryのchange()で値が変更されたら発動 こちらの記事を参考しました。

Djangoというより、jQueryの仕様だったようですね。