Skip to content

Commit 89d0a3f

Browse files
refactor: add status filtering to tasks API with DONE exclusion by default
- Exclude DONE tasks from GET /v1/tasks by default for cleaner active task view - Add ?status=DONE query parameter to specifically retrieve DONE tasks - Implement status filtering across all task endpoints: - GET /v1/tasks - general tasks with status filtering - GET /v1/tasks?profile=true - user's tasks with status filtering - GET /v1/tasks?teamId={id} - team tasks with status filtering - Fix get_tasks_for_user to include both created AND assigned tasks - Update repository layer (list, count, get_tasks_for_user) with status_filter parameter - Update service and view layers to pass status filter from query params - Update serializer to accept status query parameter - Fix all test assertions to match new method signatures
1 parent fe9f2e1 commit 89d0a3f

File tree

8 files changed

+169
-46
lines changed

8 files changed

+169
-46
lines changed

todo/repositories/task_repository.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@ class TaskRepository(MongoRepository):
1717

1818
@classmethod
1919
def list(
20-
cls, page: int, limit: int, sort_by: str, order: str, user_id: str = None, team_id: str = None
20+
cls,
21+
page: int,
22+
limit: int,
23+
sort_by: str,
24+
order: str,
25+
user_id: str = None,
26+
team_id: str = None,
27+
status_filter: str = None,
2128
) -> List[TaskModel]:
2229
tasks_collection = cls.get_collection()
2330
logger = logging.getLogger(__name__)
2431

25-
base_filter = {"status": {"$ne": TaskStatus.DONE.value}}
32+
if status_filter:
33+
base_filter = {"status": status_filter}
34+
else:
35+
base_filter = {"status": {"$ne": TaskStatus.DONE.value}}
2636

2737
if team_id:
2838
logger.debug(f"TaskRepository.list: team_id={team_id}")
@@ -72,10 +82,13 @@ def _get_assigned_task_ids_for_user(cls, user_id: str) -> List[ObjectId]:
7282
return direct_task_ids + team_task_ids
7383

7484
@classmethod
75-
def count(cls, user_id: str = None, team_id: str = None) -> int:
85+
def count(cls, user_id: str = None, team_id: str = None, status_filter: str = None) -> int:
7686
tasks_collection = cls.get_collection()
7787

78-
base_filter = {"status": {"$ne": TaskStatus.DONE.value}}
88+
if status_filter:
89+
base_filter = {"status": status_filter}
90+
else:
91+
base_filter = {"status": {"$ne": TaskStatus.DONE.value}}
7992

8093
if team_id:
8194
team_assignments = TaskAssignmentRepository.get_by_assignee_id(team_id, "team")
@@ -215,11 +228,16 @@ def update(cls, task_id: str, update_data: dict) -> TaskModel | None:
215228
return None
216229

217230
@classmethod
218-
def get_tasks_for_user(cls, user_id: str, page: int, limit: int) -> List[TaskModel]:
231+
def get_tasks_for_user(cls, user_id: str, page: int, limit: int, status_filter: str = None) -> List[TaskModel]:
219232
tasks_collection = cls.get_collection()
220233
assigned_task_ids = cls._get_assigned_task_ids_for_user(user_id)
221234

222-
query = {"$and": [{"status": {"$ne": TaskStatus.DONE.value}}, {"_id": {"$in": assigned_task_ids}}]}
235+
if status_filter:
236+
base_filter = {"status": status_filter}
237+
else:
238+
base_filter = {"status": {"$ne": TaskStatus.DONE.value}}
239+
240+
query = {"$and": [base_filter, {"$or": [{"createdBy": user_id}, {"_id": {"$in": assigned_task_ids}}]}]}
223241
tasks_cursor = tasks_collection.find(query).skip((page - 1) * limit).limit(limit)
224242
return [TaskModel(**task) for task in tasks_cursor]
225243

todo/serializers/get_tasks_serializer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class GetTaskQueryParamsSerializer(serializers.Serializer):
3737

3838
teamId = serializers.CharField(required=False, allow_blank=False, allow_null=True)
3939

40+
status = serializers.CharField(required=False, allow_blank=False, allow_null=True)
41+
4042
def validate(self, attrs):
4143
validated_data = super().validate(attrs)
4244

