Save line items in same form as order item

Hi,

class Orders(models.Model):
    ID = models.AutoField(primary_key=True)
    OrderNo = models.IntegerField('OrderNo', blank=False, null=False)
    Customer = models.ForeignKey('Customers', on_delete=models.DO_NOTHING)
    ShippingAddress = models.ForeignKey('CustomerAddresses', on_delete=models.DO_NOTHING, related_name="ShippingAddress")
    BillingAddress = models.ForeignKey('CustomerAddresses', on_delete=models.DO_NOTHING, related_name="BillingAddress")
    Items = models.ManyToManyField('OrderItems')
    OrderStatus = models.CharField('OrderStatus', max_length=48, blank=False, null=False)
    Payment_Status = models.CharField('Payment_Status', max_length=128, blank=False, null=False)
    Created_Date = models.DateTimeField(auto_now_add=True)
    Updated_Date = models.DateTimeField(auto_now=True, null=True)
    ShippingMethod = models.ForeignKey('DeliveryMethods', on_delete=models.DO_NOTHING)
    PaymentMethod = models.ForeignKey('PaymentProvider', on_delete=models.DO_NOTHING)
    UserID = models.ForeignKey('MasterData', on_delete=models.CASCADE)

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


class OrderItems(models.Model):
    ID = models.AutoField(primary_key=True)
    Item = models.ForeignKey('Products', on_delete=models.DO_NOTHING)
    Quantity = models.IntegerField('Quantity')
    Price = models.DecimalField('Price', max_digits=10, decimal_places=2)
    OrderNo = models.ForeignKey('Orders', on_delete=models.DO_NOTHING)

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

I want to save the Order Items, which I render now manually in my view, how can I say on save button it should also save the Order items? Two Forms? Inline Forms? What is best practise?

regards
Christopher.

Sounds like a job for Formsets (also see ModelFormsets and inline formsets).

Briefly, a formset is a set of identical forms. You can create your Order form, then a formset for the OrderItems, and render them all on your page. If they’re all part of the same html form element, they’ll all be posted back to the server, and saving the formset will save all the individual forms within the formset.
Note: since you have an FK relating back to the Order object, in the case of a new Order being added and not edited, you may need to assign that FK value to each form in the formset before saving.
Sidebar: The common convention is that Model names are singular, not plural. The general recommendation would be that those models be called Order and OrderItem.

2 Likes

Hi, thank you, I render the Items for the Order not over a Form, because UI experience would be bad, I have the articles in the view in a list object from session, do you know how I can for every object in this list save it via the view in the form? For a single value, it’s easy to fill out the value in the if form.is_valid() part but how do I this if I have 5 articles in the session and create 5 records for one order?

Can you post your view and template associated with this process? I’m not understanding what you’re trying to say here, and I think it would be a lot more helpful if we could talk about this in the context of the actual code being used.

It’s a lot of code :slight_smile: But I save the items which is chosen in a session and safe it in a variable, like this:

        if 'add_product' in request.POST:
            try:
                productlist = request.session['product_ids']
            except:
                request.session['product_ids'] = []
                productlist = []
            product_id = request.POST.get('product_id')
            productlist.append(product_id)
            request.session.modified = True
            return redirect('add_order_view')

and after refreshing:

    try:
        choosen_products = Products.objects.filter(ID__in=request.session.get('product_ids'))
    except:
        choosen_products = None

this variable did I use for rendering the article informations in the template in a table. That is good for design / UX because if I render OrderItem via Form I must use ModelChoiceField because of the foreign key field, and I cannot let the users choose article from dropdown if they have hundreds of articles. Therefor I use for displaying the data this solution. If the customer / user has chosen 5 articles I have a variable with 5 query sets, and these must save in the database. I hope this makes sense.

Just a quick comment - this statement is not true. You can render your fields using any widgets you choose and apply any styling you wish to use. Django does not require the use of any specific type of field for any purpose. Yes, there are some defaults applied, but you are not compelled to use those defaults.

Strange because I was fighting a whole day for having a read-only text field widget for choosing customer field which is a foreign key field in database to customers, for design reasons I have returned first name and last name and rendered it in the text field but if I clicked on submit I got error that the value should be a whole number (which is true) it’s hard to find the right way between clean code and user experience. No Im using a select2 widget which have a search function for a single customer it is ok but for adding products I can’t render multiple dropdown menus :smiley:

FormSet: How can I pass variables to the form?

Old:
Itemform = OrderItemsForm(request.user.id, order_no_int,request.POST)

New:
Itemform = modelformset_factory(OrderItems, form=OrderItemsForm)

