Skip to content

Commit 3347936

Browse files
authored
fix: portal frontend 45 (#131)
* filter by user type * fix: include independents in queryset * fix: requesting_to_join_class
1 parent fffc0e0 commit 3347936

File tree

6 files changed

+147
-59
lines changed

6 files changed

+147
-59
lines changed

codeforlife/user/filters/user.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
)
1313

1414
from ...filters import FilterSet # isort: skip
15-
from ..models import User # isort: skip
15+
from ..models import ( # isort: skip
16+
User,
17+
TeacherUser,
18+
StudentUser,
19+
IndependentUser,
20+
)
1621

1722

1823
# pylint: disable-next=missing-class-docstring
@@ -22,14 +27,22 @@ class UserFilterSet(FilterSet):
2227
"exact",
2328
)
2429

25-
_id = filters.NumberFilter(method="_id_method")
26-
_id_method = FilterSet.make_exclude_field_list_method("id")
30+
_id = filters.NumberFilter(method="_id__method")
31+
_id__method = FilterSet.make_exclude_field_list_method("id")
2732

28-
name = filters.CharFilter(method="name_method")
33+
name = filters.CharFilter(method="name__method")
2934

30-
only_teachers = filters.BooleanFilter(method="only_teachers__method")
35+
type = filters.ChoiceFilter(
36+
choices=[
37+
("teacher", "teacher"),
38+
("student", "student"),
39+
("independent", "independent"),
40+
("indy", "independent"),
41+
],
42+
method="type__method",
43+
)
3144

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

