home · contact · privacy
Add Todo. and Process.calendarize to identify what Todos to show in calendar.
authorChristian Heller <c.heller@plomlompom.de>
Sat, 18 May 2024 06:04:10 +0000 (08:04 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Sat, 18 May 2024 06:04:10 +0000 (08:04 +0200)
migrations/3_add_Todo_and_Process_calendarize.sql [new file with mode: 0644]
migrations/init_2.sql [deleted file]
migrations/init_3.sql [new file with mode: 0644]
plomtask/days.py
plomtask/db.py
plomtask/http.py
plomtask/processes.py
plomtask/todos.py
templates/calendar.html
templates/process.html
templates/todo.html

diff --git a/migrations/3_add_Todo_and_Process_calendarize.sql b/migrations/3_add_Todo_and_Process_calendarize.sql
new file mode 100644 (file)
index 0000000..dcd65b2
--- /dev/null
@@ -0,0 +1,2 @@
+ALTER TABLE todos ADD COLUMN calendarize BOOLEAN NOT NULL DEFAULT FALSE;
+ALTER TABLE processes ADD COLUMN calendarize BOOLEAN NOT NULL DEFAULT FALSE;
diff --git a/migrations/init_2.sql b/migrations/init_2.sql
deleted file mode 100644 (file)
index 17e2db5..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-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_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
-);
-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,
-    FOREIGN KEY (process) REFERENCES processes(id),
-    FOREIGN KEY (day) REFERENCES days(id)
-);
diff --git a/migrations/init_3.sql b/migrations/init_3.sql
new file mode 100644 (file)
index 0000000..f261fd7
--- /dev/null
@@ -0,0 +1,116 @@
+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_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_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)
+);
index 5e975602d112a81f71b7bafb47b2939f9084d365..fe1ba44e80e9a1c4182130b870d6d531dc9c9c06 100644 (file)
@@ -3,6 +3,7 @@ from __future__ import annotations
 from datetime import datetime, timedelta
 from plomtask.exceptions import BadFormatException
 from plomtask.db import DatabaseConnection, BaseModel
 from datetime import datetime, timedelta
 from plomtask.exceptions import BadFormatException
 from plomtask.db import DatabaseConnection, BaseModel
+from plomtask.todos import Todo
 
 DATE_FORMAT = '%Y-%m-%d'
 MIN_RANGE_DATE = '2024-01-01'
 
 DATE_FORMAT = '%Y-%m-%d'
 MIN_RANGE_DATE = '2024-01-01'
@@ -36,6 +37,7 @@ class Day(BaseModel[str]):
         super().__init__(id_)
         self.datetime = datetime.strptime(self.date, DATE_FORMAT)
         self.comment = comment
         super().__init__(id_)
         self.datetime = datetime.strptime(self.date, DATE_FORMAT)
         self.comment = comment
+        self.calendarized_todos: list[Todo] = []
 
     def __lt__(self, other: Day) -> bool:
         return self.date < other.date
 
     def __lt__(self, other: Day) -> bool:
         return self.date < other.date
@@ -105,3 +107,8 @@ class Day(BaseModel[str]):
         """Return date succeeding date of this Day."""
         next_datetime = self.datetime + timedelta(days=1)
         return next_datetime.strftime(DATE_FORMAT)
         """Return date succeeding date of this Day."""
         next_datetime = self.datetime + timedelta(days=1)
         return next_datetime.strftime(DATE_FORMAT)
+
+    def collect_calendarized_todos(self, db_conn: DatabaseConnection) -> None:
+        """Fill self.calendarized_todos."""
+        self.calendarized_todos = [t for t in Todo.by_date(db_conn, self.date)
+                                   if t.calendarize]
index 0e7df150da987334a8843a3d2ba8c084a56a917d..b4dc3e982c496833e7962ab02dc643c027235c1e 100644 (file)
@@ -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
 
 from typing import Any, Self, TypeVar, Generic
 from plomtask.exceptions import HandledException, NotFoundException
 
-EXPECTED_DB_VERSION = 2
+EXPECTED_DB_VERSION = 3
 MIGRATIONS_DIR = 'migrations'
 FILENAME_DB_SCHEMA = f'init_{EXPECTED_DB_VERSION}.sql'
 PATH_DB_SCHEMA = f'{MIGRATIONS_DIR}/{FILENAME_DB_SCHEMA}'
 MIGRATIONS_DIR = 'migrations'
 FILENAME_DB_SCHEMA = f'init_{EXPECTED_DB_VERSION}.sql'
 PATH_DB_SCHEMA = f'{MIGRATIONS_DIR}/{FILENAME_DB_SCHEMA}'
