5
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Django Model FormSet について

Last updated at Posted at 2019-05-26

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

フォームセットとは、同じページで複数のフォームを扱うための抽象化レイヤで、いわばデータグリッドのようなものです。つまり一枚の画面で、複数のエントリーフォームが定義でき、複数の投稿が可能となります。

フォームセット (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からアクセスするための設定です。

statictest/settings.py
---
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フォーム表示されています。

image.png

2-3.ソースコード

formset/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls')),
]
myapp/urls.py
from django.urls import path
from . import views

app_name = 'myapp'

urlpatterns = [
    path('', views.addPost, name='index'),
]
myapp/models.py
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個投稿すると新規投稿フォームは表示されなくなります。

モデルフォーム関数

myapp/forms.py
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します。

myapp/views.py
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

myapp/templates/myapp/base.html
<!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時にエラーになります。

myapp/templates/myapp/formset.html
{% 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 %}

今回は以上です

5
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?