Managing serialization and deserialization with relationships and hyperlinks
Our new RESTful Web API has to be able to serialize and deserialize the GameCategory
, Game
, Player
, and PlayerScore
instances into JSON representations. In this case, we also have to pay special attention to the relationships between the different models when we create the serializer classes to manage serialization to JSON and deserialization from JSON.
In our last version of the previous API, we created a subclass of the rest_framework.serializers.ModelSerializer
class to make it easier to generate a serializer and reduce boilerplate code. In this case, we will also declare a class that inherits from ModelSerializer
, but the other classes will inherit from the rest_framework.serializers.HyperlinkedModelSerializer
class.
The HyperlinkedModelSerializer
is a type of ModelSerializer
that uses hyperlinked relationships instead of primary key relationships, and therefore, it represents the realationships to other model instances with hyperlinks instead of primary key values. In addition, the HyperlinkedModelSerializer
generated a field named url
with the URL for the resource as its value. As seen in the case of ModelSerializer
, the HyperlinkedModelSerializer
class provides default implementations for the create
and update
methods.
Now, go to the gamesapi/games
folder and open the serializers.py
file. Replace the code in this file with the following code that declares the required imports and the GameCategorySerializer
class. We will add more classes to this file later. The code file for the sample is included in the restful_python_chapter_02_03
folder:
from rest_framework import serializers from games.models import GameCategory from games.models import Game from games.models import Player from games.models import PlayerScore import games.views class GameCategorySerializer(serializers.HyperlinkedModelSerializer): games = serializers.HyperlinkedRelatedField( many=True, read_only=True, view_name='game-detail') class Meta: model = GameCategory fields = ( 'url', 'pk', 'name', 'games')
The GameCategorySerializer
class is a subclass of the HyperlinkedModelSerializer
class. The GameCategorySerializer
class declares a games
attribute as an instance of serializers.HyperlinkedRelatedField
with many
and read_only
equal to True
because it is a one-to-many relationship and it is read-only. We use the games
name that we specified as the related_name
string value when we created the game_category
field as a models.ForeignKey
instance in the Game
model. This way, the games
field will provide us with an array of hyperlinks to each game that belong to the game category. The view_name
value is 'game-detail
' because we want the browsable API feature to use the game detail view to render the hyperlink when the user clicks or taps on it.
The GameCategorySerializer
class declares a Meta
inner class that declares two attributes: model
and fields
. The model
attribute specifies the model related to the serializer, that is, the GameCategory
class. The fields
attribute specifies a tuple of string whose values indicates the field names that we want to include in the serialization from the related model. We want to include both the primary key and the URL, and therefore, the code specified both 'pk'
and 'url'
as members of the tuple. There is no need to override either the create
, or update
method because the generic behavior will be enough in this case. The HyperlinkedModelSerializer
superclass provides implementations for both methods.
Now, add the following code to the serializers.py
file to declare the GameSerializer
class. The code file for the sample is included in the restful_python_chapter_02_03
folder:
class GameSerializer(serializers.HyperlinkedModelSerializer): # We want to display the game cagory's name instead of the id game_category = serializers.SlugRelatedField(queryset=GameCategory.objects.all(), slug_field='name') class Meta: model = Game fields = ( 'url', 'game_category', 'name', 'release_date', 'played')
The GameSerializer
class is a subclass of the HyperlinkedModelSerializer
class. The GameSerializer
class declares a game_category
attribute as an instance of serializers.SlugRelatedField
with its queryset
argument set to GameCategory.objects.all()
and its slug_field
argument set to 'name'
. A SlugRelatedField
is a read-write field that represents the target of the relationship by a unique slug attribute, that is, the description. We created the game_category
field as a models.ForeignKey
instance in the Game
model and we want to display the game category's name as the description (slug field) for the related GameCategory
. Thus, we specified 'name'
as the slug_field
. In case it is necessary to display the possible options for the related game category in a form in the browsable API, Django will use the expression specified in the queryset
argument to retrieve all the possible instances and display their specified slug field.
The GameCategorySerializer
class declares a Meta
inner class that declares two attributes: model
and fields
. The model
attribute specifies the model related to the serializer, that is, the Game
class. The fields
attribute specifies a tuple of string whose values indicate the field names that we want to include in the serialization from the related model. We just want to include the URL, and therefore, the code specified both 'url'
as a member of the tuple. The game_category
field will specify the name
field for the related GameCategory
.
Now, add the following code to the serializers.py
file to declare the ScoreSerializer
class. The code file for the sample is included in the restful_python_chapter_02_03
folder:
class ScoreSerializer(serializers.HyperlinkedModelSerializer): # We want to display all the details for the game game = GameSerializer() # We don't include the player because it will be nested in the player class Meta: model = PlayerScore fields = ( 'url', 'pk', 'score', 'score_date', 'game', )
The ScoreSerializer
class is a subclass of the HyperlinkedModelSerializer
class. We will use the ScoreSerializer
class to serialize PlayerScore
instances related to a Player
, that is, to display all the scores for a specific player when we serialize a Player
. We want to display all the details for the related Game
but we don't include the related Player
because the Player
will use this ScoreSerializer
serializer.
The ScoreSerializer
class declares a game
attribute as an instance of the previously coded GameSerializer
class. We created the game
field as a models.ForeignKey
instance in the PlayerScore
model and we want to serialize the same data for the game that we coded in the GameSerializer
class.
The ScoreSerializer
class declares a Meta
inner class that declares two attributes: model
and fields
. The model
attribute specifies the model related to the serializer, that is, the PlayerScore
class. As previously explain, we don't include the 'player'
field name in the fields
tuple of string to avoid serializing the player again. We will use a PlayerSerializer
as a master and the ScoreSerializer
as the detail.
Now, add the following code to the serializers.py
file to declare the PlayerSerializer
class. The code file for the sample is included in the restful_python_chapter_02_03
folder:
class PlayerSerializer(serializers.HyperlinkedModelSerializer): scores = ScoreSerializer(many=True, read_only=True) gender = serializers.ChoiceField( choices=Player.GENDER_CHOICES) gender_description = serializers.CharField( source='get_gender_display', read_only=True) class Meta: model = Player fields = ( 'url', 'name', 'gender', 'gender_description', 'scores', )
The PlayerSerializer
class is a subclass of the HyperlinkedModelSerializer
class. We will use the PlayerSerializer
class to serialize Player
instances and we will use the previously declared ScoreSerializer
class to serialize all the PlayerScore
instances related to the Player
.
The PlayerSerializer
class declares a scores
attribute as an instance of the previously coded ScoreSerializer
class. The many
argument is set to True
because it is a one-to-many relationship. We use the scores
name that we specified as the related_name
string value when we created the player
field as a models.ForeignKey
instance in the PlayerScore
model. This way, the scores
field will render each PlayerScore
that belongs to the Player
using the previously declared ScoreSerializer
.
The Player
model declared gender
as an instance of models.CharField
with the choices
attribute set to the Player.GENDER_CHOICES
string tuple. The ScoreSerializer
class declares a gender
attribute as an instance of serializers.ChoiceField
with the choices
argument set to the Player.GENDER_CHOICES
string tuple. In addition, the class declares a gender_description
attribute with read_only
set to True
and the source
argument set to 'get_gender_display'
. The source
string is built with get_
followed by the field name, gender
, and _display
. This way, the read-only gender_description
attribute will render the description for the gender choices instead of the single char stored values.
The ScoreSerializer
class declares a Meta
inner class that declares two attributes: model
and fields
. The model
attribute specifies the model related to the serializer, that is, the PlayerScore
class. As previously explained, we don't include the 'player'
field name in the fields
tuple of string to avoid serializing the player again. We will use a PlayerSerializer
as a master and the ScoreSerializer
as the detail.
Finally, add the following code to the serializers.py
file to declare the PlayerScoreSerializer
class. The code file for the sample is included in the restful_python_chapter_02_03
folder:
class PlayerScoreSerializer(serializers.ModelSerializer): player = serializers.SlugRelatedField(queryset=Player.objects.all(), slug_field='name') # We want to display the game's name instead of the id game = serializers.SlugRelatedField(queryset=Game.objects.all(), slug_field='name') class Meta: model = PlayerScore fields = ( 'url', 'pk', 'score', 'score_date', 'player', 'game', )
The PlayerScoreSerializer
class is a subclass of the HyperlinkedModelSerializer
class. We will use the PlayerScoreSerializer
class to serialize PlayerScore
instances. Previously, we created the ScoreSerializer
class to serialize PlayerScore
instances as the detail of a player. We will use the new PlayerScoreSerializer
class when we want to display the related player's name and the related game's name. In the other serializer
class, we didn't include any information related to the player and we included all the details for the game.
The PlayerScoreSerializer
class declares a player
attribute as an instance of serializers.SlugRelatedField
with its queryset
argument set to Player.objects.all()
and its slug_field
argument set to 'name'
. We created the player
field as a models.ForeignKey
instance in the PlayerScore
model and we want to display the player's name as the description (slug field) for the related Player
. Thus, we specified 'name'
as the slug_field
. In case it is necessary to display the possible options for the related game category in a form in the browsable API, Django will use the expression specified in the queryset
argument to retrieve all the possible players and display their specified slug field.
The PlayerScoreSerializer
class declares a game
attribute as an instance of serializers.SlugRelatedField
with its queryset
argument set to Game.objects.all()
and its slug_field
argument set to 'name'
. We created the game
field as a models.ForeignKey
instance in the PlayerScore
model and we want to display the game's name as the description (slug field) for the related Game
.