diff --git a/core/serializers.py b/core/serializers.py index 5b3da5d..b17951f 100644 --- a/core/serializers.py +++ b/core/serializers.py @@ -230,6 +230,21 @@ class Meta: fields = ['id', 'user', 'school', 'user_name', 'user_role', 'school_name', 'joined_at', 'is_active'] read_only_fields = ['id', 'joined_at'] +class SchoolAddTeacherSerializer(serializers.Serializer): + """Serializer for adding a teacher to school""" + teacher_email = serializers.EmailField(required=True) + teacher_role = serializers.ChoiceField(choices=TeacherProfile.TEACHER_ROLES, default="subject_teacher") + assigned_classes = serializers.ListField( + child=serializers.ChoiceField(choices=Class.ClassName.choices), + allow_empty=True, + required=False + ) + +class SchoolAddStudentSerializer(serializers.Serializer): + """Serializer for adding a student to school""" + student_email = serializers.EmailField(required=True) + assigned_class = serializers.ChoiceField(choices=Class.ClassName.choices) + # ============================================================================= # SUBJECT & CLASS SERIALIZERS diff --git a/core/urls.py b/core/urls.py index 94fe6c4..5750a5c 100644 --- a/core/urls.py +++ b/core/urls.py @@ -88,6 +88,8 @@ path('schools//members/', views.get_school_members, name='school-members'), path('schools//projects/', views.get_school_projects, name='school-projects'), path('schools//add-user/', views.add_user_to_school, name='add-user-to-school'), + path('schools//add-teacher-school/', views.add_teacher_to_school, name='add-teacher-to-school'), + path('schools//add-student-school/', views.add_student_to_school, name='add-student-to-school'), path('classes//add-student/', views.add_student_to_class, name='add-student-to-class'), # ================================================================= diff --git a/core/views.py b/core/views.py index 43d2f86..43b85a1 100644 --- a/core/views.py +++ b/core/views.py @@ -12,6 +12,7 @@ from django.views.decorators.http import require_GET from django.core.mail import send_mail import random +import logging from rest_framework import viewsets, status, permissions, filters from rest_framework.decorators import action, api_view, permission_classes @@ -32,7 +33,7 @@ ProjectParticipant ) from .serializers import ( - UserRegistrationSerializer, UserSerializer, UserUpdateSerializer, + SchoolAddStudentSerializer, SchoolAddTeacherSerializer, UserRegistrationSerializer, UserSerializer, UserUpdateSerializer, PasswordChangeSerializer, SchoolSerializer, SchoolCreateSerializer, SchoolMembershipSerializer, SubjectSerializer, ClassSerializer, TeacherProfileSerializer, StudentProfileSerializer, ProjectSerializer, @@ -1123,6 +1124,168 @@ def add_user_to_school(request, school_id): status=status.HTTP_500_INTERNAL_SERVER_ERROR) +@api_view(['POST']) +@permission_classes([CanManageSchoolMembers]) +def add_student_to_school(request, school_id): + """ + Add a student to a school. + Only school admins and teachers can add students to their schools. + """ + try: + school = get_object_or_404(School, id=school_id) + + # Check permission + if (school.admin != request.user or request.user.role != "teacher") and not request.user.is_staff: + return Response({'error': 'Only school admins and teachers can add students to schools'}, + status=status.HTTP_403_FORBIDDEN) + + serializer_class = SchoolAddStudentSerializer(data=request.data) + if not serializer_class.is_valid(): + return Response(serializer_class.errors, status=status.HTTP_400_BAD_REQUEST) + + data = serializer_class.validated_data + student_email = data['student_email'] + assigned_class_name = data['assigned_class'] + + try: + user = User.objects.get(email=student_email) + if user.role and user.role != "student": + return Response({'error': 'User already has a role other than student.'}, + status=status.HTTP_400_BAD_REQUEST) + elif not user.role: + user.role = "student" + user.save() + except User.DoesNotExist: + return Response({'error': 'User not found'}, + status=status.HTTP_404_NOT_FOUND) + + # Create school membership + membership, created = SchoolMembership.objects.get_or_create( + user=user, + school=school, + defaults={'is_active': True} + ) + + if not created and membership.is_active: + return Response({'error': 'User is already a member of this school'}, + status=status.HTTP_400_BAD_REQUEST) + elif not created: + membership.is_active = True + membership.save() + + try: + assigned_class = Class.objects.get(name=assigned_class_name, school=school) + except Class.DoesNotExist: + return Response({'error': 'Class not found in this school'}, status=status.HTTP_400_BAD_REQUEST) + + # Create appropriate profile + StudentProfile.objects.get_or_create( + user=user, + school=school, + defaults={ + 'student_id': f"{school.name[:3].upper()}{user.id}", + 'current_class': assigned_class + } + ) + + return Response({ + 'message': f'Successfully added {user.get_full_name()} as {user.role} to {school.name}', + 'user_id': str(user.id), + 'user_name': user.get_full_name(), + 'user_role': user.role, + 'school_name': school.name + }, status=status.HTTP_200_OK) + + except Exception as e: + logger.error(f"Error adding student to school: {str(e)}") + return Response({'error': f'Failed to add student to school: {str(e)}'}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +@api_view(['POST']) +@permission_classes([CanManageSchoolMembers]) +def add_teacher_to_school(request, school_id): + """ + Add a teacher to a school. + Only school admins can add teachers to their schools. + """ + try: + school = get_object_or_404(School, id=school_id) + + # Check permission + if school.admin != request.user and not request.user.is_staff: + return Response({'error': 'Only school admins can add teachers to schools'}, + status=status.HTTP_403_FORBIDDEN) + + serializer_class = SchoolAddTeacherSerializer(data=request.data) + if not serializer_class.is_valid(): + return Response(serializer_class.errors, status=status.HTTP_400_BAD_REQUEST) + + data = serializer_class.validated_data + teacher_email = data['teacher_email'] + teacher_role = data['teacher_role'] + assigned_class_names = data.get('assigned_classes') + + try: + user = User.objects.get(email=teacher_email) + if user.role and user.role != "teacher": + return Response({'error': 'User already has a role other than teacher.'}, + status=status.HTTP_400_BAD_REQUEST) + elif not user.role: + user.role = "teacher" + user.save() + except User.DoesNotExist: + return Response({'error': 'User not found'}, + status=status.HTTP_404_NOT_FOUND) + + # Create school membership + membership, created = SchoolMembership.objects.get_or_create( + user=user, + school=school, + defaults={'is_active': True} + ) + + if not created and membership.is_active: + return Response({'error': 'User is already a member of this school'}, + status=status.HTTP_400_BAD_REQUEST) + elif not created: + membership.is_active = True + membership.save() + + # Create appropriate profile + teacher_profile, _ = TeacherProfile.objects.get_or_create( + user=user, + school=school, + defaults={'teacher_role': teacher_role, 'status': 'active'} + ) + # Query classes matching names and this school + valid_class_names = Class.objects.filter(name__in=assigned_class_names, school=school).values_list('name', flat=True) + + # Find invalid class names + invalid_classes = set(assigned_class_names) - set(valid_class_names) + if invalid_classes: + return Response( + {'error': f'The following classes are invalid or do not belong to the school: {", ".join(invalid_classes)}'}, + status=status.HTTP_400_BAD_REQUEST + ) + + valid_classes_qs = Class.objects.filter(name__in=assigned_class_names, school=school) + teacher_profile.assigned_classes.set(valid_classes_qs) + + return Response({ + 'message': f'Successfully added {user.get_full_name()} as {user.role} to {school.name}', + 'user_id': str(user.id), + 'user_name': user.get_full_name(), + 'user_role': teacher_role, + 'school_name': school.name + }, status=status.HTTP_200_OK) + + except Exception as e: + logger.error(f"Error adding teacher to school: {str(e)}") + return Response({'error': f'Failed to add teacher to school: {str(e)}'}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + @api_view(['POST']) @permission_classes([CanManageSchoolContent]) def add_student_to_class(request, class_id):