home · contact · privacy
Use same date ranging code for Day and Todo filtering.
authorChristian Heller <c.heller@plomlompom.de>
Thu, 30 May 2024 06:12:33 +0000 (08:12 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Thu, 30 May 2024 06:12:33 +0000 (08:12 +0200)
plomtask/dating.py [new file with mode: 0644]
plomtask/days.py
plomtask/db.py
plomtask/http.py
plomtask/todos.py
tests/days.py
tests/todos.py

diff --git a/plomtask/dating.py b/plomtask/dating.py
new file mode 100644 (file)
index 0000000..e143fb6
--- /dev/null
@@ -0,0 +1,24 @@
+"""Various utilities for handling dates."""
+from datetime import datetime
+from plomtask.exceptions import BadFormatException
+
+DATE_FORMAT = '%Y-%m-%d'
+MIN_RANGE_DATE = '2024-01-01'
+MAX_RANGE_DATE = '2030-12-31'
+
+
+def valid_date(date_str: str) -> str:
+    """Validate date against DATE_FORMAT or 'today', return in DATE_FORMAT."""
+    if date_str == 'today':
+        date_str = todays_date()
+    try:
+        dt = datetime.strptime(date_str, DATE_FORMAT)
+    except (ValueError, TypeError) as e:
+        msg = f'Given date of wrong format: {date_str}'
+        raise BadFormatException(msg) from e
+    return dt.strftime(DATE_FORMAT)
+
+
+def todays_date() -> str:
+    """Return current date in DATE_FORMAT."""
+    return datetime.now().strftime(DATE_FORMAT)
index e3d56d7fb1049c161b146af555b05ccf2ed2f904..d7083b4310bf76c168f6f33d29cf55c8ada7a29f 100644 (file)
@@ -1,30 +1,9 @@
 """Collecting Day and date-related items."""
 from __future__ import annotations
 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'
-MAX_RANGE_DATE = '2030-12-31'
-
-
-def valid_date(date_str: str) -> str:
-    """Validate date against DATE_FORMAT or 'today', return in DATE_FORMAT."""
-    if date_str == 'today':
-        date_str = todays_date()
-    try:
-        dt = datetime.strptime(date_str, DATE_FORMAT)
-    except (ValueError, TypeError) as e:
-        msg = f'Given date of wrong format: {date_str}'
-        raise BadFormatException(msg) from e
-    return dt.strftime(DATE_FORMAT)
-
-
-def todays_date() -> str:
-    """Return current date in DATE_FORMAT."""
-    return datetime.now().strftime(DATE_FORMAT)
+from plomtask.dating import (DATE_FORMAT, valid_date)
 
 
 class Day(BaseModel[str]):
@@ -48,20 +27,11 @@ class Day(BaseModel[str]):
             fill_gaps: bool = False) -> list[Day]:
         """Return list of Days in database within (open) date_range interval.
 
-        If no range values provided, defaults them to MIN_RANGE_DATE and
-        MAX_RANGE_DATE. Also knows to properly interpret 'today' as value.
-
         On fill_gaps=True, will instantiate (without saving) Days of all dates
         within the date range that don't exist yet.
         """
-        min_date = '2024-01-01'
-        max_date = '2030-12-31'
-        start_date = valid_date(date_range[0] if date_range[0] else min_date)
-        end_date = valid_date(date_range[1] if date_range[1] else max_date)
-        days = []
-        sql = 'SELECT id FROM days WHERE id >= ? AND id <= ?'
-        for row in db_conn.exec(sql, (start_date, end_date)):
-            days += [cls.by_id(db_conn, row[0])]
+        ret = cls.by_date_range_with_limits(db_conn, date_range, 'id')
+        days, start_date, end_date = ret
         days.sort()
         if fill_gaps:
             if start_date not in [d.date for d in days]:
index 548381e72e5c473047d7b90cb7f55ea1627b0ca6..4396b444a61f0c2f59e2046b554e79c56e541faa 100644 (file)
@@ -6,6 +6,7 @@ from difflib import Differ
 from sqlite3 import connect as sql_connect, Cursor, Row
 from typing import Any, Self, TypeVar, Generic
 from plomtask.exceptions import HandledException, NotFoundException
+from plomtask.dating import (MIN_RANGE_DATE, MAX_RANGE_DATE, valid_date)
 
 EXPECTED_DB_VERSION = 4
 MIGRATIONS_DIR = 'migrations'
@@ -354,6 +355,29 @@ class BaseModel(Generic[BaseModelId]):
                 items[item.id_] = item
         return list(items.values())
 
+    @classmethod
+    def by_date_range_with_limits(cls: type[BaseModelInstance],
+                                  db_conn: DatabaseConnection,
+                                  date_range: tuple[str, str] = ('', ''),
+                                  date_col: str = 'day'
+                                  ) -> tuple[list[BaseModelInstance], str,
+                                             str]:
+        """Return list of Days in database within (open) date_range interval.
+
+        If no range values provided, defaults them to MIN_RANGE_DATE and
+        MAX_RANGE_DATE. Also knows to properly interpret 'today' as value.
+        """
+        min_date = MIN_RANGE_DATE
+        max_date = MAX_RANGE_DATE
+        start_date = valid_date(date_range[0] if date_range[0] else min_date)
+        end_date = valid_date(date_range[1] if date_range[1] else max_date)
+        items = []
+        sql = f'SELECT id FROM {cls.table_name} '
+        sql += f'WHERE {date_col} >= ? AND {date_col} <= ?'
+        for row in db_conn.exec(sql, (start_date, end_date)):
+            items += [cls.by_id(db_conn, row[0])]
+        return items, start_date, end_date
+
     @classmethod
     def matching(cls: type[BaseModelInstance], db_conn: DatabaseConnection,
                  pattern: str) -> list[BaseModelInstance]:
