"""Web server stuff."""
from typing import Any
+from collections import namedtuple
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
from urllib.parse import urlparse, parse_qs
def do_GET_day(self) -> dict[str, object]:
"""Show single Day of ?date=."""
date = self.params.get_str('date', todays_date())
- conditions_listing = []
top_todos = [t for t in Todo.by_date(self.conn, date) if not t.parents]
seen_todos: set[int] = set()
seen_conditions: set[int] = set()
todo_trees = [t.get_step_tree(seen_todos, seen_conditions)
for t in top_todos]
- for condition in Condition.all(self.conn):
- enablers = Todo.enablers_for_at(self.conn, condition, date)
- disablers = Todo.disablers_for_at(self.conn, condition, date)
- conditions_listing += [{
- 'condition': condition,
- 'enablers': enablers,
- 'disablers': disablers}]
+ ConditionsNode = namedtuple('ConditionsNode',
+ ('condition', 'enablers', 'disablers'))
+ conditions_nodes: list[ConditionsNode] = []
+ for condi in Condition.all(self.conn):
+ enablers = Todo.enablers_for_at(self.conn, condi, date)
+ disablers = Todo.disablers_for_at(self.conn, condi, date)
+ conditions_nodes += [ConditionsNode(condi, enablers, disablers)]
return {'day': Day.by_id(self.conn, date, create=True),
'todo_trees': todo_trees,
'processes': Process.all(self.conn),
- 'conditions_listing': conditions_listing}
+ 'conditions_nodes': conditions_nodes}
def do_GET_todo(self) -> dict[str, object]:
"""Show single Todo of ?id=."""
"""Collecting Processes and Process-related items."""
from __future__ import annotations
-from typing import Any, Set
+from dataclasses import dataclass
+from typing import Set
from plomtask.db import DatabaseConnection, BaseModel
from plomtask.misc import VersionedAttribute
from plomtask.conditions import Condition, ConditionsRelations
from plomtask.exceptions import NotFoundException, BadFormatException
+@dataclass
+class ProcessStepsNode:
+ """Collects what's useful to know for ProcessSteps tree display."""
+ process: Process
+ parent_id: int | None
+ is_explicit: bool
+ steps: dict[int, ProcessStepsNode]
+ seen: bool
+
+
class Process(BaseModel, ConditionsRelations):
"""Template for, and metadata for, Todos, and their arrangements."""
table_name = 'processes'
return [self.__class__.by_id(db_conn, id_) for id_ in owner_ids]
def get_steps(self, db_conn: DatabaseConnection, external_owner:
- Process | None = None) -> dict[int, dict[str, object]]:
+ Process | None = None) -> dict[int, ProcessStepsNode]:
"""Return tree of depended-on explicit and implicit ProcessSteps."""
- def make_node(step: ProcessStep) -> dict[str, object]:
+ def make_node(step: ProcessStep) -> ProcessStepsNode:
is_explicit = False
if external_owner is not None:
is_explicit = step.owner_id == external_owner.id_
process = self.__class__.by_id(db_conn, step.step_process_id)
step_steps = process.get_steps(db_conn, external_owner)
- return {'process': process, 'parent_id': step.parent_step_id,
- 'is_explicit': is_explicit, 'steps': step_steps}
+ return ProcessStepsNode(process, step.parent_step_id,
+ is_explicit, step_steps, False)
- def walk_steps(node_id: int, node: dict[str, Any]) -> None:
+ def walk_steps(node_id: int, node: ProcessStepsNode) -> None:
explicit_children = [s for s in self.explicit_steps
if s.parent_step_id == node_id]
for child in explicit_children:
- node['steps'][child.id_] = make_node(child)
- node['seen'] = node_id in seen_step_ids
+ assert isinstance(child.id_, int)
+ node.steps[child.id_] = make_node(child)
+ node.seen = node_id in seen_step_ids
seen_step_ids.add(node_id)
- for id_, step in node['steps'].items():
+ for id_, step in node.steps.items():
walk_steps(id_, step)
- steps: dict[int, dict[str, object]] = {}
+ steps: dict[int, ProcessStepsNode] = {}
seen_step_ids: Set[int] = set()
if external_owner is None:
external_owner = self
just deleted under its feet), or if the parent step would not be
owned by the current Process.
"""
+
def walk_steps(node: ProcessStep) -> None:
if node.step_process_id == self.id_:
raise BadFormatException('bad step selection causes recursion')
step_process = self.by_id(db_conn, node.step_process_id)
for step in step_process.explicit_steps:
walk_steps(step)
+
if parent_step_id is not None:
try:
parent_step = ProcessStep.by_id(db_conn, parent_step_id)
"""Actionables."""
from __future__ import annotations
-from collections import namedtuple
+from dataclasses import dataclass
from typing import Any
from sqlite3 import Row
from plomtask.db import DatabaseConnection, BaseModel
HandledException)
-TodoStepsNode = namedtuple('TodoStepsNode',
- ('item', 'is_todo', 'children', 'seen'))
+@dataclass
+class TodoStepsNode:
+ """Collects what's useful to know for Todo/Condition tree display."""
+ item: Todo | Condition
+ is_todo: bool
+ children: list[TodoStepsNode]
+ seen: bool
class Todo(BaseModel, ConditionsRelations):
def add_child(self, child: Todo) -> None:
"""Add child to self.children, avoid recursion, update parenthoods."""
+
def walk_steps(node: Todo) -> None:
if node.id_ == self.id_:
raise BadFormatException('bad child choice causes recursion')
for child in node.children:
walk_steps(child)
+
if self.id_ is None:
raise HandledException('Can only add children to saved Todos.')
if child.id_ is None:
</datalist>
</form>
<h4>conditions</h4>
-{% for node in conditions_listing %}
-<li>[{% if node['condition'].is_active %}x{% else %} {% endif %}] <a href="condition?id={{node['condition'].id_}}">{{node['condition'].title.newest|e}}</a>
+{% for node in conditions_nodes %}
+<li>[{% if node.condition.is_active %}x{% else %} {% endif %}] <a href="condition?id={{node.condition.id_}}">{{node.condition.title.newest|e}}</a>
<ul>
-{% for enabler in node['enablers'] %}
+{% for enabler in node.enablers %}
<li>< {{enabler.process.title.newest|e}}</li>
{% endfor %}
-{% for disabler in node['disablers'] %}
+{% for disabler in node.disablers %}
<li>! {{disabler.process.title.newest|e}}</li>
{% endfor %}
</ul>
add disables: <input name="disables" list="condition_candidates" autocomplete="off" />
<h4>steps</h4>
<table>
-{% for step_id, step_node in steps.items() %}
+{% for step_node in steps %}
{{ step_with_steps(step_id, step_node, 0) }}
{% endfor %}
</table>
"""Test Processes module."""
from unittest import TestCase
-from typing import Any
from tests.utils import TestCaseWithDB, TestCaseWithServer
-from plomtask.processes import Process, ProcessStep
+from plomtask.processes import Process, ProcessStep, ProcessStepsNode
from plomtask.conditions import Condition
from plomtask.exceptions import NotFoundException, HandledException
assert isinstance(self.proc3.id_, int)
steps_proc1: list[tuple[int | None, int, int | None]] = []
add_step(self.proc1, steps_proc1, (None, self.proc2.id_, None), 1)
- p_1_dict: dict[int, dict[str, Any]] = {1: {
- 'process': self.proc2, 'parent_id': None,
- 'is_explicit': True, 'steps': {}, 'seen': False
- }}
+ p_1_dict: dict[int, ProcessStepsNode] = {}
+ p_1_dict[1] = ProcessStepsNode(self.proc2, None, True, {}, False)
self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
add_step(self.proc1, steps_proc1, (None, self.proc3.id_, None), 2)
step_2 = self.proc1.explicit_steps[-1]
assert isinstance(step_2.id_, int)
- p_1_dict[2] = {
- 'process': self.proc3, 'parent_id': None,
- 'is_explicit': True, 'steps': {}, 'seen': False
- }
+ p_1_dict[2] = ProcessStepsNode(self.proc3, None, True, {}, False)
self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
steps_proc2: list[tuple[int | None, int, int | None]] = []
add_step(self.proc2, steps_proc2, (None, self.proc3.id_, None), 3)
- p_1_dict[1]['steps'] = {3: {
- 'process': self.proc3, 'parent_id': None,
- 'is_explicit': False, 'steps': {}, 'seen': False
- }}
+ p_1_dict[1].steps[3] = ProcessStepsNode(self.proc3, None,
+ False, {}, False)
self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
add_step(self.proc1, steps_proc1, (None, self.proc2.id_, step_2.id_),
4)
- p_1_dict[2]['steps'][4] = {
- 'process': self.proc2, 'parent_id': step_2.id_, 'seen': False,
- 'is_explicit': True, 'steps': {3: {
- 'process': self.proc3, 'parent_id': None,
- 'is_explicit': False, 'steps': {}, 'seen': True
- }}}
+ step_3 = ProcessStepsNode(self.proc3, None, False, {}, True)
+ p_1_dict[2].steps[4] = ProcessStepsNode(self.proc2, step_2.id_, True,
+ {3: step_3}, False)
self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
add_step(self.proc1, steps_proc1, (None, self.proc3.id_, 999), 5)
- p_1_dict[5] = {
- 'process': self.proc3, 'parent_id': None,
- 'is_explicit': True, 'steps': {}, 'seen': False
- }
+ p_1_dict[5] = ProcessStepsNode(self.proc3, None, True, {}, False)
self.assertEqual(self.proc1.get_steps(self.db_conn, None), p_1_dict)
add_step(self.proc1, steps_proc1, (None, self.proc3.id_, 3), 6)
- p_1_dict[6] = {
- 'process': self.proc3, 'parent_id': None,
- 'is_explicit': True, 'steps': {}, 'seen': False
- }
+ p_1_dict[6] = ProcessStepsNode(self.proc3, None, True, {}, False)
self.assertEqual(self.proc1.get_steps(self.db_conn, None),
p_1_dict)
self.assertEqual(self.proc1.used_as_step_by(self.db_conn),
todo_1.save(self.db_conn)
assert isinstance(todo_1.id_, int)
# test minimum
- tree_expected = TodoStepsNode(todo_1, True, [], False)
- self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+ node_0 = TodoStepsNode(todo_1, True, [], False)
+ self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
# test non_emtpy seen_todo does something
- tree_expected = TodoStepsNode(todo_1, True, [], True)
- self.assertEqual(todo_1.get_step_tree({todo_1.id_}, set()),
- tree_expected)
+ node_0.seen = True
+ self.assertEqual(todo_1.get_step_tree({todo_1.id_}, set()), node_0)
# test child shows up
todo_2 = Todo(None, self.proc, False, self.date1)
todo_2.save(self.db_conn)
assert isinstance(todo_2.id_, int)
todo_1.add_child(todo_2)
- node_todo_2 = TodoStepsNode(todo_2, True, [], False)
- tree_expected = TodoStepsNode(todo_1, True, [node_todo_2], False)
- self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+ node_2 = TodoStepsNode(todo_2, True, [], False)
+ node_0.children = [node_2]
+ node_0.seen = False
+ self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
# test child shows up with child
todo_3 = Todo(None, self.proc, False, self.date1)
todo_3.save(self.db_conn)
assert isinstance(todo_3.id_, int)
todo_2.add_child(todo_3)
- node_todo_3 = TodoStepsNode(todo_3, True, [], False)
- node_todo_2 = TodoStepsNode(todo_2, True, [node_todo_3], False)
- tree_expected = TodoStepsNode(todo_1, True, [node_todo_2], False)
- self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+ node_3 = TodoStepsNode(todo_3, True, [], False)
+ node_2.children = [node_3]
+ self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
# test same todo can be child-ed multiple times at different locations
todo_1.add_child(todo_3)
- node_todo_4 = TodoStepsNode(todo_3, True, [], True)
- tree_expected = TodoStepsNode(todo_1, True,
- [node_todo_2, node_todo_4], False)
- self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+ node_4 = TodoStepsNode(todo_3, True, [], True)
+ node_0.children += [node_4]
+ self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
# test condition shows up
todo_1.set_conditions(self.db_conn, [self.cond1.id_])
- node_cond_1 = TodoStepsNode(self.cond1, False, [], False)
- tree_expected = TodoStepsNode(todo_1, True,
- [node_todo_2, node_todo_4, node_cond_1],
- False)
- self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+ node_5 = TodoStepsNode(self.cond1, False, [], False)
+ node_0.children += [node_5]
+ self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
# test second condition shows up
todo_2.set_conditions(self.db_conn, [self.cond2.id_])
- node_cond_2 = TodoStepsNode(self.cond2, False, [], False)
- node_todo_2 = TodoStepsNode(todo_2, True,
- [node_todo_3, node_cond_2], False)
- tree_expected = TodoStepsNode(todo_1, True,
- [node_todo_2, node_todo_4, node_cond_1],
- False)
- self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+ node_6 = TodoStepsNode(self.cond2, False, [], False)
+ node_2.children += [node_6]
+ self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
# test second condition is not hidden if fulfilled by non-sibling
todo_1.set_enables(self.db_conn, [self.cond2.id_])
- self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+ self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
# test second condition is hidden if fulfilled by sibling
todo_3.set_enables(self.db_conn, [self.cond2.id_])
- node_todo_2 = TodoStepsNode(todo_2, True, [node_todo_3], False)
- tree_expected = TodoStepsNode(todo_1, True,
- [node_todo_2, node_todo_4, node_cond_1],
- False)
- self.assertEqual(todo_1.get_step_tree(set(), set()), tree_expected)
+ node_2.children.remove(node_6)
+ self.assertEqual(todo_1.get_step_tree(set(), set()), node_0)
def test_Todo_singularity(self) -> None:
"""Test pointers made for single object keep pointing to it."""