1

I've got a situation where I'd like to add an additional modelform to my CreateView. We have an entry order system that allows someone to add an order and then add items to that order. Typically, when someone adds an order for the first time they'd like to also add an item to that order, so I want to combine those models into a single form and process them on initial order entry. I'm running into a problem when the forms don't validate.

I've overridden get_context_data to add the item form to the template and I've overridden post to process the extra form. But when the forms are invalid I need to re-render the original form passing in the POST data. What's the preferred way to override get_context_data in order to render the forms with/without the POST data? Should I do something like this?

def get_context_data(self, **kwargs):
    context = super(OrderAdd, self).get_context_data(**kwargs)
    if self.request.method == 'POST':
        item_form = ItemForm(self.request.POST, prefix='item')
    else:
        item_form = ItemForm(prefix='item')
    context['item_form'] = item_form
    return context

Here's my CreateView in it's current form, where I'm currently stuck.

class OrderAdd(CreateView):
    model = Order
    form_class = OrderForm
    context_object_name = 'object'
    template_name = 'form.html'

    def get_context_data(self, **kwargs):
        context = super(OrderAdd, self).get_context_data(**kwargs)
        item_form = ItemForm(prefix='item')
        context['item_form'] = item_form
        return context

    def post(self, request, *args, **kwargs):
        order_form = OrderForm(request.POST)
        item_form = ItemForm(request.POST, prefix='item')

        if order_form.is_valid() and item_form.is_valid():
            return self.form_valid(order_form)
        else:
            context = self.get_context_data()
            return render(self.request, 'form.html', context)
scoopseven
  • 1,767
  • 3
  • 18
  • 27
  • Your proposed `get_context_data` looks right to me, except that you have your if/else clauses reversed - you're trying to bind the item form only when the request was not a POST. Hmm. Except that then you create it twice and put an unvalidated copy into your context. I think I'd have `get_context_data` not insert the `item_form` value on POST requests, and instead add it myself after calling `get_context_data()`. – Peter DeGlopper May 07 '13 at 18:53
  • Thanks, Peter. That was a copy/pasting mistake which I have corrected. What do you mean by "instead add it myself after calling get_context_data()?" – scoopseven May 07 '13 at 19:03
  • Since context is just a dictionary, you can add to or read from it outside `get_context_data`; see the answer I added for what I first had in mind, and then what I think is a slightly more elegant approach instead. – Peter DeGlopper May 07 '13 at 19:13

1 Answers1

3

One slightly inelegant approach:

def get_context_data(self, **kwargs):
    context = super(OrderAdd, self).get_context_data(**kwargs)
    if self.request.method == 'POST':
        return context
    else:
        item_form = ItemForm(prefix='item')
        context['item_form'] = item_form
        return context

def post(self, request, *args, **kwargs):
    order_form = OrderForm(request.POST)
    item_form = ItemForm(request.POST, prefix='item')

    if order_form.is_valid() and item_form.is_valid():
        return self.form_valid(order_form)
    else:
        context = self.get_context_data()
        context['item_form'] = item_form
        return render(self.request, 'form.html', context)

Another alternative:

def get_context_data(self, **kwargs):
    context = super(OrderAdd, self).get_context_data(**kwargs)
    if self.request.method == 'POST':
        item_form = ItemForm(self.request.POST, prefix='item')
    else:
        item_form = ItemForm(prefix='item')
    context['item_form'] = item_form
    return context

def post(self, request, *args, **kwargs):
    order_form = OrderForm(request.POST)
    context = self.get_context_data()
    item_form = context['item_form']

    if order_form.is_valid() and item_form.is_valid():
        return self.form_valid(order_form)
    else:
        return render(self.request, 'form.html', context)
Peter DeGlopper
  • 36,326
  • 7
  • 90
  • 83
  • Used your "Another alternative" idea and it worked perfectly. Thanks. – scoopseven May 07 '13 at 22:03
  • why inelegant ? – John Doe Apr 27 '19 at 08:33
  • 2
    @JohnDoe - well, it's been almost 6 years since I wrote that. But looking at it again, I'd say I don't especially like modifying the context outside of `get_context_data` in a class-based view if it's avoidable. Or instantiating the new form in two different methods. The second approach is cleaner. – Peter DeGlopper Apr 27 '19 at 16:19