from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, DrawLogSerializer, TournamentSerializer, UserSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, PurchaseSerializer, ShortUserSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer, CustomUserSerializer, UnregisteredTeamSerializer, UnregisteredPlayerSerializer from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken, DrawLog, UnregisteredTeam, UnregisteredPlayer from rest_framework import viewsets from rest_framework.response import Response from rest_framework.decorators import api_view from rest_framework import status from rest_framework.exceptions import MethodNotAllowed from django.http import Http404 from django.db.models import Q from .permissions import IsClubOwner from .utils import check_version_smaller_than_1_1_12 from shared.discord import send_discord_log_message from rest_framework.decorators import permission_classes from rest_framework.permissions import IsAuthenticated from django.shortcuts import get_object_or_404 from tournaments.services.payment_service import PaymentService from django.conf import settings import stripe import json import pandas as pd from tournaments.utils.extensions import create_random_filename from django.core.files.storage import default_storage from django.core.files.base import ContentFile import os from django.http import HttpResponse @api_view(['GET']) def user_by_token(request): serializer = UserSerializer(request.user) return Response(serializer.data, status=status.HTTP_200_OK) class SoftDeleteViewSet(viewsets.ModelViewSet): def destroy(self, request, *args, **kwargs): try: return super().destroy(request, *args, **kwargs) except Http404: return Response(status=status.HTTP_204_NO_CONTENT) class UserViewSet(SoftDeleteViewSet): queryset = CustomUser.objects.all() serializer_class = CustomUserSerializer permission_classes = [] # Users are public whereas the other requests are only for logged users def get_serializer_class(self): # Use UserSerializer for other actions (e.g., create, retrieve) if self.action in ['create', 'retrieve']: return UserSerializer return self.serializer_class class ClubViewSet(SoftDeleteViewSet): queryset = Club.objects.all() serializer_class = ClubSerializer permission_classes = [IsClubOwner] # Clubs are public whereas the other requests are only for logged users def perform_create(self, serializer): serializer.save(creator=self.request.user) class TournamentViewSet(SoftDeleteViewSet): queryset = Tournament.objects.all() serializer_class = TournamentSerializer def get_queryset(self): if self.request.user.is_anonymous: return [] return self.queryset.filter(event__creator=self.request.user) def perform_create(self, serializer): serializer.save() # version check app_version = self.request.headers.get('App-Version') self.warn_if_version_is_too_small(app_version) def warn_if_version_is_too_small(self, version): if check_version_smaller_than_1_1_12(version): message = f'{self.request.user.username} app version is {version}' send_discord_log_message(message) class PurchaseViewSet(SoftDeleteViewSet): queryset = Purchase.objects.all() serializer_class = PurchaseSerializer def get_queryset(self): if self.request.user: return self.queryset.filter(user=self.request.user) return [] def create(self, request, *args, **kwargs): id = request.data.get('id') if Purchase.objects.filter(id=id).exists(): return Response({"detail": "This transaction id is already registered."}, status=status.HTTP_208_ALREADY_REPORTED) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def patch(self, request, pk): raise MethodNotAllowed('PATCH') def delete(self, request, pk): raise MethodNotAllowed('DELETE') class EventViewSet(SoftDeleteViewSet): queryset = Event.objects.all() serializer_class = EventSerializer def get_queryset(self): if self.request.user.is_anonymous: return [] # return self.queryset.filter(creator=self.request.user) return self.queryset.filter( Q(creator=self.request.user) ) class RoundViewSet(SoftDeleteViewSet): queryset = Round.objects.all() serializer_class = RoundSerializer def get_queryset(self): tournament_id = self.request.query_params.get('store_id') or self.request.query_params.get('tournament') if tournament_id: return self.queryset.filter(tournament=tournament_id) if self.request.user: return self.queryset.filter(tournament__event__creator=self.request.user) return [] class GroupStageViewSet(SoftDeleteViewSet): queryset = GroupStage.objects.all() serializer_class = GroupStageSerializer def get_queryset(self): tournament_id = self.request.query_params.get('store_id') or self.request.query_params.get('tournament') if tournament_id: return self.queryset.filter(tournament=tournament_id) if self.request.user: return self.queryset.filter(tournament__event__creator=self.request.user) return [] class MatchViewSet(SoftDeleteViewSet): queryset = Match.objects.all() serializer_class = MatchSerializer def get_queryset(self): tournament_id = self.request.query_params.get('store_id') or self.request.query_params.get('tournament') if tournament_id: return self.queryset.filter(Q(group_stage__tournament=tournament_id) | Q(round__tournament=tournament_id)) if self.request.user: return self.queryset.filter(Q(group_stage__tournament__event__creator=self.request.user) | Q(round__tournament__event__creator=self.request.user)) return [] class TeamScoreViewSet(SoftDeleteViewSet): queryset = TeamScore.objects.all() serializer_class = TeamScoreSerializer def get_queryset(self): tournament_id = self.request.query_params.get('store_id') or self.request.query_params.get('tournament') if tournament_id: q = Q(team_registration__tournament=tournament_id) | Q(match__group_stage__tournament=tournament_id) | Q(match__round__tournament=tournament_id) return self.queryset.filter(q) if self.request.user: return self.queryset.filter(team_registration__tournament__event__creator=self.request.user) return [] class TeamRegistrationViewSet(SoftDeleteViewSet): queryset = TeamRegistration.objects.all() serializer_class = TeamRegistrationSerializer def get_queryset(self): tournament_id = self.request.query_params.get('store_id') or self.request.query_params.get('tournament') if tournament_id: return self.queryset.filter(tournament=tournament_id) if self.request.user: return self.queryset.filter(tournament__event__creator=self.request.user) return [] class PlayerRegistrationViewSet(SoftDeleteViewSet): queryset = PlayerRegistration.objects.all() serializer_class = PlayerRegistrationSerializer def get_queryset(self): tournament_id = self.request.query_params.get('store_id') or self.request.query_params.get('tournament') if tournament_id: return self.queryset.filter(team_registration__tournament=tournament_id) if self.request.user: return self.queryset.filter(team_registration__tournament__event__creator=self.request.user) return [] class CourtViewSet(SoftDeleteViewSet): queryset = Court.objects.all() serializer_class = CourtSerializer class DateIntervalViewSet(SoftDeleteViewSet): queryset = DateInterval.objects.all() serializer_class = DateIntervalSerializer def get_queryset(self): if self.request.user.is_anonymous: return [] return self.queryset.filter( Q(event__creator=self.request.user) ) class FailedApiCallViewSet(viewsets.ModelViewSet): queryset = FailedApiCall.objects.all() serializer_class = FailedApiCallSerializer permission_classes = [] # FailedApiCall are public whereas the other requests are only for logged users def get_queryset(self): return [] def perform_create(self, serializer): if self.request.user.is_anonymous == False: serializer.save(user=self.request.user) else: serializer.save() class LogViewSet(viewsets.ModelViewSet): queryset = Log.objects.all() serializer_class = LogSerializer permission_classes = [] # Log are public whereas the other requests are only for logged users def get_queryset(self): return [] def perform_create(self, serializer): if self.request.user.is_anonymous == False: serializer.save(user=self.request.user) else: serializer.save() class DeviceTokenViewSet(viewsets.ModelViewSet): queryset = DeviceToken.objects.all() serializer_class = DeviceTokenSerializer def get_queryset(self): if self.request.user: return self.queryset.filter(user=self.request.user) return [] def create(self, request, *args, **kwargs): value = request.data.get('value') if DeviceToken.objects.filter(value=value).exists(): return Response({"detail": "This device token is already registered."}, status=208) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def perform_create(self, serializer): serializer.save(user=self.request.user) class DrawLogViewSet(SoftDeleteViewSet): queryset = DrawLog.objects.all() serializer_class = DrawLogSerializer def get_queryset(self): tournament_id = self.request.query_params.get('tournament') or self.request.query_params.get('tournament') if tournament_id: return self.queryset.filter(tournament=tournament_id) if self.request.user: return self.queryset.filter(tournament__event__creator=self.request.user) return [] class UnregisteredTeamViewSet(SoftDeleteViewSet): queryset = UnregisteredTeam.objects.all() serializer_class = UnregisteredTeamSerializer def get_queryset(self): tournament_id = self.request.query_params.get('tournament') if tournament_id: return self.queryset.filter(tournament=tournament_id) if self.request.user: return self.queryset.filter(tournament__event__creator=self.request.user) return [] class UnregisteredPlayerViewSet(SoftDeleteViewSet): queryset = UnregisteredPlayer.objects.all() serializer_class = UnregisteredPlayerSerializer def get_queryset(self): tournament_id = self.request.query_params.get('tournament') if tournament_id: return self.queryset.filter(unregistered_team__tournament=tournament_id) if self.request.user: return self.queryset.filter(unregistered_team__tournament__event__creator=self.request.user) return [] class ShortUserViewSet(viewsets.ModelViewSet): queryset = CustomUser.objects.all() serializer_class = ShortUserSerializer permission_classes = [] # Users are public whereas the other requests are only for logged users def get_queryset(self): return self.request.user.agents @api_view(['POST']) @permission_classes([IsAuthenticated]) def process_refund(request, team_registration_id): try: # Verify the user is the tournament umpire team_registration = get_object_or_404(TeamRegistration, id=team_registration_id) if request.user != team_registration.tournament.event.creator: return Response({ 'success': False, 'message': "Vous n'êtes pas autorisé à effectuer ce remboursement" }, status=403) payment_service = PaymentService(request) players_serializer = PlayerRegistrationSerializer(team_registration.players_sorted_by_rank, many=True) success, message, refund = payment_service.process_refund(team_registration_id) return Response({ 'success': success, 'message': message, 'players': players_serializer.data }) except Exception as e: return Response({ 'success': False, 'message': str(e) }, status=400) @api_view(['POST']) @permission_classes([IsAuthenticated]) def validate_stripe_account(request): stripe.api_key = settings.STRIPE_SECRET_KEY # Parse the request body data = json.loads(request.body) account_id = data.get('account_id') if not account_id: return Response({ 'valid': False, 'error': 'Account ID is required' }, status=400) # Try to retrieve the account from Stripe try: # Basic account verification account = stripe.Account.retrieve(account_id) # Only check if the account can receive payments is_valid = account.id is not None return Response({ 'valid': is_valid, 'account': { 'id': account.id } }) except stripe.error.PermissionError: return Response({ 'valid': False, 'error': 'No permission to access this account' }, status=403) except stripe.error.InvalidRequestError: return Response({ 'valid': False, 'error': 'Invalid account ID' }, status=400) @api_view(['POST']) @permission_classes([IsAuthenticated]) def xls_to_csv(request): # Check if the request has a file if 'file' in request.FILES: uploaded_file = request.FILES['file'] # Save the uploaded file directory = 'tmp/csv/' file_path = os.path.join(directory, uploaded_file.name) file_name = default_storage.save(file_path, ContentFile(uploaded_file.read())) # Check available sheets and look for 'inscriptions' xls = pd.ExcelFile(file_name) sheet_names = xls.sheet_names # Determine which sheet to use target_sheet = 0 # Default to first sheet if 'inscriptions' in [name.lower() for name in sheet_names]: for i, name in enumerate(sheet_names): if name.lower() == 'inscriptions': target_sheet = i # or use the name directly: target_sheet = name break # Convert to csv and save data_xls = pd.read_excel(file_name, sheet_name=target_sheet, index_col=None) csv_file_name = create_random_filename('players', 'csv') output_path = os.path.join(directory, csv_file_name) data_xls.to_csv(output_path, sep=';', index=False, encoding='utf-8') # Send the processed file back with default_storage.open(output_path, 'rb') as file: response = HttpResponse(file.read(), content_type='application/octet-stream') response['Content-Disposition'] = f'attachment; filename="players.csv"' # Clean up: delete both files default_storage.delete(file_path) default_storage.delete(output_path) return response else: return HttpResponse("No file was uploaded", status=400) @api_view(['GET']) @permission_classes([IsAuthenticated]) def get_tournament_config(request): """Return tournament-related configuration settings""" config = settings.TOURNAMENT_SETTINGS return Response({ 'time_proximity_rules': config['TIME_PROXIMITY_RULES'], 'waiting_list_rules': config['WAITING_LIST_RULES'], 'business_rules': config['BUSINESS_RULES'], 'minimum_response_time': config['MINIMUM_RESPONSE_TIME'] }) @api_view(['GET']) @permission_classes([IsAuthenticated]) def get_payment_config(request): """Return payment-related configuration settings""" return Response({ 'stripe_fee': getattr(settings, 'STRIPE_FEE', 0) })