48-
def only_teachers__method(
49-
self: FilterSet, queryset: QuerySet[User], _: str, value: bool
61+
def type__method(
62+
self: FilterSet,
63+
queryset: QuerySet[User],
64+
_: str,
65+
value: t.Literal["teacher", "student", "independent"],
5066
):
51-
"""Get only teacher-users."""
52-
return (
53-
queryset.filter(new_teacher__isnull=False, new_student__isnull=True)
54-
if value
55-
else queryset
56-
)
67+
"""Get users of a specific type."""
68+
if value == "teacher":
69+
return TeacherUser.objects.filter_users(queryset)
70+
if value == "student":
71+
return StudentUser.objects.filter_users(queryset)
72+
return IndependentUser.objects.filter_users(queryset)
5773

5874
class Meta:
5975
model = User
60-
fields = ["students_in_class", "only_teachers", "_id", "name"]
76+
fields = ["students_in_class", "type", "_id", "name"]

codeforlife/user/models/user.py

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
# pylint: disable-next=imported-auth-user
1313
from django.contrib.auth.models import User as _User
14-
from django.contrib.auth.models import UserManager
14+
from django.contrib.auth.models import UserManager as _UserManager
1515
from django.db.models import F
1616
from django.db.models.query import QuerySet
1717
from django.utils.crypto import get_random_string
@@ -157,13 +157,28 @@ def anonymize(self):
157157
AnyUser = t.TypeVar("AnyUser", bound=User)
158158

159159

160-
# pylint: disable-next=missing-class-docstring,too-few-public-methods
161-
class ContactableUserManager(UserManager[AnyUser], t.Generic[AnyUser]):
160+
# pylint: disable-next=missing-class-docstring
161+
class UserManager(_UserManager[AnyUser], t.Generic[AnyUser]):
162+
def filter_users(self, queryset: QuerySet[User]):
163+
"""Filter the users to the specific type.
164+
165+
Args:
166+
queryset: The queryset of users to filter.
167+
168+
Returns:
169+
A subset of the queryset of users.
170+
"""
171+
return queryset
172+
162173
# pylint: disable-next=missing-function-docstring
163174
def get_queryset(self):
164-
return (
165-
super().get_queryset().exclude(email__isnull=True).exclude(email="")
166-
)
175+
return self.filter_users(super().get_queryset())
176+
177+
178+
# pylint: disable-next=missing-class-docstring,too-few-public-methods
179+
class ContactableUserManager(UserManager[AnyUser], t.Generic[AnyUser]):
180+
def filter_users(self, queryset: QuerySet[User]):
181+
return queryset.exclude(email__isnull=True).exclude(email="")
167182

168183

169184
class ContactableUser(User):
@@ -238,15 +253,16 @@ def create_user( # type: ignore[override]
238253

239254
return user
240255

241-
# pylint: disable-next=missing-function-docstring
242-
def get_queryset(self):
256+
def filter_users(self, queryset: QuerySet[User]):
243257
return (
244258
super()
245-
.get_queryset()
259+
.filter_users(queryset)
246260
.filter(new_teacher__isnull=False, new_student__isnull=True)
247-
.prefetch_related("new_teacher")
248261
)
249262

263+
def get_queryset(self):
264+
return super().get_queryset().prefetch_related("new_teacher")
265+
250266

251267
class TeacherUser(ContactableUser):
252268
"""A user that is a teacher."""
@@ -287,9 +303,12 @@ def create_user( # type: ignore[override]
287303
**extra_fields,
288304
)
289305

290-
# pylint: disable-next=missing-function-docstring
291-
def get_queryset(self):
292-
return super().get_queryset().filter(new_teacher__school__isnull=False)
306+
def filter_users(self, queryset: QuerySet[User]):
307+
return (
308+
super()
309+
.filter_users(queryset)
310+
.filter(new_teacher__school__isnull=False)
311+
)
293312

294313

295314
# pylint: disable-next=too-many-ancestors
@@ -317,9 +336,8 @@ def teacher(self):
317336
class AdminSchoolTeacherUserManager(
318337
SchoolTeacherUserManager["AdminSchoolTeacherUser"]
319338
):
320-
# pylint: disable-next=missing-function-docstring
321-
def get_queryset(self):
322-
return super().get_queryset().filter(new_teacher__is_admin=True)
339+
def filter_users(self, queryset: QuerySet[User]):
340+
return super().filter_users(queryset).filter(new_teacher__is_admin=True)
323341

324342

325343
# pylint: disable-next=too-many-ancestors
@@ -347,9 +365,10 @@ def teacher(self):
347365
class NonAdminSchoolTeacherUserManager(
348366
SchoolTeacherUserManager["NonAdminSchoolTeacherUser"]
349367
):
350-
# pylint: disable-next=missing-function-docstring
351-
def get_queryset(self):
352-
return super().get_queryset().filter(new_teacher__is_admin=False)
368+
def filter_users(self, queryset: QuerySet[User]):
369+
return (
370+
super().filter_users(queryset).filter(new_teacher__is_admin=False)
371+
)
353372

354373

355374
# pylint: disable-next=too-many-ancestors
@@ -377,9 +396,12 @@ def teacher(self):
377396

378397
# pylint: disable-next=missing-class-docstring,too-few-public-methods
379398
class NonSchoolTeacherUserManager(TeacherUserManager["NonSchoolTeacherUser"]):
380-
# pylint: disable-next=missing-function-docstring
381-
def get_queryset(self):
382-
return super().get_queryset().filter(new_teacher__school__isnull=True)
399+
def filter_users(self, queryset: QuerySet[User]):
400+
return (
401+
super()
402+
.filter_users(queryset)
403+
.filter(new_teacher__school__isnull=True)
404+
)
383405

384406

385407
# pylint: disable-next=too-many-ancestors
@@ -440,20 +462,17 @@ def create_user( # type: ignore[override]
440462

441463
return user
442464

443-
# pylint: disable-next=missing-function-docstring
444-
def get_queryset(self):
445-
return (
446-
super()
447-
.get_queryset()
448-
.filter(
449-
new_teacher__isnull=True,
450-
new_student__isnull=False,
451-
# TODO: remove in new model
452-
new_student__class_field__isnull=False,
453-
)
454-
.prefetch_related("new_student")
465+
def filter_users(self, queryset: QuerySet[User]):
466+
return queryset.filter(
467+
new_teacher__isnull=True,
468+
new_student__isnull=False,
469+
# TODO: remove in new model
470+
new_student__class_field__isnull=False,
455471
)
456472

473+
def get_queryset(self):
474+
return super().get_queryset().prefetch_related("new_student")
475+
457476

458477
class StudentUser(User):
459478
"""A user that is a student."""
@@ -509,20 +528,21 @@ def set_password(self, raw_password: t.Optional[str] = None):
509528

510529
# pylint: disable-next=missing-class-docstring,too-few-public-methods
511530
class IndependentUserManager(ContactableUserManager["IndependentUser"]):
512-
# pylint: disable-next=missing-function-docstring
513-
def get_queryset(self):
514-
# TODO: student__isnull=True in new model
531+
def filter_users(self, queryset: QuerySet[User]):
515532
return (
516533
super()
517-
.get_queryset()
534+
.filter_users(queryset)
518535
.filter(
519536
new_teacher__isnull=True,
537+
# TODO: student__isnull=True in new model
520538
new_student__isnull=False,
521539
new_student__class_field__isnull=True,
522540
)
523-
.prefetch_related("new_student")
524541
)
525542

543+
def get_queryset(self):
544+
return super().get_queryset().prefetch_related("new_student")
545+
526546
def create_user( # type: ignore[override]
527547
self,
528548
first_name: str,

codeforlife/user/serializers/user.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ def to_representation(self, instance):
7171
except Student.DoesNotExist:
7272
student = None
7373

74+
try:
75+
requesting_to_join_class = (
76+
instance.new_student.pending_class_request.access_code
77+
if instance.new_student
78+
and instance.new_student.pending_class_request
79+
else None
80+
)
81+
except Student.DoesNotExist:
82+
requesting_to_join_class = None
83+
7484
try:
7585
teacher = (
7686
TeacherSerializer[Teacher](instance.new_teacher).data
@@ -87,6 +97,7 @@ def to_representation(self, instance):
8797
"email": instance.email,
8898
"is_active": instance.is_active,
8999
"date_joined": instance.date_joined,
100+
"requesting_to_join_class": requesting_to_join_class,
90101
"student": student,
91102
"teacher": teacher,
92103
}

codeforlife/user/serializers/user_test.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def test_to_representation__teacher(self):
2222
self.assert_to_representation(
2323
user,
2424
new_data={
25+
"requesting_to_join_class": None,
2526
"teacher": {
2627
"id": user.teacher.id,
2728
"school": user.teacher.school.id,
@@ -39,6 +40,7 @@ def test_to_representation__student(self):
3940
self.assert_to_representation(
4041
user,
4142
new_data={
43+
"requesting_to_join_class": None,
4244
"teacher": None,
4345
"student": {
4446
"id": user.student.id,
@@ -55,5 +57,9 @@ def test_to_representation__indy(self):
5557

5658
self.assert_to_representation(
5759
user,
58-
new_data={"teacher": None, "student": None},
60+
new_data={
61+
"requesting_to_join_class": None,
62+
"teacher": None,
63+
"student": None,
64+
},
5965
)

codeforlife/user/views/user.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,19 @@ def get_queryset(
5454
new_student__class_field__teacher=user.teacher
5555
)
5656
)
57+
independents = (
58+
user_class.objects.filter(
59+
new_student__pending_class_request__teacher__school=(
60+
user.teacher.school_id
61+
)
62+
)
63+
if user.teacher.is_admin
64+
else user_class.objects.filter(
65+
new_student__pending_class_request__teacher=user.teacher
66+
)
67+
)
5768

58-
return teachers | students
69+
return teachers | students | independents
5970

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

codeforlife/user/views/user_test.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
class TestUserViewSet(ModelViewSetTestCase[RequestUser, User]):
2929
basename = "user"
3030
model_view_set_class = UserViewSet
31-
fixtures = ["non_school_teacher", "school_1"]
31+
fixtures = ["non_school_teacher", "school_1", "independent"]
3232

3333
def setUp(self):
3434
self.admin_school_teacher_user = AdminSchoolTeacherUser.objects.get(
@@ -160,7 +160,7 @@ def test_list__students_in_class(self):
160160
filters={"students_in_class": klass.access_code},
161161
)
162162

163-
def test_list__only_teachers(self):
163+
def test_list__type__teacher(self):
164164
"""Can successfully list only teacher-users."""
165165
user = self.admin_school_teacher_user
166166
school_teacher_users = user.teacher.school_teacher_users.all()
@@ -169,7 +169,31 @@ def test_list__only_teachers(self):
169169
self.client.login_as(user)
170170
self.client.list(
171171
models=school_teacher_users,
172-
filters={"only_teachers": str(True)},
172+
filters={"type": "teacher"},
173+
)
174+
175+
def test_list__type__student(self):
176+
"""Can successfully list only student-users."""
177+
user = self.admin_school_teacher_user
178+
student_users = user.teacher.student_users.all()
179+
assert student_users.exists()
180+
181+
self.client.login_as(user)
182+
self.client.list(
183+
models=student_users,
184+
filters={"type": "student"},
185+
)
186+
187+
def test_list__type__indy(self):
188+
"""Can successfully list only independent-users."""
189+
user = self.admin_school_teacher_user
190+
indy_users = user.teacher.indy_users.all()
191+
assert indy_users.exists()
192+
193+
self.client.login_as(user)
194+
self.client.list(
195+
models=indy_users,
196+
filters={"type": "indy"},
173197
)
174198

175199
def test_list___id(self):

0 commit comments

Comments
 (0)