Deserialization of nested fields into several Model objects

I have the following json that I want to deserialize in order to insert data into 3 tables.

{
   "id":"game_id",
   "rated":true,
   "variant":"standard",
   "speed":"fast",
   "perf":"fast",
   "createdAt": 1234,
   "lastMoveAt":1234,
   "status":"resign",
   "players":{
      "white":{
         "user":{
            "name":"player1",
            "id":"player1"
         },
         "rating":1000,
         "ratingDiff":-5
      },
      "black":{
         "user":{
            "name":"player2",
            "id":"player2"
         },
         "rating":1001,
         "ratingDiff":5
      }
   },
   "winner":"black",
   "opening":{
      "eco":"Z99",
      "name":"my opening",
      "ply":2
   },
   "moves":"1, 2, 3, 4",
   "clock":{
      "initial":60,
      "increment":0,
      "totalTime":60
   }
}

Here are my models :

class User(models.Model):

    class Meta:
        ordering = ["id"]

    id = models.CharField(primary_key=True, max_length=100)
    name = models.CharField(max_length=100)

    def __repr__(self):
        return f"User(id={self.id!r}, name={self.name!r})"


class Opening(models.Model):

    class Meta:
        ordering = ["eco"]
        constraints = [
            models.UniqueConstraint(
                fields=["name", "eco"], name="eco_name"
            )
        ]

    name = models.CharField(max_length=200)
    eco = models.CharField(max_length=3)

    def __repr__(self):
        return f"User(name={self.name!r}, eco={self.eco!r})"


class Game(models.Model):

    class Meta:
        ordering = ["last_move_at"]

    id = models.CharField(primary_key=True, max_length=15)
    rated = models.BooleanField(default=True)
    variant = models.CharField(max_length=200)
    speed = models.CharField(max_length=50)
    perf = models.CharField(max_length=50)
    created_at = models.IntegerField()
    last_move_at = models.IntegerField()
    status = models.CharField(max_length=50)
    winner = models.CharField(max_length=100)
    moves = models.TextField(max_length=1000)
    white_rating = models.IntegerField()
    white_rating_diff = models.IntegerField()
    white = models.ForeignKey(User, related_name="white_id", on_delete=models.CASCADE)
    black_rating = models.IntegerField()
    black_rating_diff = models.IntegerField()
    black = models.ForeignKey(User, related_name="black_id", on_delete=models.CASCADE)
    opening = models.ForeignKey(Opening, related_name="eco_name", on_delete=models.CASCADE)
    initial_time = models.IntegerField(null=True)
    increment = models.IntegerField(null=True)
    total_time = models.CharField(max_length=200)

Here are my serializers :

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["id", "name"]

class OpeningSerializer(serializers.ModelSerializer):
    class Meta:
        model = Opening
        fields = ["name", "eco"]

    def create(self, validated_data):
        return Opening.objects.get_or_create(**validated_data)[0]


class GameSerializer(serializers.ModelSerializer):

    class Meta:
        model = Game
        fields = ['id', 'rated', 'variant', 'speed', 'perf', 'created_at', 'last_move_at',
                  'status', 'winner', 'moves', 'white_rating', 'white_rating_diff',
                  'white', 'black_rating', 'black_rating_diff', 'black', "opening",
                  'initial_time', 'increment', 'total_time']
      
  def to_internal_value(self, data):
    internal_value = {"opening": data.pop("opening")}
    del data["pgn"]
    white_data = data.get("players")["white"]
    internal_value["white"] = white_data["user"]
    internal_value["white_rating"] = white_data["rating"]
    internal_value["white_rating_diff"] = white_data["ratingDiff"]
      
    black_data = data.pop("players")["black"]
    internal_value["black"] = black_data["user"]
    internal_value["black_rating"] = black_data["rating"]
    internal_value["black_rating_diff"] = black_data["ratingDiff"]
      
    internal_value["created_at"] = data.pop("createdAt")
    internal_value["last_move_at"] = data.pop("lastMoveAt")
      
    internal_value["initial_time"] = data.get("clock").get("initial")
    internal_value["increment"] = data.get("clock").get("increment")
    internal_value["total_time"] = data.pop("clock").get("totalTime")
      
    return {**internal_value, **data}

    def create(self, validated_data):
        white = validated_data.pop("white")
        white_user, is_created = User.objects.get_or_create(id=white["id"], name=white["name"])
        black = validated_data.pop("black")
        black_user, is_created = User.objects.get_or_create(id=black["id"], name=black["name"])

        opening = validated_data.pop("opening")
        opening_model, is_created = Opening.objects.get_or_create(name=opening["name"], eco=opening["eco"])
        return Game.objects.get_or_create(white=white_user, black=black_user, opening=opening_model, **validated_data)[0]

I have two problems :

The first one is that I am new to django and DRF so I don’t know if I am following good practices here.
The second one is that sometimes the json I receive contains additional fields, like:

"players":{
    "white":{
        "user":{
            "name":"player1",
            "id":"player1",
            **"moderator": True**
        }
    }
}

and my deserialization process fails because it contains an unexpected field.

How can I handle it ?

Can someone give me a clue ?
The question I am asking myself is : should I use the “to_internal_value” method to update the data fetched before saving it into the db ?
Or should I use another method ?

Just a side note here: The Django Rest Framework is a third-party package, not part of Django itself.
They have their own support channels identified at Home - Django REST framework

Yes, there are some people here who do try to help with DRF questions, but you may get answers more quickly through the official channels.

1 Like