Skip to content

Commit 43988e9

Browse files
committed
[1.2.X] Fixed #13095 -- formfield_callback keyword argument is now more sane and works with widgets defined in ModelForm.Meta.widgets. Thanks, hvdklauw for bug report, vung for initial patch, and carljm for review. Backport of r13730 from trunk.
git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@13731 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent aec5cbc commit 43988e9

File tree

3 files changed

+118
-8
lines changed

3 files changed

+118
-8
lines changed

django/forms/models.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def model_to_dict(instance, fields=None, exclude=None):
150150
data[f.name] = f.value_from_object(instance)
151151
return data
152152

153-
def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
153+
def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None):
154154
"""
155155
Returns a ``SortedDict`` containing form fields for the given model.
156156
@@ -175,7 +175,14 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c
175175
kwargs = {'widget': widgets[f.name]}
176176
else:
177177
kwargs = {}
178-
formfield = formfield_callback(f, **kwargs)
178+
179+
if formfield_callback is None:
180+
formfield = f.formfield(**kwargs)
181+
elif not callable(formfield_callback):
182+
raise TypeError('formfield_callback must be a function or callable')
183+
else:
184+
formfield = formfield_callback(f, **kwargs)
185+
179186
if formfield:
180187
field_list.append((f.name, formfield))
181188
else:
@@ -198,8 +205,7 @@ def __init__(self, options=None):
198205

199206
class ModelFormMetaclass(type):
200207
def __new__(cls, name, bases, attrs):
201-
formfield_callback = attrs.pop('formfield_callback',
202-
lambda f, **kwargs: f.formfield(**kwargs))
208+
formfield_callback = attrs.pop('formfield_callback', None)
203209
try:
204210
parents = [b for b in bases if issubclass(b, ModelForm)]
205211
except NameError:
@@ -376,7 +382,7 @@ class ModelForm(BaseModelForm):
376382
__metaclass__ = ModelFormMetaclass
377383

378384
def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
379-
formfield_callback=lambda f: f.formfield()):
385+
formfield_callback=None):
380386
# Create the inner Meta class. FIXME: ideally, we should be able to
381387
# construct a ModelForm without creating and passing in a temporary
382388
# inner class.
@@ -658,7 +664,7 @@ def pk_is_not_editable(pk):
658664
form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=HiddenInput)
659665
super(BaseModelFormSet, self).add_fields(form, index)
660666

661-
def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
667+
def modelformset_factory(model, form=ModelForm, formfield_callback=None,
662668
formset=BaseModelFormSet,
663669
extra=1, can_delete=False, can_order=False,
664670
max_num=None, fields=None, exclude=None):
@@ -813,7 +819,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
813819
formset=BaseInlineFormSet, fk_name=None,
814820
fields=None, exclude=None,
815821
extra=3, can_order=False, can_delete=True, max_num=None,
816-
formfield_callback=lambda f: f.formfield()):
822+
formfield_callback=None):
817823
"""
818824
Returns an ``InlineFormSet`` for the given kwargs.
819825

tests/regressiontests/model_forms_regress/tests.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,47 @@ def test_http_prefixing(self):
250250
form.is_valid()
251251
# self.assertTrue(form.is_valid())
252252
# self.assertEquals(form.cleaned_data['url'], 'http://example.com/test')
253+
254+
255+
class FormFieldCallbackTests(TestCase):
256+
257+
def test_baseform_with_widgets_in_meta(self):
258+
"""Regression for #13095: Using base forms with widgets defined in Meta should not raise errors."""
259+
widget = forms.Textarea()
260+
261+
class BaseForm(forms.ModelForm):
262+
class Meta:
263+
model = Person
264+
widgets = {'name': widget}
265+
266+
Form = modelform_factory(Person, form=BaseForm)
267+
self.assertTrue(Form.base_fields['name'].widget is widget)
268+
269+
def test_custom_callback(self):
270+
"""Test that a custom formfield_callback is used if provided"""
271+
272+
callback_args = []
273+
274+
def callback(db_field, **kwargs):
275+
callback_args.append((db_field, kwargs))
276+
return db_field.formfield(**kwargs)
277+
278+
widget = forms.Textarea()
279+
280+
class BaseForm(forms.ModelForm):
281+
class Meta:
282+
model = Person
283+
widgets = {'name': widget}
284+
285+
_ = modelform_factory(Person, form=BaseForm,
286+
formfield_callback=callback)
287+
id_field, name_field = Person._meta.fields
288+
289+
self.assertEqual(callback_args,
290+
[(id_field, {}), (name_field, {'widget': widget})])
291+
292+
def test_bad_callback(self):
293+
# A bad callback provided by user still gives an error
294+
self.assertRaises(TypeError, modelform_factory, Person,
295+
formfield_callback='not a function or callable')
296+

tests/regressiontests/model_formsets_regress/tests.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
from django.forms.models import modelform_factory, inlineformset_factory
1+
from django import forms
2+
from django.forms.models import modelform_factory, inlineformset_factory, modelformset_factory
23
from django.test import TestCase
34

45
from models import User, UserSite, Restaurant, Manager
56

7+
68
class InlineFormsetTests(TestCase):
79
def test_formset_over_to_field(self):
810
"A formset over a ForeignKey with a to_field can be saved. Regression for #10243"
@@ -156,3 +158,61 @@ def test_formset_with_none_instance(self):
156158
# you can create a formset with an instance of None
157159
form = Form(instance=None)
158160
formset = FormSet(instance=None)
161+
162+
163+
class CustomWidget(forms.CharField):
164+
pass
165+
166+
167+
class UserSiteForm(forms.ModelForm):
168+
class Meta:
169+
model = UserSite
170+
widgets = {'data': CustomWidget}
171+
172+
173+
class Callback(object):
174+
175+
def __init__(self):
176+
self.log = []
177+
178+
def __call__(self, db_field, **kwargs):
179+
self.log.append((db_field, kwargs))
180+
return db_field.formfield(**kwargs)
181+
182+
183+
class FormfieldCallbackTests(TestCase):
184+
"""
185+
Regression for #13095: Using base forms with widgets
186+
defined in Meta should not raise errors.
187+
"""
188+
189+
def test_inlineformset_factory_default(self):
190+
Formset = inlineformset_factory(User, UserSite, form=UserSiteForm)
191+
form = Formset({}).forms[0]
192+
self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
193+
194+
def test_modelformset_factory_default(self):
195+
Formset = modelformset_factory(UserSite, form=UserSiteForm)
196+
form = Formset({}).forms[0]
197+
self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
198+
199+
def assertCallbackCalled(self, callback):
200+
id_field, user_field, data_field = UserSite._meta.fields
201+
expected_log = [
202+
(id_field, {}),
203+
(user_field, {}),
204+
(data_field, {'widget': CustomWidget}),
205+
]
206+
self.assertEqual(callback.log, expected_log)
207+
208+
def test_inlineformset_custom_callback(self):
209+
callback = Callback()
210+
inlineformset_factory(User, UserSite, form=UserSiteForm,
211+
formfield_callback=callback)
212+
self.assertCallbackCalled(callback)
213+
214+
def test_modelformset_custom_callback(self):
215+
callback = Callback()
216+
modelformset_factory(UserSite, form=UserSiteForm,
217+
formfield_callback=callback)
218+
self.assertCallbackCalled(callback)

0 commit comments

Comments
 (0)