Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #1084 from erikr/master

Fixed #13546 -- Easier handling of localize field options in ModelForm
  • Loading branch information...
commit 16683f29ea81e1e979ee88c79cbef279046dd8b1 2 parents ef73a8e + 756b81d
Florian Apolloner authored May 18, 2013
37  django/forms/models.py
@@ -136,7 +136,7 @@ def model_to_dict(instance, fields=None, exclude=None):
136 136
             data[f.name] = f.value_from_object(instance)
137 137
     return data
138 138
 
139  
-def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None):
  139
+def fields_for_model(model, fields=None, exclude=None, widgets=None, localized_fields=None, formfield_callback=None):
140 140
     """
141 141
     Returns a ``SortedDict`` containing form fields for the given model.
142 142
 
@@ -162,10 +162,12 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c
162 162
             continue
163 163
         if exclude and f.name in exclude:
164 164
             continue
  165
+
  166
+        kwargs = {}
165 167
         if widgets and f.name in widgets:
166  
-            kwargs = {'widget': widgets[f.name]}
167  
-        else:
168  
-            kwargs = {}
  168
+            kwargs['widget'] = widgets[f.name]
  169
+        if localized_fields == ALL_FIELDS or (localized_fields and f.name in localized_fields):
  170
+            kwargs['localize'] = True
169 171
 
170 172
         if formfield_callback is None:
171 173
             formfield = f.formfield(**kwargs)
@@ -192,6 +194,7 @@ def __init__(self, options=None):
192 194
         self.fields = getattr(options, 'fields', None)
193 195
         self.exclude = getattr(options, 'exclude', None)
194 196
         self.widgets = getattr(options, 'widgets', None)
  197
+        self.localized_fields = getattr(options, 'localized_fields', None)
195 198
 
196 199
 
197 200
 class ModelFormMetaclass(type):
@@ -215,7 +218,7 @@ def __new__(cls, name, bases, attrs):
215 218
         # We check if a string was passed to `fields` or `exclude`,
216 219
         # which is likely to be a mistake where the user typed ('foo') instead
217 220
         # of ('foo',)
218  
-        for opt in ['fields', 'exclude']:
  221
+        for opt in ['fields', 'exclude', 'localized_fields']:
219 222
             value = getattr(opts, opt)
220 223
             if isinstance(value, six.string_types) and value != ALL_FIELDS:
221 224
                 msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
@@ -242,8 +245,9 @@ def __new__(cls, name, bases, attrs):
242 245
                 # fields from the model"
243 246
                 opts.fields = None
244 247
 
245  
-            fields = fields_for_model(opts.model, opts.fields,
246  
-                                      opts.exclude, opts.widgets, formfield_callback)
  248
+            fields = fields_for_model(opts.model, opts.fields, opts.exclude,
  249
+                                      opts.widgets, opts.localized_fields, formfield_callback)
  250
+
247 251
             # make sure opts.fields doesn't specify an invalid field
248 252
             none_model_fields = [k for k, v in six.iteritems(fields) if not v]
249 253
             missing_fields = set(none_model_fields) - \
@@ -409,7 +413,7 @@ class ModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)):
409 413
     pass
410 414
 
411 415
 def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
412  
-                      formfield_callback=None,  widgets=None):
  416
+                      localized_fields=None, widgets=None, formfield_callback=None):
413 417
     """
414 418
     Returns a ModelForm containing form fields for the given model.
415 419
 
@@ -423,6 +427,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
423 427
 
424 428
     ``widgets`` is a dictionary of model field names mapped to a widget.
425 429
 
  430
+    ``localized_fields`` is a list of names of fields which should be localized.
  431
+
426 432
     ``formfield_callback`` is a callable that takes a model field and returns
427 433
     a form field.
428 434
     """
@@ -438,6 +444,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
438 444
         attrs['exclude'] = exclude
439 445
     if widgets is not None:
440 446
         attrs['widgets'] = widgets
  447
+    if localized_fields is not None:
  448
+        attrs['localized_fields'] = localized_fields
441 449
 
442 450
     # If parent form class already has an inner Meta, the Meta we're
443 451
     # creating needs to inherit from the parent's inner meta.
@@ -726,8 +734,8 @@ def pk_is_not_editable(pk):
726 734
 
727 735
 def modelformset_factory(model, form=ModelForm, formfield_callback=None,
728 736
                          formset=BaseModelFormSet, extra=1, can_delete=False,
729  
-                         can_order=False, max_num=None, fields=None,
730  
-                         exclude=None, widgets=None, validate_max=False):
  737
+                         can_order=False, max_num=None, fields=None, exclude=None,
  738
+                         widgets=None, validate_max=False, localized_fields=None):
731 739
     """
732 740
     Returns a FormSet class for the given Django model class.
