【Django】1 つのテンプレートに複数フォームを配置する実装方法|サブミットボタンごとに処理を振り分け

スポンサードリンク



Django · Forms · 1 テンプレート複数フォーム

数の独立したフォームを 1 つのテンプレート内に配置したい場面はよくあります(例: ログインフォームとサインアップフォームを同じページに表示、複数のサブミットボタンで処理を分岐したい等)。Django の標準ビューは 1 ビュー = 1 フォームを前提にしているため、工夫が必要です。本記事では、1 テンプレートに複数フォームを配置し、サブミットボタンごとに処理を振り分ける実装パターン を、サンプルコード付きで解説します。

この記事では、Djangoで複数フォームを1テンプレート内に表示する方法について試行錯誤した際の対応をまとめました。

今回説明する以下のデモでは、学習用データのアップロードとテスト用データのアップロードを異なるタブ上(同じテンプレートファイル上)で実行したいようなケースを想定して作成しています。

 

user@sinyblog:~/article 01_section_1.mdモデルの定義

今回は、学習用データファイル(UploadTrainData)とテスト用ファイル(UploadTestData)のファイルアップロード用のモデルクラスを以下の通り定義します。

python


from django.db import models





class UploadTrainData(models.Model):

    

    class Meta:

        verbose_name = '学習データ'

        verbose_name_plural = '学習データ'



    trainFile= models.FileField(verbose_name="Train data Upload", upload_to='train_data/', null=True, blank=True)

    



class UploadTestData(models.Model):

    

    class Meta:

        verbose_name = 'テストデータ'

        verbose_name_plural = 'テストデータ'



    testFile= models.FileField(verbose_name="Test data Upload", upload_to='test_data/', null=True, blank=True)



 

学習用データは 「upload_to='train_data/」、テスト用データは「upload_to='test_data/」としてアップロード先のフォルダを分けています。

あわせて、メディアファイルの設定としてsettings.pyに以下の設定を行っておきます。

python


MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

MEDIA_URL = '/media/'

アップロード先のフォルダも以下の通り作成しておきます。

<プロジェクト>medial\train_data

<プロジェクト>medial\test_data

user@sinyblog:~/article 02_section_2.mdフォームの定義

フォームについても、学習用データとテスト用データのアップロードフォームをそれぞれ定義しておきます。

今回はModelFormを使って先ほど定義したモデルの定義を使っています。

python


from django import forms

from .models import UploadTrainData, UploadTestData







class UploadTrainDataForm(forms.ModelForm):

    class Meta:

        model = UploadTrainData

        fields = ('trainFile',)

    

    

    trainFile = forms.FileField(label="学習データのアップロード",

        widget=forms.ClearableFileInput(attrs={'multiple': True}))





class UploadTestDataForm(forms.ModelForm):

    class Meta:

        model = UploadTestData

        fields = ('testFile',)

    

    

    testFile = forms.FileField(label="Upload Test File",

        widget=forms.ClearableFileInput(attrs={'multiple': True}))

 

user@sinyblog:~/article 03_section_3.mdビューの定義

続いてビューの定義です。

今回はTemplateViewを承継してUploadViewというクラスを定義しています。

 

python


from django.shortcuts import render

from django.views.generic import TemplateView

from .forms import UploadTrainDataForm, UploadTestDataForm

from .models import UploadTrainData, UploadTestData





class UploadView(TemplateView):

    

    test_form_class = UploadTestDataForm

    train_form_class = UploadTrainDataForm

    template_name = 'app1/main.html'

    

    def post(self, request):

        train_form = self.train_form_class(request.POST, request.FILES or None)

        test_form = self.test_form_class(request.POST, request.FILES or None)

        context = self.get_context_data(train_form=train_form,

                                        test_form=test_form)



        if train_form.is_valid():

            self.form_save(train_form)

            message_f1 = "学習データのアップロードが完了しました。"

            context = {

                   'train_form': train_form,

                   'test_form': test_form,

                   'message_f1': message_f1,}

            return render(self.request, 'app1/main.html', context)



 

        if test_form.is_valid():

            self.form_save(test_form)

            message_f2 = "テストデータのアップロードが完了しました。"

            context = {

                  'train_form': train_form,

                  'test_form': test_form,

                  'message_f2': message_f2,}

            return render(self.request, 'app1/main.html', context)

 

        else:

            context = {

                  'train_form': train_form,

                  'test_form': test_form,

                  }

            return render(self.request, 'app1/main.html', context)



    def form_save(self, form):

        obj = form.save()

        return obj



 

まず、学習用データとテスト用データのアップロードフォームを別々に定義します。

default


test_form_class = UploadTestDataForm  #テストデータアップロード用フォーム

train_form_class = UploadTrainDataForm #学習用データアップロード用フォーム

template_name = 'app1/main.html'

 

postメソッドを新たに定義して、テンプレート側からのリクエストオブジェクトをそれぞれtrain_form、test_formとして受け取ります。

さらにget_context_dataメソッドをオーバーライドしてtrain_formとtest_formをテンプレートに渡せるようにします。

python


   def post(self, request):

        train_form = self.train_form_class(request.POST, request.FILES or None)

        test_form = self.test_form_class(request.POST, request.FILES or None)

        context = self.get_context_data(train_form=train_form,

                                        test_form=test_form)

 

続いて、postメソッドの後半部分です。

