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,181 @@
import time, datetime
import requests
from rest_framework import viewsets
from rest_framework.permissions import BasePermission, IsAdminUser, SAFE_METHODS, AllowAny
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from celery.exceptions import TimeoutError
from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend
from django.shortcuts import get_object_or_404
from django.conf import settings
from django.core.cache import cache
from .serializers import PasswordResetSerializer, PasswordResetConfirmSerializer
from .models import CustomUser
from .serializers import CustomUserSerializer
from .filters import CustomUserFilter
from .strava import sync_strava
class IsOwnerOrReadOnly(BasePermission):
""" Permission class to only allow admins and owner to edit or delete entry """
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):
# Read requests always allowed
if request.method in SAFE_METHODS:
return True # allow GET, HEAD, OPTIONS (GET is filtered at viweset level to only show allowed entries)
# Only workout user can edit workout
if hasattr(obj, 'user') and obj.user == request.user:
return True
# Only owner of competition can modify
elif hasattr(obj, 'owner') and obj.owner == request.user:
return True
# Only owner can modify goals and awards
elif hasattr(obj, 'competition') and hasattr(obj.competition, 'owner') and obj.competition.owner == request.user:
return True
# If admin allow all requests
if bool(request.user and request.user.is_staff):
return True
return False
class UserPermissionClass(BasePermission):
""" Allow unauthenticated users to POST data - i.e. for registration """
def has_permission(self, request, view):
# Only create new requsts - i.e. POST
if request.method in ('POST', 'OPTIONS'):
return True
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
return obj.pk == request.user.pk
class CustomUserViewSet(viewsets.ModelViewSet):
#queryset = Competition.objects.all()
serializer_class = CustomUserSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = CustomUserFilter
permission_classes = [UserPermissionClass]
def get_queryset(self):
# return all competitions the user is owner of or a participant of
#time.sleep(3) # throttle for testing
return CustomUser.objects.filter(Q(pk=self.request.user.pk) | Q(my_competitions__in=self.request.user.my_competitions.all())).distinct().order_by('username', 'id')
def get_object(self):
lookup_value = self.kwargs.get(self.lookup_field)
# Modify filter if I ask for myself instead of the id number
if str(lookup_value).lower() in ['me', 'my', 'myself', 'i']:
lookup_value = self.request.user.id
return get_object_or_404(self.get_queryset(), pk=lookup_value)
class PasswordResetView(APIView):
permission_classes = [AllowAny]
def post(self, request):
serializer = PasswordResetSerializer(data=request.data)
if serializer.is_valid():
serializer.save(request=request)
return Response({"detail": "Password reset e-mail sent."})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class PasswordResetConfirmView(APIView):
permission_classes = [AllowAny]
def post(self, request):
serializer = PasswordResetConfirmSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({"detail": "Password has been reset."})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class LinkStravaView(APIView):
""" API post view for users to link with Strava. """
permission_classes = [IsAuthenticated]
def post(self, request, code):
user = request.user
client_id = settings.STRAVA_CLIENT_ID
client_secret = settings.STRAVA_CLIENT_SECRET
if client_id == 1234321 or client_secret == "ReplaceWithClientSecret":
return Response({"message": "Sever configuration error - STRAVA_CLIENT_ID and/or STRAVA_CLIENT_SECRET are not set."}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
response = requests.post(
url='https://www.strava.com/oauth/token',
data={
'client_id': client_id,
'client_secret': client_secret,
'code': code,
'grant_type': 'authorization_code'
}
)
if response.ok is False:
return Response({"message": "Invalid Strava linkage code"}, status=status.HTTP_400_BAD_REQUEST)
strava_tokens = response.json()
setattr(user, 'strava_refresh_token', strava_tokens.get('refresh_token', None))
setattr(user, 'strava_athlete_id', strava_tokens.get('athlete', {}).get('id', None))
user.save()
cache.set(f"strava_access_token_{user.id}", strava_tokens.get('access_token', None), int(strava_tokens.get('expires_in', 21600)) - 60)
try:
running_task = sync_strava.delay(user__id=user.id, start_datetime=datetime.datetime.now() - datetime.timedelta(days=43))
try:
running_task.get(timeout=100)
except TimeoutError:
print(f"Strava sync task is still running ({running_task.id}). Don't let the user wait so long.")
except requests.exceptions.HTTPError as err:
if '401 Client Error: Unauthorized' in str(err):
return Response({'message': 'Access to activities denied by Strava. Not sufficient permissions to download activities.'}, status=status.HTTP_403_FORBIDDEN)
else:
raise Response(err.response.json(), status=err.response.status_code)
return Response({"message": "Successfully linked Strava."}, status=status.HTTP_200_OK)
class UnlinkStravaView(APIView):
""" API post view for users to unlink Strava. """
permission_classes = [IsAuthenticated]
def post(self, request):
user = request.user
setattr(user, 'strava_refresh_token', None)
setattr(user, 'strava_athlete_id', None)
user.save()
return Response({"message": "Successfully unlinked Strava."}, status=status.HTTP_200_OK)
class SyncStravaView(APIView):
""" API get view for users to sync Strava. """
permission_classes = [IsAuthenticated]
def get(self, request):
user = request.user
if user.strava_refresh_token is None or user.strava_refresh_token == '':
return Response({"message": "Strava is not linked."}, status=status.HTTP_400_BAD_REQUEST)
if user.strava_last_synced_at is None or user.strava_last_synced_at == '' or user.strava_last_synced_at < (timezone.now() - datetime.timedelta(minutes=59)):
sync_strava(user__id=user.id)
return Response({"message": f"Successfully synced Strava."}, status=status.HTTP_200_OK)
return Response({"message": "Too many requests! You can only request a Strava sync every 60 minutes."}, status=status.HTTP_429_TOO_MANY_REQUESTS)