home · contact · privacy
Refactor saving and caching tests, treatment of None IDs.
[plomtask] / plomtask / days.py
index d7083b4310bf76c168f6f33d29cf55c8ada7a29f..0bd942cbd8804b8900a3271b9f4f0e4881f105ec 100644 (file)
@@ -1,5 +1,7 @@
 """Collecting Day and date-related items."""
 from __future__ import annotations
 """Collecting Day and date-related items."""
 from __future__ import annotations
+from typing import Any
+from sqlite3 import Row
 from datetime import datetime, timedelta
 from plomtask.db import DatabaseConnection, BaseModel
 from plomtask.todos import Todo
 from datetime import datetime, timedelta
 from plomtask.db import DatabaseConnection, BaseModel
 from plomtask.todos import Todo
@@ -10,43 +12,71 @@ class Day(BaseModel[str]):
     """Individual days defined by their dates."""
     table_name = 'days'
     to_save = ['comment']
     """Individual days defined by their dates."""
     table_name = 'days'
     to_save = ['comment']
+    can_create_by_id = True
 
     def __init__(self, date: str, comment: str = '') -> None:
         id_ = valid_date(date)
         super().__init__(id_)
         self.datetime = datetime.strptime(self.date, DATE_FORMAT)
         self.comment = comment
 
     def __init__(self, date: str, comment: str = '') -> None:
         id_ = valid_date(date)
         super().__init__(id_)
         self.datetime = datetime.strptime(self.date, DATE_FORMAT)
         self.comment = comment
-        self.calendarized_todos: list[Todo] = []
+        self.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
 
+    @property
+    def as_dict(self) -> dict[str, object]:
+        """Return self as (json.dumps-coompatible) dict."""
+        d = super().as_dict
+        d['todos'] = [t.as_dict for t in self.todos]
+        return d
+
+    @classmethod
+    def from_table_row(cls, db_conn: DatabaseConnection, row: Row | list[Any]
+                       ) -> Day:
+        """Make from DB row, with linked Todos."""
+        day = super().from_table_row(db_conn, row)
+        assert isinstance(day.id_, str)
+        day.todos = Todo.by_date(db_conn, day.id_)
+        return day
+
+    @classmethod
+    def by_id(cls, db_conn: DatabaseConnection, id_: str) -> Day:
+        """Extend BaseModel.by_id checking for new/lost .todos."""
+        day = super().by_id(db_conn, id_)
+        if day.id_ in Todo.days_to_update:
+            Todo.days_to_update.remove(day.id_)
+            day.todos = Todo.by_date(db_conn, day.id_)
+        return day
+
     @classmethod
     @classmethod
-    def all(cls, db_conn: DatabaseConnection,
-            date_range: tuple[str, str] = ('', ''),
-            fill_gaps: bool = False) -> list[Day]:
-        """Return list of Days in database within (open) date_range interval.
-
-        On fill_gaps=True, will instantiate (without saving) Days of all dates
-        within the date range that don't exist yet.
-        """
-        ret = cls.by_date_range_with_limits(db_conn, date_range, 'id')
+    def by_date_range_filled(cls, db_conn: DatabaseConnection,
+                             start: str, end: str) -> list[Day]:
+        """Return days existing and non-existing between dates start/end."""
+        ret = cls.by_date_range_with_limits(db_conn, (start, end), 'id')
         days, start_date, end_date = ret
         days, start_date, end_date = ret
+        return cls.with_filled_gaps(days, start_date, end_date)
+
+    @classmethod
+    def with_filled_gaps(cls, days: list[Day], start_date: str, end_date: str
+                         ) -> list[Day]:
+        """In days, fill with (un-saved) Days gaps between start/end_date."""
+        if start_date > end_date:
+            return days
         days.sort()
         days.sort()
-        if fill_gaps:
-            if start_date not in [d.date for d in days]:
-                days = [Day(start_date)] + days
-            if end_date not in [d.date for d in days]:
-                days += [Day(end_date)]
-            if len(days) > 1:
-                gapless_days = []
-                for i, day in enumerate(days):
-                    gapless_days += [day]
-                    if i < len(days) - 1:
-                        while day.next_date != days[i+1].date:
-                            day = Day(day.next_date)
-                            gapless_days += [day]
-                days = gapless_days
+        if start_date not in [d.date for d in days]:
+            days[:] = [Day(start_date)] + days
+        if end_date not in [d.date for d in days]:
+            days += [Day(end_date)]
+        if len(days) > 1:
+            gapless_days = []
+            for i, day in enumerate(days):
+                gapless_days += [day]
+                if i < len(days) - 1:
+                    while day.next_date != days[i+1].date:
+                        day = Day(day.next_date)
+                        gapless_days += [day]
+            days[:] = gapless_days
         return days
 
     @property
         return days
 
     @property
@@ -83,7 +113,15 @@ class Day(BaseModel[str]):
         next_datetime = self.datetime + timedelta(days=1)
         return next_datetime.strftime(DATE_FORMAT)
 
         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]
+    @property
+    def calendarized_todos(self) -> list[Todo]:
+        """Return only those of self.todos that have .calendarize set."""
+        return [t for t in self.todos if t.calendarize]
+
+    @property
+    def total_effort(self) -> float:
+        """"Sum all .performed_effort of self.todos."""
+        total_effort = 0.0
+        for todo in self.todos:
+            total_effort += todo.performed_effort
+        return total_effort