I need to pass two varaibles to forms because I filter the query in the Form:

def __init__(self, user, order, *args, **kwargs):

See Passing custom parameters to formset forms.

The modelformset_factory does not create a formset, it creates a formset class. You still need to create the instance of that class to create the formset. It’s in that call you pass the forms_kwargs parameter to pass a parameter to the creation of each instance of the form. (The docs also show how you can change the parameters being passed based on the instance of the form.)

Hi, what is not clear to me, that formset will it be create in the views or forms.py file? Django examples mix up examples and its very confusing to me.

I have this in my forms:

class OrderItemsForm(forms.ModelForm):
    Quantity = forms.IntegerField(widget=forms.TextInput(
        attrs={'class': 'form-control', 'autofocus': True, 'data-toggle': 'touchspin', 'value': 1,
               'style': 'max-width: 50px;', 'data-bts-max': 1}))
    Price = forms.DecimalField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True}))

    class Meta:
        model = OrderItems
        fields = ['Item', 'Price', 'OrderNo', 'Quantity']

    def __init__(self, user,  *args, **kwargs):
        super(OrderItemsForm, self).__init__(*args, **kwargs)
        self.fields['Item'] = forms.ModelChoiceField(widget=forms.Select(
            attrs={'class': 'select2 form-control select2-multiple mb-3', 'multiple': 'multiple',
                   'data-toggle': 'select2'}), queryset=Products.objects.filter(UserID=user), empty_label=None)
        self.fields['OrderNo'] = forms.ModelChoiceField(
            widget=forms.Select(attrs={'class': 'form-control select2 mb-3', 'data-toggle': 'select2'}),
            queryset=Orders.objects.filter(OrderNo=order), empty_label=None)
    # self.fields['Quantity'] = forms.IntegerField(widget=forms.TextInput(attrs={'class': 'form-control', 'autofocus': True, 'data-toggle': 'touchspin', 'value': 1, 'style': 'max-width: 50px;', 'data-bts-max': Products.objects.values_list('Stocklevel', flat=True).filter(ID=product)}))


OrderItemFormSet = modelformset_factory(
    OrderItems,
    form=OrderItemsForm,
    #fields=('Item', 'Quantity', 'Price', 'OrderNo'),
    extra=2, widgets={'Quantity': forms.TextInput(
        attrs={'class': 'form-control', 'autofocus': True, 'data-toggle': 'touchspin', 'value': 1,
               'style': 'max-width: 50px;'}), 'Item': forms.Select(attrs={'class': 'form-control select2 mb-3', 'data-toggle': 'select2'}), 'Price': forms.TextInput(attrs={'class': 'form-control', 'autofocus': True, 'data-bts-postfix': 'kr.'})})

if I comment out the form= it works but I want use the querysets from OrderItemsForm where the filters will be used for the logged in user.

It’s created in your views.

You wrote:

More precisely, you might want to have something like:
ItemFormSetClass = modelformset_factory(OrderItems, form=OrderItemsForm)
because once you’ve created the class, you still need to do this to create the actual formset to be rendered:
item_formset = ItemFormSetClass(...)
It’s in this call that you have the option to pass the forms_kwargs parameter.

1 Like

Thank you now it is working :slight_smile:

Is there a trick that the extra formsets will be saved? I have Formset.save() and it gives no errors with print(Formset.errors)

views:

Itemform = modelformset_factory(OrderItems, form=OrderItemsForm, extra=ProductsCount)

    # if this is a POST request we need to process the form data
    if request.method == 'POST':
        Orderform = EditOrderForm(request.user.id, choosen_customer_int, request.POST)
        Formset = Itemform(request.POST)
        if Orderform.is_valid() and Formset.is_valid():
            edit_form = Orderform.save()
            Formset.save()
            edit_order_id = edit_form.pk
            request.session['edit_order_id'] = edit_order_id
            return HttpResponseRedirect(reverse('add_order_view'))

If the Formset forms need FK assignments, you need to iterate through the individual forms and assign the FK value for each form. (If I remember correctly, the inline formset handles that for you, but you’re using a standard modelformset, which leaves you responsible for doing it.)

See the docs and the second example in Saving objects in the formset.

I tried this:

formset = Formset.save(commit=False)
        formset.OrderNo = edit_order_id
        formset.save()

but didnt help :frowning: I was so good with my app and hanging two weeks on this module.

If the Formset forms need FK assignments, you need to iterate through the individual forms and assign the FK value for each form

See the docs and the second example in Saving objects in the formset .

Fixed everything thank you for your help