diff --git a/api/serializers.py b/api/serializers.py index c483f47..3f24200 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -11,6 +11,7 @@ from django.core.mail import EmailMessage from django.contrib.sites.shortcuts import get_current_site from api.tokens import account_activation_token +from tournaments.models.data_access import DataAccess class UserSerializer(serializers.ModelSerializer): @@ -225,3 +226,9 @@ class DeviceTokenSerializer(serializers.ModelSerializer): model = DeviceToken fields = '__all__' read_only_fields = ['user'] + +class DataAccessSerializer(serializers.ModelSerializer): + class Meta: + model = DataAccess + fields = '__all__' + read_only_fields = ['user'] diff --git a/api/urls.py b/api/urls.py index 6c894fb..171aaa2 100644 --- a/api/urls.py +++ b/api/urls.py @@ -21,6 +21,7 @@ router.register(r'date-intervals', views.DateIntervalViewSet) router.register(r'failed-api-calls', views.FailedApiCallViewSet) router.register(r'logs', views.LogViewSet) router.register(r'device-token', views.DeviceTokenViewSet) +router.register(r'data-access', views.DataAccessViewSet) urlpatterns = [ path('', include(router.urls)), diff --git a/api/views.py b/api/views.py index 11beff4..b8ed962 100644 --- a/api/views.py +++ b/api/views.py @@ -1,5 +1,5 @@ -from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, TournamentSerializer, UserSerializer, ChangePasswordSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, LiveMatchSerializer, PurchaseSerializer, CustomUserSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer -from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken, ModelLog +from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, TournamentSerializer, UserSerializer, ChangePasswordSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, LiveMatchSerializer, PurchaseSerializer, CustomUserSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer, DataAccessSerializer +from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken, ModelLog, DataAccess from rest_framework import viewsets, permissions from rest_framework.authtoken.models import Token @@ -32,6 +32,7 @@ class DataApi(APIView): model_name = request.data.get('model_name') data = request.data.get('data') store_id = request.data.get('store_id') + print(f"DataApi post > {model_operation} {model_name}") serializer_class = build_serializer_class(model_name) @@ -42,8 +43,9 @@ class DataApi(APIView): instance = model.objects.get(id=data_id) if model_operation == 'DELETE': - return self.delete_and_save_log(request, data_id, model_operation, model_name, store_id) - else: # POST/PUT + parent_model, parent_id = instance.get_parent_reference() + return self.delete_and_save_log(request, data_id, model_operation, model_name, store_id, parent_id, parent_model) + else: # PUT serializer = serializer_class(instance, data=data, context={'request': request}) if serializer.is_valid(): if instance.last_update <= serializer.validated_data.get('last_update'): @@ -54,8 +56,7 @@ class DataApi(APIView): return Response(serializer.data, status=status.HTTP_203_NON_AUTHORITATIVE_INFORMATION) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - except model.DoesNotExist: - # If the instance doesn't exist, we should be in a POST situation + except model.DoesNotExist: # POST print('>>> insert') serializer = serializer_class(data=data, context={'request': request}) if serializer.is_valid(): @@ -65,18 +66,21 @@ class DataApi(APIView): def save_and_create_log(self, request, serializer, model_operation, model_name, store_id): instance = serializer.save() + parent_model, parent_id = instance.get_parent_reference() self.create_and_save_model_log( user=request.user, model_operation=model_operation, model_name=model_name, model_id=instance.id, - store_id=store_id + store_id=store_id, + parent_id=parent_id, + parent_model=parent_model ) return Response(serializer.data, status=status.HTTP_201_CREATED) - def delete_and_save_log(self, request, data_id, model_operation, model_name, store_id): + def delete_and_save_log(self, request, data_id, model_operation, model_name, store_id, parent_id, parent_model): model = apps.get_model(app_label='tournaments', model_name=model_name) print(model) @@ -94,12 +98,14 @@ class DataApi(APIView): model_operation=model_operation, model_name=model_name, model_id=data_id, - store_id=store_id + store_id=store_id, + parent_id=parent_id, + parent_model=parent_model ) return Response(status=status.HTTP_204_NO_CONTENT) - def create_and_save_model_log(self, user, model_operation, model_name, model_id, store_id): + def create_and_save_model_log(self, user, model_operation, model_name, model_id, store_id, parent_id, parent_model): model_log = ModelLog() model_log.user = user model_log.operation = model_operation @@ -107,6 +113,8 @@ class DataApi(APIView): model_log.model_name = model_name model_log.model_id = model_id model_log.store_id = store_id + model_log.parent_model_id = parent_id + model_log.parent_model_name = parent_model model_log.save() def get(self, request, *args, **kwargs): @@ -123,7 +131,9 @@ class DataApi(APIView): except ValueError: return Response({"error": f"Invalid date format for last_update: {last_update}"}, status=status.HTTP_400_BAD_REQUEST) - logs = ModelLog.objects.filter(date__gt=last_update).order_by('date') + data_access = DataAccess.objects.filter(shared_with=request.user).values_list('model_id', flat=True) + log_query = Q(date__gt=last_update) & (Q(user=request.user) | Q(model_id__in=data_access) | Q(parent_model_id__in=data_access)) + logs = ModelLog.objects.filter(log_query).order_by('date') updates = defaultdict(dict) deletions = defaultdict(list) @@ -133,15 +143,20 @@ class DataApi(APIView): if log.operation in ['POST', 'PUT']: try: - instance = model.objects.get(id=log.model_id) - serializer_class = build_serializer_class(log.model_name) - serializer = serializer_class(instance) - updates[log.model_name][log.model_id] = serializer.data + data = self.get_data(model, log) + + # instance = model.objects.get(id=log.model_id) + # serializer_class = build_serializer_class(log.model_name) + # serializer = serializer_class(instance) + updates[log.model_name][log.model_id] = data except model.DoesNotExist: # If the instance doesn't exist, it might have been deleted after this log was created pass elif log.operation == 'DELETE': deletions[log.model_name].append({'model_id': log.model_id, 'store_id': log.store_id}) + elif log.operation == 'SHARE': + data = self.get_data(model, log) + updates[log.model_name][log.model_id] = data # Convert updates dict to list for each model for model_name in updates: @@ -151,12 +166,7 @@ class DataApi(APIView): for model_name in deletions: deletions[model_name] = deletions[model_name] - # local_time = timezone.localtime(timezone.now()) - # print(local_time.isoformat(timespec='seconds')) - - date = logs.last().date.astimezone().isoformat(timespec='seconds') if logs else None - print(date) response_data = { "updates": dict(updates), @@ -166,6 +176,12 @@ class DataApi(APIView): return Response(response_data, status=status.HTTP_200_OK) + def get_data(self, model, log): + instance = model.objects.get(id=log.model_id) + serializer_class = build_serializer_class(log.model_name) + serializer = serializer_class(instance) + return serializer.data + class CustomAuthToken(APIView): permission_classes = [] @@ -245,16 +261,31 @@ class TournamentViewSet(viewsets.ModelViewSet): def get_queryset(self): if self.request.user.is_anonymous: return [] - return self.queryset.filter(event__creator=self.request.user) + + return self.queryset.filter( + Q(event__creator=self.request.user) | + Q(id__in=DataAccess.objects.filter( + shared_with=self.request.user, + model_name=self.queryset.model.__name__ + ).values_list('model_id', flat=True)) + ) class PurchaseViewSet(viewsets.ModelViewSet): queryset = Purchase.objects.all() serializer_class = PurchaseSerializer def get_queryset(self): - if self.request.user: - return self.queryset.filter(user=self.request.user) - return [] + if self.request.user.is_anonymous: + return [] + + # return self.queryset.filter(user=self.request.user) + return self.queryset.filter( + Q(user=self.request.user) | + Q(id__in=DataAccess.objects.filter( + shared_with=self.request.user, + model_name=self.queryset.model.__name__ + ).values_list('model_id', flat=True)) + ) def put(self, request, pk): raise MethodNotAllowed('PUT') @@ -290,7 +321,14 @@ class EventViewSet(viewsets.ModelViewSet): def get_queryset(self): if self.request.user.is_anonymous: return [] - return self.queryset.filter(creator=self.request.user) + # return self.queryset.filter(creator=self.request.user) + return self.queryset.filter( + Q(creator=self.request.user) | + Q(id__in=DataAccess.objects.filter( + shared_with=self.request.user, + model_name=self.queryset.model.__name__ + ).values_list('model_id', flat=True)) + ) class RoundViewSet(viewsets.ModelViewSet): queryset = Round.objects.all() @@ -373,9 +411,16 @@ class DateIntervalViewSet(viewsets.ModelViewSet): serializer_class = DateIntervalSerializer def get_queryset(self): - if self.request.user: - return self.queryset.filter(event__creator=self.request.user) - return [] + if self.request.user.is_anonymous: + return [] + + return self.queryset.filter( + Q(event__creator=self.request.user) | + Q(event__id__in=DataAccess.objects.filter( + shared_with=self.request.user, + model_name=self.queryset.model.__name__ + ).values_list('model_id', flat=True)) + ) class FailedApiCallViewSet(viewsets.ModelViewSet): queryset = FailedApiCall.objects.all() @@ -427,3 +472,12 @@ class DeviceTokenViewSet(viewsets.ModelViewSet): def perform_create(self, serializer): serializer.save(user=self.request.user) + +class DataAccessViewSet(viewsets.ModelViewSet): + queryset = DataAccess.objects.all() + serializer_class = DataAccessSerializer + + def get_queryset(self): + if self.request.user: + return self.queryset.filter(owner=self.request.user) + return [] diff --git a/tournaments/admin.py b/tournaments/admin.py index f44b95e..0be7cce 100644 --- a/tournaments/admin.py +++ b/tournaments/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from tournaments.models import team_registration +from tournaments.models.data_access import DataAccess from tournaments.models.device_token import DeviceToken from .models import Club, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, Court, DateInterval, FailedApiCall, Log, ModelLog @@ -98,6 +99,11 @@ class ModelLogAdmin(admin.ModelAdmin): list_filter = ['user'] ordering = ['-date'] +class DataAccessAdmin(admin.ModelAdmin): + list_display = ['owner', 'shared_with', 'model_name', 'model_id', 'granted_at'] + list_filter = ['owner', 'shared_with'] + ordering = ['-granted_at'] + admin.site.register(CustomUser, CustomUserAdmin) admin.site.register(Club, ClubAdmin) admin.site.register(Event, EventAdmin) @@ -115,3 +121,4 @@ admin.site.register(FailedApiCall, FailedApiCallAdmin) admin.site.register(Log, LogAdmin) admin.site.register(DeviceToken, DeviceTokenAdmin) admin.site.register(ModelLog, ModelLogAdmin) +admin.site.register(DataAccess, DataAccessAdmin) diff --git a/tournaments/migrations/0092_dataaccess.py b/tournaments/migrations/0092_dataaccess.py new file mode 100644 index 0000000..395fec9 --- /dev/null +++ b/tournaments/migrations/0092_dataaccess.py @@ -0,0 +1,27 @@ +# Generated by Django 5.1 on 2024-11-01 08:02 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0091_modellog_store_id'), + ] + + operations = [ + migrations.CreateModel( + name='DataAccess', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('model_name', models.CharField(max_length=50)), + ('model_id', models.UUIDField()), + ('granted_at', models.DateTimeField(auto_now_add=True)), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owned_data', to=settings.AUTH_USER_MODEL)), + ('shared_with', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='shared_data', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/tournaments/models/__init__.py b/tournaments/models/__init__.py index 3fd8708..8d295c1 100644 --- a/tournaments/models/__init__.py +++ b/tournaments/models/__init__.py @@ -18,3 +18,4 @@ from .failed_api_call import FailedApiCall from .log import Log from .device_token import DeviceToken from .model_log import ModelLog +from .data_access import DataAccess diff --git a/tournaments/models/base.py b/tournaments/models/base.py index 6c85543..563d1c8 100644 --- a/tournaments/models/base.py +++ b/tournaments/models/base.py @@ -8,6 +8,10 @@ class BaseModel(models.Model): class Meta: abstract = True + def get_parent_reference(self): + """Override in child models to provide parent reference""" + return None, None + class SideStoreModel(BaseModel): store_id = models.CharField(max_length=100) diff --git a/tournaments/models/data_access.py b/tournaments/models/data_access.py new file mode 100644 index 0000000..37f95be --- /dev/null +++ b/tournaments/models/data_access.py @@ -0,0 +1,41 @@ +from django.db import models +from django.utils import timezone +from django.apps import apps +import uuid + +from . import ModelLog, SideStoreModel + +class DataAccess(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4) + owner = models.ForeignKey('CustomUser', related_name='owned_data', on_delete=models.CASCADE) + shared_with = models.ForeignKey('CustomUser', related_name='shared_data', on_delete=models.CASCADE) + model_name = models.CharField(max_length=50) + model_id = models.UUIDField() + granted_at = models.DateTimeField(auto_now_add=True) + + def save(self, *args, **kwargs): + is_new = self._state.adding # Check if this is a new DataAccess + super().save(*args, **kwargs) + + if is_new: + self.create_initial_sync_logs() + + def create_initial_sync_logs(self): + + model_class = apps.get_model(self.model_name) + obj = model_class.objects.get(id=self.model_id) + parent_model, parent_id = obj.get_parent_reference() + store_id = None + if isinstance(obj, SideStoreModel): + store_id = obj.store_id + + ModelLog.objects.create( + user=self.shared_with, # The user receiving access + model_id=self.model_id, + model_name=self.model_name, + operation='SHARE', # New operation type + date=timezone.now(), + store_id=store_id, + parent_model_id=parent_id, + parent_model_name=parent_model + ) diff --git a/tournaments/models/model_log.py b/tournaments/models/model_log.py index 9bc15f1..b4d9ea7 100644 --- a/tournaments/models/model_log.py +++ b/tournaments/models/model_log.py @@ -10,3 +10,5 @@ class ModelLog(models.Model): date = models.DateTimeField() model_name = models.CharField(max_length=50) store_id = models.CharField(max_length=200, blank=True, null=True) + parent_model_id = models.UUIDField(null=True) + parent_model_name = models.CharField(max_length=50, null=True) diff --git a/tournaments/models/views.py b/tournaments/models/views.py new file mode 100644 index 0000000..4114602 --- /dev/null +++ b/tournaments/models/views.py @@ -0,0 +1,472 @@ +from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, TournamentSerializer, UserSerializer, ChangePasswordSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, LiveMatchSerializer, PurchaseSerializer, CustomUserSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer, DataAccessSerializer +from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken, ModelLog, DataAccess + +from rest_framework import viewsets, permissions +from rest_framework.authtoken.models import Token +from rest_framework.response import Response +from rest_framework.decorators import api_view +from rest_framework import status +from rest_framework.generics import UpdateAPIView +from rest_framework.exceptions import MethodNotAllowed +from rest_framework.permissions import IsAuthenticated +from rest_framework.views import APIView + +from django.contrib.auth import authenticate +from django.db.models import Q +from django.core.exceptions import ObjectDoesNotExist +from django.utils import timezone +from django.apps import apps + +from collections import defaultdict + +from .permissions import IsClubOwner +from .utils import is_valid_email, build_serializer_class + +class DataApi(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, *args, **kwargs): + + # unfold content + model_operation = request.data.get('operation') + model_name = request.data.get('model_name') + data = request.data.get('data') + store_id = request.data.get('store_id') + + print(f"DataApi post > {model_operation} {model_name}") + + serializer_class = build_serializer_class(model_name) + + model = apps.get_model(app_label='tournaments', model_name=model_name) + try: + data_id = data.get('id') + instance = model.objects.get(id=data_id) + + if model_operation == 'DELETE': + parent_model, parent_id = instance.get_parent_reference() + return self.delete_and_save_log(request, data_id, model_operation, model_name, store_id, parent_id, parent_model) + else: # PUT + serializer = serializer_class(instance, data=data, context={'request': request}) + if serializer.is_valid(): + if instance.last_update <= serializer.validated_data.get('last_update'): + print('>>> update') + return self.save_and_create_log(request, serializer, model_operation, model_name, store_id) + else: + print('>>> return 203') + return Response(serializer.data, status=status.HTTP_203_NON_AUTHORITATIVE_INFORMATION) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + except model.DoesNotExist: # POST + print('>>> insert') + serializer = serializer_class(data=data, context={'request': request}) + if serializer.is_valid(): + return self.save_and_create_log(request, serializer, model_operation, model_name, store_id) + else: + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def save_and_create_log(self, request, serializer, model_operation, model_name, store_id): + instance = serializer.save() + parent_model, parent_id = instance.get_parent_reference() + + self.create_and_save_model_log( + user=request.user, + model_operation=model_operation, + model_name=model_name, + model_id=instance.id, + store_id=store_id, + parent_id=parent_id, + parent_model=parent_model + ) + + return Response(serializer.data, status=status.HTTP_201_CREATED) + + def delete_and_save_log(self, request, data_id, model_operation, model_name, store_id, parent_id, parent_model): + + model = apps.get_model(app_label='tournaments', model_name=model_name) + print(model) + try: + instance = model.objects.get(id=data_id) + instance.delete() + except model.DoesNotExist: + return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND) + + # we delete all logs linked to the instance because they won't be needed anymore + ModelLog.objects.filter(model_id=data_id).delete() + + self.create_and_save_model_log( + user=request.user, + model_operation=model_operation, + model_name=model_name, + model_id=data_id, + store_id=store_id, + parent_id=parent_id, + parent_model=parent_model + ) + + return Response(status=status.HTTP_204_NO_CONTENT) + + def create_and_save_model_log(self, user, model_operation, model_name, model_id, store_id, parent_id, parent_model): + model_log = ModelLog() + model_log.user = user + model_log.operation = model_operation + model_log.date = timezone.localtime(timezone.now()) + model_log.model_name = model_name + model_log.model_id = model_id + model_log.store_id = store_id + model_log.parent_model_id = parent_id + model_log.parent_model_name = parent_model + model_log.save() + + def get(self, request, *args, **kwargs): + print('/data GET YEAH!') + + last_update = request.query_params.get('last_update') + if not last_update: + return Response({"error": "last_update parameter is required"}, status=status.HTTP_400_BAD_REQUEST) + + print(last_update) + + try: + last_update = timezone.datetime.fromisoformat(last_update) + except ValueError: + return Response({"error": f"Invalid date format for last_update: {last_update}"}, status=status.HTTP_400_BAD_REQUEST) + + data_access = DataAccess.objects.filter(shared_with=request.user).values_list('model_id', flat=True) + log_query = Q(date__gt=last_update) & (Q(user=request.user) | Q(model_id__in=data_access)) + logs = ModelLog.objects.filter(log_query).order_by('date') + + updates = defaultdict(dict) + deletions = defaultdict(list) + + for log in logs: + model = apps.get_model(app_label='tournaments', model_name=log.model_name) + + if log.operation in ['POST', 'PUT']: + try: + instance = model.objects.get(id=log.model_id) + serializer_class = build_serializer_class(log.model_name) + serializer = serializer_class(instance) + updates[log.model_name][log.model_id] = serializer.data + except model.DoesNotExist: + # If the instance doesn't exist, it might have been deleted after this log was created + pass + elif log.operation == 'DELETE': + deletions[log.model_name].append({'model_id': log.model_id, 'store_id': log.store_id}) + + # Convert updates dict to list for each model + for model_name in updates: + updates[model_name] = list(updates[model_name].values()) + + # Convert deletions set to list for each model + for model_name in deletions: + deletions[model_name] = deletions[model_name] + + date = logs.last().date.astimezone().isoformat(timespec='seconds') if logs else None + + response_data = { + "updates": dict(updates), + "deletions": dict(deletions), + "date": date + } + + return Response(response_data, status=status.HTTP_200_OK) + +class CustomAuthToken(APIView): + permission_classes = [] + + def post(self, request, *args, **kwargs): + username = request.data.get('username') + password = request.data.get('password') + device_id = request.data.get('device_id') + + user = authenticate(username=username, password=password) + + if user is None and is_valid_email(username) == True: + true_username = self.get_username_from_email(username) + user = authenticate(username=true_username, password=password) + + if user is not None: + + # if user.device_id is None or user.device_id == device_id or user.username == 'apple-test': + # if user.device_id is None or user.device_id == device_id or user.username == 'apple-test': + user.device_id = device_id + user.save() + + token, created = Token.objects.get_or_create(user=user) + return Response({'token': token.key}) + # else: + # return Response({'error': 'Vous ne pouvez pour l\'instant vous connecter sur plusieurs appareils en même temps. Veuillez vous déconnecter du précédent appareil. Autrement, veuillez contacter le support.'}, status=status.HTTP_403_FORBIDDEN) + else: + return Response({'error': 'L\'utilisateur et le mot de passe de correspondent pas'}, status=status.HTTP_401_UNAUTHORIZED) + + def get_username_from_email(self, email): + try: + user = CustomUser.objects.get(email=email) + return user.username + except ObjectDoesNotExist: + return None # or handle the case where the user doesn't exist + +class Logout(APIView): + permission_classes = (IsAuthenticated,) + + def post(self, request, *args, **kwargs): + # request.user.auth_token.delete() + + device_id = request.data.get('device_id') + if request.user.device_id == device_id: + request.user.device_id = None + request.user.save() + + return Response(status=status.HTTP_200_OK) + +@api_view(['GET']) +def user_by_token(request): + serializer = UserSerializer(request.user) + return Response(serializer.data, status=status.HTTP_200_OK) + +class UserViewSet(viewsets.ModelViewSet): + 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(viewsets.ModelViewSet): + 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(viewsets.ModelViewSet): + queryset = Tournament.objects.all() + serializer_class = TournamentSerializer + + def get_queryset(self): + if self.request.user.is_anonymous: + return [] + + return self.queryset.filter( + Q(event__creator=self.request.user) | + Q(id__in=DataAccess.objects.filter( + shared_with=self.request.user, + model_name=self.queryset.model.__name__ + ).values_list('model_id', flat=True)) + ) + +class PurchaseViewSet(viewsets.ModelViewSet): + queryset = Purchase.objects.all() + serializer_class = PurchaseSerializer + + def get_queryset(self): + if self.request.user.is_anonymous: + return [] + + # return self.queryset.filter(user=self.request.user) + return self.queryset.filter( + Q(user=self.request.user) | + Q(id__in=DataAccess.objects.filter( + shared_with=self.request.user, + model_name=self.queryset.model.__name__ + ).values_list('model_id', flat=True)) + ) + + def put(self, request, pk): + raise MethodNotAllowed('PUT') + + def patch(self, request, pk): + raise MethodNotAllowed('PATCH') + + def delete(self, request, pk): + raise MethodNotAllowed('DELETE') + +# class ExpandedTournamentViewSet(viewsets.ModelViewSet): +# queryset = Tournament.objects.all() +# serializer_class = ExpandedTournamentSerializer + +class ChangePasswordView(UpdateAPIView): + serializer_class = ChangePasswordSerializer + + def update(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = serializer.save() + # if using drf authtoken, create a new token + if hasattr(user, 'auth_token'): + user.auth_token.delete() + token, created = Token.objects.get_or_create(user=user) + # return new token + return Response({'token': token.key}, status=status.HTTP_200_OK) + +class EventViewSet(viewsets.ModelViewSet): + 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) | + Q(id__in=DataAccess.objects.filter( + shared_with=self.request.user, + model_name=self.queryset.model.__name__ + ).values_list('model_id', flat=True)) + ) + +class RoundViewSet(viewsets.ModelViewSet): + queryset = Round.objects.all() + serializer_class = RoundSerializer + + 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 GroupStageViewSet(viewsets.ModelViewSet): + queryset = GroupStage.objects.all() + serializer_class = GroupStageSerializer + + 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 MatchViewSet(viewsets.ModelViewSet): + queryset = Match.objects.all() + serializer_class = MatchSerializer + + def get_queryset(self): + tournament_id = 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(viewsets.ModelViewSet): + queryset = TeamScore.objects.all() + serializer_class = TeamScoreSerializer + + def get_queryset(self): + tournament_id = 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 TeamRegistrationViewSet(viewsets.ModelViewSet): + queryset = TeamRegistration.objects.all() + serializer_class = TeamRegistrationSerializer + + 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 PlayerRegistrationViewSet(viewsets.ModelViewSet): + queryset = PlayerRegistration.objects.all() + serializer_class = PlayerRegistrationSerializer + + def get_queryset(self): + tournament_id = 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(viewsets.ModelViewSet): + queryset = Court.objects.all() + serializer_class = CourtSerializer + +class DateIntervalViewSet(viewsets.ModelViewSet): + 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) | + Q(event__id__in=DataAccess.objects.filter( + shared_with=self.request.user, + model_name=self.queryset.model.__name__ + ).values_list('model_id', flat=True)) + ) + +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 DataAccessViewSet(viewsets.ModelViewSet): + queryset = DataAccess.objects.all() + serializer_class = DataAccessSerializer + + def get_queryset(self): + if self.request.user: + return self.queryset.filter(owner=self.request.user) + return []