rcx935
rcx935

Reputation: 327

Django is validating formset that has no value selected for required field

I've just started playing with formsets. No idea what I'm doing at the moment. I've got to the point where a form with formset is being saved to the database. However, if nothing is selected for the required field "ledger" then validation still passes, and Django throws up "Key error".

My forms.py:

class JournalEntryForm(forms.Form):
    date = forms.DateField(widget=DateTypeInput())
    description = forms.CharField(required=False)

class LineItemForm(forms.Form):
    ledger = forms.ModelChoiceField(queryset=Ledger.objects.all())
    description = forms.CharField(required=False)
    project = forms.ModelChoiceField(queryset=Project.objects.all(), required=False)
    cr = forms.DecimalField(decimal_places=2, required=False)
    dr = forms.DecimalField(decimal_places=2, required=False)

My function in views.py. I've marked line 33 which is the line where the "key error" occurrs.

@login_required
def entries_new(request):

    # Takes form and returns form set. So we now have a form set.
    LineItemFormSet = formset_factory(LineItemForm, extra=2)

    if request.method == 'POST':
        journal_entry_form = JournalEntryForm(request.POST)
        lineitem_formset = LineItemFormSet(request.POST)

        if journal_entry_form.is_valid() and lineitem_formset.is_valid():
            q0 = JournalEntry(user=request.user, date=journal_entry_form.cleaned_data['date'], type="JE")
            q0.save()

            for lineitem_form in lineitem_formset:
                q1 = LineItem(
                    journal_entry=q0,
                    ledger=lineitem_form.cleaned_data['ledger'], #<---- This is line 33 referenced in the error
                    cr=lineitem_form.cleaned_data['cr'],
                    dr=lineitem_form.cleaned_data['dr'],
                    project=lineitem_form.cleaned_data['project'],
                    )
                q1.save()

            messages.success(request, "Journal entry successfully saved.")
            return HttpResponseRedirect(reverse('journal:entries_show_all') )
    else:
        journal_entry_form = JournalEntryForm()
        lineitem_formset = LineItemFormSet()

    context = { 'journal_entry_form': journal_entry_form, 'lineitem_formset': lineitem_formset, }

    return render(request, 'journal/entries_new.html', {'journal_entry_form': journal_entry_form, 'lineitem_formset': lineitem_formset})

The error I get in my browser:

KeyError at /journal/entries/new/
'ledger'
Request Method: POST
Request URL:    http://localhost/journal/entries/new/
Django Version: 3.0
Exception Type: KeyError
Exception Value:    
'ledger'
Exception Location: C:\Users\Philip\CodeRepos\Acacia2\Journal\views.py in entries_new, line 33
Python Executable:  C:\Users\Philip\CodeRepos\Acacia2\venv\Scripts\python.exe
Python Version: 3.8.0
Python Path:    
['C:\\Users\\Philip\\CodeRepos\\Acacia2',
 'C:\\Users\\Philip\\AppData\\Local\\Programs\\Python\\Python38-32\\python38.zip',
 'C:\\Users\\Philip\\AppData\\Local\\Programs\\Python\\Python38-32\\DLLs',
 'C:\\Users\\Philip\\AppData\\Local\\Programs\\Python\\Python38-32\\lib',
 'C:\\Users\\Philip\\AppData\\Local\\Programs\\Python\\Python38-32',
 'C:\\Users\\Philip\\CodeRepos\\Acacia2\\venv',
 'C:\\Users\\Philip\\CodeRepos\\Acacia2\\venv\\lib\\site-packages']
Server time:    Thu, 26 Dec 2019 20:42:45 +0000

Upvotes: 0

Views: 168

Answers (2)

rcx935
rcx935

Reputation: 327

So after some digging, it turns out that Django does not validate any empty formsets. I added the following init to my form and now I get a nice formerror if an empty formset is submitted:

class LineItemForm(forms.Form):
    ledger = forms.ModelChoiceField(queryset=Ledger.objects.all(),)
    description = forms.CharField(required=False)
    project = forms.ModelChoiceField(queryset=Project.objects.all(), required=False)
    cr = forms.DecimalField(decimal_places=2, required=False)
    dr = forms.DecimalField(decimal_places=2, required=False)
    # This init disallows empty formsets
    def __init__(self, *arg, **kwarg):
            super(LineItemForm, self).__init__(*arg, **kwarg)
            self.empty_permitted = False

I have no idea what the init does (arg, kwargs and super are all still a mystery to me). I copied it from another page. It works though.

Upvotes: 1

KrazyMax
KrazyMax

Reputation: 959

Source : Django documentation

Default value for ModelChoiceField is None, even if you specified the queryset attribute. If the form is valid, to me it means than None in Ledger.objects.all() is True! Are you sure you do have Ledger objects in your database?

Upvotes: 0

Related Questions