From 53943583a092b29a295f4b4a824b5f38d77cbd4c Mon Sep 17 00:00:00 2001 From: Raj Patel Date: Tue, 7 Jan 2025 16:11:51 +0530 Subject: [PATCH] Include invite object in the organization members endpoint --- kobo/apps/organizations/serializers.py | 38 +++++++++++++- kobo/apps/organizations/views.py | 69 ++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/kobo/apps/organizations/serializers.py b/kobo/apps/organizations/serializers.py index 5d601659d9..24e14bc48c 100644 --- a/kobo/apps/organizations/serializers.py +++ b/kobo/apps/organizations/serializers.py @@ -31,6 +31,7 @@ class OrganizationUserSerializer(serializers.ModelSerializer): + invite = serializers.SerializerMethodField() user = serializers.HyperlinkedRelatedField( queryset=get_user_model().objects.all(), lookup_field='username', @@ -62,7 +63,8 @@ class Meta: 'role', 'user__has_mfa_enabled', 'date_joined', - 'user__is_active' + 'user__is_active', + 'invite' ] def get_url(self, obj): @@ -76,6 +78,40 @@ def get_url(self, obj): request=request ) + def get_invite(self, obj): + """ + Get the latest invite for the user if it exists + """ + invite = OrganizationInvitation.objects.filter( + invitee=obj.user + ).order_by('-created').first() + + if invite: + return OrgMembershipInviteSerializer( + invite, context=self.context + ).data + return {} + + def to_representation(self, instance): + """ + Handle representation of invite objects. + + For users who have been invited to an organization but have not yet + registered, we include the invite object and show user object data as null. + """ + if isinstance(instance, OrganizationInvitation): + invite_serializer = OrgMembershipInviteSerializer( + instance, context=self.context + ) + response = {field: None for field in self.Meta.fields} + response.update({ + 'invite': invite_serializer.data, + }) + return response + else: + representation = super().to_representation(instance) + return representation + def update(self, instance, validated_data): if role := validated_data.get('role', None): validated_data['is_admin'] = role == 'admin' diff --git a/kobo/apps/organizations/views.py b/kobo/apps/organizations/views.py index aa01514ae3..31b7912dd8 100644 --- a/kobo/apps/organizations/views.py +++ b/kobo/apps/organizations/views.py @@ -293,7 +293,8 @@ class OrganizationMemberViewSet(viewsets.ModelViewSet): > "role": "owner", > "user__has_mfa_enabled": true, > "date_joined": "2024-08-11T12:36:32Z", - > "user__is_active": true + > "user__is_active": true, + > "invite": {} > }, > { > "url": "http://[kpi]/api/v2/organizations/org_12345/ \ @@ -305,8 +306,39 @@ class OrganizationMemberViewSet(viewsets.ModelViewSet): > "role": "admin", > "user__has_mfa_enabled": false, > "date_joined": "2024-10-21T06:38:45Z", - > "user__is_active": true - > } + > "user__is_active": true, + > "invite": { + > "url": "http://[kpi]/api/v2/organizations/org_12345/ + > invites/83c725f1-3f41-4f72-9657-9e6250e130e1/", + > "invited_by": "http://[kpi]/api/v2/users/raj_patel/", + > "status": "accepted", + > "invitee_role": "admin", + > "created": "2024-10-21T05:38:45Z", + > "modified": "2024-10-21T05:40:45Z", + > "invitee": "john_doe" + > } + > }, + > { + > "url": null, + > "user": null, + > "user__username": null, + > "user__email": "null, + > "user__extra_details__name": "null, + > "role": null, + > "user__has_mfa_enabled": null, + > "date_joined": null, + > "user__is_active": null, + > "invite": { + > "url": "http://[kpi]/api/v2/organizations/org_12345/ + > invites/83c725f1-3f41-4f72-9657-9e6250e130e1/", + > "invited_by": "http://[kpi]/api/v2/users/raj_patel/", + > "status": "pending", + > "invitee_role": "admin", + > "created": "2025-01-07T09:03:50Z", + > "modified": "2025-01-07T09:03:50Z", + > "invitee": "demo" + > } + > }, > ] > } @@ -429,6 +461,37 @@ def get_queryset(self): ), has_mfa_enabled=Exists(mfa_subquery) ) + + if self.action == 'list': + # Include invited users who are not yet part of this organization + invitation_queryset = OrganizationInvitation.objects.filter( + organization_id=organization_id, status='pending' + ) + + # Queryset for invited users who have registered + registered_invitees = OrganizationUser.objects.filter( + user_id__in=invitation_queryset.values('invitee_id') + ).select_related('user__extra_details').annotate( + role=Case( + When(Exists(owner_subquery), then=Value('owner')), + When(is_admin=True, then=Value('admin')), + default=Value('member'), + output_field=CharField() + ), + has_mfa_enabled=Exists(mfa_subquery) + ) + + # Queryset for invited users who have not yet registered + unregistered_invitees = invitation_queryset.filter( + invitee_id__isnull=True + ) + + combined_queryset = ( + list(queryset) + + list(registered_invitees) + + list(unregistered_invitees) + ) + return combined_queryset return queryset