todo/services/task_service.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def get_tasks(
7171
order: str,
7272
user_id: str,
7373
team_id: str = None,
74+
status_filter: str = None,
7475
) -> GetTasksResponse:
7576
try:
7677
cls._validate_pagination_params(page, limit)
@@ -89,8 +90,10 @@ def get_tasks(
8990
},
9091
)
9192

92-
tasks = TaskRepository.list(page, limit, sort_by, order, user_id, team_id=team_id)
93-
total_count = TaskRepository.count(user_id, team_id=team_id)
93+
tasks = TaskRepository.list(
94+
page, limit, sort_by, order, user_id, team_id=team_id, status_filter=status_filter
95+
)
96+
total_count = TaskRepository.count(user_id, team_id=team_id, status_filter=status_filter)
9497

9598
if not tasks:
9699
return GetTasksResponse(tasks=[], links=None)
@@ -665,9 +668,10 @@ def get_tasks_for_user(
665668
user_id: str,
666669
page: int = PaginationConfig.DEFAULT_PAGE,
667670
limit: int = PaginationConfig.DEFAULT_LIMIT,
671+
status_filter: str = None,
668672
) -> GetTasksResponse:
669673
cls._validate_pagination_params(page, limit)
670-
tasks = TaskRepository.get_tasks_for_user(user_id, page, limit)
674+
tasks = TaskRepository.get_tasks_for_user(user_id, page, limit, status_filter=status_filter)
671675
if not tasks:
672676
return GetTasksResponse(tasks=[], links=None)
673677

todo/tests/integration/test_task_sorting_integration.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ def test_priority_sorting_integration(self, mock_list, mock_count):
2424
response = self.client.get("/v1/tasks", {"sort_by": "priority", "order": "desc"})
2525

2626
self.assertEqual(response.status_code, status.HTTP_200_OK)
27-
mock_list.assert_called_with(1, 20, SORT_FIELD_PRIORITY, SORT_ORDER_DESC, str(self.user_id), team_id=None)
27+
mock_list.assert_called_with(
28+
1, 20, SORT_FIELD_PRIORITY, SORT_ORDER_DESC, str(self.user_id), team_id=None, status_filter=None
29+
)
2830

2931
@patch("todo.repositories.task_repository.TaskRepository.count")
3032
@patch("todo.repositories.task_repository.TaskRepository.list")
@@ -36,7 +38,9 @@ def test_due_at_default_order_integration(self, mock_list, mock_count):
3638

3739
self.assertEqual(response.status_code, status.HTTP_200_OK)
3840

39-
mock_list.assert_called_with(1, 20, SORT_FIELD_DUE_AT, SORT_ORDER_ASC, str(self.user_id), team_id=None)
41+
mock_list.assert_called_with(
42+
1, 20, SORT_FIELD_DUE_AT, SORT_ORDER_ASC, str(self.user_id), team_id=None, status_filter=None
43+
)
4044

4145
@patch("todo.repositories.task_repository.TaskRepository.count")
4246
@patch("todo.repositories.task_repository.TaskRepository.list")
@@ -49,7 +53,9 @@ def test_assignee_sorting_uses_aggregation(self, mock_list, mock_count):
4953
self.assertEqual(response.status_code, status.HTTP_200_OK)
5054

5155
# Assignee sorting now falls back to createdAt sorting
52-
mock_list.assert_called_once_with(1, 20, SORT_FIELD_ASSIGNEE, SORT_ORDER_ASC, str(self.user_id), team_id=None)
56+
mock_list.assert_called_once_with(
57+
1, 20, SORT_FIELD_ASSIGNEE, SORT_ORDER_ASC, str(self.user_id), team_id=None, status_filter=None
58+
)
5359

5460
@patch("todo.repositories.task_repository.TaskRepository.count")
5561
@patch("todo.repositories.task_repository.TaskRepository.list")
@@ -72,7 +78,9 @@ def test_field_specific_defaults_integration(self, mock_list, mock_count):
7278
response = self.client.get("/v1/tasks", {"sort_by": sort_field})
7379

7480
self.assertEqual(response.status_code, status.HTTP_200_OK)
75-
mock_list.assert_called_with(1, 20, sort_field, expected_order, str(self.user_id), team_id=None)
81+
mock_list.assert_called_with(
82+
1, 20, sort_field, expected_order, str(self.user_id), team_id=None, status_filter=None
83+
)
7684