index b81083b26e35a4d3a6dfb140781d95f14bae5c3c..a2e8fa69fd05d4423751a4fa3ad3623ed4dffe8c 100644 (file)
@@ -5,7 +5,8 @@ from http.server import HTTPServer
 from urllib.parse import urlparse, parse_qs
 from os.path import split as path_split
 from jinja2 import Environment as JinjaEnv, FileSystemLoader as JinjaFSLoader
-from plomtask.days import Day, todays_date
+from plomtask.dating import todays_date
+from plomtask.days import Day
 from plomtask.exceptions import HandledException, BadFormatException, \
         NotFoundException
 from plomtask.db import DatabaseConnection, DatabaseFile
@@ -160,11 +161,9 @@ class TaskHandler(BaseHTTPRequestHandler):
         process_id = self.params.get_int_or_none('process_id')
         comment_pattern = self.params.get_str('comment_pattern')
         todos = []
-        for t in Todo.matching(self.conn, comment_pattern):
-            # pylint: disable=too-many-boolean-expressions
-            if (start and t.date < start)\
-                    or (end and t.date > end)\
-                    or (process_id and t.process.id_ != process_id):
+        for t in Todo.by_date_range(self.conn, (start, end)):
+            if (process_id and t.process.id_ != process_id)\
+                    or (comment_pattern not in t.comment):
                 continue
             todos += [t]
         if sort_by == 'doneness':
index ffef677d29e652ad82d18e2d2c9f7b848e73a698..46a353d91456a13b7968ba6718e02f1ea70e81a8 100644 (file)
@@ -9,6 +9,7 @@ from plomtask.versioned_attributes import VersionedAttribute
 from plomtask.conditions import Condition, ConditionsRelations
 from plomtask.exceptions import (NotFoundException, BadFormatException,
                                  HandledException)
+from plomtask.dating import valid_date
 
 
 @dataclass
@@ -46,7 +47,7 @@ class Todo(BaseModel[int], ConditionsRelations):
             raise NotFoundException('Process of Todo without ID (not saved?)')
         self.process = process
         self._is_done = is_done
-        self.date = date
+        self.date = valid_date(date)
         self.comment = comment
         self.effort = effort
         self.children: list[Todo] = []
@@ -59,6 +60,13 @@ class Todo(BaseModel[int], ConditionsRelations):
             self.enables = self.process.enables[:]
             self.disables = self.process.disables[:]
 
+    @classmethod
+    def by_date_range(cls, db_conn: DatabaseConnection,
+                      date_range: tuple[str, str] = ('', '')) -> list[Todo]:
+        """Collect Todos of Days within date_range."""
+        todos, _, _ = cls.by_date_range_with_limits(db_conn, date_range)
+        return todos
+
     @classmethod
     def create_with_children(cls, db_conn: DatabaseConnection, date: str,
                              process_ids: list[int]) -> list[Todo]:
@@ -117,10 +125,7 @@ class Todo(BaseModel[int], ConditionsRelations):
     @classmethod
     def by_date(cls, db_conn: DatabaseConnection, date: str) -> list[Todo]:
         """Collect all Todos for Day of date."""
-        todos = []
-        for id_ in db_conn.column_where('todos', 'id', 'day', date):
-            todos += [cls.by_id(db_conn, id_)]
-        return todos
+        return cls.by_date_range(db_conn, (date, date))
 
     @property
     def is_doable(self) -> bool:
index 055616480e7b8df3392badd41fe1b49fc818dbdf..c1e1343c63c7cb24e6e7133f09538a06f7104e11 100644 (file)
@@ -2,7 +2,8 @@
 from unittest import TestCase
 from datetime import datetime
 from tests.utils import TestCaseWithDB, TestCaseWithServer
-from plomtask.days import Day, todays_date
+from plomtask.dating import todays_date
+from plomtask.days import Day
 from plomtask.exceptions import BadFormatException
 
 
index 4ba5a1c6ee4643ef673653661579740219f80081..86986b6486ce6fe0ed75a34284daf57175de1d54 100644 (file)
@@ -63,7 +63,8 @@ class TestsWithDB(TestCaseWithDB):
         t2.save(self.db_conn)
         self.assertEqual(Todo.by_date(self.db_conn, self.date1), [t1, t2])
         self.assertEqual(Todo.by_date(self.db_conn, self.date2), [])
-        self.assertEqual(Todo.by_date(self.db_conn, 'foo'), [])
+        with self.assertRaises(BadFormatException):
+            self.assertEqual(Todo.by_date(self.db_conn, 'foo'), [])
 
     def test_Todo_on_conditions(self) -> None:
         """Test effect of Todos on Conditions."""