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
View File
+12
View File
@@ -0,0 +1,12 @@
import unfold
from unfold.admin import ModelAdmin as UnfoldModelAdmin
from django.contrib import admin
from .models import Club
@admin.register(Club)
class ClubAdmin(UnfoldModelAdmin):
list_display = ['name', 'short_name', 'is_active', 'created_at']
list_filter = ['is_active']
search_fields = ['name', 'short_name']
readonly_fields = ['created_at', 'updated_at']
+6
View File
@@ -0,0 +1,6 @@
from django.apps import AppConfig
class ClubsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'clubs'
@@ -0,0 +1,171 @@
import random
from datetime import date, timedelta
from django.core.management.base import BaseCommand
from clubs.models import Club
from wrestlers.models import Wrestler
from trainers.models import Trainer
from locations.models import Location
from exercises.models import Exercise
from templates.models import TrainingTemplate, TemplateExercise
from trainings.models import Training, Attendance
class Command(BaseCommand):
help = 'Populates database with comprehensive sample data for testing'
def handle(self, *args, **options):
self.stdout.write('Creating comprehensive sample data...')
# Create 3 clubs
clubs = [
Club.objects.create(name='KSV Wiesental', short_name='KSV', is_active=True),
Club.objects.create(name='RSV Mannheim', short_name='RSV', is_active=True),
Club.objects.create(name='AV Germering', short_name='AVG', is_active=True),
]
# 3 Trainers (one per club)
trainers = [
Trainer.objects.create(first_name='Max', last_name='Mueller', club=clubs[0], email='max.mueller@ksv.de', is_active=True),
Trainer.objects.create(first_name='Anna', last_name='Schmidt', club=clubs[1], email='anna.schmidt@rsv.de', is_active=True),
Trainer.objects.create(first_name='Tom', last_name='Bauer', club=clubs[2], email='tom.bauer@avg.de', is_active=True),
]
# 30 Wrestlers (10 per club, distributed across groups)
first_names_m = ['Felix', 'Leon', 'Paul', 'Lukas', 'Jonas', 'Max', 'Tim', 'David', 'Kevin', 'Marco', 'Stefan', 'Daniel', 'Florian', 'Tobias', 'Christian']
first_names_f = ['Emma', 'Sophie', 'Marie', 'Laura', 'Lena', 'Anna', 'Lisa', 'Sarah', 'Julia', 'Laura', 'Nina', 'Lea', 'Laura', 'Lena', 'Laura']
last_names = ['Mueller', 'Schmidt', 'Weber', 'Fischer', 'Klein', 'Bauer', 'Wolf', 'Schulz', 'Neumann', 'Hoffmann', 'Koch', 'Becker', 'Richter', 'Wagner', 'Weiss']
wrestlers = []
for i in range(30):
club_idx = i % 3
group = random.choice(['kids', 'kids', 'youth', 'youth', 'youth', 'adults', 'adults'])
gender = random.choice(['m', 'f'])
first_name = random.choice(first_names_m if gender == 'm' else first_names_f)
last_name = random.choice(last_names)
year_offset = {'kids': 8, 'youth': 4, 'adults': 0}[group]
dob = date(2010 + year_offset + random.randint(-2, 2), random.randint(1, 12), random.randint(1, 28))
weight = random.uniform(25, 90)
w = Wrestler.objects.create(
first_name=first_name,
last_name=last_name,
club=clubs[club_idx],
group=group,
date_of_birth=dob,
gender=gender,
weight_kg=round(weight, 1),
is_active=True
)
wrestlers.append(w)
# 3 Locations (one per club)
locations = [
Location.objects.create(name='Sporthalle Wiesental', address='Ringstrasse 15, 79541 Wiesental', is_active=True),
Location.objects.create(name='Sportzentrum Mannheim', address='Friedrich-Ebert-Strasse 88, 68159 Mannheim', is_active=True),
Location.objects.create(name='Turnhalle Germering', address='Augsburger Strasse 45, 82110 Germering', is_active=True),
]
# 15 Exercises
exercises_data = [
{'name': 'Springseil', 'category': 'warmup', 'exercise_type': 'time', 'default_value': '60'},
{'name': 'Armkreisen', 'category': 'warmup', 'exercise_type': 'time', 'default_value': '60'},
{'name': 'Beinspätze', 'category': 'warmup', 'exercise_type': 'time', 'default_value': '60'},
{'name': 'Liegestuetze', 'category': 'kraft', 'exercise_type': 'reps', 'default_value': '15'},
{'name': 'Kniebeugen', 'category': 'kraft', 'exercise_type': 'reps', 'default_value': '20'},
{'name': 'Sit-ups', 'category': 'kraft', 'exercise_type': 'reps', 'default_value': '25'},
{'name': 'Plank', 'category': 'kraft', 'exercise_type': 'time', 'default_value': '60'},
{'name': 'Doppelbeintechnik', 'category': 'technik', 'exercise_type': 'reps', 'default_value': '10'},
{'name': 'Ausputzer', 'category': 'technik', 'exercise_type': 'reps', 'default_value': '8'},
{'name': 'Beinsäge', 'category': 'technik', 'exercise_type': 'reps', 'default_value': '12'},
{'name': 'Armheber', 'category': 'technik', 'exercise_type': 'reps', 'default_value': '10'},
{'name': 'Tempolauf', 'category': 'ausdauer', 'exercise_type': 'time', 'default_value': '180'},
{'name': 'Seilspringen Station', 'category': 'ausdauer', 'exercise_type': 'time', 'default_value': '120'},
{'name': 'Fangspiel', 'category': 'spiele', 'exercise_type': 'time', 'default_value': '300'},
{'name': 'Dehnung', 'category': 'cool_down', 'exercise_type': 'time', 'default_value': '180'},
]
exercises = [Exercise.objects.create(**data) for data in exercises_data]
# 6 Templates (2 per group)
templates = []
for idx, (group, group_name) in enumerate([('kids', 'Kids'), ('youth', 'Youth'), ('adults', 'Adults')]):
for t_idx in range(2):
t = TrainingTemplate.objects.create(
name=f'{group_name} Training {"A" if t_idx == 0 else "B"}',
description=f'Standard training for {group_name.lower()} group - Part {"A" if t_idx == 0 else "B"}',
category='main',
is_active=True
)
templates.append(t)
# Kids Training A: warmup + kraft + spiele
for i, ex in enumerate([exercises[0], exercises[3], exercises[5], exercises[13]]):
TemplateExercise.objects.create(template=templates[0], exercise=ex, order=i, default_value=ex.default_value)
# Kids Training B: warmup + technik + spiele
for i, ex in enumerate([exercises[1], exercises[7], exercises[9], exercises[13]]):
TemplateExercise.objects.create(template=templates[1], exercise=ex, order=i, default_value=ex.default_value)
# Youth Training A: warmup + technik + kraft
for i, ex in enumerate([exercises[0], exercises[3], exercises[4], exercises[7], exercises[8]]):
TemplateExercise.objects.create(template=templates[2], exercise=ex, order=i, default_value=ex.default_value)
# Youth Training B: technik + ausdauer
for i, ex in enumerate([exercises[7], exercises[8], exercises[9], exercises[11], exercises[14]]):
TemplateExercise.objects.create(template=templates[3], exercise=ex, order=i, default_value=ex.default_value)
# Adults Training A: warmup + kraft + technik
for i, ex in enumerate([exercises[0], exercises[3], exercises[4], exercises[6], exercises[7], exercises[14]]):
TemplateExercise.objects.create(template=templates[4], exercise=ex, order=i, default_value=ex.default_value)
# Adults Training B: kraft + ausdauer + technik
for i, ex in enumerate([exercises[3], exercises[5], exercises[7], exercises[11], exercises[12], exercises[14]]):
TemplateExercise.objects.create(template=templates[5], exercise=ex, order=i, default_value=ex.default_value)
# 30 Trainings spread over 3 months (past, present, future)
today = date.today()
start_date = today - timedelta(days=90) # 3 months ago
trainings = []
group_map = {'kids': [0, 1], 'youth': [2, 3], 'adults': [4, 5]}
for week_offset in range(15): # ~2 trainings per week over 3 months
for day_offset in [0, 3]: # Monday and Thursday
training_date = start_date + timedelta(days=week_offset * 7 + day_offset)
if training_date > today + timedelta(days=14):
continue # Don't create trainings too far in future
group = random.choice(['kids', 'youth', 'adults'])
template = random.choice(group_map[group])
location = random.choice(locations)
is_completed = training_date < today
t = Training.objects.create(
date=training_date,
start_time='17:00' if group == 'kids' else '18:30' if group == 'youth' else '19:30',
end_time='18:30' if group == 'kids' else '20:00' if group == 'youth' else '21:00',
location=location,
group=group,
is_completed=is_completed,
notes=f'Training für {group}'
)
t.trainers.set([random.choice(trainers)])
trainings.append(t)
# Create attendances for completed trainings
for training in trainings:
if training.is_completed:
group_wrestlers = [w for w in wrestlers if w.group == training.group]
num_attendees = min(len(group_wrestlers), random.randint(3, 8))
for w in random.sample(group_wrestlers, num_attendees):
Attendance.objects.create(training=training, wrestler=w)
self.stdout.write(self.style.SUCCESS(
f'Created: {Club.objects.count()} clubs, '
f'{Trainer.objects.count()} trainers, '
f'{Wrestler.objects.count()} wrestlers, '
f'{Location.objects.count()} locations, '
f'{Exercise.objects.count()} exercises, '
f'{TrainingTemplate.objects.count()} templates, '
f'{Training.objects.count()} trainings, '
f'{Attendance.objects.count()} attendances'
))
+29
View File
@@ -0,0 +1,29 @@
# Generated by Django 4.2.29 on 2026-03-19 09:05
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Club',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('short_name', models.CharField(blank=True, max_length=50)),
('logo', models.ImageField(blank=True, null=True, upload_to='clubs/logos/')),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'ordering': ['name'],
},
),
]
+16
View File
@@ -0,0 +1,16 @@
from django.db import models
class Club(models.Model):
name = models.CharField(max_length=200)
short_name = models.CharField(max_length=50, blank=True)
logo = models.ImageField(upload_to='clubs/logos/', null=True, blank=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['name']
def __str__(self):
return self.name
+14
View File
@@ -0,0 +1,14 @@
from rest_framework import serializers
from .models import Club
class ClubSerializer(serializers.ModelSerializer):
wrestler_count = serializers.SerializerMethodField()
class Meta:
model = Club
fields = '__all__'
read_only_fields = ['created_at', 'updated_at']
def get_wrestler_count(self, obj):
return obj.wrestlers.filter(is_active=True).count()
+30
View File
@@ -0,0 +1,30 @@
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework.test import APITestCase
from rest_framework import status
from clubs.models import Club
from auth_app.models import UserProfile
class ClubAPITest(APITestCase):
def setUp(self):
self.user = User.objects.create_user(username='testuser', password='testpass123')
self.club = Club.objects.create(name='Test Club')
UserProfile.objects.create(user=self.user, club=self.club)
self.client.force_authenticate(user=self.user)
def test_list_clubs(self):
response = self.client.get('/api/v1/clubs/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_create_club(self):
initial_count = Club.objects.count()
data = {'name': 'Test Club API', 'short_name': 'TC'}
response = self.client.post('/api/v1/clubs/', data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Club.objects.count(), initial_count + 1)
self.assertEqual(Club.objects.last().name, 'Test Club API')
def test_club_str(self):
club = Club.objects.create(name="API Test Club")
self.assertEqual(str(club), "API Test Club")
+17
View File
@@ -0,0 +1,17 @@
from rest_framework import viewsets, filters
from rest_framework.permissions import IsAuthenticated
from django_filters.rest_framework import DjangoFilterBackend
from .models import Club
from .serializers import ClubSerializer
from wrestleDesk.pagination import StandardResultsSetPagination
class ClubViewSet(viewsets.ModelViewSet):
queryset = Club.objects.all()
serializer_class = ClubSerializer
pagination_class = StandardResultsSetPagination
permission_classes = [IsAuthenticated]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['is_active']
search_fields = ['name', 'short_name']
ordering_fields = ['name', 'created_at']