Skip to content

Commit 4ae8671

Browse files
committed
Merge pull request #84 from oxan/source-wildcard
2 parents d275d6d + 34baaae commit 4ae8671

File tree

3 files changed

+76
-0
lines changed

3 files changed

+76
-0
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Features & fixes:
1515
* Support dataclasses with fields that have ``init=False``.
1616
* Support ``save()`` on serializers with ``many=True``.
1717
* Support for fields with union types.
18+
* Support nested serializers with ``source='*'``.
1819
* Fix ``child_kwargs`` defined in dataclass field metadata (as opposed to ``extra_kwargs`` field on ``Meta`` subclass).
1920

2021
1.2.0, 18 November 2022

rest_framework_dataclasses/serializers.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import collections
34
import copy
45
import dataclasses
56
import datetime
@@ -665,6 +666,23 @@ def to_internal_value(self, data: Dict[str, Any]) -> T:
665666
"""
666667
native_values = super(DataclassSerializer, self).to_internal_value(data)
667668

669+
# If the source is a wildcard, don't try to instantiate anything, but just return the native values as a dict.
670+
# It'll be merged into the native values of the parent serializer by the parent class.
671+
if self.source == '*':
672+
return native_values
673+
674+
# If any fields have a wildcard source, their native value will hold a values dictionary instead of a dataclass,
675+
# so convert them into a dataclass.
676+
if any(field.source == '*' for field in self.fields.values()):
677+
for field_name, tp in self.dataclass_definition.field_types.items():
678+
if (
679+
field_name in native_values and
680+
dataclasses.is_dataclass(tp) and
681+
isinstance(native_values[field_name], collections.abc.Mapping)
682+
):
683+
dataclass_fields = {field.name: field for field in dataclasses.fields(tp)}
684+
native_values[field_name] = _create_instance(tp, dataclass_fields, native_values[field_name])
685+
668686
# Only insert empty sentinel value for non-supplied values when the root serializer is in partial mode, to
669687
# prevent them from showing up otherwise.
670688
if self.root.partial:

tests/test_functional.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from unittest import TestCase
1010

1111
from rest_framework import fields
12+
from rest_framework.serializers import Serializer
1213

1314
from rest_framework_dataclasses.serializers import DataclassSerializer
1415
from rest_framework_dataclasses.types import Literal
@@ -268,3 +269,59 @@ class ObscureFeaturesTest(TestCase, FunctionalTestMixin):
268269

269270
def setUp(self):
270271
self.instance.name = 'Bob'
272+
273+
# Testcase 5 (source='*')
274+
275+
276+
@dataclasses.dataclass
277+
class PersonPet:
278+
name: str
279+
pet: Pet
280+
281+
282+
class PersonPetEmbeddedDataclassSerializer(DataclassSerializer):
283+
class PetSerializer(DataclassSerializer):
284+
class Meta:
285+
dataclass = Pet
286+
fields = ['name', 'owner_name', 'animal']
287+
288+
name = fields.CharField(source='pet.name')
289+
animal = fields.CharField(source='pet.animal')
290+
owner_name = fields.CharField(source='name')
291+
292+
class Meta:
293+
dataclass = PersonPet
294+
fields = ['name', 'pet']
295+
296+
name = fields.CharField()
297+
pet = PetSerializer(source='*')
298+
299+
300+
class PersonPetEmbeddedDefaultSerializer(DataclassSerializer):
301+
class PetSerializer(Serializer):
302+
class Meta:
303+
dataclass = Pet
304+
fields = ['name', 'owner_name', 'animal']
305+
306+
name = fields.CharField(source='pet.name')
307+
animal = fields.CharField(source='pet.animal')
308+
owner_name = fields.CharField(source='name')
309+
310+
class Meta:
311+
dataclass = PersonPet
312+
fields = ['name', 'pet']
313+
314+
name = fields.CharField()
315+
pet = PetSerializer(source='*')
316+
317+
318+
class SourceStarDataclassTest(TestCase, FunctionalTestMixin):
319+
serializer = PersonPetEmbeddedDataclassSerializer
320+
instance = PersonPet(name='Milo', pet=Pet(name='Katsu', animal='cat'))
321+
representation = {'name': 'Milo', 'pet': {'name': 'Katsu', 'animal': 'cat', 'owner_name': 'Milo'}}
322+
323+
324+
class SourceStarDefaultTest(TestCase, FunctionalTestMixin):
325+
serializer = PersonPetEmbeddedDefaultSerializer
326+
instance = PersonPet(name='Milo', pet=Pet(name='Katsu', animal='cat'))
327+
representation = {'name': 'Milo', 'pet': {'name': 'Katsu', 'animal': 'cat', 'owner_name': 'Milo'}}

0 commit comments

Comments
 (0)