Skip to content

Commit 70d5d0f

Browse files
committed
fix: issues #384(reuse sugggest schema)) and #386(align medical)
1 parent 9ec9678 commit 70d5d0f

File tree

5 files changed

+64
-157
lines changed

5 files changed

+64
-157
lines changed

adminpage/api_v2/crud/crud_groups.py

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -107,103 +107,6 @@ def get_free_places_for_sport(sport_id: int) -> int:
107107
return total_free
108108

109109

110-
def get_clubs_as_trainings(student: Optional[Student] = None):
111-
"""
112-
Retrieve all upcoming club trainings in the same format as weekly schedule
113-
@param student - if student passed, filter applicable trainings
114-
@return list of training objects with participants info
115-
"""
116-
from sport.models import TrainingCheckIn
117-
from django.db.models import Prefetch
118-
119-
# Get current time to filter out past trainings
120-
current_time = timezone.now()
121-
122-
# Get groups for current semester that are clubs
123-
groups_query = Group.objects.filter(
124-
semester__pk=get_current_semester_crud().pk,
125-
is_club=True # Only clubs
126-
)
127-
128-
if student:
129-
groups_query = groups_query.filter(
130-
Q(allowed_medical_groups=student.medical_group)
131-
| Q(allowed_students=student.pk)
132-
).exclude(banned_students=student.pk)
133-
134-
# Get upcoming trainings for these club groups
135-
group_prefetch = Prefetch("group", queryset=Group.objects.select_related("sport").prefetch_related(
136-
"allowed_medical_groups"))
137-
138-
trainings = Training.objects.filter(
139-
group__in=groups_query,
140-
start__gt=current_time, # Only future trainings
141-
group__sport__isnull=False # Exclude special groups
142-
).select_related(
143-
'group', 'group__sport', 'training_class'
144-
).prefetch_related(
145-
group_prefetch, "checkins"
146-
).order_by('start')
147-
148-
# Get student check-ins if student provided
149-
student_checkins_map = {}
150-
if student:
151-
student_checkins = TrainingCheckIn.objects.filter(
152-
student=student,
153-
training__in=trainings
154-
).select_related("training")
155-
student_checkins_map = {checkin.training_id: checkin for checkin in student_checkins}
156-
157-
trainings_data = []
158-
159-
for training in trainings:
160-
# Calculate can_check_in
161-
can_check_in_result = False
162-
checked_in = False
163-
164-
if student:
165-
can_check_in_result = can_check_in(student, training)
166-
checked_in = training.id in student_checkins_map
167-
168-
# Calculate participants info (similar to weekly schedule)
169-
from api_v2.crud.crud_training import get_students_grades
170-
participants = get_students_grades(training.id)
171-
172-
participants_info = {
173-
'total_checked_in': len([p for p in participants if p.get('hours', 0) >= 0]),
174-
'students': [
175-
{
176-
'id': p['student_id'],
177-
'name': p['full_name'],
178-
'email': p['email'],
179-
'medical_group': p['med_group'],
180-
'hours': p.get('hours', 0),
181-
'attended': p.get('hours', 0) > 0
182-
}
183-
for p in participants
184-
]
185-
}
186-
187-
training_dict = {
188-
"id": training.id,
189-
"start": training.start,
190-
"end": training.end,
191-
"group_id": training.group.id,
192-
"group_name": training.group.to_frontend_name(),
193-
"training_class": training.training_class.name if training.training_class else None,
194-
"group_accredited": training.group.accredited,
195-
"can_grade": False, # Students can't grade
196-
"can_check_in": can_check_in_result,
197-
"checked_in": checked_in,
198-
"capacity": training.group.capacity,
199-
"available_spots": training.group.capacity - participants_info['total_checked_in'],
200-
"participants": participants_info
201-
}
202-
trainings_data.append(training_dict)
203-
204-
return trainings_data
205-
206-
207110
def get_sports_with_groups(student: Optional[Student] = None):
208111
"""
209112
Retrieves all sports with their groups and detailed information

adminpage/api_v2/crud/crud_training.py

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -262,37 +262,43 @@ def get_students_grades(training_id: int):
262262
@return list of student grades
263263
"""
264264
with connection.cursor() as cursor:
265-
cursor.execute('SELECT '
266-
'd.id AS student_id, '
267-
'd.first_name AS first_name, '
268-
'd.last_name AS last_name, '
269-
'd.email AS email, '
270-
'a.hours AS hours, '
271-
'm.name AS med_group, '
272-
'concat(d.first_name, \' \', d.last_name) as full_name '
273-
'FROM training t, attendance a, auth_user d, student s '
274-
'LEFT JOIN medical_group m ON m.id = s.medical_group_id '
275-
'WHERE s.user_id = a.student_id '
276-
'AND d.id = a.student_id '
277-
'AND a.training_id = %(training_id)s '
278-
'AND t.id = %(training_id)s '
279-
'UNION DISTINCT '
280-
'SELECT '
281-
'd.id AS student_id, '
282-
'd.first_name AS first_name, '
283-
'd.last_name AS last_name, '
284-
'd.email AS email, '
285-
'COALESCE(a.hours, 0) AS hours, '
286-
'm.name AS med_group, '
287-
'concat(d.first_name, \' \', d.last_name) as full_name '
288-
'FROM training t, sport_trainingcheckin e, auth_user d, student s '
289-
'LEFT JOIN attendance a ON a.student_id = s.user_id AND a.training_id = %(training_id)s '
290-
'LEFT JOIN medical_group m ON m.id = s.medical_group_id '
291-
'WHERE s.user_id = e.student_id '
292-
'AND d.id = e.student_id '
293-
'AND t.id = %(training_id)s '
294-
'AND t.id = e.training_id ', {"training_id": training_id})
295-
return dictfetchall(cursor)
265+
cursor.execute("""
266+
SELECT
267+
u.id AS student_id,
268+
u.first_name,
269+
u.last_name,
270+
u.email,
271+
COALESCE(a.hours, 0) AS hours,
272+
m.id AS med_group_id,
273+
m.name AS med_group_name,
274+
m.description AS med_group_description
275+
FROM auth_user u
276+
JOIN student s ON s.user_id = u.id
277+
LEFT JOIN medical_group m ON m.id = s.medical_group_id
278+
LEFT JOIN attendance a
279+
ON a.student_id = u.id AND a.training_id = %(training_id)s
280+
WHERE u.id IN (
281+
SELECT student_id FROM attendance WHERE training_id = %(training_id)s
282+
UNION
283+
SELECT student_id FROM sport_trainingcheckin WHERE training_id = %(training_id)s
284+
)
285+
""", {"training_id": training_id})
286+
287+
rows = dictfetchall(cursor)
288+
289+
for row in rows:
290+
row["id"] = row.pop("student_id")
291+
if row.get("med_group_id") is not None:
292+
row["medical_group"] = {
293+
"id": row.pop("med_group_id"),
294+
"name": row.pop("med_group_name"),
295+
"description": row.pop("med_group_description"),
296+
}
297+
else:
298+
row["medical_group"] = None
299+
300+
return rows
301+
296302

