Files
WrestleDesk/docs/superpowers/plans/2026-03-22-homework-redesign-plan.md
T
Andrej Spielmann 3fefc550fe 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
2026-03-26 13:24:57 +01:00

413 lines
13 KiB
Markdown

# Homework System Redesign - Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Redesign homework system to assign exercises directly from training page without templates.
**Architecture:**
- New models: `TrainingHomework`, `TrainingHomeworkExercise`, `TrainingHomeworkAssignment`
- Training detail page redesigned with 2-column layout: participants | training homework
- Homework button per participant opens modal to select exercises
- Homework page shows all assignments grouped by training
**Tech Stack:** Django + DRF backend, Next.js + React frontend
---
## File Structure
### Backend (Modified)
- `backend/homework/models.py` - Add new models
- `backend/homework/serializers.py` - Add new serializers
- `backend/homework/views.py` - Add new ViewSet
- `backend/homework/urls.py` - Add new routes
- `backend/homework/admin.py` - Register new models
### Frontend (Modified)
- `frontend/src/lib/api.ts` - Add new types
- `frontend/src/app/(dashboard)/trainings/[id]/page.tsx` - Complete redesign
- `frontend/src/app/(dashboard)/homework/page.tsx` - Complete rewrite
- `frontend/src/app/(dashboard)/dashboard/page.tsx` - Add homework stats
---
## Tasks
### Task 1: Backend - New Models
**Files:**
- Modify: `backend/homework/models.py`
- [ ] **Step 1: Add new models at end of file**
```python
class TrainingHomework(models.Model):
training = models.ForeignKey('trainings.Training', on_delete=models.CASCADE, related_name='homework_assignments')
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):
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}"
class TrainingHomeworkAssignment(models.Model):
training_homework = models.ForeignKey(TrainingHomework, on_delete=models.CASCADE, related_name='assignments')
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_homework', 'wrestler']
ordering = ['-created_at']
indexes = [
models.Index(fields=['wrestler']),
models.Index(fields=['is_completed']),
models.Index(fields=['club']),
]
def __str__(self):
return f"{self.wrestler} - Training {self.training_homework.training_id}"
```
- [ ] **Step 2: Run makemigrations**
Run: `cd backend && python manage.py makemigrations homework`
Expected: "Migrations for 'homework': homework/migrations/xxx_create_training_homework.py"
- [ ] **Step 3: Run migrate**
Run: `cd backend && python manage.py migrate`
Expected: "Applying homework.xxx_create_training_homework... OK"
---
### Task 2: Backend - Serializers
**Files:**
- Modify: `backend/homework/serializers.py`
- [ ] **Step 1: Add new serializers**
Add at end of file:
```python
class TrainingHomeworkExerciseSerializer(serializers.ModelSerializer):
exercise_name = serializers.CharField(source='exercise.name', read_only=True)
exercise_category = serializers.CharField(source='exercise.category', read_only=True)
class Meta:
model = TrainingHomeworkExercise
fields = ['id', 'exercise', 'exercise_name', 'exercise_category', 'reps', 'time_minutes', 'order']
class TrainingHomeworkSerializer(serializers.ModelSerializer):
exercises = TrainingHomeworkExerciseSerializer(many=True, read_only=True)
training_date = serializers.DateField(source='training.date', read_only=True)
training_group = serializers.CharField(source='training.group', read_only=True)
class Meta:
model = TrainingHomework
fields = ['id', 'training', 'training_date', 'training_group', 'exercises', 'created_at']
class TrainingHomeworkAssignmentSerializer(serializers.ModelSerializer):
training_homework_detail = TrainingHomeworkSerializer(source='training_homework', read_only=True)
wrestler_name = serializers.SerializerMethodField()
wrestler_group = serializers.CharField(source='wrestler.group', read_only=True)
class Meta:
model = TrainingHomeworkAssignment
fields = ['id', 'training_homework', 'training_homework_detail', 'wrestler', 'wrestler_name', 'wrestler_group', 'notes', 'is_completed', 'completion_date', 'created_at']
def get_wrestler_name(self, obj):
return f"{obj.wrestler.first_name} {obj.wrestler.last_name}"
class TrainingHomeworkAssignmentCreateSerializer(serializers.Serializer):
training = serializers.IntegerField()
wrestler = serializers.IntegerField()
exercises = serializers.ListField(
child=serializers.DictField(child=serializers.IntegerField(allow_null=True))
)
notes = serializers.CharField(required=False, allow_blank=True, default='')
def create(self, validated_data):
from clubs.utils import get_user_club
from exercises.models import Exercise
from trainings.models import Training
from wrestlers.models import Wrestler
user = self.context['request'].user
club = get_user_club(user)
training_id = validated_data['training']
wrestler_id = validated_data['wrestler']
exercises_data = validated_data['exercises']
notes = validated_data.get('notes', '')
# Create or get TrainingHomework for this training
training_obj = Training.objects.get(id=training_id)
training_homework, _ = TrainingHomework.objects.get_or_create(training=training_obj)
# Create assignment
assignment = TrainingHomeworkAssignment.objects.create(
training_homework=training_homework,
wrestler_id=wrestler_id,
club=club,
notes=notes
)
# Add exercises
for i, ex in enumerate(exercises_data):
TrainingHomeworkExercise.objects.create(
training_homework=training_homework,
exercise_id=ex['exercise'],
reps=ex.get('reps'),
time_minutes=ex.get('time_minutes'),
order=i
)
return assignment
```
---
### Task 3: Backend - Views
**Files:**
- Modify: `backend/homework/views.py`
- [ ] **Step 1: Add new ViewSet**
Add at end of file:
```python
class TrainingHomeworkAssignmentViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, ClubLevelPermission]
filter_backends = [ClubFilterBackend, filters.SearchFilter, filters.OrderingFilter]
serializer_class = TrainingHomeworkAssignmentSerializer
filterset_fields = ['is_completed']
search_fields = ['wrestler__first_name', 'wrestler__last_name']
ordering_fields = ['created_at', 'is_completed']
http_method_names = ['get', 'post', 'patch', 'delete']
def get_queryset(self):
from clubs.utils import get_user_club
club = get_user_club(self.request.user)
return TrainingHomeworkAssignment.objects.filter(club=club).select_related(
'training_homework', 'training_homework__training', 'wrestler'
)
def get_serializer_class(self):
if self.action == 'create':
return TrainingHomeworkAssignmentCreateSerializer
return TrainingHomeworkAssignmentSerializer
@action(detail=True, methods=['post'])
def complete(self, request, pk=None):
assignment = self.get_object()
assignment.is_completed = True
assignment.completion_date = timezone.now().date()
assignment.save()
serializer = self.get_serializer(assignment)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def uncomplete(self, request, pk=None):
assignment = self.get_object()
assignment.is_completed = False
assignment.completion_date = None
assignment.save()
serializer = self.get_serializer(assignment)
return Response(serializer.data)
```
- [ ] **Step 2: Import new serializers at top of file**
Add to imports:
```python
from .serializers import (
# ... existing imports ...
TrainingHomeworkAssignmentSerializer,
TrainingHomeworkAssignmentCreateSerializer,
)
```
---
### Task 4: Backend - URLs
**Files:**
- Modify: `backend/homework/urls.py`
- [ ] **Step 1: Add new route**
Add to urlpatterns:
```python
router.register(r'training-assignments', views.TrainingHomeworkAssignmentViewSet, basename='training-assignment')
```
---
### Task 5: Backend - Admin
**Files:**
- Modify: `backend/homework/admin.py`
- [ ] **Step 1: Register new models**
Add at end of file:
```python
@admin.register(TrainingHomework)
class TrainingHomeworkAdmin(admin.ModelAdmin):
list_display = ['id', 'training', 'created_at']
list_select_related = ['training']
@admin.register(TrainingHomeworkAssignment)
class TrainingHomeworkAssignmentAdmin(admin.ModelAdmin):
list_display = ['id', 'wrestler', 'training_homework', 'is_completed', 'created_at']
list_filter = ['is_completed', 'created_at']
list_select_related = ['wrestler', 'training_homework__training']
```
---
### Task 6: Frontend - Types
**Files:**
- Modify: `frontend/src/lib/api.ts`
- [ ] **Step 1: Add new interfaces**
Add after existing homework interfaces:
```typescript
export interface ITrainingHomeworkExercise {
id: number
exercise: number
exercise_name: string
exercise_category: string
reps: number | null
time_minutes: number | null
order: number
}
export interface ITrainingHomework {
id: number
training: number
training_date: string
training_group: string
exercises: ITrainingHomeworkExercise[]
created_at: string
}
export interface ITrainingHomeworkAssignment {
id: number
training_homework: number
training_homework_detail: ITrainingHomework
wrestler: number
wrestler_name: string
wrestler_group: string
notes: string
is_completed: boolean
completion_date: string | null
created_at: string
}
```
---
### Task 7: Frontend - Training Page Redesign
**Files:**
- Modify: `frontend/src/app/(dashboard)/trainings/[id]/page.tsx`
This is a complete rewrite. Key changes:
- 2-column layout: left = participants, right = training homework for this session
- Each participant has a homework button (BookOpen icon)
- Click opens modal to select exercises and assign
- [ ] **Step 1: Read current file** (already done above)
- [ ] **Step 2: Replace the entire file content**
Write new content with:
1. State for homework modal
2. State for selected exercises (with reps/time)
3. Fetch exercises for the modal
4. POST to `/homework/training-assignments/` on submit
5. 2-column layout below details section
---
### Task 8: Frontend - Homework Page
**Files:**
- Modify: `frontend/src/app/(dashboard)/homework/page.tsx`
Complete rewrite:
- Fetch from `/homework/training-assignments/`
- Group by training date
- Filter: All / Open / Completed
- Search by wrestler name
- Each assignment shows wrestler, exercises, status
- Toggle complete/uncomplete via API
---
### Task 9: Frontend - Dashboard Stats
**Files:**
- Modify: `frontend/src/app/(dashboard)/dashboard/page.tsx`
- [ ] **Step 1: Add homework count to stats**
Add a new stat card showing count of incomplete assignments from `/homework/training-assignments/?is_completed=false`
---
### Task 10: Build & Test
- [ ] **Step 1: Run backend migrate**
Run: `cd backend && python manage.py migrate`
- [ ] **Step 2: Run frontend build**
Run: `cd frontend && npm run build`
- [ ] **Step 3: Test manually**
1. Go to training detail page
2. Add a participant
3. Click homework button on participant
4. Select exercises and assign
5. Go to homework page and verify assignment appears
6. Toggle completion status
7. Check dashboard for homework count