Three-level inline formsets

I’m trying to accomplish a three-level stacked inline form in Django. Suppose these models:

class Anuncio(models.Model):
    title = models.CharField(max_length=200)
    delivery = models.CharField(max_length=100)

class Product(models.Model):
    anuncio = models.ForeignKey(Anuncio, on_delete=models.CASCADE)
    name = models.CharField(max_length=200)
    quantity = models.PositiveIntegerField(default=1)
    price = models.PositiveIntegerField()

class Image(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    image = models.ImageField()

There is a relation Anuncio-Product and another relation Product-Image. With this Django package, I accomplished exactly what I want in the Django admin: when creating an Anuncio object, I can add as many Products as I want, and those products can have as many Images as I want. I’m trying to accomplish this in the front end.

I think the way to go is with Django formsets, but I’m facing some problems. All the resources I’ve been able to find online are only ‘two-level’ formsets or in ‘three-level’ cases all the foreign keys point to the same parent model.

With this forms.py file:

class ProductForm(ModelForm):
    class Meta: 
        model = Product 
        fields = ['name', 'quantity', 'price']

class ImageForm(ModelForm):
    class Meta: 
        model = Imagen 
        fields = ['image']

class AnuncioForm(ModelForm):
    class Meta: 
        model = Anuncio 
        fields = ['title', 'delivery']

And this views.py function:

def anunciocreateview(request):
 form = AnuncioForm(request.POST or None)
 ProductFormSet = inlineformset_factory(Anuncio, Product, form=ProductForm)
 ImageFormSet = inlineformset_factory(Product, Image, form=ImageForm)
 if all([form.is_valid(), ProductFormSet.is_valid(), ImageFormSet.is_valid()]):
     parent = form.save(commit=False)
     parent.anunciante = request.user
     parent.save()
     for form in ProductoFormSet:
         child = form.save(commit=False)
         child.anuncio = parent 
         child.save()
     for form in ImagenFormSet:
         imagen = form.save(commit=False)
         imagen.product = form.product
         imagen.save()
  context = {
     'form_1' : form,
     'form_2' : ProductFormSet,
     'form_3' : ImageFormSet,
    }

But I think I’m missing important points when it comes to add the proper relations between models. This set-up gives an AttributeError of: 'ProductForm' object has no attribute '__name__'

The, for example, ‘add (extra) Product’ that appears in AdminStackedInLine I guess it can be accomplished with JavaScript, playing with hidden forms and changing attributes on click events.

Anyone has experience doing something similar or can guide me through the correct direction? Also on how to manage the data and the relations of the submitted forms?

Yes it can be done. We do it in one of our projects.

Yes it can get a bit messy.

You will need to iterate over the top level items to create the second level formset. For each instance of the second level formset, you will need to create the third level formset, using the second level object as the parent instance. You’ll also need to ensure you’re creating unique prefixes for both the individual second and third level formsets.

My post at Multiples of one formset - #12 by KenWhitesell goes into a little bit more detail about how we do this.