Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: portal frontend 45 #131

Merged
merged 3 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 31 additions & 15 deletions codeforlife/user/filters/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
)

from ...filters import FilterSet # isort: skip
from ..models import User # isort: skip
from ..models import ( # isort: skip
User,
TeacherUser,
StudentUser,
IndependentUser,
)


# pylint: disable-next=missing-class-docstring
Expand All @@ -22,14 +27,22 @@ class UserFilterSet(FilterSet):
"exact",
)

_id = filters.NumberFilter(method="_id_method")
_id_method = FilterSet.make_exclude_field_list_method("id")
_id = filters.NumberFilter(method="_id__method")
_id__method = FilterSet.make_exclude_field_list_method("id")

name = filters.CharFilter(method="name_method")
name = filters.CharFilter(method="name__method")

only_teachers = filters.BooleanFilter(method="only_teachers__method")
type = filters.ChoiceFilter(
choices=[
("teacher", "teacher"),
("student", "student"),
("independent", "independent"),
("indy", "independent"),
],
method="type__method",
)

def name_method(
def name__method(
self: FilterSet, queryset: QuerySet[User], name: str, *args
):
"""Get all first names and last names that contain a substring."""
Expand All @@ -45,16 +58,19 @@ def name_method(
| Q(last_name__icontains=last_name)
)

def only_teachers__method(
self: FilterSet, queryset: QuerySet[User], _: str, value: bool
def type__method(
self: FilterSet,
queryset: QuerySet[User],
_: str,
value: t.Literal["teacher", "student", "independent"],
):
"""Get only teacher-users."""
return (
queryset.filter(new_teacher__isnull=False, new_student__isnull=True)
if value
else queryset
)
"""Get users of a specific type."""
if value == "teacher":
return TeacherUser.objects.filter_users(queryset)
if value == "student":
return StudentUser.objects.filter_users(queryset)
return IndependentUser.objects.filter_users(queryset)

class Meta:
model = User
fields = ["students_in_class", "only_teachers", "_id", "name"]
fields = ["students_in_class", "type", "_id", "name"]
98 changes: 59 additions & 39 deletions codeforlife/user/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

# pylint: disable-next=imported-auth-user
from django.contrib.auth.models import User as _User
from django.contrib.auth.models import UserManager
from django.contrib.auth.models import UserManager as _UserManager
from django.db.models import F
from django.db.models.query import QuerySet
from django.utils.crypto import get_random_string
Expand Down Expand Up @@ -157,13 +157,28 @@
AnyUser = t.TypeVar("AnyUser", bound=User)


# pylint: disable-next=missing-class-docstring,too-few-public-methods
class ContactableUserManager(UserManager[AnyUser], t.Generic[AnyUser]):
# pylint: disable-next=missing-class-docstring
class UserManager(_UserManager[AnyUser], t.Generic[AnyUser]):
def filter_users(self, queryset: QuerySet[User]):
"""Filter the users to the specific type.

Args:
queryset: The queryset of users to filter.

Returns:
A subset of the queryset of users.
"""
return queryset

Check warning on line 171 in codeforlife/user/models/user.py

View check run for this annotation

Codecov / codecov/patch

codeforlife/user/models/user.py#L171

Added line #L171 was not covered by tests

# pylint: disable-next=missing-function-docstring
def get_queryset(self):
return (
super().get_queryset().exclude(email__isnull=True).exclude(email="")
)
return self.filter_users(super().get_queryset())


# pylint: disable-next=missing-class-docstring,too-few-public-methods
class ContactableUserManager(UserManager[AnyUser], t.Generic[AnyUser]):
def filter_users(self, queryset: QuerySet[User]):
return queryset.exclude(email__isnull=True).exclude(email="")


class ContactableUser(User):
Expand Down Expand Up @@ -238,15 +253,16 @@

return user

# pylint: disable-next=missing-function-docstring
def get_queryset(self):
def filter_users(self, queryset: QuerySet[User]):
return (
super()
.get_queryset()
.filter_users(queryset)
.filter(new_teacher__isnull=False, new_student__isnull=True)
.prefetch_related("new_teacher")
)

def get_queryset(self):
return super().get_queryset().prefetch_related("new_teacher")


class TeacherUser(ContactableUser):
"""A user that is a teacher."""
Expand Down Expand Up @@ -287,9 +303,12 @@
**extra_fields,
)

# pylint: disable-next=missing-function-docstring
def get_queryset(self):
return super().get_queryset().filter(new_teacher__school__isnull=False)
def filter_users(self, queryset: QuerySet[User]):
return (
super()
.filter_users(queryset)
.filter(new_teacher__school__isnull=False)
)


# pylint: disable-next=too-many-ancestors
Expand Down Expand Up @@ -317,9 +336,8 @@
class AdminSchoolTeacherUserManager(
SchoolTeacherUserManager["AdminSchoolTeacherUser"]
):
# pylint: disable-next=missing-function-docstring
def get_queryset(self):
return super().get_queryset().filter(new_teacher__is_admin=True)
def filter_users(self, queryset: QuerySet[User]):
return super().filter_users(queryset).filter(new_teacher__is_admin=True)


# pylint: disable-next=too-many-ancestors
Expand Down Expand Up @@ -347,9 +365,10 @@
class NonAdminSchoolTeacherUserManager(
SchoolTeacherUserManager["NonAdminSchoolTeacherUser"]
):
# pylint: disable-next=missing-function-docstring
def get_queryset(self):
return super().get_queryset().filter(new_teacher__is_admin=False)
def filter_users(self, queryset: QuerySet[User]):
return (
super().filter_users(queryset).filter(new_teacher__is_admin=False)
)


# pylint: disable-next=too-many-ancestors
Expand Down Expand Up @@ -377,9 +396,12 @@

# pylint: disable-next=missing-class-docstring,too-few-public-methods
class NonSchoolTeacherUserManager(TeacherUserManager["NonSchoolTeacherUser"]):
# pylint: disable-next=missing-function-docstring
def get_queryset(self):
return super().get_queryset().filter(new_teacher__school__isnull=True)
def filter_users(self, queryset: QuerySet[User]):
return (
super()
.filter_users(queryset)
.filter(new_teacher__school__isnull=True)
)


# pylint: disable-next=too-many-ancestors
Expand Down Expand Up @@ -440,20 +462,17 @@

return user

# pylint: disable-next=missing-function-docstring
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
new_teacher__isnull=True,
new_student__isnull=False,
# TODO: remove in new model
new_student__class_field__isnull=False,
)
.prefetch_related("new_student")
def filter_users(self, queryset: QuerySet[User]):
return queryset.filter(
new_teacher__isnull=True,
new_student__isnull=False,
# TODO: remove in new model
new_student__class_field__isnull=False,
)