7785
@patch("todo.repositories.task_repository.TaskRepository.count")
7886
@patch("todo.repositories.task_repository.TaskRepository.list")
@@ -84,7 +92,9 @@ def test_pagination_with_sorting_integration(self, mock_list, mock_count):
8492

8593
self.assertEqual(response.status_code, status.HTTP_200_OK)
8694

87-
mock_list.assert_called_with(3, 5, SORT_FIELD_CREATED_AT, SORT_ORDER_ASC, str(self.user_id), team_id=None)
95+
mock_list.assert_called_with(
96+
3, 5, SORT_FIELD_CREATED_AT, SORT_ORDER_ASC, str(self.user_id), team_id=None, status_filter=None
97+
)
8898

8999
def test_invalid_sort_parameters_integration(self):
90100
response = self.client.get("/v1/tasks", {"sort_by": "invalid_field"})
@@ -103,7 +113,9 @@ def test_default_behavior_integration(self, mock_list, mock_count):
103113

104114
self.assertEqual(response.status_code, status.HTTP_200_OK)
105115

106-
mock_list.assert_called_with(1, 20, SORT_FIELD_CREATED_AT, SORT_ORDER_DESC, str(self.user_id), team_id=None)
116+
mock_list.assert_called_with(
117+
1, 20, SORT_FIELD_CREATED_AT, SORT_ORDER_DESC, str(self.user_id), team_id=None, status_filter=None
118+
)
107119

108120
@patch("todo.services.task_service.reverse_lazy", return_value="/v1/tasks")
109121
@patch("todo.repositories.task_repository.TaskRepository.count")

todo/tests/integration/test_tasks_pagination.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ def test_pagination_settings_integration(self, mock_get_tasks):
2121

2222
self.assertEqual(response.status_code, 200)
2323
mock_get_tasks.assert_called_with(
24-
page=1, limit=default_limit, sort_by="createdAt", order="desc", user_id=str(self.user_id), team_id=None
24+
page=1,
25+
limit=default_limit,
26+
sort_by="createdAt",
27+
order="desc",
28+
user_id=str(self.user_id),
29+
team_id=None,
30+
status_filter=None,
2531
)
2632

2733
mock_get_tasks.reset_mock()
@@ -30,7 +36,13 @@ def test_pagination_settings_integration(self, mock_get_tasks):
3036

3137
self.assertEqual(response.status_code, 200)
3238
mock_get_tasks.assert_called_with(
33-
page=1, limit=10, sort_by="createdAt", order="desc", user_id=str(self.user_id), team_id=None
39+
page=1,
40+
limit=10,
41+
sort_by="createdAt",
42+
order="desc",
43+
user_id=str(self.user_id),
44+
team_id=None,
45+
status_filter=None,
3446
)
3547

3648
# Verify API rejects values above max limit

todo/tests/unit/services/test_task_service.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ def test_get_tasks_returns_paginated_response(
7171
response.links.prev, f"{self.mock_reverse_lazy('tasks')}?page=1&limit=1&sort_by=createdAt&order=desc"
7272
)
7373

74-
mock_list.assert_called_once_with(2, 1, "createdAt", "desc", str(self.user_id), team_id=None)
74+
mock_list.assert_called_once_with(
75+
2, 1, "createdAt", "desc", str(self.user_id), team_id=None, status_filter=None
76+
)
7577
mock_count.assert_called_once()
7678

