diff --git a/HISTORY.rst b/HISTORY.rst index 95dc1f5..c5db1f1 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,15 @@ History ------- +0.2.3 (2017-10-18) +~~~~~~~~~~~~~~~~~~ + +* Fixed how choices are normalized in ``SerializerForm`` to ``[(key, key)]`` + vs previously ``[(key, value)]`` to match DRF's handling of choices unlike Django forms. +* Added support for ``Form.all_fields`` and ``Form.all_base_fields`` + in ``SerializerForm`` which supports custom form classes which implement such API. +* Added support for ``FormSerializer.Meta.exclude`` which allows to specify fields to exclude. + 0.2.2 (2017-05-09) ~~~~~~~~~~~~~~~~~~ diff --git a/drf_braces/__init__.py b/drf_braces/__init__.py index 1c2723b..252c95f 100755 --- a/drf_braces/__init__.py +++ b/drf_braces/__init__.py @@ -4,4 +4,4 @@ __author__ = 'Miroslav Shubernetskiy' __email__ = 'miroslav@miki725.com' -__version__ = '0.2.2' +__version__ = '0.2.3' diff --git a/drf_braces/serializers/form_serializer.py b/drf_braces/serializers/form_serializer.py index 4ce9511..2f4de1b 100644 --- a/drf_braces/serializers/form_serializer.py +++ b/drf_braces/serializers/form_serializer.py @@ -85,6 +85,7 @@ def __init__(self, meta, class_name): self.failure_mode = getattr(meta, 'failure_mode', FormSerializerFailure.fail) self.minimum_required = getattr(meta, 'minimum_required', []) self.field_mapping = getattr(meta, 'field_mapping', {}) + self.exclude = getattr(meta, 'exclude', []) assert self.form, ( 'Class {serializer_class} missing "Meta.form" attribute'.format( @@ -192,7 +193,12 @@ def get_fields(self): # Iterate over the form fields, creating an # instance of serializer field for each. - for field_name, form_field in self.Meta.form.base_fields.items(): + form = self.Meta.form + for field_name, form_field in getattr(form, 'all_base_fields', form.base_fields).items(): + # if field is specified as excluded field + if field_name in getattr(self.Meta, 'exclude', []): + continue + # if field is already defined via declared fields # skip mapping it from forms which then honors # the custom validation defined on the DRF declared field @@ -248,6 +254,10 @@ def _get_field_kwargs(self, form_field, serializer_field_class): """ attrs = find_matching_class_kwargs(form_field, serializer_field_class) + if 'choices' in attrs: + choices = OrderedDict(attrs['choices']).keys() + attrs['choices'] = OrderedDict(zip(choices, choices)) + if getattr(form_field, 'initial', None): attrs['default'] = form_field.initial @@ -321,9 +331,11 @@ def repopulate_form_fields(self): """ instance = self.get_form() - for form_field_name, form_field in instance.fields.items(): + for form_field_name, form_field in getattr(instance, 'all_fields', instance.fields).items(): if hasattr(form_field, 'choices'): - self.fields[form_field_name].choices = OrderedDict(form_field.choices) + # let drf normalize choices down to key: key + # key:value is unsupported unlike in django form fields + self.fields[form_field_name].choices = OrderedDict(form_field.choices).keys() self.fields[form_field_name].choice_strings_to_values = { six.text_type(key): key for key in OrderedDict(form_field.choices).keys() } @@ -347,6 +359,6 @@ def set_form_partial_validation(form, minimum_required): :param minimum_required: list of minimum required fields :return: None """ - for field_name, field in form.fields.items(): + for field_name, field in getattr(form, 'all_fields', form.fields).items(): if field_name not in minimum_required: field.required = False diff --git a/drf_braces/tests/serializers/test_form_serialzier.py b/drf_braces/tests/serializers/test_form_serialzier.py index e378e8b..2e6af73 100644 --- a/drf_braces/tests/serializers/test_form_serialzier.py +++ b/drf_braces/tests/serializers/test_form_serialzier.py @@ -186,6 +186,18 @@ def test_get_fields(self): self.assertIsInstance(serializer_fields['bar'], fields.IntegerField) self.assertIsInstance(serializer_fields['other'], fields.CharField) + def test_get_fields_excluded(self): + serializer = self.serializer_class() + serializer.Meta.exclude = ['foo'] + serializer.Meta.field_mapping.update({ + forms.CharField: fields.BooleanField, + }) + + serializer_fields = serializer.get_fields() + + self.assertIsInstance(serializer_fields, OrderedDict) + self.assertNotIn('foo', serializer_fields) + def test_get_fields_not_mapped(self): serializer = self.serializer_class() @@ -235,6 +247,21 @@ def test_get_field_kwargs(self): }, kwargs) self.assertNotIn('required', kwargs) + def test_get_field_kwargs_choice_field(self): + serializer = self.serializer_class() + form_field = forms.ChoiceField( + choices=[('foo', 'FOO'), ('bar', 'BAR')] + ) + + kwargs = serializer._get_field_kwargs(form_field, fields.ChoiceField) + + self.assertDictContainsSubset({ + 'choices': OrderedDict([ + ('foo', 'foo'), + ('bar', 'bar'), + ]), + }, kwargs) + def test_validate(self): serializer = self.serializer_class(data={ 'foo': 'hello', @@ -307,8 +334,8 @@ def test_repopulate_form_fields(self): serializer.repopulate_form_fields() - self.assertDictEqual(serializer.fields['happy'].choices, {'happy': 'choices'}) - self.assertDictEqual(serializer.fields['happy'].choice_strings_to_values, + self.assertDictEqual(dict(serializer.fields['happy'].choices), {'happy': 'happy'}) + self.assertDictEqual(dict(serializer.fields['happy'].choice_strings_to_values), {'happy': 'happy'}) @mock.patch.object(serializers.Serializer, 'to_internal_value') diff --git a/requirements-dev.txt b/requirements-dev.txt index 9450404..2959d4a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,6 +4,7 @@ django-extensions flake8 importanize mock +pdbpp Sphinx sphinx-autobuild sphinx-rtd-theme