# 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