Djangoの標準機能であるModel FormSetを使ってみましたので、その備忘録です。FormSetは1画面に複数のフォームを表示するものです。
1.Model FormSetとは
まずはModel Formや単なるFormSetの復習・予習です。
1-1.Model Form
Djangoではmodelが定義してある場合、そのmodelから簡単にformを生成してくれるヘルパークラスModelFormがあります。これによりmodelとformで2重にfieldを定義することを避けることができ、一貫した定義が可能となります。
from django.db import models
from django.forms import ModelForm
TITLE_CHOICES = [
('MR', 'Mr.'),
('MRS', 'Mrs.'),
('MS', 'Ms.'),
]
class Author(models.Model):
name = models.CharField(max_length=100)
title = models.CharField(max_length=3, choices=TITLE_CHOICES)
birth_date = models.DateField(blank=True, null=True)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ['name', 'title', 'birth_date']
class BookForm(ModelForm):
class Meta:
model = Book
fields = ['name', 'authors']
1-2.FormSet
フォームセットとは、同じページで複数のフォームを扱うための抽象化レイヤで、いわばデータグリッドのようなものです。つまり一枚の画面で、複数のエントリーフォームが定義でき、複数の投稿が可能となります。
>>> from django import forms
>>> class ArticleForm(forms.Form):
... title = forms.CharField()
... pub_date = forms.DateField()
>>> from django.forms import formset_factory
>>> ArticleFormSet = formset_factory(ArticleForm)
1-3.Model FormSet
通常のフォームセット のように、Django には Django モデルを簡単に扱うための拡張的なフォームセットのクラスが用意されています。
[モデルのフォームセット]
(https://docs.djangoproject.com/ja/2.2/topics/forms/modelforms/#using-the-formset-in-the-template)
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=('name', 'title'))
2.Model FormSetの実例
2-1.プロジェクト作成
まずはDjangoのプロジェクトを作成します。
python -m venv formset
source formset/bin/activate
cd formset
pip freeze # インストール済みは空であることを確認
pip install django # Djangoをインストール
django-admin startproject formset
cd formset
Remoteからアクセスするための設定です。
---
ALLOWED_HOSTS = ["www.mypress.jp"]
---
ここまででプロジェクトが動作するので確認します。
python manage.py migrate
python manage.py runserver 0:8080
テスト用のアプリを作成します
python manage.py startapp myapp
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'myapp',
]
2-2.画面イメージ
1行が1フォームです。5行あるので5フォーム表示されています。
2-3.ソースコード
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('myapp.urls')),
]
from django.urls import path
from . import views
app_name = 'myapp'
urlpatterns = [
path('', views.addPost, name='index'),
]
from django.db import models
from django.utils import timezone
class Post(models.Model):
title1 = models.CharField('タイトル1', blank=True, default='', max_length=200)
title2 = models.CharField('タイトル2', blank=True, default='', max_length=200)
title3 = models.CharField('タイトル3', blank=True, default='', max_length=200)
title4 = models.CharField('タイトル4', blank=True, default='', max_length=200)
def __str__(self):
return self.title1
**modelformset_factory()**では、与えられた model クラスに対して、FormSet クラスを返します。さらにここではextra=1を指定していますので新規投稿フォームが1個表示されます。max_num=5なので5個投稿すると新規投稿フォームは表示されなくなります。
from django import forms
from .models import Post
class PostCreateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
field.widget.attrs['class'] = 'form-control'
class Meta:
model = Post
fields = '__all__'
### modelformset_factoryはFormSetクラスを返します
PostCreateFormSet = forms.modelformset_factory(
Post, form=PostCreateForm, extra=1, max_num=5
)
post requestの場合は、**formset.save()**で保存してredirectします。
from django.shortcuts import render, redirect
from .forms import PostCreateFormSet
def addPost(request):
### formSetクラスのインスタンス
formset = PostCreateFormSet(request.POST or None)
print(formset.errors)
if request.method == 'POST' and formset.is_valid():
formset.save()
return redirect('myapp:index')
context = {
'formset': formset ### テンプレートにformsetを渡す
}
return render(request, 'myapp/formset.html', context)
Bootstrapの設定をbase.htmlで行います。公式サイトのものをコピペします。
Starter template - Bootstrap
<!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.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<title>Django Model FormSet について</title>
</head>
<body>
<div class="container mt-5">
{% 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.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>
</html>
{{ formset.management_form }} は、深い意味は知りませんが、formsetに必要です。
また**{{ form.id }}**を手動で追加していることに注意してください。idを明記しないとupdate時にエラーになります。
{% extends 'myapp/base.html' %}
{% block content %}
<form action="" method="post">
{% for form in formset %}
{{ form.id }}
<div class="row">
<div class="col-sm-3">
{{ form.title1 }}
{{ form.title1.errors }}
</div>
<div class="col-sm-3">
{{ form.title2 }}
{{ form.title2.errors }}
</div>
<div class="col-sm-3">
{{ form.title3 }}
{{ form.title3.errors }}
</div>
<div class="col-sm-3">
{{ form.title4 }}
{{ form.title4.errors }}
</div>
</div>
{% endfor %}
{{ formset.management_form }}
{% csrf_token %}
<button type="submit" class="btn btn-primary">送信</button>
</form>
{% endblock %}
今回は以上です