Building RESTful Python Web Services
上QQ阅读APP看书,第一时间看更新

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.