Opened 2 years ago

Closed 2 years ago

#33995 closed Bug (fixed)

Rendering empty_form crashes when empty_permitted is passed to form_kwargs

Reported by: claypooj21 Owned by: Bhuvnesh
Component: Forms Version: 4.1
Severity: Normal Keywords: formset, empty_form, empty_permitted, form
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: yes UI/UX: no

Description

Issue

When explicitly setting form_kwargs = {'empty_permitted':True} or form_kwargs = {'empty_permitted':False} , a KeyError occurs when rendering a template that uses a formset's empty_form.

Expected Behavior

empty_permitted is ignored for formset.empty_form since empty_permitted is irrelevant for empty_form, as empty_form is not meant to be used to pass data and therefore does not need to be validated.

Steps to Reproduce

# views.py
from django.shortcuts import render
from .models import MyModel

def test_view(request):
    context = {}
    ff = modelformset_factory(MyModel, fields = ['a_field'])
    context['formset'] = ff(
        queryset = MyModel.objects.none(),
        form_kwargs = {'empty_permitted':True} # or form_kwargs = {'empty_permitted':False}
    )
    return render(request, 'my_app/my_model_formset.html', context)

# urls.py
from django.urls import path, include
from .views import test_view

urlpatterns = [
    path('test', test_view)
]

# my_model_formset.html
{% extends "my_app/base.html" %}

{% block content %}
<form id="my-form" method="post">
  {% csrf_token %}
  {{ formset }}
  <input type="submit" value="Save">
</form>

{{ formset.empty_form }}

{% endblock %}

Change History (6)

comment:1 by Mariusz Felisiak, 2 years ago

Easy pickings: set
Summary: Key Error 'empty_permitted' when rendering 'formset.empty_form'Rendering empty_form crashes when empty_permitted is passed to form_kwargs
Triage Stage: UnreviewedAccepted

Thanks for the report. It should be enough to change form_kwargs for empty_form, e.g.

  • django/forms/formsets.py

    diff --git a/django/forms/formsets.py b/django/forms/formsets.py
    index 57676428ff..b73d1d742e 100644
    a b class BaseFormSet(RenderableFormMixin):  
    257257
    258258    @property
    259259    def empty_form(self):
    260         form = self.form(
    261             auto_id=self.auto_id,
    262             prefix=self.add_prefix("__prefix__"),
    263             empty_permitted=True,
    264             use_required_attribute=False,
     260        form_kwargs = {
    265261            **self.get_form_kwargs(None),
    266             renderer=self.renderer,
    267         )
     262            "auto_id": self.auto_id,
     263            "prefix": self.add_prefix("__prefix__"),
     264            "use_required_attribute": False,
     265            "empty_permitted": True,
     266            "renderer": self.renderer,
     267        }
     268        form = self.form(**form_kwargs)
    268269        self.add_fields(form, None)
    269270        return form
    270271

Would you like to prepare a patch? (a regression test is required)

comment:2 by Carlton Gibson, 2 years ago

The KeyError is confusing here. It's raised because we're in the context of rendering the template:

>>> self.form(empty_permitted=True, **self.get_form_kwargs(None))
Traceback (most recent call last):
  File "/Users/carlton/Projects/Django/django/django/template/base.py", line 880, in _resolve_lookup
    current = current[bit]
  File "/Users/carlton/Projects/Django/django/django/forms/formsets.py", line 118, in __getitem__
    return self.forms[index]
TypeError: list indices must be integers or slices, not str

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
KeyError: 'empty_permitted'

The real exception is better seen using the formset directly:

>>> from ticket_33995.models import MyModel
>>> from django.forms import modelformset_factory
>>> ff = modelformset_factory(MyModel, fields=['name'])
>>> formset = ff(queryset=MyModel.objects.none(), form_kwargs={'empty_permitted': True})
>>> formset.empty_form
> /Users/carlton/Projects/Django/django/django/forms/formsets.py(262)empty_form()
-> form_kwargs = self.get_form_kwargs(None)
(Pdb) c
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/carlton/Projects/Django/django/django/forms/formsets.py", line 265, in empty_form
    form = self.form(
TypeError: django.forms.widgets.MyModelForm() got multiple values for keyword argument 'empty_permitted'

That's expected:

>>> class Example:
...     def __init__(self, a_kwarg=None):
...         pass
... 
>>> Example(a_kwarg=True)
<__main__.Example object at 0x102352950>
>>> Example(a_kwarg=True, a_kwarg=False)
  File "<stdin>", line 1
SyntaxError: keyword argument repeated: a_kwarg
>>> {"a":1, **{"a":2}}
{'a': 2}
>>> Example(a_kwarg=True, **{"a_kwarg": False})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __main__.Example() got multiple values for keyword argument 'a_kwarg'

Resolving the kwargs before the constructor call, as per Mariusz' suggestion would resolve.

#21501 was on a similar topic.

comment:3 by Bhuvnesh, 2 years ago

Owner: changed from nobody to Bhuvnesh
Status: newassigned

comment:4 by Mariusz Felisiak, 2 years ago

Has patch: set
Patch needs improvement: set

comment:5 by Mariusz Felisiak, 2 years ago

Patch needs improvement: unset
Triage Stage: AcceptedReady for checkin

comment:6 by Mariusz Felisiak <felisiak.mariusz@…>, 2 years ago

Resolution: fixed
Status: assignedclosed

In f3cd252c:

Fixed #33995 -- Fixed FormSet.empty_form crash when empty_permitted is passed to form_kwargs.

Note: See TracTickets for help on using tickets.
Back to Top