diff --git a/django/forms/formsets.py b/django/forms/formsets.py index fd4d53a44f..e279751601 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -99,6 +99,8 @@ class BaseFormSet(RenderableFormMixin): self.error_class = error_class self._errors = None self._non_form_errors = None + self.form_renderer = self.renderer + self.renderer = self.renderer or get_default_renderer() messages = {} for cls in reversed(type(self).__mro__): @@ -224,7 +226,7 @@ class BaseFormSet(RenderableFormMixin): # incorrect validation for extra, optional, and deleted # forms in the formset. "use_required_attribute": False, - "renderer": self.renderer, + "renderer": self.form_renderer, } if self.is_bound: defaults["data"] = self.data @@ -261,7 +263,7 @@ class BaseFormSet(RenderableFormMixin): "prefix": self.add_prefix("__prefix__"), "empty_permitted": True, "use_required_attribute": False, - "renderer": self.renderer, + "renderer": self.form_renderer, } form = self.form(**form_kwargs) self.add_fields(form, None) @@ -566,7 +568,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) diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py index ee0ae5a426..3c260010c2 100644 --- a/tests/forms_tests/tests/test_formsets.py +++ b/tests/forms_tests/tests/test_formsets.py @@ -23,7 +23,11 @@ from django.forms.formsets import ( all_valid, formset_factory, ) -from django.forms.renderers import TemplatesSetting +from django.forms.renderers import ( + DjangoTemplates, + TemplatesSetting, + get_default_renderer, +) from django.forms.utils import ErrorList from django.forms.widgets import HiddenInput from django.test import SimpleTestCase @@ -1553,6 +1557,60 @@ class FormsFormsetTestCase(SimpleTestCase): 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) + formset = ChoiceFormSet(data, prefix="choices") + self.assertEqual( + formset.forms[0].renderer, ChoiceWithDefaultRenderer.default_renderer + ) + self.assertEqual( + formset.empty_form.renderer, ChoiceWithDefaultRenderer.default_renderer + ) + default_renderer = get_default_renderer() + self.assertIsInstance(formset.renderer, type(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) + formset = ChoiceFormSet(data, prefix="choices") + self.assertIsInstance(formset.forms[0].renderer, CustomRenderer) + self.assertIsInstance(formset.empty_form.renderer, CustomRenderer) + default_renderer = get_default_renderer() + self.assertIsInstance(formset.renderer, type(default_renderer)) + def test_repr(self): valid_formset = self.make_choiceformset([("test", 1)]) valid_formset.full_clean() diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index 598dc57e7a..f78772da56 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -9,10 +9,12 @@ from django.db import models 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 @@ -2365,3 +2367,44 @@ class TestModelFormsetOverridesTroughFormMeta(TestCase): 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 + ) + self.assertEqual( + formset.empty_form.renderer, ModelFormWithDefaultRenderer.default_renderer + ) + self.assertIsInstance(formset.renderer, DjangoTemplates) + + 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.forms[0].renderer, ModelFormWithDefaultRenderer.default_renderer + ) + self.assertEqual( + formset.empty_form.renderer, ModelFormWithDefaultRenderer.default_renderer + ) + self.assertIsInstance(formset.renderer, DjangoTemplates)