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>