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:
@@ -0,0 +1,73 @@
|
||||
import unfold
|
||||
from unfold.admin import ModelAdmin as UnfoldModelAdmin
|
||||
from django.contrib import admin
|
||||
from .models import Homework, HomeworkExerciseItem, HomeworkAssignment, HomeworkAssignmentItem, HomeworkStatus, TrainingHomework, TrainingHomeworkExercise, TrainingHomeworkAssignment
|
||||
|
||||
|
||||
class HomeworkExerciseItemInline(admin.TabularInline):
|
||||
model = HomeworkExerciseItem
|
||||
extra = 1
|
||||
raw_id_fields = ['exercise']
|
||||
|
||||
|
||||
@admin.register(Homework)
|
||||
class HomeworkAdmin(UnfoldModelAdmin):
|
||||
list_display = ['title', 'club', 'due_date', 'is_active', 'exercise_count']
|
||||
list_filter = ['is_active', 'club']
|
||||
search_fields = ['title', 'description']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
raw_id_fields = ['club']
|
||||
inlines = [HomeworkExerciseItemInline]
|
||||
|
||||
def exercise_count(self, obj):
|
||||
return obj.exercise_items.count()
|
||||
exercise_count.short_description = 'Exercises'
|
||||
|
||||
|
||||
class HomeworkAssignmentItemInline(admin.TabularInline):
|
||||
model = HomeworkAssignmentItem
|
||||
extra = 0
|
||||
raw_id_fields = ['exercise']
|
||||
readonly_fields = ['completion_date']
|
||||
|
||||
|
||||
@admin.register(HomeworkAssignment)
|
||||
class HomeworkAssignmentAdmin(UnfoldModelAdmin):
|
||||
list_display = ['wrestler', 'homework', 'club', 'due_date', 'is_completed_display']
|
||||
list_filter = ['club']
|
||||
search_fields = ['wrestler__first_name', 'wrestler__last_name', 'homework__title']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
raw_id_fields = ['homework', 'wrestler', 'club']
|
||||
inlines = [HomeworkAssignmentItemInline]
|
||||
|
||||
def is_completed_display(self, obj):
|
||||
return obj.is_completed
|
||||
is_completed_display.short_description = 'Completed'
|
||||
is_completed_display.boolean = True
|
||||
|
||||
|
||||
@admin.register(HomeworkExerciseItem)
|
||||
class HomeworkExerciseItemAdmin(UnfoldModelAdmin):
|
||||
list_display = ['homework', 'exercise', 'reps', 'time_minutes', 'order']
|
||||
list_filter = ['homework']
|
||||
raw_id_fields = ['homework', 'exercise']
|
||||
|
||||
|
||||
@admin.register(HomeworkStatus)
|
||||
class HomeworkStatusAdmin(UnfoldModelAdmin):
|
||||
list_display = ['homework', 'wrestler', 'is_completed', 'completion_date']
|
||||
list_filter = ['is_completed']
|
||||
raw_id_fields = ['homework', 'wrestler']
|
||||
|
||||
|
||||
@admin.register(TrainingHomework)
|
||||
class TrainingHomeworkAdmin(UnfoldModelAdmin):
|
||||
list_display = ['id', 'training', 'created_at']
|
||||
list_select_related = ['training']
|
||||
|
||||
|
||||
@admin.register(TrainingHomeworkAssignment)
|
||||
class TrainingHomeworkAssignmentAdmin(UnfoldModelAdmin):
|
||||
list_display = ['id', 'wrestler', 'training', 'is_completed', 'created_at']
|
||||
list_filter = ['is_completed', 'created_at']
|
||||
list_select_related = ['wrestler', 'training']
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HomeworkConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'homework'
|
||||
@@ -0,0 +1,60 @@
|
||||
# Generated by Django 4.2.29 on 2026-03-19 09:05
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('exercises', '0001_initial'),
|
||||
('wrestlers', '0001_initial'),
|
||||
('clubs', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Homework',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('target_group', models.CharField(choices=[('kids', 'Kids'), ('youth', 'Youth'), ('adults', 'Adults'), ('all', 'All')], default='all', max_length=20)),
|
||||
('due_date', models.DateField(blank=True, null=True)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('club', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='homework_assignments', to='clubs.club')),
|
||||
('exercises', models.ManyToManyField(related_name='homework_assignments', to='exercises.exercise')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HomeworkStatus',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_completed', models.BooleanField(default=False)),
|
||||
('completion_date', models.DateField(blank=True, null=True)),
|
||||
('notes', models.TextField(blank=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('homework', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='statuses', to='homework.homework')),
|
||||
('wrestler', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='homework_statuses', to='wrestlers.wrestler')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('homework', 'wrestler')},
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='homework',
|
||||
index=models.Index(fields=['target_group'], name='homework_ho_target__66652f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='homework',
|
||||
index=models.Index(fields=['due_date'], name='homework_ho_due_dat_5d3fcb_idx'),
|
||||
),
|
||||
]
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
# Generated by Django 4.2.29 on 2026-03-19 14:44
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wrestlers', '0001_initial'),
|
||||
('clubs', '0001_initial'),
|
||||
('exercises', '0001_initial'),
|
||||
('homework', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='HomeworkAssignment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('due_date', models.DateField(blank=True, null=True)),
|
||||
('notes', models.TextField(blank=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HomeworkAssignmentItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_completed', models.BooleanField(default=False)),
|
||||
('completion_date', models.DateField(blank=True, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HomeworkExerciseItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('reps', models.IntegerField(blank=True, null=True)),
|
||||
('time_minutes', models.IntegerField(blank=True, null=True)),
|
||||
('order', models.IntegerField(default=0)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['homework', 'order'],
|
||||
},
|
||||
),
|
||||
migrations.RemoveIndex(
|
||||
model_name='homework',
|
||||
name='homework_ho_target__66652f_idx',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='homework',
|
||||
name='exercises',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='homework',
|
||||
name='target_group',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='homework',
|
||||
name='club',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='homework_templates', to='clubs.club'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='homework',
|
||||
index=models.Index(fields=['club'], name='homework_ho_club_id_126bad_idx'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homeworkexerciseitem',
|
||||
name='exercise',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='homework_items', to='exercises.exercise'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homeworkexerciseitem',
|
||||
name='homework',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='exercise_items', to='homework.homework'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homeworkassignmentitem',
|
||||
name='assignment',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='homework.homeworkassignment'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homeworkassignmentitem',
|
||||
name='exercise',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='assignment_items', to='exercises.exercise'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homeworkassignment',
|
||||
name='club',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='homework_assignments', to='clubs.club'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homeworkassignment',
|
||||
name='homework',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='assignments', to='homework.homework'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homeworkassignment',
|
||||
name='wrestler',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='homework_assignments', to='wrestlers.wrestler'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='homeworkexerciseitem',
|
||||
unique_together={('homework', 'exercise')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='homeworkassignmentitem',
|
||||
unique_together={('assignment', 'exercise')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='homeworkassignment',
|
||||
index=models.Index(fields=['wrestler'], name='homework_ho_wrestle_ddebdf_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='homeworkassignment',
|
||||
index=models.Index(fields=['due_date'], name='homework_ho_due_dat_12e964_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='homeworkassignment',
|
||||
unique_together={('homework', 'wrestler')},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.2.29 on 2026-03-20 14:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('homework', '0002_homeworkassignment_homeworkassignmentitem_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='homeworkassignmentitem',
|
||||
index=models.Index(fields=['assignment', 'is_completed'], name='homework_ho_assignm_791a15_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='homeworkassignmentitem',
|
||||
index=models.Index(fields=['completion_date'], name='homework_ho_complet_55c380_idx'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.2.29 on 2026-03-20 14:40
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('homework', '0003_add_assignment_item_indexes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='homeworkexerciseitem',
|
||||
name='reps',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='homeworkexerciseitem',
|
||||
name='time_minutes',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
# Generated by Django 4.2.29 on 2026-03-22 12:17
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wrestlers', '0002_alter_wrestler_license_scan_alter_wrestler_photo'),
|
||||
('clubs', '0001_initial'),
|
||||
('trainings', '0005_training_club_and_more'),
|
||||
('exercises', '0003_alter_exercise_media'),
|
||||
('homework', '0004_alter_homeworkexerciseitem_reps_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TrainingHomework',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('training', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='homework_assignments', to='trainings.training')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrainingHomeworkExercise',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('reps', models.PositiveIntegerField(blank=True, null=True)),
|
||||
('time_minutes', models.PositiveIntegerField(blank=True, null=True)),
|
||||
('order', models.IntegerField(default=0)),
|
||||
('exercise', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='training_homework_items', to='exercises.exercise')),
|
||||
('training_homework', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='exercises', to='homework.traininghomework')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['training_homework', 'order'],
|
||||
'unique_together': {('training_homework', 'exercise')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TrainingHomeworkAssignment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('notes', models.TextField(blank=True)),
|
||||
('is_completed', models.BooleanField(default=False)),
|
||||
('completion_date', models.DateField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('club', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='training_homework_assignments', to='clubs.club')),
|
||||
('training_homework', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='assignments', to='homework.traininghomework')),
|
||||
('wrestler', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='training_homework_assignments', to='wrestlers.wrestler')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['wrestler'], name='homework_tr_wrestle_63f0d7_idx'), models.Index(fields=['is_completed'], name='homework_tr_is_comp_a157f2_idx'), models.Index(fields=['club'], name='homework_tr_club_id_4648ff_idx')],
|
||||
'unique_together': {('training_homework', 'wrestler')},
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='traininghomework',
|
||||
index=models.Index(fields=['training'], name='homework_tr_trainin_950ce2_idx'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,66 @@
|
||||
# Generated by Django 4.2.29 on 2026-03-23 06:45
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wrestlers', '0002_alter_wrestler_license_scan_alter_wrestler_photo'),
|
||||
('exercises', '0003_alter_exercise_media'),
|
||||
('trainings', '0005_training_club_and_more'),
|
||||
('homework', '0005_traininghomework_traininghomeworkexercise_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TrainingHomeworkExerciseItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('reps', models.PositiveIntegerField(blank=True, null=True)),
|
||||
('time_minutes', models.PositiveIntegerField(blank=True, null=True)),
|
||||
('order', models.IntegerField(default=0)),
|
||||
('is_completed', models.BooleanField(default=False)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['assignment', 'order'],
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='traininghomeworkassignment',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='traininghomeworkassignment',
|
||||
name='training',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='homework_assignments', to='trainings.training'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='traininghomework',
|
||||
name='training',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='homework_legacy', to='trainings.training'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='traininghomeworkassignment',
|
||||
unique_together={('training', 'wrestler')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='traininghomeworkassignment',
|
||||
index=models.Index(fields=['training'], name='homework_tr_trainin_048980_idx'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='traininghomeworkexerciseitem',
|
||||
name='assignment',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='exercises', to='homework.traininghomeworkassignment'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='traininghomeworkexerciseitem',
|
||||
name='exercise',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='training_homework_exercises', to='exercises.exercise'),
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='traininghomeworkassignment',
|
||||
name='training_homework',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,188 @@
|
||||
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}"
|
||||
@@ -0,0 +1,190 @@
|
||||
from rest_framework import serializers
|
||||
from .models import (
|
||||
Homework, HomeworkExerciseItem, HomeworkAssignment,
|
||||
HomeworkAssignmentItem, HomeworkStatus,
|
||||
TrainingHomeworkAssignment, TrainingHomeworkExerciseItem
|
||||
)
|
||||
|
||||
|
||||
class HomeworkExerciseItemSerializer(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 = HomeworkExerciseItem
|
||||
fields = ['id', 'exercise', 'exercise_name', 'exercise_category', 'reps', 'time_minutes', 'order']
|
||||
|
||||
|
||||
class HomeworkSerializer(serializers.ModelSerializer):
|
||||
exercise_items = HomeworkExerciseItemSerializer(many=True, read_only=True)
|
||||
club_name = serializers.CharField(source='club.name', read_only=True)
|
||||
exercise_count = serializers.IntegerField(source='exercise_items.count', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Homework
|
||||
fields = ['id', 'title', 'description', 'club', 'club_name', 'due_date', 'is_active',
|
||||
'exercise_items', 'exercise_count', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
class HomeworkDetailSerializer(HomeworkSerializer):
|
||||
exercise_items = HomeworkExerciseItemSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta(HomeworkSerializer.Meta):
|
||||
fields = HomeworkSerializer.Meta.fields
|
||||
|
||||
|
||||
class HomeworkAssignmentItemSerializer(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 = HomeworkAssignmentItem
|
||||
fields = ['id', 'exercise', 'exercise_name', 'exercise_category', 'is_completed', 'completion_date']
|
||||
|
||||
|
||||
class HomeworkAssignmentSerializer(serializers.ModelSerializer):
|
||||
homework_title = serializers.CharField(source='homework.title', read_only=True)
|
||||
club_name = serializers.CharField(source='club.name', read_only=True)
|
||||
wrestler_name = serializers.SerializerMethodField()
|
||||
completed_items = serializers.SerializerMethodField()
|
||||
total_items = serializers.SerializerMethodField()
|
||||
items = HomeworkAssignmentItemSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = HomeworkAssignment
|
||||
fields = ['id', 'homework', 'homework_title', 'wrestler', 'wrestler_name', 'club', 'club_name',
|
||||
'due_date', 'notes', 'is_completed', 'completion_date', 'completed_items',
|
||||
'total_items', 'items', 'created_at']
|
||||
|
||||
def get_wrestler_name(self, obj):
|
||||
return f"{obj.wrestler.first_name} {obj.wrestler.last_name}"
|
||||
|
||||
def get_completed_items(self, obj):
|
||||
return obj.items.filter(is_completed=True).count()
|
||||
|
||||
def get_total_items(self, obj):
|
||||
return obj.items.count()
|
||||
|
||||
|
||||
class HomeworkAssignmentListSerializer(serializers.ModelSerializer):
|
||||
homework_title = serializers.CharField(source='homework.title', read_only=True)
|
||||
club_name = serializers.CharField(source='club.name', read_only=True)
|
||||
wrestler_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = HomeworkAssignment
|
||||
fields = ['id', 'homework', 'homework_title', 'wrestler', 'wrestler_name', 'club', 'club_name',
|
||||
'due_date', 'notes', 'is_completed', 'completion_date', 'created_at']
|
||||
|
||||
def get_wrestler_name(self, obj):
|
||||
return f"{obj.wrestler.first_name} {obj.wrestler.last_name}"
|
||||
|
||||
|
||||
class AssignHomeworkSerializer(serializers.Serializer):
|
||||
homework = serializers.IntegerField()
|
||||
wrestlers = serializers.ListField(child=serializers.IntegerField())
|
||||
due_date = serializers.DateField(required=False, allow_null=True)
|
||||
notes = serializers.CharField(required=False, allow_blank=True, default='')
|
||||
|
||||
|
||||
class CompleteItemSerializer(serializers.Serializer):
|
||||
item_id = serializers.IntegerField()
|
||||
|
||||
|
||||
class HomeworkStatusSerializer(serializers.ModelSerializer):
|
||||
wrestler_name = serializers.CharField(source='wrestler.__str__', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = HomeworkStatus
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
# NEUES SYSTEM: Training-basierte Homework
|
||||
|
||||
class TrainingHomeworkExerciseItemSerializer(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 = TrainingHomeworkExerciseItem
|
||||
fields = ['id', 'exercise', 'exercise_name', 'exercise_category', 'reps', 'time_minutes', 'order', 'is_completed']
|
||||
|
||||
|
||||
class TrainingHomeworkAssignmentSerializer(serializers.ModelSerializer):
|
||||
exercises = serializers.SerializerMethodField()
|
||||
training_date = serializers.DateField(source='training.date', read_only=True)
|
||||
training_group = serializers.CharField(source='training.group', read_only=True)
|
||||
wrestler_name = serializers.SerializerMethodField()
|
||||
wrestler_group = serializers.CharField(source='wrestler.group', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = TrainingHomeworkAssignment
|
||||
fields = ['id', 'training', 'training_date', 'training_group', 'wrestler', 'wrestler_name',
|
||||
'wrestler_group', 'notes', 'is_completed', 'completion_date', 'exercises', 'created_at']
|
||||
|
||||
def get_wrestler_name(self, obj):
|
||||
return f"{obj.wrestler.first_name} {obj.wrestler.last_name}"
|
||||
|
||||
def get_exercises(self, obj):
|
||||
exercises = obj.exercises.all().order_by('order')
|
||||
return TrainingHomeworkExerciseItemSerializer(exercises, many=True).data
|
||||
|
||||
|
||||
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 utils.permissions 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', '')
|
||||
|
||||
try:
|
||||
training_obj = Training.objects.get(id=training_id)
|
||||
except Training.DoesNotExist:
|
||||
raise serializers.ValidationError({'training': f'Training with id {training_id} does not exist'})
|
||||
|
||||
try:
|
||||
wrestler_obj = Wrestler.objects.get(id=wrestler_id)
|
||||
except Wrestler.DoesNotExist:
|
||||
raise serializers.ValidationError({'wrestler': f'Wrestler with id {wrestler_id} does not exist'})
|
||||
|
||||
# Create assignment
|
||||
assignment = TrainingHomeworkAssignment.objects.create(
|
||||
training=training_obj,
|
||||
wrestler=wrestler_obj,
|
||||
club=club,
|
||||
notes=notes
|
||||
)
|
||||
|
||||
# Create individual exercises for this assignment
|
||||
for i, ex in enumerate(exercises_data):
|
||||
try:
|
||||
exercise_obj = Exercise.objects.get(id=ex['exercise'])
|
||||
TrainingHomeworkExerciseItem.objects.create(
|
||||
assignment=assignment,
|
||||
exercise=exercise_obj,
|
||||
reps=ex.get('reps'),
|
||||
time_minutes=ex.get('time_minutes'),
|
||||
order=i
|
||||
)
|
||||
except Exercise.DoesNotExist:
|
||||
pass
|
||||
|
||||
return assignment
|
||||
|
||||
def to_representation(self, instance):
|
||||
return TrainingHomeworkAssignmentSerializer(instance, context=self.context).data
|
||||
@@ -0,0 +1,237 @@
|
||||
from rest_framework import viewsets, filters, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django.utils import timezone
|
||||
from django.db import transaction
|
||||
|
||||
from utils.permissions import ClubLevelPermission, ClubFilterBackend
|
||||
from .models import (
|
||||
Homework, HomeworkExerciseItem, HomeworkAssignment,
|
||||
HomeworkAssignmentItem, HomeworkStatus,
|
||||
TrainingHomeworkAssignment, TrainingHomeworkExerciseItem
|
||||
)
|
||||
from .serializers import (
|
||||
HomeworkSerializer, HomeworkDetailSerializer, HomeworkExerciseItemSerializer,
|
||||
HomeworkAssignmentSerializer, HomeworkAssignmentListSerializer,
|
||||
AssignHomeworkSerializer, CompleteItemSerializer, HomeworkStatusSerializer,
|
||||
TrainingHomeworkAssignmentSerializer, TrainingHomeworkAssignmentCreateSerializer
|
||||
)
|
||||
from wrestleDesk.pagination import StandardResultsSetPagination
|
||||
|
||||
|
||||
class HomeworkViewSet(viewsets.ModelViewSet):
|
||||
queryset = Homework.objects.prefetch_related('exercise_items', 'exercise_items__exercise').all()
|
||||
serializer_class = HomeworkSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [IsAuthenticated]
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
filterset_fields = ['club', 'is_active']
|
||||
search_fields = ['title', 'description']
|
||||
ordering_fields = ['created_at', 'due_date']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'retrieve':
|
||||
return HomeworkDetailSerializer
|
||||
return HomeworkSerializer
|
||||
|
||||
|
||||
class HomeworkExerciseItemViewSet(viewsets.ModelViewSet):
|
||||
queryset = HomeworkExerciseItem.objects.select_related('homework', 'exercise').all()
|
||||
serializer_class = HomeworkExerciseItemSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [IsAuthenticated]
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['homework']
|
||||
|
||||
|
||||
class HomeworkAssignmentViewSet(viewsets.ModelViewSet):
|
||||
queryset = HomeworkAssignment.objects.select_related('homework', 'wrestler', 'club').prefetch_related('items').all()
|
||||
serializer_class = HomeworkAssignmentSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [IsAuthenticated]
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
filterset_fields = ['homework', 'wrestler', 'club', 'is_completed']
|
||||
search_fields = ['wrestler__first_name', 'wrestler__last_name']
|
||||
ordering_fields = ['created_at', 'due_date']
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == 'list':
|
||||
return HomeworkAssignmentListSerializer
|
||||
return HomeworkAssignmentSerializer
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
@transaction.atomic
|
||||
def complete_item(self, request, pk=None):
|
||||
serializer = CompleteItemSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
assignment = self.get_object()
|
||||
item_id = serializer.validated_data['item_id']
|
||||
|
||||
try:
|
||||
item = assignment.items.get(id=item_id)
|
||||
except HomeworkAssignmentItem.DoesNotExist:
|
||||
return Response(
|
||||
{'detail': 'Item not found'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
item.is_completed = True
|
||||
item.completion_date = timezone.now()
|
||||
item.save()
|
||||
|
||||
return Response({'status': 'item completed'})
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
@transaction.atomic
|
||||
def assign(self, request):
|
||||
serializer = AssignHomeworkSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
homework_id = serializer.validated_data['homework']
|
||||
wrestler_ids = serializer.validated_data['wrestlers']
|
||||
due_date = serializer.validated_data.get('due_date')
|
||||
notes = serializer.validated_data.get('notes', '')
|
||||
|
||||
try:
|
||||
homework = Homework.objects.get(id=homework_id)
|
||||
except Homework.DoesNotExist:
|
||||
return Response({'detail': 'Homework not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
created_assignments = []
|
||||
errors = []
|
||||
|
||||
for wrestler_id in wrestler_ids:
|
||||
try:
|
||||
existing = HomeworkAssignment.objects.filter(
|
||||
homework=homework,
|
||||
wrestler_id=wrestler_id
|
||||
).exists()
|
||||
|
||||
if existing:
|
||||
errors.append(f"Wrestler {wrestler_id} hat bereits diese Hausaufgabe")
|
||||
continue
|
||||
|
||||
assignment = HomeworkAssignment.objects.create(
|
||||
homework=homework,
|
||||
wrestler_id=wrestler_id,
|
||||
club=homework.club,
|
||||
due_date=due_date,
|
||||
notes=notes
|
||||
)
|
||||
|
||||
# Copy exercises from homework to assignment
|
||||
for exercise_item in homework.exercise_items.all():
|
||||
HomeworkAssignmentItem.objects.create(
|
||||
assignment=assignment,
|
||||
exercise=exercise_item.exercise
|
||||
)
|
||||
|
||||
created_assignments.append(assignment.id)
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"Wrestler {wrestler_id}: {str(e)}")
|
||||
|
||||
return Response({
|
||||
'created': created_assignments,
|
||||
'errors': errors
|
||||
})
|
||||
|
||||
|
||||
class HomeworkStatusViewSet(viewsets.ModelViewSet):
|
||||
queryset = HomeworkStatus.objects.select_related('homework', 'wrestler').all()
|
||||
serializer_class = HomeworkStatusSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [IsAuthenticated]
|
||||
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
|
||||
filterset_fields = ['homework', 'wrestler', 'is_completed']
|
||||
ordering_fields = ['created_at', 'completion_date']
|
||||
|
||||
|
||||
# NEUES SYSTEM: Training-basierte Homework
|
||||
|
||||
class TrainingHomeworkAssignmentViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
serializer_class = TrainingHomeworkAssignmentSerializer
|
||||
filterset_fields = ['is_completed', 'training', 'wrestler']
|
||||
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):
|
||||
queryset = TrainingHomeworkAssignment.objects.select_related(
|
||||
'training', 'wrestler'
|
||||
).prefetch_related(
|
||||
'exercises', 'exercises__exercise'
|
||||
).all()
|
||||
|
||||
# Filter by training ID if provided
|
||||
training_id = self.request.query_params.get('training')
|
||||
if training_id:
|
||||
queryset = queryset.filter(training_id=training_id)
|
||||
|
||||
return queryset
|
||||
|
||||
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()
|
||||
# Also mark all exercises as completed
|
||||
assignment.exercises.update(is_completed=True)
|
||||
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()
|
||||
# Also mark all exercises as not completed
|
||||
assignment.exercises.update(is_completed=False)
|
||||
serializer = self.get_serializer(assignment)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def complete_exercise(self, request, pk=None):
|
||||
assignment = self.get_object()
|
||||
exercise_id = request.data.get('exercise_id')
|
||||
|
||||
if not exercise_id:
|
||||
return Response({'detail': 'exercise_id is required'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
item = assignment.exercises.get(id=exercise_id)
|
||||
item.is_completed = True
|
||||
item.save()
|
||||
return Response({'status': 'exercise completed'})
|
||||
except TrainingHomeworkExerciseItem.DoesNotExist:
|
||||
return Response({'detail': 'Exercise not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def uncomplete_exercise(self, request, pk=None):
|
||||
assignment = self.get_object()
|
||||
exercise_id = request.data.get('exercise_id')
|
||||
|
||||
if not exercise_id:
|
||||
return Response({'detail': 'exercise_id is required'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
item = assignment.exercises.get(id=exercise_id)
|
||||
item.is_completed = False
|
||||
item.save()
|
||||
return Response({'status': 'exercise uncompleted'})
|
||||
except TrainingHomeworkExerciseItem.DoesNotExist:
|
||||
return Response({'detail': 'Exercise not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||
Reference in New Issue
Block a user