@@ -561,6 +561,8 @@ Note that the `WritableField` class that was present in version 2.x no longer ex
561
561
562
562
## Examples
563
563
564
+ ### A Basic Custom Field
565
+
564
566
Let's look at an example of serializing a class that represents an RGB color value:
565
567
566
568
class Color(object):
@@ -600,7 +602,7 @@ As an example, let's create a field that can be used to represent the class name
600
602
"""
601
603
return obj.__class__.__name__
602
604
603
- #### Raising validation errors
605
+ ### Raising validation errors
604
606
605
607
Our ` ColorField ` class above currently does not perform any data validation.
606
608
To 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
646
648
647
649
This style keeps your error messages cleaner and more separated from your code, and should be preferred.
648
650
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
+
649
782
# Third party packages
650
783
651
784
The following third party packages are also available.
0 commit comments