Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Banned students #341

Merged
merged 5 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy_production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 !</dev/tcp/db/5432; do sleep 1; done;"
docker compose -f docker-compose.prod.yaml exec -T adminpanel bash -c './manage.py collectstatic --noinput && ./manage.py migrate && ./manage.py createcachetable'
docker compose -f docker-compose.prod.yaml exec -T adminpanel bash -c 'python3 manage.py collectstatic --noinput && python3 manage.py migrate && python3 manage.py createcachetable'
docker compose -f docker-compose.prod.yaml build nginx
docker compose -f docker-compose.prod.yaml restart nginx
python3 ${{ secrets.BACKUP_SCRIPT }}
2 changes: 1 addition & 1 deletion .github/workflows/deploy_staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ 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 !</dev/tcp/db/5432; do sleep 1; done;"
docker compose -f docker-compose.prod.yaml exec -T adminpanel bash -c './manage.py collectstatic --noinput && ./manage.py migrate && ./manage.py createcachetable'
docker compose -f docker-compose.prod.yaml exec -T adminpanel bash -c 'python3 manage.py collectstatic --noinput && python3 manage.py migrate && python3 manage.py createcachetable'
docker compose -f docker-compose.prod.yaml build nginx
docker compose -f docker-compose.prod.yaml restart nginx
46 changes: 36 additions & 10 deletions adminpage/api/crud/crud_training.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,27 @@ def can_check_in(
free_places = training.group.capacity - training.checkins.count()
allowed_medical_groups = training.group.allowed_medical_groups.all()
allowed_students = training.group.allowed_students.all()
banned_students = training.group.banned_students.all()

# All conditions must be True for the student to be able to check in.
result = (
# The training must have free places left
free_places > 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
Expand All @@ -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
Expand Down
12 changes: 5 additions & 7 deletions adminpage/api/crud/crud_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 2 additions & 0 deletions adminpage/sport/admin/groupAdmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class GroupAdmin(DefaultFilterMixIn):
# "trainer",
'trainers',
'allowed_students',
'banned_students',
)

list_filter = (
Expand Down Expand Up @@ -130,6 +131,7 @@ class GroupAdmin(DefaultFilterMixIn):
"allowed_medical_groups",
"allowed_gender",
"allowed_students",
"banned_students",
)

def get_changeform_initial_data(self, request):
Expand Down
Original file line number Diff line number Diff line change
@@ -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'),
),
]
20 changes: 18 additions & 2 deletions adminpage/sport/models/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading