Closed davidcr01 closed 1 year ago
The model to store the friend relationships is the following:
class FriendList(models.Model):
sender = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='friend_requests_sent')
receiver = models.ForeignKey(Player, on_delete=models.CASCADE, related_name='friend_requests_received')
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=['sender', 'receiver'], name='unique_friendship') ]
def clean(self):
if self.sender == self.receiver:
raise ValidationError('You can not be friend of yourself.')
if FriendList.objects.filter(sender=self.receiver, receiver=self.sender).exists():
raise ValidationError('This friend relation already exists.')
def __str__(self):
return f"{self.sender.user.username} - {self.receiver.user.username}"
Notice that:
The serializer has been a little modified compared to the rest. It only returns the information of the friends and excludes the information of the player who is making the request.
class FriendListSerializer(serializers.ModelSerializer):
friend = serializers.SerializerMethodField()
class Meta:
model = FriendList
fields = ['friend']
def get_friend(self, obj):
request = self.context.get('request')
player = request.user.player
if obj.sender == player:
friend = obj.receiver
else:
friend = obj.sender
return {'username': friend.user.username, 'id': friend.user.id}
The view follows the same line of the previous serializer. We check if the player who makes the request is actually a player, and we filter the information either by sender or receiver using the Q
function that allows complex queries. As we are only interested in the list of the friends, we only define the list
method:
class FriendListViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = FriendListSerializer
permission_classes = [permissions.IsAuthenticated]
def list(self, request, *args, **kwargs):
player = getattr(request.user, 'player', None)
if not player:
return Response({'error': 'Player not found'}, status=404)
queryset = FriendList.objects.filter(Q(sender=player) | Q(receiver=player))
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
With this, if we use a token of an administrator:
If we use the token of player
:
If we use the token of player3
, who is a friend of player
:
The mockup for this view is the following:
In the frontend, a new page friendlist
has been generated. This page is loaded when the user clicks on the 1vs1
button of the main page.
The HTML code is divided into two parts: the ion-toolbar to change the view and display the current friends or the friend request; and the cards containing the friend username and the related actions: play, view profile and delete friend:
<ion-toolbar>
<ion-segment mode="ios" value="friends">
<ion-segment-button value="friends">
<ion-label>Friends</ion-label>
</ion-segment-button>
<ion-segment-button value="requests">
<ion-label>Requests</ion-label>
</ion-segment-button>
</ion-segment>
</ion-toolbar>
</ion-header>
<ion-content>
<div *ngIf="friendList && friendList.length > 0">
<ion-card *ngFor="let friend of friendList">
<ion-card-header>
{{ friend.username }}
</ion-card-header>
<ion-card-content>
<ion-list>
<ion-item>
<ion-icon name="people" slot="start"></ion-icon>
<ion-label>Play Wordle</ion-label>
<ion-button fill="clear" (click)="playWithFriend(friend.id)">
<ion-icon name="play" slot="icon-only"></ion-icon>
</ion-button>
</ion-item>
<ion-item>
<ion-icon name="information-circle" slot="start"></ion-icon>
<ion-label>View profile</ion-label>
<ion-button fill="clear" (click)="viewFriendInfo(friend.id)">
<ion-icon name="information-circle-outline" slot="icon-only"></ion-icon>
</ion-button>
</ion-item>
<ion-item>
<ion-icon name="trash" slot="start"></ion-icon>
<ion-label>Delete friend</ion-label>
<ion-button fill="clear" (click)="removeFriend(friend.id)">
<ion-icon name="close" slot="icon-only"></ion-icon>
</ion-button>
</ion-item>
</ion-list>
</ion-card-content>
</ion-card>
</div>
About the logic of the page, is necessary to fetch the friends using the API, using the endpoint defined in the previous comment. To do this, a new method in the apiService has been added:
async getFriendlist(): Promise<any[]> {
let url = `${this.baseURL}/api/friendlist/`;
const accessToken = this.storageService.getAccessToken();
if (!accessToken) {
return throwError('Access token not found') as any;
}
const decryptedToken = this.encryptionService.decryptData(await accessToken);
const headers = new HttpHeaders({
Authorization: `Token ${decryptedToken}`,
'Content-Type': 'application/json'
});
return this.http.get<any[]>(url, { headers }).toPromise();
}
And this method is used in the ts
file of the friendlist page:
export class FriendlistPage implements OnInit {
friendList: any[];
constructor(private apiService: ApiService, private toastController: ToastController) {}
ngOnInit() {
this.loadFriendList();
}
async loadFriendList() {
try {
const friends = await this.apiService.getFriendlist() as any;
this.friendList = friends.map(item => item.friend);
console.log(this.friendList)
} catch (error) {
console.error('Error loading friend list:', error);
const toast = await this.toastController.create({
message: 'Error loading friend list',
duration: 2000,
color: 'danger'
});
toast.present();
}
}
}
Notice that we have to map the items of the returned information. In this case, we have to get the 'friend' items of the list, and save them in the friendList
array. If an error is generated, a toast is displayed.
In case of the logged player has friends added, the page looks like this:
In case of the player does not have friends yet, this information is displayed:
Description
Is necessary to implement a new page to display the friends of the player.
Tasks