Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions todo/repositories/task_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,36 @@ class TaskRepository(MongoRepository):

@classmethod
def _build_status_filter(cls, status_filter: str = None) -> dict:
"""
Build status filter for task queries.
now = datetime.now(timezone.utc)

if status_filter == TaskStatus.DEFERRED.value:
return {
"$and": [
{"deferredDetails": {"$ne": None}},
{"deferredDetails.deferredTill": {"$gt": now}},
]
}

elif status_filter == TaskStatus.DONE.value:
return {
"$or": [
{"deferredDetails": None},
{"deferredDetails.deferredTill": {"$lt": now}},
]
}

"""
if status_filter:
if status_filter == TaskStatus.DONE.value:
return {} # No status filtering, include all tasks
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I know why you deleted this ??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not deleted ,we have modified it to not to include deferred task

return {"status": status_filter}
else:
return {"status": {"$ne": TaskStatus.DONE.value}}
return {
"$and": [
{"status": {"$ne": TaskStatus.DONE.value}},
{
"$or": [
{"deferredDetails": None},
{"deferredDetails.deferredTill": {"$lt": now}},
]
},
]
}

@classmethod
def list(
Expand Down
25 changes: 19 additions & 6 deletions todo/services/task_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.core.exceptions import ValidationError
from django.urls import reverse_lazy
from urllib.parse import urlencode
from datetime import datetime, timezone, timedelta
from datetime import datetime, timezone
from todo.dto.deferred_details_dto import DeferredDetailsDTO
from todo.dto.label_dto import LabelDTO
from todo.dto.task_dto import TaskDTO, CreateTaskDTO
Expand All @@ -28,7 +28,6 @@
from todo.constants.task import (
TaskStatus,
TaskPriority,
MINIMUM_DEFERRAL_NOTICE_DAYS,
)
from todo.constants.messages import ApiErrors, ValidationErrors
from django.conf import settings
Expand Down Expand Up @@ -173,6 +172,11 @@ def prepare_task_dto(cls, task_model: TaskModel, user_id: str = None) -> TaskDTO
if watchlist_entry:
in_watchlist = watchlist_entry.isActive

task_status = task_model.status

if task_model.deferredDetails and task_model.deferredDetails.deferredTill > datetime.now(timezone.utc):
task_status = TaskStatus.DEFERRED.value

return TaskDTO(
id=str(task_model.id),
displayId=task_model.displayId,
Expand All @@ -183,7 +187,7 @@ def prepare_task_dto(cls, task_model: TaskModel, user_id: str = None) -> TaskDTO
labels=label_dtos,
startedAt=task_model.startedAt,
dueAt=task_model.dueAt,
status=task_model.status,
status=task_status,
priority=task_model.priority,
deferredDetails=deferred_details,
in_watchlist=in_watchlist,
Expand Down Expand Up @@ -420,6 +424,16 @@ def update_task_with_assignee_from_dict(cls, task_id: str, validated_data: dict,
if validated_data.get("status") == TaskStatus.IN_PROGRESS and not current_task.startedAt:
update_payload["startedAt"] = datetime.now(timezone.utc)

if (
validated_data.get("status") is not None
and validated_data.get("status") != TaskStatus.DEFERRED.value
and current_task.deferredDetails
):
update_payload["deferredDetails"] = None

if validated_data.get("status") == TaskStatus.DEFERRED.value:
update_payload["status"] = current_task.status

# Update task if there are changes
if update_payload:
update_payload["updatedBy"] = user_id
Expand Down Expand Up @@ -547,9 +561,7 @@ def defer_task(cls, task_id: str, deferred_till: datetime, user_id: str) -> Task
else current_task.dueAt.astimezone(timezone.utc)
)

defer_limit = due_at - timedelta(days=MINIMUM_DEFERRAL_NOTICE_DAYS)

if deferred_till > defer_limit:
if deferred_till >= due_at:
raise UnprocessableEntityException(
ValidationErrors.CANNOT_DEFER_TOO_CLOSE_TO_DUE_DATE,
source={ApiErrorSource.PARAMETER: "deferredTill"},
Expand All @@ -562,6 +574,7 @@ def defer_task(cls, task_id: str, deferred_till: datetime, user_id: str) -> Task
)

update_payload = {
"status": TaskStatus.TODO.value,
"deferredDetails": deferred_details.model_dump(),
"updatedBy": user_id,
}
Expand Down
11 changes: 5 additions & 6 deletions todo/tests/integration/test_task_defer_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from bson import ObjectId
from django.urls import reverse
from todo.constants.messages import ApiErrors, ValidationErrors
from todo.constants.task import MINIMUM_DEFERRAL_NOTICE_DAYS, TaskPriority, TaskStatus
from todo.constants.task import TaskPriority, TaskStatus
from todo.tests.integration.base_mongo_test import AuthenticatedMongoTestCase
from todo.tests.fixtures.task import tasks_db_data

Expand Down Expand Up @@ -52,7 +52,7 @@ def _insert_task(self, *, status: str = TaskStatus.TODO.value, due_at: datetime

def test_defer_task_success(self):
now = datetime.now(timezone.utc)
due_at = now + timedelta(days=MINIMUM_DEFERRAL_NOTICE_DAYS + 30)
due_at = now + timedelta(days=30)
task_id = self._insert_task(due_at=due_at)
deferred_till = now + timedelta(days=10)

Expand All @@ -76,11 +76,10 @@ def test_defer_task_success(self):

def test_defer_task_too_close_to_due_date_returns_422(self):
now = datetime.now(timezone.utc)
due_at = now + timedelta(days=MINIMUM_DEFERRAL_NOTICE_DAYS + 5)
due_at = now + timedelta(days=5)
task_id = self._insert_task(due_at=due_at)

defer_limit = due_at - timedelta(days=MINIMUM_DEFERRAL_NOTICE_DAYS)
deferred_till = defer_limit + timedelta(days=1)
deferred_till = due_at + timedelta(days=1)

url = reverse("task_detail", args=[task_id]) + "?action=defer"
response = self.client.patch(url, data={"deferredTill": deferred_till.isoformat()}, format="json")
Expand Down Expand Up @@ -129,7 +128,7 @@ def test_defer_task_with_missing_date_returns_400(self):

def test_defer_task_unauthorized(self):
now = datetime.now(timezone.utc)
due_at = now + timedelta(days=MINIMUM_DEFERRAL_NOTICE_DAYS + 30)
due_at = now + timedelta(days=30)
task_id = self._insert_task(due_at=due_at)
deferred_till = now + timedelta(days=10)
url = reverse("task_detail", args=[task_id]) + "?action=defer"
Expand Down
7 changes: 6 additions & 1 deletion todo/tests/unit/repositories/test_task_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ def test_count_returns_total_task_count(self):
result = TaskRepository.count()

self.assertEqual(result, 42)
self.mock_collection.count_documents.assert_called_once_with({"status": {"$ne": "DONE"}})

self.mock_collection.count_documents.assert_called_once()
actual_filter = self.mock_collection.count_documents.call_args[0][0]
self.assertIn("$and", actual_filter)
self.assertIn("status", actual_filter["$and"][0])
self.assertIn("$or", actual_filter["$and"][1])

def test_get_all_returns_all_tasks(self):
self.mock_collection.find.return_value = self.task_data
Expand Down
2 changes: 1 addition & 1 deletion todo/tests/unit/services/test_task_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,7 @@ def test_defer_task_success(self, mock_prepare_dto, mock_repo_update, mock_repo_
@patch("todo.services.task_service.TaskRepository.get_by_id")
def test_defer_task_too_close_to_due_date_raises_exception(self, mock_repo_get_by_id):
mock_repo_get_by_id.return_value = self.task_model
deferred_till = self.due_at - timedelta(days=1)
deferred_till = self.due_at + timedelta(days=1)

with self.assertRaises(UnprocessableEntityException):
TaskService.defer_task(self.task_id, deferred_till, self.user_id)
Expand Down