Skip to content

Commit

Permalink
Register teacher (#315)
Browse files Browse the repository at this point in the history
* transfer class

* transfer students

* merge from development

* add toml support

* use new py package

* add student view set and serializer

* test get queryset

* quick save

* fix transfer students

* move bulk destroy students and house keeping

* merge from dev

* fix tests

* fix tests again lolz

* so much test fixing :(

* remove duplicate fields

* install new py package

* merge from dev

* allow users to update password and converted reset password to dedicated update actions

* convert handle-join-class-request to update

* use cfl action

* merge from dev

* move all teacher logic to teacher view set

* install new py package

* feedback

* feedback

* install new py package
  • Loading branch information
SKairinos authored Mar 26, 2024
1 parent 52268fc commit 3c725d8
Show file tree
Hide file tree
Showing 22 changed files with 831 additions and 499 deletions.
4 changes: 2 additions & 2 deletions backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ name = "pypi"
# 5. Run `pipenv install --dev` in your terminal.

[packages]
codeforlife = {ref = "v0.14.9", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"}
codeforlife = {ref = "v0.14.11", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"}
# 🚫 Don't add [packages] below that are inhertited from the CFL package.
# TODO: check if we need the below packages
whitenoise = "==6.5.0"
Expand All @@ -48,7 +48,7 @@ google-cloud-container = "==2.3.0"
# "django-anymail[amazon_ses]" = "==7.0.*"

[dev-packages]
codeforlife = {ref = "v0.14.9", git = "https://github.com/ocadotechnology/codeforlife-package-python.git", extras = ["dev"]}
codeforlife = {ref = "v0.14.11", git = "https://github.com/ocadotechnology/codeforlife-package-python.git", extras = ["dev"]}
# codeforlife = {file = "../../codeforlife-package-python", editable = true, extras = ["dev"]}
# 🚫 Don't add [dev-packages] below that are inhertited from the CFL package.
# TODO: check if we need the below packages
Expand Down
28 changes: 14 additions & 14 deletions backend/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion backend/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
ResetStudentPasswordSerializer,
TransferStudentSerializer,
)
from .teacher import TeacherSerializer
from .teacher import (
CreateTeacherSerializer,
RemoveTeacherFromSchoolSerializer,
SetSchoolTeacherAdminAccessSerializer,
)
from .user import (
HandleIndependentUserJoinClassRequestSerializer,
RequestUserPasswordResetSerializer,
Expand Down
24 changes: 16 additions & 8 deletions backend/api/serializers/klass.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,27 @@

import string

from codeforlife.serializers import ModelListSerializer
from codeforlife.user.models import Class, Teacher
from codeforlife.user.serializers import ClassSerializer as _ClassSerializer
from django.utils.crypto import get_random_string
from rest_framework import serializers

# pylint: disable=missing-function-docstring
# pylint: disable=missing-class-docstring
# pylint: disable=too-many-ancestors


class ClassListSerializer(ModelListSerializer[Class]):
def update(self, instance, validated_data):
for klass, data in zip(instance, validated_data):
klass.name = data.get("name", klass.name)
klass.teacher = data.get("teacher", klass.teacher)
klass.save()

return instance


# pylint: disable-next=missing-class-docstring,too-many-ancestors
class ClassSerializer(_ClassSerializer):
read_classmates_data = serializers.BooleanField(
source="classmates_data_viewable",
Expand All @@ -28,25 +42,19 @@ class Meta(_ClassSerializer.Meta):
"name": {"read_only": False},
"teacher": {"required": False},
}
list_serializer_class = ClassListSerializer

# pylint: disable-next=missing-function-docstring
def validate_teacher(self, value: Teacher):
user = self.request.school_teacher_user
if value.school_id != user.teacher.school_id:
raise serializers.ValidationError(
"This teacher is not in your school.",
code="not_in_school",
)
if value != user.teacher and not user.teacher.is_admin:
raise serializers.ValidationError(
"Cannot assign another teacher if you're not an admin.",
code="not_admin",
)

return value

# TODO: set unique_together=("name", "school") for in new Class model.
# pylint: disable-next=missing-function-docstring
def validate_name(self, value: str):
if Class.objects.filter(
teacher__school=self.request.school_teacher_user.teacher.school,
Expand Down
107 changes: 92 additions & 15 deletions backend/api/serializers/teacher.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,105 @@

import typing as t

from codeforlife.user.models import Teacher, User
from codeforlife.user.serializers import TeacherSerializer as _TeacherSerializer
from codeforlife.types import DataDict
from codeforlife.user.models import (
AdminSchoolTeacher,
SchoolTeacher,
Teacher,
TeacherUser,
teacher_as_type,
)
from codeforlife.user.serializers import TeacherSerializer
from rest_framework import serializers

from .user import BaseUserSerializer

# pylint: disable-next=missing-class-docstring,too-many-ancestors
class TeacherSerializer(_TeacherSerializer[Teacher]):
class Meta(_TeacherSerializer.Meta):
# pylint: disable=missing-class-docstring
# pylint: disable=too-many-ancestors
# pylint: disable=missing-function-docstring


class CreateTeacherSerializer(TeacherSerializer[Teacher]):
class UserSerializer(BaseUserSerializer):
add_to_newsletter = serializers.BooleanField(write_only=True)

class Meta(BaseUserSerializer.Meta):
fields = [
*BaseUserSerializer.Meta.fields,
"password",
"add_to_newsletter",
]
extra_kwargs = {
**BaseUserSerializer.Meta.extra_kwargs,
"first_name": {"min_length": 1},
"last_name": {"min_length": 1},
"password": {"write_only": True},
"email": {"read_only": False},
}

user = UserSerializer(source="new_user")

class Meta(TeacherSerializer.Meta):
fields = [*TeacherSerializer.Meta.fields, "user"]

def create(self, validated_data):
user_fields = t.cast(DataDict, validated_data["new_user"])
add_to_newsletter = user_fields.pop("add_to_newsletter")

teacher_user = TeacherUser.objects.create_user(**user_fields)
if add_to_newsletter:
teacher_user.add_contact_to_dot_digital()

# TODO: send verification email.

return teacher_user.teacher


class RemoveTeacherFromSchoolSerializer(TeacherSerializer[SchoolTeacher]):
def validate(self, attrs):
if self.non_none_instance.classes.exists():
raise serializers.ValidationError(
"Cannot leave school when you have classes assigned to you.",
code="has_classes",
)

if (
self.non_none_instance.is_admin
and teacher_as_type(
self.non_none_instance, AdminSchoolTeacher
).is_last_admin
):
raise serializers.ValidationError(
"There must be at least one admin teacher in the school.",
code="last_admin",
)

return attrs

def update(self, instance, validated_data):
instance.school = None
instance.save(update_fields=["school"])
return instance


class SetSchoolTeacherAdminAccessSerializer(TeacherSerializer[SchoolTeacher]):
class Meta(TeacherSerializer.Meta):
extra_kwargs = {
**_TeacherSerializer.Meta.extra_kwargs,
**TeacherSerializer.Meta.extra_kwargs,
"is_admin": {"read_only": False},
}

# pylint: disable-next=missing-function-docstring
def validate_is_admin(self, value: bool):
instance = t.cast(t.Optional[User], self.parent.instance)
if instance:
user = self.request.school_teacher_user
if user.pk == instance.pk:
raise serializers.ValidationError(
"Cannot update own permissions.",
code="is_self",
)
if (
not value
and self.non_none_instance.is_admin
and teacher_as_type(
self.non_none_instance, AdminSchoolTeacher
).is_last_admin
):
raise serializers.ValidationError(
"There must be at least one admin teacher in the school.",
code="last",
)

return value
Loading

0 comments on commit 3c725d8

Please sign in to comment.