diff --git a/codeforlife/request.py b/codeforlife/request.py index c82021e..ed3a8da 100644 --- a/codeforlife/request.py +++ b/codeforlife/request.py @@ -12,7 +12,16 @@ from django.http import HttpRequest as _HttpRequest from rest_framework.request import Request as _Request -from .user.models import User +from .user.models import ( + AdminSchoolTeacherUser, + IndependentUser, + NonAdminSchoolTeacherUser, + NonSchoolTeacherUser, + SchoolTeacherUser, + StudentUser, + TeacherUser, + User, +) from .user.models.session import SessionStore @@ -32,3 +41,50 @@ class HttpRequest(_HttpRequest): class Request(_Request): session: SessionStore user: t.Union[User, AnonymousUser] + + @property + def anon_user(self): + """The anonymous user that made the request.""" + return t.cast(AnonymousUser, self.user) + + @property + def auth_user(self): + """The authenticated user that made the request.""" + return t.cast(User, self.user) + + @property + def teacher_user(self): + """The authenticated teacher-user that made the request.""" + return self.auth_user.as_type(TeacherUser) + + @property + def school_teacher_user(self): + """The authenticated school-teacher-user that made the request.""" + return self.auth_user.as_type(SchoolTeacherUser) + + @property + def admin_school_teacher_user(self): + """The authenticated admin-school-teacher-user that made the request.""" + return self.auth_user.as_type(AdminSchoolTeacherUser) + + @property + def non_admin_school_teacher_user(self): + """ + The authenticated non-admin-school-teacher-user that made the request. + """ + return self.auth_user.as_type(NonAdminSchoolTeacherUser) + + @property + def non_school_teacher_user(self): + """The authenticated non-school-teacher-user that made the request.""" + return self.auth_user.as_type(NonSchoolTeacherUser) + + @property + def student_user(self): + """The authenticated student-user that made the request.""" + return self.auth_user.as_type(StudentUser) + + @property + def indy_user(self): + """The authenticated independent-user that made the request.""" + return self.auth_user.as_type(IndependentUser) diff --git a/codeforlife/serializers/base.py b/codeforlife/serializers/base.py index fb31563..e91df07 100644 --- a/codeforlife/serializers/base.py +++ b/codeforlife/serializers/base.py @@ -7,21 +7,10 @@ import typing as t -from django.contrib.auth.models import AnonymousUser from django.views import View from rest_framework.serializers import BaseSerializer as _BaseSerializer from ..request import Request -from ..user.models import ( # TODO: add IndependentUser - AdminSchoolTeacherUser, - IndependentUser, - NonAdminSchoolTeacherUser, - NonSchoolTeacherUser, - SchoolTeacherUser, - StudentUser, - TeacherUser, - User, -) # pylint: disable-next=abstract-method @@ -34,87 +23,6 @@ def request(self): return t.cast(Request, self.context["request"]) - @property - def request_user(self): - """ - The user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(User, self.request.user) - - @property - def request_teacher_user(self): - """ - The teacher-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(TeacherUser, self.request.user) - - @property - def request_school_teacher_user(self): - """ - The school-teacher-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(SchoolTeacherUser, self.request.user) - - @property - def request_admin_school_teacher_user(self): - """ - The admin-school-teacher-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(AdminSchoolTeacherUser, self.request.user) - - @property - def request_non_admin_school_teacher_user(self): - """ - The non-admin-school-teacher-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(NonAdminSchoolTeacherUser, self.request.user) - - @property - def request_non_school_teacher_user(self): - """ - The non-school-teacher-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(NonSchoolTeacherUser, self.request.user) - - @property - def request_student_user(self): - """ - The student-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(StudentUser, self.request.user) - - @property - def request_indy_user(self): - """ - The independent-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(IndependentUser, self.request.user) - - @property - def request_anon_user(self): - """ - The user that made the request. - Assumes the user has not authenticated. - """ - - return t.cast(AnonymousUser, self.request.user) - @property def view(self): """The view that instantiated this serializer.""" diff --git a/codeforlife/user/models/user.py b/codeforlife/user/models/user.py index 059dae4..acdd019 100644 --- a/codeforlife/user/models/user.py +++ b/codeforlife/user/models/user.py @@ -101,6 +101,29 @@ def totp_provisioning_uri(self): issuer_name="Code for Life", ) + def as_type(self, typed_user_class: t.Type["AnyTypedUser"]): + """Convert this generic user to a typed user. + + Args: + typed_user_class: The type of user to convert to. + + Returns: + An instance of the typed user. + """ + return typed_user_class( + pk=self.pk, + first_name=self.first_name, + last_name=self.last_name, + username=self.username, + is_active=self.is_active, + email=self.email, + is_staff=self.is_staff, + date_joined=self.date_joined, + is_superuser=self.is_superuser, + password=self.password, + last_login=self.last_login, + ) + AnyUser = t.TypeVar("AnyUser", bound=User) @@ -138,7 +161,7 @@ def get_queryset(self): return super().get_queryset().filter(new_teacher__school__isnull=False) -class SchoolTeacherUser(User): +class SchoolTeacherUser(TeacherUser): """A user that is a teacher in a school.""" teacher: SchoolTeacher @@ -161,7 +184,8 @@ def get_queryset(self): return super().get_queryset().filter(new_teacher__is_admin=True) -class AdminSchoolTeacherUser(User): +# pylint: disable-next=too-many-ancestors +class AdminSchoolTeacherUser(SchoolTeacherUser): """A user that is an admin-teacher in a school.""" teacher: AdminSchoolTeacher @@ -184,7 +208,8 @@ def get_queryset(self): return super().get_queryset().filter(new_teacher__is_admin=False) -class NonAdminSchoolTeacherUser(User): +# pylint: disable-next=too-many-ancestors +class NonAdminSchoolTeacherUser(SchoolTeacherUser): """A user that is a non-admin-teacher in a school.""" teacher: NonAdminSchoolTeacher @@ -205,7 +230,7 @@ def get_queryset(self): return super().get_queryset().filter(new_teacher__school__isnull=True) -class NonSchoolTeacherUser(User): +class NonSchoolTeacherUser(TeacherUser): """A user that is a teacher not in a school.""" teacher: NonSchoolTeacher diff --git a/codeforlife/user/views/klass.py b/codeforlife/user/views/klass.py index c4e466e..21e7d62 100644 --- a/codeforlife/user/views/klass.py +++ b/codeforlife/user/views/klass.py @@ -30,11 +30,11 @@ def get_permissions(self): # pylint: disable-next=missing-function-docstring def get_queryset(self): - user = self.request_user + user = self.request.auth_user if user.student: return Class.objects.filter(students=user.student) - user = self.request_school_teacher_user + user = self.request.school_teacher_user if user.teacher.is_admin: return Class.objects.filter(teacher__school=user.teacher.school) diff --git a/codeforlife/user/views/school.py b/codeforlife/user/views/school.py index 1e474f8..4739ecd 100644 --- a/codeforlife/user/views/school.py +++ b/codeforlife/user/views/school.py @@ -24,12 +24,12 @@ def get_permissions(self): # pylint: disable-next=missing-function-docstring def get_queryset(self): - user = self.request_user + user = self.request.auth_user if user.student: return School.objects.filter( # TODO: should be user.student.school_id id=user.student.class_field.teacher.school_id ) - user = self.request_school_teacher_user + user = self.request.school_teacher_user return School.objects.filter(id=user.teacher.school_id) diff --git a/codeforlife/user/views/user.py b/codeforlife/user/views/user.py index 4331cca..eae4166 100644 --- a/codeforlife/user/views/user.py +++ b/codeforlife/user/views/user.py @@ -19,7 +19,7 @@ class UserViewSet(ModelViewSet[User]): # pylint: disable-next=missing-function-docstring def get_queryset(self): - user = self.request_user + user = self.request.auth_user if user.student: if user.student.class_field is None: return User.objects.filter(id=user.id) @@ -33,7 +33,7 @@ def get_queryset(self): return teachers | students - user = self.request_teacher_user + user = self.request.teacher_user if user.teacher.school: teachers = User.objects.filter( new_teacher__school=user.teacher.school_id diff --git a/codeforlife/views/__init__.py b/codeforlife/views/__init__.py index 558094e..5526b90 100644 --- a/codeforlife/views/__init__.py +++ b/codeforlife/views/__init__.py @@ -3,4 +3,6 @@ Created on 24/01/2024 at 13:07:38(+00:00). """ +from .api import APIView +from .csrf import CookieView from .model import ModelViewSet diff --git a/codeforlife/views/api.py b/codeforlife/views/api.py index 64f60d0..5dd30a9 100644 --- a/codeforlife/views/api.py +++ b/codeforlife/views/api.py @@ -3,102 +3,23 @@ Created on 05/02/2024 at 16:33:52(+00:00). """ -import typing as t - -from django.contrib.auth.models import AnonymousUser from rest_framework.views import APIView as _APIView -from ..user.models import ( - AdminSchoolTeacherUser, - IndependentUser, - NonAdminSchoolTeacherUser, - NonSchoolTeacherUser, - SchoolTeacherUser, - StudentUser, - TeacherUser, - User, -) +from ..request import Request # pylint: disable-next=missing-class-docstring class APIView(_APIView): - @property - def request_user(self): - """ - The user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(User, self.request.user) - - @property - def request_teacher_user(self): - """ - The teacher-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(TeacherUser, self.request.user) - - @property - def request_school_teacher_user(self): - """ - The school-teacher-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(SchoolTeacherUser, self.request.user) - - @property - def request_admin_school_teacher_user(self): - """ - The admin-school-teacher-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(AdminSchoolTeacherUser, self.request.user) - - @property - def request_non_admin_school_teacher_user(self): - """ - The non-admin-school-teacher-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(NonAdminSchoolTeacherUser, self.request.user) - - @property - def request_non_school_teacher_user(self): - """ - The non-school-teacher-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(NonSchoolTeacherUser, self.request.user) - - @property - def request_student_user(self): - """ - The student-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(StudentUser, self.request.user) - - @property - def request_indy_user(self): - """ - The independent-user that made the request. - Assumes the user has authenticated. - """ - - return t.cast(IndependentUser, self.request.user) - - @property - def request_anon_user(self): - """ - The user that made the request. - Assumes the user has not authenticated. - """ - - return t.cast(AnonymousUser, self.request.user) + request: Request + + def initialize_request(self, request, *args, **kwargs): + # NOTE: Call to super has side effects and is required. + super().initialize_request(request, *args, **kwargs) + + return Request( + request, + parsers=self.get_parsers(), + authenticators=self.get_authenticators(), + negotiator=self.get_content_negotiator(), + parser_context=self.get_parser_context(request), + ) diff --git a/codeforlife/views/model.py b/codeforlife/views/model.py index da7c75b..d5c94cc 100644 --- a/codeforlife/views/model.py +++ b/codeforlife/views/model.py @@ -100,6 +100,40 @@ class _ModelListSerializer( return serializer + # pylint: disable-next=useless-parent-delegation + def destroy( # type: ignore[override] + self, request: Request, *args, **kwargs + ): + return super().destroy(request, *args, **kwargs) + + # pylint: disable-next=useless-parent-delegation + def create( # type: ignore[override] + self, request: Request, *args, **kwargs + ): + return super().create(request, *args, **kwargs) + + # pylint: disable-next=useless-parent-delegation + def list(self, request: Request, *args, **kwargs): # type: ignore[override] + return super().list(request, *args, **kwargs) + + # pylint: disable-next=useless-parent-delegation + def retrieve( # type: ignore[override] + self, request: Request, *args, **kwargs + ): + return super().retrieve(request, *args, **kwargs) + + # pylint: disable-next=useless-parent-delegation + def update( # type: ignore[override] + self, request: Request, *args, **kwargs + ): + return super().update(request, *args, **kwargs) + + # pylint: disable-next=useless-parent-delegation + def partial_update( # type: ignore[override] + self, request: Request, *args, **kwargs + ): + return super().partial_update(request, *args, **kwargs) + def bulk_create(self, request: Request): """Bulk create many instances of a model.