297303

298304
def get_student_last_attended_dates(group_id: int):

adminpage/api_v2/serializers/reference.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ class Meta:
1414
fields = ['image', 'start', 'end', 'student_comment']
1515

1616

17-
class ReferenceUploadResponseSerializer(serializers.Serializer):
18-
message = serializers.CharField()
19-
reference_id = serializers.IntegerField()
20-
hours = serializers.FloatField()
21-
start = serializers.DateField()
22-
end = serializers.DateField()
23-
uploaded = serializers.DateTimeField()
17+
class ReferenceUploadResponseSerializer(serializers.ModelSerializer[Reference]):
18+
message = serializers.CharField(default="Medical certificate uploaded successfully")
19+
20+
class Meta:
21+
model = Reference
22+
fields = ("id", "student_id", "semester", "hours", "start", "end", "uploaded", "message")
23+
2424

2525

2626
class MedicalGroupReferenceUploadSerializer(serializers.Serializer):
@@ -41,7 +41,9 @@ class MedicalGroupReferenceUploadSerializer(serializers.Serializer):
4141
max_length=1000,
4242
)
4343

44-
class MedicalGroupReferenceUploadResponseSerializer(serializers.ModelSerializer[MedicalGroupReference]):
44+
class MedicalGroupReferenceUploadResponseSerializer(serializers.ModelSerializer):
4545
class Meta:
4646
model = MedicalGroupReference
47-
fields = ('id', 'student', 'uploaded', 'semester')
47+
fields = ('id', 'student_id', 'semester', 'uploaded')
48+
49+

