Django UpdateView Two Seperate Form Save (included inlineformset_factory)

I have a specific problem with my forms. I think it would be better to share my codes instead of explaining the problem in detail.

However, to explain in a nutshell; inside my model I have field OneToOneField and model of that field has inlineformset_factory form. My new model also has a form and I want to save both forms.

I get the following error when I want to save the offer update form:

TypeError at /ru/mytarget/offer-update/T2GTTT053E9/

AdminOfferUpdateView.form_invalid() missing 2 required positional arguments: ‘request_form’ and ‘request_item_formset’

Models:

request.py

class RequestModel(models.Model):
    customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="customer_requests")
    id = ShortUUIDField(primary_key=True, length=10, max_length=10, prefix="T", alphabet="ARGET0123456789", unique=True, editable=False)
    status = models.BooleanField(default=True)
    request_title = models.CharField(max_length=300)
    delivery_time = models.CharField(max_length=50)
    shipping_country = models.CharField(max_length=50)
    shipping_address = models.CharField(max_length=300)
    preferred_currency = models.ForeignKey(Currency, on_delete=models.CASCADE)
    shipping_term = models.ForeignKey(ShippingTerm, on_delete=models.CASCADE)
    delivery_term = models.ForeignKey(DeliveryTerms, on_delete=models.CASCADE)
    request_statuses = models.ForeignKey(RequestStatus, on_delete=models.CASCADE, blank=True, default=1)
    is_accepted = models.BooleanField(default=False)
    is_rejected = models.BooleanField(default=False)
    is_offer_created = models.BooleanField(default=False)
    updated_on = models.DateTimeField(auto_now=True)
    published_date = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return str(self.request_title)

    class Meta:
        verbose_name_plural = "Requests"
        verbose_name = "Request"

    def get_absolute_url(self):
        return reverse('mytarget:customer_request_details', kwargs={'pk': self.id})


class RequestItem(models.Model):
    request_model = models.ForeignKey(RequestModel, on_delete=models.CASCADE, related_name="request_items")
    product_name = models.CharField(max_length=300)
    product_info = models.TextField(max_length=2000, blank=True)
    target_price = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True)
    price = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True)
    quantity = models.DecimalField(max_digits=10, decimal_places=2)

    dimensions = models.CharField(max_length=100, blank=True)
    net_weight = models.CharField(max_length=20, blank=True)
    gross_weight = models.CharField(max_length=20, blank=True)
    hs_or_tn_ved_code = models.CharField(max_length=100, blank=True)
    brand = models.CharField(max_length=100, blank=True)
    manufacturer = models.CharField(max_length=100, blank=True)
    origin_country = models.CharField(max_length=50, blank=True)
    manufacturer_address = models.CharField(max_length=300, blank=True)

offer.py

class OfferModel(models.Model):
    request_model_name = models.OneToOneField(RequestModel, on_delete=models.CASCADE, primary_key=True)
    status = models.BooleanField(default=True)
    offer_validity = models.CharField(max_length=50, blank=True)
    terms_of_payment = models.CharField(max_length=100, blank=True, default=_("100% upon order"))
    profit_rate = models.PositiveIntegerField(blank=True, default=0)
    swift_payment_slip = models.FileField(upload_to='payment-slips/%Y/%m/%d/', validators=[max_file_size], blank=True)

    shipping_costs = models.CharField(max_length=300, blank=True)
    shipping_costs_quantity = models.PositiveIntegerField(blank=True, default=0)
    shipping_costs_price = models.DecimalField(max_digits=15, decimal_places=2, blank=True, default=0)
    additional_expenses = models.CharField(max_length=300, blank=True)
    additional_expenses_quantity = models.PositiveIntegerField(blank=True, default=0)
    additional_expenses_price = models.DecimalField(max_digits=15, decimal_places=2, blank=True, default=0)

    upload_shipping_details = models.FileField(upload_to='offer-files/%Y/%m/%d/', blank=True, null=True)
    upload_additional_documents = models.FileField(upload_to='offer-files/%Y/%m/%d/', blank=True, null=True)
    product_photo = models.ImageField(upload_to='offer-files/%Y/%m/%d/', blank=True, null=True)
    product_photo_thumbnail = ImageSpecField(source='product_photo',
                                             processors=[Transpose(),
                                                         SmartResize(width=2000, height=1500, upscale=False)
                                                         ],
                                             format='WEBP',
                                             options={'quality': 100})
    product_shipment_photo = models.ImageField(upload_to='offer-files/%Y/%m/%d/', blank=True, null=True)
    product_shipment_photo_thumbnail = ImageSpecField(source='product_shipment_photo',
                                                      processors=[Transpose(),
                                                                  SmartResize(width=2000, height=1500, upscale=False)
                                                                  ],
                                                      format='WEBP',
                                                      options={'quality': 100})
    shipping_tracking_code = models.CharField(max_length=500, blank=True)

    is_offer_accepted = models.BooleanField(default=False)
    is_offer_rejected = models.BooleanField(default=False)
    is_offer_canceled = models.BooleanField(default=False)
    send_proforma_invoice = models.BooleanField(default=False)
    is_payment_made = models.BooleanField(default=False)
    send_invoice = models.BooleanField(default=False)
    send_offer = models.BooleanField(default=False)
    is_detailed_offer = models.BooleanField(default=False)

    updated_on = models.DateTimeField(auto_now=True)
    published_date = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return str(self.request_model_name)

    class Meta:
        verbose_name_plural = "Offers"
        verbose_name = "Offer"

    def get_absolute_url(self):
        return reverse('mytarget:admin_offer_update', kwargs={'pk': self.request_model_name})