733 741
     """
@@ -748,7 +756,7 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None,
748 756
 
749 757
     form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
750 758
                              formfield_callback=formfield_callback,
751  
-                             widgets=widgets)
  759
+                             widgets=widgets, localized_fields=localized_fields)
752 760
     FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
753 761
                               can_order=can_order, can_delete=can_delete,
754 762
                               validate_max=validate_max)
@@ -885,9 +893,9 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False):
885 893
 
886 894
 def inlineformset_factory(parent_model, model, form=ModelForm,
887 895
                           formset=BaseInlineFormSet, fk_name=None,
888  
-                          fields=None, exclude=None,
889  
-                          extra=3, can_order=False, can_delete=True, max_num=None,
890  
-                          formfield_callback=None, widgets=None, validate_max=False):
  896
+                          fields=None, exclude=None, extra=3, can_order=False,
  897
+                          can_delete=True, max_num=None, formfield_callback=None,
  898
+                          widgets=None, validate_max=False, localized_fields=None):
891 899
     """
892 900
     Returns an ``InlineFormSet`` for the given kwargs.
893 901
 
@@ -910,6 +918,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
910 918
         'max_num': max_num,
911 919
         'widgets': widgets,
912 920
         'validate_max': validate_max,
  921
+        'localized_fields': localized_fields,
913 922
     }
914 923
     FormSet = modelformset_factory(model, **kwargs)
915 924
     FormSet.fk = fk
16  docs/ref/forms/models.txt
@@ -5,7 +5,7 @@ Model Form Functions
5 5
 .. module:: django.forms.models
6 6
    :synopsis: Django's functions for building model forms and formsets.
7 7
 
8  
-.. function:: modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None,  widgets=None)
  8
+.. function:: modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None,  widgets=None, localized_fields=None)
9 9
 
10 10
     Returns a :class:`~django.forms.ModelForm` class for the given ``model``.
11 11
     You can optionally pass a ``form`` argument to use as a starting point for
@@ -20,6 +20,8 @@ Model Form Functions
20 20
 
21 21
     ``widgets`` is a dictionary of model field names mapped to a widget.
22 22
 
  23
+    ``localized_fields`` is a list of names of fields which should be localized.
  24
+
23 25
     ``formfield_callback`` is a callable that takes a model field and returns
24 26
     a form field.
25 27
 
@@ -33,12 +35,14 @@ Model Form Functions
33 35
     information. Omitting any definition of the fields to use will result in all
34 36
     fields being used, but this behaviour is deprecated.
35 37
 
36  
-.. function:: modelformset_factory(model, form=ModelForm, formfield_callback=None, formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, max_num=None, fields=None, exclude=None, widgets=None, validate_max=False)
  38
+    The ``localized_fields`` parameter was added.
  39
+
  40
+.. function:: modelformset_factory(model, form=ModelForm, formfield_callback=None, formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, max_num=None, fields=None, exclude=None, widgets=None, validate_max=False, localized_fields=None)
37 41
 
38 42
     Returns a ``FormSet`` class for the given ``model`` class.
39 43
 
40 44
     Arguments ``model``, ``form``, ``fields``, ``exclude``,
41  
-    ``formfield_callback`` and ``widgets`` are all passed through to
  45
+    ``formfield_callback``, ``widgets`` and ``localized_fields`` are all passed through to
42 46
     :func:`~django.forms.models.modelform_factory`.
43 47
 
44 48
     Arguments ``formset``, ``extra``, ``max_num``, ``can_order``,
@@ -50,9 +54,9 @@ Model Form Functions
50 54
 
51 55
     .. versionchanged:: 1.6
52 56
 
53  
-        The ``widgets`` and the ``validate_max`` parameters were added.
  57
+        The ``widgets``, ``validate_max`` and ``localized_fields`` parameters were added.
54 58
 
55  
-.. function:: inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False)
  59
+.. function:: inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False, localized_fields=None)
56 60
 
57 61
     Returns an ``InlineFormSet`` using :func:`modelformset_factory` with
58 62
     defaults of ``formset=BaseInlineFormSet``, ``can_delete=True``, and
@@ -65,4 +69,4 @@ Model Form Functions
65 69
 
66 70
     .. versionchanged:: 1.6
67 71
 
68  
-        The ``widgets`` and the ``validate_max`` parameters were added.
  72
+        The ``widgets``, ``validate_max`` and ``localized_fields`` parameters were added.
4  docs/releases/1.6.txt
@@ -234,6 +234,10 @@ Minor features
234 234
 .. _`Pillow`: https://pypi.python.org/pypi/Pillow
235 235
 .. _`PIL`: https://pypi.python.org/pypi/PIL
236 236
 
  237
+* :doc:`ModelForm </topics/forms/modelforms/>` accepts a new
  238
+  Meta option: ``localized_fields``. Fields included in this list will be localized
  239
+  (by setting ``localize`` on the form field).
  240
+
237 241
 Backwards incompatible changes in 1.6
238 242
 =====================================
239 243
 
36  docs/topics/forms/modelforms.txt
@@ -474,6 +474,24 @@ parameter when declaring the form field::
474 474
     See the :doc:`form field documentation </ref/forms/fields>` for more information
475 475
     on fields and their arguments.
476 476
 
  477
+
  478
+Enabling localization of fields
  479
+-------------------------------
  480
+
  481
+.. versionadded:: 1.6
  482
+
  483
+By default, the fields in a ``ModelForm`` will not localize their data. To
  484
