From aed1d5968abf97976db3725347fe4e7672c935e7 Mon Sep 17 00:00:00 2001
From: Christian Heller <c.heller@plomlompom.de>
Date: Sat, 13 Apr 2024 04:25:36 +0200
Subject: [PATCH] Disable Todo.is_done setting if children are not done yet.

---
 plomtask/http.py  |  7 ++++++-
 plomtask/todos.py |  8 ++++++++
 tests/todos.py    | 22 ++++++++++++++--------
 3 files changed, 28 insertions(+), 9 deletions(-)

diff --git a/plomtask/http.py b/plomtask/http.py
index 5d165ec..5a7126e 100644
--- a/plomtask/http.py
+++ b/plomtask/http.py
@@ -209,7 +209,12 @@ class TaskHandler(BaseHTTPRequestHandler):
         if child_id is not None:
             child = Todo.by_id(conn, child_id)
             todo.add_child(child)
-        todo.is_done = len(form_data.get_all_str('done')) > 0
+        if len(form_data.get_all_str('done')) > 0:
+            if not todo.is_doable:
+                raise BadFormatException('cannot set undoable Todo to done')
+            todo.is_done = True
+        else:
+            todo.is_done = False
         todo.save(conn)
 
     def do_POST_process(self, conn: DatabaseConnection, params: ParamsParser,
diff --git a/plomtask/todos.py b/plomtask/todos.py
index 2b9fd1d..43ada0b 100644
--- a/plomtask/todos.py
+++ b/plomtask/todos.py
@@ -61,6 +61,14 @@ class Todo:
             todos += [cls.by_id(db_conn, row[0])]
         return todos
 
+    @property
+    def is_doable(self) -> bool:
+        """Decide whether .is_done can be set to True based on children's."""
+        for child in self.children:
+            if not child.is_done:
+                return False
+        return True
+
     def add_child(self, child: Todo) -> None:
         """Add child to self.children, guard against recursion"""
         def walk_steps(node: Todo) -> None:
diff --git a/tests/todos.py b/tests/todos.py
index 6ef8b09..6fbb944 100644
--- a/tests/todos.py
+++ b/tests/todos.py
@@ -106,6 +106,11 @@ class TestsWithServer(TestCaseWithServer):
 
     def test_do_POST_todo(self) -> None:
         """Test POST /todo."""
+        def post_and_reload(form_data: dict[str, object],
+                            status: int = 302) -> Todo:
+            self.check_post(form_data, '/todo?id=1', status, '/')
+            self.db_conn.cached_todos = {}
+            return Todo.by_date(self.db_conn, '2024-01-01')[0]
         form_data = {'title': '', 'description': '', 'effort': 1}
         self.check_post(form_data, '/process', 302, '/')
         form_data = {'comment': '', 'new_todo': 1}
@@ -115,16 +120,16 @@ class TestsWithServer(TestCaseWithServer):
         self.check_post(form_data, '/todo?id=', 404)
         self.check_post(form_data, '/todo?id=FOO', 400)
         self.check_post(form_data, '/todo?id=0', 404)
-        self.check_post(form_data, '/todo?id=1', 302, '/')
-        todo1 = Todo.by_date(self.db_conn, '2024-01-01')[0]
+        todo1 = post_and_reload(form_data)
         self.assertEqual(todo1.children, [])
         self.assertEqual(todo1.parents, [])
         self.assertEqual(todo1.is_done, False)
         form_data = {'done': ''}
-        self.check_post(form_data, '/todo?id=1', 302, '/')
-        self.db_conn.cached_todos = {}
-        todo1 = Todo.by_date(self.db_conn, '2024-01-01')[0]
+        todo1 = post_and_reload(form_data)
         self.assertEqual(todo1.is_done, True)
+        form_data = {}
+        todo1 = post_and_reload(form_data)
+        self.assertEqual(todo1.is_done, False)
         form_data = {'adopt': 'foo'}
         self.check_post(form_data, '/todo?id=1', 400)
         form_data = {'adopt': 1}
@@ -134,15 +139,16 @@ class TestsWithServer(TestCaseWithServer):
         form_data = {'comment': '', 'new_todo': 1}
         self.check_post(form_data, '/day?date=2024-01-01', 302, '/')
         form_data = {'adopt': 2}
-        self.check_post(form_data, '/todo?id=1', 302, '/')
-        self.db_conn.cached_todos = {}
-        todo1 = Todo.by_date(self.db_conn, '2024-01-01')[0]
+        todo1 = post_and_reload(form_data)
         todo2 = Todo.by_date(self.db_conn, '2024-01-01')[1]
         self.assertEqual(todo1.children, [todo2])
         self.assertEqual(todo1.parents, [])
         self.assertEqual(todo2.children, [])
         self.assertEqual(todo2.parents, [todo1])
         self.check_post(form_data, '/todo?id=1', 400, '/')
+        form_data = {'done': ''}
+        todo1 = post_and_reload(form_data, 400)
+        self.assertEqual(todo1.is_done, False)
 
     def test_do_GET_todo(self) -> None:
         """Test GET /todo response codes."""
-- 
2.30.2