first commit

sync
Laurent 1 year ago
parent 19a55869a9
commit 0fe9272002
  1. 16
      api/serializers.py
  2. 2
      api/urls.py
  3. 18
      api/utils.py
  4. 158
      api/views.py
  5. 1
      asgi.py
  6. 26
      padelclub_backend/asgi.py
  7. 1
      padelclub_backend/routing.py
  8. 2
      padelclub_backend/settings.py
  9. 6
      padelclub_backend/settings_app.py
  10. 5
      requirements.txt
  11. 7
      tournaments/admin.py
  12. 41
      tournaments/consumers.py
  13. 24
      tournaments/migrations/0080_modellog.py
  14. 18
      tournaments/migrations/0081_alter_modellog_operation.py
  15. 18
      tournaments/migrations/0082_alter_modellog_operation.py
  16. 20
      tournaments/migrations/0083_modellog_creator.py
  17. 18
      tournaments/migrations/0084_rename_creator_modellog_user.py
  18. 18
      tournaments/migrations/0085_modellog_store_id.py
  19. 154
      tournaments/migrations/0086_club_creation_date_club_last_update_and_more.py
  20. 17
      tournaments/migrations/0087_remove_modellog_store_id.py
  21. 19
      tournaments/migrations/0088_customuser_last_update.py
  22. 49
      tournaments/migrations/0089_groupstage_store_id_match_store_id_and_more.py
  23. 79
      tournaments/migrations/0090_dynamic_store_id.py
  24. 4
      tournaments/models/__init__.py
  25. 15
      tournaments/models/base.py
  26. 3
      tournaments/models/club.py
  27. 4
      tournaments/models/court.py
  28. 5
      tournaments/models/custom_user.py
  29. 3
      tournaments/models/date_interval.py
  30. 3
      tournaments/models/device_token.py
  31. 5
      tournaments/models/enums.py
  32. 4
      tournaments/models/event.py
  33. 4
      tournaments/models/failed_api_call.py
  34. 8
      tournaments/models/group_stage.py
  35. 4
      tournaments/models/log.py
  36. 7
      tournaments/models/match.py
  37. 12
      tournaments/models/model_log.py
  38. 7
      tournaments/models/player_registration.py
  39. 4
      tournaments/models/purchase.py
  40. 7
      tournaments/models/round.py
  41. 7
      tournaments/models/team_registration.py
  42. 15
      tournaments/models/team_score.py
  43. 4
      tournaments/models/tournament.py
  44. 9
      tournaments/routing.py
  45. 1
      tournaments/static/tournaments/ja.html

