Does Django inlineformset allow for editing & submitting related model data?

I'm attempting to display a form for a tennis event (location, time/date) and each of its participants (name, status). I was recommended to use an inlineformset, which I assume would allow editing of all those fields with one submit button. What I'm getting: the participants fields are all editable fields, but the event fields are not (they're just displayed):

Am I correct in assuming that an inlineformset should allow this approach?

Models:

class Event(models.Model):
    date_time = models.DateTimeField(default=datetime.now(), auto_now=False, auto_now_add=False, null=False, blank=False) # 
    location = models.CharField(max_length=50, null=False, blank=False, help_text="Where the event will happen, e.g. location and court") 
    roster = models.ForeignKey(Roster, related_name='events', on_delete=models.CASCADE, default=None, null=True, help_text="The event's roster") 
    comment = models.CharField(max_length=400, null=True, blank=True, help_text="A comment about an event, e.g. time / court constraints etc.")

class Participant(models.Model):
    member_name = models.CharField(max_length=50, null=False, blank=False)
    event = models.ForeignKey(Event, on_delete=models.CASCADE, null=False, related_name="participants")
    status = models.CharField(max_length=50, choices=StatusChoices, default="Unknown")
    comment = models.CharField(max_length=400, blank=True, null=True)

View:

def event_edit(request, event_id):

    event = get_object_or_404(Event, pk=event_id)
    roster = event.roster

    ParticipantInlineFormSet = inlineformset_factory(Event, Participant, fields=["id","member_name", "status","comment"], extra=0)

    if request.method == 'POST':
        formset = ParticipantInlineFormSet(request.POST, request.FILES, instance=event)
        if formset.is_valid():
            formset.save()
            return render(request,'event_list.html',{"roster": roster, 'events': roster.events.all()})
    else:
        formset = ParticipantInlineFormSet(instance=event)
    print(formset)
    return render(request, "event_edit_2.html", {"formset": formset, "event": event})

Template:

<form method="post">

    {% csrf_token %}
    {{ formset.management_form }}
    {{ form.as_p }}

    <p>{{formset.instance.location}}</p>
    <p>{{formset.instance.date_time}}</p>
    <p>{{formset.instance.comment}}</p>

    {% for form in formset %}
        {{form.member_name}}
        {{form.status}}
        {{form.comment}}<br>
    {% endfor %}

    <button class="button" type="submit">Save</button>
</form>

What you're doing wrong here is you're trying to render the Event instance inside the html. It doesn't work in that way, you need to create a separete form for the Event like here:


class EventForm(forms.ModelForm):
    class Meta:
        model = Event
        fields = ['location', 'date_time', 'comment']

Secondly, `ParticipantInlineFormSet` is for only the `Participant` model to be edited/created, you're just connecting Event instance to this formset because to mark a specific Event instance as a parent model.

So inside the view, you need to use the EventForm I gave you above like that:


def event_edit(request, event_id):
    event = get_object_or_404(Event, pk=event_id)
    roster = event.roster
    
        ParticipantInlineFormSet = inlineformset_factory(
            Event, 
            Participant, 
            fields=["member_name", "status", "comment"], 
            extra=0,
            can_delete=True
        )
        
        if request.method == 'POST':
            form = EventForm(request.POST, instance=event)
            formset = ParticipantInlineFormSet(request.POST, instance=event)
        
            # You need to save both separately
            if form.is_valid() and formset.is_valid():
                form.save()
                formset.save()
                return render(request, 'event_list.html', {"roster": roster, 'events': roster.events.all()})
        else:
            form = EventForm(instance=event)
            formset = ParticipantInlineFormSet(instance=event)
        
        return render(request, "event_edit_2.html", {
            "form": form,       # parent form
            "formset": formset, # children formSet
            "event": event
        })

Before saving inside the post handling scope, you need to check for both form and formset to be valid via `is_valid` built-in method.

You can render those inside the html like that:

<form method="post">
    {% csrf_token %}
    
    <!-- Render EventForm -->
    <h3>Event Details</h3>
    {{ form.as_p }}
    <!-- END Render EventForm -->
    <hr>

    <!-- Render ParticipantInlineFormSet -->
    <h3>Participants</h3>
    {{ formset.management_form }}
    
    <!-- Loop over the forms in the formset and render them -->
    {% for participant_form in formset %}
        <div class="participant-row">
            <!-- Hidden ID field (for existing instances) -->
            {{ participant_form.id }}
            
            {{ participant_form.member_name.label }}: {{ participant_form.member_name }}
            {{ participant_form.status.label }}: {{ participant_form.status }}
            {{ participant_form.comment.label }}: {{ participant_form.comment }}
            
            <!-- deletion logic here **needs extra code inside the view** -->
            {% if formset.can_delete %}
                Delete? {{ participant_form.DELETE }}
            {% endif %}
            <br>
        </div>
    {% endfor %}
    <!-- END Loop over the forms in the formset and render them -->
    <!-- END Render ParticipantInlineFormSet -->
    <button class="button" type="submit">Save</button>
</form>
Вернуться на верх