I am encountering a problem with nested formsets that fail to save correctly when I submit the form from the frontend. These formsets are meant for managing meeting schedules and their respective time slots, which function perfectly in the Django admin interface but do not work on the custom frontend form.
My Data Models
class MeetingPackage(models.Model):
title = models.CharField(_("Package title"), max_length=120)
cost = models.DecimalField(_("Cost"), max_digits=10, decimal_places=2)
url_slug = AutoSlugField(populate_from='title', unique=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = _("Meeting Package")
verbose_name_plural = _("Meeting Packages")
def __str__(self):
return f"{self.title} - {self.cost}USD"
class Meeting(models.Model):
title = models.CharField(_("Meeting title"), max_length=120)
url_slug = AutoSlugField(populate_from='title', unique=True)
created_at = models.DateTimeField(auto_now_add=True)
packages = models.ManyToManyField(
MeetingPackage, verbose_name=_("Available Packages"), blank=True)
base_price = models.DecimalField(_("Base Price"), max_digits=10, decimal_places=2)
details = models.TextField(_("Meeting Details"), blank=True, null=True)
class Meta:
verbose_name = _("Meeting")
verbose_name_plural = _("Meetings")
def __str__(self):
return self.title
class MeetingSchedule(models.Model):
meeting = models.ForeignKey(
Meeting, on_delete=models.CASCADE, related_name='schedules')
schedule_date = models.DateField(_("Schedule date"))
class Meta:
verbose_name = _("Meeting Schedule")
verbose_name_plural = _("Meeting Schedules")
def __str__(self):
return self.schedule_date.strftime("%d %B %Y")
class TimeSlot(models.Model):
schedule = models.ForeignKey(
MeetingSchedule, on_delete=models.CASCADE, related_name='time_slots')
slot_time = models.TimeField(_("Time slot"))
capacity = models.PositiveIntegerField(
_("Maximum capacity"), default=8)
class Meta:
verbose_name = _("Time Slot")
verbose_name_plural = _("Time Slots")
ordering = ['slot_time']
constraints = [
models.UniqueConstraint(
fields=['schedule', 'slot_time'], name='unique_schedule_time')
]
def clean(self):
if self.pk and self.capacity < self.registered_count:
raise ValidationError({
'capacity': _('Capacity cannot be lower than registered participants (%(count)d)')
% {'count': self.registered_count}
})
@property
def registered_count(self):
return self.participants.count() if hasattr(self, 'participants') else 0
def available_spots(self):
return max(0, self.capacity - self.registered_count)
def is_at_capacity(self):
return self.registered_count >= self.capacity
def __str__(self):
return f"{self.slot_time.strftime('%H:%M')} ({self.available_spots()} spots left)"
View Logic
class MeetingFormView(LoginRequiredMixin, SuccessMessageMixin):
model = Meeting
form_class = MeetingForm
template_name = "meetings/meeting_form.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
context['schedule_formset'] = ScheduleFormSet(
self.request.POST, instance=self.object)
for idx, schedule_form in enumerate(context['schedule_formset']):
form_prefix = f'slot_{idx}'
if schedule_form.instance.pk:
schedule_form.timeslot_formset = TimeSlotFormSet(
self.request.POST, instance=schedule_form.instance, prefix=form_prefix)
else:
schedule_form.timeslot_formset = TimeSlotFormSet(
self.request.POST, prefix=form_prefix)
else:
context['schedule_formset'] = ScheduleFormSet(
instance=self.object)
for idx, schedule_form in enumerate(context['schedule_formset']):
form_prefix = f'slot_{idx}'
if schedule_form.instance.pk:
schedule_form.timeslot_formset = TimeSlotFormSet(
instance=schedule_form.instance, prefix=form_prefix)
else:
schedule_form.timeslot_formset = TimeSlotFormSet(
prefix=form_prefix)
return context
def form_valid(self, form):
context = self.get_context_data()
schedule_formset = context['schedule_formset']
if form.is_valid() and schedule_formset.is_valid():
self.object = form.save()
schedule_formset.instance = self.object
saved_schedules = schedule_formset.save()
for idx, meeting_schedule in enumerate(saved_schedules):
schedule_form = schedule_formset.forms[idx]
if hasattr(schedule_form, 'timeslot_formset'):
slot_formset = schedule_form.timeslot_formset
if slot_formset.is_bound and slot_formset.is_valid():
for slot_form in slot_formset:
if slot_form.is_valid() and slot_form.cleaned_data and not slot_form.cleaned_data.get('DELETE', False):
slot_instance = slot_form.save(commit=False)
slot_instance.schedule = meeting_schedule
slot_instance.save()
return super().form_valid(form)
return self.form_invalid(form)
class CreateMeetingView(MeetingFormView, CreateView):
success_url = reverse_lazy('meeting_list')
success_message = _("Meeting created successfully.")
class UpdateMeetingView(MeetingFormView, UpdateView):
success_message = _("Meeting updated successfully.")
def get_success_url(self):
return reverse('edit_meeting', kwargs={'pk': self.object.pk})
The issue is that when I add new schedules and time slots using Alpine.js on the frontend, they don’t get saved even though the form validation passes. This only happens with dynamically added forms, not the existing ones. Do you have any suggestions for fixing my formset handling?