Initial commit: WrestleDesk full project

- Django backend with DRF (clubs, wrestlers, trainers, exercises, templates, trainings, homework, locations, leistungstest)
- Next.js 16 frontend with React, Shadcn UI, Tailwind
- JWT authentication
- Full CRUD for all entities
- Calendar view for trainings
- Homework management system
- Leistungstest tracking
This commit is contained in:
Andrej Spielmann
2026-03-26 13:24:57 +01:00
commit 3fefc550fe
256 changed files with 38295 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from .models import LeistungstestTemplate, LeistungstestTemplateExercise, LeistungstestResult, LeistungstestResultItem
from .serializers import (
LeistungstestTemplateSerializer,
LeistungstestTemplateExerciseSerializer,
LeistungstestResultSerializer,
LeistungstestResultItemSerializer,
)
from .stats import get_template_leaderboard, get_exercise_leaderboard, get_used_exercises
class LeistungstestTemplateViewSet(viewsets.ModelViewSet):
queryset = LeistungstestTemplate.objects.all()
serializer_class = LeistungstestTemplateSerializer
def get_queryset(self):
return LeistungstestTemplate.objects.all().prefetch_related('exercises__exercise')
def create(self, request, *args, **kwargs):
name = request.data.get('name')
exercises_data = request.data.get('exercises', [])
# Create template first
template = LeistungstestTemplate.objects.create(name=name)
# Create exercises
for i, ex_data in enumerate(exercises_data):
LeistungstestTemplateExercise.objects.create(
template=template,
exercise_id=ex_data['exercise'],
target_reps=ex_data['target_reps'],
order=ex_data.get('order', i),
)
# Reload with prefetch to get exercise names
template = LeistungstestTemplate.objects.prefetch_related('exercises__exercise').get(pk=template.pk)
return Response(
LeistungstestTemplateSerializer(template).data,
status=status.HTTP_201_CREATED
)
@action(detail=True, methods=['post'])
def duplicate(self, request, pk=None):
template = self.get_object()
new_template = LeistungstestTemplate.objects.create(name=f"{template.name} (Kopie)")
for exercise in template.exercises.all():
LeistungstestTemplateExercise.objects.create(
template=new_template,
exercise=exercise.exercise,
target_reps=exercise.target_reps,
order=exercise.order,
)
return Response(
LeistungstestTemplateSerializer(new_template).data,
status=status.HTTP_201_CREATED
)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
instance.name = request.data.get('name', instance.name)
instance.save()
exercises_data = request.data.get('exercises')
if exercises_data is not None:
instance.exercises.all().delete()
for i, ex_data in enumerate(exercises_data):
LeistungstestTemplateExercise.objects.create(
template=instance,
exercise_id=ex_data['exercise'],
target_reps=ex_data['target_reps'],
order=ex_data.get('order', i),
)
instance = LeistungstestTemplate.objects.prefetch_related('exercises__exercise').get(pk=instance.pk)
return Response(LeistungstestTemplateSerializer(instance).data)
class LeistungstestTemplateExerciseViewSet(viewsets.ModelViewSet):
queryset = LeistungstestTemplateExercise.objects.all()
serializer_class = LeistungstestTemplateExerciseSerializer
class LeistungstestResultViewSet(viewsets.ModelViewSet):
queryset = LeistungstestResult.objects.all()
serializer_class = LeistungstestResultSerializer
def get_queryset(self):
queryset = LeistungstestResult.objects.all().prefetch_related('items__exercise')
template_id = self.request.query_params.get('template')
wrestler_id = self.request.query_params.get('wrestler')
if template_id:
queryset = queryset.filter(template_id=template_id)
if wrestler_id:
queryset = queryset.filter(wrestler_id=wrestler_id)
return queryset
def create(self, request, *args, **kwargs):
template_id = request.data.get('template')
wrestler_id = request.data.get('wrestler')
items_data = request.data.get('items', [])
result = LeistungstestResult.objects.create(
template_id=template_id,
wrestler_id=wrestler_id,
total_time_seconds=request.data.get('total_time_seconds') or None,
rating=request.data.get('rating', 3),
notes=request.data.get('notes', ''),
)
for i, item_data in enumerate(items_data):
LeistungstestResultItem.objects.create(
result=result,
exercise_id=item_data['exercise'],
target_reps=item_data.get('target_reps', 0),
actual_reps=item_data.get('actual_reps', 0),
elapsed_seconds=item_data.get('elapsed_seconds', 0),
order=item_data.get('order', i),
)
result_items = LeistungstestResultItem.objects.filter(result=result)
total_target = sum(item.target_reps for item in result_items)
total_actual = sum(item.actual_reps for item in result_items)
if total_target > 0:
score = round((total_actual / total_target) * 100, 1)
else:
score = 0.0
result.refresh_from_db()
result_data = LeistungstestResultSerializer(result).data
result_data['score_percent'] = score
return Response(result_data, status=status.HTTP_201_CREATED)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
instance.total_time_seconds = request.data.get('total_time_seconds', instance.total_time_seconds)
instance.rating = request.data.get('rating', instance.rating)
instance.notes = request.data.get('notes', instance.notes)
instance.save()
items_data = request.data.get('items')
if items_data is not None:
instance.items.all().delete()
for i, item_data in enumerate(items_data):
LeistungstestResultItem.objects.create(
result=instance,
exercise_id=item_data['exercise'],
target_reps=item_data.get('target_reps', 0),
actual_reps=item_data.get('actual_reps', 0),
elapsed_seconds=item_data.get('elapsed_seconds', 0),
order=item_data.get('order', i),
)
result_items = LeistungstestResultItem.objects.filter(result=instance)
total_target = sum(item.target_reps for item in result_items)
total_actual = sum(item.actual_reps for item in result_items)
if total_target > 0:
score = round((total_actual / total_target) * 100, 1)
else:
score = 0.0
result_data = LeistungstestResultSerializer(instance).data
result_data['score_percent'] = score
return Response(result_data)
@action(detail=False, methods=['get'])
def leaderboard(self, request):
template_id = request.query_params.get('template')
if not template_id:
return Response(
{'error': 'template parameter is required'},
status=status.HTTP_400_BAD_REQUEST
)
limit = int(request.query_params.get('limit', 10))
results = LeistungstestResult.objects.filter(template_id=template_id)\
.select_related('wrestler')
leaderboard_data = []
for result in results:
leaderboard_data.append({
'rank': 0,
'result_id': result.id,
'wrestler_id': result.wrestler.id,
'wrestler_name': str(result.wrestler),
'score_percent': result.score_percent,
'completed_at': result.completed_at,
'total_time_seconds': result.total_time_seconds,
'rating': result.rating,
})
leaderboard_data.sort(key=lambda x: x['score_percent'], reverse=True)
leaderboard_data = leaderboard_data[:limit]
for i, entry in enumerate(leaderboard_data, 1):
entry['rank'] = i
return Response(leaderboard_data)
class LeistungstestResultItemViewSet(viewsets.ModelViewSet):
queryset = LeistungstestResultItem.objects.all()
serializer_class = LeistungstestResultItemSerializer
class LeistungstestStatsViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated]
@action(detail=False, methods=['get'])
def leaderboard(self, request):
lb_type = request.query_params.get('type', 'template')
template_id = request.query_params.get('template_id')
exercise_id = request.query_params.get('exercise_id')
period = request.query_params.get('period', 'all')
limit = int(request.query_params.get('limit', 10))
if lb_type == 'template' and template_id:
results = get_template_leaderboard(int(template_id), period, limit)
template = LeistungstestTemplate.objects.get(pk=template_id)
return Response({
'template_id': template_id,
'template_name': template.name,
'period': period,
'results': results,
})
elif lb_type == 'exercise' and exercise_id:
from exercises.models import Exercise
results = get_exercise_leaderboard(int(exercise_id), period, limit)
exercise = Exercise.objects.get(pk=exercise_id)
return Response({
'exercise_id': exercise_id,
'exercise_name': exercise.name,
'period': period,
'results': results,
})
return Response({'error': 'Invalid parameters'}, status=400)
@action(detail=False, methods=['get'])
def exercises(self, request):
exercises = get_used_exercises()
return Response([{'id': e.id, 'name': e.name} for e in exercises])