python


        if train_form.is_valid():

            self.form_save(train_form)

            message_f1 = "学習データのアップロードが完了しました。"

            context = {

                   'train_form': train_form,

                   'test_form': test_form,

                   'message_f1': message_f1,}

            return render(self.request, 'app1/main.html', context)



 

        if test_form.is_valid():

            self.form_save(test_form)

            message_f2 = "テストデータのアップロードが完了しました。"

            context = {

                  'train_form': train_form,

                  'test_form': test_form,

                  'message_f2': message_f2,}

            return render(self.request, 'app1/main.html', context)

 

        else:

            context = {

                  'train_form': train_form,

                  'test_form': test_form,

                  }

            return render(self.request, 'app1/main.html', context)

 

train_formとtest_formに対してis_valid()メソッドを実行して、バリデーションチェックOKの場合にform_saveメソッドをCALLしてファイルを保存するようにします。

CALLするform_saveメソッドは以下の通りで、formのsaveメソッドを実行してフォーム(ファイル)を保存してあげます。

python


    def form_save(self, form):

        obj = form.save()

        return obj

 

user@sinyblog:~/article 04_section_4.mdテンプレートファイルの設定

テンプレートはbase.htmlとmain.htmlの2つを作成します。

まずは共通テンプレートbase.htmlです。

default


<!doctype html>

<html>

<head>

  <meta charset="utf-8" />

  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

  {# Bootstrap4を使う #}

  <!-- linkタグでbootstrapのcssファイルを読み込む -->

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css">

  <!-- jQuery -->

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

  <!-- jQuery UI -->

  <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>

  <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css">

  <!-- タイトルの設定-->

  <title>Tutorial</title>

<body>

{% block content %}

   <!-- block content ~ endblockの間に子テンプレートの内容が差し込まれる -->

{% endblock %}



<!-- bootstrapのjavascrit読み込み-->

<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>

</body>

</html>

 

bootstrapとJQueryを利用するためにCDNを利用している以外は特段ないです。

最後にmain.htmlです。

default


<!-- 親テンプレートを読み込む-->

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

{% block content %}





<div id="tabs">

  <ul class="nav nav-tabs">

    <li class="nav-item"><a href="#tabs-1" class="nav-link">学習データロード</a></li>

    <li class="nav-item"><a href="#tabs-2" class="nav-link">テストデータロード</a></li>

    <li class="nav-item"><a href="#tabs-3" class="nav-link">学習の実行</a></li>

  </ul>

  <div id="tabs-1" class="tab-pane">

    <div class="card text-white bg-dark mb-3" style="max-width: 200rem;">

      <div class="card-header"><h2>学習データロード</h2></div>

      <div class="card-body">

      <h4 class="card-title">Upload train data</h4>

      <p class="card-text">学習用データをアップロードしてください。</p>

      <form method="post" enctype="multipart/form-data"> 

       {% csrf_token %}

       {{train_form.trainFile}}

       <button type="submit" class="btn btn-sm btn-primary">アップロード</button>    

       </form>

       </div>

       </div>

      {% if message_f1 %}

       <div class="card-body alert alert-info alert-dismissible">

          <strong>{{message_f1}}</strong>

      </div>

      {% endif %}

  </div>



  <div id="tabs-2" class="tab-pane">

      <div class="card text-white bg-dark mb-3" style="max-width: 200rem;">

        <div class="card-header"><h2>テストデータロード</h2></div>

        <div class="card-body">

        <h4 class="card-title">Upload test data</h4>

        <p class="card-text">テスト用データをアップロードしてください。</p>

        <form method="post" enctype="multipart/form-data"> 

         {% csrf_token %}

         {{test_form.testFile}}

         <button type="submit" class="btn btn-sm btn-primary">アップロード</button>    

         </form>

         </div>

         </div>

     {% if message_f2 %}

     <div class="card-body alert alert-info alert-dismissible">

        <strong>{{message_f2}}</strong>

     </div>

     {% endif %}

  </div>

  





      <div id="tabs-3" class="tab-pane">

          <div class="card text-white bg-dark mb-3" style="max-width: 200rem;">

            <div class="card-header"><h2>学習</h2></div>

            <div class="card-body">

            <h4 class="card-title">学習</h4>

            <p class="card-text">テスト用データを使って学習を実行します。</p>

            <form method='POST' enctype="multipart/form-data">

              {% csrf_token %}<br>

             <button type="submit" id="indicator" value=" 送信 " class="btn btn-sm btn-primary">実行</button>

           </form>

             </div>

             </div>

      </div>



</div>



<script>

$(function() {

  $( "#tabs" ).tabs();

});

</script>





{% endblock content %}

 

タブの切り替え実装については省略しますが、以下の記事を参考にしました。

 

今回は、「学習データロード」、「テストデータロード」、「学習」という3つのタブを定義しています。

各タブ内の設定で、以下の通りそれぞれフォームを指定することでタブ毎に異なったフォームを表示させます。

  • 「学習データロード」タブ

       → train_formフォームを指定  {{train_form.trainFile}}

  • 「テストデータロード」タブ

       → test_formフォームを指定   {{test_form.testFile}}

以下は、ファイルアップロード完了時のメッセージを表示させるために設定しています。

default


{% if message_f1 %}

<div class="card-body alert alert-info alert-dismissible">

<strong>{{message_f1}}</strong>

</div>

{% endif %}





{% if message_f2 %}

<div class="card-body alert alert-info alert-dismissible">

<strong>{{message_f2}}</strong>

</div>

{% endif %}

 

以上で、デモ動画のような画面が完成します。

 

おすすめの記事