davidcr01 / WordlePlus

Repository to store all the documentation, files and structure of my Final Degree Project (TFG in Spanish). The main goal is to develop, as full-stack web developer, a remodel of the Wordle game, including more features and functionalities using Ionic, Django REST Api and PostgreSQL.
1 stars 0 forks source link

Hide users information when a player list other players (S1) #7

Closed davidcr01 closed 1 year ago

davidcr01 commented 1 year ago

It is necessary to hide the users' information when the players are listed. This information has to be hidden from the players and has to be in line with their permissions, as they can not see the users' private information (first_name, last_name, email... etc)

davidcr01 commented 1 year ago

Update Report

Development

There are multiple ways to perform this. As we are not interested in every field of the user when fetching the player (is_staff, first_name, last_name... etc) we can edit the PlayerSerializer and insert into it a condition.

class PlayerSerializer(serializers.ModelSerializer):
    # As a player is related to an user, it needs its seralizer
    user = serializers.SerializerMethodField()

    class Meta:
        model = Player
        fields = ('user', 'wins', 'wins_pvp', 'wins_tournament', 'xp')

    def get_user(self, obj):
        if self.context['request'].method == 'GET' and 'pk' not in self.context['view'].kwargs:
            # If we are getting all the players, it returns only the ID and username
             return {'id': obj.user.id, 'username': obj.user.username}
        else:
            # If we are getting only a player, it returns the UserInfoSerializer data
            user_serializer = UserInfoSerializer(obj.user)
            return user_serializer.data

Here, we specify that:

This new serializer is needed to complement the modification of the PlayerSerializer. Basically, it is the same as CustomUserSerializer, but it returns fewer fields.

class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomUser
        fields = ('id', 'username', 'last_login', 'date_joined')

Testing

Now, if we fetch the information of the player:

Result:

{
  "user": {
    "id": 13,
    "username": "testCreate",
    "last_login": "2023-05-30T10:02:12.345953Z",
    "date_joined": "2023-05-30T10:02:12.345980Z"
  },
  "wins": 0,
  "wins_pvp": 0,
  "wins_tournament": 0,
  "xp": 0
}

Result:

{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "user": {
        "id": 13,
        "username": "testCreate"
      },
      "wins": 0,
      "wins_pvp": 0,
      "wins_tournament": 0,
      "xp": 0
    },
    {
      "user": {
        "id": 19,
        "username": "notstaff2"
      },
      "wins": 0,
      "wins_pvp": 0,
      "wins_tournament": 0,
      "xp": 0
    }
  ]
}
davidcr01 commented 1 year ago

Update Report

Getting player

The problem has been solved by creating a new serializer UserInfoSerializer, which returns less information than CustomUserSerializer. This new serializer represents the needed information for the player that may be useful. Besides, this information can be again filtered in the frontend if needed.

class UserInfoSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomUser
        fields = ('id', 'username', 'last_login', 'date_joined')

The serializer is the same as CustomUserSerializer but returns the ID, username, last_login and date_joined of the user. This new serializer is used by another new serializer, PlayerInfoSerializer, which returns this information related to the players. This new serializer is needed to not alter the creation of the player, which needs different fields other than the fields we need when we get a player.

class PlayerInfoSerializer(serializers.ModelSerializer):
    # As a player is related to an user, it needs its seralizer
    user = serializers.SerializerMethodField()

    class Meta:
        model = Player
        fields = ('id', 'user', 'wins', 'wins_pvp', 'wins_tournament', 'xp')

    def get_user(self, obj):
        if self.context['request'].method == 'GET' and 'pk' not in self.context['view'].kwargs:
            # If we are getting all the players, it returns only the ID and username
             return {'id': obj.user.id, 'username': obj.user.username}
        else:
            # If we are getting only a player, it returns the UserInfoSerializer data
            user_serializer = UserInfoSerializer(obj.user)
            return user_serializer.data

Notice that:

To select the serializer when managing the players is necessary to change the Players' viewset.

def get_serializer_class(self):
        if self.action == 'create':
            # Use PlayerSerializer for creating a player
            return PlayerSerializer
        else:
            # Use PlayerInfoSerializer for other actions
            return PlayerInfoSerializer

This code snippet is added to the PlayerViewSet, it specifies the serializer depending on the action is performed.

By doing this, if we get the info of a player: GET, http://localhost/api/players/13

{
  "user": {
    "id": 13,
    "username": "testCreate",
    "last_login": "2023-05-30T10:02:12.345953Z",
    "date_joined": "2023-05-30T10:02:12.345980Z"
  },
  "wins": 0,
  "wins_pvp": 0,
  "wins_tournament": 0,
  "xp": 0
}

If we get all the players:

`GET, http://localhost/api/players/`

{ "user": { "id": 13, "username": "testCreate" }, "wins": 0, "wins_pvp": 0, "wins_tournament": 0, "xp": 0 }


Notice that the ID is returned. This information may be useful if we need the whole information of the user at another moment.
davidcr01 commented 1 year ago

Update Report

Player creation bug

In this development, a new bug was found while creating a new player using the POST request over the api/players/ path. This path wasn't working properly, and the player was not being created.

To solve this, the create method of the PlayerSerializer has been overwritten.

class PlayerSerializer(serializers.ModelSerializer):
    user = CustomUserSerializer()

    class Meta:
        model = Player
        fields = ('user', 'wins', 'wins_pvp', 'wins_tournament', 'xp')

    def create(self, validated_data):
        user_data = validated_data.pop('user', None)
        user = None

        if user_data:
            # Create the user only if user_data is provided
            user = CustomUser.objects.create_user(**user_data)

        player = Player.objects.create(user=user, **validated_data)
        return player

This new method specifies that we need to extract the user information from the body of the request by using the pop method, and create the user with that information if is valid. After creating the user, we create the related player.

With this improvement, we can create a player and a user simultaneously like this:

Body:

{
  "user": {
    "username": "john6",
    "password": "test123test",
    "email": "john@gmail.com"
  },
  "wins": 10,
  "wins_pvp": 5,
  "wins_tournament": 2,
  "xp": 100
}

And the result is a 201 code (created) and this new user:

{
  "user": {
    "id": 32,
    "is_superuser": false,
    "username": "john6",
    "first_name": "",
    "last_name": "",
    "email": "john@gmail.com",
    "is_staff": false,
    "is_active": true,
    "avatar": null,
    "last_login": "2023-06-01T11:29:27.852559Z",
    "date_joined": "2023-06-01T11:29:27.852573Z",
    "groups": [],
    "user_permissions": []
  },
  "wins": 10,
  "wins_pvp": 5,
  "wins_tournament": 2,
  "xp": 100
}