Skip to content

Commit

Permalink
Merge pull request #21 from dealertrack/multiform
Browse files Browse the repository at this point in the history
Multiform
  • Loading branch information
miki725 committed Oct 23, 2017
2 parents ad98c6a + 7a57b45 commit d3aa951
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 7 deletions.
9 changes: 9 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion drf_braces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

__author__ = 'Miroslav Shubernetskiy'
__email__ = 'miroslav@miki725.com'
__version__ = '0.2.2'
__version__ = '0.2.3'
20 changes: 16 additions & 4 deletions drf_braces/serializers/form_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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()
}
Expand All @@ -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
31 changes: 29 additions & 2 deletions drf_braces/tests/serializers/test_form_serialzier.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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')
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ django-extensions
flake8
importanize
mock
pdbpp
Sphinx
sphinx-autobuild
sphinx-rtd-theme
Expand Down

0 comments on commit d3aa951

Please sign in to comment.