mirror of
https://github.com/workhardbekind/workout-challenge.git
synced 2026-07-04 09:23:32 -04:00
first commit
This commit is contained in:
commit
e7f627801f
152 changed files with 35352 additions and 0 deletions
0
src-backend/competition/__init__.py
Normal file
0
src-backend/competition/__init__.py
Normal file
53
src-backend/competition/admin.py
Normal file
53
src-backend/competition/admin.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import Competition, ActivityGoal, Team, Award
|
||||
from custom_user.models import CustomUser
|
||||
|
||||
# Register your models here.
|
||||
class ActivityGoalInline(admin.TabularInline):
|
||||
"""Table of Competition ActivityGoal"""
|
||||
|
||||
model = ActivityGoal
|
||||
fk_name = "competition"
|
||||
can_delete = False
|
||||
extra = 0
|
||||
|
||||
|
||||
class AwardsInline(admin.TabularInline):
|
||||
"""Table of Awards"""
|
||||
|
||||
model = Award
|
||||
fk_name = "competition"
|
||||
can_delete = False
|
||||
extra = 0
|
||||
|
||||
|
||||
class TeamInline(admin.TabularInline):
|
||||
"""Table of Competition teams"""
|
||||
|
||||
model = Team
|
||||
fk_name = "competition"
|
||||
can_delete = False
|
||||
extra = 0
|
||||
|
||||
|
||||
|
||||
@admin.register(Competition)
|
||||
class CompetitionAdmin(admin.ModelAdmin):
|
||||
"""Admin view of Competition - the highest level e.g. Football World Cup 2024"""
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
"""Block admins form deleting a Tournament"""
|
||||
return False
|
||||
|
||||
list_display = [
|
||||
"name",
|
||||
"start_date",
|
||||
"end_date",
|
||||
]
|
||||
inlines = [
|
||||
ActivityGoalInline,
|
||||
AwardsInline,
|
||||
TeamInline,
|
||||
]
|
||||
|
||||
6
src-backend/competition/apps.py
Normal file
6
src-backend/competition/apps.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CompetitionConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'competition'
|
||||
231
src-backend/competition/models.py
Normal file
231
src-backend/competition/models.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
import time, re, random
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.core.validators import MinLengthValidator, RegexValidator
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from workouts.models import Workout, SPORT_TYPE_GROUPS, SPORT_TYPES
|
||||
from custom_user.models import CustomUser
|
||||
from .scorer import trigger_goal_change, trigger_competition_change
|
||||
|
||||
# Create your models here.
|
||||
COMPETITION_METRCIS = [
|
||||
('min', 'Time (Minutes)'),
|
||||
('num', 'Number of times (x)'),
|
||||
('kcal', 'Calories (Kcal)'),
|
||||
('km', 'Distance (Km)'),
|
||||
('kj', 'Effort (Kilojoules)'),
|
||||
]
|
||||
|
||||
POINT_REF_PERIODS = [
|
||||
('day', 'daily'),
|
||||
('week', 'weekly'),
|
||||
('month', 'monthly'),
|
||||
('year', 'yearly'),
|
||||
('competition', 'competition end'),
|
||||
]
|
||||
|
||||
|
||||
class Competition(models.Model):
|
||||
"""Competition users can compete in"""
|
||||
|
||||
owner = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=False, blank=False)
|
||||
|
||||
name = models.CharField(null=False, max_length=60)
|
||||
start_date = models.DateField(null=False)
|
||||
end_date = models.DateField(null=False)
|
||||
has_teams = models.BooleanField(default=False)
|
||||
organizer_assigns_teams = models.BooleanField(default=False)
|
||||
|
||||
join_code = models.CharField(
|
||||
blank=False,
|
||||
null=False,
|
||||
max_length=20,
|
||||
validators=[
|
||||
MinLengthValidator(10),
|
||||
RegexValidator(r'^[a-zA-Z0-9]+$', message="Only letters and numbers allowed"),
|
||||
],
|
||||
unique=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def start_date_fmt(self):
|
||||
return self.start_date.strftime("%a, %b %-d")
|
||||
|
||||
@property
|
||||
def start_date_epoch(self):
|
||||
return int(time.mktime(self.start_date.timetuple()))
|
||||
|
||||
@property
|
||||
def end_date_fmt(self):
|
||||
return self.end_date.strftime("%a, %b %-d")
|
||||
|
||||
@property
|
||||
def end_date_epoch(self):
|
||||
return int(time.mktime(self.end_date.timetuple()))
|
||||
|
||||
def __str__(self):
|
||||
"""str print-out of model entry"""
|
||||
return f"{self.name} ({self.start_date} - {self.end_date})"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
""" save initial field values to be able to detect changes """
|
||||
super().__init__(*args, **kwargs)
|
||||
self._original = self._dict()
|
||||
|
||||
#@property
|
||||
def _dict(self):
|
||||
""" dict of current fields and values - to detect changes """
|
||||
return {f.name: round(float(self.__dict__[f.attname]), 2) if isinstance(self.__dict__.get(f.attname), (Decimal, float)) else self.__dict__.get(f.attname) for f in self._meta.fields}
|
||||
|
||||
def get_changed_fields(self):
|
||||
""" check which fields have changed """
|
||||
current = self._dict()
|
||||
return {
|
||||
k: (v, current.get(k))
|
||||
for k, v in self._original.items()
|
||||
if v != current.get(k)
|
||||
}
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
""" trigger recalculation of points_capped if competition changes """
|
||||
is_create = self.pk is None
|
||||
if self.join_code == '':
|
||||
self.join_code = re.sub(r'[^a-zA-Z0-9]', '', self.name)[:8] + str(self.owner.pk).zfill(3) + str(random.randint(10_000, 99_999))
|
||||
self.join_code = self.join_code.upper()
|
||||
super().save(*args, **kwargs)
|
||||
changed = self.get_changed_fields()
|
||||
trigger_competition_change(
|
||||
instance=self,
|
||||
new=is_create,
|
||||
changes=changed
|
||||
)
|
||||
self._original = self._dict() # reset
|
||||
|
||||
# add default activity goals if new competition
|
||||
if is_create:
|
||||
ActivityGoal(name ='Exercise', competition = self, metric = 'min', goal = 150, period = 'week', max_per_day = 60, max_per_week = 240).save() # WHO recommends at least 75-150 min vigorous activity per week (capped at 4h)
|
||||
ActivityGoal(name='Move', competition=self, metric='kcal', goal=1_800, period='week', max_per_day=1_000, max_per_week=3_000).save() # 12kcal per minute
|
||||
self.owner.my_competitions.add(self) # add owner as participant
|
||||
|
||||
|
||||
class Team(models.Model):
|
||||
"""Competition teams users can join"""
|
||||
|
||||
competition = models.ForeignKey(Competition, on_delete=models.CASCADE, null=False, blank=False)
|
||||
|
||||
name = models.CharField(null=False, max_length=60)
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
#self.validate_members()
|
||||
|
||||
|
||||
# ToDo: Check if user is participant in competition he/she whats to join the team of
|
||||
#def validate_members(self):
|
||||
# if self.competition.pk not in [member.competition.pk for member in self.members]:
|
||||
# raise ValidationError({'member': 'User must have joined competition to be a team member of team.'})
|
||||
|
||||
def __str__(self):
|
||||
"""str print-out of model entry"""
|
||||
return f"{self.competition} - Team: {self.name}"
|
||||
|
||||
|
||||
class ActivityGoal(models.Model):
|
||||
"""Activity goals in Competition - user will earn points for each rule/category"""
|
||||
|
||||
competition = models.ForeignKey(Competition, on_delete=models.CASCADE, null=False, blank=False)
|
||||
|
||||
name = models.CharField(null=False, max_length=60)
|
||||
|
||||
metric = models.CharField(null=False, max_length=4, choices=COMPETITION_METRCIS)
|
||||
goal = models.DecimalField(null=False, max_digits=10, decimal_places=2)
|
||||
period = models.CharField(null=False, max_length=12, default='day', choices=POINT_REF_PERIODS)
|
||||
|
||||
count_steps_as_walks = models.BooleanField(default=True)
|
||||
|
||||
min_per_workout = models.DecimalField(null=True, blank=True, max_digits=10, decimal_places=2)
|
||||
max_per_workout = models.DecimalField(null=True, blank=True, max_digits=10, decimal_places=2)
|
||||
min_per_day = models.DecimalField(null=True, blank=True, max_digits=10, decimal_places=2)
|
||||
max_per_day = models.DecimalField(null=True, blank=True, max_digits=10, decimal_places=2)
|
||||
min_per_week = models.DecimalField(null=True, blank=True, max_digits=10, decimal_places=2)
|
||||
max_per_week = models.DecimalField(null=True, blank=True, max_digits=10, decimal_places=2)
|
||||
|
||||
def __str__(self):
|
||||
"""str print-out of model entry"""
|
||||
return f"{self.competition}: {self.name} ({self.goal} {self.metric})"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
""" save initial field values to be able to detect changes """
|
||||
super().__init__(*args, **kwargs)
|
||||
self._original = self._dict()
|
||||
|
||||
#@property
|
||||
def _dict(self):
|
||||
""" dict of current fields and values - to detect changes """
|
||||
return {f.name: round(float(self.__dict__[f.attname]), 2) if isinstance(self.__dict__.get(f.attname), (Decimal, float)) else self.__dict__.get(f.attname) for f in self._meta.fields}
|
||||
|
||||
def get_changed_fields(self):
|
||||
""" check which fields have changed """
|
||||
current = self._dict()
|
||||
return {
|
||||
k: (v, current.get(k))
|
||||
for k, v in self._original.items()
|
||||
if v != current.get(k)
|
||||
}
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
""" trigger recalculation of points_capped if goal changes """
|
||||
is_create = self.pk is None
|
||||
super().save(*args, **kwargs)
|
||||
changed = self.get_changed_fields()
|
||||
trigger_goal_change(
|
||||
instance=self,
|
||||
new=is_create,
|
||||
changes=changed
|
||||
)
|
||||
self._original = self._dict() # reset
|
||||
|
||||
|
||||
|
||||
|
||||
class Award(models.Model):
|
||||
"""Awards in Competition - user can earn points for comppleting awards"""
|
||||
|
||||
competition = models.ForeignKey(Competition, on_delete=models.CASCADE, null=False, blank=False)
|
||||
|
||||
name = models.CharField(null=False, max_length=60)
|
||||
sport = models.CharField(null=False, default='GROUP_ANY', max_length=40, choices=SPORT_TYPE_GROUPS + SPORT_TYPES)
|
||||
threshold = models.DecimalField(null=False, max_digits=10, decimal_places=2)
|
||||
period = models.CharField(null=False, max_length=12, default='day', choices=POINT_REF_PERIODS)
|
||||
reward_points = models.IntegerField(null=False)
|
||||
|
||||
def __str__(self):
|
||||
"""str print-out of model entry"""
|
||||
return f"{self.competition}: {self.name} ({self.reward_points} {self.period})"
|
||||
|
||||
|
||||
|
||||
class Points(models.Model):
|
||||
"""Points earned for User's Workout for this category or award"""
|
||||
|
||||
goal = models.ForeignKey(ActivityGoal, on_delete=models.CASCADE, null=True, blank=True)
|
||||
award = models.ForeignKey(Award, on_delete=models.CASCADE, null=True, blank=True)
|
||||
workout = models.ForeignKey(Workout, on_delete=models.CASCADE, null=False, blank=False)
|
||||
|
||||
points_raw = models.DecimalField(null=False, max_digits=10, decimal_places=2)
|
||||
points_capped = models.DecimalField(null=True, max_digits=10, decimal_places=2)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Points"
|
||||
verbose_name_plural = "Points"
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['goal', 'award', 'workout'], name='unique_goal_award_workout')
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
"""str print-out of model entry"""
|
||||
return f"{self.award if self.goal is None else self.goal} - {self.points_raw}"
|
||||
204
src-backend/competition/scorer.py
Normal file
204
src-backend/competition/scorer.py
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
import datetime
|
||||
from django.apps import apps
|
||||
|
||||
from custom_user.point_recalc import trigger_recalc_points
|
||||
|
||||
|
||||
def _calculate_points_raw(goal, workout, user):
|
||||
goal_metric = goal.metric
|
||||
goal_target = float(goal.goal)
|
||||
|
||||
if goal_metric == 'min':
|
||||
if workout.duration is None or workout.duration == '':
|
||||
points = 0
|
||||
else:
|
||||
points = float(workout.duration.total_seconds()) / 60 / goal_target
|
||||
elif goal_metric == 'num':
|
||||
points = 1 / goal_target
|
||||
elif goal_metric == 'kcal':
|
||||
if workout.kcal is None or workout.kcal == '':
|
||||
points = 0
|
||||
else:
|
||||
points = float(workout.kcal) / (goal_target * float(user.scaling_kcal))
|
||||
elif goal_metric == 'km':
|
||||
if workout.distance is None or workout.distance == '':
|
||||
points = 0
|
||||
else:
|
||||
points = float(workout.distance) / (goal_target * float(user.scaling_distance))
|
||||
elif goal_metric == 'kj':
|
||||
if workout.kcal is None or workout.kcal == '':
|
||||
points = 0
|
||||
else:
|
||||
points = float(workout.kcal) * 4.18 / (goal_target * float(user.scaling_kcal))
|
||||
return points * 100
|
||||
|
||||
|
||||
def trigger_workout_delete(instance):
|
||||
RecalcRequest = apps.get_model('custom_user', 'RecalcRequest')
|
||||
for points in instance.points_set.all():
|
||||
RecalcRequest(user=instance.user, goal=points.goal, start_datetime=instance.start_datetime).save()
|
||||
print(f"Workout ({instance.pk}) deletion triggered point cap recalc - after {instance.start_datetime.isoformat()}")
|
||||
|
||||
trigger_recalc_points()
|
||||
|
||||
|
||||
def trigger_workout_change(instance, new, changes):
|
||||
|
||||
RecalcRequest = apps.get_model('custom_user', 'RecalcRequest')
|
||||
|
||||
if new:
|
||||
# newly created workout - add point entries
|
||||
Points = apps.get_model('competition', 'Points')
|
||||
start_datetime = datetime.datetime.strptime(instance.start_datetime, '%Y-%m-%dT%H:%M:%SZ') if type(instance.start_datetime) is str else instance.start_datetime
|
||||
for competition in instance.user.my_competitions.filter(start_date__lte=start_datetime, end_date__gte=start_datetime):
|
||||
for goal in competition.activitygoal_set.all():
|
||||
if goal.count_steps_as_walks or instance.sport_type != 'Steps':
|
||||
points = _calculate_points_raw(goal=goal, workout=instance, user=instance.user)
|
||||
Points(goal=goal, workout=instance, points_raw=points, points_capped=points).save()
|
||||
RecalcRequest(user=instance.user, goal=goal, start_datetime=start_datetime).save()
|
||||
else:
|
||||
# updated existing workout
|
||||
# check if relevant field was changed
|
||||
metric_change_lst = []
|
||||
if 'start_datetime' in changes:
|
||||
metric_change_lst.extend(['min', 'num', 'kcal', 'km', 'kj'])
|
||||
if 'duration' in changes:
|
||||
metric_change_lst.extend(['min'])
|
||||
if 'kcal' in changes:
|
||||
metric_change_lst.extend(['kcal', 'kj'])
|
||||
if 'distance' in changes:
|
||||
metric_change_lst.extend(['km'])
|
||||
|
||||
recalc_start_datetime = changes.get('start_datetime', [instance.start_datetime])[0]
|
||||
for recalc_points, recalc_goal in [(i, i.goal) for i in instance.points_set.all() if i.goal.metric in metric_change_lst]:
|
||||
points = _calculate_points_raw(goal=recalc_goal, workout=instance, user=instance.user)
|
||||
setattr(recalc_points, 'points_raw', points)
|
||||
setattr(recalc_points, 'points_capped', points)
|
||||
recalc_points.save()
|
||||
RecalcRequest(user=instance.user, goal=recalc_goal, start_datetime=recalc_start_datetime).save()
|
||||
|
||||
print(f"Workout ({instance.pk}) update triggered point cap recalc - {'NEW ENTRY' if new else 'EXISTING CHANGED'}" + ("" if new else f" - {changes}"))
|
||||
|
||||
trigger_recalc_points()
|
||||
|
||||
|
||||
def trigger_goal_change(instance, new, changes):
|
||||
RecalcRequest = apps.get_model('custom_user', 'RecalcRequest')
|
||||
Points = apps.get_model('competition', 'Points')
|
||||
Workout = apps.get_model('workouts', 'Workout')
|
||||
if new:
|
||||
# newly created goal - add point entries
|
||||
workout_lst = Workout.objects.filter(start_datetime__gte=instance.competition.start_date, start_datetime__lte=instance.competition.end_date + datetime.timedelta(days=1), user__in=instance.competition.user.all())
|
||||
if instance.count_steps_as_walks is False:
|
||||
workout_lst = workout_lst.exclude(sport_type='Steps')
|
||||
for workout in workout_lst:
|
||||
points = _calculate_points_raw(goal=instance, workout=workout, user=workout.user)
|
||||
Points(goal=instance, workout=workout, points_raw=points, points_capped=points).save()
|
||||
RecalcRequest(user=workout.user, goal=instance, start_datetime=workout.start_datetime).save()
|
||||
else:
|
||||
# updated existing workout
|
||||
# check if relevant field was changed
|
||||
_ = changes.pop('name', None)
|
||||
if len(changes) > 0:
|
||||
if 'count_steps_as_walks' in changes:
|
||||
# add steps
|
||||
if changes['count_steps_as_walks'][1]:
|
||||
for workout in Workout.objects.filter(start_datetime__gte=instance.competition.start_date, start_datetime__lte=instance.competition.end_date + datetime.timedelta(days=1), user__in=instance.competition.user.all(), sport_type='Steps'):
|
||||
points = _calculate_points_raw(goal=instance, workout=workout, user=workout.user)
|
||||
Points(goal=instance, workout=workout, points_raw=points, points_capped=points).save()
|
||||
# remove steps
|
||||
else:
|
||||
for point in instance.points_set.filter(workout__sport_type='Steps'):
|
||||
point.delete()
|
||||
for user in instance.competition.user.all():
|
||||
RecalcRequest(user=user, goal=instance, start_datetime=instance.competition.start_date).save()
|
||||
|
||||
trigger_recalc_points()
|
||||
|
||||
|
||||
def trigger_competition_change(instance, new, changes):
|
||||
Points = apps.get_model('competition', 'Points')
|
||||
RecalcRequest = apps.get_model('custom_user', 'RecalcRequest')
|
||||
Workout = apps.get_model('workouts', 'Workout')
|
||||
|
||||
# newly created competitions are ignored as only relevant if new goals are created
|
||||
# only catching changes of the start_date and end_date below
|
||||
|
||||
if 'start_date' in changes:
|
||||
if changes['start_date'][1] < changes['start_date'][0]:
|
||||
# add point entries before changes['start_date'][0] till [1]
|
||||
for goal in instance.activitygoal_set.all():
|
||||
for workout in Workout.objects.filter(start_datetime__gte=changes['start_date'][1], start_datetime__lte=changes['start_date'][0], user__in=instance.user.all()):
|
||||
points = _calculate_points_raw(goal=goal, workout=workout, user=workout.user)
|
||||
Points(goal=goal, workout=workout, points_raw=points, points_capped=points).save()
|
||||
RecalcRequest(user=workout.user, goal=goal, start_datetime=workout.start_datetime).save()
|
||||
print(f"Competition ({instance.pk}) start_date was extended from {changes['start_date'][0]} to {changes['start_date'][1]} triggering point cap recalc")
|
||||
else:
|
||||
# remove point entries before changes['start_date'][1]
|
||||
points_to_delete = Points.objects.filter(goal__competition=instance, workout__start_datetime__lt=changes['start_date'][1])
|
||||
CustomUser = apps.get_model('custom_user', 'CustomUser')
|
||||
ActivityGoal = apps.get_model('competition', 'ActivityGoal')
|
||||
for user in [CustomUser.objects.get(pk=i) for i in set(points_to_delete.values_list('workout__user', flat=True))]:
|
||||
for goal in [ActivityGoal.objects.get(pk=i) for i in set(points_to_delete.values_list('goal', flat=True))]:
|
||||
RecalcRequest(user=user, goal=goal, start_datetime=changes['start_date'][1]).save()
|
||||
points_to_delete.delete()
|
||||
print(f"Competition ({instance.pk}) start_date was shortened from {changes['start_date'][0]} to {changes['start_date'][1]} triggering point cap recalc")
|
||||
|
||||
trigger_recalc_points
|
||||
|
||||
if 'end_date' in changes:
|
||||
if changes['end_date'][1] > changes['end_date'][0]:
|
||||
# add point entries after changes['end_date'][0] till [1]
|
||||
for goal in instance.activitygoal_set.all():
|
||||
for workout in Workout.objects.filter(start_datetime__gte=changes['end_date'][0] + datetime.timedelta(days=1), start_datetime__lte=changes['end_date'][1] + datetime.timedelta(days=1), user__in=instance.user.all()):
|
||||
points = _calculate_points_raw(goal=goal, workout=workout, user=workout.user)
|
||||
Points(goal=goal, workout=workout, points_raw=points, points_capped=points).save()
|
||||
RecalcRequest(user=workout.user, goal=goal, start_datetime=workout.start_datetime).save()
|
||||
print(f"Competition ({instance.pk}) end_date was extended from {changes['end_date'][0]} to {changes['end_date'][1]} triggering point cap recalc")
|
||||
else:
|
||||
# remove point entries after changes['end_date'][1]
|
||||
Points.objects.filter(goal__competition=instance, workout__start_datetime__gt=changes['end_date'][1]).delete()
|
||||
print(f"Competition ({instance.pk}) end_date was shortened from {changes['end_date'][0]} to {changes['end_date'][1]} NOT triggering point cap recalc")
|
||||
|
||||
trigger_recalc_points()
|
||||
|
||||
|
||||
def trigger_user_change(instance, new, changes):
|
||||
Points = apps.get_model('competition', 'Points')
|
||||
RecalcRequest = apps.get_model('custom_user', 'RecalcRequest')
|
||||
|
||||
# check if user leaves or joins a competition
|
||||
if 'my_competitions' in changes:
|
||||
# instance user obj / changes = pk_set comp id to add/remove
|
||||
if changes['my_competitions'][0] is None:
|
||||
# add/join competition
|
||||
Workout = apps.get_model('workouts', 'Workout')
|
||||
Competition = apps.get_model('competition', 'Competition')
|
||||
for competition in Competition.objects.filter(pk__in=changes['my_competitions'][1]):
|
||||
workout_lst = Workout.objects.filter(user=instance, start_datetime__gte=competition.start_date, start_datetime__lte=competition.end_date + datetime.timedelta(days=1))
|
||||
for goal in competition.activitygoal_set.all():
|
||||
for workout in workout_lst:
|
||||
points = _calculate_points_raw(goal=goal, workout=workout, user=instance)
|
||||
Points(goal=goal, workout=workout, points_raw=points, points_capped=points).save()
|
||||
RecalcRequest(user=instance, goal=goal, start_datetime=competition.start_date).save()
|
||||
print(f"User ({instance.pk}) join competitions {changes['my_competitions'][1]} triggering point cap recalc")
|
||||
else:
|
||||
# remove/leave competition
|
||||
Points.objects.filter(goal__competition__in=changes['my_competitions'][0], workout__user=instance).delete()
|
||||
print(f"User ({instance.pk}) left competitions {changes['my_competitions'][0]} NOT triggering point cap recalc")
|
||||
|
||||
trigger_recalc_points()
|
||||
|
||||
# check if equalizing / scaling factors were changed
|
||||
if 'scaling_distance' in changes or 'scaling_kcal' in changes:
|
||||
goal_metrics = (['km'] if 'scaling_distance' in changes else []) + (['kcal', 'kj'] if 'scaling_kcal' in changes else [])
|
||||
recalc_points = Points.objects.filter(goal__metric__in=goal_metrics, workout__user=instance)
|
||||
|
||||
for recalc_point in recalc_points:
|
||||
points = _calculate_points_raw(goal=recalc_point.goal, workout=recalc_point.workout, user=instance)
|
||||
setattr(recalc_point, 'points_raw', points)
|
||||
setattr(recalc_point, 'points_capped', points)
|
||||
recalc_point.save()
|
||||
RecalcRequest(user=instance, goal=recalc_point.goal, start_datetime=recalc_point.workout.start_datetime).save()
|
||||
|
||||
print(f"User ({instance.pk}) scaling factors {goal_metrics} changed triggering point cap recalc")
|
||||
57
src-backend/competition/serializers.py
Normal file
57
src-backend/competition/serializers.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from rest_framework import serializers
|
||||
from custom_user.models import CustomUser
|
||||
from .models import Competition, ActivityGoal, Team, Points
|
||||
|
||||
|
||||
class CompetitionSerializer(serializers.ModelSerializer):
|
||||
owner = serializers.PrimaryKeyRelatedField(
|
||||
queryset=CustomUser.objects.all(),
|
||||
required=False
|
||||
)
|
||||
user_info = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Competition
|
||||
fields = ['id', 'owner', 'user', 'user_info', 'name', 'start_date', 'start_date_fmt', 'start_date_epoch', 'end_date', 'end_date_fmt', 'end_date_epoch', 'has_teams', 'organizer_assigns_teams', 'join_code']
|
||||
read_only_fields = ['join_code', 'user', 'user_info']
|
||||
|
||||
def get_user_info(self, obj):
|
||||
# Assuming `obj.user` is a ManyToMany or related manager
|
||||
users = obj.user.all().order_by('username') if hasattr(obj.user, 'all') else [obj.user]
|
||||
return [{'id': u.id, 'username': u.username} for u in users]
|
||||
|
||||
|
||||
class TeamSerializer(serializers.ModelSerializer):
|
||||
user_info = serializers.SerializerMethodField()
|
||||
my = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = ['id', 'name', 'competition', 'user', 'user_info', 'my']
|
||||
read_only_fields = ['user', 'user_info', 'my']
|
||||
|
||||
def get_user_info(self, obj):
|
||||
# Assuming `obj.user` is a ManyToMany or related manager
|
||||
users = obj.user.all().order_by('username') if hasattr(obj.user, 'all') else [obj.user]
|
||||
return [{'id': u.id, 'username': u.username} for u in users]
|
||||
|
||||
def get_my(self, obj):
|
||||
# if it is the user's team
|
||||
request = self.context.get('request')
|
||||
if request and hasattr(request, "user"):
|
||||
return obj.user.filter(id=request.user.id).exists()
|
||||
return False
|
||||
|
||||
|
||||
class ActivityGoalSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ActivityGoal
|
||||
fields = '__all__'
|
||||
read_only_fields = []
|
||||
|
||||
|
||||
class PointsSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Points
|
||||
fields = ['id', 'goal', 'award', 'workout', 'points_raw', 'points_capped']
|
||||
read_only_fields = []
|
||||
169
src-backend/competition/stats.py
Normal file
169
src-backend/competition/stats.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.apps import apps
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
from django.db.models import Sum, Count, ExpressionWrapper, DurationField, F, Q, IntegerField
|
||||
from django.db.models.functions import TruncDay, Now, ExtractDay
|
||||
|
||||
|
||||
def _add_rank(data, key, enhance_dict, id_field, rank_field='rank', reverse=True):
|
||||
sorted_data = sorted(data, key=lambda x: x[key], reverse=reverse)
|
||||
rank = 0
|
||||
last_value = None
|
||||
user_lst = []
|
||||
for idx, item in enumerate(sorted_data, start=1):
|
||||
if item[key] != last_value:
|
||||
rank = idx
|
||||
last_value = item[key]
|
||||
sorted_data[idx-1] = {**item, rank_field: rank, **enhance_dict[item[id_field]]}
|
||||
enhance_dict[item[id_field]]['rank'] = rank
|
||||
enhance_dict[item[id_field]]['points'] = item[key]
|
||||
user_lst.append(item[id_field])
|
||||
for i in [i for i in enhance_dict.keys() if i not in user_lst]:
|
||||
sorted_data.append({**enhance_dict[i], rank_field: None, key: None})
|
||||
enhance_dict[i]['rank'] = None
|
||||
enhance_dict[i]['points'] = None
|
||||
return sorted_data
|
||||
|
||||
|
||||
def get_competition_stats(competition, last_seven_days=False):
|
||||
CustomUser = apps.get_model('custom_user', 'CustomUser')
|
||||
Competition = apps.get_model('competition', 'Competition')
|
||||
Points = apps.get_model('competition', 'Points')
|
||||
Team = apps.get_model('competition', 'Team')
|
||||
|
||||
# Custom query logic
|
||||
try:
|
||||
competition_obj = Competition.objects.get(id=competition)
|
||||
except Competition.DoesNotExist:
|
||||
return Response({"detail": "Competition not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
all_points = Points.objects.filter(Q(award__competition__id=competition) | Q(goal__competition_id=competition))
|
||||
|
||||
if last_seven_days:
|
||||
today = datetime.date.today()
|
||||
last_sunday = today - datetime.timedelta(days=today.weekday() + 1) if today.weekday() != 6 else today
|
||||
monday_before = last_sunday - datetime.timedelta(days=6)
|
||||
all_points = all_points.filter(workout__start_datetime__gte=monday_before, workout__start_datetime__lt=last_sunday + datetime.timedelta(days=1))
|
||||
|
||||
# For SQLite
|
||||
if settings.DATABASES.get('default', {}).get('ENGINE') == 'django.db.backends.sqlite3':
|
||||
all_points_date = (
|
||||
all_points
|
||||
.annotate(date=TruncDay('workout__start_datetime'))
|
||||
.annotate(tmp_today=TruncDay(Now()))
|
||||
.annotate(tmp_start_date=TruncDay(F('workout__start_datetime')))
|
||||
.annotate(days_ago=ExpressionWrapper((F('tmp_today') - F('tmp_start_date')) / 86_400_000_000, output_field=IntegerField()))
|
||||
)
|
||||
# For Postgres
|
||||
else:
|
||||
all_points_date = (
|
||||
all_points
|
||||
.annotate(date=TruncDay('workout__start_datetime'))
|
||||
.annotate(days_ago_duration=ExpressionWrapper(TruncDay(Now()) - TruncDay(F('workout__start_datetime')), output_field=DurationField()))
|
||||
.annotate(days_ago=ExtractDay(F('days_ago_duration')))
|
||||
)
|
||||
tmp_all = (
|
||||
all_points_date
|
||||
.values('days_ago')
|
||||
.annotate(total=Sum('points_capped'))
|
||||
.values('days_ago', 'total')
|
||||
.order_by('-days_ago')
|
||||
)
|
||||
timeseries_all = {}
|
||||
for i in tmp_all:
|
||||
days_ago = i.pop('days_ago')
|
||||
timeseries_all[days_ago] = i
|
||||
|
||||
tmp_user = (
|
||||
all_points_date
|
||||
.values('days_ago', 'workout__user__id')
|
||||
.annotate(total=Sum('points_capped'))
|
||||
.order_by('-days_ago')
|
||||
)
|
||||
timeseries_user = {}
|
||||
for i in tmp_user:
|
||||
user_id = i.pop('workout__user__id')
|
||||
days_ago = i.pop('days_ago')
|
||||
if user_id not in timeseries_user:
|
||||
timeseries_user[user_id] = {}
|
||||
timeseries_user[user_id][days_ago] = i
|
||||
|
||||
tmp_team = (
|
||||
all_points_date
|
||||
.values('days_ago', 'workout__user__my_teams')
|
||||
.annotate(total=Sum('points_capped'))
|
||||
.order_by('-days_ago')
|
||||
)
|
||||
timeseries_team = {}
|
||||
for i in tmp_team:
|
||||
team_id = i.pop('workout__user__my_teams')
|
||||
days_ago = i.pop('days_ago')
|
||||
if team_id not in timeseries_team:
|
||||
timeseries_team[team_id] = {}
|
||||
timeseries_team[team_id][days_ago] = i
|
||||
|
||||
# Get user data
|
||||
user_dict = {i['id']: i for i in CustomUser.objects.filter(my_competitions=competition).values('id', 'username', 'strava_allow_follow', 'strava_athlete_id').order_by('username', 'id')}
|
||||
for key, value in user_dict.items():
|
||||
if value['strava_allow_follow'] is False:
|
||||
value['strava_athlete_id'] = None
|
||||
|
||||
# Get user rankings
|
||||
leaderboard_user = (
|
||||
all_points
|
||||
.values('workout__user__id')
|
||||
.annotate(total_capped=Sum('points_capped'))
|
||||
.order_by('-total_capped')
|
||||
)
|
||||
leaderboard_user = _add_rank(leaderboard_user, key="total_capped", enhance_dict=user_dict, id_field='workout__user__id')
|
||||
leaderboard_user_dict = {i['id']: i for i in leaderboard_user}
|
||||
|
||||
# Get team data
|
||||
team_dict = {i.id: {'id': i.id, 'name': i.name, 'members': [leaderboard_user_dict.get(i.id, {'id': i.id, 'username': 'ERROR', 'total_capped': None}) for i in i.user.all()]} for i in Team.objects.filter(competition=competition).prefetch_related('user')}
|
||||
for key, value in team_dict.items():
|
||||
value['active_member_count'] = sum(1 for i in value.get('members', []) if i.get('total_capped', 0) is not None and i.get('total_capped', 0) > 0)
|
||||
value['member_count'] = len(value.get('members', []))
|
||||
|
||||
# Get team rankings
|
||||
leaderboard_team = (
|
||||
all_points
|
||||
.values('workout__user__my_teams__id')
|
||||
.annotate(total_capped=Sum('points_capped'))
|
||||
.order_by('-total_capped')
|
||||
)
|
||||
leaderboard_team = [{**i, 'total_capped': i['total_capped'] / max(1, team_dict[i['workout__user__my_teams__id']]['active_member_count'])} for i in leaderboard_team if i['workout__user__my_teams__id'] in team_dict]
|
||||
leaderboard_team = _add_rank(leaderboard_team, key="total_capped", enhance_dict=team_dict, id_field='workout__user__my_teams__id')
|
||||
team_dict = {i['id']: i for i in leaderboard_team}
|
||||
|
||||
competition_details = {
|
||||
'name': competition_obj.name,
|
||||
'owner': user_dict.get(competition_obj.owner.pk, {'id': competition_obj.owner.pk, 'username': 'ERROR', 'total_capped': None}),
|
||||
'members': [user_dict.get(i, {'id': i, 'username': 'ERROR', 'total_capped': None}) for i in list(competition_obj.user.all().values_list('pk', flat=True))],
|
||||
'member_count': competition_obj.user.all().count(),
|
||||
'active_member_count': len(timeseries_user),
|
||||
'start_date': competition_obj.start_date,
|
||||
'start_date_count': (datetime.date.today() - competition_obj.start_date).days,
|
||||
'end_date': competition_obj.end_date,
|
||||
'end_date_count': (datetime.date.today() - competition_obj.end_date).days,
|
||||
'has_teams': competition_obj.has_teams,
|
||||
'goals': competition_obj.activitygoal_set.all().values(),
|
||||
}
|
||||
|
||||
response_obj = {
|
||||
'competition': competition_details,
|
||||
'users': user_dict,
|
||||
'teams': team_dict,
|
||||
'timeseries': {
|
||||
'all': timeseries_all,
|
||||
'user': timeseries_user,
|
||||
'team': timeseries_team,
|
||||
},
|
||||
'leaderboard': {
|
||||
'team': leaderboard_team,
|
||||
'individual': leaderboard_user,
|
||||
}
|
||||
}
|
||||
return response_obj
|
||||
3
src-backend/competition/tests.py
Normal file
3
src-backend/competition/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
306
src-backend/competition/views.py
Normal file
306
src-backend/competition/views.py
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework import status
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_page
|
||||
from rest_framework.permissions import BasePermission
|
||||
|
||||
from django.db.models import Sum
|
||||
|
||||
from custom_user.views import IsOwnerOrReadOnly
|
||||
from custom_user.models import CustomUser
|
||||
from custom_user.strava import sync_strava
|
||||
from custom_user.point_recalc import recalc_points
|
||||
from .models import Competition, Team, ActivityGoal, Points
|
||||
from .serializers import CompetitionSerializer, TeamSerializer, ActivityGoalSerializer, PointsSerializer
|
||||
from .stats import get_competition_stats
|
||||
|
||||
from celery import current_app
|
||||
import json
|
||||
|
||||
class CompetitionViewSet(viewsets.ModelViewSet):
|
||||
#queryset = Competition.objects.all()
|
||||
serializer_class = CompetitionSerializer
|
||||
|
||||
permission_classes = [IsOwnerOrReadOnly]
|
||||
|
||||
def get_queryset(self):
|
||||
# return all competitions the user is owner of or a participant of
|
||||
#time.sleep(3) # throttle for testing
|
||||
return Competition.objects.filter(Q(owner=self.request.user) | Q(user=self.request.user)).distinct().order_by('-end_date', '-start_date', '-id')
|
||||
|
||||
def perform_create(self, serializer):
|
||||
# when creating a new competition, set the owner to the request user
|
||||
serializer.save(owner=self.request.user)
|
||||
|
||||
|
||||
class TeamViewSet(viewsets.ModelViewSet):
|
||||
#queryset = Team.objects.all()
|
||||
serializer_class = TeamSerializer
|
||||
|
||||
permission_classes = [IsOwnerOrReadOnly]
|
||||
|
||||
def get_queryset(self):
|
||||
# return all teams the user is a member of and all teams of competitions the user participates in
|
||||
#time.sleep(3) # throttle for testing
|
||||
return Team.objects.filter(Q(user=self.request.user) | Q(competition__user=self.request.user)).distinct().order_by('name')
|
||||
|
||||
def perform_create(self, serializer):
|
||||
|
||||
competition_obj = serializer.validated_data.get('competition')
|
||||
|
||||
# if has_teams is disabled, don't allow creation of teams
|
||||
if competition_obj.has_teams is False:
|
||||
raise PermissionDenied("Teams are disabled for this competition.")
|
||||
|
||||
# only allow user to create a team if they are a member or owner of the competition
|
||||
if not (competition_obj.owner == self.request.user) and not (competition_obj in self.request.user.my_competitions.all()):
|
||||
raise PermissionDenied("You are not a participant of the competition you want to create a team for.")
|
||||
|
||||
serializer.save()
|
||||
|
||||
|
||||
class ActivityGoalViewSet(viewsets.ModelViewSet):
|
||||
#queryset = ActivityGoal.objects.all()
|
||||
serializer_class = ActivityGoalSerializer
|
||||
|
||||
permission_classes = [IsOwnerOrReadOnly]
|
||||
|
||||
def get_queryset(self):
|
||||
# return all competition categories the user is owner of or a participant of
|
||||
#time.sleep(3) # throttle for testing
|
||||
return ActivityGoal.objects.filter(Q(competition__owner=self.request.user) | Q(competition__user=self.request.user)).distinct().order_by('name')
|
||||
|
||||
def perform_create(self, serializer):
|
||||
competition_obj = serializer.validated_data.get('competition')
|
||||
|
||||
# only allow user to create a team if they are a member or owner of the competition
|
||||
if competition_obj.owner != self.request.user:
|
||||
raise PermissionDenied("You can only create and edit competition goals if you are the owner.")
|
||||
|
||||
serializer.save()
|
||||
|
||||
|
||||
class PointsViewSet(viewsets.ModelViewSet):
|
||||
#queryset = Points.objects.all()
|
||||
serializer_class = PointsSerializer
|
||||
|
||||
permission_classes = [IsOwnerOrReadOnly]
|
||||
|
||||
def get_queryset(self):
|
||||
# return all points the user is owner of, a participant of, or of his/her own workouts
|
||||
#time.sleep(3) # throttle for testing
|
||||
return Points.objects.filter(Q(goal__competition__owner=self.request.user) | Q(goal__competition__user=self.request.user) | Q(workout__user=self.request.user)).distinct().order_by('-workout__start_datetime', '-workout__duration', '-workout', '-workout__user')
|
||||
|
||||
|
||||
class StatsPermissions(BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
# Only authenticated users
|
||||
if request.user.is_authenticated:
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
competition_lst = Competition.objects.filter(
|
||||
Q(pk=view.kwargs.get('competition', 0)) & (Q(owner=request.user) | Q(user=request.user))
|
||||
)
|
||||
return len(competition_lst) > 0
|
||||
|
||||
|
||||
class IsAdmin(BasePermission):
|
||||
"""
|
||||
Custom permission class to allow access only to admin users.
|
||||
"""
|
||||
def has_permission(self, request, view):
|
||||
# Check if user is authenticated and is an admin
|
||||
return bool(request.user and request.user.is_authenticated and request.user.is_staff)
|
||||
|
||||
|
||||
class CeleryQueryView(APIView):
|
||||
permission_classes = [IsAdmin]
|
||||
|
||||
def get(self, request, task_id=None):
|
||||
if task_id:
|
||||
# Get status of specific task
|
||||
try:
|
||||
task = current_app.AsyncResult(task_id)
|
||||
return Response({
|
||||
'task_id': task.id,
|
||||
'status': task.status,
|
||||
'result': task.result if task.successful() else None,
|
||||
'error': str(task.result) if task.failed() else None
|
||||
})
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"error": f"Error retrieving task status: {str(e)}"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
else:
|
||||
# List all registered tasks
|
||||
try:
|
||||
registered_tasks = [
|
||||
name
|
||||
for name, task in sorted(current_app.tasks.items())
|
||||
if not name.startswith('celery.')
|
||||
]
|
||||
return Response(registered_tasks)
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"error": f"Error retrieving tasks: {str(e)}"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
def post(self, request):
|
||||
task = request.query_params.get('task')
|
||||
args = request.query_params.get('args', '[]')
|
||||
kwargs = request.query_params.get('kwargs', '{}')
|
||||
|
||||
if not task:
|
||||
return Response(
|
||||
{"error": "Task name is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
try:
|
||||
# Convert string args and kwargs to Python objects
|
||||
args_list = json.loads(args)
|
||||
kwargs_dict = json.loads(kwargs)
|
||||
|
||||
# Get the task by name and apply it with args and kwargs
|
||||
celery_task = current_app.tasks[task]
|
||||
result = celery_task.delay(*args_list, **kwargs_dict)
|
||||
|
||||
return Response({
|
||||
"task_id": result.task_id,
|
||||
"status": "Task sent successfully"
|
||||
})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return Response(
|
||||
{"error": "Invalid JSON format in args or kwargs"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
except KeyError:
|
||||
return Response(
|
||||
{"error": f"Task '{task}' not found"},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"error": str(e)},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
|
||||
class CompetitionStatsQueryView(APIView):
|
||||
permission_classes = [StatsPermissions]
|
||||
|
||||
@method_decorator(cache_page(30)) # cache for 30 seconds
|
||||
def get(self, request, competition):
|
||||
response_obj = get_competition_stats(competition)
|
||||
self.check_object_permissions(request, response_obj)
|
||||
return Response(response_obj)
|
||||
|
||||
|
||||
class FeedPermissions(BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
# Only authenticated users
|
||||
if request.user.is_authenticated:
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if len(obj) == 0:
|
||||
return False
|
||||
obj = obj[0]
|
||||
return request.user.id in [obj.owner.pk] + list(obj.user.all().values_list('pk', flat=True))
|
||||
|
||||
|
||||
class FeedQueryView(APIView):
|
||||
""" API view to get the activity/point feed for a competition. """
|
||||
permission_classes = [FeedPermissions]
|
||||
|
||||
def get(self, request, competition):
|
||||
# Custom query logic
|
||||
#time.sleep(3) # throttle for testing
|
||||
|
||||
competition_obj = Competition.objects.filter(id=competition)
|
||||
self.check_object_permissions(request, competition_obj)
|
||||
|
||||
all_points = Points.objects.filter(Q(award__competition__id=competition) | Q(goal__competition_id=competition)).order_by('-workout__start_datetime', '-workout__steps', '-workout__duration', '-workout', '-workout__user')
|
||||
|
||||
grouped_points = {i['workout']: i for i in all_points.values('workout__user', 'workout__user__username', 'workout__user__strava_allow_follow', 'workout', 'workout__sport_type', 'workout__start_datetime', 'workout__duration', 'workout__steps', 'workout__strava_id', 'award').annotate(points_capped=Sum('points_capped'), points_raw=Sum('points_raw')).order_by('-workout__start_datetime', '-workout__duration', '-workout', '-workout__user')}
|
||||
|
||||
for i in all_points.values('workout', 'id', 'goal', 'goal__name', 'award', 'award__name', 'points_capped', 'points_raw'):
|
||||
if 'details' not in grouped_points[i['workout']]:
|
||||
grouped_points[i['workout']]['details'] = []
|
||||
grouped_points[i['workout']]['details'].append(i)
|
||||
|
||||
return Response(list(grouped_points.values()))
|
||||
|
||||
|
||||
|
||||
class JoinCompetitionView(APIView):
|
||||
""" API post view for users to join a competition. """
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request, join_code):
|
||||
competition = Competition.objects.filter(join_code=join_code.upper())
|
||||
if len(competition) == 0:
|
||||
return Response({"message": "Invalid join code."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
competition = competition[0]
|
||||
competition.user.add(request.user)
|
||||
competition.save()
|
||||
return Response({"message": "Successfully joined competition.", "competition": competition.id}, status=status.HTTP_200_OK)
|
||||
|
||||
def delete(self, request, join_code):
|
||||
id = int(join_code)
|
||||
|
||||
request.user.my_competitions.remove(id)
|
||||
teams = request.user.my_teams.filter(competition=id)
|
||||
for team in teams:
|
||||
team.user.remove(request.user)
|
||||
team.save()
|
||||
request.user.save()
|
||||
|
||||
points = Points.objects.filter((Q(award__competition__id=id) | Q(goal__competition_id=id)) & Q(workout__user=request.user))
|
||||
points.delete()
|
||||
|
||||
return Response({"message": "Successfully left competition.", "competition": id}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class JoinTeamView(APIView):
|
||||
""" API post view for users to join a team and make sure they are only a member of one team per competition. """
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
team_id = request.query_params.get('team')
|
||||
team = Team.objects.filter(id=team_id)
|
||||
if len(team) == 0:
|
||||
return Response({"message": "Invalid team id."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
team = team[0]
|
||||
|
||||
user_id = request.query_params.get('user', request.user.id)
|
||||
user = CustomUser.objects.filter(id=user_id)
|
||||
if len(user) == 0:
|
||||
return Response({"message": "Invalid user id."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
user = user[0]
|
||||
|
||||
competition = team.competition
|
||||
competition_teams = competition.team_set.all()
|
||||
|
||||
if user != request.user and request.user != competition.owner and len(competition_teams.filter(user=user)) > 0:
|
||||
return Response({"message": "Unauthorized. You can only change your own team or add people to your team if they are currently in no team."}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
for competition_team in competition_teams:
|
||||
competition_team.user.remove(user.id)
|
||||
competition_team.save()
|
||||
user.my_teams.add(team.id)
|
||||
user.save()
|
||||
|
||||
return Response({"message": "Successfully joined team.", "team": team.id, "user": user.id}, status=status.HTTP_200_OK)
|
||||
Loading…
Add table
Add a link
Reference in a new issue