Skip to content

Commit 6007932

Browse files
feat: add remove team member functionality (#230)
* feat: add remove team member functionality - Introduced RemoveTeamMemberView to handle the removal of users from teams via a DELETE request. - Implemented the remove_member_from_team method in TeamService to manage the removal logic and handle exceptions. - Updated URLs to include the new endpoint for removing team members. - Added unit tests for the RemoveTeamMemberView to ensure proper functionality and error handling. This feature enhances team management capabilities by allowing for the dynamic removal of members from teams. * refactor: clean up imports in team_repository and test files - Removed unused logging import from team_repository.py to streamline the code. - Updated test_team.py to remove the import of RemoveTeamMemberView, reflecting its removal from the test scope. These changes enhance code clarity and maintainability by eliminating unnecessary dependencies. * refactor: improve code formatting and organization in team-related files - Added missing commas in import statements and OpenApiParameter definitions for consistency. - Standardized the use of whitespace in the UserTeamDetailsRepository and TeamService classes to enhance readability. - Updated test cases to reflect changes in the TeamService import structure. These adjustments contribute to cleaner code and improved maintainability across the team management functionality. --------- Co-authored-by: Amit Prakash <[email protected]>
1 parent 72a5466 commit 6007932

File tree

5 files changed

+109
-0
lines changed

5 files changed

+109
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from bson import ObjectId
2+
from todo.repositories.common.mongo_repository import MongoRepository
3+
4+
5+
class UserTeamDetailsRepository(MongoRepository):
6+
collection_name = "user_team_details"
7+
8+
@classmethod
9+
def remove_member_from_team(cls, user_id: str, team_id: str) -> bool:
10+
collection = cls.get_collection()
11+
try:
12+
user_id_obj = ObjectId(user_id)
13+
except Exception:
14+
user_id_obj = user_id
15+
try:
16+
team_id_obj = ObjectId(team_id)
17+
except Exception:
18+
team_id_obj = team_id
19+
queries = [
20+
{"user_id": user_id_obj, "team_id": team_id_obj},
21+
{"user_id": user_id, "team_id": team_id_obj},
22+
{"user_id": user_id_obj, "team_id": team_id},
23+
{"user_id": user_id, "team_id": team_id},
24+
]
25+
for query in queries:
26+
print(f"DEBUG: Trying user_team_details delete query: {query}")
27+
result = collection.delete_one(query)
28+
print(f"DEBUG: delete_one result: deleted={result.deleted_count}")
29+
if result.deleted_count > 0:
30+
return True
31+
return False

todo/services/team_service.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,15 @@ def add_team_members(cls, team_id: str, member_ids: List[str], added_by_user_id:
379379

380380
except Exception as e:
381381
raise ValueError(f"Failed to add team members: {str(e)}")
382+
383+
class TeamOrUserNotFound(Exception):
384+
pass
385+
386+
@classmethod
387+
def remove_member_from_team(cls, user_id: str, team_id: str):
388+
from todo.repositories.user_team_details_repository import UserTeamDetailsRepository
389+
390+
success = UserTeamDetailsRepository.remove_member_from_team(user_id=user_id, team_id=team_id)
391+
if not success:
392+
raise cls.TeamOrUserNotFound()
393+
return True

todo/tests/unit/views/test_team.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,34 @@ def test_join_team_by_invite_code_validation_error(self):
134134
response = self.view.post(mock_request)
135135
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
136136
self.assertIn("invite_code", response.data)
137+
138+
139+
class RemoveTeamMemberViewTests(TestCase):
140+
def setUp(self):
141+
self.client = APIClient()
142+
self.team_id = "507f1f77bcf86cd799439012"
143+
self.user_id = "507f1f77bcf86cd799439011"
144+
self.url = f"/teams/{self.team_id}/members/{self.user_id}/"
145+
146+
@patch("todo.views.team.TeamService.remove_member_from_team")
147+
def test_remove_member_success(self, mock_remove):
148+
mock_remove.return_value = True
149+
response = self.client.delete(self.url)
150+
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
151+
mock_remove.assert_called_once_with(user_id=self.user_id, team_id=self.team_id)
152+
153+
@patch("todo.views.team.TeamService.remove_member_from_team")
154+
def test_remove_member_not_found(self, mock_remove):
155+
from todo.services.team_service import TeamService
156+
157+
mock_remove.side_effect = TeamService.TeamOrUserNotFound()
158+
response = self.client.delete(self.url)
159+
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
160+
self.assertIn("not found", response.data["detail"])
161+
162+
@patch("todo.views.team.TeamService.remove_member_from_team")
163+
def test_remove_member_generic_error(self, mock_remove):
164+
mock_remove.side_effect = Exception("Something went wrong")
165+
response = self.client.delete(self.url)
166+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
167+
self.assertIn("Something went wrong", response.data["detail"])

todo/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
AddTeamMembersView,
1313
TeamInviteCodeView,
1414
TeamActivityTimelineView,
15+
RemoveTeamMemberView,
1516
)
1617
from todo.views.watchlist import WatchlistListView, WatchlistDetailView, WatchlistCheckView
1718
from todo.views.task_assignment import TaskAssignmentView, TaskAssignmentDetailView
@@ -42,3 +43,7 @@
4243
path("auth/logout", LogoutView.as_view(), name="google_logout"),
4344
path("users", UsersView.as_view(), name="users"),
4445
]
46+
47+
urlpatterns += [
48+
path("teams/<str:team_id>/members/<str:user_id>", RemoveTeamMemberView.as_view(), name="remove_team_member"),
49+
]

todo/views/team.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,3 +449,33 @@ def get(self, request: Request, team_id: str):
449449
entry["status_to"] = log.status_to
450450
timeline.append(entry)
451451
return Response({"timeline": timeline}, status=status.HTTP_200_OK)
452+
453+
454+
class RemoveTeamMemberView(APIView):
455+
@extend_schema(
456+
summary="Remove a user from a team",
457+
description="Removes the specified user from the specified team.",
458+
parameters=[
459+
OpenApiParameter(name="team_id", type=str, location=OpenApiParameter.PATH, description="ID of the team"),
460+
OpenApiParameter(
461+
name="user_id", type=str, location=OpenApiParameter.PATH, description="ID of the user to remove"
462+
),
463+
],
464+
responses={
465+
204: OpenApiResponse(description="User removed from team successfully."),
466+
404: OpenApiResponse(description="Team or user not found."),
467+
400: OpenApiResponse(description="Bad request or other error."),
468+
},
469+
tags=["teams"],
470+
)
471+
def delete(self, request, team_id, user_id):
472+
print(f"DEBUG: RemoveTeamMemberView.delete called with team_id={team_id}, user_id={user_id}")
473+
from todo.services.team_service import TeamService
474+
475+
try:
476+
TeamService.remove_member_from_team(user_id=user_id, team_id=team_id)
477+
return Response(status=status.HTTP_204_NO_CONTENT)
478+
except TeamService.TeamOrUserNotFound:
479+
return Response({"detail": "Team or user not found."}, status=status.HTTP_404_NOT_FOUND)
480+
except Exception as e:
481+
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

0 commit comments

Comments
 (0)