class SchoolPrincipalSerializer(BaseUserProfileDetailSerializer): labels = PrimaryKeyRelatedField(queryset=Label.objects.all(), many=True) school_unit = serializers.SerializerMethodField() class Meta(UserProfileBaseSerializer.Meta): extra_fields = ('labels', 'school_unit') fields = BaseUserProfileDetailSerializer.Meta.fields + extra_fields def validate(self, attrs): labels = attrs.get('labels') user_role = attrs['user_role'] self.validate_labels_set(labels, user_role) return attrs @staticmethod def get_school_unit(obj): if obj.school_unit is None: return from edualert.schools.serializers import RegisteredSchoolUnitBaseSerializer return RegisteredSchoolUnitBaseSerializer(obj.school_unit).data def to_representation(self, instance): representation = super().to_representation(instance) representation['labels'] = LabelSerializer(instance=instance.labels, many=True).data return representation
class ParentSerializer(BaseUserProfileDetailSerializer): labels = PrimaryKeyRelatedField(queryset=Label.objects.all(), many=True) class Meta(UserProfileBaseSerializer.Meta): extra_fields = ('labels', 'address') fields = BaseUserProfileDetailSerializer.Meta.fields + extra_fields def validate(self, attrs): labels = attrs.get('labels') user_role = attrs['user_role'] self.validate_labels_set(labels, user_role) return attrs def to_representation(self, instance): representation = super().to_representation(instance) representation['labels'] = LabelSerializer(instance=instance.labels, many=True).data return representation
class AcademicProgramCreateSerializer(serializers.ModelSerializer): generic_academic_program = PrimaryKeyRelatedField( queryset=GenericAcademicProgram.objects.all()) optional_subjects = OptionalProgramSubjectThroughSerializer( many=True, allow_null=False) class Meta: model = AcademicProgram fields = ('id', 'name', 'classes_count', 'academic_year', 'generic_academic_program', 'core_subject', 'optional_subjects') read_only_fields = ('id', 'name', 'classes_count', 'academic_year') def validate(self, attrs): generic_academic_program = attrs['generic_academic_program'] school_unit = self.context['school_unit'] if not self.context.get('is_update') and \ AcademicProgram.objects.filter(generic_academic_program=generic_academic_program, school_unit=school_unit, academic_year=self.context['academic_year']).exists(): raise serializers.ValidationError({ 'generic_academic_program': _('You already defined this program this year.') }) academic_profile = generic_academic_program.academic_profile if generic_academic_program.category_id not in school_unit.categories.values_list('id', flat=True) or \ academic_profile != school_unit.academic_profile: raise serializers.ValidationError({ 'generic_academic_program': _('Invalid generic academic program.') }) core_subject = attrs.get('core_subject') if academic_profile and academic_profile.name in PROFILES_WITH_CORE_SUBJECTS: if core_subject is None: raise serializers.ValidationError({ 'core_subject': _('This academic program must have a core subject.') }) if not ProgramSubjectThrough.objects.filter( generic_academic_program=generic_academic_program, subject=core_subject).exists(): raise serializers.ValidationError( {'core_subject': _('Invalid core subject.')}) else: if core_subject is not None: raise serializers.ValidationError({ 'core_subject': _('This academic program does not have a core subject.') }) subjects_class_grades = {} unique_errors = {} hours_errors = {} program_max_optional_weekly_hours = generic_academic_program.optional_subjects_weekly_hours for subject in attrs['optional_subjects']: class_grade = subject['class_grade'] subject_name = subject['subject'] if class_grade in subjects_class_grades: if subject_name in subjects_class_grades[class_grade]: unique_errors[class_grade] = _( 'Subjects must be unique per class grade.') else: subjects_class_grades[class_grade].append(subject_name) else: subjects_class_grades[class_grade] = [subject_name] max_hours = program_max_optional_weekly_hours.get(class_grade, 0) if max_hours == 0: hours_errors[class_grade] = _( "This program does not accept optionals for this class grade." ) elif subject['weekly_hours_count'] > max_hours: hours_errors[class_grade] = _("Each optional's weekly hours should not be greater than {}.") \ .format(program_max_optional_weekly_hours.get(class_grade, 0)) if unique_errors: raise serializers.ValidationError( {'optional_subjects': unique_errors}) if hours_errors: raise serializers.ValidationError( {'optional_subjects': hours_errors}) return attrs def create(self, validated_data): optional_subjects = validated_data.pop('optional_subjects') validated_data.update({ 'academic_year': self.context['academic_year'], 'school_unit': self.context['school_unit'], 'name': validated_data['generic_academic_program'].name }) academic_program = super().create(validated_data) self.create_program_subject_through_set(optional_subjects, academic_program) return academic_program @staticmethod def create_program_subject_through_set(optional_subjects, academic_program): program_subject_through_set = [] for subject_through_data in optional_subjects: subject_name = subject_through_data.pop('subject') subject, created = Subject.objects.get_or_create(name=subject_name) if created: subject.should_be_in_taught_subjects = False subject.save() subject_through_data['subject'] = subject subject_through_data['subject_name'] = subject_name subject_through_data['is_mandatory'] = False subject_through_data['academic_program'] = academic_program subject_through_data['class_grade_arabic'] = CLASS_GRADE_MAPPING[ subject_through_data['class_grade']] program_subject_through_set.append( ProgramSubjectThrough(**subject_through_data)) ProgramSubjectThrough.objects.bulk_create(program_subject_through_set) def to_representation(self, instance): serializer = AcademicProgramDetailSerializer(instance=instance) return serializer.data
class StudyClassPartiallyUpdateSerializer(StudyClassBaseSerializer): updated_teachers = TeacherClassThroughPartiallyUpdateSerializer(many=True, required=False) new_students = PrimaryKeyRelatedField( queryset=UserProfile.objects.filter(user_role=UserProfile.UserRoles.STUDENT, is_active=True), many=True ) deleted_students = PrimaryKeyRelatedField( queryset=UserProfile.objects.filter(user_role=UserProfile.UserRoles.STUDENT, is_active=True), many=True ) class Meta(StudyClassBaseSerializer.Meta): fields = ('class_master', 'updated_teachers', 'new_students', 'deleted_students') def validate(self, attrs): school_unit = self.context['principal_school'] new_class_master = attrs.get('class_master') updated_teachers = attrs.get('updated_teachers', []) new_students = attrs.get('new_students', []) deleted_students = attrs.get('deleted_students', []) old_class_master = self.instance.class_master if new_class_master and new_class_master != old_class_master and \ (new_class_master.user_role != UserProfile.UserRoles.TEACHER or not new_class_master.is_active or new_class_master.school_unit_id != school_unit.id or new_class_master.mastering_study_classes.filter(academic_year=self.instance.academic_year).exists()): raise serializers.ValidationError({'class_master': _('Invalid user.')}) for teacher_class_through in updated_teachers: teacher = teacher_class_through['teacher'] instance_id = teacher_class_through['id'] if teacher.user_role != UserProfile.UserRoles.TEACHER or not teacher.is_active or teacher.school_unit_id != school_unit.id: raise serializers.ValidationError({'updated_teachers': _('At least one teacher is invalid.')}) try: teacher_class_through_instance = TeacherClassThrough.objects.get(id=instance_id, study_class_id=self.instance.id) except ObjectDoesNotExist: raise serializers.ValidationError({'updated_teachers': _(f'Invalid pk "{instance_id}" - object does not exist.')}) subject = teacher_class_through_instance.subject if subject.is_coordination: raise serializers.ValidationError({'updated_teachers': _(f'Invalid pk "{instance_id}" - object does not exist.')}) class_master = new_class_master or old_class_master if not teacher_class_through_instance.is_optional_subject and subject not in teacher.taught_subjects.all() \ and (self.instance.class_grade_arabic in range(5, 14) or (self.instance.class_grade_arabic in range(0, 5) and teacher != class_master)): raise serializers.ValidationError({'updated_teachers': _('Teacher {} does not teach {}.').format(teacher.full_name, subject.name)}) teacher_class_through['instance'] = teacher_class_through_instance for student in new_students: if student.school_unit_id != school_unit.id or student.student_in_class: raise serializers.ValidationError({'new_students': _('At least one student is invalid.')}) for student in deleted_students: if student.student_in_class != self.instance: raise serializers.ValidationError({'deleted_students': _('At least one student is invalid.')}) return attrs def update(self, instance, validated_data): new_class_master = validated_data.get('class_master') updated_teachers = validated_data.get('updated_teachers', []) new_students = validated_data.get('new_students', []) deleted_students = validated_data.get('deleted_students', []) actual_class_master = instance.class_master # Update teachers instances_to_update = [] for teacher_class_through in updated_teachers: teacher_class_through_instance = teacher_class_through['instance'] teacher = teacher_class_through['teacher'] teacher_class_through_instance.teacher = teacher teacher_class_through_instance.is_class_master = teacher == actual_class_master instances_to_update.append(teacher_class_through_instance) # Also update catalogs for all students in this class for this subject StudentCatalogPerSubject.objects.filter(study_class_id=instance.id, subject_id=teacher_class_through_instance.subject_id) \ .update(teacher=teacher) TeacherClassThrough.objects.bulk_update(instances_to_update, ['teacher', 'is_class_master']) # Update class master if new_class_master: TeacherClassThrough.objects.filter(study_class_id=instance.id, teacher=actual_class_master) \ .update(is_class_master=False) instance.class_master = new_class_master instance.save() TeacherClassThrough.objects.filter(study_class_id=instance.id, is_coordination_subject=True) \ .update(teacher=new_class_master) TeacherClassThrough.objects.filter(study_class_id=instance.id, teacher=new_class_master) \ .update(is_class_master=True) StudentCatalogPerSubject.objects.filter(study_class_id=instance.id, is_coordination_subject=True) \ .update(teacher=new_class_master) # Add students if new_students: self.add_students_to_class(instance, new_students) # Delete students if deleted_students: StudentCatalogPerYear.objects.filter(student__in=deleted_students, study_class=instance).delete() StudentCatalogPerSubject.objects.filter(student__in=deleted_students, study_class=instance).delete() deleted_students.update(student_in_class=None) return instance @staticmethod def add_students_to_class(instance, students): optional_subjects = StudyClassCreateUpdateSerializer.get_optional_subjects(instance.academic_program, instance) teachers_class_through = instance.teacher_class_through.all() catalogs_per_year = [] catalogs_per_subject = [] student_ids = [] for student in students: student_ids.append(student.id) student.student_in_class = instance catalogs_per_year.append( StudentCatalogPerYear(student=student, study_class=instance, academic_year=instance.academic_year) ) for teacher_class_through in teachers_class_through: subject = teacher_class_through.subject catalogs_per_subject.append( StudentCatalogPerSubject(student=student, teacher=teacher_class_through.teacher, study_class=instance, academic_year=instance.academic_year, subject=subject, subject_name=subject.name, is_coordination_subject=subject.is_coordination, is_enrolled=subject.id not in optional_subjects) ) UserProfile.objects.bulk_update(students, ['student_in_class']) StudentCatalogPerYear.objects.bulk_create(catalogs_per_year) StudentCatalogPerSubject.objects.bulk_create(catalogs_per_subject) create_behavior_grades_task.delay(student_ids) import_students_data.delay(student_ids, instance.class_grade_arabic) def to_representation(self, instance): return StudyClassDetailSerializer(instance, context=self.context).data
class StudyClassCreateUpdateSerializer(StudyClassBaseSerializer): teachers_class_through = TeacherClassThroughCreateUpdateSerializer(many=True, write_only=True) students = PrimaryKeyRelatedField( queryset=UserProfile.objects.filter(user_role=UserProfile.UserRoles.STUDENT, is_active=True), many=True, write_only=True ) class Meta(StudyClassBaseSerializer.Meta): fields = ('id', 'class_grade', 'class_letter', 'academic_year', 'academic_program', 'academic_program_name', 'class_master', 'teachers_class_through', 'students', 'has_previous_catalog_data') read_only_fields = ('academic_year', 'academic_program_name') extra_kwargs = { 'academic_program': {'write_only': True} } def validate(self, attrs): school_unit = self.context['principal_school'] academic_year = self.context['academic_year'] class_grade = attrs['class_grade'] class_letter = attrs['class_letter'] class_master = attrs['class_master'] has_previous_catalog_data = self.get_has_previous_catalog_data(self.instance) if self.instance else False if class_grade not in CLASS_GRADE_MAPPING or \ CATEGORY_LEVELS_CLASS_MAPPING[class_grade] not in school_unit.categories.values_list('category_level', flat=True): raise serializers.ValidationError({'class_grade': _('Invalid class grade.')}) if self.instance and class_grade != self.instance.class_grade and has_previous_catalog_data: raise serializers.ValidationError({'class_grade': _('Cannot change the class grade for a class that has previous catalog data.')}) if not bool(re.search(r'^[A-Z0-9]*$', class_letter)): raise serializers.ValidationError({'class_letter': _('This value can contain only uppercase letters and digits.')}) exclude = {'id': self.instance.id} if self.instance else {} if StudyClass.objects.filter(school_unit_id=school_unit.id, academic_year=academic_year, class_grade=class_grade, class_letter=class_letter) \ .exclude(**exclude).exists(): raise serializers.ValidationError({'general_errors': _('This study class already exists.')}) if (not self.instance or self.instance.class_master != class_master) \ and (class_master.user_role != UserProfile.UserRoles.TEACHER or not class_master.is_active or class_master.school_unit_id != school_unit.id or class_master.mastering_study_classes.filter(academic_year=academic_year).exists()): raise serializers.ValidationError({'class_master': _('Invalid user.')}) academic_program = attrs.get('academic_program') if academic_program is None: raise serializers.ValidationError({'academic_program': _('This field is required.')}) if academic_program.academic_year != academic_year or academic_program.school_unit_id != school_unit.id or \ academic_program.generic_academic_program.category.category_level != CATEGORY_LEVELS_CLASS_MAPPING[class_grade]: raise serializers.ValidationError({'academic_program': _('Invalid academic program.')}) if self.instance and academic_program != self.instance.academic_program and has_previous_catalog_data: raise serializers.ValidationError({'academic_program': _('Cannot change the academic program for a class that has previous catalog data.')}) # Validate subjects against the academic program mandatory & optional subjects. teachers_class_through = attrs['teachers_class_through'] program_subjects = set(academic_program.program_subjects_through.filter(class_grade=class_grade).values_list('subject_id', flat=True)) program_subjects.update(set(academic_program.generic_academic_program.program_subjects_through .filter(class_grade=class_grade).values_list('subject_id', flat=True))) request_subjects = set(teacher_class_through['subject'].id for teacher_class_through in teachers_class_through) if program_subjects != request_subjects: raise serializers.ValidationError({'teachers_class_through': _('The subjects do not correspond with the academic program subjects.')}) class_grade_arabic = CLASS_GRADE_MAPPING[class_grade] for teacher_class_through in teachers_class_through: teacher = teacher_class_through['teacher'] subject = teacher_class_through['subject'] if teacher.user_role != UserProfile.UserRoles.TEACHER or not teacher.is_active or teacher.school_unit_id != school_unit.id: raise serializers.ValidationError({'teachers_class_through': _('At least one teacher is invalid.')}) if subject not in teacher.taught_subjects.all() and \ ((class_grade_arabic in range(0, 5) and teacher != class_master) or (class_grade_arabic in range(5, 14) and subject not in academic_program.optional_subjects.all())): raise serializers.ValidationError({'teachers_class_through': _('Teacher {} does not teach {}.').format(teacher.full_name, subject.name)}) for student in attrs['students']: if student.school_unit_id != school_unit.id or student.student_in_class not in [None, self.instance]: raise serializers.ValidationError({'students': _('At least one student is invalid.')}) attrs['academic_program_name'] = academic_program.name if academic_program else None attrs['class_grade_arabic'] = class_grade_arabic attrs['teachers_class_through'].append( { "teacher": class_master, "subject": Subject.objects.get(is_coordination=True) } ) if not self.instance: attrs['school_unit'] = school_unit attrs['academic_year'] = academic_year return attrs def create(self, validated_data): teachers_class_through = validated_data.pop('teachers_class_through') students = validated_data.pop('students') instance = StudyClass.objects.create(**validated_data) optional_subjects = self.get_optional_subjects(instance.academic_program, instance) # Add class teachers self.create_teachers_class_through(instance, teachers_class_through, instance.academic_year, optional_subjects) # Add class students self.add_students_to_class(instance, students, instance.academic_year, teachers_class_through, optional_subjects) create_students_at_risk_counts_for_study_class_task.delay(instance.id) return instance def update(self, instance, validated_data): teachers_class_through = validated_data.pop('teachers_class_through') students = validated_data.pop('students') academic_program = validated_data.get('academic_program') old_academic_program = instance.academic_program if academic_program != old_academic_program: academic_program.classes_count += 1 academic_program.save() if old_academic_program is not None and old_academic_program.classes_count > 0: old_academic_program.classes_count -= 1 old_academic_program.save() instance = super().update(instance, validated_data) optional_subjects = self.get_optional_subjects(academic_program, instance) # Update teachers TeacherClassThrough.objects.filter(study_class=instance).delete() self.create_teachers_class_through(instance, teachers_class_through, instance.academic_year, optional_subjects) # Update students # Remove all students from class, because it's more efficient than updating all teachers for each of them. existing_students = UserProfile.objects.filter(user_role=UserProfile.UserRoles.STUDENT, student_in_class=instance) StudentCatalogPerYear.objects.filter(student__in=existing_students, study_class=instance).delete() StudentCatalogPerSubject.objects.filter(student__in=existing_students, study_class=instance).delete() existing_students.update(student_in_class=None) # Students that were added to the class self.add_students_to_class(instance, students, instance.academic_year, teachers_class_through, optional_subjects) return instance @staticmethod def get_optional_subjects(academic_program, instance): return academic_program.program_subjects_through.filter(class_grade=instance.class_grade).values_list('subject_id', flat=True) @staticmethod def create_teachers_class_through(instance, teachers_class_through, academic_year, optional_subjects): teacher_class_through_instances = [] for teacher_class_through in teachers_class_through: subject = teacher_class_through['subject'] teacher = teacher_class_through['teacher'] teacher_class_through_instances.append( TeacherClassThrough(study_class=instance, teacher=teacher, subject=subject, academic_year=academic_year, is_class_master=teacher.id == instance.class_master_id, class_grade=instance.class_grade, class_letter=instance.class_letter, academic_program_name=instance.academic_program_name, subject_name=subject.name, is_optional_subject=subject.id in optional_subjects, is_coordination_subject=subject.is_coordination) ) TeacherClassThrough.objects.bulk_create(teacher_class_through_instances) @staticmethod def add_students_to_class(instance, students, academic_year, teachers_class_through, optional_subjects): catalogs_per_year = [] catalogs_per_subject = [] student_ids = [] for student in students: student_ids.append(student.id) student.student_in_class = instance catalogs_per_year.append( StudentCatalogPerYear(student=student, study_class=instance, academic_year=academic_year) ) for teacher_class_through in teachers_class_through: subject = teacher_class_through['subject'] catalogs_per_subject.append( StudentCatalogPerSubject(student=student, teacher=teacher_class_through['teacher'], study_class=instance, academic_year=academic_year, subject=subject, subject_name=subject.name, is_coordination_subject=subject.is_coordination, is_enrolled=subject.id not in optional_subjects) ) UserProfile.objects.bulk_update(students, ['student_in_class']) StudentCatalogPerYear.objects.bulk_create(catalogs_per_year) StudentCatalogPerSubject.objects.bulk_create(catalogs_per_subject) create_behavior_grades_task.delay(student_ids) def to_representation(self, instance): return StudyClassDetailSerializer(instance, context=self.context).data
class DeactivateUserSerializer(serializers.ModelSerializer): new_school_principal = PrimaryKeyRelatedField( queryset=UserProfile.objects.filter( user_role=UserProfile.UserRoles.PRINCIPAL, is_active=True, school_unit__isnull=True), required=False) new_teachers = TeacherClassThroughPartiallyUpdateSerializer(many=True, required=False) class Meta: model = UserProfile fields = ('new_school_principal', 'new_teachers') @lru_cache(maxsize=None) def get_current_academic_calendar(self): return get_current_academic_calendar() @lru_cache(maxsize=None) def get_user_assigned_classes(self): current_academic_calendar = self.get_current_academic_calendar() return self.instance.teacher_class_through \ .filter(academic_year=current_academic_calendar.academic_year) if current_academic_calendar else [] def validate(self, attrs): if self.instance.user_role == UserProfile.UserRoles.PRINCIPAL: new_school_principal = attrs.get('new_school_principal') if self.instance.school_unit and new_school_principal is None: raise serializers.ValidationError( {'new_school_principal': _('This field is required.')}) elif self.instance.user_role == UserProfile.UserRoles.TEACHER: new_teachers = attrs.get('new_teachers', []) assigned_classes = self.get_user_assigned_classes() if assigned_classes and not new_teachers: raise serializers.ValidationError( {'new_teachers': _('This field is required.')}) new_teachers_ids = [ teacher_class_through['id'] for teacher_class_through in new_teachers ] new_teachers_ids_set = set(new_teachers_ids) if len(new_teachers_ids) != len(new_teachers_ids_set): raise serializers.ValidationError( {'new_teachers': _('No duplicates allowed.')}) if set(assigned_class.id for assigned_class in assigned_classes) != new_teachers_ids_set: raise serializers.ValidationError({ 'new_teachers': _('There must be provided teachers for all classes and subjects.' ) }) school_unit = self.context['request'].user.user_profile.school_unit current_academic_calendar = self.get_current_academic_calendar() for teacher_class_through in new_teachers: teacher = teacher_class_through['teacher'] if teacher.user_role != UserProfile.UserRoles.TEACHER or not teacher.is_active or teacher.school_unit_id != school_unit.id: raise serializers.ValidationError({ 'new_teachers': _('At least one teacher is invalid.') }) teacher_class_through_instance = TeacherClassThrough.objects.get( id=teacher_class_through['id']) subject = teacher_class_through_instance.subject study_class = teacher_class_through_instance.study_class if not teacher_class_through_instance.is_optional_subject and not subject.is_coordination \ and subject not in teacher.taught_subjects.all() and \ (study_class.class_grade_arabic in range(5, 14) or (study_class.class_grade_arabic in range(0, 5) and teacher.id != study_class.class_master_id)): raise serializers.ValidationError({ 'new_teachers': _('Teacher {} does not teach {}.').format( teacher.full_name, subject.name) }) if subject.is_coordination and teacher.mastering_study_classes.filter( academic_year=current_academic_calendar.academic_year ).exists(): raise serializers.ValidationError( {'new_teachers': _('Invalid class master.')}) teacher_class_through[ 'instance'] = teacher_class_through_instance return attrs def update(self, instance, validated_data): if instance.user_role == UserProfile.UserRoles.PRINCIPAL: school_unit = instance.school_unit if school_unit: new_school_principal = validated_data['new_school_principal'] school_unit.school_principal = new_school_principal school_unit.save() new_school_principal.school_unit = school_unit new_school_principal.save() instance.school_unit = None elif instance.user_role == UserProfile.UserRoles.TEACHER: assigned_classes = self.get_user_assigned_classes() if assigned_classes: new_teachers = validated_data['new_teachers'] new_class_master = None class_master_study_class = None instances_to_update = [] for teacher_class_through in new_teachers: teacher_class_through_instance = teacher_class_through[ 'instance'] teacher = teacher_class_through['teacher'] study_class = teacher_class_through_instance.study_class teacher_class_through_instance.teacher = teacher teacher_class_through_instance.is_class_master = teacher.id == study_class.class_master_id instances_to_update.append(teacher_class_through_instance) if teacher_class_through_instance.is_coordination_subject: new_class_master = teacher class_master_study_class = study_class # Also update catalogs for all students in this class for this subject StudentCatalogPerSubject.objects.filter(study_class_id=teacher_class_through_instance.study_class_id, subject_id=teacher_class_through_instance.subject_id) \ .update(teacher=teacher) TeacherClassThrough.objects.bulk_update( instances_to_update, ['teacher', 'is_class_master']) if new_class_master and class_master_study_class: class_master_study_class.class_master = new_class_master class_master_study_class.save() TeacherClassThrough.objects.filter(study_class_id=class_master_study_class.id, teacher=new_class_master) \ .update(is_class_master=True) instance.is_active = False instance.save() instance.user.is_active = False instance.user.save() # Delete all tokens & Refresh tokens for this users AccessToken.objects.filter(user__user_profile=instance).delete() RefreshToken.objects.filter(user__user_profile=instance).delete() return instance def to_representation(self, instance): if instance.user_role == UserProfile.UserRoles.PRINCIPAL: return SchoolPrincipalSerializer(instance).data if instance.user_role == UserProfile.UserRoles.TEACHER: return SchoolTeacherSerializer(instance).data if instance.user_role == UserProfile.UserRoles.PARENT: return ParentSerializer(instance).data if instance.user_role == UserProfile.UserRoles.STUDENT: return StudentSerializer(instance).data return BaseUserProfileDetailSerializer(instance).data
class StudentSerializer(BaseUserProfileDetailSerializer): personal_id_number = serializers.CharField(validators=[ PersonalIdNumberValidator, ], required=False, allow_null=True) educator_phone_number = serializers.CharField(required=False, validators=[ PhoneNumberValidator, ], allow_null=True) parents = PrimaryKeyRelatedField( queryset=UserProfile.objects.filter( user_role=UserProfile.UserRoles.PARENT, ), many=True, ) labels = PrimaryKeyRelatedField(queryset=Label.objects.all(), many=True) student_in_class = StudyClassNameSerializer(read_only=True) class Meta(UserProfileBaseSerializer.Meta): extra_fields = ('address', 'personal_id_number', 'birth_date', 'educator_full_name', 'educator_email', 'educator_phone_number', 'labels', 'parents', 'student_in_class', 'risk_description') fields = BaseUserProfileDetailSerializer.Meta.fields + extra_fields read_only_fields = ('student_in_class', 'risk_description') def validate(self, attrs): if attrs.get('educator_full_name') and not ( attrs.get('educator_email') or attrs.get('educator_phone_number')): raise serializers.ValidationError({ 'educator_full_name': _('Either email or phone number is required for the educator.') }) # Birth date must be in the past birth_date = attrs.get('birth_date') if birth_date and birth_date >= timezone.now().date(): raise serializers.ValidationError( {'birth_date': BIRTH_DATE_IN_THE_PAST_ERROR}) labels = attrs.get('labels') user_role = attrs['user_role'] self.validate_labels_set(labels, user_role) # Parents must belong to the request user's school unit parents = attrs.get('parents') if parents: allowed_school = self.context[ 'request'].user.user_profile.school_unit for parent in parents: if parent.school_unit != allowed_school: raise serializers.ValidationError({ 'parents': _("Parents must belong to the user's school unit.") }) return attrs def to_representation(self, instance): representation = super().to_representation(instance) representation['labels'] = LabelSerializer(instance=instance.labels, many=True).data representation['parents'] = ParentBaseSerializer( instance=instance.parents, many=True).data return representation
class SchoolTeacherSerializer(BaseUserProfileDetailSerializer): labels = PrimaryKeyRelatedField(queryset=Label.objects.all(), many=True) taught_subjects = PrimaryKeyRelatedField(queryset=Subject.objects.all(), many=True) assigned_study_classes = serializers.SerializerMethodField() new_teachers = TeacherClassThroughPartiallyUpdateSerializer( many=True, required=False, write_only=True) class Meta(UserProfileBaseSerializer.Meta): extra_fields = ('labels', 'taught_subjects', 'assigned_study_classes', 'new_teachers') fields = BaseUserProfileDetailSerializer.Meta.fields + extra_fields @lru_cache(maxsize=None) def get_current_academic_calendar(self): return get_current_academic_calendar() @lru_cache(maxsize=None) def get_assigned_study_classes(self, obj): current_academic_calendar = self.get_current_academic_calendar() if current_academic_calendar: return TeacherClassThroughAssignedStudyClassSerializer( obj.teacher_class_through.filter( academic_year=current_academic_calendar.academic_year). order_by('study_class__class_grade_arabic', 'class_letter', Lower('subject_name')), many=True).data return [] def validate(self, attrs): labels = attrs.get('labels') user_role = attrs['user_role'] self.validate_labels_set(labels, user_role) new_teachers = attrs.get('new_teachers', []) new_teachers_compatible = False if self.instance: existing_subjects = set( self.instance.taught_subjects.values_list('id', flat=True)) request_subjects = set( subject.id for subject in attrs.get('taught_subjects', [])) removed_subjects = existing_subjects - request_subjects if removed_subjects: new_teachers_ids = [ teacher_class_through['id'] for teacher_class_through in new_teachers ] new_teachers_ids_set = set(new_teachers_ids) assigned_classes = self.get_assigned_study_classes( self.instance) subject_assigned_classes = [ assigned_class for assigned_class in assigned_classes if assigned_class['subject_id'] in removed_subjects ] if subject_assigned_classes: if set(assigned_class['id'] for assigned_class in subject_assigned_classes) != new_teachers_ids_set: raise serializers.ValidationError({ 'new_teachers': _('There must be provided teachers for all classes for this subject.' ) }) if len(new_teachers_ids) != len(new_teachers_ids_set): raise serializers.ValidationError( {'new_teachers': _('No duplicates allowed.')}) school_unit = self.context[ 'request'].user.user_profile.school_unit for teacher_class_through in new_teachers: teacher = teacher_class_through['teacher'] if teacher.user_role != UserProfile.UserRoles.TEACHER or not teacher.is_active or teacher.school_unit_id != school_unit.id: raise serializers.ValidationError({ 'new_teachers': _('At least one teacher is invalid.') }) teacher_class_through_instance = TeacherClassThrough.objects.get( id=teacher_class_through['id']) subject = teacher_class_through_instance.subject study_class = teacher_class_through_instance.study_class if subject not in teacher.taught_subjects.all() and \ (study_class.class_grade_arabic in range(5, 14) or (study_class.class_grade_arabic in range(0, 5) and teacher.id != study_class.class_master_id)): raise serializers.ValidationError({ 'new_teachers': _('Teacher {} does not teach {}.').format( teacher.full_name, subject.name) }) teacher_class_through[ 'instance'] = teacher_class_through_instance new_teachers_compatible = True self.get_assigned_study_classes.cache_clear() if new_teachers and not new_teachers_compatible: raise serializers.ValidationError({ 'new_teachers': _('This field is incompatible with the request data.') }) return attrs def to_representation(self, instance): representation = super().to_representation(instance) representation['labels'] = LabelSerializer(instance=instance.labels, many=True).data representation['taught_subjects'] = SubjectSerializer( instance=instance.taught_subjects, many=True).data return representation
class RegisteredSchoolUnitCreateUpdateBaseSerializer( serializers.ModelSerializer): categories = PrimaryKeyRelatedField( queryset=SchoolUnitCategory.objects.all(), many=True) phone_number = serializers.CharField(validators=[ PhoneNumberValidator, ]) class Meta: model = RegisteredSchoolUnit def validate(self, attrs): validated_data = super().validate(attrs) if not self.instance and RegisteredSchoolUnit.objects.filter( name=attrs['name'], district=attrs['district'], city=attrs['city']).exists(): raise serializers.ValidationError( {'general_errors': _('This school is already registered.')}) categories = validated_data['categories'] if len(categories) != len( set(category.category_level for category in categories)): raise serializers.ValidationError({ 'categories': _('Cannot have multiple categories for the same school level.') }) academic_profile = validated_data.get('academic_profile', None) if academic_profile and academic_profile.category not in categories: raise serializers.ValidationError({ 'academic_profile': _('The academic profile does not correspond with the school category.' ) }) school_principal = validated_data['school_principal'] if school_principal.user_role != UserProfile.UserRoles.PRINCIPAL or not school_principal.is_active: raise serializers.ValidationError( {'school_principal': _('Invalid user.')}) if getattr( school_principal, 'registered_school_unit', None ) and school_principal.registered_school_unit != self.instance: raise serializers.ValidationError({ 'school_principal': _('This school principal already has a school assigned.') }) return validated_data def to_representation(self, instance): representation = super().to_representation(instance) representation['categories'] = SchoolUnitCategorySerializer( instance.categories, many=True).data if instance.academic_profile: representation['academic_profile'] = SchoolUnitProfileSerializer( instance.academic_profile).data representation['school_principal'] = UserProfileBaseSerializer( instance.school_principal).data return representation