naritoブログ

このブログはPython/DjangoとBootstrap4で作成されました
Githubのソースコード

Djangoで、自作ページのインラインフォームに追加ボタン

約26日前 2018年5月1日15:28
プログラミング関連
Django Python

概要


Djangoの管理画面でインラインフォームを使うと、インラインで表示させているデータを自由に追加できます。
どういうことかといいますと....

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

これを押すと、1行追加されます。
自由に追加ができる

この機能を、自作のテンプレートでもつけ加えます。

インラインフォームを自作のテンプレートで使うには、こちらで解説しています。

ソースコード


models.py


問題と、それに紐づく解答モデルがあります。
from django.db import models


class Question(models.Model):
    """問題"""
    text = models.CharField('問題文', max_length=30)

    def __str__(self):
        return self.text


class Answer(models.Model):
    """解答"""
    text = models.CharField('解答文', max_length=200)
    question = models.ForeignKey(Question, verbose_name='問題', on_delete=models.PROTECT)

    def __str__(self):
        return self.text



forms.py


わかりやすいように、extra=1として、最初は1件しか表示されないようにしておきます。
from django import forms
from .models import Question, Answer

AnswerInlineFormSet = forms.inlineformset_factory(
    Question, Answer, fields='__all__', extra=1, can_delete=False,
)



views.py


CreateViewに、インラインフォームセットを加えたビューです。
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.views import generic
from .forms import AnswerInlineFormSet
from .models import Question


class QuestionCreate(generic.CreateView):
    model = Question
    fields = '__all__'
    success_url = reverse_lazy('app:question_list')

    def form_valid(self, form):
        # カテゴリはまだ保存せず、オブジェクトだけ取得する
        question = form.save(commit=False)

        # 取得したカテゴリを、instance引数に渡す
        formset = AnswerInlineFormSet(self.request.POST, instance=question)

        # 記事達が問題なければ、カテゴリを保存し、記事達も保存する
        if formset.is_valid():
            question.save()
            formset.save()
            return HttpResponseRedirect(self.get_success_url())
        else:
            return self.render_to_response(self.get_context_data(form=form, formset=formset))

    def get_context_data(self, **kwargs):
        if 'formset' not in kwargs:
            kwargs['formset'] = AnswerInlineFormSet(self.request.POST or None)
        return super().get_context_data(**kwargs)



ここまでは、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.0/css/bootstrap.min.css"
          integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
</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.0/umd/popper.min.js"
            integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ"
            crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"
            integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm"
            crossorigin="anonymous"></script>
    {% block extrajs %}{% endblock %}
</body>
</html>



question_form.html


少し複雑です。{% block content %}内は、通常のインラインフォームセットと同様です。
{% extends 'app/base.html' %}

{% block content %}
<form action="" method="POST">
    <!-- ここから、問題( form ) の部分 -->
    <h2>問題の追加</h2>
    {{ form.non_field_errors }}
    {% for field in form %}
        {{ field.label_tag }}
        {{ field }} {{ field.errors }}
    {% endfor %}<!-- ここまで、問題の追加 -->

    <hr>

    <!-- ここから、解答( formset )の部分 -->
    <h2>解答の追加</h2>
    {{ formset.management_form }}
    <div id="answers">
    {% for form in formset %}
        {% for field in form.hidden_fields %}{{ field }}{% endfor %}
        {{ form.non_field_errors }}
        {% for field in form.visible_fields %}
            {{ field.label_tag }}
            {{ field }} {{ field.errors }}
        {% endfor %}
        <br>
    {% endfor %}<!-- ここまで、解答の追加 -->
    </div>
    <hr>
    <button id="add" type="button" class="btn btn-primary">解答の追加</button>
    <button type="submit" class="btn btn-primary">送信</button>
    {% csrf_token %}
</form>
{% endblock %}

{% block extrajs %}
<script>
$(function(){
  var current_answer_count = parseInt($('input#id_answer_set-TOTAL_FORMS').val());
  $('button#add').on('click', function(){
    var element = `<label for="id_answer_set-${current_answer_count}-text">解答文:</label><input type="text" name="answer_set-${current_answer_count}-text" maxlength="200" id="id_answer_set-${current_answer_count}-text" /><br> `;
    $('div#answers').append(element);
    current_answer_count += 1;
    $('input#id_answer_set-TOTAL_FORMS').attr('value', current_answer_count);
  });
});

</script>
{% endblock %}


解答の部分には、divタグ等で囲っておきます(div id="answer")。後で解答を追加する際に、このdivを目印にして要素を追加するためです。
    <!-- ここから、解答( formset )の部分 -->
    <h2>解答の追加</h2>
    {{ formset.management_form }}
    <div id="answers">
    {% for form in formset %}
        {% for field in form.hidden_fields %}{{ field }}{% endfor %}
        {{ form.non_field_errors }}
        {% for field in form.visible_fields %}
            {{ field.label_tag }}
            {{ field }} {{ field.errors }}
        {% endfor %}
        <br>
    {% endfor %}<!-- ここまで、解答の追加 -->
    </div>



formsetには、フォームの数を管理するhiddenな要素があり、最初にその数を取得しておきます。
追加するボタンをクリックで、解答文:入力欄 な要素を作成しますが、この際に正しい数を入れなければなりません。特に、入力欄のname属性が重要です。answer_set-0-textのように、0から番号を振られることに注意しましょう。その後は、フォームの数を管理するhiddenな要素('input#id_answer_set-TOTAL_FORMS)のvalueを+1します。
{% block extrajs %}
<script>
$(function(){
  var current_answer_count = parseInt($('input#id_answer_set-TOTAL_FORMS').val());
  $('button#add').on('click', function(){
    var element = `<label for="id_answer_set-${current_answer_count}-text">解答文:</label><input type="text" name="answer_set-${current_answer_count}-text" maxlength="200" id="id_answer_set-${current_answer_count}-text" /><br> `;
    $('div#answers').append(element);
    current_answer_count += 1;
    $('input#id_answer_set-TOTAL_FORMS').attr('value', current_answer_count);
  });
});

</script>
{% endblock %}



見た目


追加するボタンを押してみると...
自作ページのインラインフォーム

正しく、入力欄が追加されました。送信しても、きちんと動きます。
自由に追加ができる