From ef79582e8630cb3c119caed52130c9671188addd Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 22 Jun 2013 09:25:14 +0200 Subject: [PATCH] Fixed 17478 -- Allowed queryset overriding in BaseModelFormSet init BaseModelFormSet.forms is now a cached property instead of being populated in the __init__ method. This behaviour also matches an example in the documentation. Thanks Thomasz Swiderski for the report and Simon Charette for the review. --- django/forms/formsets.py | 15 ++++++++------- tests/forms_tests/tests/test_formsets.py | 3 ++- tests/model_formsets/tests.py | 19 ++++++++++++++++++- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 9c2e88bee7..cb3126e6d7 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -6,6 +6,7 @@ from django.forms.fields import IntegerField, BooleanField from django.forms.util import ErrorList from django.forms.widgets import HiddenInput from django.utils.encoding import python_2_unicode_compatible +from django.utils.functional import cached_property from django.utils.safestring import mark_safe from django.utils import six from django.utils.six.moves import xrange @@ -55,8 +56,6 @@ class BaseFormSet(object): self.error_class = error_class self._errors = None self._non_form_errors = None - # construct the forms in the formset - self._construct_forms() def __str__(self): return self.as_table() @@ -125,12 +124,14 @@ class BaseFormSet(object): initial_forms = len(self.initial) if self.initial else 0 return initial_forms - def _construct_forms(self): - # instantiate all the forms and put them in self.forms - self.forms = [] + @cached_property + def forms(self): + """ + Instantiate forms at first property access. + """ # DoS protection is included in total_form_count() - for i in xrange(self.total_form_count()): - self.forms.append(self._construct_form(i)) + forms = [self._construct_form(i) for i in xrange(self.total_form_count())] + return forms def _construct_form(self, i, **kwargs): """ diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py index b26017bc78..41577e6049 100644 --- a/tests/forms_tests/tests/test_formsets.py +++ b/tests/forms_tests/tests/test_formsets.py @@ -1072,7 +1072,8 @@ ArticleFormSet = formset_factory(ArticleForm) class TestIsBoundBehavior(TestCase): def test_no_data_raises_validation_error(self): - self.assertRaises(ValidationError, ArticleFormSet, {}) + with self.assertRaises(ValidationError): + ArticleFormSet({}).is_valid() def test_with_management_data_attrs_work_fine(self): data = { diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py index 62870c7462..43509c471f 100644 --- a/tests/model_formsets/tests.py +++ b/tests/model_formsets/tests.py @@ -8,7 +8,7 @@ from decimal import Decimal from django import forms from django.db import models from django.forms.models import (_get_foreign_key, inlineformset_factory, - modelformset_factory) + modelformset_factory, BaseModelFormSet) from django.test import TestCase, skipUnlessDBFeature from django.utils import six @@ -386,6 +386,23 @@ class ModelFormsetTest(TestCase): formset = PostFormSet() self.assertFalse("subtitle" in formset.forms[0].fields) + def test_custom_queryset_init(self): + """ + Test that a queryset can be overriden in the __init__ method. + https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#changing-the-queryset + """ + author1 = Author.objects.create(name='Charles Baudelaire') + author2 = Author.objects.create(name='Paul Verlaine') + + class BaseAuthorFormSet(BaseModelFormSet): + def __init__(self, *args, **kwargs): + super(BaseAuthorFormSet, self).__init__(*args, **kwargs) + self.queryset = Author.objects.filter(name__startswith='Charles') + + AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet) + formset = AuthorFormSet() + self.assertEqual(len(formset.get_queryset()), 1) + def test_model_inheritance(self): BetterAuthorFormSet = modelformset_factory(BetterAuthor, fields="__all__") formset = BetterAuthorFormSet()