Skip to content

Commit 0b948e0

Browse files
committed
implement add teacher and student to school
1 parent 34e4c29 commit 0b948e0

File tree

4 files changed

+197
-1
lines changed

4 files changed

+197
-1
lines changed

core/serializers.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,21 @@ class Meta:
230230
fields = ['id', 'user', 'school', 'user_name', 'user_role', 'school_name', 'joined_at', 'is_active']
231231
read_only_fields = ['id', 'joined_at']
232232

233+
class SchoolAddTeacherSerializer(serializers.Serializer):
234+
"""Serializer for adding a teacher to school"""
235+
teacher_email = serializers.EmailField(required=True)
236+
teacher_role = serializers.ChoiceField(choices=TeacherProfile.TEACHER_ROLES, default="subject_teacher")
237+
assigned_classes = serializers.ListField(
238+
child=serializers.ChoiceField(choices=Class.ClassName.choices),
239+
allow_empty=True,
240+
required=False
241+
)
242+
243+
class SchoolAddStudentSerializer(serializers.Serializer):
244+
"""Serializer for adding a student to school"""
245+
student_email = serializers.EmailField(required=True)
246+
assigned_class = serializers.ChoiceField(choices=Class.ClassName.choices)
247+
233248

234249
# =============================================================================
235250
# SUBJECT & CLASS SERIALIZERS

core/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@
9393
path('schools/<uuid:pk>/members/', views.get_school_members, name='school-members'),
9494
path('schools/<uuid:pk>/projects/', views.get_school_projects, name='school-projects'),
9595
path('schools/<uuid:school_id>/add-user/', views.add_user_to_school, name='add-user-to-school'),
96+
path('schools/<uuid:school_id>/add-teacher-school/', views.add_teacher_to_school, name='add-teacher-to-school'),
97+
path('schools/<uuid:school_id>/add-student-school/', views.add_student_to_school, name='add-student-to-school'),
9698
path('classes/<uuid:class_id>/add-student/', views.add_student_to_class, name='add-student-to-class'),
9799

98100
# =================================================================

core/views.py

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from django.views.decorators.http import require_GET
1212
from django.core.mail import send_mail
1313
import random
14+
import logging
1415

