home · contact · privacy
Enable deletion of Processes.
authorChristian Heller <c.heller@plomlompom.de>
Sun, 28 Apr 2024 20:00:24 +0000 (22:00 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Sun, 28 Apr 2024 20:00:24 +0000 (22:00 +0200)
plomtask/conditions.py
plomtask/db.py
plomtask/http.py
plomtask/processes.py
templates/process.html
tests/processes.py

index 66961256e28ad2843ae9d72ba9babbbd552b6f60..a45260092fdc6c75a94846eb0893b775eb9316dd 100644 (file)
@@ -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']
 
index ebd8c6c544fd9dd3aca7e040bccb3d354629806d..deeb748de0b427b1ff2f999e58713d3cee4e7807 100644 (file)
@@ -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_)
index c8486003ce48fb785474432f512b08a380193a32..b4c2b0886c08baada1f848eb66e832a0ade99b72 100644 (file)
@@ -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'))
index c0b13b551862b0c8a00c927c4cc0665ae178daeb..e67b13414e26d40a87e77545ce57d238bc050028 100644 (file)
@@ -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)
index ec06d3a4c44232032266d07a1a1d42ab924632a4..8c219c6233a748db6e73bb2a37ca0a86c113f7fb 100644 (file)
@@ -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>
index c718677f78e7afea5a95fdaa65e3c3981df96f1b..6695f78363656cffb5963b539c23bf2444db1703 100644 (file)
@@ -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)."""