@ -34,6 +34,7 @@ class UserSerializer(serializers.ModelSerializer):
user = CustomUser.objects.create_user( user = CustomUser.objects.create_user(
username=validated_data['username'], username=validated_data['username'],
last_update=validated_data.get('last_update'),
email=validated_data['email'], email=validated_data['email'],
password=validated_data['password'], password=validated_data['password'],
first_name=validated_data['first_name'], first_name=validated_data['first_name'],
@ -78,7 +79,7 @@ class UserSerializer(serializers.ModelSerializer):
model = CustomUser model = CustomUser
fields = '__all__' # ['id', 'username', 'password', 'umpire_code', 'clubs', 'phone', 'first_name', 'last_name', 'licence_id'] fields = '__all__' # ['id', 'username', 'password', 'umpire_code', 'clubs', 'phone', 'first_name', 'last_name', 'licence_id']
class UserUpdateSerializer(serializers.ModelSerializer): class CustomUserSerializer(serializers.ModelSerializer): ### the one matching the CustomUser class and used for sync
class Meta: class Meta:
model = CustomUser model = CustomUser
fields = CustomUser.fields_for_update() fields = CustomUser.fields_for_update()
@ -86,7 +87,12 @@ class UserUpdateSerializer(serializers.ModelSerializer):
class ClubSerializer(serializers.ModelSerializer): class ClubSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Club model = Club
fields = '__all__' # ['id', 'name', 'acronym', 'phone', 'code', 'federal_club_data', 'address', 'city', 'zip_code', 'latitude', 'longitude'] fields = '__all__'
def create(self, validated_data):
user = self.context['request'].user
validated_data['creator'] = user
return super().create(validated_data)
class TournamentSerializer(serializers.ModelSerializer): class TournamentSerializer(serializers.ModelSerializer):
class Meta: class Meta:
@ -153,10 +159,16 @@ class PlayerRegistrationSerializer(serializers.ModelSerializer):
# ['id', 'team_registration_id', 'first_name', 'last_name', 'licence_id', 'rank', 'has_paid'] # ['id', 'team_registration_id', 'first_name', 'last_name', 'licence_id', 'rank', 'has_paid']
class PurchaseSerializer(serializers.ModelSerializer): class PurchaseSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Purchase model = Purchase
fields = '__all__' fields = '__all__'
def create(self, validated_data):
user = self.context['request'].user
validated_data['user'] = user
return super().create(validated_data)
class ChangePasswordSerializer(serializers.Serializer): class ChangePasswordSerializer(serializers.Serializer):
old_password = serializers.CharField(max_length=128, write_only=True, required=True) old_password = serializers.CharField(max_length=128, write_only=True, required=True)
new_password1 = serializers.CharField(max_length=128, write_only=True, required=True) new_password1 = serializers.CharField(max_length=128, write_only=True, required=True)

@ -25,6 +25,8 @@ router.register(r'device-token', views.DeviceTokenViewSet)
urlpatterns = [ urlpatterns = [
path('', include(router.urls)), path('', include(router.urls)),
path('data/', views.DataApi.as_view(), name="data"),
path('api-token-auth/', obtain_auth_token, name='api_token_auth'), path('api-token-auth/', obtain_auth_token, name='api_token_auth'),
path("user-by-token/", views.user_by_token, name="user_by_token"), path("user-by-token/", views.user_by_token, name="user_by_token"),
path("change-password/", views.ChangePasswordView.as_view(), name="change_password"), path("change-password/", views.ChangePasswordView.as_view(), name="change_password"),

@ -1,5 +1,23 @@
import re import re
import importlib
def is_valid_email(email): def is_valid_email(email):
email_regex = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$' email_regex = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
return re.match(email_regex, email) is not None return re.match(email_regex, email) is not None
def build_serializer_class(source):
# Remove the 's' character at the end if present
if source.endswith('s'):
source = source[:-1]
# Capitalize words separated by a dash
words = source.split('-')
capitalized_words = [word[0].upper() + word[1:] for word in words]
transformed_string = ''.join(capitalized_words)
# Add 'Serializer' at the end
transformed_string += 'Serializer'
module = importlib.import_module('api.serializers')
return getattr(module, transformed_string)

@ -1,5 +1,5 @@
from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, TournamentSerializer, UserSerializer, ChangePasswordSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, LiveMatchSerializer, PurchaseSerializer, UserUpdateSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer 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 from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken, ModelLog
from rest_framework import viewsets, permissions from rest_framework import viewsets, permissions
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
@ -14,9 +14,152 @@ from rest_framework.views import APIView
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.db.models import Q from django.db.models import Q
from django.core.exceptions import ObjectDoesNotExist 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 .permissions import IsClubOwner
from .utils import is_valid_email 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')
print(f"/data {model_operation} {model_name}")
serializer_class = build_serializer_class(model_name)
model = apps.get_model(app_label='tournaments', model_name=model_name)
try:
instance = model.objects.get(id=data.get('id'))
if model_operation == 'DELETE':
return self.delete_and_save_log(request, data, model_operation, model_name)
else: # POST/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)
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:
# If the instance doesn't exist, we should be in a POST situation
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)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def save_and_create_log(self, request, serializer, model_operation, model_name):
instance = serializer.save()
self.create_and_save_model_log(
user=request.user,
model_operation=model_operation,
model_name=model_name,
model_id=instance.id
)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def delete_and_save_log(self, request, data, model_operation, model_name):
model = apps.get_model(app_label='tournaments', model_name=model_name)
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=instance.id).delete()
self.create_and_save_model_log(
user=request.user,
model_operation=model_operation,
model_name=model_name,
model_id=instance.id
)
return Response(status=status.HTTP_204_NO_CONTENT)
def create_and_save_model_log(self, user, model_operation, model_name, model_id):
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.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)
logs = ModelLog.objects.filter(date__gt=last_update).order_by('date')
updates = defaultdict(dict)
deletions = defaultdict(set)
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].add(log.model_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] = list(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),
"deletions": dict(deletions),
"date": date
}
return Response(response_data, status=status.HTTP_200_OK)
class CustomAuthToken(APIView): class CustomAuthToken(APIView):
permission_classes = [] permission_classes = []
@ -34,14 +177,15 @@ class CustomAuthToken(APIView):
if user is not None: 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':
# if user.device_id is None or user.device_id == device_id or user.username == 'apple-test':
user.device_id = device_id user.device_id = device_id
user.save() user.save()
token, created = Token.objects.get_or_create(user=user) token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key}) return Response({'token': token.key})
else: # 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) # 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: else:
return Response({'error': 'L\'utilisateur et le mot de passe de correspondent pas'}, status=status.HTTP_401_UNAUTHORIZED) return Response({'error': 'L\'utilisateur et le mot de passe de correspondent pas'}, status=status.HTTP_401_UNAUTHORIZED)
@ -72,7 +216,7 @@ def user_by_token(request):
class UserViewSet(viewsets.ModelViewSet): class UserViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.all() queryset = CustomUser.objects.all()
serializer_class = UserUpdateSerializer serializer_class = CustomUserSerializer
permission_classes = [] # Users are public whereas the other requests are only for logged users permission_classes = [] # Users are public whereas the other requests are only for logged users
def get_serializer_class(self): def get_serializer_class(self):

