home · contact · privacy
Expand POST /todo adoption tests.
[plomtask] / plomtask / todos.py
index 4d7e393bc49191db242235c1f11092bafce450ed..cb72640fb2c088ed317302df42cf5d12a4ff9108 100644 (file)
@@ -11,27 +11,55 @@ from plomtask.exceptions import (NotFoundException, BadFormatException,
 from plomtask.dating import valid_date
 
 
 from plomtask.dating import valid_date
 
 
-class TodoNode:
+class DictableNode:
+    """Template for TodoNode, TodoOrStepsNode providing .as_dict_and_refs."""
+    # pylint: disable=too-few-public-methods
+    _to_dict: list[str] = []
+
+    def __init__(self, *args: Any) -> None:
+        for i, arg in enumerate(args):
+            setattr(self, self._to_dict[i], arg)
+
+    @property
+    def as_dict_and_refs(self) -> tuple[dict[str, object], list[Any]]:
+        """Return self as json.dumps-ready dict, list of referenced objects."""
+        d = {}
+        refs = []
+        for name in self._to_dict:
+            attr = getattr(self, name)
+            if hasattr(attr, 'id_'):
+                d[name] = attr.id_
+                continue
+            if isinstance(attr, list):
+                d[name] = []
+                for item in attr:
+                    item_d, item_refs = item.as_dict_and_refs
+                    d[name] += [item_d]
+                    for item_ref in [r for r in item_refs if r not in refs]:
+                        refs += [item_ref]
+                continue
+            d[name] = attr
+        return d, refs
+
+
+class TodoNode(DictableNode):
     """Collects what's useful to know for Todo/Condition tree display."""
     # pylint: disable=too-few-public-methods
     todo: Todo
     seen: bool
     children: list[TodoNode]
     """Collects what's useful to know for Todo/Condition tree display."""
     # pylint: disable=too-few-public-methods
     todo: Todo
     seen: bool
     children: list[TodoNode]
+    _to_dict = ['todo', 'seen', 'children']
 
 
-    def __init__(self,
-                 todo: Todo,
-                 seen: bool,
-                 children: list[TodoNode]) -> None:
-        self.todo = todo
-        self.seen = seen
-        self.children = children
 
 
-    @property
-    def as_dict(self) -> dict[str, object]:
-        """Return self as (json.dumps-coompatible) dict."""
-        return {'todo': self.todo.id_,
-                'seen': self.seen,
-                'children': [c.as_dict for c in self.children]}
+class TodoOrProcStepNode(DictableNode):
+    """Collect what's useful for Todo-or-ProcessStep tree display."""
+    # pylint: disable=too-few-public-methods
+    node_id: int
+    todo: Todo | None
+    process: Process | None
+    children: list[TodoOrProcStepNode]  # pylint: disable=undefined-variable
+    fillable: bool = False
+    _to_dict = ['node_id', 'todo', 'process', 'children', 'fillable']
 
 
 class Todo(BaseModel[int], ConditionsRelations):
 
 
 class Todo(BaseModel[int], ConditionsRelations):
@@ -39,8 +67,8 @@ class Todo(BaseModel[int], ConditionsRelations):
     # pylint: disable=too-many-instance-attributes
     # pylint: disable=too-many-public-methods
     table_name = 'todos'
     # pylint: disable=too-many-instance-attributes
     # pylint: disable=too-many-public-methods
     table_name = 'todos'
-    to_save = ['process_id', 'is_done', 'date', 'comment', 'effort',
-               'calendarize']
+    to_save_simples = ['process_id', 'is_done', 'date', 'comment', 'effort',
+                       'calendarize']
     to_save_relations = [('todo_conditions', 'todo', 'conditions', 0),
                          ('todo_blockers', 'todo', 'blockers', 0),
                          ('todo_enables', 'todo', 'enables', 0),
     to_save_relations = [('todo_conditions', 'todo', 'conditions', 0),
                          ('todo_blockers', 'todo', 'blockers', 0),
                          ('todo_enables', 'todo', 'enables', 0),
@@ -51,6 +79,10 @@ class Todo(BaseModel[int], ConditionsRelations):
     days_to_update: Set[str] = set()
     children: list[Todo]
     parents: list[Todo]
     days_to_update: Set[str] = set()
     children: list[Todo]
     parents: list[Todo]
+    sorters = {'doneness': lambda t: t.is_done,
+               'title': lambda t: t.title_then,
+               'comment': lambda t: t.comment,
+               'date': lambda t: t.date}
 
     # pylint: disable=too-many-arguments
     def __init__(self, id_: int | None,
 
     # pylint: disable=too-many-arguments
     def __init__(self, id_: int | None,
@@ -88,7 +120,13 @@ class Todo(BaseModel[int], ConditionsRelations):
     @classmethod
     def create_with_children(cls, db_conn: DatabaseConnection,
                              process_id: int, date: str) -> Todo:
     @classmethod
     def create_with_children(cls, db_conn: DatabaseConnection,
                              process_id: int, date: str) -> Todo:
-        """Create Todo of process for date, ensure children."""
+        """Create Todo of process for date, ensure children demanded by chain.
+
+        At minimum creates Todo of process_id, but checks the respective
+        Process for its step tree, and walks down that to provide the initial
+        Todo with all descendants defined there, either adopting existing
+        Todos, or creating them where necessary.
+        """
 
         def key_order_func(n: ProcessStepsNode) -> int:
             assert isinstance(n.process.id_, int)
 
         def key_order_func(n: ProcessStepsNode) -> int:
             assert isinstance(n.process.id_, int)
@@ -227,6 +265,7 @@ class Todo(BaseModel[int], ConditionsRelations):
     @property
     def title(self) -> VersionedAttribute:
         """Shortcut to .process.title."""
     @property
     def title(self) -> VersionedAttribute:
         """Shortcut to .process.title."""
+        assert isinstance(self.process.title, VersionedAttribute)
         return self.process.title
 
     @property
         return self.process.title
 
     @property