@@ -131,6 +131,9 @@ class DatabaseFile:  # pylint: disable=too-few-public-methods
                         new_row += [f'    {segment}']
                 new_row[0] = new_row[0].lstrip()
                 new_row[-1] = new_row[-1].lstrip()
                         new_row += [f'    {segment}']
                 new_row[0] = new_row[0].lstrip()
                 new_row[-1] = new_row[-1].lstrip()
+                if new_row[-1] != ')' and new_row[-3][-1] != ',':
+                    new_row[-3] = new_row[-3] + ','
+                    new_row[-2:] = ['    ' + new_row[-1][:-1]] + [')']
                 new_rows += ['\n'.join(new_row)]
             return new_rows
 
                 new_rows += ['\n'.join(new_row)]
             return new_rows
 
index d76ac052014c1b9f8d548ed24267561aead04892..080af8ce1a127880368c8c4bedb3cf4e0aebaffc 100644 (file)
@@ -113,6 +113,8 @@ class TaskHandler(BaseHTTPRequestHandler):
         start = self.params.get_str('start')
         end = self.params.get_str('end')
         days = Day.all(self.conn, date_range=(start, end), fill_gaps=True)
         start = self.params.get_str('start')
         end = self.params.get_str('end')
         days = Day.all(self.conn, date_range=(start, end), fill_gaps=True)
+        for day in days:
+            day.collect_calendarized_todos(self.conn)
         return {'start': start, 'end': end, 'days': days}
 
     def do_GET_day(self) -> dict[str, object]:
         return {'start': start, 'end': end, 'days': days}
 
     def do_GET_day(self) -> dict[str, object]:
@@ -302,6 +304,7 @@ class TaskHandler(BaseHTTPRequestHandler):
         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
         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
+        todo.calendarize = len(self.form_data.get_all_str('calendarize')) > 0
         todo.comment = self.form_data.get_str('comment', ignore_strict=True)
         todo.save(self.conn)
         for condition in todo.enables:
         todo.comment = self.form_data.get_str('comment', ignore_strict=True)
         todo.save(self.conn)
         for condition in todo.enables:
@@ -325,6 +328,7 @@ class TaskHandler(BaseHTTPRequestHandler):
                                self.form_data.get_all_int('condition'))
         process.set_enables(self.conn, self.form_data.get_all_int('enables'))
         process.set_disables(self.conn, self.form_data.get_all_int('disables'))
                                self.form_data.get_all_int('condition'))
         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') != []
         process.save(self.conn)
         process.explicit_steps = []
         steps: list[tuple[int | None, int, int | None]] = []
         process.save(self.conn)
         process.explicit_steps = []
         steps: list[tuple[int | None, int, int | None]] = []
index 1778e4f73b8eff992322b9b1e166f6987047e05d..e1364215cfe57e5c66d376cd95cf721fe4f6f65a 100644 (file)
@@ -22,20 +22,21 @@ class ProcessStepsNode:
 
 class Process(BaseModel[int], ConditionsRelations):
     """Template for, and metadata for, Todos, and their arrangements."""
 
 class Process(BaseModel[int], ConditionsRelations):
     """Template for, and metadata for, Todos, and their arrangements."""
+    # pylint: disable=too-many-instance-attributes
     table_name = 'processes'
     table_name = 'processes'
+    to_save = ['calendarize']
     to_save_versioned = ['title', 'description', 'effort']
     to_save_relations = [('process_conditions', 'process', 'conditions'),
                          ('process_enables', 'process', 'enables'),
                          ('process_disables', 'process', 'disables')]
 
     to_save_versioned = ['title', 'description', 'effort']
     to_save_relations = [('process_conditions', 'process', 'conditions'),
                          ('process_enables', 'process', 'enables'),
                          ('process_disables', 'process', 'disables')]
 
-    # pylint: disable=too-many-instance-attributes
-
-    def __init__(self, id_: int | None) -> None:
+    def __init__(self, id_: int | None, calendarize: bool = False) -> None:
         super().__init__(id_)
         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] = []
         super().__init__(id_)
         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] = []
         self.conditions: list[Condition] = []
         self.enables: list[Condition] = []
         self.disables: list[Condition] = []
index 7cbe989b538018e480d24752d2d0cc44450f7396..0fea23445ac1881a18ea87eca95cba7f03510f95 100644 (file)
@@ -23,7 +23,8 @@ class Todo(BaseModel[int], ConditionsRelations):
     """Individual actionable."""
     # pylint: disable=too-many-instance-attributes
     table_name = 'todos'
     """Individual actionable."""
     # pylint: disable=too-many-instance-attributes
     table_name = 'todos'
