How to calculate current day streak in Django?

Question:

I would like to calculate every time a ClassAttempt with a status "completed" is created or updated.

class ClassAttempt(models.Model):
    user = models.ForeignKey(to=User,on_delete= models.PROTECT, null=True)
    related_class = models.ForeignKey(to=Class, related_name='attempt', on_delete= models.PROTECT, null=True)
    collected_xp = models.IntegerField(null=True, blank=True, default=0)
    status = models.CharField(
        verbose_name='status',
        max_length=20,
        choices=(
            ('no-completed', 'no-completed'),
            ('completed', 'completed'),
        ),
        default='no-completed'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
Asked By: Riccardo

||

Answers:

I’m not sure to understand your issue but a post_save signal (https://docs.djangoproject.com/en/4.1/ref/signals/#post-save) allows you to run some python code every time the .save() method is called on a certain model. (Aslo works on model.objects.create())
You could check the status of the saved instance and increase some kind of counter within the receiver of the signal. (although it is unclear from your post where this counter would be stored)

Edit:

@receiver(post_save, sender=ClassAttempt, dispatch_uid="post_save_class_attempt_streak_handler")
def post_save_class_attempt_streak_handler(sender, instance: ClassAttempt, **kargs):
    user: User = instance.user
    last_attempt: Optional[ClassAttempt] = ClassAttempt.objects.filter(user=instance.user).exclude(id=instance.id).order_by("-updated_at").first()
    if not last_attempt:
        # first attempt
        user.streak = 1
        user.save()
        return
    today: date = timezone.now().date()
    last_attempt_date = last_attempt.updated_at.date()
    if last_attempt_date == today - timedelta(days=1):
        # last attempt was yesterday
        user.streak += 1
        user.save()
    elif last_attempt_date != today:
        # last attempt was before yesterday
        user.streak = 1
        user.save()
Answered By: Alombaros

Thank you so much, I have an other question as here is where I’m keeping track of used day streak and no User model, how to do that exactly.
So the receiver I know is being triggered every time the ClassAttempt is updated or created but we have to keep track on a different model.

class UserLeaderboardTracking(models.Model):

    user = models.ForeignKey(to=User, related_name='user_leaderboard', on_delete=models.CASCADE, null=True)
    xp_value = models.PositiveIntegerField(null=True, blank=True)
    day_streak = models.PositiveIntegerField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"id : {self.pk} Username : {self.user.username}"
Answered By: Riccardo

This should work I guess I will confirm after some test.

    def post_save_class_attempt_streak_handler(sender, instance: ClassAttempt, **kargs):
        user: User = instance.user
        last_attempt: Optional[ClassAttempt] = ClassAttempt.objects.filter(user=instance.user).exclude(
            id=instance.id).order_by("-updated_at").first()
        if not last_attempt:
            # first attempt
            UserStatisticStatus.day_streak = 1
            user.save()
            return
        today: date = timezone.now().date()
        last_attempt_date = last_attempt.updated_at.date()
        if last_attempt_date == today - timedelta(days=1):
            # last attempt was yesterday
            UserStatisticStatus.day_streak += 1
            user.save()
        elif last_attempt_date != today:
            # last attempt was before yesterday
            UserStatisticStatus.day_streak = 1
            user.save()
Answered By: Riccardo

This is working for me

@receiver(post_save, sender=ClassAttempt)
    def post_save_class_attempt_streak_handler(sender,  instance, *args, **kwargs):
        last_attempt_updated = ClassAttempt.objects.filter(user=instance.user).exclude(id=instance.id).order_by("-updated_at").first()
        last_attempt_created = ClassAttempt.objects.filter(user=instance.user, status='completed', id=instance.id).first()

        if not last_attempt_updated:
            # first attempt
            UserStatisticStatus.objects.update(day_streak=1)
            return
        today: date = timezone.now().date()
        last_attempt_date_upd = last_attempt_updated.updated_at.date()


        if last_attempt_date_upd == today - timedelta(days=1) and last_attempt_created:

            # last attempt was yesterday
            current_streak = UserStatisticStatus.objects.filter(user=instance.user).first().day_streak
            UserStatisticStatus.objects.update(day_streak=current_streak + 1)


        elif last_attempt_date_upd != today:
            # last attempt was before yesterday
            UserStatisticStatus.objects.update(day_streak=1)
Answered By: Riccardo
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.