Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Fixed #13546 -- Easier handling of localize field options in ModelForm #1084

Merged
merged 1 commit into from

2 participants

@erikr
Collaborator

Added a localized_fields ModelForm Meta option which sets the localize
option on the field.

@apollo13 apollo13 merged commit 16683f2 into django:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
37 django/forms/models.py
@@ -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.
@@ -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
+
+ kwargs = {}
if widgets and f.name in widgets:
- kwargs = {'widget': widgets[f.name]}
- else:
- kwargs = {}
+ 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)
@@ -192,6 +194,7 @@ def __init__(self, options=None):
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):
@@ -215,7 +218,7 @@ def __new__(cls, name, bases, attrs):
# 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. "
@@ -242,8 +245,9 @@ def __new__(cls, name, bases, attrs):
# 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) - \
@@ -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.
@@ -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.
"""
@@ -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.
@@ -726,8 +734,8 @@ def pk_is_not_editable(pk):
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.
"""
@@ -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)
@@ -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.
@@ -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
View
16 docs/ref/forms/models.txt
@@ -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
@@ -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.
@@ -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``,
@@ -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
@@ -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.
View
4 docs/releases/1.6.txt
@@ -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
=====================================
View
36 docs/topics/forms/modelforms.txt
@@ -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
@@ -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
@@ -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
------------------------
View
35 tests/model_forms_regress/tests.py
@@ -92,6 +92,41 @@ def test_override_clean(self):
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.
View
7 tests/model_formsets_regress/tests.py
@@ -273,6 +273,7 @@ class Meta:
'id': CustomWidget,
'data': CustomWidget,
}
+ localized_fields = ('data',)
class Callback(object):
@@ -297,19 +298,23 @@ def test_inlineformset_factory_default(self):
form = Formset().forms[0]
self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
+ self.assertFalse(form.fields['id'].localize)
+ self.assertTrue(form.fields['data'].localize)
def test_modelformset_factory_default(self):
Formset = modelformset_factory(UserSite, form=UserSiteForm)
form = Formset().forms[0]
self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
+ self.assertFalse(form.fields['id'].localize)
+ self.assertTrue(form.fields['data'].localize)
def assertCallbackCalled(self, callback):
id_field, user_field, data_field = UserSite._meta.fields
expected_log = [
(id_field, {'widget': CustomWidget}),
(user_field, {}),
- (data_field, {'widget': CustomWidget}),
+ (data_field, {'widget': CustomWidget, 'localize': True}),
]
self.assertEqual(callback.log, expected_log)
Something went wrong with that request. Please try again.