From eb16b47ddcaefaeab2f616419ea746cc32346893 Mon Sep 17 00:00:00 2001 From: Christian Heller Date: Tue, 21 May 2024 02:30:23 +0200 Subject: [PATCH] Add Todo/Process.blockers for Conditions that block rather than enable. --- .../4_create_Process_blockers_Todo_blockers.sql | 14 ++++++++++++++ migrations/{init_3.sql => init_4.sql} | 14 ++++++++++++++ plomtask/conditions.py | 13 ++++++++++++- plomtask/db.py | 2 +- plomtask/http.py | 9 ++++++++- plomtask/processes.py | 9 ++++----- plomtask/todos.py | 13 ++++++++----- templates/day.html | 7 ++++++- templates/process.html | 5 +++++ templates/todo.html | 5 +++++ 10 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 migrations/4_create_Process_blockers_Todo_blockers.sql rename migrations/{init_3.sql => init_4.sql} (88%) diff --git a/migrations/4_create_Process_blockers_Todo_blockers.sql b/migrations/4_create_Process_blockers_Todo_blockers.sql new file mode 100644 index 0000000..8e82ca1 --- /dev/null +++ b/migrations/4_create_Process_blockers_Todo_blockers.sql @@ -0,0 +1,14 @@ +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 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) +); diff --git a/migrations/init_3.sql b/migrations/init_4.sql similarity index 88% rename from migrations/init_3.sql rename to migrations/init_4.sql index f261fd7..067d934 100644 --- a/migrations/init_3.sql +++ b/migrations/init_4.sql @@ -20,6 +20,13 @@ 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, @@ -75,6 +82,13 @@ 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, diff --git a/plomtask/conditions.py b/plomtask/conditions.py index a6e9c97..8ab4282 100644 --- a/plomtask/conditions.py +++ b/plomtask/conditions.py @@ -40,7 +40,7 @@ class Condition(BaseModel[int]): if self.id_ is None: raise HandledException('cannot remove unsaved item') for item in ('process', 'todo'): - for attr in ('conditions', 'enables', 'disables'): + for attr in ('conditions', 'blockers', 'enables', 'disables'): table_name = f'{item}_{attr}' for _ in db_conn.row_where(table_name, 'condition', self.id_): raise HandledException('cannot remove Condition in use') @@ -50,6 +50,12 @@ class Condition(BaseModel[int]): class ConditionsRelations: """Methods for handling relations to Conditions, for Todo and Process.""" + def __init__(self) -> None: + self.conditions: list[Condition] = [] + self.blockers: list[Condition] = [] + self.enables: list[Condition] = [] + self.disables: list[Condition] = [] + def set_conditions(self, db_conn: DatabaseConnection, ids: list[int], target: str = 'conditions') -> None: """Set self.[target] to Conditions identified by ids.""" @@ -59,6 +65,11 @@ class ConditionsRelations: for id_ in ids: target_list += [Condition.by_id(db_conn, id_)] + def set_blockers(self, db_conn: DatabaseConnection, + ids: list[int]) -> None: + """Set self.enables to Conditions identified by ids.""" + self.set_conditions(db_conn, ids, 'blockers') + def set_enables(self, db_conn: DatabaseConnection, ids: list[int]) -> None: """Set self.enables to Conditions identified by ids.""" diff --git a/plomtask/db.py b/plomtask/db.py index b4dc3e9..d2791b1 100644 --- a/plomtask/db.py +++ b/plomtask/db.py @@ -7,7 +7,7 @@ from sqlite3 import connect as sql_connect, Cursor, Row from typing import Any, Self, TypeVar, Generic from plomtask.exceptions import HandledException, NotFoundException -EXPECTED_DB_VERSION = 3 +EXPECTED_DB_VERSION = 4 MIGRATIONS_DIR = 'migrations' FILENAME_DB_SCHEMA = f'init_{EXPECTED_DB_VERSION}.sql' PATH_DB_SCHEMA = f'{MIGRATIONS_DIR}/{FILENAME_DB_SCHEMA}' diff --git a/plomtask/http.py b/plomtask/http.py index 080af8c..41ce5d6 100644 --- a/plomtask/http.py +++ b/plomtask/http.py @@ -123,19 +123,24 @@ class TaskHandler(BaseHTTPRequestHandler): todays_todos = Todo.by_date(self.conn, date) conditions_present = [] enablers_for = {} + disablers_for = {} for todo in todays_todos: - for condition in todo.conditions: + for condition in todo.conditions + todo.blockers: if condition not in conditions_present: conditions_present += [condition] enablers_for[condition.id_] = [p for p in Process.all(self.conn) if condition in p.enables] + disablers_for[condition.id_] = [p for p in + Process.all(self.conn) + if condition in p.disables] seen_todos: set[int] = set() top_nodes = [t.get_step_tree(seen_todos) for t in todays_todos if not t.parents] return {'day': Day.by_id(self.conn, date, create=True), 'top_nodes': top_nodes, 'enablers_for': enablers_for, + 'disablers_for': disablers_for, 'conditions_present': conditions_present, 'processes': Process.all(self.conn)} @@ -301,6 +306,7 @@ class TaskHandler(BaseHTTPRequestHandler): effort = self.form_data.get_str('effort', ignore_strict=True) todo.effort = float(effort) if effort else None todo.set_conditions(self.conn, self.form_data.get_all_int('condition')) + todo.set_blockers(self.conn, self.form_data.get_all_int('blocker')) todo.set_enables(self.conn, self.form_data.get_all_int('enables')) todo.set_disables(self.conn, self.form_data.get_all_int('disables')) todo.is_done = len(self.form_data.get_all_str('done')) > 0 @@ -326,6 +332,7 @@ class TaskHandler(BaseHTTPRequestHandler): process.effort.set(self.form_data.get_float('effort')) process.set_conditions(self.conn, self.form_data.get_all_int('condition')) + 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.calendarize = self.form_data.get_all_str('calendarize') != [] diff --git a/plomtask/processes.py b/plomtask/processes.py index e136421..c23c6de 100644 --- a/plomtask/processes.py +++ b/plomtask/processes.py @@ -27,19 +27,18 @@ class Process(BaseModel[int], ConditionsRelations): to_save = ['calendarize'] to_save_versioned = ['title', 'description', 'effort'] to_save_relations = [('process_conditions', 'process', 'conditions'), + ('process_blockers', 'process', 'blockers'), ('process_enables', 'process', 'enables'), ('process_disables', 'process', 'disables')] def __init__(self, id_: int | None, calendarize: bool = False) -> None: - super().__init__(id_) + BaseModel.__init__(self, id_) + ConditionsRelations.__init__(self) self.title = VersionedAttribute(self, 'process_titles', 'UNNAMED') self.description = VersionedAttribute(self, 'process_descriptions', '') self.effort = VersionedAttribute(self, 'process_efforts', 1.0) self.explicit_steps: list[ProcessStep] = [] self.calendarize = calendarize - self.conditions: list[Condition] = [] - self.enables: list[Condition] = [] - self.disables: list[Condition] = [] @classmethod def from_table_row(cls, db_conn: DatabaseConnection, @@ -55,7 +54,7 @@ class Process(BaseModel[int], ConditionsRelations): process.id_): step = ProcessStep.from_table_row(db_conn, row_) process.explicit_steps += [step] # pylint: disable=no-member - for name in ('conditions', 'enables', 'disables'): + for name in ('conditions', 'blockers', 'enables', 'disables'): table = f'process_{name}' assert isinstance(process.id_, int) for c_id in db_conn.column_where(table, 'condition', diff --git a/plomtask/todos.py b/plomtask/todos.py index b3d50e9..a7af99f 100644 --- a/plomtask/todos.py +++ b/plomtask/todos.py @@ -26,6 +26,7 @@ class Todo(BaseModel[int], ConditionsRelations): to_save = ['process_id', 'is_done', 'date', 'comment', 'effort', 'calendarize'] to_save_relations = [('todo_conditions', 'todo', 'conditions'), + ('todo_blockers', 'todo', 'blockers'), ('todo_enables', 'todo', 'enables'), ('todo_disables', 'todo', 'disables'), ('todo_children', 'parent', 'children'), @@ -38,7 +39,8 @@ class Todo(BaseModel[int], ConditionsRelations): date: str, comment: str = '', effort: None | float = None, calendarize: bool = False) -> None: - super().__init__(id_) + BaseModel.__init__(self, id_) + ConditionsRelations.__init__(self) if process.id_ is None: raise NotFoundException('Process of Todo without ID (not saved?)') self.process = process @@ -49,12 +51,10 @@ class Todo(BaseModel[int], ConditionsRelations): self.children: list[Todo] = [] self.parents: list[Todo] = [] self.calendarize = calendarize - self.conditions: list[Condition] = [] - self.enables: list[Condition] = [] - self.disables: list[Condition] = [] if not self.id_: self.calendarize = self.process.calendarize self.conditions = self.process.conditions[:] + self.blockers = self.process.blockers[:] self.enables = self.process.enables[:] self.disables = self.process.disables[:] @@ -77,7 +77,7 @@ class Todo(BaseModel[int], ConditionsRelations): 'child', todo.id_): # pylint: disable=no-member todo.parents += [cls.by_id(db_conn, t_id)] - for name in ('conditions', 'enables', 'disables'): + for name in ('conditions', 'blockers', 'enables', 'disables'): table = f'todo_{name}' assert isinstance(todo.id_, int) for cond_id in db_conn.column_where(table, 'condition', @@ -103,6 +103,9 @@ class Todo(BaseModel[int], ConditionsRelations): for condition in self.conditions: if not condition.is_active: return False + for condition in self.blockers: + if condition.is_active: + return False return True @property diff --git a/templates/day.html b/templates/day.html index 9af3754..6954fd3 100644 --- a/templates/day.html +++ b/templates/day.html @@ -38,7 +38,7 @@ td.todo_line { {% endif %} {% for condition in conditions_present %} -{% if condition in node.todo.conditions %}{% if not condition.is_active %}O{% endif %}{% endif %} +{% if condition in node.todo.conditions and not condition.is_active %}O{% elif condition in node.todo.blockers and condition.is_active %}!{% endif %} {% endfor %} -> @@ -131,6 +131,7 @@ add todo: states t add enabler +add disabler {% for condition in conditions_present %} @@ -162,6 +163,10 @@ add todo: {{ macros.datalist_of_titles(list_name, enablers_for[condition.id_]) }} +{% set list_name = "todos_against_%s"|format(condition.id_) %} + +{{ macros.datalist_of_titles(list_name, disablers_for[condition.id_]) }} + {% endfor %} diff --git a/templates/process.html b/templates/process.html index 6dea493..7ad59b8 100644 --- a/templates/process.html +++ b/templates/process.html @@ -65,6 +65,11 @@ add: {{ macros.simple_checkbox_table("condition", process.conditions, "condition", "condition_candidates") }} + +blockers +{{ macros.simple_checkbox_table("blocker", process.blockers, "condition", "condition_candidates") }} + + enables {{ macros.simple_checkbox_table("enables", process.enables, "condition", "condition_candidates") }} diff --git a/templates/todo.html b/templates/todo.html index efaabdd..a711568 100644 --- a/templates/todo.html +++ b/templates/todo.html @@ -43,6 +43,11 @@ {{ macros.simple_checkbox_table("condition", todo.conditions, "condition", "condition_candidates") }} + +blockers +{{ macros.simple_checkbox_table("blocker", todo.blockers, "condition", "condition_candidates") }} + + enables {{ macros.simple_checkbox_table("enables", todo.enables, "condition", "condition_candidates") }} -- 2.30.2