first commit

This commit is contained in:
vanalmsick 2025-09-27 18:19:06 +01:00
commit e7f627801f
152 changed files with 35352 additions and 0 deletions

View file

@ -0,0 +1,336 @@
import datetime, random
from openai import OpenAI
from django.core.cache import cache
from django.conf import settings
from django.apps import apps
from django.template.loader import render_to_string
from workout_challenge.celery import app
from django.db.models import Sum, Count, Q
from django.db.models.functions import TruncDate, TruncDay
from .multipurpose import send_email
from competition.stats import get_competition_stats
@app.task()
def welcome_email(user_pk):
"""Welcome email for new users."""
CustomUser = apps.get_model('custom_user', 'CustomUser')
user_obj = CustomUser.objects.get(pk=user_pk)
email_subject = 'Welcome to the Workout Challenge!'
email_body = render_to_string(
"email_welcome.html",
{
'first_name': user_obj.first_name,
'MAIN_HOST': settings.MAIN_HOST,
'EMAIL_REPLY_TO': settings.EMAIL_REPLY_TO[0] if settings.EMAIL_REPLY_TO is not None else settings.EMAIL_FROM,
'link_strava_note': user_obj.strava_refresh_token is None or user_obj.strava_refresh_token == '',
}
)
if settings.DEBUG:
with open('tmp_email.html', 'w') as file:
file.write(email_body)
send_email(subject=email_subject, body=email_body, to_email=user_obj.email)
return {'pk': user_obj.pk, 'username': user_obj.username, 'email': user_obj.email}
@app.task()
def send_all_log_workouts_email():
print("Scheduling log workout emails...")
CustomUser = apps.get_model('custom_user', 'CustomUser')
user_lst = CustomUser.objects.filter(
Q(my_competitions__start_date__lte=datetime.date.today()) &
Q(my_competitions__end_date__gte=datetime.date.today())
).order_by('pk')
task_log = []
if len(user_lst) > 0:
eta_steps = max(min((60 * 60) // len(user_lst), 60), 10)
eta = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=10)
for user_obj in user_lst:
result = log_workouts_email.apply_async(args=[user_obj.pk], eta=eta)
task_log.append({'pk': user_obj.pk, 'username': user_obj.username, 'email': user_obj.email, 'task_id': result.task_id, 'eta': eta.isoformat()})
eta += datetime.timedelta(seconds=eta_steps)
return task_log
@app.task()
def log_workouts_email(user_pk):
"""Email reminder for users to please log their workouts."""
CustomUser = apps.get_model('custom_user', 'CustomUser')
user_obj = CustomUser.objects.get(pk=user_pk)
workout_obj_lst = user_obj.workout_set.order_by('-start_datetime')[:3]
email_subject = 'Workout Challenge - Log Your Workouts!'
email_body = render_to_string(
"email_log_workouts.html",
{
'first_name': user_obj.first_name,
'last_workouts': workout_obj_lst,
'link_strava_note': user_obj.strava_refresh_token is None or user_obj.strava_refresh_token == '',
'MAIN_HOST': settings.MAIN_HOST,
'EMAIL_REPLY_TO': settings.EMAIL_REPLY_TO[0] if settings.EMAIL_REPLY_TO is not None else settings.EMAIL_FROM,
}
)
if settings.DEBUG:
with open('tmp_email.html', 'w') as file:
file.write(email_body)
send_email(subject=email_subject, body=email_body, to_email=user_obj.email)
return {'pk': user_obj.pk, 'username': user_obj.username, 'email': user_obj.email}
@app.task()
def send_all_competition_start_email():
print("Scheduling competition start emails...")
Competition = apps.get_model('competition', 'Competition')
competition_lst = Competition.objects.filter(start_date=datetime.date.today() + datetime.timedelta(days=1)).order_by('pk')
task_log = []
for i, competition_obj in enumerate(competition_lst):
eta = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=10) + datetime.timedelta(minutes=(15 * i))
user_lst = competition_obj.user.all().order_by('pk')
if len(user_lst) > 0:
eta_steps = max(min((60 * 60) // len(user_lst), 60), 10)
for user_obj in user_lst:
result = competition_start_email.apply_async(args=[competition_obj.pk, user_obj.pk], eta=eta)
task_log.append({'user_pk': user_obj.pk, 'username': user_obj.username, 'email': user_obj.email, 'competition_pk': competition_obj.pk, 'competition_name': competition_obj.name, 'task_id': result.task_id, 'eta': eta.isoformat()})
eta += datetime.timedelta(seconds=eta_steps)
return task_log
@app.task()
def competition_start_email(competition_pk, user_pk):
"""Email for competition start tomorrow."""
Competition = apps.get_model('competition', 'Competition')
competition_obj = Competition.objects.get(pk=competition_pk)
goal_objs = competition_obj.activitygoal_set.all()
CustomUser = apps.get_model('custom_user', 'CustomUser')
user_obj = CustomUser.objects.get(pk=user_pk)
email_subject = 'Workout Challenge - READY, SET, GO!'
email_body = render_to_string(
"email_competition_start.html",
{
'first_name': user_obj.first_name,
'MAIN_HOST': settings.MAIN_HOST,
'competition': competition_obj,
'goals': goal_objs,
'EMAIL_REPLY_TO': settings.EMAIL_REPLY_TO[0] if settings.EMAIL_REPLY_TO is not None else settings.EMAIL_FROM,
'goal_equalizer_note': user_obj.scaling_kcal == 1 and user_obj.scaling_distance == 1,
}
)
if settings.DEBUG:
with open('tmp_email.html', 'w') as file:
file.write(email_body)
send_email(subject=email_subject, body=email_body, to_email=user_obj.email)
return ({'pk': user_obj.pk, 'username': user_obj.username, 'email': user_obj.email})
@app.task()
def send_all_leaderboard_emails():
print("Scheduling leaderboard emails...")
CustomUser = apps.get_model('custom_user', 'CustomUser')
user_lst = CustomUser.objects.filter(my_competitions__start_date__lt=datetime.date.today(), my_competitions__end_date__gte=datetime.date.today()).order_by('pk')
task_log = []
if len(user_lst) > 0:
eta_steps = max(min((60 * 60) // len(user_lst), 60), 10)
eta = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=10)
for user_obj in user_lst:
result = leaderboard_email.apply_async(args=[user_obj.pk], eta=eta)
task_log.append({'pk': user_obj.pk, 'username': user_obj.username, 'email': user_obj.email, 'task_id': result.task_id, 'eta': eta.isoformat()})
eta += datetime.timedelta(seconds=eta_steps)
return task_log
@app.task()
def leaderboard_email(user_pk):
"""Email to send users their leaderboard."""
CustomUser = apps.get_model('custom_user', 'CustomUser')
user_obj = CustomUser.objects.get(pk=user_pk)
competition_all_data = []
competition_7d_data = []
for competition in user_obj.my_competitions.filter(start_date__lte=datetime.date.today(), end_date__gte=datetime.date.today()).order_by('-start_date'):
competition_all_stats = get_competition_stats(competition.pk)
competition_all_data.append({
'competition': competition_all_stats['competition'],
'leaderboard': competition_all_stats['leaderboard'],
})
competition_7d_stats = get_competition_stats(competition.pk, last_seven_days=True)
competition_7d_data.append({
'competition': competition_7d_stats['competition'],
'leaderboard': competition_7d_stats['leaderboard'],
})
email_subject = 'Workout Challenge - Your Spot on the Leaderboard!'
email_body = render_to_string(
"email_leaderboard.html",
{
'first_name': user_obj.first_name,
'MAIN_HOST': settings.MAIN_HOST,
'competitions_all': competition_all_data,
'competitions_7d': competition_7d_data,
'EMAIL_REPLY_TO': settings.EMAIL_REPLY_TO[0] if settings.EMAIL_REPLY_TO is not None else settings.EMAIL_FROM,
'goal_equalizer_note': user_obj.scaling_kcal == 1 and user_obj.scaling_distance == 1,
}
)
if settings.DEBUG:
with open('tmp_email.html', 'w') as file:
file.write(email_body)
send_email(subject=email_subject, body=email_body, to_email=user_obj.email)
return ({'pk': user_obj.pk, 'username': user_obj.username, 'email': user_obj.email})
@app.task()
def send_all_weekly_emails():
print("Scheduling weekly emails...")
CustomUser = apps.get_model('custom_user', 'CustomUser')
user_lst = CustomUser.objects.filter(email_mid_week=True).order_by('pk')
task_log = []
if len(user_lst) > 0:
eta_steps = max(min((60 * 60) // len(user_lst), 60), 10)
eta = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=10)
for user_obj in user_lst:
result = weekly_email.apply_async(args=[user_obj.pk], eta=eta)
task_log.append({'pk': user_obj.pk, 'username': user_obj.username, 'email': user_obj.email, 'task_id': result.task_id, 'eta': eta.isoformat()})
eta += datetime.timedelta(seconds=eta_steps)
return task_log
def openai_quote():
if settings.OPENAI_API_KEY is None:
return None
todays_ai_quote = cache.get('todays_ai_quote', None)
if todays_ai_quote is None:
client = OpenAI(api_key=settings.OPENAI_API_KEY)
options = ["fitness", "health", "nutritional", "workout"]
selection = random.choice(options)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "user", "content": f"Tell me a one sentence {selection} fact."},
],
temperature=1.0,
top_p=1.0
)
todays_ai_quote = response.choices[0].message.content
cache.set('todays_ai_quote', todays_ai_quote, 60 * 60 * 20)
print('Todays AI Quote:', todays_ai_quote)
return todays_ai_quote
def calendar_stats(user_pk):
today = datetime.date.today()
# Step 1: Find next Sunday (or today if Sunday)
days_until_sunday = (6 - today.weekday()) % 7 # weekday(): Monday=0, Sunday=6
next_sunday = today + datetime.timedelta(days=days_until_sunday)
# Step 2: Create list of days going back 5 weeks (inclusive)
dates_list = [next_sunday - datetime.timedelta(days=i) for i in range(34, -1, -1)]
Workout = apps.get_model('workouts', 'Workout')
all_workouts = Workout.objects.filter(user=user_pk).annotate(date=TruncDay('start_datetime')).values('date').annotate(count=Count('id'))
workouts_by_date = {row['date'].date().isoformat(): row['count'] for row in all_workouts}
workouts_by_week = {(settings.TIME_ZONE_OBJ.localize(datetime.datetime.combine(next_sunday, datetime.datetime.min.time())) - row['date']).days // 7: True for row in all_workouts}
streak_weeks = 0
streak_i = 0
streak_true = True
while streak_true:
if workouts_by_week.get(streak_i, False):
streak_weeks += 1
elif streak_i == 0:
pass
else:
streak_true = False
streak_i += 1
return_calendar = []
for date in dates_list:
workout_num = workouts_by_date.get(date.isoformat(), 0)
return_calendar.append({
'datetime': date,
'day': date.day,
'workout_num': workout_num,
'color': '#FFFFFF' if date == today or workout_num > 0 else ('#e5e5e5' if date > today else '#000000'),
'background_color': '#7F1D1D' if date == today else ('#075971' if workout_num > 0 else '#FFFFFF')
})
return streak_weeks, [return_calendar[i:i+7] for i in range(0, len(return_calendar), 7)]
@app.task()
def weekly_email(user_pk):
"""Email to send users their weekly update."""
CustomUser = apps.get_model('custom_user', 'CustomUser')
user_obj = CustomUser.objects.get(pk=user_pk)
Workout = apps.get_model('workouts', 'Workout')
workout_7day_stats = Workout.objects.filter(
user=user_obj,
start_datetime__gte=datetime.date.today() - datetime.timedelta(days=7)
).annotate(
day=TruncDate('start_datetime')
).aggregate(
total_duration=Sum('duration'),
total_distance=Sum('distance'),
distinct_days=Count('day', distinct=True)
)
week_streak, calendar = calendar_stats(user_pk)
todays_ai_quote = openai_quote()
email_subject = 'Workout Challenge - Your Weekly Update!'
recorded_total_duration = 0 if workout_7day_stats["total_duration"] is None else (workout_7day_stats["total_duration"].seconds // 60)
recorded_total_distance = 0 if workout_7day_stats["total_distance"] is None else workout_7day_stats["total_distance"]
recorded_distinct_days = 0 if workout_7day_stats["distinct_days"] is None else workout_7day_stats["distinct_days"]
email_body = render_to_string(
"email_weekly.html",
{
'first_name': user_obj.first_name,
'MAIN_HOST': settings.MAIN_HOST,
'calendar': calendar,
'week_streak': week_streak,
'goals': {
'active_days': None if user_obj.goal_active_days is None or user_obj.goal_active_days == '' else {'recorded': recorded_distinct_days,'target': user_obj.goal_active_days, 'percent': min(1, recorded_distinct_days / user_obj.goal_active_days) * 100, 'percent_vml': int(min(1, recorded_distinct_days / user_obj.goal_active_days) * 100 * 2.5)},
'distance': None if user_obj.goal_distance is None or user_obj.goal_distance == '' else {'recorded': recorded_total_distance,'target': user_obj.goal_distance, 'percent': min(1, recorded_total_distance / user_obj.goal_distance) * 100, 'percent_vml': int(min(1, recorded_total_distance / user_obj.goal_distance) * 100 * 2.5)},
'minutes': None if user_obj.goal_workout_minutes is None or user_obj.goal_workout_minutes == '' else {'recorded': recorded_total_duration,'target': user_obj.goal_workout_minutes, 'percent': min(1, recorded_total_duration / user_obj.goal_workout_minutes) * 100, 'percent_vml': int(min(1, recorded_total_duration / user_obj.goal_workout_minutes) * 100 * 2.5)},
},
'openai_quote': todays_ai_quote,
'EMAIL_REPLY_TO': settings.EMAIL_REPLY_TO[0] if settings.EMAIL_REPLY_TO is not None else settings.EMAIL_FROM,
}
)
if settings.DEBUG:
with open('tmp_email.html', 'w') as file:
file.write(email_body)
send_email(subject=email_subject, body=email_body, to_email=user_obj.email)
return {'pk': user_obj.pk, 'username': user_obj.username, 'email': user_obj.email}