@@ -561,6 +561,8 @@ Note that the `WritableField` class that was present in version 2.x no longer ex
561561
562562## Examples
563563
564+ ### A Basic Custom Field
565+
564566Let's look at an example of serializing a class that represents an RGB color value:
565567
566568 class Color(object):
@@ -600,7 +602,7 @@ As an example, let's create a field that can be used to represent the class name
600602 """
601603 return obj.__class__.__name__
602604
603- #### Raising validation errors
605+ ### Raising validation errors
604606
605607Our ` ColorField ` class above currently does not perform any data validation.
606608To indicate invalid data, we should raise a ` serializers.ValidationError ` , like so:
@@ -646,6 +648,137 @@ The `.fail()` method is a shortcut for raising `ValidationError` that takes a me
646648
647649This style keeps your error messages cleaner and more separated from your code, and should be preferred.
648650
651+ ### Using ` source='*' `
652+
653+ Here we'll take an example of a _ flat_ ` DataPoint ` model with ` x_coordinate ` and ` y_coordinate ` attributes.
654+
655+ class DataPoint(models.Model):
656+ label = models.CharField(max_length=50)
657+ x_coordinate = models.SmallIntegerField()
658+ y_coordinate = models.SmallIntegerField()
659+
660+ Using a custom field and ` source='*' ` we can provide a nested representation of
661+ the coordinate pair:
662+
663+ class CoordinateField(serializers.Field):
664+
665+ def to_representation(self, obj):
666+ ret = {
667+ "x": obj.x_coordinate,
668+ "y": obj.y_coordinate
669+ }
670+ return ret
671+
672+ def to_internal_value(self, data):
673+ ret = {
674+ "x_coordinate": data["x"],
675+ "y_coordinate": data["y"],
676+ }
677+ return ret
678+
679+
680+ class DataPointSerializer(serializers.ModelSerializer):
681+ coordinates = CoordinateField(source='*')
682+
683+ class Meta:
684+ model = DataPoint
685+ fields = ['label', 'coordinates']
686+
687+ Note that this example doesn't handle validation. Partly for that reason, in a
688+ real project, the coordinate nesting might be better handled with a nested serialiser
689+ using ` source='*' ` , with two ` IntegerField ` instances, each with their own ` source `
690+ pointing to the relevant field.
691+
692+ The key points from the example, though, are:
693+
694+ * ` to_representation ` is passed the entire ` DataPoint ` object and must map from that
695+ to the desired output.
696+
697+ >>> instance = DataPoint(label='Example', x_coordinate=1, y_coordinate=2)
698+ >>> out_serializer = DataPointSerializer(instance)
699+ >>> out_serializer.data
700+ ReturnDict([('label', 'testing'), ('coordinates', {'x': 1, 'y': 2})])
701+
702+ * Unless our field is to be read-only, ` to_internal_value ` must map back to a dict
703+ suitable for updating our target object. With ` source='*' ` , the return from
704+ ` to_internal_value ` will update the root validated data dictionary, rather than a single key.
705+
706+ >>> data = {
707+ ... "label": "Second Example",
708+ ... "coordinates": {
709+ ... "x": 3,
710+ ... "y": 4,
711+ ... }
712+ ... }
713+ >>> in_serializer = DataPointSerializer(data=data)
714+ >>> in_serializer.is_valid()
715+ True
716+ >>> in_serializer.validated_data
717+ OrderedDict([('label', 'Second Example'),
718+ ('y_coordinate', 4),
719+ ('x_coordinate', 3)])
720+
721+ For completeness lets do the same thing again but with the nested serialiser
722+ approach suggested above:
723+
724+ class NestedCoordinateSerializer(serializers.Serializer):
725+ x = serializers.IntegerField(source='x_coordinate')
726+ y = serializers.IntegerField(source='y_coordinate')
727+
728+
729+ class DataPointSerializer(serializers.ModelSerializer):
730+ coordinates = NestedCoordinateSerializer(source='*')
731+
732+ class Meta:
733+ model = DataPoint
734+ fields = ['label', 'coordinates']
735+
736+ Here the mapping between the target and source attribute pairs (` x ` and
737+ ` x_coordinate ` , ` y ` and ` y_coordinate ` ) is handled in the ` IntegerField `
738+ declarations. It's our ` NestedCoordinateSerializer ` that takes ` source='*' ` .
739+
740+ Our new ` DataPointSerializer ` exhibits the same behaviour as the custom field
741+ approach.
742+
743+ Serialising:
744+
745+ >>> out_serializer = DataPointSerializer(instance)
746+ >>> out_serializer.data
747+ ReturnDict([('label', 'testing'),
748+ ('coordinates', OrderedDict([('x', 1), ('y', 2)]))])
749+
750+ Deserialising:
751+
752+ >>> in_serializer = DataPointSerializer(data=data)
753+ >>> in_serializer.is_valid()
754+ True
755+ >>> in_serializer.validated_data
756+ OrderedDict([('label', 'still testing'),
757+ ('x_coordinate', 3),
758+ ('y_coordinate', 4)])
759+
760+ But we also get the built-in validation for free:
761+
762+ >>> invalid_data = {
763+ ... "label": "still testing",
764+ ... "coordinates": {
765+ ... "x": 'a',
766+ ... "y": 'b',
767+ ... }
768+ ... }
769+ >>> invalid_serializer = DataPointSerializer(data=invalid_data)
770+ >>> invalid_serializer.is_valid()
771+ False
772+ >>> invalid_serializer.errors
773+ ReturnDict([('coordinates',
774+ {'x': ['A valid integer is required.'],
775+ 'y': ['A valid integer is required.']})])
776+
777+ For this reason, the nested serialiser approach would be the first to try. You
778+ would use the custom field approach when the nested serialiser becomes infeasible
779+ or overly complex.
780+
781+
649782# Third party packages
650783
651784The following third party packages are also available.
0 commit comments