7779
@patch("todo.services.task_service.UserRepository.get_by_id")
@@ -111,7 +113,7 @@ def test_get_tasks_returns_empty_response_if_no_tasks_present(self, mock_list: M
111113
self.assertEqual(len(response.tasks), 0)
112114
self.assertIsNone(response.links)
113115

114-
mock_list.assert_called_once_with(1, 10, "createdAt", "desc", "test_user", team_id=None)
116+
mock_list.assert_called_once_with(1, 10, "createdAt", "desc", "test_user", team_id=None, status_filter=None)
115117
mock_count.assert_called_once()
116118

117119
@patch("todo.services.task_service.TaskRepository.count")
@@ -294,7 +296,9 @@ def test_get_tasks_default_sorting(self, mock_list, mock_count):
294296

295297
TaskService.get_tasks(page=1, limit=20, sort_by="createdAt", order="desc", user_id="test_user")
296298

297-
mock_list.assert_called_once_with(1, 20, SORT_FIELD_CREATED_AT, SORT_ORDER_DESC, "test_user", team_id=None)
299+
mock_list.assert_called_once_with(
300+
1, 20, SORT_FIELD_CREATED_AT, SORT_ORDER_DESC, "test_user", team_id=None, status_filter=None
301+
)
298302

299303
@patch("todo.services.task_service.TaskRepository.count")
300304
@patch("todo.services.task_service.TaskRepository.list")
@@ -304,7 +308,9 @@ def test_get_tasks_explicit_sort_by_priority(self, mock_list, mock_count):
304308

305309
TaskService.get_tasks(page=1, limit=20, sort_by=SORT_FIELD_PRIORITY, order=SORT_ORDER_DESC, user_id="test_user")
306310

307-
mock_list.assert_called_once_with(1, 20, SORT_FIELD_PRIORITY, SORT_ORDER_DESC, "test_user", team_id=None)
311+
mock_list.assert_called_once_with(
312+
1, 20, SORT_FIELD_PRIORITY, SORT_ORDER_DESC, "test_user", team_id=None, status_filter=None
313+
)
308314

309315
@patch("todo.services.task_service.TaskRepository.count")
310316
@patch("todo.services.task_service.TaskRepository.list")
@@ -314,7 +320,9 @@ def test_get_tasks_sort_by_due_at_default_order(self, mock_list, mock_count):
314320

315321
TaskService.get_tasks(page=1, limit=20, sort_by=SORT_FIELD_DUE_AT, order="asc", user_id="test_user")
316322

317-
mock_list.assert_called_once_with(1, 20, SORT_FIELD_DUE_AT, SORT_ORDER_ASC, "test_user", team_id=None)
323+
mock_list.assert_called_once_with(
324+
1, 20, SORT_FIELD_DUE_AT, SORT_ORDER_ASC, "test_user", team_id=None, status_filter=None
325+
)
318326

319327
@patch("todo.services.task_service.TaskRepository.count")
320328
@patch("todo.services.task_service.TaskRepository.list")
@@ -324,7 +332,9 @@ def test_get_tasks_sort_by_priority_default_order(self, mock_list, mock_count):
324332

325333
TaskService.get_tasks(page=1, limit=20, sort_by=SORT_FIELD_PRIORITY, order="desc", user_id="test_user")
326334

327-
mock_list.assert_called_once_with(1, 20, SORT_FIELD_PRIORITY, SORT_ORDER_DESC, "test_user", team_id=None)
335+
mock_list.assert_called_once_with(
336+
1, 20, SORT_FIELD_PRIORITY, SORT_ORDER_DESC, "test_user", team_id=None, status_filter=None
337+
)
328338

329339
@patch("todo.services.task_service.TaskRepository.count")
330340
@patch("todo.services.task_service.TaskRepository.list")
@@ -334,7 +344,9 @@ def test_get_tasks_sort_by_assignee_default_order(self, mock_list, mock_count):
334344

335345
TaskService.get_tasks(page=1, limit=20, sort_by=SORT_FIELD_ASSIGNEE, order="asc", user_id="test_user")
336346

337-
mock_list.assert_called_once_with(1, 20, SORT_FIELD_ASSIGNEE, SORT_ORDER_ASC, "test_user", team_id=None)
347+
mock_list.assert_called_once_with(
348+
1, 20, SORT_FIELD_ASSIGNEE, SORT_ORDER_ASC, "test_user", team_id=None, status_filter=None
349+
)
338350

339351
@patch("todo.services.task_service.TaskRepository.count")
340352
@patch("todo.services.task_service.TaskRepository.list")
@@ -344,7 +356,9 @@ def test_get_tasks_sort_by_created_at_default_order(self, mock_list, mock_count)
344356

345357
TaskService.get_tasks(page=1, limit=20, sort_by=SORT_FIELD_CREATED_AT, order="desc", user_id="test_user")
346358

347-
mock_list.assert_called_once_with(1, 20, SORT_FIELD_CREATED_AT, SORT_ORDER_DESC, "test_user", team_id=None)
359+
mock_list.assert_called_once_with(
360+
1, 20, SORT_FIELD_CREATED_AT, SORT_ORDER_DESC, "test_user", team_id=None, status_filter=None
361+
)
348362

349363
@patch("todo.services.task_service.reverse_lazy", return_value="/v1/tasks")
350364
def test_build_page_url_includes_sort_parameters(self, mock_reverse):

0 commit comments

Comments
 (0)