1516
from rest_framework import viewsets, status, permissions, filters
1617
from rest_framework.decorators import action, api_view, permission_classes
@@ -31,7 +32,7 @@
3132
ProjectParticipant
3233
)
3334
from .serializers import (
34-
UserRegistrationSerializer, UserSerializer, UserUpdateSerializer,
35+
SchoolAddStudentSerializer, SchoolAddTeacherSerializer, UserRegistrationSerializer, UserSerializer, UserUpdateSerializer,
3536
PasswordChangeSerializer, SchoolSerializer, SchoolCreateSerializer,
3637
SchoolMembershipSerializer, SubjectSerializer, ClassSerializer,
3738
TeacherProfileSerializer, StudentProfileSerializer, ProjectSerializer,
@@ -53,6 +54,8 @@
5354
from rest_framework import serializers
5455
from rest_framework.exceptions import PermissionDenied
5556

57+
logger = logging.getLogger("__name__")
58+
5659

5760
# =============================================================================
5861
# AUTHENTICATION & LOGIN VIEWS (Grouped at the top for clarity)
@@ -1110,6 +1113,168 @@ def add_user_to_school(request, school_id):
11101113
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
11111114

11121115

1116+
@api_view(['POST'])
1117+
@permission_classes([CanManageSchoolMembers])
1118+
def add_student_to_school(request, school_id):
1119+
"""
1120+
Add a student to a school.
1121+
Only school admins and teachers can add students to their schools.
1122+
"""
1123+
try:
1124+
school = get_object_or_404(School, id=school_id)
1125+
1126+
# Check permission
1127+
if (school.admin != request.user or request.user.role != "teacher") and not request.user.is_staff:
1128+
return Response({'error': 'Only school admins and teachers can add students to schools'},
1129+
status=status.HTTP_403_FORBIDDEN)
1130+
1131+
serializer_class = SchoolAddStudentSerializer(data=request.data)
1132+
if not serializer_class.is_valid():
1133+
return Response(serializer_class.errors, status=status.HTTP_400_BAD_REQUEST)
1134+
1135+
data = serializer_class.validated_data
1136+
student_email = data['student_email']
1137+
assigned_class_name = data['assigned_class']
1138+
1139+
try:
1140+
user = User.objects.get(email=student_email)
1141+
if user.role and user.role != "student":
1142+
return Response({'error': 'User already has a role other than student.'},
1143+
status=status.HTTP_400_BAD_REQUEST)
1144+
elif not user.role:
1145+
user.role = "student"
1146+
user.save()
1147+
except User.DoesNotExist:
1148+
return Response({'error': 'User not found'},
1149+
status=status.HTTP_404_NOT_FOUND)
1150+
1151+
# Create school membership
1152+
membership, created = SchoolMembership.objects.get_or_create(
1153+
user=user,
1154+
school=school,
1155+
defaults={'is_active': True}
1156+
)
1157+
1158+
if not created and membership.is_active:
1159+
return Response({'error': 'User is already a member of this school'},
1160+
status=status.HTTP_400_BAD_REQUEST)
1161+
elif not created:
1162+
membership.is_active = True
1163+
membership.save()
1164+
1165+
try:
1166+
assigned_class = Class.objects.get(name=assigned_class_name, school=school)
1167+
except Class.DoesNotExist:
1168+
return Response({'error': 'Class not found in this school'}, status=status.HTTP_400_BAD_REQUEST)
1169+
1170+
# Create appropriate profile
1171+
StudentProfile.objects.get_or_create(
1172+
user=user,
1173+
school=school,
1174+
defaults={
1175+
'student_id': f"{school.name[:3].upper()}{user.id}",
1176+
'current_class': assigned_class
1177+
}
1178+
)
1179+
1180+
return Response({
1181+
'message': f'Successfully added {user.get_full_name()} as {user.role} to {school.name}',
1182+
'user_id': str(user.id),
1183+
'user_name': user.get_full_name(),
1184+
'user_role': user.role,
1185+
'school_name': school.name
1186+
}, status=status.HTTP_200_OK)
1187+
1188+
except Exception as e:
1189+
logger.error(f"Error adding student to school: {str(e)}")
1190+
return Response({'error': f'Failed to add student to school: {str(e)}'},
1191+
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
1192+
1193+
1194+
@api_view(['POST'])
1195+
@permission_classes([CanManageSchoolMembers])
1196+
def add_teacher_to_school(request, school_id):
1197+
"""
1198+
Add a teacher to a school.
1199+
Only school admins can add teachers to their schools.
1200+
"""
1201+
try:
1202+
school = get_object_or_404(School, id=school_id)
1203+
1204+
# Check permission
1205+
if school.admin != request.user and not request.user.is_staff:
1206+
return Response({'error': 'Only school admins can add teachers to schools'},
1207+
status=status.HTTP_403_FORBIDDEN)
1208+
1209+
serializer_class = SchoolAddTeacherSerializer(data=request.data)
1210+
if not serializer_class.is_valid():
1211+
return Response(serializer_class.errors, status=status.HTTP_400_BAD_REQUEST)
1212+
1213+
data = serializer_class.validated_data
1214+
teacher_email = data['teacher_email']
1215+
teacher_role = data['teacher_role']
1216+
assigned_class_names = data.get('assigned_classes')
1217+
1218+
try:
1219+
user = User.objects.get(email=teacher_email)
1220+
if user.role and user.role != "teacher":
1221+
return Response({'error': 'User already has a role other than teacher.'},
1222+
status=status.HTTP_400_BAD_REQUEST)
1223+
elif not user.role:
1224+
user.role = "teacher"
1225+
user.save()
1226+
except User.DoesNotExist:
1227+
return Response({'error': 'User not found'},
1228+
status=status.HTTP_404_NOT_FOUND)
1229+
1230+
# Create school membership
1231+
membership, created = SchoolMembership.objects.get_or_create(
1232+
user=user,
1233+
school=school,
1234+
defaults={'is_active': True}
1235+
)
1236+
1237+
if not created and membership.is_active:
1238+
return Response({'error': 'User is already a member of this school'},
1239+
status=status.HTTP_400_BAD_REQUEST)
1240+
elif not created:
1241+
membership.is_active = True
1242+
membership.save()
1243+
1244+
# Create appropriate profile
1245+
teacher_profile, _ = TeacherProfile.objects.get_or_create(
1246+
user=user,
1247+
school=school,
1248+
defaults={'teacher_role': teacher_role, 'status': 'active'}
1249+
)
1250+
# Query classes matching names and this school
1251+
valid_class_names = Class.objects.filter(name__in=assigned_class_names, school=school).values_list('name', flat=True)
1252+
1253+
# Find invalid class names
1254+
invalid_classes = set(assigned_class_names) - set(valid_class_names)
1255+
if invalid_classes:
1256+
return Response(
1257+
{'error': f'The following classes are invalid or do not belong to the school: {", ".join(invalid_classes)}'},
1258+
status=status.HTTP_400_BAD_REQUEST
1259+
)
1260+
1261+
valid_classes_qs = Class.objects.filter(name__in=assigned_class_names, school=school)
1262+
teacher_profile.assigned_classes.set(valid_classes_qs)
1263+
1264+
return Response({
1265+
'message': f'Successfully added {user.get_full_name()} as {user.role} to {school.name}',
1266+
'user_id': str(user.id),
1267+
'user_name': user.get_full_name(),
1268+
'user_role': teacher_role,
1269+
'school_name': school.name
1270+
}, status=status.HTTP_200_OK)
1271+
1272+
except Exception as e:
1273+
logger.error(f"Error adding teacher to school: {str(e)}")
1274+
return Response({'error': f'Failed to add teacher to school: {str(e)}'},
1275+
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
1276+
1277+
11131278
@api_view(['POST'])
11141279
@permission_classes([CanManageSchoolContent])
11151280
def add_student_to_class(request, class_id):

docker-compose.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
services:
2+
db:
3+
image: postgres:14-alpine
4+
environment:
5+
POSTGRES_USER: global_classrooms_user
6+
POSTGRES_PASSWORD: GlobalClass2025
7+
POSTGRES_DB: global_classrooms
8+
ports:
9+
- "5432:5432"
10+
volumes:
11+
- postgres_data:/var/lib/postgresql/data/
12+
13+
volumes:
14+
postgres_data:

0 commit comments

Comments
 (0)