3fefc550fe
- 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
189 lines
7.6 KiB
Python
189 lines
7.6 KiB
Python
from django.db import models
|
|
|
|
|
|
class Homework(models.Model):
|
|
title = models.CharField(max_length=200)
|
|
description = models.TextField(blank=True)
|
|
club = models.ForeignKey('clubs.Club', on_delete=models.CASCADE, related_name='homework_templates', null=True, blank=True)
|
|
due_date = models.DateField(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 = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['due_date']),
|
|
models.Index(fields=['club']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
|
|
class HomeworkExerciseItem(models.Model):
|
|
homework = models.ForeignKey(Homework, on_delete=models.CASCADE, related_name='exercise_items')
|
|
exercise = models.ForeignKey('exercises.Exercise', on_delete=models.CASCADE, related_name='homework_items')
|
|
reps = models.PositiveIntegerField(null=True, blank=True)
|
|
time_minutes = models.PositiveIntegerField(null=True, blank=True)
|
|
order = models.IntegerField(default=0)
|
|
|
|
class Meta:
|
|
ordering = ['homework', 'order']
|
|
unique_together = ['homework', 'exercise']
|
|
|
|
def __str__(self):
|
|
return f"{self.homework.title} - {self.exercise.name}"
|
|
|
|
|
|
class HomeworkAssignment(models.Model):
|
|
homework = models.ForeignKey(Homework, on_delete=models.CASCADE, related_name='assignments')
|
|
wrestler = models.ForeignKey('wrestlers.Wrestler', on_delete=models.CASCADE, related_name='homework_assignments')
|
|
club = models.ForeignKey('clubs.Club', on_delete=models.CASCADE, related_name='homework_assignments', null=True, blank=True)
|
|
due_date = models.DateField(null=True, blank=True)
|
|
notes = models.TextField(blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
unique_together = ['homework', 'wrestler']
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['wrestler']),
|
|
models.Index(fields=['due_date']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.wrestler} - {self.homework.title}"
|
|
|
|
def clean(self):
|
|
from wrestlers.models import Wrestler
|
|
if self.wrestler and self.homework and self.wrestler.club_id != self.homework.club_id:
|
|
from django.core.exceptions import ValidationError
|
|
raise ValidationError('Wrestler must belong to the same club as the homework')
|
|
|
|
@property
|
|
def is_completed(self):
|
|
items = self.items.all()
|
|
if not items.exists():
|
|
return False
|
|
return all(item.is_completed for item in items)
|
|
|
|
@property
|
|
def completion_date(self):
|
|
if self.is_completed:
|
|
return self.items.filter(is_completed=True).order_by('-completion_date').first().completion_date
|
|
return None
|
|
|
|
|
|
class HomeworkAssignmentItem(models.Model):
|
|
assignment = models.ForeignKey(HomeworkAssignment, on_delete=models.CASCADE, related_name='items')
|
|
exercise = models.ForeignKey('exercises.Exercise', on_delete=models.CASCADE, related_name='assignment_items')
|
|
is_completed = models.BooleanField(default=False)
|
|
completion_date = models.DateField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
unique_together = ['assignment', 'exercise']
|
|
indexes = [
|
|
models.Index(fields=['assignment', 'is_completed']),
|
|
models.Index(fields=['completion_date']),
|
|
]
|
|
|
|
def __str__(self):
|
|
status = "✓" if self.is_completed else "✗"
|
|
return f"{self.assignment} - {self.exercise.name} {status}"
|
|
|
|
|
|
class HomeworkStatus(models.Model):
|
|
homework = models.ForeignKey(Homework, on_delete=models.CASCADE, related_name='statuses')
|
|
wrestler = models.ForeignKey('wrestlers.Wrestler', on_delete=models.CASCADE, related_name='homework_statuses')
|
|
is_completed = models.BooleanField(default=False)
|
|
completion_date = models.DateField(null=True, blank=True)
|
|
notes = models.TextField(blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
unique_together = ['homework', 'wrestler']
|
|
|
|
def __str__(self):
|
|
status = "✓" if self.is_completed else "✗"
|
|
return f"{self.wrestler} - {self.homework.title} {status}"
|
|
|
|
|
|
# NEUES SYSTEM: Training-basierte Homework
|
|
# Jeder Wrestler bekommt individuelle Übungen zugewiesen
|
|
|
|
class TrainingHomeworkAssignment(models.Model):
|
|
"""A homework assignment for a specific wrestler in a specific training"""
|
|
training = models.ForeignKey('trainings.Training', on_delete=models.CASCADE, related_name='homework_assignments', default=1)
|
|
wrestler = models.ForeignKey('wrestlers.Wrestler', on_delete=models.CASCADE, related_name='training_homework_assignments')
|
|
club = models.ForeignKey('clubs.Club', on_delete=models.CASCADE, related_name='training_homework_assignments', null=True, blank=True)
|
|
notes = models.TextField(blank=True)
|
|
is_completed = models.BooleanField(default=False)
|
|
completion_date = models.DateField(null=True, blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
unique_together = ['training', 'wrestler']
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['training']),
|
|
models.Index(fields=['wrestler']),
|
|
models.Index(fields=['is_completed']),
|
|
models.Index(fields=['club']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.wrestler} - Training {self.training_id}"
|
|
|
|
|
|
class TrainingHomeworkExerciseItem(models.Model):
|
|
"""Individual exercises assigned to a specific wrestler (NOT shared)"""
|
|
assignment = models.ForeignKey(TrainingHomeworkAssignment, on_delete=models.CASCADE, related_name='exercises')
|
|
exercise = models.ForeignKey('exercises.Exercise', on_delete=models.CASCADE, related_name='training_homework_exercises')
|
|
reps = models.PositiveIntegerField(null=True, blank=True)
|
|
time_minutes = models.PositiveIntegerField(null=True, blank=True)
|
|
order = models.IntegerField(default=0)
|
|
is_completed = models.BooleanField(default=False)
|
|
|
|
class Meta:
|
|
ordering = ['assignment', 'order']
|
|
|
|
def __str__(self):
|
|
return f"{self.assignment} - {self.exercise.name}"
|
|
|
|
|
|
# ALTES SYSTEM (für Rückwärtskompatibilität - wird nicht mehr verwendet)
|
|
|
|
class TrainingHomework(models.Model):
|
|
"""DEPRECATED: Each wrestler now has individual assignments"""
|
|
training = models.ForeignKey('trainings.Training', on_delete=models.CASCADE, related_name='homework_legacy')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['training']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"Homework for Training {self.training_id}"
|
|
|
|
|
|
class TrainingHomeworkExercise(models.Model):
|
|
"""DEPRECATED: Exercises are now per-assignment"""
|
|
training_homework = models.ForeignKey(TrainingHomework, on_delete=models.CASCADE, related_name='exercises')
|
|
exercise = models.ForeignKey('exercises.Exercise', on_delete=models.CASCADE, related_name='training_homework_items')
|
|
reps = models.PositiveIntegerField(null=True, blank=True)
|
|
time_minutes = models.PositiveIntegerField(null=True, blank=True)
|
|
order = models.IntegerField(default=0)
|
|
|
|
class Meta:
|
|
ordering = ['training_homework', 'order']
|
|
unique_together = ['training_homework', 'exercise']
|
|
|
|
def __str__(self):
|
|
return f"{self.training_homework} - {self.exercise.name}"
|