こんにちは。sinyです。
この記事では、Djangoで複数フォームを1テンプレート内に表示する方法について試行錯誤した際の対応をまとめました。
今回説明する以下のデモでは、学習用データのアップロードとテスト用データのアップロードを異なるタブ上(同じテンプレートファイル上)で実行したいようなケースを想定して作成しています。
モデルの定義
今回は、学習用データファイル(UploadTrainData)とテスト用ファイル(UploadTestData)のファイルアップロード用のモデルクラスを以下の通り定義します。
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に以下の設定を行っておきます。
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/'
アップロード先のフォルダも以下の通り作成しておきます。
<プロジェクト>medial\train_data
<プロジェクト>medial\test_data
フォームの定義
フォームについても、学習用データとテスト用データのアップロードフォームをそれぞれ定義しておきます。
今回はModelFormを使って先ほど定義したモデルの定義を使っています。
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}))
ビューの定義
続いてビューの定義です。
今回はTemplateViewを承継してUploadViewというクラスを定義しています。
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
まず、学習用データとテスト用データのアップロードフォームを別々に定義します。
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をテンプレートに渡せるようにします。
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メソッドの後半部分です。
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メソッドを実行してフォーム(ファイル)を保存してあげます。
def form_save(self, form): obj = form.save() return obj
テンプレートファイルの設定
テンプレートはbase.htmlとmain.htmlの2つを作成します。
まずは共通テンプレートbase.htmlです。
<!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です。
<!-- 親テンプレートを読み込む--> {% 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}}
以下は、ファイルアップロード完了時のメッセージを表示させるために設定しています。
{% 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 %}
以上で、デモ動画のような画面が完成します。