@ -9,8 +9,30 @@ https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
import os import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'padelclub_backend.settings') os.environ.setdefault("DJANGO_SETTINGS_MODULE", "padelclub_backend.settings")
application = get_asgi_application() django_asgi_app = get_asgi_application()
from tournaments.routing import websocket_urlpatterns
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
),
}
)
# import os
# from django.core.asgi import get_asgi_application
# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'padelclub_backend.settings')
# application = get_asgi_application()

@ -32,6 +32,7 @@ ALLOWED_HOSTS = ['*']
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'daphne',
'tournaments', 'tournaments',
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
@ -77,6 +78,7 @@ TEMPLATES = [
WSGI_APPLICATION = 'padelclub_backend.wsgi.application' WSGI_APPLICATION = 'padelclub_backend.wsgi.application'
ASGI_APPLICATION = "padelclub_backend.asgi.application"
# Database # Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases # https://docs.djangoproject.com/en/4.1/ref/settings/#databases

@ -41,3 +41,9 @@ CACHES = {
} }
QR_CODE_CACHE_ALIAS = 'qr-code' QR_CODE_CACHE_ALIAS = 'qr-code'
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}

@ -1,9 +1,10 @@
Django==4.2.11 Django==5.1
djangorestframework==3.14.0 djangorestframework==3.14.0
psycopg2-binary==2.9.9 psycopg2-binary==2.9.9
dj-rest-auth==5.1.0 dj-rest-auth==6.0.0
django-qr-code==4.0.1 django-qr-code==4.0.1
pycryptodome==3.20.0 pycryptodome==3.20.0
requests==2.31.0 requests==2.31.0
PyJWT==2.8.0 PyJWT==2.8.0
httpx[http2]==0.27.0 httpx[http2]==0.27.0
channels[daphne]==4.1.0

@ -3,7 +3,7 @@ from django.contrib import admin
from tournaments.models import team_registration from tournaments.models import team_registration
from tournaments.models.device_token import DeviceToken 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 from .models import Club, TeamScore, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamRegistration, PlayerRegistration, Purchase, Court, DateInterval, FailedApiCall, Log, ModelLog
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserCreationForm, UserChangeForm from django.contrib.auth.forms import UserCreationForm, UserChangeForm
@ -93,6 +93,10 @@ class LogAdmin(admin.ModelAdmin):
class DeviceTokenAdmin(admin.ModelAdmin): class DeviceTokenAdmin(admin.ModelAdmin):
list_display = ['user', 'value'] list_display = ['user', 'value']
class ModelLogAdmin(admin.ModelAdmin):
list_display = ['user', 'date', 'operation', 'model_id', 'model_name']
list_filter = ['user']
admin.site.register(CustomUser, CustomUserAdmin) admin.site.register(CustomUser, CustomUserAdmin)
admin.site.register(Club, ClubAdmin) admin.site.register(Club, ClubAdmin)
admin.site.register(Event, EventAdmin) admin.site.register(Event, EventAdmin)
@ -109,3 +113,4 @@ admin.site.register(DateInterval, DateIntervalAdmin)
admin.site.register(FailedApiCall, FailedApiCallAdmin) admin.site.register(FailedApiCall, FailedApiCallAdmin)
admin.site.register(Log, LogAdmin) admin.site.register(Log, LogAdmin)
admin.site.register(DeviceToken, DeviceTokenAdmin) admin.site.register(DeviceToken, DeviceTokenAdmin)
admin.site.register(ModelLog, ModelLogAdmin)

