Skip to content

Commit

Permalink
fix: portal frontend 45 (#131)
Browse files Browse the repository at this point in the history
* filter by user type

* fix: include independents in queryset

* fix: requesting_to_join_class
  • Loading branch information
SKairinos authored Sep 2, 2024
1 parent fffc0e0 commit 3347936
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 59 deletions.
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 @@ def anonymize(self):
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

# 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 @@ def create_user( # type: ignore[override]

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 @@ def create_user( # type: ignore[override]
**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 @@ def teacher(self):
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 @@ def teacher(self):
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 @@ def teacher(self):

# 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 @@ def create_user( # type: ignore[override]

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 @@ def set_password(self, raw_password: t.Optional[str] = None):

# 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

0 comments on commit 3347936

Please sign in to comment.