Skip to content
11 changes: 10 additions & 1 deletion django/forms/formsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,15 @@ def formset_factory(
absolute_max = max_num + DEFAULT_MAX_NUM
if max_num > absolute_max:
raise ValueError("'absolute_max' must be greater or equal to 'max_num'.")

if renderer is None:
if form.default_renderer is None:
renderer = get_default_renderer()
else:
renderer = form.default_renderer
if isinstance(form.default_renderer, type):
renderer = renderer()

attrs = {
"form": form,
"extra": extra,
Expand All @@ -566,7 +575,7 @@ def formset_factory(
"absolute_max": absolute_max,
"validate_min": validate_min,
"validate_max": validate_max,
"renderer": renderer or get_default_renderer(),
"renderer": renderer,
}
return type(form.__name__ + "FormSet", (formset,), attrs)

Expand Down
48 changes: 47 additions & 1 deletion tests/forms_tests/tests/test_formsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
all_valid,
formset_factory,
)
from django.forms.renderers import TemplatesSetting
from django.forms.renderers import DjangoTemplates, TemplatesSetting
from django.forms.utils import ErrorList
from django.forms.widgets import HiddenInput
from django.test import SimpleTestCase
Expand Down Expand Up @@ -1553,6 +1553,52 @@ def test_custom_renderer(self):
self.assertEqual(formset.non_form_errors().renderer, renderer)
self.assertEqual(formset.empty_form.renderer, renderer)

def test_form_default_renderer(self):
"""
In the absence of a renderer passed to the formset_factory(),
Form.default_renderer is respected.
"""

class CustomRenderer(DjangoTemplates):
pass

class ChoiceWithDefaultRenderer(Choice):
default_renderer = CustomRenderer()

data = {
"choices-TOTAL_FORMS": "1",
"choices-INITIAL_FORMS": "0",
"choices-MIN_NUM_FORMS": "0",
}

ChoiceFormSet = formset_factory(ChoiceWithDefaultRenderer, renderer=None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to pass renderer=None.

formset = ChoiceFormSet(data, prefix="choices")
self.assertEqual(
formset.forms[0].renderer, ChoiceWithDefaultRenderer.default_renderer
)

def test_form_default_renderer_class(self):
"""
In the absence of a renderer passed to the formset_factory(),
Form.default_renderer is respected.
"""

class CustomRenderer(DjangoTemplates):
pass

class ChoiceWithDefaultRenderer(Choice):
default_renderer = CustomRenderer

data = {
"choices-TOTAL_FORMS": "1",
"choices-INITIAL_FORMS": "0",
"choices-MIN_NUM_FORMS": "0",
}

ChoiceFormSet = formset_factory(ChoiceWithDefaultRenderer, renderer=None)
formset = ChoiceFormSet(data, prefix="choices")
self.assertIsInstance(formset.forms[0].renderer, CustomRenderer)

def test_repr(self):
valid_formset = self.make_choiceformset([("test", 1)])
valid_formset.full_clean()
Expand Down
35 changes: 35 additions & 0 deletions tests/model_formsets/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
from django.forms.formsets import formset_factory
from django.forms.models import (
BaseModelFormSet,
ModelForm,
_get_foreign_key,
inlineformset_factory,
modelformset_factory,
)
from django.forms.renderers import DjangoTemplates
from django.http import QueryDict
from django.test import TestCase, skipUnlessDBFeature

Expand Down Expand Up @@ -2365,3 +2367,36 @@ def test_modelformset_factory_passes_renderer(self):
BookFormSet = modelformset_factory(Author, fields="__all__", renderer=renderer)
formset = BookFormSet()
self.assertEqual(formset.renderer, renderer)

def test_modelformset_factory_default_renderer(self):
class CustomRenderer(DjangoTemplates):
pass

class ModelFormWithDefaultRenderer(ModelForm):
default_renderer = CustomRenderer()

BookFormSet = modelformset_factory(
Author, form=ModelFormWithDefaultRenderer, fields="__all__"
)
formset = BookFormSet()
self.assertEqual(
formset.forms[0].renderer, ModelFormWithDefaultRenderer.default_renderer
)

def test_inlineformset_factory_default_renderer(self):
class CustomRenderer(DjangoTemplates):
pass

class ModelFormWithDefaultRenderer(ModelForm):
default_renderer = CustomRenderer()

BookFormSet = inlineformset_factory(
Author,
Book,
form=ModelFormWithDefaultRenderer,
fields="__all__",
)
formset = BookFormSet()
self.assertEqual(
formset.renderer, ModelFormWithDefaultRenderer.default_renderer
)