diff --git a/CHANGES b/CHANGES index ed2ce9b..cc0cb14 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ 1.2.0 (not yet released) ------------------------ + - (Feature) Add partial template for rendering formsets [Scott Clark, #18] 1.1.0 (2014-08-04) ------------------ diff --git a/betterforms/templates/betterforms/formset_as_fieldsets.html b/betterforms/templates/betterforms/formset_as_fieldsets.html new file mode 100644 index 0000000..fd12f89 --- /dev/null +++ b/betterforms/templates/betterforms/formset_as_fieldsets.html @@ -0,0 +1,31 @@ +{% block form_head %} + {% if not no_head %} + {% if not csrf_exempt %} + {% csrf_token %} + {% endif %} + {% endif %} +{% endblock %} + +{% block form_body %} + {% with formset=form %} +
+ {{ formset.management_form }} + {{ formset.non_form_errors }} + {% for form in formset.forms %} +
+ {{ form.non_field_errors }} + {# Hack to allow recursive template inclusion #} + {% with fieldset_template_name="betterforms/fieldset_as_div.html" field_template_name="betterforms/field_as_div.html" %} + {% for thing in form %} + {% if thing.is_fieldset %} + {% include fieldset_template_name with fieldset=thing %} + {% else %} + {% include field_template_name with field=thing %} + {% endif %} + {% endfor %} + {% endwith %} +
+ {% endfor %} +
+ {% endwith %} +{% endblock %} diff --git a/docs/basics.rst b/docs/basics.rst index 545d8f9..b708dc4 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -177,3 +177,11 @@ convenient class attribute on the :class:`BetterForm` and #18134`_. .. _Django bug #18134: https://code.djangoproject.com/ticket/18134 + +Betterforms also provides a partial template for rendering formsets. +``betterforms/formset_as_fieldsets.html`` will render the management +form for the formset as well as wrap each individual form in a ``div`` +containing the class attributes of ``formSetForm`` and the unique +prefix value of each form. Form fields, including additional fields +for deleting and ordering the forms within the formset, will be +rendered using the ``betterforms/field_as_div.html`` template. diff --git a/tests/tests/forms.py b/tests/tests/forms.py index ad669d8..1853ad2 100644 --- a/tests/tests/forms.py +++ b/tests/tests/forms.py @@ -4,6 +4,8 @@ from django.utils.datastructures import SortedDict as OrderedDict # NOQA from django import forms +from django.forms.formsets import formset_factory +from django.forms.models import modelformset_factory from django.contrib.admin import widgets as admin_widgets from django.core.exceptions import ValidationError @@ -66,10 +68,16 @@ class NeedsFileField(MultiForm): class BadgeForm(forms.ModelForm): + class Meta: model = Badge fields = ('name', 'color',) + # self.label_suffix has to be declared with form instantiation per Django 1.6 + def __init__(self, *args, **kwargs): + super(BadgeForm, self).__init__(*args, **kwargs) + self.label_suffix = '' + class BadgeMultiForm(MultiModelForm): form_classes = { @@ -77,6 +85,17 @@ class BadgeMultiForm(MultiModelForm): 'badge2': BadgeForm, } +BadgeFormSet = formset_factory(BadgeForm, extra=2) + +BadgeDeleteFormSet = formset_factory(BadgeForm, can_delete=True, extra=2) + +BadgeOrderFormSet = formset_factory(BadgeForm, can_order=True, extra=2) + +try: + BadgeModelFormSet = modelformset_factory(Badge, form=BadgeForm, extra=2) +except PendingDeprecationWarning: + BadgeModelFormSet = modelformset_factory(Badge, form=BadgeForm, fields='__all__', extra=2) + class NonModelForm(forms.Form): field1 = forms.CharField() diff --git a/tests/tests/tests.py b/tests/tests/tests.py index 4d4f2f3..eca2ab3 100644 --- a/tests/tests/tests.py +++ b/tests/tests/tests.py @@ -1,11 +1,18 @@ +try: + from unittest import skipIf, skipUnless +except ImportError: # Python 2.6, Django < 1.7 + from django.utils.unittest import skipIf, skipUnless + try: from collections import OrderedDict except ImportError: # Python 2.6, Django < 1.7 from django.utils.datastructures import SortedDict as OrderedDict # NOQA +import django from django.test import TestCase from django.test.client import RequestFactory from django.views.generic import CreateView +from django.template.loader import render_to_string try: from django.utils.encoding import force_text @@ -16,6 +23,8 @@ from .forms import ( UserProfileMultiForm, BadgeMultiForm, ErrorMultiForm, MixedForm, NeedsFileField, ManyToManyMultiForm, + BadgeFormSet, BadgeDeleteFormSet, BadgeOrderFormSet, + BadgeModelFormSet, ) @@ -244,3 +253,219 @@ def test_works_with_create_view_post(self): resp = viewfn(request) self.assertEqual(resp.status_code, 302) self.assertEqual(Badge.objects.count(), 2) + + +class FormSetRenderTest(TestCase): + def setUp(self): + if django.VERSION < (1, 7, 0): + self.rendered_management_form = """ + + """ + else: + self.rendered_management_form = """ + + """ + + def test_formset_rendering(self): + self.formset = BadgeFormSet(prefix="badge") + env = { + 'form': self.formset, + 'no_head': True, + } + self.assertHTMLEqual( + render_to_string('betterforms/formset_as_fieldsets.html', env), + """ +
+ {0} +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ """.format(self.rendered_management_form) + ) + + def test_formset_can_delete_rendering(self): + self.formset = BadgeDeleteFormSet(prefix="badge") + env = { + 'form': self.formset, + 'no_head': True, + } + self.assertHTMLEqual( + render_to_string('betterforms/formset_as_fieldsets.html', env), + """ +
+ {0} +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ """.format(self.rendered_management_form) + ) + + @skipIf(django.VERSION < (1, 6, 0), "Order field changed type from text to number in Django 1.6") + def test_formset_can_order_rendering_post_16(self): + self.formset = BadgeOrderFormSet(prefix="badge") + env = { + 'form': self.formset, + 'no_head': True, + } + self.assertHTMLEqual( + render_to_string('betterforms/formset_as_fieldsets.html', env), + """ +
+ {0} +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ """.format(self.rendered_management_form) + ) + + @skipUnless(django.VERSION < (1, 6, 0), "Order field changed type from text to number in Django 1.6") + def test_formset_can_order_rendering_pre_16(self): + self.formset = BadgeOrderFormSet(prefix="badge") + env = { + 'form': self.formset, + 'no_head': True, + } + self.assertHTMLEqual( + render_to_string('betterforms/formset_as_fieldsets.html', env), + """ +
+ {0} +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ """.format(self.rendered_management_form) + ) + + def test_modelformset_rendering(self): + self.formset = BadgeModelFormSet(prefix="badge") + env = { + 'form': self.formset, + 'no_head': True, + } + self.maxDiff = None + self.assertHTMLEqual( + render_to_string('betterforms/formset_as_fieldsets.html', env), + """ +
+ {0} +
+
+ + +
+
+ + +
+ +
+
+
+ + +
+
+ + +
+ +
+
+ """.format(self.rendered_management_form) + )