def get_queryset(self):
return super().get_queryset().prefetch_related("new_student")


class StudentUser(User):
"""A user that is a student."""
Expand Down Expand Up @@ -509,20 +528,21 @@

# pylint: disable-next=missing-class-docstring,too-few-public-methods
class IndependentUserManager(ContactableUserManager["IndependentUser"]):
# pylint: disable-next=missing-function-docstring
def get_queryset(self):
# TODO: student__isnull=True in new model
def filter_users(self, queryset: QuerySet[User]):
return (
super()
.get_queryset()
.filter_users(queryset)
.filter(
new_teacher__isnull=True,
# TODO: student__isnull=True in new model
new_student__isnull=False,
new_student__class_field__isnull=True,
)
.prefetch_related("new_student")
)

def get_queryset(self):
return super().get_queryset().prefetch_related("new_student")

def create_user( # type: ignore[override]
self,
first_name: str,
Expand Down
11 changes: 11 additions & 0 deletions codeforlife/user/serializers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,16 @@ def to_representation(self, instance):
except Student.DoesNotExist:
student = None

try:
requesting_to_join_class = (
instance.new_student.pending_class_request.access_code
if instance.new_student
and instance.new_student.pending_class_request
else None
)
except Student.DoesNotExist:
requesting_to_join_class = None

try:
teacher = (
TeacherSerializer[Teacher](instance.new_teacher).data
Expand All @@ -87,6 +97,7 @@ def to_representation(self, instance):
"email": instance.email,
"is_active": instance.is_active,
"date_joined": instance.date_joined,
"requesting_to_join_class": requesting_to_join_class,
"student": student,
"teacher": teacher,
}
8 changes: 7 additions & 1 deletion codeforlife/user/serializers/user_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def test_to_representation__teacher(self):
self.assert_to_representation(
user,
new_data={
"requesting_to_join_class": None,
"teacher": {
"id": user.teacher.id,
"school": user.teacher.school.id,
Expand All @@ -39,6 +40,7 @@ def test_to_representation__student(self):
self.assert_to_representation(
user,
new_data={
"requesting_to_join_class": None,
"teacher": None,
"student": {
"id": user.student.id,
Expand All @@ -55,5 +57,9 @@ def test_to_representation__indy(self):

self.assert_to_representation(
user,
new_data={"teacher": None, "student": None},
new_data={
"requesting_to_join_class": None,
"teacher": None,
"student": None,
},
)
13 changes: 12 additions & 1 deletion codeforlife/user/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,19 @@ def get_queryset(
new_student__class_field__teacher=user.teacher
)
)
independents = (
user_class.objects.filter(
new_student__pending_class_request__teacher__school=(
user.teacher.school_id
)
)
if user.teacher.is_admin
else user_class.objects.filter(
new_student__pending_class_request__teacher=user.teacher
)
)

return teachers | students
return teachers | students | independents

return user_class.objects.filter(pk=user.pk)

Expand Down
30 changes: 27 additions & 3 deletions codeforlife/user/views/user_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
class TestUserViewSet(ModelViewSetTestCase[RequestUser, User]):
basename = "user"
model_view_set_class = UserViewSet
fixtures = ["non_school_teacher", "school_1"]
fixtures = ["non_school_teacher", "school_1", "independent"]

def setUp(self):
self.admin_school_teacher_user = AdminSchoolTeacherUser.objects.get(
Expand Down Expand Up @@ -160,7 +160,7 @@ def test_list__students_in_class(self):
filters={"students_in_class": klass.access_code},
)

def test_list__only_teachers(self):
def test_list__type__teacher(self):
"""Can successfully list only teacher-users."""
user = self.admin_school_teacher_user
school_teacher_users = user.teacher.school_teacher_users.all()
Expand All @@ -169,7 +169,31 @@ def test_list__only_teachers(self):
self.client.login_as(user)
self.client.list(
models=school_teacher_users,
filters={"only_teachers": str(True)},
filters={"type": "teacher"},
)

def test_list__type__student(self):
"""Can successfully list only student-users."""
user = self.admin_school_teacher_user
student_users = user.teacher.student_users.all()
assert student_users.exists()

self.client.login_as(user)
self.client.list(
models=student_users,
filters={"type": "student"},
)

def test_list__type__indy(self):
"""Can successfully list only independent-users."""
user = self.admin_school_teacher_user
indy_users = user.teacher.indy_users.all()
assert indy_users.exists()

self.client.login_as(user)
self.client.list(
models=indy_users,
filters={"type": "indy"},
)

def test_list___id(self):
Expand Down