Forms:

request_create_form.py

class CustomerRequestForm(forms.ModelForm):

    disabled_fields = ("customer",)

    class Meta:
        model = RequestModel
        fields = ("customer", "request_title", "delivery_time", "shipping_country", "shipping_address",
                  "preferred_currency", "shipping_term", "delivery_term")

        widgets = {
            'request_title': TextInput(attrs={'class': 'form-control tableFormInputs',
                                              'placeholder': _('Example: Printers, Toner, and Cartridges')}),
            'delivery_time': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'shipping_country': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'shipping_address': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'preferred_currency': Select(attrs={'class': 'form-select tableFormInputs',
                                                'aria-label': _('Preferred Currency')}),
            'shipping_term': Select(attrs={'class': 'form-select tableFormInputs',
                                           'aria-label': _('Shipping Term')}),
            'delivery_term': Select(attrs={'class': 'form-select tableFormInputs',
                                           'aria-label': _('Delivery Terms')}),
        }

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('customer')
        super(CustomerRequestForm, self).__init__(*args, **kwargs)
        self.fields['preferred_currency'].queryset = self.fields['preferred_currency'].queryset.translated().order_by("translations__currency_name")
        self.fields['shipping_term'].queryset = self.fields['shipping_term'].queryset.translated().order_by("translations__shipping_term")

        for field in self.disabled_fields:
            self.fields[field].widget = forms.HiddenInput()
            self.fields[field].disabled = True


class CustomerRequestItemForm(forms.ModelForm):

    class Meta:
        model = RequestItem
        fields = ("product_name", "product_info", "target_price", "price", "quantity", "dimensions", "net_weight", "gross_weight",
                  "hs_or_tn_ved_code", "brand", "manufacturer", "origin_country", "manufacturer_address")
        exclude = ()

        widgets = {
            'product_name': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'product_info': Textarea(attrs={'class': 'form-control tableFormInputs', 'maxlength': 1000, 'rows': 3}),
            'target_price': NumberInput(attrs={'class': 'form-control tableFormInputs'}),
            'price': NumberInput(attrs={'class': 'form-control tableFormInputs'}),
            'quantity': NumberInput(attrs={'class': 'form-control tableFormInputs'}),

            'dimensions': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'net_weight': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'gross_weight': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'hs_or_tn_ved_code': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'brand': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'manufacturer': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'origin_country': TextInput(attrs={'class': 'form-control tableFormInputs'}),
            'manufacturer_address': TextInput(attrs={'class': 'form-control tableFormInputs'}),
        }


RequestItemInlineFormset = inlineformset_factory(RequestModel, RequestItem,
                                                 form=CustomerRequestItemForm,
                                                 extra=1,
                                                 can_delete=True
                                                 )

offer_update_form.py

