Commit ef79582e authored by Claude Paroz's avatar Claude Paroz
Browse files

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.
parent 9be93aa8
Loading
Loading
Loading
Loading
+8 −7
Original line number Diff line number Diff line
@@ -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):
        """
+2 −1
Original line number Diff line number Diff line
@@ -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 = {
+18 −1
Original line number Diff line number Diff line
@@ -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()