From 1ca2e6cb218d8953206689c212b1d9482152081a Mon Sep 17 00:00:00 2001 From: Miroslav Shubernetskiy Date: Wed, 18 Oct 2017 15:03:21 -0400 Subject: [PATCH 1/4] normalizing choices in FormSerializer to support latest DRF --- drf_braces/serializers/form_serializer.py | 8 +++++++- .../tests/serializers/test_form_serialzier.py | 19 +++++++++++++++++-- requirements-dev.txt | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/drf_braces/serializers/form_serializer.py b/drf_braces/serializers/form_serializer.py index 4ce9511..e73fb7c 100644 --- a/drf_braces/serializers/form_serializer.py +++ b/drf_braces/serializers/form_serializer.py @@ -248,6 +248,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 @@ -323,7 +327,9 @@ def repopulate_form_fields(self): for form_field_name, form_field in 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() } diff --git a/drf_braces/tests/serializers/test_form_serialzier.py b/drf_braces/tests/serializers/test_form_serialzier.py index e378e8b..86fa8a4 100644 --- a/drf_braces/tests/serializers/test_form_serialzier.py +++ b/drf_braces/tests/serializers/test_form_serialzier.py @@ -235,6 +235,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 +322,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 From 599fa243c44854bc4db72a91ac4e6079cd8ea683 Mon Sep 17 00:00:00 2001 From: Miroslav Shubernetskiy Date: Wed, 18 Oct 2017 15:08:42 -0400 Subject: [PATCH 2/4] added support for all_fields and all_base_fields in FormSerializer --- drf_braces/serializers/form_serializer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drf_braces/serializers/form_serializer.py b/drf_braces/serializers/form_serializer.py index e73fb7c..67ee822 100644 --- a/drf_braces/serializers/form_serializer.py +++ b/drf_braces/serializers/form_serializer.py @@ -192,7 +192,8 @@ 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 already defined via declared fields # skip mapping it from forms which then honors # the custom validation defined on the DRF declared field @@ -325,7 +326,7 @@ 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'): # let drf normalize choices down to key: key # key:value is unsupported unlike in django form fields @@ -353,6 +354,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 From c1b5dd63b653456cdaa42d399f394f6552e669fd Mon Sep 17 00:00:00 2001 From: Miroslav Shubernetskiy Date: Wed, 18 Oct 2017 15:11:50 -0400 Subject: [PATCH 3/4] bumping version to 0.2.3 [ci skip] --- HISTORY.rst | 8 ++++++++ drf_braces/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 95dc1f5..c79355a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,14 @@ 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. + 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' From 7a57b45569fe373428c0dd14f9b12a75e730c56e Mon Sep 17 00:00:00 2001 From: Miroslav Shubernetskiy Date: Thu, 19 Oct 2017 11:30:10 -0400 Subject: [PATCH 4/4] added support for excluding fields in FormSerializer --- HISTORY.rst | 1 + drf_braces/serializers/form_serializer.py | 5 +++++ drf_braces/tests/serializers/test_form_serialzier.py | 12 ++++++++++++ 3 files changed, 18 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index c79355a..c5db1f1 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -10,6 +10,7 @@ History 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/serializers/form_serializer.py b/drf_braces/serializers/form_serializer.py index 67ee822..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( @@ -194,6 +195,10 @@ def get_fields(self): # instance of serializer field for each. 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 diff --git a/drf_braces/tests/serializers/test_form_serialzier.py b/drf_braces/tests/serializers/test_form_serialzier.py index 86fa8a4..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()