diff --git a/.github/workflows/deploy_production.yaml b/.github/workflows/deploy_production.yaml index 032200ab..f8c7e232 100644 --- a/.github/workflows/deploy_production.yaml +++ b/.github/workflows/deploy_production.yaml @@ -30,7 +30,7 @@ jobs: docker compose -f docker-compose.prod.yaml down docker compose -f docker-compose.prod.yaml up -d docker compose -f docker-compose.prod.yaml exec -T adminpanel bash -c "while ! 0 and + # The training must not be finished yet, and you can check in only during 1 week before the training start training.start < (time_now + _week_delta) and time_now < training.end and + # The student can only get 4 hours at one day (total_hours + training.academic_duration) <= 4 and + # The student can only get 2 hours at one day for the same sport type (same_type_hours + training.academic_duration) <= 2 and - (student.medical_group in allowed_medical_groups or student in allowed_students) and - training.group.allowed_gender in (student.gender, -1) + # Students in "Banned students" list are always prohibited + student not in banned_students and + ( + # Students in "Allowed students" list can check in, no matter their medical group or gender + student in allowed_students or + # Other students must be of allowed medical groups and allowed gender + (student.medical_group in allowed_medical_groups and + training.group.allowed_gender in (student.gender, -1)) + ) ) return result @@ -98,14 +110,28 @@ def get_trainings_for_student(student: Student, start: datetime, end: datetime): # Assuming TrainingCheckIn model has a 'student' and 'training' foreign key. # And Training has a 'group' foreign key with an 'allowed_medical_groups' many-to-many field. semester_id = get_ongoing_semester().id - trainings = Training.objects.filter( - Q(start__range=(start, end)) | Q(end__range=(start, end)) | (Q(start__lte=start) & Q(end__gte=end)), - ~Q(group__sport=None), - group__semester=semester_id, - ).prefetch_related( - group_prefetch, - "training_class", - "checkins", + trainings = ( + Training.objects.filter( + # Filter by requested time range + Q(start__range=(start, end)) + | Q(end__range=(start, end)) + | (Q(start__lte=start) & Q(end__gte=end)), + # Do not show 'Self training', 'Extra sport events', 'Medical leave', etc. trainings + ~Q(group__sport=None), + # The student must either have acceptable medical group + Q(group__allowed_medical_groups=student.medical_group) + # ... or be in 'Allowed students' list + | Q(group__allowed_students=student.pk), + # Show only for current semester + group__semester=semester_id, + ) + # Do not show the training if a student is in 'Banned students' list + .exclude(group__banned_students=student.pk) + .prefetch_related( + group_prefetch, + "training_class", + "checkins", + ) ) # get all student check-ins for the given time range diff --git a/adminpage/api/crud/crud_users.py b/adminpage/api/crud/crud_users.py index 984a18e7..0d1aca70 100644 --- a/adminpage/api/crud/crud_users.py +++ b/adminpage/api/crud/crud_users.py @@ -37,15 +37,13 @@ def get_email_name_like_students(pattern: str, limit: int = 5, requirement=~Q(pk 'medical_group__name', 'gender' )[:limit] - return list(query) def get_email_name_like_students_filtered_by_group(pattern: str, limit: int = 5, group=None): group = Group.objects.get(id=group) - - medical_group_condition = Q(pk=None) - for medical_group in group.allowed_medical_groups.all(): - medical_group_condition = medical_group_condition | Q(medical_group__id=medical_group.id) - - return get_email_name_like_students(pattern, limit, medical_group_condition) + # Don't suggest the student that is in 'Banned students' list + not_banned_condition = ~Q(user_id__in=group.banned_students.values_list('user_id', flat=True)) + # The student must either be in 'Allowed students' list, or have acceptable medical group + allowed_condition = Q(user_id__in=group.allowed_students.values_list('user_id', flat=True)) | Q(medical_group__in=group.allowed_medical_groups.all()) + return get_email_name_like_students(pattern, limit, not_banned_condition & allowed_condition) diff --git a/adminpage/sport/admin/groupAdmin.py b/adminpage/sport/admin/groupAdmin.py index 287e3b7a..88fc8e5d 100644 --- a/adminpage/sport/admin/groupAdmin.py +++ b/adminpage/sport/admin/groupAdmin.py @@ -92,6 +92,7 @@ class GroupAdmin(DefaultFilterMixIn): # "trainer", 'trainers', 'allowed_students', + 'banned_students', ) list_filter = ( @@ -130,6 +131,7 @@ class GroupAdmin(DefaultFilterMixIn): "allowed_medical_groups", "allowed_gender", "allowed_students", + "banned_students", ) def get_changeform_initial_data(self, request): diff --git a/adminpage/sport/migrations/0126_group_banned_students_alter_group_allowed_students.py b/adminpage/sport/migrations/0126_group_banned_students_alter_group_allowed_students.py new file mode 100644 index 00000000..70152d9e --- /dev/null +++ b/adminpage/sport/migrations/0126_group_banned_students_alter_group_allowed_students.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.3 on 2025-01-24 16:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sport', '0125_alter_group_allowed_students'), + ] + + operations = [ + migrations.AddField( + model_name='group', + name='banned_students', + field=models.ManyToManyField(blank=True, help_text='List of students that can not attent classes', related_name='banned_groups', to='sport.student'), + ), + migrations.AlterField( + model_name='group', + name='allowed_students', + field=models.ManyToManyField(blank=True, help_text='List of students that are allowed to attend classes', related_name='allowed_groups', to='sport.student'), + ), + ] diff --git a/adminpage/sport/models/group.py b/adminpage/sport/models/group.py index a410bc64..49dc52a5 100644 --- a/adminpage/sport/models/group.py +++ b/adminpage/sport/models/group.py @@ -16,17 +16,33 @@ class Group(models.Model): accredited = models.BooleanField(default=True, null=False) # minimum_medical_group = models.ForeignKey('MedicalGroup', on_delete=models.DO_NOTHING, null=True, blank=True) - allowed_medical_groups = models.ManyToManyField('MedicalGroup', blank=True) + allowed_medical_groups = models.ManyToManyField( + 'MedicalGroup', + blank=True, + help_text='Select medical groups required to attend the trainings. If this is empty, nobody will see the training (except students in "Allowed students" list, they always see the training).' + ) allowed_gender = models.IntegerField( choices=GenderInFTGrading.choices, default=GenderInFTGrading.BOTH, + help_text='Select genders that are allowed to attend the trainings (works with "Allowed medical groups" filter).' ) allowed_qr = models.IntegerField( choices=GroupQR.choices, default=-1, verbose_name="Is a QR required?" ) - allowed_students = models.ManyToManyField('Student', related_name='allowed_groups', blank=True) + allowed_students = models.ManyToManyField( + 'Student', + related_name='allowed_groups', + blank=True, + help_text='List of students that are allowed to attend classes (in addition to "Allowed medical groups" filter).' + ) + banned_students = models.ManyToManyField( + 'Student', + related_name='banned_groups', + blank=True, + help_text='List of students that can not attend classes. The students will not see the training, and the teacher will not be able to set hours for these students.' + ) class Meta: db_table = "group"