Skip to content

Commit

Permalink
Portal frontend 45 (#360)
Browse files Browse the repository at this point in the history
* include teacher when reading classes

* include user

* new package

* read user serializer

* use latest package python
  • Loading branch information
SKairinos authored Sep 3, 2024
1 parent 4cd73d7 commit 0bd0178
Show file tree
Hide file tree
Showing 11 changed files with 451 additions and 684 deletions.
4 changes: 2 additions & 2 deletions 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.18.4", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"}
codeforlife = {ref = "v0.18.6", git = "https://github.com/ocadotechnology/codeforlife-package-python.git"}
# 🚫 Don't add [packages] below that are inherited from the CFL package.
pyjwt = "==2.6.0" # TODO: upgrade to latest version
# TODO: Needed by RR. Remove when RR has moved to new system.
Expand All @@ -32,7 +32,7 @@ django-sekizai = "==2.0.0"
django-classy-tags = "==2.0.0"

[dev-packages]
codeforlife = {ref = "v0.18.4", git = "https://github.com/ocadotechnology/codeforlife-package-python.git", extras = ["dev"]}
codeforlife = {ref = "v0.18.6", 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 inherited from the CFL package.

Expand Down
982 changes: 335 additions & 647 deletions Pipfile.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

from .auth_factor import AuthFactorSerializer
from .klass import ClassSerializer
from .klass import ReadClassSerializer, WriteClassSerializer
from .school import SchoolSerializer
from .school_teacher_invitation import (
AcceptSchoolTeacherInvitationSerializer,
Expand All @@ -25,6 +25,7 @@
from .user import (
CreateUserSerializer,
HandleIndependentUserJoinClassRequestSerializer,
ReadUserSerializer,
RegisterEmailToNewsletter,
RequestUserPasswordResetSerializer,
ResetUserPasswordSerializer,
Expand Down
29 changes: 25 additions & 4 deletions src/api/serializers/klass.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@
import string

from codeforlife.serializers import ModelListSerializer
from codeforlife.user.models import Class, Teacher, User
from codeforlife.user.models import (
Class,
SchoolTeacher,
SchoolTeacherUser,
Teacher,
User,
)
from codeforlife.user.serializers import BaseUserSerializer
from codeforlife.user.serializers import ClassSerializer as _ClassSerializer
from codeforlife.user.serializers import TeacherSerializer as _TeacherSerializer
from django.utils.crypto import get_random_string
from rest_framework import serializers

Expand All @@ -16,7 +24,7 @@
# pylint: disable=too-many-ancestors


class ClassListSerializer(ModelListSerializer[User, Class]):
class WriteClassListSerializer(ModelListSerializer[User, Class]):
def update(self, instance, validated_data):
for klass, data in zip(instance, validated_data):
klass.name = data.get("name", klass.name)
Expand All @@ -26,7 +34,7 @@ def update(self, instance, validated_data):
return instance


class ClassSerializer(_ClassSerializer):
class WriteClassSerializer(_ClassSerializer):
read_classmates_data = serializers.BooleanField(
source="classmates_data_viewable",
)
Expand All @@ -42,7 +50,7 @@ class Meta(_ClassSerializer.Meta):
"name": {"read_only": False},
"teacher": {"required": False},
}
list_serializer_class = ClassListSerializer
list_serializer_class = WriteClassListSerializer

def validate_teacher(self, value: Teacher):
user = self.request.school_teacher_user
Expand Down Expand Up @@ -97,3 +105,16 @@ def create(self, validated_data):
),
}
)


class ReadClassSerializer(_ClassSerializer):
class TeacherSerializer(_TeacherSerializer[SchoolTeacher]):
user = BaseUserSerializer[SchoolTeacherUser](
source="new_user",
read_only=True,
)

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

teacher = TeacherSerializer(read_only=True)
6 changes: 3 additions & 3 deletions src/api/serializers/klass_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
User,
)

from .klass import ClassSerializer
from .klass import WriteClassSerializer

# pylint: disable=missing-class-docstring


class TestClassSerializer(ModelSerializerTestCase[User, Class]):
model_serializer_class = ClassSerializer
class TestWriteClassSerializer(ModelSerializerTestCase[User, Class]):
model_serializer_class = WriteClassSerializer
fixtures = ["school_1", "school_2"]

