Skip to content

Commit

Permalink
Create class (#262)
Browse files Browse the repository at this point in the history
* create school

* remove is_unique_email action

* merge from development

* fix unit tests

* Merge branch 'development' into create_class

* everything but the serializer

* fix: create class

* Merge branch 'development' into create_class

* fix feedback

* use py package v0.12.8

* feedback
  • Loading branch information
SKairinos authored Feb 8, 2024
1 parent 0af7c1b commit 233dc83
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 20 deletions.
2 changes: 1 addition & 1 deletion backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ google-cloud-logging = "==1.*"
google-auth = "==2.*"
google-cloud-container = "==2.3.0"
# "django-anymail[amazon_ses]" = "==7.0.*"
codeforlife = {ref = "v0.12.4", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"}
codeforlife = {ref = "v0.12.8", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"}
django = "==3.2.23"
djangorestframework = "==3.13.1"
django-cors-headers = "==4.1.0"
Expand Down
10 changes: 3 additions & 7 deletions backend/Pipfile.lock

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

94 changes: 92 additions & 2 deletions backend/api/serializers/klass.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,100 @@
Created on 24/01/2024 at 12:14:21(+00:00).
"""

import string

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-next=missing-class-docstring
# pylint: disable-next=missing-class-docstring,too-many-ancestors
class ClassSerializer(_ClassSerializer):
teacher = serializers.IntegerField(
source="teacher.id",
required=False,
)

read_classmates_data = serializers.BooleanField(
source="classmates_data_viewable",
)

receive_requests_until = serializers.DateTimeField(
source="accept_requests_until",
required=False,
)

class Meta(_ClassSerializer.Meta):
pass
extra_kwargs = {
**_ClassSerializer.Meta.extra_kwargs,
"name": {"read_only": False},
}

# pylint: disable-next=missing-function-docstring
def validate_teacher(self, value: int):
queryset = Teacher.objects.filter(id=value)
if not queryset.exists():
raise serializers.ValidationError(
"This teacher does not exist.",
code="does_not_exist",
)

user = self.request_school_teacher_user
if not queryset.filter(school=user.teacher.school_id).exists():
raise serializers.ValidationError(
"This teacher is not in your school.",
code="not_in_school",
)
if value != user.teacher.id and not user.teacher.is_admin:
raise serializers.ValidationError(
"Cannot assign another teacher if you're not 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,
name=value,
).exists():
raise serializers.ValidationError(
"Name already taken.",
code="name_not_unique",
)

return value

def create(self, validated_data):
# TODO: move generation logic to new Class model.
access_code = None
while (
access_code is None
or Class.objects.filter(access_code=access_code).exists()
):
access_code = get_random_string(
length=5,
allowed_chars=string.ascii_uppercase,
)

# TODO: set school to teacher's school on new Class model.
return super().create(
{
"access_code": access_code,
"name": validated_data["name"],
"teacher_id": (
validated_data["teacher"]["id"]
if "teacher" in validated_data
else self.request_school_teacher_user.teacher.id
),
"classmates_data_viewable": validated_data[
"classmates_data_viewable"
],
"accept_requests_until": validated_data.get(
"accept_requests_until"
),
}
)
1 change: 0 additions & 1 deletion backend/api/serializers/school.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,6 @@ class SchoolSerializer(_SchoolSerializer):

uk_county = serializers.ChoiceField( # type: ignore[assignment]
source="county",
default="",
choices=[
"Aberdeen City",
"Aberdeenshire",
Expand Down
123 changes: 123 additions & 0 deletions backend/api/tests/serializers/test_klass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""
© Ocado Group
Created on 05/02/2024 at 15:31:59(+00:00).
"""

from codeforlife.tests import ModelSerializerTestCase
from codeforlife.user.models import Class, SchoolTeacherUser, Teacher

from ...serializers import ClassSerializer


# pylint: disable-next=missing-class-docstring
class ClassSerializerTestCase(ModelSerializerTestCase[Class]):
model_serializer_class = ClassSerializer
fixtures = ["school_1"]

def setUp(self):
self.school_teacher_user = SchoolTeacherUser.objects.get(
email="[email protected]"
)
self.class_1 = Class.objects.get(name="Class 1 @ School 1")

def test_validate_teacher__does_not_exist(self):
"""
Teacher must exist.
"""

self.assert_validate_field(
name="teacher",
value=-1,
error_code="does_not_exist",
)

def test_validate_teacher__not_in_school(self):
"""
Teacher must be in school.
"""

teacher = Teacher.objects.exclude(
school=self.school_teacher_user.teacher.school
).first()
assert teacher

self.assert_validate_field(
name="teacher",
value=teacher.id,
error_code="not_in_school",
context={
"request": self.init_request("POST", self.school_teacher_user)
},
)

def test_validate_teacher__not_admin(self):
"""
Teacher cannot assign another teacher if they're not an admin.
"""

assert not self.school_teacher_user.teacher.is_admin

teacher = (
Teacher.objects.filter(
school=self.school_teacher_user.teacher.school
)
.exclude(pk=self.school_teacher_user.teacher.pk)
.first()
)
assert teacher

self.assert_validate_field(
name="teacher",
value=teacher.id,
error_code="not_admin",
context={
"request": self.init_request("POST", self.school_teacher_user)
},
)

def test_validate_name__name_not_unique(self):
"""
Class names must be unique per school.
"""

self.assert_validate_field(
name="name",
value=self.class_1.name,
error_code="name_not_unique",
context={
"request": self.init_request("POST", self.school_teacher_user)
},
)

def test_create__teacher(self):
"""
Can successfully create with setting the teacher field.
"""

self.assert_create(
{
"name": "ExampleClass",
"teacher": {
"id": self.school_teacher_user.teacher.id,
},
"classmates_data_viewable": False,
}
)

def test_create__no_teacher(self):
"""
Can successfully create without setting the teacher field.
"""

self.assert_create(
{
"name": "ExampleClass",
"classmates_data_viewable": False,
},
new_data={
"teacher": self.school_teacher_user.teacher.id,
},
context={
"request": self.init_request("POST", self.school_teacher_user),
},
)
8 changes: 4 additions & 4 deletions backend/api/tests/serializers/test_student.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def test_validate_klass__teacher_not_in_school(self):
name="klass",
value="",
error_code="teacher_not_in_school",
user=user,
context={"request": self.init_request("POST", user)},
)

def test_validate_klass__does_not_exist(self):
Expand All @@ -47,7 +47,7 @@ def test_validate_klass__does_not_exist(self):
name="klass",
value="",
error_code="does_not_exist",
user=user,
context={"request": self.init_request("POST", user)},
)

def test_validate_klass__teacher_not_in_same_school(self):
Expand All @@ -68,7 +68,7 @@ def test_validate_klass__teacher_not_in_same_school(self):
name="klass",
value=klass.access_code,
error_code="teacher_not_in_same_school",
user=user,
context={"request": self.init_request("POST", user)},
)

def test_validate_klass__teacher_not_admin_or_class_owner(self):
Expand All @@ -93,5 +93,5 @@ def test_validate_klass__teacher_not_admin_or_class_owner(self):
name="klass",
value=klass.access_code,
error_code="teacher_not_admin_or_class_owner",
user=user,
context={"request": self.init_request("POST", user)},
)
Loading

0 comments on commit 233dc83

Please sign in to comment.