class AdminOfferUpdateForm(forms.ModelForm):
    disabled_fields = ()
    hidden_fields = ("request_model_name",)

    request_title = forms.CharField(required=False, widget=TextInput(attrs={'class': 'form-control tableFormInputs', 'placeholder': _('Example: Printers, Toner, and Cartridges')}))
    delivery_time = forms.CharField(required=False, widget=TextInput(attrs={'class': 'form-control tableFormInputs'}))
    shipping_country = forms.CharField(required=False, widget=TextInput(attrs={'class': 'form-control tableFormInputs'}))
    shipping_address = forms.CharField(required=False, widget=Textarea(attrs={'class': 'form-control tableFormInputs', 'rows': 3}))
    preferred_currency = forms.ChoiceField(required=False, widget=Select(attrs={'class': 'form-select tableFormInputs', 'aria-label': _('Preferred Currency')}))
    shipping_term = forms.ChoiceField(required=False, widget=Select(attrs={'class': 'form-select tableFormInputs', 'aria-label': _('Shipping Term')}))
    delivery_term = forms.ChoiceField(required=False, widget=Select(attrs={'class': 'form-select tableFormInputs', 'aria-label': _('Delivery Terms')}))
    request_statuses = forms.ChoiceField(required=False, widget=Select(attrs={'class': 'form-select tableFormInputs', 'aria-label': _('Delivery Terms')}))

    class Meta:
        model = OfferModel
        fields = ("request_model_name", "offer_validity", "terms_of_payment", "profit_rate", "is_offer_accepted",
                  "is_offer_rejected", "send_proforma_invoice", "is_payment_made", "send_invoice", "send_offer",
                  "is_detailed_offer", "is_offer_canceled", "shipping_costs", "shipping_costs_quantity", "shipping_costs_price", "additional_expenses",
                  "additional_expenses_quantity", "additional_expenses_price", "upload_shipping_details",
                  "upload_additional_documents", "product_photo", "product_shipment_photo", "shipping_tracking_code",

                  "request_title", "delivery_time", "shipping_country", "shipping_address", "preferred_currency",
                  "shipping_term", "delivery_term", "request_statuses",
                  )

        widgets = {'offer_validity': TextInput(attrs={'class': 'form-control tableFormInputs'}),
                   'terms_of_payment': TextInput(attrs={'class': 'form-control tableFormInputs'}),
                   'profit_rate': NumberInput(attrs={'class': 'form-control tableFormInputs'}),

                   'shipping_costs': TextInput(attrs={'class': 'form-control tableFormInputs'}),
                   'shipping_costs_quantity': NumberInput(attrs={'class': 'form-control tableFormInputs'}),
                   'shipping_costs_price': NumberInput(attrs={'class': 'form-control tableFormInputs'}),
                   'additional_expenses': TextInput(attrs={'class': 'form-control tableFormInputs'}),
                   'additional_expenses_quantity': NumberInput(attrs={'class': 'form-control tableFormInputs'}),
                   'additional_expenses_price': NumberInput(attrs={'class': 'form-control tableFormInputs'}),

                   'upload_shipping_details': ClearableFileInput(attrs={'class': 'form-control w-auto tableFormInputs'}),
                   'upload_additional_documents': ClearableFileInput(attrs={'class': 'form-control w-auto tableFormInputs'}),
                   'product_photo': ClearableFileInput(attrs={'class': 'form-control w-auto tableFormInputs'}),
                   'product_shipment_photo': ClearableFileInput(attrs={'class': 'form-control w-auto tableFormInputs'}),
                   'shipping_tracking_code': TextInput(attrs={'class': 'form-control tableFormInputs'}),

                   'is_offer_accepted': CheckboxInput(attrs={'class': 'form-check-input'}),
                   'is_offer_rejected': CheckboxInput(attrs={'class': 'form-check-input'}),
                   'is_offer_canceled': CheckboxInput(attrs={'class': 'form-check-input'}),
                   'send_proforma_invoice': CheckboxInput(attrs={'class': 'form-check-input'}),
                   'is_payment_made': CheckboxInput(attrs={'class': 'form-check-input'}),
                   'send_invoice': CheckboxInput(attrs={'class': 'form-check-input'}),
                   'send_offer': CheckboxInput(attrs={'class': 'form-check-input'}),
                   'is_detailed_offer': CheckboxInput(attrs={'class': 'form-check-input'}),
                   }

    def __init__(self, *args, **kwargs):
        super(AdminOfferUpdateForm, self).__init__(*args, **kwargs)
        self.fields["preferred_currency"].choices = [(c.id, c.currency_name) for c in Currency.objects.all()]
        self.fields["shipping_term"].choices = [(st.id, st.shipping_term) for st in ShippingTerm.objects.all()]
        self.fields["delivery_term"].choices = [(dt.id, dt.delivery_term) for dt in DeliveryTerms.objects.all()]
        self.fields["request_statuses"].choices = [(r.id, r.status) for r in RequestStatus.objects.all()]

        for field in self.disabled_fields:
            self.fields[field].disabled = True

        for field in self.hidden_fields:
            self.fields[field].widget = forms.HiddenInput()