adminpage/api_v2/views/reference.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class ReferenceErrors:
3131
methods=["POST"],
3232
tags=["For student"],
3333
summary="Upload medical certificate",
34-
description="Upload a medical certificate for sick leave. The system automatically calculates hours based on the duration of illness. Only one upload per day is allowed.",
34+
description="Upload a medical certificate for sick leave. Only one upload per day is allowed. Hours are calculated automatically.",
3535
request=ReferenceUploadSerializer,
3636
responses={
3737
status.HTTP_200_OK: ReferenceUploadResponseSerializer,
@@ -45,17 +45,19 @@ def reference_upload(request, **kwargs):
4545
serializer = ReferenceUploadSerializer(data=request.data)
4646
serializer.is_valid(raise_exception=True)
4747

48-
image = serializer.validated_data['image']
49-
5048
student = request.user # user.pk == user.student.pk
5149

5250
try:
5351
with transaction.atomic():
5452
ref = serializer.save(
5553
semester=get_current_semester_crud(),
5654
student_id=student.pk,
57-
hours=(serializer.validated_data['end'] - serializer.validated_data['start']).days // 7 * get_current_semester_crud().number_hours_one_week_ill
55+
hours=(
56+
(serializer.validated_data['end'] - serializer.validated_data['start']).days // 7
57+
* get_current_semester_crud().number_hours_one_week_ill
58+
)
5859
)
60+
5961
count = Reference.objects.filter(
6062
student_id=student.pk,
6163
uploaded__date=make_naive(ref.uploaded).date()
@@ -66,22 +68,16 @@ def reference_upload(request, **kwargs):
6668
status=status.HTTP_400_BAD_REQUEST,
6769
data=error_detail(*ReferenceErrors.TOO_MUCH_UPLOADS_PER_DAY)
6870
)
69-
70-
# Return informative response
71-
return Response({
72-
"message": "Medical certificate uploaded successfully",
73-
"reference_id": ref.id,
74-
"hours": ref.hours,
75-
"start": ref.start,
76-
"end": ref.end,
77-
"uploaded": ref.uploaded
78-
})
71+
72+
response_serializer = ReferenceUploadResponseSerializer(ref)
73+
return Response(response_serializer.data, status=status.HTTP_200_OK)
74+
7975

8076
@extend_schema(
8177
methods=["POST"],
8278
tags=["For student"],
8379
summary="Upload medical certificate",
84-
description="Upload a medical certificate for sick leave. The system automatically calculates hours based on the duration of illness. Only one upload per day is allowed.",
80+
description="Upload a medical certificate for sick leave. Only one upload per day is allowed.",
8581
request=MedicalGroupReferenceUploadSerializer,
8682
responses={
8783
status.HTTP_200_OK: MedicalGroupReferenceUploadResponseSerializer,
@@ -101,19 +97,18 @@ def medical_group_upload(request, **kwargs):
10197

10298
try:
10399
with transaction.atomic():
104-
105100
reference = MedicalGroupReference.objects.create(
106101
student_id=student.pk,
107102
semester=get_current_semester_crud(),
108103
student_comment=student_comment,
109104
)
110-
105+
111106
reference_images = [
112107
MedicalGroupReferenceImage(reference=reference, image=image)
113108
for image in images
114109
]
115110
MedicalGroupReferenceImage.objects.bulk_create(reference_images)
116-
111+
117112
except Exception as e:
118113
return Response(
119114
{
@@ -122,4 +117,5 @@ def medical_group_upload(request, **kwargs):
122117
status=status.HTTP_400_BAD_REQUEST
123118
)
124119

125-
return Response(MedicalGroupReferenceUploadResponseSerializer(reference).data)
120+
response_serializer = MedicalGroupReferenceUploadResponseSerializer(reference)
121+
return Response(response_serializer.data, status=status.HTTP_200_OK)

adminpage/api_v2/views/self_sport_report.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def get_selfsport_reports_for_student(request, student_id: int, **kwargs):
302302
),
303303
post=extend_schema(
304304
operation_id="v2_selfsport_reports_create",
305-
tags=["Self Sport"],
305+
tags=["For student"],
306306
summary="Upload self sport report",
307307
description="""
308308
Creates a new self-sport report.

0 commit comments

Comments
 (0)