Loading django/forms/models.py +23 −14 Original line number Diff line number Diff line Loading @@ -136,7 +136,7 @@ def model_to_dict(instance, fields=None, exclude=None): data[f.name] = f.value_from_object(instance) return data def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None): def fields_for_model(model, fields=None, exclude=None, widgets=None, localized_fields=None, formfield_callback=None): """ Returns a ``SortedDict`` containing form fields for the given model. Loading @@ -162,10 +162,12 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c continue if exclude and f.name in exclude: continue if widgets and f.name in widgets: kwargs = {'widget': widgets[f.name]} else: kwargs = {} if widgets and f.name in widgets: kwargs['widget'] = widgets[f.name] if localized_fields == ALL_FIELDS or (localized_fields and f.name in localized_fields): kwargs['localize'] = True if formfield_callback is None: formfield = f.formfield(**kwargs) Loading @@ -192,6 +194,7 @@ class ModelFormOptions(object): self.fields = getattr(options, 'fields', None) self.exclude = getattr(options, 'exclude', None) self.widgets = getattr(options, 'widgets', None) self.localized_fields = getattr(options, 'localized_fields', None) class ModelFormMetaclass(type): Loading @@ -215,7 +218,7 @@ class ModelFormMetaclass(type): # We check if a string was passed to `fields` or `exclude`, # which is likely to be a mistake where the user typed ('foo') instead # of ('foo',) for opt in ['fields', 'exclude']: for opt in ['fields', 'exclude', 'localized_fields']: value = getattr(opts, opt) if isinstance(value, six.string_types) and value != ALL_FIELDS: msg = ("%(model)s.Meta.%(opt)s cannot be a string. " Loading @@ -242,8 +245,9 @@ class ModelFormMetaclass(type): # fields from the model" opts.fields = None fields = fields_for_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback) fields = fields_for_model(opts.model, opts.fields, opts.exclude, opts.widgets, opts.localized_fields, formfield_callback) # make sure opts.fields doesn't specify an invalid field none_model_fields = [k for k, v in six.iteritems(fields) if not v] missing_fields = set(none_model_fields) - \ Loading Loading @@ -409,7 +413,7 @@ class ModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)): pass def modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None): localized_fields=None, widgets=None, formfield_callback=None): """ Returns a ModelForm containing form fields for the given model. Loading @@ -423,6 +427,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None, ``widgets`` is a dictionary of model field names mapped to a widget. ``localized_fields`` is a list of names of fields which should be localized. ``formfield_callback`` is a callable that takes a model field and returns a form field. """ Loading @@ -438,6 +444,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None, attrs['exclude'] = exclude if widgets is not None: attrs['widgets'] = widgets if localized_fields is not None: attrs['localized_fields'] = localized_fields # If parent form class already has an inner Meta, the Meta we're # creating needs to inherit from the parent's inner meta. Loading Loading @@ -726,8 +734,8 @@ class BaseModelFormSet(BaseFormSet): def 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): can_order=False, max_num=None, fields=None, exclude=None, widgets=None, validate_max=False, localized_fields=None): """ Returns a FormSet class for the given Django model class. """ Loading @@ -748,7 +756,7 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None, form = modelform_factory(model, form=form, fields=fields, exclude=exclude, formfield_callback=formfield_callback, widgets=widgets) widgets=widgets, localized_fields=localized_fields) FormSet = formset_factory(form, formset, extra=extra, max_num=max_num, can_order=can_order, can_delete=can_delete, validate_max=validate_max) Loading Loading @@ -885,9 +893,9 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False): def 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): 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): """ Returns an ``InlineFormSet`` for the given kwargs. Loading @@ -910,6 +918,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm, 'max_num': max_num, 'widgets': widgets, 'validate_max': validate_max, 'localized_fields': localized_fields, } FormSet = modelformset_factory(model, **kwargs) FormSet.fk = fk Loading docs/ref/forms/models.txt +10 −6 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ Model Form Functions .. module:: django.forms.models :synopsis: Django's functions for building model forms and formsets. .. function:: modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None) .. function:: modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None, localized_fields=None) Returns a :class:`~django.forms.ModelForm` class for the given ``model``. You can optionally pass a ``form`` argument to use as a starting point for Loading @@ -20,6 +20,8 @@ Model Form Functions ``widgets`` is a dictionary of model field names mapped to a widget. ``localized_fields`` is a list of names of fields which should be localized. ``formfield_callback`` is a callable that takes a model field and returns a form field. Loading @@ -33,12 +35,14 @@ Model Form Functions information. Omitting any definition of the fields to use will result in all fields being used, but this behaviour is deprecated. .. 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) The ``localized_fields`` parameter was added. .. 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) Returns a ``FormSet`` class for the given ``model`` class. Arguments ``model``, ``form``, ``fields``, ``exclude``, ``formfield_callback`` and ``widgets`` are all passed through to ``formfield_callback``, ``widgets`` and ``localized_fields`` are all passed through to :func:`~django.forms.models.modelform_factory`. Arguments ``formset``, ``extra``, ``max_num``, ``can_order``, Loading @@ -50,9 +54,9 @@ Model Form Functions .. versionchanged:: 1.6 The ``widgets`` and the ``validate_max`` parameters were added. The ``widgets``, ``validate_max`` and ``localized_fields`` parameters were added. .. 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) .. 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) Returns an ``InlineFormSet`` using :func:`modelformset_factory` with defaults of ``formset=BaseInlineFormSet``, ``can_delete=True``, and Loading @@ -65,4 +69,4 @@ Model Form Functions .. versionchanged:: 1.6 The ``widgets`` and the ``validate_max`` parameters were added. The ``widgets``, ``validate_max`` and ``localized_fields`` parameters were added. docs/releases/1.6.txt +4 −0 Original line number Diff line number Diff line Loading @@ -234,6 +234,10 @@ Minor features .. _`Pillow`: https://pypi.python.org/pypi/Pillow .. _`PIL`: https://pypi.python.org/pypi/PIL * :doc:`ModelForm </topics/forms/modelforms/>` accepts a new Meta option: ``localized_fields``. Fields included in this list will be localized (by setting ``localize`` on the form field). Backwards incompatible changes in 1.6 ===================================== Loading docs/topics/forms/modelforms.txt +36 −0 Original line number Diff line number Diff line Loading @@ -474,6 +474,24 @@ parameter when declaring the form field:: See the :doc:`form field documentation </ref/forms/fields>` for more information on fields and their arguments. Enabling localization of fields ------------------------------- .. versionadded:: 1.6 By default, the fields in a ``ModelForm`` will not localize their data. To enable localization for fields, you can use the ``localized_fields`` attribute on the ``Meta`` class. >>> class AuthorForm(ModelForm): ... class Meta: ... model = Author ... localized_fields = ('birth_date',) If ``localized_fields`` is set to the special value ``'__all__'``, all fields will be localized. .. _overriding-modelform-clean-method: Overriding the clean() method Loading Loading @@ -570,6 +588,10 @@ keyword arguments, or the corresponding attributes on the ``ModelForm`` inner ``Meta`` class. Please see the ``ModelForm`` :ref:`modelforms-selecting-fields` documentation. ... or enable localization for specific fields:: >>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",)) .. _model-formsets: Model formsets Loading Loading @@ -663,6 +685,20 @@ class of a ``ModelForm`` works:: >>> AuthorFormSet = modelformset_factory( ... Author, widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20}) Enabling localization for fields with ``localized_fields`` ---------------------------------------------------------- .. versionadded:: 1.6 Using the ``localized_fields`` parameter, you can enable localization for fields in the form. >>> AuthorFormSet = modelformset_factory( ... Author, localized_fields=('value',)) If ``localized_fields`` is set to the special value ``'__all__'``, all fields will be localized. Providing initial values ------------------------ Loading tests/model_forms_regress/tests.py +35 −0 Original line number Diff line number Diff line Loading @@ -92,6 +92,41 @@ class OverrideCleanTests(TestCase): self.assertEqual(form.instance.left, 1) class PartiallyLocalizedTripleForm(forms.ModelForm): class Meta: model = Triple localized_fields = ('left', 'right',) class FullyLocalizedTripleForm(forms.ModelForm): class Meta: model = Triple localized_fields = "__all__" class LocalizedModelFormTest(TestCase): def test_model_form_applies_localize_to_some_fields(self): f = PartiallyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10}) self.assertTrue(f.is_valid()) self.assertTrue(f.fields['left'].localize) self.assertFalse(f.fields['middle'].localize) self.assertTrue(f.fields['right'].localize) def test_model_form_applies_localize_to_all_fields(self): f = FullyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10}) self.assertTrue(f.is_valid()) self.assertTrue(f.fields['left'].localize) self.assertTrue(f.fields['middle'].localize) self.assertTrue(f.fields['right'].localize) def test_model_form_refuses_arbitrary_string(self): with self.assertRaises(TypeError): class BrokenLocalizedTripleForm(forms.ModelForm): class Meta: model = Triple localized_fields = "foo" # Regression test for #12960. # Make sure the cleaned_data returned from ModelForm.clean() is applied to the # model instance. Loading Loading
django/forms/models.py +23 −14 Original line number Diff line number Diff line Loading @@ -136,7 +136,7 @@ def model_to_dict(instance, fields=None, exclude=None): data[f.name] = f.value_from_object(instance) return data def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None): def fields_for_model(model, fields=None, exclude=None, widgets=None, localized_fields=None, formfield_callback=None): """ Returns a ``SortedDict`` containing form fields for the given model. Loading @@ -162,10 +162,12 @@ def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c continue if exclude and f.name in exclude: continue if widgets and f.name in widgets: kwargs = {'widget': widgets[f.name]} else: kwargs = {} if widgets and f.name in widgets: kwargs['widget'] = widgets[f.name] if localized_fields == ALL_FIELDS or (localized_fields and f.name in localized_fields): kwargs['localize'] = True if formfield_callback is None: formfield = f.formfield(**kwargs) Loading @@ -192,6 +194,7 @@ class ModelFormOptions(object): self.fields = getattr(options, 'fields', None) self.exclude = getattr(options, 'exclude', None) self.widgets = getattr(options, 'widgets', None) self.localized_fields = getattr(options, 'localized_fields', None) class ModelFormMetaclass(type): Loading @@ -215,7 +218,7 @@ class ModelFormMetaclass(type): # We check if a string was passed to `fields` or `exclude`, # which is likely to be a mistake where the user typed ('foo') instead # of ('foo',) for opt in ['fields', 'exclude']: for opt in ['fields', 'exclude', 'localized_fields']: value = getattr(opts, opt) if isinstance(value, six.string_types) and value != ALL_FIELDS: msg = ("%(model)s.Meta.%(opt)s cannot be a string. " Loading @@ -242,8 +245,9 @@ class ModelFormMetaclass(type): # fields from the model" opts.fields = None fields = fields_for_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback) fields = fields_for_model(opts.model, opts.fields, opts.exclude, opts.widgets, opts.localized_fields, formfield_callback) # make sure opts.fields doesn't specify an invalid field none_model_fields = [k for k, v in six.iteritems(fields) if not v] missing_fields = set(none_model_fields) - \ Loading Loading @@ -409,7 +413,7 @@ class ModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)): pass def modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None): localized_fields=None, widgets=None, formfield_callback=None): """ Returns a ModelForm containing form fields for the given model. Loading @@ -423,6 +427,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None, ``widgets`` is a dictionary of model field names mapped to a widget. ``localized_fields`` is a list of names of fields which should be localized. ``formfield_callback`` is a callable that takes a model field and returns a form field. """ Loading @@ -438,6 +444,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None, attrs['exclude'] = exclude if widgets is not None: attrs['widgets'] = widgets if localized_fields is not None: attrs['localized_fields'] = localized_fields # If parent form class already has an inner Meta, the Meta we're # creating needs to inherit from the parent's inner meta. Loading Loading @@ -726,8 +734,8 @@ class BaseModelFormSet(BaseFormSet): def 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): can_order=False, max_num=None, fields=None, exclude=None, widgets=None, validate_max=False, localized_fields=None): """ Returns a FormSet class for the given Django model class. """ Loading @@ -748,7 +756,7 @@ def modelformset_factory(model, form=ModelForm, formfield_callback=None, form = modelform_factory(model, form=form, fields=fields, exclude=exclude, formfield_callback=formfield_callback, widgets=widgets) widgets=widgets, localized_fields=localized_fields) FormSet = formset_factory(form, formset, extra=extra, max_num=max_num, can_order=can_order, can_delete=can_delete, validate_max=validate_max) Loading Loading @@ -885,9 +893,9 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False): def 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): 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): """ Returns an ``InlineFormSet`` for the given kwargs. Loading @@ -910,6 +918,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm, 'max_num': max_num, 'widgets': widgets, 'validate_max': validate_max, 'localized_fields': localized_fields, } FormSet = modelformset_factory(model, **kwargs) FormSet.fk = fk Loading
docs/ref/forms/models.txt +10 −6 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ Model Form Functions .. module:: django.forms.models :synopsis: Django's functions for building model forms and formsets. .. function:: modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None) .. function:: modelform_factory(model, form=ModelForm, fields=None, exclude=None, formfield_callback=None, widgets=None, localized_fields=None) Returns a :class:`~django.forms.ModelForm` class for the given ``model``. You can optionally pass a ``form`` argument to use as a starting point for Loading @@ -20,6 +20,8 @@ Model Form Functions ``widgets`` is a dictionary of model field names mapped to a widget. ``localized_fields`` is a list of names of fields which should be localized. ``formfield_callback`` is a callable that takes a model field and returns a form field. Loading @@ -33,12 +35,14 @@ Model Form Functions information. Omitting any definition of the fields to use will result in all fields being used, but this behaviour is deprecated. .. 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) The ``localized_fields`` parameter was added. .. 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) Returns a ``FormSet`` class for the given ``model`` class. Arguments ``model``, ``form``, ``fields``, ``exclude``, ``formfield_callback`` and ``widgets`` are all passed through to ``formfield_callback``, ``widgets`` and ``localized_fields`` are all passed through to :func:`~django.forms.models.modelform_factory`. Arguments ``formset``, ``extra``, ``max_num``, ``can_order``, Loading @@ -50,9 +54,9 @@ Model Form Functions .. versionchanged:: 1.6 The ``widgets`` and the ``validate_max`` parameters were added. The ``widgets``, ``validate_max`` and ``localized_fields`` parameters were added. .. 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) .. 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) Returns an ``InlineFormSet`` using :func:`modelformset_factory` with defaults of ``formset=BaseInlineFormSet``, ``can_delete=True``, and Loading @@ -65,4 +69,4 @@ Model Form Functions .. versionchanged:: 1.6 The ``widgets`` and the ``validate_max`` parameters were added. The ``widgets``, ``validate_max`` and ``localized_fields`` parameters were added.
docs/releases/1.6.txt +4 −0 Original line number Diff line number Diff line Loading @@ -234,6 +234,10 @@ Minor features .. _`Pillow`: https://pypi.python.org/pypi/Pillow .. _`PIL`: https://pypi.python.org/pypi/PIL * :doc:`ModelForm </topics/forms/modelforms/>` accepts a new Meta option: ``localized_fields``. Fields included in this list will be localized (by setting ``localize`` on the form field). Backwards incompatible changes in 1.6 ===================================== Loading
docs/topics/forms/modelforms.txt +36 −0 Original line number Diff line number Diff line Loading @@ -474,6 +474,24 @@ parameter when declaring the form field:: See the :doc:`form field documentation </ref/forms/fields>` for more information on fields and their arguments. Enabling localization of fields ------------------------------- .. versionadded:: 1.6 By default, the fields in a ``ModelForm`` will not localize their data. To enable localization for fields, you can use the ``localized_fields`` attribute on the ``Meta`` class. >>> class AuthorForm(ModelForm): ... class Meta: ... model = Author ... localized_fields = ('birth_date',) If ``localized_fields`` is set to the special value ``'__all__'``, all fields will be localized. .. _overriding-modelform-clean-method: Overriding the clean() method Loading Loading @@ -570,6 +588,10 @@ keyword arguments, or the corresponding attributes on the ``ModelForm`` inner ``Meta`` class. Please see the ``ModelForm`` :ref:`modelforms-selecting-fields` documentation. ... or enable localization for specific fields:: >>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",)) .. _model-formsets: Model formsets Loading Loading @@ -663,6 +685,20 @@ class of a ``ModelForm`` works:: >>> AuthorFormSet = modelformset_factory( ... Author, widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20}) Enabling localization for fields with ``localized_fields`` ---------------------------------------------------------- .. versionadded:: 1.6 Using the ``localized_fields`` parameter, you can enable localization for fields in the form. >>> AuthorFormSet = modelformset_factory( ... Author, localized_fields=('value',)) If ``localized_fields`` is set to the special value ``'__all__'``, all fields will be localized. Providing initial values ------------------------ Loading
tests/model_forms_regress/tests.py +35 −0 Original line number Diff line number Diff line Loading @@ -92,6 +92,41 @@ class OverrideCleanTests(TestCase): self.assertEqual(form.instance.left, 1) class PartiallyLocalizedTripleForm(forms.ModelForm): class Meta: model = Triple localized_fields = ('left', 'right',) class FullyLocalizedTripleForm(forms.ModelForm): class Meta: model = Triple localized_fields = "__all__" class LocalizedModelFormTest(TestCase): def test_model_form_applies_localize_to_some_fields(self): f = PartiallyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10}) self.assertTrue(f.is_valid()) self.assertTrue(f.fields['left'].localize) self.assertFalse(f.fields['middle'].localize) self.assertTrue(f.fields['right'].localize) def test_model_form_applies_localize_to_all_fields(self): f = FullyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10}) self.assertTrue(f.is_valid()) self.assertTrue(f.fields['left'].localize) self.assertTrue(f.fields['middle'].localize) self.assertTrue(f.fields['right'].localize) def test_model_form_refuses_arbitrary_string(self): with self.assertRaises(TypeError): class BrokenLocalizedTripleForm(forms.ModelForm): class Meta: model = Triple localized_fields = "foo" # Regression test for #12960. # Make sure the cleaned_data returned from ModelForm.clean() is applied to the # model instance. Loading