0

I am trying to implement a trivia game backend in Django. I have several models but I will mention two of them which are Score and Player.

Here is the important part of Player:

class Player(BaseModel, AbstractUser):
    overall_score = models.PositiveIntegerField(default=0)

    class Meta:
        ordering = ['-overall_score']

and here is the important part of Score:

class Score(BaseModel):
    score = models.PositiveSmallIntegerField()
    player = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='scores')

    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        self.player.overall_score += self.score

There are other fields in Score and Player but I did not mention them.

What I am trying to do is letting the player to solve a quiz, and after saving the single quiz score to the database, I want to calculate the all-the-time overall score which is the sum of all of his scores. It is just a sum, not an average.

Is my implementation of save method correct?

I am afraid that the saving procedure will fail for any reason, but the overall_score would be already updated. Also, I am afraid that if I used aggregation, it will not be an efficient way because every time the user will access the leaderboard, the sum will be calculated which can be avoided by saving the sum so we avoid summing all of the scores every time, am I right?

EDIT 1: I tried to implement with signals as suggested by Doodle. However, the code is not working and giving me an error which is:

TypeError: unsupported operand type(s) for +=: 'int' and 'DeferredAttribute'

Here is my code of signals.py:

from django.db.models.signals import post_save
from django.dispatch.dispatcher import receiver
from .models import Score


@receiver(post_save, sender=Score)
def create_score(sender, instance, **kwargs):
    instance.player.overall_score += instance.score
    instance.player.save()

I tried to fix the error in several ways but I couldn't.

Ambitions
  • 2,369
  • 3
  • 13
  • 24
  • 1
    have a look https://docs.djangoproject.com/en/2.2/topics/signals/ you will get some idea, comment if still need some explanation – Nothing Jul 24 '19 at 15:09
  • @Doodle I edited my question with my implementation with signals as you have suggested. However, it is not working. – Ambitions Jul 24 '19 at 17:14

1 Answers1

1

As is your save() method won't save the Score object or the player.overall_score

To do that it would need to be:

def save(self, *args, **kwargs):
    super().save(*args, **kwargs)
    self.player.overall_score += self.score
    self.player.save()

Keep in mind if the score object is ever updated that it will keep adding the score to the player's total. For example

>>> player = Player()
>>> player.save()
>>> player.overall_score()
0
>>> score = Score(player=player, score=10, other_field='hey')
>>> score.save()
>>> player.overall_score()
10
>>> score.other_field = 'you'
>>> score.save()
>>> player.overall_score()
20

The best design may be different depending on how scores are to be saved. Can a player complete a quiz multiple times and will the new scores keep getting added to their total? Or can they only complete a quiz once? Can they complete multiple times but only have one score for each quiz?

bdoubleu
  • 5,568
  • 2
  • 20
  • 53
  • The user can not complete the same quiz multiple times. Once the score instance is created, there is no way to modify it. Do you think that `signals` is a better solution than overriding the `save` method? – Ambitions Jul 24 '19 at 17:17
  • 1
    Personally, I don't use signals unless I absolutely have to because the code is broken up over multiple files which makes it harder to understand. Overriding the save method is less code, easier to read, easier to understand and easier to test. – bdoubleu Jul 24 '19 at 17:21