def setUp(self):
Expand Down
40 changes: 40 additions & 0 deletions src/api/serializers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@
Class,
ContactableUser,
IndependentUser,
SchoolTeacher,
Student,
StudentUser,
Teacher,
TeacherUser,
User,
)
from codeforlife.user.serializers import (
BaseUserSerializer as _BaseUserSerializer,
)
from codeforlife.user.serializers import ClassSerializer as _ClassSerializer
from codeforlife.user.serializers import TeacherSerializer as _TeacherSerializer
from codeforlife.user.serializers import UserSerializer as _UserSerializer
from django.conf import settings
from django.contrib.auth.password_validation import (
Expand Down Expand Up @@ -435,3 +439,39 @@ def create(self, validated_data):
user.add_contact_to_dot_digital()

return user


class ReadUserSerializer(_UserSerializer[User]):
class ClassSerializer(_ClassSerializer):
class TeacherSerializer(_TeacherSerializer[SchoolTeacher]):
user = _BaseUserSerializer[TeacherUser](
source="new_user", read_only=True
)

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

teacher = TeacherSerializer(read_only=True)

requesting_to_join_class = ClassSerializer( # type: ignore[assignment]
source="new_student.pending_class_request",
read_only=True,
)

def to_representation(self, instance):
representation = super().to_representation(instance)

try:
if (
instance.new_student
and instance.new_student.pending_class_request
):
representation[
"requesting_to_join_class"
] = self.ClassSerializer(
instance.new_student.pending_class_request
).data
except Student.DoesNotExist:
pass

return representation
11 changes: 8 additions & 3 deletions src/api/views/klass.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@
from rest_framework import status
from rest_framework.response import Response

from ..serializers import ClassSerializer
from ..serializers import ReadClassSerializer, WriteClassSerializer


# pylint: disable-next=missing-class-docstring,too-many-ancestors
class ClassViewSet(_ClassViewSet):
http_method_names = ["get", "post", "patch", "delete"]
serializer_class = ClassSerializer

def get_permissions(self):
# Bulk actions not allowed for classes.
# Only bulk-partial-update allowed for classes.
if self.action == "bulk":
return (
[OR(IsTeacher(is_admin=True), IsTeacher(in_class=True))]
Expand All @@ -31,6 +30,12 @@ def get_permissions(self):

return super().get_permissions()

def get_serializer_class(self):
if self.action in ["create", "partial_update", "bulk"]:
return WriteClassSerializer

return ReadClassSerializer

def destroy(self, request, *args, **kwargs):
klass = self.get_object()

Expand Down
31 changes: 31 additions & 0 deletions src/api/views/klass_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from django.utils import timezone
from rest_framework import status

from ..serializers import ReadClassSerializer, WriteClassSerializer
from .klass import ClassViewSet


Expand Down Expand Up @@ -103,6 +104,36 @@ def test_get_permissions__retrieve(self):
action="retrieve",
)

# test: get serializer class

def test_get_serializer_class__create(self):
"""Creating a class has a dedicated serializer."""
self.assert_get_serializer_class(
serializer_class=WriteClassSerializer,
action="create",
)

def test_get_serializer_class__partial_update(self):
"""Partially updating a class has a dedicated serializer."""
self.assert_get_serializer_class(
serializer_class=WriteClassSerializer,
action="partial_update",
)

def test_get_serializer_class__retrieve(self):
"""Retrieving a class has a dedicated serializer."""
self.assert_get_serializer_class(
serializer_class=ReadClassSerializer,
action="retrieve",
)

def test_get_serializer_class__list(self):
"""Listing classes has a dedicated serializer."""
self.assert_get_serializer_class(
serializer_class=ReadClassSerializer,
action="list",
)

# test: default actions

def test_create__self(self):
Expand Down
3 changes: 2 additions & 1 deletion src/api/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from ..serializers import (
CreateUserSerializer,
HandleIndependentUserJoinClassRequestSerializer,
ReadUserSerializer,
RegisterEmailToNewsletter,
RequestUserPasswordResetSerializer,
ResetUserPasswordSerializer,
Expand Down Expand Up @@ -96,7 +97,7 @@ def get_serializer_class(self):
if self.action == "register_to_newsletter":
return RegisterEmailToNewsletter

return super().get_serializer_class()
return ReadUserSerializer

def get_queryset(self, user_class=User):
if self.action in ["reset_password", "verify_email_address"]:
Expand Down
6 changes: 3 additions & 3 deletions src/api/views/user_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
UserProfile,
)
from codeforlife.user.permissions import IsIndependent, IsTeacher
from codeforlife.user.serializers import UserSerializer
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.contrib.auth.tokens import (
Expand All @@ -45,6 +44,7 @@
from ..serializers import (
CreateUserSerializer,
HandleIndependentUserJoinClassRequestSerializer,
ReadUserSerializer,
RegisterEmailToNewsletter,
RequestUserPasswordResetSerializer,
ResetUserPasswordSerializer,
Expand Down Expand Up @@ -303,14 +303,14 @@ def test_get_serializer_class__partial_update(self):
def test_get_serializer_class__retrieve(self):
"""Retrieving a user uses the general serializer."""
self.assert_get_serializer_class(
serializer_class=UserSerializer,
serializer_class=ReadUserSerializer,
action="retrieve",
)

def test_get_serializer_class__list(self):
"""Listing users uses the general serializer."""
self.assert_get_serializer_class(
serializer_class=UserSerializer,
serializer_class=ReadUserSerializer,
action="list",
)

Expand Down
20 changes: 0 additions & 20 deletions src/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,8 @@
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""

from aimmo import urls as aimmo_urls # type: ignore[import-untyped]
from codeforlife.urls import get_urlpatterns
from django.urls import include, path
from portal.views.aimmo.dashboard import ( # type: ignore[import-untyped]
StudentAimmoDashboard,
TeacherAimmoDashboard,
)

from .api.urls import urlpatterns

Expand All @@ -30,21 +25,6 @@
include("game.urls"),
name="rapidrouter",
),
path(
"teach/kurono/dashboard/",
TeacherAimmoDashboard.as_view(),
name="teacher_aimmo_dashboard",
),
path(
"play/kurono/dashboard/",
StudentAimmoDashboard.as_view(),
name="student_aimmo_dashboard",
),
path(
"kurono/",
include(aimmo_urls),
name="kurono",
),
*get_urlpatterns(urlpatterns, include_user_urls=False),
path(
"api/sso/",
Expand Down

0 comments on commit 0bd0178

Please sign in to comment.