+enable localization for fields, you can use the ``localized_fields``
  485
+attribute on the ``Meta`` class.
  486
+
  487
+    >>> class AuthorForm(ModelForm):
  488
+    ...     class Meta:
  489
+    ...         model = Author
  490
+    ...         localized_fields = ('birth_date',)
  491
+
  492
+If ``localized_fields`` is set to the special value ``'__all__'``, all fields
  493
+will be localized.
  494
+
477 495
 .. _overriding-modelform-clean-method:
478 496
 
479 497
 Overriding the clean() method
@@ -570,6 +588,10 @@ keyword arguments, or the corresponding attributes on the ``ModelForm`` inner
570 588
 ``Meta`` class. Please see the ``ModelForm`` :ref:`modelforms-selecting-fields`
571 589
 documentation.
572 590
 
  591
+... or enable localization for specific fields::
  592
+
  593
+    >>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))
  594
+
573 595
 .. _model-formsets:
574 596
 
575 597
 Model formsets
@@ -663,6 +685,20 @@ class of a ``ModelForm`` works::
663 685
     >>> AuthorFormSet = modelformset_factory(
664 686
     ...     Author, widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})
665 687
 
  688
+Enabling localization for fields with ``localized_fields``
  689
+----------------------------------------------------------
  690
+
  691
+.. versionadded:: 1.6
  692
+
  693
+Using the ``localized_fields`` parameter, you can enable localization for
  694
+fields in the form.
  695
+
  696
+    >>> AuthorFormSet = modelformset_factory(
  697
+    ...     Author, localized_fields=('value',))
  698
+
  699
+If ``localized_fields`` is set to the special value ``'__all__'``, all fields
  700
+will be localized.
  701
+
666 702
 Providing initial values
667 703
 ------------------------
668 704
 
35  tests/model_forms_regress/tests.py
@@ -92,6 +92,41 @@ def test_override_clean(self):
92 92
         self.assertEqual(form.instance.left, 1)
93 93
 
94 94
 
  95
+
  96
+class PartiallyLocalizedTripleForm(forms.ModelForm):
  97
+    class Meta:
  98
+        model = Triple
  99
+        localized_fields = ('left', 'right',)
  100
+
  101
+
  102
+class FullyLocalizedTripleForm(forms.ModelForm):
  103
+    class Meta:
  104
+        model = Triple
  105
+        localized_fields = "__all__"
  106
+
  107
+class LocalizedModelFormTest(TestCase):
  108
+    def test_model_form_applies_localize_to_some_fields(self):
  109
+        f = PartiallyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10})
  110
+        self.assertTrue(f.is_valid())
  111
+        self.assertTrue(f.fields['left'].localize)
  112
+        self.assertFalse(f.fields['middle'].localize)
  113
+        self.assertTrue(f.fields['right'].localize)
  114
+
  115
+    def test_model_form_applies_localize_to_all_fields(self):
  116
+        f = FullyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10})
  117
+        self.assertTrue(f.is_valid())
  118
+        self.assertTrue(f.fields['left'].localize)
  119
+        self.assertTrue(f.fields['middle'].localize)
  120
+        self.assertTrue(f.fields['right'].localize)
  121
+
  122
+    def test_model_form_refuses_arbitrary_string(self):
  123
+        with self.assertRaises(TypeError):
  124
+            class BrokenLocalizedTripleForm(forms.ModelForm):
  125
+                class Meta:
  126
+                    model = Triple
  127
+                    localized_fields = "foo"
  128
+
  129
+
95 130
 # Regression test for #12960.
96 131
 # Make sure the cleaned_data returned from ModelForm.clean() is applied to the
97 132
 # model instance.
7  tests/model_formsets_regress/tests.py
@@ -273,6 +273,7 @@ class Meta:
273 273
             'id': CustomWidget,
274 274
             'data': CustomWidget,
275 275
         }
  276
+        localized_fields = ('data',)
276 277
 
277 278
 
278 279
 class Callback(object):
@@ -297,19 +298,23 @@ def test_inlineformset_factory_default(self):
297 298
         form = Formset().forms[0]
298 299
         self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
299 300
         self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
  301
+        self.assertFalse(form.fields['id'].localize)
  302
+        self.assertTrue(form.fields['data'].localize)
300 303
 
301 304
     def test_modelformset_factory_default(self):
302 305
         Formset = modelformset_factory(UserSite, form=UserSiteForm)
303 306
         form = Formset().forms[0]
304 307
         self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
305 308
         self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
  309
+        self.assertFalse(form.fields['id'].localize)
  310
+        self.assertTrue(form.fields['data'].localize)
306 311
 
307 312
     def assertCallbackCalled(self, callback):
308 313
         id_field, user_field, data_field = UserSite._meta.fields
309 314
         expected_log = [
310 315
             (id_field, {'widget': CustomWidget}),
311 316
             (user_field, {}),
312  
-            (data_field, {'widget': CustomWidget}),
  317
+            (data_field, {'widget': CustomWidget, 'localize': True}),
313 318
         ]
314 319
         self.assertEqual(callback.log, expected_log)
315 320
 

0 notes on commit 16683f2

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