@ -0,0 +1,41 @@
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = 'main'
self.room_group_name = f"chat_{self.room_name}"
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name, self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name, self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
# text_data_json = json.loads(text_data)
# message = text_data_json["message"]
print(f'received {text_data}')
# Send message to room group
# chat.message calls the chat_message method
async_to_sync(self.channel_layer.group_send)(
self.room_group_name, {"type": "chat.message", "message": text_data}
)
# Receive message from room group
def chat_message(self, event):
message = event["message"]
# Send message to WebSocket
self.send(text_data=message)

@ -0,0 +1,24 @@
# Generated by Django 5.1 on 2024-09-12 13:49
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0079_alter_event_creator'),
]
operations = [
migrations.CreateModel(
name='ModelLog',
fields=[
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('model_id', models.UUIDField()),
('operation', models.IntegerField(choices=[(0, 'POST'), (1, 'PUT'), (2, 'DELETE')])),
('date', models.DateTimeField()),
('model_name', models.CharField(max_length=50)),
],
),
]

@ -0,0 +1,18 @@
# Generated by Django 5.1 on 2024-09-12 15:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0080_modellog'),
]
operations = [
migrations.AlterField(
model_name='modellog',
name='operation',
field=models.IntegerField(choices=[('POST', 'POST'), ('PUT', 'PUT'), ('DELETE', 'DELETE')]),
),
]

@ -0,0 +1,18 @@
# Generated by Django 5.1 on 2024-09-12 15:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0081_alter_modellog_operation'),
]
operations = [
migrations.AlterField(
model_name='modellog',
name='operation',
field=models.CharField(choices=[('POST', 'POST'), ('PUT', 'PUT'), ('DELETE', 'DELETE')], max_length=50),
),
]

@ -0,0 +1,20 @@
# Generated by Django 5.1 on 2024-10-09 08:10
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0082_alter_modellog_operation'),
]
operations = [
migrations.AddField(
model_name='modellog',
name='creator',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
),
]

@ -0,0 +1,18 @@
# Generated by Django 5.1 on 2024-10-09 08:18
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0083_modellog_creator'),
]
operations = [
migrations.RenameField(
model_name='modellog',
old_name='creator',
new_name='user',
),
]

@ -0,0 +1,18 @@
# Generated by Django 5.1 on 2024-10-09 11:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0084_rename_creator_modellog_user'),
]
operations = [
migrations.AddField(
model_name='modellog',
name='store_id',
field=models.CharField(blank=True, max_length=200, null=True),
),
]

