Django multiple forms

Hello, I’m starting to use Django 5 and I’m building a web project for a Secret Santa draw. For that, I created the following model:

python

class Draw(models.Model):
    organizer = models.CharField(max_length=100)
    celebration_date = models.DateField()
    budget = models.DecimalField(max_digits=10, decimal_places=2)

class Participant(models.Model):
    draw = models.ForeignKey(Draw, related_name='participants', on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    email = models.EmailField()

    class Meta:
        unique_together = ['draw', 'email']

And the following form:

python

class SorteoForm(forms.ModelForm):
    class Meta:
        model = Sorteo
        fields = ['organizador', 'fecha_celebracion', 'presupuesto']

class ParticipanteForm(forms.ModelForm):
    class Meta:
        model = Participante
        fields = ['nombre', 'email']
        
ParticipanteFormSet = formset_factory(ParticipanteForm)

To handle all this, I’m using the following view:

python

def create_draw(request):
    if request.method == 'POST':
        draw_form = DrawForm(request.POST)
        if draw_form.is_valid():
            draw = draw_form.save()
            participant = ParticipantForm(request.POST)
            if participant.is_valid():
                participant.draw = draw.id
                participant.save()
                return render(request, 'draw_created.html')
    else:
        context = {
            'draw_form': DrawForm(),
            'participant_form': ParticipantForm(),
        }
        return render(request, 'create_draw.html', context)

Right now, all I need is to save the draw data along with the participants’ data in the database, so I can later implement the draw logic. I’ve managed to save one draw with one participant using the following template, where after a while, I managed to duplicate the participant form with a button:

html

<form action="" method="post">
    {% csrf_token %}
    {{ draw_form }}
    <div id="participants" style="border: 1px solid; margin: 20px;">
        {{ participant_form }}
        {{ participant_form }}
        {{ participant_form }}
    </div>
    <input type="submit" value="Create draw">
    <button type="button" onclick="addParticipant()">Add participant</button>
</form>

<script type="text/javascript">
    function addParticipant(){
        var divParticipants = document.getElementById("participants")
        divParticipants.innerHTML = divParticipants.innerHTML + `{{ participant_form }}`
        console.log(divParticipants)
    }
</script>

My problem arises when saving the data received in the view method. Only one participant is saved, even though at least three are always added in the template.

Welcome @Anthonyx82 !

Side note, you reposted your models instead of the forms in your second box.

When you’re creating multiple instances of the same form, you’ll want to use a Django formset

Also see Model formsets for working with multiple instances of the same model form.

And, since formsets to tend to being a bit confusing at first, you may want to see the thread and the related links at Django formsets tutorials.

Yeah I just discover it im going to try it

I finally managed to solve it: In the template, we need to add the following Django variable {{ participante_formset.management_form }} which allows us to work with forms, in addition to adding a button to add participants that will call a JavaScript function responsible for adding forms with different IDs so that they do not end up with the same name. Finally, the template would look like this:

<body>
    <style>
        .hidden {
            display: none;
        }
    </style>
    <form action="" method="post">
        {% csrf_token %}
        {{ sorteo_form }}
        {{ participante_formset.management_form }}
        <div id='participante-form-list'>
            {% for form in participante_formset %}
            <div class='participante-form'>
                {{ participante_formset.as_p }}
            </div>
            {% endfor %}
        </div>
        <div id='empty-form' class='hidden'>{{ participante_formset.empty_form.as_p }}</div>
        <button id='add-more' type='button'>+ participant</button>
        <input type="submit" value="Carry out draw">
    </form>
</body>

Along with the JavaScript:

<script>
    const addMoreBtn = document.getElementById('add-more')
    const totalNewForms = document.getElementById('id_participante-TOTAL_FORMS')
    addMoreBtn.addEventListener('click', add_new_form)

    function add_new_form(event) {
        if (event) {
            event.preventDefault()
        }

        const currentParticipantes = document.getElementsByClassName('participante-form')
        let currentFormCount = currentParticipantes.length

        const formCopyTarget = document.getElementById('participante-form-list')
        const copyEmptyFormEL = document.getElementById('empty-form').cloneNode(true)
        copyEmptyFormEL.setAttribute('class', 'participante-form')
        copyEmptyFormEL.setAttribute('id', `form-${currentFormCount}`)
        const regex = new RegExp('__prefix__', 'g')
        copyEmptyFormEL.innerHTML = copyEmptyFormEL.innerHTML.replace(regex,
        currentFormCount)
        totalNewForms.setAttribute('value', currentFormCount + 1)

        formCopyTarget.append(copyEmptyFormEL)
    }
</script>

Finally, in the view, we just need to declare the formset with extra set to 0:

ParticipanteFormSet = formset_factory(ParticipanteForm, extra=0)

I arrived at this code thanks to the following video, which surely will be very helpful for you as it was for me: