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)
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()
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}"
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()
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)
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)
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()
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}"
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()
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)