Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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
Esempio n. 8
0
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
Esempio n. 9
0
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