djangomodelformformsetinline-formsetupdateview

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)
    ......
    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)
    ......

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)
    ......

    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')}),
            ......
        }

    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'}),
            ......
        }


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')}))
    ......

    class Meta:
        model = OfferModel
        fields = ("request_model_name", "offer_validity", ......
                  )

        widgets = {'offer_validity': TextInput(attrs={'class': 'form-control tableFormInputs'}),
                   ......
                   '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})


Solution

  • I solved my problem. I created a button function that creates a new model with inheritance of other model fields. In this way, there is no need to edit the form of the other model inside the form of my current model.