1

I am trying to build a e-shop using django 2.0, but I have a 'logic' question not a syntax one, how to build an invoice that can accept many Items? In other words I have a model for the invoice item that is connected To a product model but I do not know how to build an invoice view that That gives the user the ability to add multiple items to this invoice How to do so knowing that I use CBV for the views? Thanks in advance

1 Answers1

2

What you are looking for are formsets. Basically you create a normal product form, and then apply formset_factory to it to create a formset, which is an itterable object. https://docs.djangoproject.com/en/2.0/topics/forms/formsets/

Also there are a few variations, such as the model formset which accepts queries to populate the forms. I'm not sure about your project but it's worth looking at these different versions.

Edit:

It's hard for me to guess exactly what you need since I can't see the application and its views as you have them in mind exactly. Below I have created a simple example that allows you to create a new invoice with 2 orderlines attached to it. If you want to edit existing invoices you will have to add an instance, which for forms initialize, e.g.

order_formset = OrderFormSet(request.POST, initial=...)

if you want to add more than 2 orderlines, you will have to copy the forms in your formset inside the template. This can be achieved with java / jquery, there are plenty examples out there on how to do this. I suggest looking around for one that fits your needs. By and large the jquery code will copy the first / last form and paste it into your template, clear all the values of the new copy, and update the id's of the inputs in the form. See e.g. Dynamically adding a form to a Django formset with Ajax

Example:

models.py

class Invoice(models.Model):
    ...

class Order(models.Model):
    ...
    ordered = models.ForeignKey('Invoice', related_name = 'orderline')

forms.py:

class InvoiceForm(ModelForm):
    class Meta:
        model = Invoice
        fields = ['field_names_go_here']

class OrderForm(ModelForm):
    class Meta:
        model = Order
        fields = ['field_names_go_here']

views.py

def create_order(request):

    if request.method == 'POST':

        invoice = InvoiceForm(request.POST)
        if invoice.is_valid():
            invoice.save()

        OrderFormSet = formset_factory(OrderForm)
        order_formset = OrderFormSet(request.POST)

        if order_formset.is_valid():

            new_orders = []
            for order_form in order_formset:
                new order = Order(
                    ...
                    ordered = invoice,
                )
                new_orders.append(order)

            Order.objects.bulk_create(new_orders)

        return ...

    if request.method == 'GET':

       invoice_form = InvoiceForm()

       OrderFormSet = formset_factory(OrderForm, extra=2)
       order_formset = OrderFormSet()

       template_context = {
           'invoice_form': invoice_form,
           'order_formset': order_formset,
           }

       template = 'template_name.html'

       return render(request, template, template_context)

template

<form enctype="multipart/form-data" action="{% url 'some_url_name' %}" method="post">
    {% csrf_token %}

        {{ order_formset.management_form }}

        {{ invoice_form }}

        {% for order_form in order_formset %}
             {{ order_form }}
        {% endfor %}

        <input type="submit" value="submit" />
</form>

Edit2: It occured to me that you might have preset items you can order. In which case all you need to do is set up a many to many relationship, generate a list of order objects, display them in your template and add a form/button for each one that adds them to some invoice. This can be achieved by adding a hidden input containing the pk of the Order object. Then you won't even need to bother with formsets.

DisneylandSC
  • 926
  • 1
  • 5
  • 19
  • Thanks for your response, I am building a solution for restaurant where the check or the invoice will have multiple items (sandwiches, drinks, etc) and I have a model for the meal and another model for the invoice item and I want to know how to build a view that gives the user the ability to add many items to the same invoice. The form-set is something I thought about but honestly I've never try it before but how to use it at the view? – AboJo TheGreat Jan 15 '18 at 19:06
  • See the edit. It shows the basic concept. Obviously to what extend this fits your needs depends on the types of views your application will have. It's function based because it is what I'm used to, but changing it to CBV should be p straight forward. Note that you will probably need some sort of java / jquery scripts to dynamicaly add extra forms to your formset so you can add orderlines. – DisneylandSC Jan 15 '18 at 21:21