-    to_save = ['process_id', 'is_done', 'date', 'comment', 'effort']
+    to_save = ['process_id', 'is_done', 'date', 'comment', 'effort',
+               'calendarize']
     to_save_relations = [('todo_conditions', 'todo', 'conditions'),
                          ('todo_enables', 'todo', 'enables'),
                          ('todo_disables', 'todo', 'disables'),
     to_save_relations = [('todo_conditions', 'todo', 'conditions'),
                          ('todo_enables', 'todo', 'enables'),
                          ('todo_disables', 'todo', 'disables'),
@@ -31,9 +32,12 @@ class Todo(BaseModel[int], ConditionsRelations):
                          ('todo_children', 'child', 'parents')]
 
     # pylint: disable=too-many-arguments
                          ('todo_children', 'child', 'parents')]
 
     # pylint: disable=too-many-arguments
-    def __init__(self, id_: int | None, process: Process,
-                 is_done: bool, date: str, comment: str = '',
-                 effort: None | float = None) -> None:
+    def __init__(self, id_: int | None,
+                 process: Process,
+                 is_done: bool,
+                 date: str, comment: str = '',
+                 effort: None | float = None,
+                 calendarize: bool = False) -> None:
         super().__init__(id_)
         if process.id_ is None:
             raise NotFoundException('Process of Todo without ID (not saved?)')
         super().__init__(id_)
         if process.id_ is None:
             raise NotFoundException('Process of Todo without ID (not saved?)')
@@ -44,10 +48,12 @@ class Todo(BaseModel[int], ConditionsRelations):
         self.effort = effort
         self.children: list[Todo] = []
         self.parents: list[Todo] = []
         self.effort = effort
         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.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.enables = self.process.enables[:]
             self.disables = self.process.disables[:]
             self.conditions = self.process.conditions[:]
             self.enables = self.process.enables[:]
             self.disables = self.process.disables[:]
index 220e9bb0f8ced2fc07cba917c3256d1b1fa82c2a..77242037982762100a20e21bf0d8b453877c7fe3 100644 (file)
@@ -49,6 +49,14 @@ to <input name="end" value="{{end}}" />
 <td>{{day.comment|e}}</td>
 </tr>
 
 <td>{{day.comment|e}}</td>
 </tr>
 
+{% for todo in day.calendarized_todos %}
+<tr>
+<td>[{% if todo.is_done %}X{% else %} {% endif %}]</td>
+<td><a href="todo?id={{todo.id_}}">{{todo.title.newest|e}}</td>
+<td>{{todo.comment|e}}</td>
+</tr>
+{% endfor %}
+
 {% endfor %}
 </table>
 {% endblock %}
 {% endfor %}
 </table>
 {% endblock %}
index d833c52a838e143c27452aa962936c88eb8a815e..6dea4937527248d1edbe38911723895bc2399e94 100644 (file)
@@ -55,6 +55,11 @@ add: <input name="new_step_to_{{step_id}}" list="candidates" autocomplete="off"
 <td><textarea name="description">{{process.description.newest|e}}</textarea><br />{% if process.id_ %} [<a href="process_descriptions?id={{process.id_}}">history</a>]{% endif %}</td>
 </tr>
 
 <td><textarea name="description">{{process.description.newest|e}}</textarea><br />{% if process.id_ %} [<a href="process_descriptions?id={{process.id_}}">history</a>]{% endif %}</td>
 </tr>
 
+<tr>
+<th>calendarize</th>
+<td><input type="checkbox" name="calendarize" {% if process.calendarize %}checked {% endif %}</td>
+</tr>
+
 <tr>
 <th>conditions</th>
 <td>{{ macros.simple_checkbox_table("condition", process.conditions, "condition", "condition_candidates") }}</td>
 <tr>
 <th>conditions</th>
 <td>{{ macros.simple_checkbox_table("condition", process.conditions, "condition", "condition_candidates") }}</td>
index 6817cb9fb5c037ba08d0bc2798ca105dc8fd4781..efaabdd2a4de4f6aba490e1fe77b618782434104 100644 (file)
 <td><input name="comment" value="{{todo.comment|e}}"/></td>
 </tr>
 
 <td><input name="comment" value="{{todo.comment|e}}"/></td>
 </tr>
 
+<tr>
+<th>calendarize</th>
+<td><input type="checkbox" name="calendarize" {% if todo.calendarize %}checked {% endif %}</td>
+</tr>
+
 <tr>
 <th>conditions</th>
 <td>{{ macros.simple_checkbox_table("condition", todo.conditions, "condition", "condition_candidates") }}</td>
 <tr>
 <th>conditions</th>
 <td>{{ macros.simple_checkbox_table("condition", todo.conditions, "condition", "condition_candidates") }}</td>