Skip to content

Commit 0401fc4

Browse files
committed
Refactor organization member API to eliminate redundancy and optimize query for user mfa details
1 parent 024f911 commit 0401fc4

File tree

4 files changed

+28
-23
lines changed

4 files changed

+28
-23
lines changed

kobo/apps/organizations/serializers.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,17 @@ class OrganizationUserSerializer(serializers.ModelSerializer):
2121
view_name='user-kpi-detail',
2222
)
2323
role = serializers.CharField()
24-
has_mfa_enabled = serializers.SerializerMethodField()
24+
user__has_mfa_enabled = serializers.BooleanField(
25+
source='has_mfa_enabled', read_only=True
26+
)
2527
url = serializers.SerializerMethodField()
2628
date_joined = serializers.DateTimeField(
2729
source='user.date_joined', format='%Y-%m-%dT%H:%M:%SZ'
2830
)
2931
user__username = serializers.ReadOnlyField(source='user.username')
3032
user__name = serializers.ReadOnlyField(source='user.get_full_name')
3133
user__email = serializers.ReadOnlyField(source='user.email')
32-
is_active = serializers.ReadOnlyField(source='user.is_active')
34+
user__is_active = serializers.ReadOnlyField(source='user.is_active')
3335

3436
class Meta:
3537
model = OrganizationUser
@@ -40,13 +42,15 @@ class Meta:
4042
'user__email',
4143
'user__name',
4244
'role',
43-
'has_mfa_enabled',
45+
'user__has_mfa_enabled',
4446
'date_joined',
45-
'is_active'
47+
'user__is_active'
4648
]
4749

48-
def get_has_mfa_enabled(self, obj):
49-
return config.MFA_ENABLED
50+
def update(self, instance, validated_data):
51+
if role := validated_data.get('role', None):
52+
validated_data['is_admin'] = role == 'admin'
53+
return super().update(instance, validated_data)
5054

5155
def get_url(self, obj):
5256
request = self.context.get('request')

kobo/apps/organizations/tests/test_organization_members_api.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,14 @@ class OrganizationMemberAPITestCase(BaseTestCase):
1313
URL_NAMESPACE = URL_NAMESPACE
1414

1515
def setUp(self):
16-
self.organization = baker.make(Organization, id='org_12345')
16+
self.organization = baker.make(
17+
Organization, id='org_12345', mmo_override=True
18+
)
1719
self.owner_user = baker.make(User, username='owner')
1820
self.member_user = baker.make(User, username='member')
19-
self.invited_user = baker.make(User, username='invited')
2021

21-
self.organization_user_owner = baker.make(
22-
OrganizationUser,
23-
organization=self.organization,
24-
user=self.owner_user,
25-
is_admin=True,
26-
)
27-
self.organization_user_member = baker.make(
28-
OrganizationUser,
29-
organization=self.organization,
30-
user=self.member_user
31-
)
22+
self.organization.add_user(self.owner_user)
23+
self.organization.add_user(self.member_user, is_admin=False)
3224

3325
self.client.force_login(self.owner_user)
3426
self.list_url = reverse(

kobo/apps/organizations/views.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from kpi import filters
2020
from kpi.constants import ASSET_TYPE_SURVEY
2121
from kpi.models.asset import Asset
22-
from kpi.paginators import AssetUsagePagination, OrganizationPagination
22+
from kpi.paginators import AssetUsagePagination, OrganizationMemberPagination
2323
from kpi.permissions import IsAuthenticated
2424
from kpi.serializers.v2.service_usage import (
2525
CustomAssetUsageSerializer,
@@ -29,6 +29,7 @@
2929
from .models import Organization, OrganizationOwner, OrganizationUser
3030
from .permissions import IsOrgAdminOrReadOnly
3131
from .serializers import OrganizationSerializer, OrganizationUserSerializer
32+
from ..accounts.mfa.models import MfaMethod
3233
from ..stripe.constants import ACTIVE_STRIPE_STATUSES
3334

3435

@@ -336,12 +337,19 @@ class OrganizationMemberViewSet(viewsets.ModelViewSet):
336337
"""
337338
serializer_class = OrganizationUserSerializer
338339
permission_classes = [IsAuthenticated]
339-
pagination_class = OrganizationPagination
340+
pagination_class = OrganizationMemberPagination
341+
http_method_names = ['get', 'patch', 'delete']
340342
lookup_field = 'user__username'
341343

342344
def get_queryset(self):
343345
organization_id = self.kwargs['organization_id']
344346

347+
# Subquery to check if the user has an active MFA method
348+
mfa_subquery = MfaMethod.objects.filter(
349+
user=OuterRef('user_id'),
350+
is_active=True
351+
).values('pk')
352+
345353
# Subquery to check if the user is the owner
346354
owner_subquery = OrganizationOwner.objects.filter(
347355
organization_id=organization_id,
@@ -357,7 +365,8 @@ def get_queryset(self):
357365
When(is_admin=True, then=Value('admin')),
358366
default=Value('member'),
359367
output_field=CharField()
360-
)
368+
),
369+
has_mfa_enabled=Exists(mfa_subquery)
361370
)
362371
return queryset
363372

kpi/paginators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ class TinyPaginated(PageNumberPagination):
144144
page_size = 50
145145

146146

147-
class OrganizationPagination(PageNumberPagination):
147+
class OrganizationMemberPagination(PageNumberPagination):
148148
"""
149149
Pagination class for Organization
150150
"""

0 commit comments

Comments
 (0)