From ee18435127ad396c24dbee2c7efcdbe6810d5a91 Mon Sep 17 00:00:00 2001 From: Christian Heller <c.heller@plomlompom.de> Date: Sun, 28 Apr 2024 22:00:24 +0200 Subject: [PATCH] Enable deletion of Processes. --- plomtask/conditions.py | 2 +- plomtask/db.py | 9 ++++++- plomtask/http.py | 4 +++ plomtask/processes.py | 16 ++++++++++++ templates/process.html | 55 +++++++++++++++++++++++++++++++----------- tests/processes.py | 16 ++++++++++++ 6 files changed, 86 insertions(+), 16 deletions(-) diff --git a/plomtask/conditions.py b/plomtask/conditions.py index 6696125..a452600 100644 --- a/plomtask/conditions.py +++ b/plomtask/conditions.py @@ -7,7 +7,7 @@ from plomtask.misc import VersionedAttribute class Condition(BaseModel[int]): - """Non Process-dependency for ProcessSteps and Todos.""" + """Non-Process dependency for ProcessSteps and Todos.""" table_name = 'conditions' to_save = ['is_active'] diff --git a/plomtask/db.py b/plomtask/db.py index ebd8c6c..deeb748 100644 --- a/plomtask/db.py +++ b/plomtask/db.py @@ -102,7 +102,8 @@ class DatabaseConnection: return [row[0] for row in self.exec(f'SELECT {column} FROM {table_name}')] - def delete_where(self, table_name: str, key: str, target: int) -> None: + def delete_where(self, table_name: str, key: str, + target: int | str) -> None: """Delete from table where key == target.""" self.exec(f'DELETE FROM {table_name} WHERE {key} = ?', (target,)) @@ -234,3 +235,9 @@ class BaseModel(Generic[BaseModelId]): if update_with_lastrowid: self.id_ = cursor.lastrowid # type: ignore[assignment] self.cache() + + def remove(self, db_conn: DatabaseConnection) -> None: + """Remove from DB.""" + assert isinstance(self.id_, int | str) + self.uncache() + db_conn.delete_where(self.table_name, 'id', self.id_) diff --git a/plomtask/http.py b/plomtask/http.py index c848600..b4c2b08 100644 --- a/plomtask/http.py +++ b/plomtask/http.py @@ -236,6 +236,10 @@ class TaskHandler(BaseHTTPRequestHandler): def do_POST_process(self) -> None: """Update or insert Process of ?id= and fields defined in postvars.""" id_ = self.params.get_int_or_none('id') + for _ in self.form_data.get_all_str('delete'): + process = Process.by_id(self.conn, id_) + process.remove(self.conn) + return process = Process.by_id(self.conn, id_, create=True) process.title.set(self.form_data.get_str('title')) process.description.set(self.form_data.get_str('description')) diff --git a/plomtask/processes.py b/plomtask/processes.py index c0b13b5..e67b134 100644 --- a/plomtask/processes.py +++ b/plomtask/processes.py @@ -169,6 +169,16 @@ class Process(BaseModel[int], ConditionsRelations): for step in self.explicit_steps: step.save(db_conn) + def remove(self, db_conn: DatabaseConnection) -> None: + """Remove from DB, with dependencies.""" + assert isinstance(self.id_, int) + db_conn.delete_where('process_conditions', 'process', self.id_) + db_conn.delete_where('process_enables', 'process', self.id_) + db_conn.delete_where('process_disables', 'process', self.id_) + for step in self.explicit_steps: + step.remove(db_conn) + super().remove(db_conn) + class ProcessStep(BaseModel[int]): """Sub-unit of Processes.""" @@ -185,3 +195,9 @@ class ProcessStep(BaseModel[int]): def save(self, db_conn: DatabaseConnection) -> None: """Default to simply calling self.save_core for simple cases.""" self.save_core(db_conn) + + def remove(self, db_conn: DatabaseConnection) -> None: + """Remove from DB, and owner's .explicit_steps.""" + owner = Process.by_id(db_conn, self.owner_id) + owner.explicit_steps.remove(self) + super().remove(db_conn) diff --git a/templates/process.html b/templates/process.html index ec06d3a..8c219c6 100644 --- a/templates/process.html +++ b/templates/process.html @@ -33,10 +33,22 @@ add step: <input name="new_step_to_{{step_id}}" list="candidates" autocomplete=" {% block content %} <h3>process</h3> <form action="process?id={{process.id_ or ''}}" method="POST"> -title: <input name="title" value="{{process.title.newest|e}}" /> -description: <input name="description" value="{{process.description.newest|e}}" /> -default effort: <input name="effort" type="number" step=0.1 value={{process.effort.newest}} /> -<h4>conditions</h4> +<table> +<tr> +<th>title</th> +<td><input name="title" value="{{process.title.newest|e}}" /></td> +</tr> +<tr> +<th>default effort</th> +<td><input name="effort" type="number" step=0.1 value={{process.effort.newest}} /></td> +</tr> +<tr> +<th>description</th> +<td><textarea name="description">{{process.description.newest|e}}</textarea></td> +</tr> +<tr> +<th>conditions</th> +<td> <table> {% for condition in process.conditions %} <tr> @@ -50,12 +62,11 @@ default effort: <input name="effort" type="number" step=0.1 value={{process.effo {% endfor %} </table> add condition: <input name="condition" list="condition_candidates" autocomplete="off" /> -<datalist id="condition_candidates"> -{% for condition_candidate in condition_candidates %} -<option value="{{condition_candidate.id_}}">{{condition_candidate.title.newest|e}}</option> -{% endfor %} -</datalist> -<h4>enables</h4> +</td> +</tr> +<tr> +<th>enables</th> +<td> <table> {% for condition in process.enables %} <tr> @@ -69,7 +80,11 @@ add condition: <input name="condition" list="condition_candidates" autocomplete= {% endfor %} </table> add enables: <input name="enables" list="condition_candidates" autocomplete="off" /> -<h4>disables</h4> +</td> +</tr> +<tr> +<th>disables</th> +<td> <table> {% for condition in process.disables %} <tr> @@ -83,20 +98,32 @@ add enables: <input name="enables" list="condition_candidates" autocomplete="off {% endfor %} </table> add disables: <input name="disables" list="condition_candidates" autocomplete="off" /> -<h4>steps</h4> +</td> +</tr> +<tr> +<th>steps</th> +<td> <table> {% for step_id, step_node in steps.items() %} {{ step_with_steps(step_id, step_node, 0) }} {% endfor %} </table> add step: <input name="new_top_step" list="step_candidates" autocomplete="off" /> +</td> +<tr> +</table> +<datalist id="condition_candidates"> +{% for condition_candidate in condition_candidates %} +<option value="{{condition_candidate.id_}}">{{condition_candidate.title.newest|e}}</option> +{% endfor %} +</datalist> <datalist id="step_candidates"> {% for candidate in step_candidates %} <option value="{{candidate.id_}}">{{candidate.title.newest|e}}</option> {% endfor %} </datalist> -<h4>save</h4> -<input type="submit" value="OK" /> +<input type="submit" name="update" value="update" /> +<input type="submit" name="delete" value="delete" /> </form> <h4>step of</h4> <ul> diff --git a/tests/processes.py b/tests/processes.py index c718677..6695f78 100644 --- a/tests/processes.py +++ b/tests/processes.py @@ -157,6 +157,22 @@ class TestsWithDB(TestCaseWithDB): p_loaded = Process.by_id(self.db_conn, self.proc1.id_) self.assertEqual(self.proc1.title.history, p_loaded.title.history) + def test_Process_removal(self) -> None: + """Test removal of Processes.""" + assert isinstance(self.proc3.id_, int) + self.proc1.remove(self.db_conn) + self.assertEqual({self.proc2.id_, self.proc3.id_}, + set(p.id_ for p in Process.all(self.db_conn))) + self.proc2.set_steps(self.db_conn, [(None, self.proc3.id_, None)]) + self.proc2.explicit_steps[0].remove(self.db_conn) + retrieved = Process.by_id(self.db_conn, self.proc2.id_) + self.assertEqual(retrieved.explicit_steps, []) + self.proc2.set_steps(self.db_conn, [(None, self.proc3.id_, None)]) + step = retrieved.explicit_steps[0] + self.proc2.remove(self.db_conn) + with self.assertRaises(NotFoundException): + ProcessStep.by_id(self.db_conn, step.id_) + class TestsWithServer(TestCaseWithServer): """Module tests against our HTTP server/handler (and database).""" -- 2.30.2