@ -0,0 +1,154 @@
# Generated by Django 5.1 on 2024-10-15 14:42
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0085_modellog_store_id'),
]
operations = [
migrations.AddField(
model_name='club',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='club',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='court',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='court',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='dateinterval',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='dateinterval',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='devicetoken',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='devicetoken',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='event',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='failedapicall',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='failedapicall',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='groupstage',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='groupstage',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='log',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='log',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='match',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='match',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='playerregistration',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='playerregistration',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='purchase',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='purchase',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='round',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='round',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='teamregistration',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='teamregistration',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='teamscore',
name='creation_date',
field=models.DateTimeField(default=django.utils.timezone.now, editable=False),
),
migrations.AddField(
model_name='teamscore',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='tournament',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

@ -0,0 +1,17 @@
# Generated by Django 5.1 on 2024-10-16 12:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0086_club_creation_date_club_last_update_and_more'),
]
operations = [
migrations.RemoveField(
model_name='modellog',
name='store_id',
),
]

@ -0,0 +1,19 @@
# Generated by Django 5.1 on 2024-10-17 10:36
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0087_remove_modellog_store_id'),
]
operations = [
migrations.AddField(
model_name='customuser',
name='last_update',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

@ -0,0 +1,49 @@
# Generated by Django 5.1 on 2024-10-17 13:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0088_customuser_last_update'),
]
operations = [
migrations.AddField(
model_name='groupstage',
name='store_id',
field=models.CharField(default='', max_length=100),
preserve_default=False,
),
migrations.AddField(
model_name='match',
name='store_id',
field=models.CharField(default='', max_length=100),
preserve_default=False,
),
migrations.AddField(
model_name='playerregistration',
name='store_id',
field=models.CharField(default='', max_length=100),
preserve_default=False,
),
migrations.AddField(
model_name='round',
name='store_id',
field=models.CharField(default='', max_length=100),
preserve_default=False,
),
migrations.AddField(
model_name='teamregistration',
name='store_id',
field=models.CharField(default='', max_length=100),
preserve_default=False,
),
migrations.AddField(
model_name='teamscore',
name='store_id',
field=models.CharField(default='', max_length=100),
preserve_default=False,
),
]

@ -0,0 +1,79 @@
# Generated by Django 5.1 on 2024-10-17 13:02
from ast import Match
from django.db import migrations
from tournaments.models.player_registration import PlayerRegistration
def update_group_stage_store_id(apps):
GroupStage = apps.get_model('tournaments', 'GroupStage')
for group_stage in GroupStage.objects.all():
group_stage.store_id = str(group_stage.tournament.id)
group_stage.save()
def update_round_store_id(apps):
Round = apps.get_model('tournaments', 'Round')
for round in Round.objects.all():
round.store_id = str(round.tournament.id)
round.save()
def update_team_registration_store_id(apps):
TeamRegistration = apps.get_model('tournaments', 'TeamRegistration')
for tr in TeamRegistration.objects.all():
tr.store_id = str(tr.tournament.id)
tr.save()
def update_player_registration_store_id(apps):
PlayerRegistration = apps.get_model('tournaments', 'PlayerRegistration')
for pr in PlayerRegistration.objects.all():
pr.store_id = str(pr.team_registration.tournament.id)
pr.save()
def update_match_store_id(apps):
Match = apps.get_model('tournaments', 'Match')
for match in Match.objects.all():
if match.round:
tournament = match.round.tournament
else:
tournament = match.group_stage.tournament
match.store_id = str(tournament.id)
match.save()
def update_team_score_store_id(apps):
TeamScore = apps.get_model('tournaments', 'TeamScore')
for team_score in TeamScore.objects.all():
tournament = None
if team_score.team_registration:
tournament = team_score.team_registration.tournament
elif team_score.match:
if team_score.match.round:
tournament = team_score.match.round.tournament
else:
tournament = team_score.team_registration.tournament
team_score.store_id = str(tournament.id)
team_score.save()
def update_models(apps, schema_editor):
update_group_stage_store_id(apps)
update_round_store_id(apps)
update_team_registration_store_id(apps)
update_player_registration_store_id(apps)
update_match_store_id(apps)
update_team_score_store_id(apps)
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0089_groupstage_store_id_match_store_id_and_more'),
]
operations = [
migrations.RunPython(update_models),
]

@ -1,8 +1,9 @@
from .base import BaseModel, SideStoreModel
from .custom_user import CustomUser from .custom_user import CustomUser
from .club import Club from .club import Club
from .court import Court from .court import Court
from .date_interval import DateInterval from .date_interval import DateInterval
from .enums import TournamentPayment, FederalCategory, FederalLevelCategory, FederalAgeCategory, FederalMatchCategory from .enums import TournamentPayment, FederalCategory, FederalLevelCategory, FederalAgeCategory, FederalMatchCategory, ModelOperation
from .player_enums import PlayerSexType, PlayerDataSource, PlayerPaymentType from .player_enums import PlayerSexType, PlayerDataSource, PlayerPaymentType
from .event import Event from .event import Event
from .tournament import Tournament, TeamSummon, TeamSortingType, TeamList from .tournament import Tournament, TeamSummon, TeamSortingType, TeamList
@ -16,3 +17,4 @@ from .purchase import Purchase
from .failed_api_call import FailedApiCall from .failed_api_call import FailedApiCall
from .log import Log from .log import Log
from .device_token import DeviceToken from .device_token import DeviceToken
from .model_log import ModelLog

@ -0,0 +1,15 @@
from django.db import models
from django.utils.timezone import now
class BaseModel(models.Model):
creation_date = models.DateTimeField(default=now, editable=False)
last_update = models.DateTimeField(default=now)
class Meta:
abstract = True
class SideStoreModel(BaseModel):
store_id = models.CharField(max_length=100)
class Meta:
abstract = True

