Multiple Models to one Form

Hi , I´ve been looking around the net for hours and I just can´t find a solution. There seems to be a lot of confusion about how to create a single form which sends the data to all models at the same time. I wanted to create an app where the user can write a post and upload multiple pictures and also add multiple tags from either a selection of existing ones or create one himself by just clicking a create new button. I thought this was easy but it´s getting more and more complicated the deeper I read into it. For a better understanding see the picture.

I would have just created a single model but the problem is that you can´t store more than one value in a table cell. So when the user wants to add 10 tags to his post it wont work. The other problem is the relationship. If I save multiple model data at once in one form will the relationship between the Post and the other models also be saved at the same time?

And the biggest question is: HOW DO I DO THIS? :slight_smile:

Side note: It’s a whole lot easier if you copy/paste the code related to those models into the body of your post. When doing so, enclose the code (or template) between lines of three backtick - ` characters. This means you’ll have a line of ```, then your code (or template), then another line of ```. This forces the forum software to keep your code properly formatted.

This is a topic that has been discussed here in the forum in multiple locations. Some threads you may wish to read include:

(There are probably more - these are just ones that I remember as potentially being the most useful.)

A key point to remember is that you can render multiple forms in the same template. You can then do whatever processing is necessary when saving those forms.

You may also want to review the docs and examples at the save method.

1 Like

Yes thanks for the answer. I´ve tried all this but it´s still not working.

Here is my models:

from django.db import models
from django.urls import reverse
from datetime import datetime


class Category(models.Model):
    name = models.CharField(max_length=250, help_text='Kategorie', blank=True)

    class Meta:
        verbose_name_plural = "Categories"

    def __str__(self):
        return self.name



class Link(models.Model):
    alias = models.CharField(max_length=250, help_text='Linkalias', blank=True)
    link = models.URLField(("Links zum Thema"), max_length=128, db_index=True, unique=True, blank=True)

    def __str__(self):
        return self.alias


class File(models.Model):
    name = models.CharField(max_length=250, help_text='Dateiname', blank=True)
    upload = models.FileField(upload_to ='uploads/', null=True, blank=False)

    def __str__(self):
        return self.name


class Tag(models.Model):
    name = models.CharField(max_length=250, help_text='tagname', blank=True)

    def __str__(self):
        return self.name


class Thema(models.Model):
    publication_date = models.DateField(verbose_name="Date the Post was published.", default=datetime.now)

    title = models.CharField(max_length=70, help_text="The title of the Post")
    text = models.TextField(max_length=5000,default='text 1')
    text2 = models.TextField(max_length=5000,default='text 2')
    text3 = models.TextField(max_length=5000,default='text 3')

    # Relation Fields
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    links = models.ManyToManyField(Link)
    files = models.ManyToManyField(File)
    tags = models.ManyToManyField(Tag)


    def get_absolute_url(self):
        return reverse('thema-detail', kwargs={'pk' : self.pk})

    def __str__(self):
        return self.title

class Bilder(models.Model):
    name = models.CharField(max_length=250, help_text='Bildname', blank=True)
    beschreibung = models.CharField(max_length=400, help_text='Kurzbschreibung', blank=True)
    thema = models.ForeignKey(Thema, on_delete=models.CASCADE, related_name='bilder', null=True)
    image = models.ImageField(null=True, blank=True, upload_to='images/')

    class Meta:
        verbose_name_plural = "Bilder"

    def __str__(self):
        return self.name

HERE ARE MY VIEWS:

from .models import Thema
from django.views.generic import ListView, DetailView
from .forms import ThemaForm, BilderForm, LinkForm, CategoryForm, TagForm, FileForm
from django.shortcuts import redirect, render
from django.shortcuts import render

class HomePageView(ListView):
    model = Thema
    context_object_name = 'themas'
    template_name = "home/home.html"


class ThemaView(DetailView):
    model = Thema
    context_object_name = 'thema'
    template_name = "home/thema.html"

def get_name(request):
    if request.method == 'POST':
        bilder = BilderForm(request.POST, prefix='bilder')
        thema = ThemaForm(request.POST, prefix='thema')
        link = LinkForm(request.POST, prefix='link')
        tag = TagForm(request.POST, prefix='tag')
        category = CategoryForm(request.POST, prefix='category')
        file = FileForm(request.POST, prefix='file')
        if bilder.is_valid() and thema.is_valid() and link.is_valid() and tag.is_valid() and category.is_valid() and file.is_valid():
            return redirect('home')
    else:
        bilder = BilderForm(prefix='name')
        thema = ThemaForm(prefix='person')
        file = FileForm(prefix='person')
        link = LinkForm(prefix='person')
        tag = TagForm(prefix='person')
        category = CategoryForm(prefix='person')
    return render(request, 'home/create.html', {'bilder': bilder, 'thema': thema, 'link': link, 'tag': tag, 'category': category, 'file': file})

AND HERE ARE THE FORMS:

from django import forms
from .models import Bilder, Thema, Category, Link, Tag, File


class BilderForm(forms.ModelForm):
    class Meta:
        model = Bilder
        fields = ('name', 'beschreibung', 'image')

class ThemaForm(forms.ModelForm):
    class Meta:
        model = Thema
        fields = ('title', 'text')

class CategoryForm(forms.ModelForm):
    class Meta:
        model = Category
        fields = ('name',)

class LinkForm(forms.ModelForm):
    class Meta:
        model = Link
        fields = ('alias', 'link')

class FileForm(forms.ModelForm):
    class Meta:
        model = File
        fields = ('name', 'upload')

class TagForm(forms.ModelForm):
    class Meta:
        model = Tag
        fields = ('name',)


HERE IS THE TEMPLATE:

<form method="post">
    {% csrf_token %}
    {{ bilder.as_p }}
    {{ thema.as_p }}
    {{ link.as_p }}
    {{ category.as_p }}
    {{ file.as_p }}
    {{ tag.as_p }}
    <input type="submit" value="Save">
</form>

I think I also need FormSets. The problem is if I create for example a TAG this way. The tag is saved but at the same time the thema model needs to store the relation to this tag in his own model which it probably can´t because I can´t set it in the form because it isn´t in the database by that time…

Yes, you would want formsets for any of those items for which multiple entries could be made.

The link I provided to the docs for the save method addresses the foreign key / many-to-many field relationship issue you’ve identified.

Okay thanks a lot. I will read more about it. Right now the form fields are all showing up but the somehow I can´t submit them. After I hit the submit button I always get the message that the field is required but all fields are filled. Maybe I need to change the request.POST to request.FILES also in the image and file forms.

How can I use the formSets? Because as far as I have understood they are for making multiple form instances of the same form like more fields to upload files, which is also what I need but I will do this later. First I want to make it work somehow.

Ok, I missed the file upload parts of this. For those, you do need to read File Uploads | Django documentation | Django. They do require some changes both in your form handling and a change in your template. (See the note on enctype.)

Yes, model formsets are what you use to create multiple instances of the same model. They generally require some JavaScript to be written to make them most useful, but it’s not strictly-speaking required.

I´ve changed the view with request.FILES and set enctype in the form. Still it won´t let me submit the form.

def get_name(request):
    if request.method == 'POST':
        bilder = BilderForm(request.POST,request.FILES, prefix='bilder')
        thema = ThemaForm(request.POST, prefix='thema')
        link = LinkForm(request.POST, prefix='link')
        tag = TagForm(request.POST, prefix='tag')
        category = CategoryForm(request.POST, prefix='category')
        file = FileForm(request.POST,request.FILES, prefix='file')
        if bilder.is_valid() and thema.is_valid() and link.is_valid() and tag.is_valid() and category.is_valid() and file.is_valid():
            bilder.save()
            thema.save()
            link.save()
            tag.save()
            category.save()
            file.save()
            return redirect('home')
    else:
        bilder = BilderForm(prefix='name')
        thema = ThemaForm(prefix='person')
        file = FileForm(prefix='person')
        link = LinkForm(prefix='person')
        tag = TagForm(prefix='person')
        category = CategoryForm(prefix='person')
    return render(request, 'home/create.html', {'bilder': bilder, 'thema': thema, 'link': link, 'tag': tag, 'category': category, 'file': file})

In the long run: I used to use formsets, but now I much prefer to use native HTML custom web components with Django rest framework.

You can also look into django.db.models.JSONField for some many to one use cases.

It would be helpful if you provided the complete set of errors being generated by the submission.

I do notice that your prefixes don’t match for your forms between your GET and POST handling.

Also, you might find it easier to diagnose if you reduced this to one form, and added one form at a time until the errors started to appear. It may make it easier to identify where the problems are occurring.

You can’t submit your form because of the missing enctype="multipart/form-data" in the form.

Note: form couldn’t be submitted if there is ImageField or FileField

                    <form method="post" enctype="multipart/form-data">
                        {% csrf_token %}
                        {{ bilder.as_p }}
                      {{ thema.as_p }}
                         {{ link.as_p }}
                     {{ category.as_p }}
                          {{ file.as_p }}
                          {{ tag.as_p }}
                            <br>
                        <button type="submit" class="btn btn-primary">Create Student</button>
                    </form>

Thanks for the answers. I´ve set the enctype in the html template. The problem is

NOT NULL constraint failed: home_thema.category_id

So what I think is that there is a two step process:

  1. Create the entry in the thema table
  2. assign it a category id

the problem is that I can´t assign it a category which doesn´t exist by that time. It has to be created the same time as the thema entry. So I want to store both with the relationship at the same time in different tables. I don´t know if this is possible. Mybe I need to change the order of the save() method.

The order of the saves are important.

You may also need to “manually” set a value on these objects in the process of saving them.

Again, this concept is covered in the previously referenced link to the save method.

But it doesn´t make a difference in this case. I still get the same error even I´ve put the thema.save() at the end:

        if bilder.is_valid() and thema.is_valid() and link.is_valid() and tag.is_valid() and category.is_valid() and file.is_valid():
            bilder.save()
            category.save()
            link.save()
            tag.save()
            file.save()
            thema.save()
            return redirect('home')