Djangoで、2種類のインラインフォームセットを使う

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

Python - Django
2018年11月22日21:14に更新(約21日前)
2018年10月18日6:03に作成(約56日前)

旧ブログ移行記事です。

概要

以下のようなモデルがあったとします。

from django.db import models
from django.utils import timezone


class Post(models.Model):
    title = models.CharField('タイトル', max_length=200)
    text = models.TextField('本文')
    date = models.DateTimeField('日付', default=timezone.now)

    def __str__(self):
        return self.title


class File(models.Model):
    name = models.CharField('ファイル名', max_length=255)
    src = models.FileField('添付ファイル')
    target = models.ForeignKey(
        Post, verbose_name='紐づく記事',
        blank=True, null=True,
        on_delete=models.SET_NULL
    )


class Image(models.Model):
    name = models.CharField('画像名', max_length=255)
    src = models.ImageField('添付画像')
    target = models.ForeignKey(
        Post, verbose_name='紐づく記事',
        blank=True, null=True,
        on_delete=models.SET_NULL
    )

Django、インラインフォームセットを使う のmodels.pyに、Imageモデルが増えました。Fileと同様に、記事と紐づきます。

記事の追加の際に、Fileと同様にImageもインラインで追加できるようにしたいとします。つまり、Postを親とした2種類のインラインフォームセットを使います。2種類としていますが、3種類、4種類も同じやり方です。

管理サイト

Django、インラインフォームセットを使う のadmin.pyに少し付け加えます。PostAdminのInlinesには複数指定でき、ここで指定したものがインラインでどんどん表示されていきます。

from django.contrib import admin
from .models import File, Image, Post


class FileInline(admin.StackedInline):
    model = File
    extra = 1


class ImageInline(admin.StackedInline):
    model = Image
    extra = 1


class PostAdmin(admin.ModelAdmin):
    inlines = [FileInline, ImageInline]


admin.site.register(Post, PostAdmin)
admin.site.register(File)

ファイルと画像が追加できるようになっていますね。 ファイルと画像がインラインで追加できる

通常ページ

Django、インラインフォームセットを使う のforms.py、views.py、post_form.htmlに少し付け加えます。やることは単純で、フォームセットを地道に追加するだけです。

forms.py extra引数の数が多いと表示フォームが増えて縦長になるので、1にしています。

from django import forms
from .models import Post, File, Image


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__'


FileFormset = forms.inlineformset_factory(
    Post, File, fields='__all__',
    extra=1,
)

# 増えた
ImageFormset = forms.inlineformset_factory(
    Post, Image, fields='__all__',
    extra=1,
)

views.py

def add_post(request):
    form = PostCreateForm(request.POST or None)
    context = {'form': form}
    if request.method == 'POST' and form.is_valid():
        post = form.save(commit=False)
        file_formset = FileFormset(request.POST, files=request.FILES, instance=post)
        image_formset = ImageFormset(request.POST, files=request.FILES, instance=post)  # 増えた
        if file_formset.is_valid() and image_formset.is_valid():  # image_formset.is_valid()が増えた
            post.save()
            file_formset.save()
            image_formset.save()  # 増えた
            return redirect('app:index')

        # エラーメッセージつきのformsetをテンプレートへ渡すため、contextに格納
        else:
            context['file_formset'] = file_formset
            context['image_formset'] = image_formset  # 増えた

    # GETのとき
    else:
        # 空のformsetをテンプレートへ渡す
        context['file_formset'] = FileFormset()
        context['image_formset'] = ImageFormset()  # 増えた

    return render(request, 'app/post_form.html', context)

post_form.html

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

{% block content %}

<form action="" method="post" enctype="multipart/form-data">
    <h2>記事</h2>
    {{ form.as_p }}

    <h2>添付ファイル</h2>
    {{ file_formset.management_form }}
        {% for file_form in file_formset %}
            {{ file_form.as_p }}
            <hr>
        {% endfor %}

    <h2>添付画像</h2>
    {{ image_formset.management_form }}
        {% for image_form in image_formset %}
            {{ image_form.as_p }}
            <hr>
        {% endfor %}

    {% csrf_token %}
    <button type="submit" class="btn btn-primary">送信</button>
</form>

{% endblock %}

Twitterでシェア FaceBookでシェア はてなブックマークでシェア

記事にコメントする