Search code examples
djangoformsvalidationinline-formset

Django Inlineformsets - Custom validation with django-extra-views


So I have a question about the form_valid for django-extra-views. I used the CreateWithInlinesView method to create a form with multiple inline formsets.

The base form called "Order" contains two different formsets, "Booking" and "Payment". An Order always has to include a minimum of one Booking but does not necessarily need to have a Payment when the Order is created.

The form for the Payment will be generated nonetheless. I would like to validate the Payment form on a "payment_amount" > 0. If no payment is made at the moment the order is created, no PaymentInline should be saved.

views.py

class BookingInline(InlineFormSetFactory):
    model = Booking
    form_class = BookingForm
    prefix = 'booking_formset'
    factory_kwargs = {
        'extra': 0,
        'min_num': 1,
        'validate_min': True,
        'can_delete': True
    }

class PaymentInline(InlineFormSetFactory):
    model = Payment
    form_class = PaymentForm
    prefix = 'payment_formset'
    factory_kwargs = {
        'extra': 1,
        'min_num': 0,
        'validate_min': False,
        'can_delete': True
    }

class OrderCreateView(NamedFormsetsMixin, CreateWithInlinesView):
    model = Order
    inlines = [BookingInline, PaymentInline]
    inlines_names = ['booking_formset', 'payment_formset']
    form_class = OrderForm
    template_name = 'orders/order_form.html'

    def get_success_url(self):
        return reverse_lazy('order_detail', kwargs={'pk': self.object.pk})

    def forms_valid(self, form, inlines):
        """
        If the form and formsets are valid, save the associated models.
        """
        self.object = form.save(commit=False)
        self.object.created_by = self.request.user
        form.save(commit=True)
        for formset in inlines:
            formset.save()
        return HttpResponseRedirect(self.get_success_url())

So the logic would need to be something like the following, though I get an error saying 'Order' object has no attribute 'payment', since it is a reversed relation...

views.py

def forms_valid(self, form, inlines):
    """
    If the form and formsets are valid, save the associated models.
    """
    self.object = form.save(commit=False)
    self.object.created_by = self.request.user
    form.save(commit=True)
    for booking_formset in inlines:
        booking_formset.save()
    for payment_formset in inlines:
        if self.object.payment.amount > 0:
            payment_formset.save()
        else:
            pass
    return HttpResponseRedirect(self.get_success_url())

Does anyone know how to address the different formsets within the form?

Thanks in advance!


Solution

  • I'm not sure which fields are available on your formset but you could save with commit=False then iterate over the un-committed payment instances to get the total payment amount.

    def forms_valid(self, form, inlines):
        booking_formset = inlines[0]
        payment_formset = inlines[1]
    
        self.object = form.save(commit=False)
        self.object.created_by = self.request.user
        form.save(commit=True)
        booking_formset.save()
    
        payments = payment_formset.save(commit=False)
        total_payment = sum(payment.amount for payment in payments)
        if total_payment > 0:
            payment_formset.save()
    
        return HttpResponseRedirect(self.get_success_url())
    

    UPDATE:

    The lines booking_formset in inlines: and payment_formset in inlines: will cause a problem. Assuming inlines is a list you will be iterating over the list twice. It's the equivalent of doing below:

    lst = [1, 2]
    for x in lst:
        print(x)
    
    for y in lst:
        print(y)
    
    >>> 1
    >>> 2
    >>> 1
    >>> 2