Fixed #13546 -- Easier handling of localize field options in ModelForm
This commit is contained in:
parent
be826aafac
commit
756b81dbd1
|
@ -136,7 +136,7 @@ def model_to_dict(instance, fields=None, exclude=None):
|
||||||
data[f.name] = f.value_from_object(instance)
|
data[f.name] = f.value_from_object(instance)
|
||||||
return data
|
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.
|
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
|
continue
|
||||||
if exclude and f.name in exclude:
|
if exclude and f.name in exclude:
|
||||||
continue
|
continue
|
||||||
if widgets and f.name in widgets:
|
|
||||||
kwargs = {'widget': widgets[f.name]}
|
|
||||||
else:
|
|
||||||
kwargs = {}
|
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:
|
if formfield_callback is None:
|
||||||
formfield = f.formfield(**kwargs)
|
formfield = f.formfield(**kwargs)
|
||||||
|
@ -192,6 +194,7 @@ class ModelFormOptions(object):
|
||||||
self.fields = getattr(options, 'fields', None)
|
self.fields = getattr(options, 'fields', None)
|
||||||
self.exclude = getattr(options, 'exclude', None)
|
self.exclude = getattr(options, 'exclude', None)
|
||||||
self.widgets = getattr(options, 'widgets', None)
|
self.widgets = getattr(options, 'widgets', None)
|
||||||
|
self.localized_fields = getattr(options, 'localized_fields', None)
|
||||||
|
|
||||||
|
|
||||||
class ModelFormMetaclass(type):
|
class ModelFormMetaclass(type):
|
||||||
|
@ -215,7 +218,7 @@ class ModelFormMetaclass(type):
|
||||||
# We check if a string was passed to `fields` or `exclude`,
|
# We check if a string was passed to `fields` or `exclude`,
|
||||||
# which is likely to be a mistake where the user typed ('foo') instead
|
# which is likely to be a mistake where the user typed ('foo') instead
|
||||||
# of ('foo',)
|
# of ('foo',)
|
||||||
for opt in ['fields', 'exclude']:
|
for opt in ['fields', 'exclude', 'localized_fields']:
|
||||||
value = getattr(opts, opt)
|
value = getattr(opts, opt)
|
||||||
if isinstance(value, six.string_types) and value != ALL_FIELDS:
|
if isinstance(value, six.string_types) and value != ALL_FIELDS:
|
||||||
msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
|
msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
|
||||||
|
@ -242,8 +245,9 @@ class ModelFormMetaclass(type):
|
||||||
# fields from the model"
|
# fields from the model"
|
||||||
opts.fields = None
|
opts.fields = None
|
||||||
|
|
||||||
fields = fields_for_model(opts.model, opts.fields,
|
fields = fields_for_model(opts.model, opts.fields, opts.exclude,
|
||||||
opts.exclude, opts.widgets, formfield_callback)
|
opts.widgets, opts.localized_fields, formfield_callback)
|
||||||
|
|
||||||
# make sure opts.fields doesn't specify an invalid field
|
# make sure opts.fields doesn't specify an invalid field
|
||||||
none_model_fields = [k for k, v in six.iteritems(fields) if not v]
|
none_model_fields = [k for k, v in six.iteritems(fields) if not v]
|
||||||
missing_fields = set(none_model_fields) - \
|
missing_fields = set(none_model_fields) - \
|
||||||
|
@ -409,7 +413,7 @@ class ModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
|
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.
|
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.
|
``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
|
``formfield_callback`` is a callable that takes a model field and returns
|
||||||
a form field.
|
a form field.
|
||||||
"""
|
"""
|
||||||
|
@ -438,6 +444,8 @@ def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
|
||||||
attrs['exclude'] = exclude
|
attrs['exclude'] = exclude
|
||||||
if widgets is not None:
|
if widgets is not None:
|
||||||
attrs['widgets'] = widgets
|
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
|
# If parent form class already has an inner Meta, the Meta we're
|
||||||
# creating needs to inherit from the parent's inner meta.
|
# creating needs to inherit from the parent's inner meta.
|
||||||
|
@ -726,8 +734,8 @@ class BaseModelFormSet(BaseFormSet):
|
||||||
|
|
||||||
def modelformset_factory(model, form=ModelForm, formfield_callback=None,
|
def modelformset_factory(model, form=ModelForm, formfield_callback=None,
|
||||||
formset=BaseModelFormSet, extra=1, can_delete=False,
|
formset=BaseModelFormSet, extra=1, can_delete=False,
|
||||||
can_order=False, max_num=None, fields=None,
|
can_order=False, max_num=None, fields=None, exclude=None,
|
||||||
exclude=None, widgets=None, validate_max=False):
|
widgets=None, validate_max=False, localized_fields=None):
|
||||||
"""
|
"""
|
||||||
Returns a FormSet class for the given Django model class.
|
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,
|
form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
|
||||||
formfield_callback=formfield_callback,
|
formfield_callback=formfield_callback,
|
||||||
widgets=widgets)
|
widgets=widgets, localized_fields=localized_fields)
|
||||||
FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
|
FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
|
||||||
can_order=can_order, can_delete=can_delete,
|
can_order=can_order, can_delete=can_delete,
|
||||||
validate_max=validate_max)
|
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,
|
def inlineformset_factory(parent_model, model, form=ModelForm,
|
||||||
formset=BaseInlineFormSet, fk_name=None,
|
formset=BaseInlineFormSet, fk_name=None,
|
||||||
fields=None, exclude=None,
|
fields=None, exclude=None, extra=3, can_order=False,
|
||||||
extra=3, can_order=False, can_delete=True, max_num=None,
|
can_delete=True, max_num=None, formfield_callback=None,
|
||||||
formfield_callback=None, widgets=None, validate_max=False):
|
widgets=None, validate_max=False, localized_fields=None):
|
||||||
"""
|
"""
|
||||||
Returns an ``InlineFormSet`` for the given kwargs.
|
Returns an ``InlineFormSet`` for the given kwargs.
|
||||||
|
|
||||||
|
@ -910,6 +918,7 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
|
||||||
'max_num': max_num,
|
'max_num': max_num,
|
||||||
'widgets': widgets,
|
'widgets': widgets,
|
||||||
'validate_max': validate_max,
|
'validate_max': validate_max,
|
||||||
|
'localized_fields': localized_fields,
|
||||||
}
|
}
|
||||||
FormSet = modelformset_factory(model, **kwargs)
|
FormSet = modelformset_factory(model, **kwargs)
|
||||||
FormSet.fk = fk
|
FormSet.fk = fk
|
||||||
|
|
|
@ -5,7 +5,7 @@ Model Form Functions
|
||||||
.. module:: django.forms.models
|
.. module:: django.forms.models
|
||||||
:synopsis: Django's functions for building model forms and formsets.
|
: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``.
|
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
|
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.
|
``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
|
``formfield_callback`` is a callable that takes a model field and returns
|
||||||
a form field.
|
a form field.
|
||||||
|
|
||||||
|
@ -33,12 +35,14 @@ Model Form Functions
|
||||||
information. Omitting any definition of the fields to use will result in all
|
information. Omitting any definition of the fields to use will result in all
|
||||||
fields being used, but this behaviour is deprecated.
|
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.
|
Returns a ``FormSet`` class for the given ``model`` class.
|
||||||
|
|
||||||
Arguments ``model``, ``form``, ``fields``, ``exclude``,
|
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`.
|
:func:`~django.forms.models.modelform_factory`.
|
||||||
|
|
||||||
Arguments ``formset``, ``extra``, ``max_num``, ``can_order``,
|
Arguments ``formset``, ``extra``, ``max_num``, ``can_order``,
|
||||||
|
@ -50,9 +54,9 @@ Model Form Functions
|
||||||
|
|
||||||
.. versionchanged:: 1.6
|
.. 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
|
Returns an ``InlineFormSet`` using :func:`modelformset_factory` with
|
||||||
defaults of ``formset=BaseInlineFormSet``, ``can_delete=True``, and
|
defaults of ``formset=BaseInlineFormSet``, ``can_delete=True``, and
|
||||||
|
@ -65,4 +69,4 @@ Model Form Functions
|
||||||
|
|
||||||
.. versionchanged:: 1.6
|
.. versionchanged:: 1.6
|
||||||
|
|
||||||
The ``widgets`` and the ``validate_max`` parameters were added.
|
The ``widgets``, ``validate_max`` and ``localized_fields`` parameters were added.
|
||||||
|
|
|
@ -234,6 +234,10 @@ Minor features
|
||||||
.. _`Pillow`: https://pypi.python.org/pypi/Pillow
|
.. _`Pillow`: https://pypi.python.org/pypi/Pillow
|
||||||
.. _`PIL`: https://pypi.python.org/pypi/PIL
|
.. _`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
|
Backwards incompatible changes in 1.6
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -474,6 +474,24 @@ parameter when declaring the form field::
|
||||||
See the :doc:`form field documentation </ref/forms/fields>` for more information
|
See the :doc:`form field documentation </ref/forms/fields>` for more information
|
||||||
on fields and their arguments.
|
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-modelform-clean-method:
|
||||||
|
|
||||||
Overriding the 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`
|
``Meta`` class. Please see the ``ModelForm`` :ref:`modelforms-selecting-fields`
|
||||||
documentation.
|
documentation.
|
||||||
|
|
||||||
|
... or enable localization for specific fields::
|
||||||
|
|
||||||
|
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=("birth_date",))
|
||||||
|
|
||||||
.. _model-formsets:
|
.. _model-formsets:
|
||||||
|
|
||||||
Model formsets
|
Model formsets
|
||||||
|
@ -663,6 +685,20 @@ class of a ``ModelForm`` works::
|
||||||
>>> AuthorFormSet = modelformset_factory(
|
>>> AuthorFormSet = modelformset_factory(
|
||||||
... Author, widgets={'name': Textarea(attrs={'cols': 80, 'rows': 20})
|
... 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
|
Providing initial values
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,41 @@ class OverrideCleanTests(TestCase):
|
||||||
self.assertEqual(form.instance.left, 1)
|
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.
|
# Regression test for #12960.
|
||||||
# Make sure the cleaned_data returned from ModelForm.clean() is applied to the
|
# Make sure the cleaned_data returned from ModelForm.clean() is applied to the
|
||||||
# model instance.
|
# model instance.
|
||||||
|
|
|
@ -273,6 +273,7 @@ class UserSiteForm(forms.ModelForm):
|
||||||
'id': CustomWidget,
|
'id': CustomWidget,
|
||||||
'data': CustomWidget,
|
'data': CustomWidget,
|
||||||
}
|
}
|
||||||
|
localized_fields = ('data',)
|
||||||
|
|
||||||
|
|
||||||
class Callback(object):
|
class Callback(object):
|
||||||
|
@ -297,19 +298,23 @@ class FormfieldCallbackTests(TestCase):
|
||||||
form = Formset().forms[0]
|
form = Formset().forms[0]
|
||||||
self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
|
self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
|
||||||
self.assertTrue(isinstance(form['data'].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):
|
def test_modelformset_factory_default(self):
|
||||||
Formset = modelformset_factory(UserSite, form=UserSiteForm)
|
Formset = modelformset_factory(UserSite, form=UserSiteForm)
|
||||||
form = Formset().forms[0]
|
form = Formset().forms[0]
|
||||||
self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
|
self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
|
||||||
self.assertTrue(isinstance(form['data'].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):
|
def assertCallbackCalled(self, callback):
|
||||||
id_field, user_field, data_field = UserSite._meta.fields
|
id_field, user_field, data_field = UserSite._meta.fields
|
||||||
expected_log = [
|
expected_log = [
|
||||||
(id_field, {'widget': CustomWidget}),
|
(id_field, {'widget': CustomWidget}),
|
||||||
(user_field, {}),
|
(user_field, {}),
|
||||||
(data_field, {'widget': CustomWidget}),
|
(data_field, {'widget': CustomWidget, 'localize': True}),
|
||||||
]
|
]
|
||||||
self.assertEqual(callback.log, expected_log)
|
self.assertEqual(callback.log, expected_log)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue