+++ /dev/null
-CREATE TABLE condition_descriptions (
- parent INTEGER NOT NULL,
- timestamp TEXT NOT NULL,
- description TEXT NOT NULL,
- PRIMARY KEY (parent, timestamp),
- FOREIGN KEY (parent) REFERENCES conditions(id)
-);
-CREATE TABLE condition_titles (
- parent INTEGER NOT NULL,
- timestamp TEXT NOT NULL,
- title TEXT NOT NULL,
- PRIMARY KEY (parent, timestamp),
- FOREIGN KEY (parent) REFERENCES conditions(id)
-);
-CREATE TABLE conditions (
- id INTEGER PRIMARY KEY,
- is_active BOOLEAN NOT NULL
-);
-CREATE TABLE days (
- id TEXT PRIMARY KEY,
- comment TEXT NOT NULL
-);
-CREATE TABLE process_blockers (
- process INTEGER NOT NULL,
- condition INTEGER NOT NULL,
- PRIMARY KEY (process, condition),
- FOREIGN KEY (process) REFERENCES processes(id),
- FOREIGN KEY (condition) REFERENCES conditions(id)
-);
-CREATE TABLE process_conditions (
- process INTEGER NOT NULL,
- condition INTEGER NOT NULL,
- PRIMARY KEY (process, condition),
- FOREIGN KEY (process) REFERENCES processes(id),
- FOREIGN KEY (condition) REFERENCES conditions(id)
-);
-CREATE TABLE process_descriptions (
- parent INTEGER NOT NULL,
- timestamp TEXT NOT NULL,
- description TEXT NOT NULL,
- PRIMARY KEY (parent, timestamp),
- FOREIGN KEY (parent) REFERENCES processes(id)
-);
-CREATE TABLE process_disables (
- process INTEGER NOT NULL,
- condition INTEGER NOT NULL,
- PRIMARY KEY(process, condition),
- FOREIGN KEY (process) REFERENCES processes(id),
- FOREIGN KEY (condition) REFERENCES conditions(id)
-);
-CREATE TABLE process_efforts (
- parent INTEGER NOT NULL,
- timestamp TEXT NOT NULL,
- effort REAL NOT NULL,
- PRIMARY KEY (parent, timestamp),
- FOREIGN KEY (parent) REFERENCES processes(id)
-);
-CREATE TABLE process_enables (
- process INTEGER NOT NULL,
- condition INTEGER NOT NULL,
- PRIMARY KEY(process, condition),
- FOREIGN KEY (process) REFERENCES processes(id),
- FOREIGN KEY (condition) REFERENCES conditions(id)
-);
-CREATE TABLE process_steps (
- id INTEGER PRIMARY KEY,
- owner INTEGER NOT NULL,
- step_process INTEGER NOT NULL,
- parent_step INTEGER,
- FOREIGN KEY (owner) REFERENCES processes(id),
- FOREIGN KEY (step_process) REFERENCES processes(id),
- FOREIGN KEY (parent_step) REFERENCES process_steps(step_id)
-);
-CREATE TABLE process_titles (
- parent INTEGER NOT NULL,
- timestamp TEXT NOT NULL,
- title TEXT NOT NULL,
- PRIMARY KEY (parent, timestamp),
- FOREIGN KEY (parent) REFERENCES processes(id)
-);
-CREATE TABLE processes (
- id INTEGER PRIMARY KEY,
- calendarize BOOLEAN NOT NULL DEFAULT FALSE
-);
-CREATE TABLE todo_blockers (
- todo INTEGER NOT NULL,
- condition INTEGER NOT NULL,
- PRIMARY KEY (todo, condition),
- FOREIGN KEY (todo) REFERENCES todos(id),
- FOREIGN KEY (condition) REFERENCES conditions(id)
-);
-CREATE TABLE todo_children (
- parent INTEGER NOT NULL,
- child INTEGER NOT NULL,
- PRIMARY KEY (parent, child),
- FOREIGN KEY (parent) REFERENCES todos(id),
- FOREIGN KEY (child) REFERENCES todos(id)
-);
-CREATE TABLE todo_conditions (
- todo INTEGER NOT NULL,
- condition INTEGER NOT NULL,
- PRIMARY KEY(todo, condition),
- FOREIGN KEY (todo) REFERENCES todos(id),
- FOREIGN KEY (condition) REFERENCES conditions(id)
-);
-CREATE TABLE todo_disables (
- todo INTEGER NOT NULL,
- condition INTEGER NOT NULL,
- PRIMARY KEY(todo, condition),
- FOREIGN KEY (todo) REFERENCES todos(id),
- FOREIGN KEY (condition) REFERENCES conditions(id)
-);
-CREATE TABLE todo_enables (
- todo INTEGER NOT NULL,
- condition INTEGER NOT NULL,
- PRIMARY KEY(todo, condition),
- FOREIGN KEY (todo) REFERENCES todos(id),
- FOREIGN KEY (condition) REFERENCES conditions(id)
-);
-CREATE TABLE todos (
- id INTEGER PRIMARY KEY,
- process INTEGER NOT NULL,
- is_done BOOLEAN NOT NULL,
- day TEXT NOT NULL,
- comment TEXT NOT NULL DEFAULT "",
- effort REAL,
- calendarize BOOLEAN NOT NULL DEFAULT FALSE,
- FOREIGN KEY (process) REFERENCES processes(id),
- FOREIGN KEY (day) REFERENCES days(id)
-);
--- /dev/null
+CREATE TABLE condition_descriptions (
+ parent INTEGER NOT NULL,
+ timestamp TEXT NOT NULL,
+ description TEXT NOT NULL,
+ PRIMARY KEY (parent, timestamp),
+ FOREIGN KEY (parent) REFERENCES conditions(id)
+);
+CREATE TABLE condition_titles (
+ parent INTEGER NOT NULL,
+ timestamp TEXT NOT NULL,
+ title TEXT NOT NULL,
+ PRIMARY KEY (parent, timestamp),
+ FOREIGN KEY (parent) REFERENCES conditions(id)
+);
+CREATE TABLE conditions (
+ id INTEGER PRIMARY KEY,
+ is_active BOOLEAN NOT NULL
+);
+CREATE TABLE days (
+ id TEXT PRIMARY KEY,
+ comment TEXT NOT NULL
+);
+CREATE TABLE process_blockers (
+ process INTEGER NOT NULL,
+ condition INTEGER NOT NULL,
+ PRIMARY KEY (process, condition),
+ FOREIGN KEY (process) REFERENCES processes(id),
+ FOREIGN KEY (condition) REFERENCES conditions(id)
+);
+CREATE TABLE process_conditions (
+ process INTEGER NOT NULL,
+ condition INTEGER NOT NULL,
+ PRIMARY KEY (process, condition),
+ FOREIGN KEY (process) REFERENCES processes(id),
+ FOREIGN KEY (condition) REFERENCES conditions(id)
+);
+CREATE TABLE process_descriptions (
+ parent INTEGER NOT NULL,
+ timestamp TEXT NOT NULL,
+ description TEXT NOT NULL,
+ PRIMARY KEY (parent, timestamp),
+ FOREIGN KEY (parent) REFERENCES processes(id)
+);
+CREATE TABLE process_disables (
+ process INTEGER NOT NULL,
+ condition INTEGER NOT NULL,
+ PRIMARY KEY(process, condition),
+ FOREIGN KEY (process) REFERENCES processes(id),
+ FOREIGN KEY (condition) REFERENCES conditions(id)
+);
+CREATE TABLE process_efforts (
+ parent INTEGER NOT NULL,
+ timestamp TEXT NOT NULL,
+ effort REAL NOT NULL,
+ PRIMARY KEY (parent, timestamp),
+ FOREIGN KEY (parent) REFERENCES processes(id)
+);
+CREATE TABLE process_enables (
+ process INTEGER NOT NULL,
+ condition INTEGER NOT NULL,
+ PRIMARY KEY(process, condition),
+ FOREIGN KEY (process) REFERENCES processes(id),
+ FOREIGN KEY (condition) REFERENCES conditions(id)
+);
+CREATE TABLE process_step_suppressions (
+ process INTEGER NOT NULL,
+ process_step INTEGER NOT NULL,
+ PRIMARY KEY (process, process_step),
+ FOREIGN KEY (process) REFERENCES processes(id),
+ FOREIGN KEY (process_step) REFERENCES process_steps(id)
+);
+CREATE TABLE process_steps (
+ id INTEGER PRIMARY KEY,
+ owner INTEGER NOT NULL,
+ step_process INTEGER NOT NULL,
+ parent_step INTEGER,
+ FOREIGN KEY (owner) REFERENCES processes(id),
+ FOREIGN KEY (step_process) REFERENCES processes(id),
+ FOREIGN KEY (parent_step) REFERENCES process_steps(step_id)
+);
+CREATE TABLE process_titles (
+ parent INTEGER NOT NULL,
+ timestamp TEXT NOT NULL,
+ title TEXT NOT NULL,
+ PRIMARY KEY (parent, timestamp),
+ FOREIGN KEY (parent) REFERENCES processes(id)
+);
+CREATE TABLE processes (
+ id INTEGER PRIMARY KEY,
+ calendarize BOOLEAN NOT NULL DEFAULT FALSE
+);
+CREATE TABLE todo_blockers (
+ todo INTEGER NOT NULL,
+ condition INTEGER NOT NULL,
+ PRIMARY KEY (todo, condition),
+ FOREIGN KEY (todo) REFERENCES todos(id),
+ FOREIGN KEY (condition) REFERENCES conditions(id)
+);
+CREATE TABLE todo_children (
+ parent INTEGER NOT NULL,
+ child INTEGER NOT NULL,
+ PRIMARY KEY (parent, child),
+ FOREIGN KEY (parent) REFERENCES todos(id),
+ FOREIGN KEY (child) REFERENCES todos(id)
+);
+CREATE TABLE todo_conditions (
+ todo INTEGER NOT NULL,
+ condition INTEGER NOT NULL,
+ PRIMARY KEY(todo, condition),
+ FOREIGN KEY (todo) REFERENCES todos(id),
+ FOREIGN KEY (condition) REFERENCES conditions(id)
+);
+CREATE TABLE todo_disables (
+ todo INTEGER NOT NULL,
+ condition INTEGER NOT NULL,
+ PRIMARY KEY(todo, condition),
+ FOREIGN KEY (todo) REFERENCES todos(id),
+ FOREIGN KEY (condition) REFERENCES conditions(id)
+);
+CREATE TABLE todo_enables (
+ todo INTEGER NOT NULL,
+ condition INTEGER NOT NULL,
+ PRIMARY KEY(todo, condition),
+ FOREIGN KEY (todo) REFERENCES todos(id),
+ FOREIGN KEY (condition) REFERENCES conditions(id)
+);
+CREATE TABLE todos (
+ id INTEGER PRIMARY KEY,
+ process INTEGER NOT NULL,
+ is_done BOOLEAN NOT NULL,
+ day TEXT NOT NULL,
+ comment TEXT NOT NULL DEFAULT "",
+ effort REAL,
+ calendarize BOOLEAN NOT NULL DEFAULT FALSE,
+ FOREIGN KEY (process) REFERENCES processes(id),
+ FOREIGN KEY (day) REFERENCES days(id)
+);
from plomtask.exceptions import HandledException, NotFoundException
from plomtask.dating import valid_date
-EXPECTED_DB_VERSION = 4
+EXPECTED_DB_VERSION = 5
MIGRATIONS_DIR = 'migrations'
FILENAME_DB_SCHEMA = f'init_{EXPECTED_DB_VERSION}.sql'
PATH_DB_SCHEMA = f'{MIGRATIONS_DIR}/{FILENAME_DB_SCHEMA}'
process.set_blockers(self.conn, self.form_data.get_all_int('blocker'))
process.set_enables(self.conn, self.form_data.get_all_int('enables'))
process.set_disables(self.conn, self.form_data.get_all_int('disables'))
+ process.set_step_suppressions(self.conn,
+ self.form_data.get_all_int('suppresses'))
process.calendarize = self.form_data.get_all_str('calendarize') != []
process.save(self.conn)
assert isinstance(process.id_, int)
parent_id: int | None
is_explicit: bool
steps: dict[int, ProcessStepsNode]
- seen: bool
+ seen: bool = False
+ is_suppressed: bool = False
class Process(BaseModel[int], ConditionsRelations):
to_save_relations = [('process_conditions', 'process', 'conditions', 0),
('process_blockers', 'process', 'blockers', 0),
('process_enables', 'process', 'enables', 0),
- ('process_disables', 'process', 'disables', 0)]
+ ('process_disables', 'process', 'disables', 0),
+ ('process_step_suppressions', 'process',
+ 'suppressed_steps', 0)]
to_search = ['title.newest', 'description.newest']
def __init__(self, id_: int | None, calendarize: bool = False) -> None:
self.description = VersionedAttribute(self, 'process_descriptions', '')
self.effort = VersionedAttribute(self, 'process_efforts', 1.0)
self.explicit_steps: list[ProcessStep] = []
+ self.suppressed_steps: list[ProcessStep] = []
self.calendarize = calendarize
@classmethod
process.id_):
step = ProcessStep.from_table_row(db_conn, row_)
process.explicit_steps += [step] # pylint: disable=no-member
+ for row_ in db_conn.row_where('process_step_suppressions', 'process',
+ process.id_):
+ step = ProcessStep.by_id(db_conn, row_[1])
+ process.suppressed_steps += [step] # pylint: disable=no-member
for name in ('conditions', 'blockers', 'enables', 'disables'):
table = f'process_{name}'
assert isinstance(process.id_, int)
Process | None = None) -> dict[int, ProcessStepsNode]:
"""Return tree of depended-on explicit and implicit ProcessSteps."""
- def make_node(step: ProcessStep) -> ProcessStepsNode:
+ def make_node(step: ProcessStep, suppressed: bool) -> 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)
+ step_steps = {}
+ if not suppressed:
+ step_steps = process.get_steps(db_conn, external_owner)
return ProcessStepsNode(process, step.parent_step_id,
- is_explicit, step_steps, False)
+ is_explicit, step_steps, False, suppressed)
def walk_steps(node_id: int, node: ProcessStepsNode) -> None:
+ node.seen = node_id in seen_step_ids
+ seen_step_ids.add(node_id)
+ if node.is_suppressed:
+ return
explicit_children = [s for s in self.explicit_steps
if s.parent_step_id == node_id]
for child in explicit_children:
assert isinstance(child.id_, int)
- node.steps[child.id_] = make_node(child)
- # # ensure that one (!) explicit step of process replaces
- # # one (!) implicit step of same process
- # for i in [i for i, s in node.steps.items()
- # if not s.process_step.owner_id == child.id_
- # and s.process.id_ == child.step_process_id]:
- # del node.steps[i]
- # break
- node.seen = node_id in seen_step_ids
- seen_step_ids.add(node_id)
+ node.steps[child.id_] = make_node(child, False)
for id_, step in node.steps.items():
walk_steps(id_, step)
for step in [s for s in self.explicit_steps
if s.parent_step_id is None]:
assert isinstance(step.id_, int)
- steps[step.id_] = make_node(step)
+ new_node = make_node(step, step in external_owner.suppressed_steps)
+ steps[step.id_] = new_node
for step_id, step_node in steps.items():
walk_steps(step_id, step_node)
return steps
+ def set_step_suppressions(self, db_conn: DatabaseConnection,
+ step_ids: list[int]) -> None:
+ """Set self.suppressed_steps from step_ids."""
+ assert isinstance(self.id_, int)
+ db_conn.delete_where('process_step_suppressions', 'process', self.id_)
+ self.suppressed_steps = [ProcessStep.by_id(db_conn, s)
+ for s in step_ids]
+
def set_steps(self, db_conn: DatabaseConnection,
steps: list[ProcessStep]) -> None:
"""Set self.explicit_steps in bulk.
sub_step_nodes = list(step_node.steps.values())
sub_step_nodes.sort(key=key_order_func)
for sub_node in sub_step_nodes:
+ if sub_node.is_suppressed:
+ continue
n_slots = len([n for n in sub_step_nodes
if n.process == sub_node.process])
filled_slots = len([t for t in satisfier.children
todo.save(db_conn)
steps_tree = process.get_steps(db_conn)
for step_node in steps_tree.values():
+ if step_node.is_suppressed:
+ continue
todo.add_child(walk_steps(todo, step_node))
todo.save(db_conn)
return todo
{% endif %}
</td>
<td>{% for i in range(indent) %}+{%endfor %}
-{% if (not step_node.is_explicit) and step_node.seen %}
+{% if step_node.is_suppressed %}<del>{% endif %}
+{% if step_node.seen %}
<a href="process?id={{step_node.process.id_}}">({{step_node.process.title.newest|e}})</a>
{% else %}
<a href="process?id={{step_node.process.id_}}">{{step_node.process.title.newest|e}}</a>
{% endif %}
+{% if step_node.is_suppressed %}<del>{% endif %}
</td>
<td>
{% if step_node.is_explicit %}
add sub-step: <input name="new_step_to_{{step_id}}" list="step_candidates" autocomplete="off" />
+{% elif step_node.seen %}
+<input type="checkbox" name="suppresses" value="{{step_id}}" {% if step_node.is_suppressed %}checked{% endif %}> suppress
{% endif %}
</td>
</tr>
</table>
add: <input name="new_top_step" list="step_candidates" autocomplete="off" />
</td>
-<tr>
+</tr>
<tr>
<th>step of</th>
<a href="process?id={{owner.id_}}">{{owner.title.newest|e}}</a><br />
{% endfor %}
</td>
-<tr>
+</tr>
<tr>
<th>todos</th>
<td>
<a href="todos?process_id={{process.id_}}">{{n_todos}}</a><br />
</td>
-<tr>
+</tr>
</table>
{{ macros.edit_buttons() }}
def test_Process_steps(self) -> None:
"""Test addition, nesting, and non-recursion of ProcessSteps"""
# pylint: disable=too-many-locals
+ # pylint: disable=too-many-statements
p1, p2, p3 = self.three_processes()
assert isinstance(p1.id_, int)
assert isinstance(p2.id_, int)
steps_p1 += [s_p2_to_p1]
p1.set_steps(self.db_conn, steps_p1)
p1_dict: dict[int, ProcessStepsNode] = {}
- p1_dict[1] = ProcessStepsNode(p2, None, True, {}, False)
+ p1_dict[1] = ProcessStepsNode(p2, None, True, {})
self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict)
# add step of process p3 as second (top-level) step to p1
s_p3_to_p1 = ProcessStep(None, p1.id_, p3.id_, None)
steps_p1 += [s_p3_to_p1]
p1.set_steps(self.db_conn, steps_p1)
- p1_dict[2] = ProcessStepsNode(p3, None, True, {}, False)
+ p1_dict[2] = ProcessStepsNode(p3, None, True, {})
self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict)
# add step of process p3 as first (top-level) step to p2,
steps_p2: list[ProcessStep] = []
steps_p2 += [s_p3_to_p2]
p2.set_steps(self.db_conn, steps_p2)
# expect it as implicit sub-step of p1's second (p3) step
- p2_dict = {3: ProcessStepsNode(p3, None, False, {}, False)}
+ p2_dict = {3: ProcessStepsNode(p3, None, False, {})}
p1_dict[1].steps[3] = p2_dict[3]
self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict)
# add step of process p2 as explicit sub-step to p1's second sub-step
p1.set_steps(self.db_conn, steps_p1)
seen_3 = ProcessStepsNode(p3, None, False, {}, True)
p1_dict[2].steps[4] = ProcessStepsNode(p2, s_p3_to_p1.id_, True,
- {3: seen_3}, False)
+ {3: seen_3})
self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict)
# add step of process p3 as explicit sub-step to non-existing p1
# sub-step (of id=999), expect it to become another p1 top-level step
s_p3_to_p1_999 = ProcessStep(None, p1.id_, p3.id_, 999)
steps_p1 += [s_p3_to_p1_999]
p1.set_steps(self.db_conn, steps_p1)
- p1_dict[5] = ProcessStepsNode(p3, None, True, {}, False)
+ p1_dict[5] = ProcessStepsNode(p3, None, True, {})
self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict)
# add step of process p3 as explicit sub-step to p1's implicit p3
# sub-step, expect it to become another p1 top-level step
s_p3_to_p1_impl_p3 = ProcessStep(None, p1.id_, p3.id_, s_p3_to_p2.id_)
steps_p1 += [s_p3_to_p1_impl_p3]
p1.set_steps(self.db_conn, steps_p1)
- p1_dict[6] = ProcessStepsNode(p3, None, True, {}, False)
+ p1_dict[6] = ProcessStepsNode(p3, None, True, {})
self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict)
self.assertEqual(p1.used_as_step_by(self.db_conn), [])
self.assertEqual(p2.used_as_step_by(self.db_conn), [p1])
# # expect it to eliminate implicit p3 sub-step
# s_p3_to_p1_first_explicit = ProcessStep(None, p1.id_, p3.id_,
# s_p2_to_p1.id_)
- # p1_dict[1].steps = {7: ProcessStepsNode(p3, 1, True, {}, False)}
+ # p1_dict[1].steps = {7: ProcessStepsNode(p3, 1, True, {})}
# p1_dict[2].steps[4].steps[3].seen = False
# steps_p1 += [s_p3_to_p1_first_explicit]
# p1.set_steps(self.db_conn, steps_p1)
s_p3_to_p2_first = ProcessStep(None, p2.id_, p3.id_, s_p3_to_p2.id_)
steps_p2 += [s_p3_to_p2_first]
p2.set_steps(self.db_conn, steps_p2)
- p1_dict[1].steps[3].steps[7] = ProcessStepsNode(p3, 3, False, {},
- False)
+ p1_dict[1].steps[3].steps[7] = ProcessStepsNode(p3, 3, False, {})
p1_dict[2].steps[4].steps[3].steps[7] = ProcessStepsNode(p3, 3, False,
{}, True)
self.assertEqual(p1.get_steps(self.db_conn, None), p1_dict)
+ # ensure suppressed step nodes are hidden
+ assert isinstance(s_p3_to_p2.id_, int)
+ p1.set_step_suppressions(self.db_conn, [s_p3_to_p2.id_])
+ p1_dict[1].steps[3].steps = {}
+ p1_dict[1].steps[3].is_suppressed = True
+ p1_dict[2].steps[4].steps[3].steps = {}
+ p1_dict[2].steps[4].steps[3].is_suppressed = True
+ self.assertEqual(p1.get_steps(self.db_conn), p1_dict)
def test_Process_conditions(self) -> None:
"""Test setting Process.conditions/enables/disables."""