@ -1,7 +1,8 @@
from django.db import models from django.db import models
import uuid import uuid
from . import BaseModel
class Club(models.Model): class Club(BaseModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
creator = models.ForeignKey('CustomUser', blank=True, null=True, on_delete=models.SET_NULL) # string to avoid circular import creator = models.ForeignKey('CustomUser', blank=True, null=True, on_delete=models.SET_NULL) # string to avoid circular import
name = models.CharField(max_length=50) name = models.CharField(max_length=50)

@ -1,8 +1,8 @@
from django.db import models from django.db import models
import uuid import uuid
from . import Club from . import BaseModel, Club
class Court(models.Model): class Court(BaseModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
index = models.IntegerField(default=0) index = models.IntegerField(default=0)
club = models.ForeignKey(Club, on_delete=models.CASCADE) club = models.ForeignKey(Club, on_delete=models.CASCADE)

@ -1,11 +1,14 @@
from django.db import models from django.db import models
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.utils.timezone import now
from . import club, enums from . import club, enums
import uuid import uuid
class CustomUser(AbstractUser): class CustomUser(AbstractUser):
pass pass
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
last_update = models.DateTimeField(default=now)
email = models.EmailField(unique=True) email = models.EmailField(unique=True)
umpire_code = models.CharField(max_length=50, blank=True, null=True) umpire_code = models.CharField(max_length=50, blank=True, null=True)
clubs = models.ManyToManyField(club.Club, blank=True) clubs = models.ManyToManyField(club.Club, blank=True)
@ -37,7 +40,7 @@ class CustomUser(AbstractUser):
def fields_for_update(): def fields_for_update():
# returns the list of fields to update without password # returns the list of fields to update without password
return ['id', 'username', 'email', 'umpire_code', 'clubs', 'phone', 'first_name', 'last_name', return ['id', 'last_update', 'username', 'email', 'umpire_code', 'clubs', 'phone', 'first_name', 'last_name',
'licence_id', 'country', 'licence_id', 'country',
'summons_message_body', 'summons_message_signature', 'summons_available_payment_methods', 'summons_message_body', 'summons_message_signature', 'summons_available_payment_methods',
'summons_display_format', 'summons_display_entry_fee', 'summons_display_format', 'summons_display_entry_fee',

@ -1,7 +1,8 @@
from django.db import models from django.db import models
import uuid import uuid
from . import BaseModel
class DateInterval(models.Model): class DateInterval(BaseModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
event = models.ForeignKey('Event', on_delete=models.CASCADE) event = models.ForeignKey('Event', on_delete=models.CASCADE)
court_index = models.IntegerField() court_index = models.IntegerField()

@ -1,8 +1,9 @@
from django.db import models from django.db import models
from . import CustomUser from . import CustomUser
import uuid import uuid
from . import BaseModel
class DeviceToken(models.Model): class DeviceToken(BaseModel):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
value = models.TextField() value = models.TextField()

@ -56,3 +56,8 @@ class FederalMatchCategory(models.IntegerChoices):
return 1 return 1
else: else:
return 3 return 3
class ModelOperation(models.TextChoices):
POST = 'POST', 'POST'
PUT = 'PUT', 'PUT'
DELETE = 'DELETE', 'DELETE'

@ -1,8 +1,8 @@
from django.db import models from django.db import models
from . import Club, CustomUser from . import BaseModel, Club, CustomUser
import uuid import uuid
class Event(models.Model): class Event(BaseModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
club = models.ForeignKey(Club, on_delete=models.SET_NULL, null=True, blank=True) club = models.ForeignKey(Club, on_delete=models.SET_NULL, null=True, blank=True)
creation_date = models.DateTimeField() creation_date = models.DateTimeField()

@ -1,8 +1,8 @@
from django.db import models from django.db import models
from . import CustomUser from . import BaseModel, CustomUser
import uuid import uuid
class FailedApiCall(models.Model): class FailedApiCall(BaseModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
date = models.DateTimeField() date = models.DateTimeField()
user = models.ForeignKey(CustomUser, blank=True, null=True, on_delete=models.SET_NULL) user = models.ForeignKey(CustomUser, blank=True, null=True, on_delete=models.SET_NULL)

@ -1,11 +1,12 @@
from django.db import models from django.db import models
from . import Tournament, FederalMatchCategory
from . import SideStoreModel, Tournament, FederalMatchCategory
import uuid import uuid
from ..utils.extensions import format_seconds from ..utils.extensions import format_seconds
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.utils import timezone from django.utils import timezone
class GroupStage(models.Model): class GroupStage(SideStoreModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE) tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE)
index = models.IntegerField(default=0) index = models.IntegerField(default=0)
@ -18,6 +19,9 @@ class GroupStage(models.Model):
return self.display_name() return self.display_name()
# return f"{self.tournament.display_name()} - {self.display_name()}" # return f"{self.tournament.display_name()} - {self.display_name()}"
def tournament_str_id(self):
return str(self.tournament.id)
def display_name(self): def display_name(self):
if self.name: if self.name:
return self.name return self.name

@ -1,8 +1,8 @@
from django.db import models from django.db import models
from . import CustomUser from . import BaseModel, CustomUser
import uuid import uuid
class Log(models.Model): class Log(BaseModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
date = models.DateTimeField() date = models.DateTimeField()
user = models.ForeignKey(CustomUser, blank=True, null=True, on_delete=models.SET_NULL) user = models.ForeignKey(CustomUser, blank=True, null=True, on_delete=models.SET_NULL)

@ -1,13 +1,13 @@
from django.db import models from django.db import models
from tournaments.models import group_stage from tournaments.models import group_stage
from . import Round, GroupStage, FederalMatchCategory from . import SideStoreModel, Round, GroupStage, FederalMatchCategory
from django.utils import timezone, formats from django.utils import timezone, formats
from datetime import timedelta from datetime import timedelta
import uuid import uuid
from ..utils.extensions import format_seconds from ..utils.extensions import format_seconds
class Match(models.Model): class Match(SideStoreModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
round = models.ForeignKey(Round, null=True, blank=True, on_delete=models.CASCADE) round = models.ForeignKey(Round, null=True, blank=True, on_delete=models.CASCADE)
group_stage = models.ForeignKey(GroupStage, null=True, blank=True, on_delete=models.CASCADE) group_stage = models.ForeignKey(GroupStage, null=True, blank=True, on_delete=models.CASCADE)
@ -36,6 +36,9 @@ class Match(models.Model):
else: else:
return self.group_stage.tournament return self.group_stage.tournament
def tournament_id(self):
return self.tournament().id
def court_name(self, index): def court_name(self, index):
club = None club = None
if self.tournament().event: if self.tournament().event:

@ -0,0 +1,12 @@
from django.db import models
import uuid
from . import ModelOperation
class ModelLog(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
user = models.ForeignKey('CustomUser', blank=True, null=True, on_delete=models.SET_NULL)
model_id = models.UUIDField()
operation = models.CharField(choices=ModelOperation.choices, max_length=50)
date = models.DateTimeField()
model_name = models.CharField(max_length=50)
# store_id = models.CharField(max_length=200, blank=True, null=True)

@ -1,8 +1,8 @@
from django.db import models from django.db import models
from . import TeamRegistration, PlayerSexType, PlayerDataSource, PlayerPaymentType from . import SideStoreModel, TeamRegistration, PlayerSexType, PlayerDataSource, PlayerPaymentType
import uuid import uuid
class PlayerRegistration(models.Model): class PlayerRegistration(SideStoreModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
team_registration = models.ForeignKey(TeamRegistration, on_delete=models.CASCADE) team_registration = models.ForeignKey(TeamRegistration, on_delete=models.CASCADE)
first_name = models.CharField(max_length=50, blank=True) first_name = models.CharField(max_length=50, blank=True)
@ -37,6 +37,9 @@ class PlayerRegistration(models.Model):
def __str__(self): def __str__(self):
return self.name() return self.name()
def tournament_id(self):
return self.team_registration.tournament.id
def name(self): def name(self):
return f"{self.first_name} {self.last_name}" return f"{self.first_name} {self.last_name}"

@ -1,8 +1,8 @@
from django.db import models from django.db import models
import uuid import uuid
from . import CustomUser from . import BaseModel, CustomUser
class Purchase(models.Model): class Purchase(BaseModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
identifier = models.BigIntegerField() identifier = models.BigIntegerField()

@ -1,8 +1,8 @@
from django.db import models from django.db import models
from . import Tournament, FederalMatchCategory from . import SideStoreModel, Tournament, FederalMatchCategory
import uuid import uuid
class Round(models.Model): class Round(SideStoreModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE) tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE)
index = models.IntegerField(default=0) index = models.IntegerField(default=0)
@ -20,6 +20,9 @@ class Round(models.Model):
# else: # else:
# return f"{self.tournament.display_name()} - {self.name()}" # return f"{self.tournament.display_name()} - {self.name()}"
def tournament_id(self):
return self.tournament.id
def name(self): def name(self):
if self.parent: if self.parent:
return "Matchs de classement" return "Matchs de classement"

@ -1,10 +1,10 @@
from django.db import models from django.db import models
from django.db.models.sql.query import Q from django.db.models.sql.query import Q
from . import Tournament, GroupStage, Match from . import SideStoreModel, Tournament, GroupStage, Match
import uuid import uuid
from django.utils import timezone from django.utils import timezone
class TeamRegistration(models.Model): class TeamRegistration(SideStoreModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE) tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE)
group_stage = models.ForeignKey(GroupStage, null=True, blank=True, on_delete=models.SET_NULL) group_stage = models.ForeignKey(GroupStage, null=True, blank=True, on_delete=models.SET_NULL)
@ -37,6 +37,9 @@ class TeamRegistration(models.Model):
# return f"{self.name}: {self.player_names()}" # return f"{self.name}: {self.player_names()}"
return self.player_names() return self.player_names()
def tournament_id(self):
return self.tournament.id
def team_names(self): def team_names(self):
if self.name: if self.name:
return [self.name] return [self.name]

@ -1,8 +1,8 @@
from django.db import models from django.db import models
from . import Match, TeamRegistration, PlayerRegistration, FederalMatchCategory from . import SideStoreModel, Match, TeamRegistration, PlayerRegistration, FederalMatchCategory
import uuid import uuid
class TeamScore(models.Model): class TeamScore(SideStoreModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
match = models.ForeignKey(Match, on_delete=models.CASCADE, related_name="team_scores") match = models.ForeignKey(Match, on_delete=models.CASCADE, related_name="team_scores")
team_registration = models.ForeignKey(TeamRegistration, on_delete=models.CASCADE, null=True, blank=True) team_registration = models.ForeignKey(TeamRegistration, on_delete=models.CASCADE, null=True, blank=True)
@ -13,6 +13,17 @@ class TeamScore(models.Model):
def __str__(self): def __str__(self):
return f"{self.match.stage_name()} #{self.match.index}: {self.player_names()}" return f"{self.match.stage_name()} #{self.match.index}: {self.player_names()}"
def tournament(self):
if self.team_registration:
return self.team_registration.tournament
elif self.match:
return self.match.tournament()
else:
return None
def tournament_id(self):
return self.tournament().id
def player_names(self): def player_names(self):
if self.team_registration: if self.team_registration:
if self.team_registration.name: if self.team_registration.name:

@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from tournaments.models import group_stage from tournaments.models import group_stage
from . import Event, TournamentPayment, FederalMatchCategory, FederalCategory, FederalLevelCategory, FederalAgeCategory from . import BaseModel, Event, TournamentPayment, FederalMatchCategory, FederalCategory, FederalLevelCategory, FederalAgeCategory
import uuid import uuid
from django.utils import timezone, formats from django.utils import timezone, formats
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -15,7 +15,7 @@ class TeamSortingType(models.IntegerChoices):
RANK = 1, 'Rank' RANK = 1, 'Rank'
INSCRIPTION_DATE = 2, 'Inscription Date' INSCRIPTION_DATE = 2, 'Inscription Date'
class Tournament(models.Model): class Tournament(BaseModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
event = models.ForeignKey(Event, blank=True, null=True, on_delete=models.CASCADE) event = models.ForeignKey(Event, blank=True, null=True, on_delete=models.CASCADE)
name = models.CharField(max_length=200, null=True, blank=True) name = models.CharField(max_length=200, null=True, blank=True)

@ -0,0 +1,9 @@
# chat/routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r"ws/chat/$", consumers.ChatConsumer.as_asgi()),
# re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
]
Loading…
Cancel
Save