Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[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
  • Loading branch information...
commit 43988e98357b6055749dadf5bf2f34dd243abc78 1 parent aec5cbc
Justin Bronn authored September 10, 2010
20  django/forms/models.py
@@ -150,7 +150,7 @@ def model_to_dict(instance, fields=None, exclude=None):
150 150
             data[f.name] = f.value_from_object(instance)
151 151
     return data
152 152
 
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):
154 154
     """
155 155
     Returns a ``SortedDict`` containing form fields for the given model.
156 156
 
@@ -175,7 +175,14 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c
175 175
             kwargs = {'widget': widgets[f.name]}
176 176
         else:
177 177
             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
+
179 186
         if formfield:
180 187
             field_list.append((f.name, formfield))
181 188
         else:
@@ -198,8 +205,7 @@ def __init__(self, options=None):
198 205
 
199 206
 class ModelFormMetaclass(type):
200 207
     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)
203 209
         try:
204 210
             parents = [b for b in bases if issubclass(b, ModelForm)]
205 211
         except NameError:
@@ -376,7 +382,7 @@ class ModelForm(BaseModelForm):
376 382
     __metaclass__ = ModelFormMetaclass
377 383
 
378 384
 def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
379  
-                       formfield_callback=lambda f: f.formfield()):
  385
+                       formfield_callback=None):
380 386
     # Create the inner Meta class. FIXME: ideally, we should be able to
381 387
     # construct a ModelForm without creating and passing in a temporary
382 388
     # inner class.
@@ -658,7 +664,7 @@ def pk_is_not_editable(pk):
658 664
             form.fields[self._pk_field.name] = ModelChoiceField(qs, initial=pk_value, required=False, widget=HiddenInput)
659 665
         super(BaseModelFormSet, self).add_fields(form, index)
660 666
 
661  
-def modelformset_factory(model, form=ModelForm, formfield_callback=lambda f: f.formfield(),
  667
+def modelformset_factory(model, form=ModelForm, formfield_callback=None,
662 668
                          formset=BaseModelFormSet,
663 669
                          extra=1, can_delete=False, can_order=False,
664 670
                          max_num=None, fields=None, exclude=None):
@@ -813,7 +819,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
813 819
                           formset=BaseInlineFormSet, fk_name=None,
814 820
                           fields=None, exclude=None,
815 821
                           extra=3, can_order=False, can_delete=True, max_num=None,
816  
-                          formfield_callback=lambda f: f.formfield()):
  822
+                          formfield_callback=None):
817 823
     """
818 824
     Returns an ``InlineFormSet`` for the given kwargs.
819 825
 
44  tests/regressiontests/model_forms_regress/tests.py
@@ -250,3 +250,47 @@ def test_http_prefixing(self):
250 250
         form.is_valid()
251 251
         # self.assertTrue(form.is_valid())
252 252
         # 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
+
62  tests/regressiontests/model_formsets_regress/tests.py
... ...
@@ -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
2 3
 from django.test import TestCase
3 4
 
4 5
 from models import User, UserSite, Restaurant, Manager
5 6
 
  7
+
6 8
 class InlineFormsetTests(TestCase):
7 9
     def test_formset_over_to_field(self):
8 10
         "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):
156 158
         # you can create a formset with an instance of None
157 159
         form = Form(instance=None)
158 160
         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 notes on commit 43988e9

Please sign in to comment.
Something went wrong with that request. Please try again.