Views:

offer_update_view.py

@method_decorator([login_required(login_url=reverse_lazy("accounts:signin")), user_is_superuser], name='dispatch')
class AdminOfferUpdateView(UpdateView):
    model = OfferModel
    form_class = AdminOfferUpdateForm
    template_name = "mytarget/admin_offer_update.html"

    def get_context_data(self, **kwargs):
        context = super(AdminOfferUpdateView, self).get_context_data(**kwargs)
        if self.request.POST:
            context['request_form'] = AdminOfferUpdateForm(self.request.POST, instance=self.object.request_model_name)
            context['request_item_formset'] = RequestItemInlineFormset(self.request.POST, instance=self.object.request_model_name)
        else:
            context['request_form'] = AdminOfferUpdateForm(instance=self.object.request_model_name)
            context['request_item_formset'] = RequestItemInlineFormset(instance=self.object.request_model_name)
        return context

    def form_valid(self, form):
        context = self.get_context_data()
        request_form = context['request_form']
        request_item_formset = context['request_item_formset']
        with transaction.atomic():
            self.object = form.save()
            if request_form.is_valid() and request_item_formset.is_valid():
                request_form.instance = self.object.request_model_name
                request_form.save()
                request_item_formset.instance = self.object.request_model_name
                request_item_formset.save(commit=False)
                for ri in request_item_formset:
                    ri.save(commit=False)
                    request_item_formset.save()
        return super(AdminOfferUpdateView, self).form_valid(form)

    def form_invalid(self, form, request_form, request_item_formset):
        return self.render_to_response(
            self.get_context_data(form=form, request_form=request_form, request_item_formset=request_item_formset)
        )

    def get_initial(self):
        self.object = self.get_object()
        if self.object:
            return {"request_model": self.object.request_model_name, "request_item_formset": self.object.request_model_name}
        return super().initial.copy()

    def get_success_url(self):
        return reverse('mytarget:admin_offer_update', kwargs={'pk': self.object.id})

Side note: When asking for assistance with an error, please post the complete traceback. Frequently, the one-line summary is not sufficient information to diagnose a problem.

In this case the answer is fairly straight-forward - the function signature for form_invalid does not accept additional parameters. The post function will call form_invalid if the form is not valid, passing only form as a parameter.

<opinion> The Django-provided edit CBVs are not a good fit for a multiple-form or multiple-object structure. My experience has always been that it ends up being easier to either use a more basic base view or an FBV in those situations. </opinion>

Thank you very much Ken for the information and warning.

For now, I would like to ask a question in order not to create the form from scratch. I was able to fetch data from the other form into the form fields in the offer_update_view.py file. Ensuring the view is saved will fix the issue. I think there is a problem with my get_context_data and form_valid functions. So I’m getting the data from the inheritance form but I can’t save it in the new form. Do you have any ideas about this?

Recommendation: Rewrite this view from scratch as an FBV. It’s going to be a lot clearer as to how it works and you’re not going to be fighting a set of functions that aren’t designed for what you’re trying to do here. Your “you in six months” will appreciate it.
Just throw this away and redo it from scratch.

OK, thank you for your feedbacks Ken. I will try from scratch.

Hello Ken,

The problem is mostly solved when starting from scratch. I was able to recreate the data in my new form by inheriting it from the other model. I just can’t render the data in the other model’s inlineformset_factory fields.

def create_offer_button(request):
    if request.method == "POST":
        post = request.POST.get("post_pk")
        obj = RequestModel.objects.get(pk=post)
        if obj.is_accepted:
            OfferModel.objects.create(request_model_name=obj,
                                      request_title=obj.request_title,
                                      delivery_time=obj.delivery_time,
                                      shipping_country=obj.shipping_country,
                                      shipping_address=obj.shipping_address,
                                      preferred_currency=obj.preferred_currency,
                                      shipping_term=obj.shipping_term,
                                      delivery_term=obj.delivery_term)

            obj.is_offer_created = True
            obj.save()
        return redirect(request.META.get("HTTP_REFERER"))

How can i access to RequestModel inlineformset_factory fields?

I solved it my own way.:

obj_items = RequestItem.objects.filter(request_model=obj)
for item in obj_items:
    OfferRequestItem.objects.create(offer_model_id=obj.id, product_name=item.product_name)