activeToday: activity recorded today (Tehran date). lost: length of a streak that broke (last active day before yesterday), so the app can show it frozen. Same rule as effective_current_streak; the weekly report keeps its own two-field streak serializer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
78 lines
3.3 KiB
Python
78 lines
3.3 KiB
Python
"""
|
||
استریک: روزهای پیاپیِ فعالیت.
|
||
|
||
تا ۲۰۲۶-۰۹-۲۴ هیچجای کد StreakRecord یا ActiveDay نمینوشت — فقط
|
||
seed_test_data. یعنی «روز پیاپی» روی صفحهی خانه برای هر کاربرِ واقعی برای
|
||
همیشه صفر بود و فقط کاربرهای دمو عدد داشتند.
|
||
|
||
«فعالیت» یعنی پاسخ به سوال یا تمام کردنِ درس/جزیره. هر روز (به وقتِ تهران،
|
||
TIME_ZONE) یک بار شمرده میشود؛ صدا زدنِ مکرر در یک روز بیاثر است.
|
||
"""
|
||
from datetime import timedelta
|
||
|
||
from django.db import transaction
|
||
from django.utils import timezone
|
||
|
||
from .models import ActiveDay, StreakRecord
|
||
|
||
|
||
def record_activity(user_id, today=None):
|
||
"""امروز را روزِ فعال ثبت میکند و استریک را جلو میبرد. idempotent در یک روز."""
|
||
today = today or timezone.localdate()
|
||
with transaction.atomic():
|
||
StreakRecord.objects.get_or_create(user_id=user_id)
|
||
streak = StreakRecord.objects.select_for_update().get(user_id=user_id)
|
||
|
||
last = (
|
||
ActiveDay.objects.filter(streak=streak)
|
||
.order_by('-date').values_list('date', flat=True).first()
|
||
)
|
||
if last == today:
|
||
return streak
|
||
|
||
if last == today - timedelta(days=1):
|
||
streak.current_streak += 1
|
||
else:
|
||
streak.current_streak = 1
|
||
streak.longest_streak = max(streak.longest_streak, streak.current_streak)
|
||
streak.save(update_fields=['current_streak', 'longest_streak'])
|
||
ActiveDay.objects.get_or_create(streak=streak, date=today)
|
||
return streak
|
||
|
||
|
||
def effective_current_streak(streak, today=None):
|
||
"""
|
||
استریکی که الان باید نشان داده شود.
|
||
|
||
current_streak فقط در لحظهی فعالیت بهروز میشود؛ کاربری که دو روز
|
||
نیامده هنوز عددِ قبلی را در دیتابیس دارد. اگر آخرین روزِ فعال نه امروز
|
||
است نه دیروز، زنجیره شکسته و عددِ واقعی صفر است.
|
||
"""
|
||
if streak is None:
|
||
return 0
|
||
today = today or timezone.localdate()
|
||
last = (
|
||
streak.active_days.order_by('-date').values_list('date', flat=True).first()
|
||
)
|
||
if last is None or last < today - timedelta(days=1):
|
||
return 0
|
||
return streak.current_streak
|
||
|
||
|
||
def streak_state(streak, today=None):
|
||
"""
|
||
وضعیتِ زنجیره برای نمایش: (current, active_today, lost).
|
||
|
||
lost طولِ زنجیرهای است که شکسته (آخرین روزِ فعال پیش از دیروز)؛ current_streakِ دیتابیس تا فعالیتِ بعدی
|
||
همان عددِ قبلی را نگه میدارد (record_activity آن را به ۱ برمیگرداند)، پس همین عدد است.
|
||
"""
|
||
if streak is None:
|
||
return 0, False, 0
|
||
today = today or timezone.localdate()
|
||
last = streak.active_days.order_by('-date').values_list('date', flat=True).first()
|
||
if last is None:
|
||
return 0, False, 0
|
||
if last < today - timedelta(days=1):
|
||
return 0, False, streak.current_streak
|
||
return streak.current_streak, last == today, 0
|