home · contact · privacy
Allow editing of VersionedAttributes timestamps.
authorChristian Heller <c.heller@plomlompom.de>
Sun, 9 Jun 2024 01:30:48 +0000 (03:30 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Sun, 9 Jun 2024 01:30:48 +0000 (03:30 +0200)
plomtask/http.py
plomtask/versioned_attributes.py
templates/_macros.html

index bb05fa9618effaf7279ffe8553f872d3d1f16e05..cf8a8290ab35046124fc1db6f42418af7b42aea0 100644 (file)
@@ -45,6 +45,13 @@ class InputsParser:
             return default
         return self.inputs[key][0]
 
+    def get_first_strings_starting(self, prefix: str) -> dict[str, str]:
+        """Retrieve list of (first) strings at key starting with prefix."""
+        ret = {}
+        for key in [k for k in self.inputs.keys() if k.startswith(prefix)]:
+            ret[key] = self.inputs[key][0]
+        return ret
+
     def get_int(self, key: str) -> int:
         """Retrieve single/first value of key as int, error if empty."""
         val = self.get_int_or_none(key)
@@ -90,6 +97,7 @@ class InputsParser:
 
 class TaskHandler(BaseHTTPRequestHandler):
     """Handles single HTTP request."""
+    # pylint: disable=too-many-public-methods
     server: TaskServer
 
     def do_GET(self) -> None:
@@ -387,6 +395,31 @@ class TaskHandler(BaseHTTPRequestHandler):
             condition.save(self.conn)
         return f'/todo?id={todo.id_}'
 
+    def _do_POST_versioned_timestamps(self, cls: Any, attr_name: str) -> str:
+        """Update history timestamps for VersionedAttribute."""
+        id_ = self.params.get_int_or_none('id')
+        item = cls.by_id(self.conn, id_)
+        attr = getattr(item, attr_name)
+        for k, v in self.form_data.get_first_strings_starting('at:').items():
+            old = k[3:]
+            if old[19:] != v:
+                attr.reset_timestamp(old, f'{v}.0')
+        attr.save(self.conn)
+        cls_name = cls.__name__.lower()
+        return f'/{cls_name}_{attr_name}s?id={item.id_}'
+
+    def do_POST_process_descriptions(self) -> str:
+        """Update history timestamps for Process.description."""
+        return self._do_POST_versioned_timestamps(Process, 'description')
+
+    def do_POST_process_efforts(self) -> str:
+        """Update history timestamps for Process.effort."""
+        return self._do_POST_versioned_timestamps(Process, 'effort')
+
+    def do_POST_process_titles(self) -> str:
+        """Update history timestamps for Process.title."""
+        return self._do_POST_versioned_timestamps(Process, 'title')
+
     def do_POST_process(self) -> str:
         """Update or insert Process of ?id= and fields defined in postvars."""
         # pylint: disable=too-many-branches
@@ -455,6 +488,14 @@ class TaskHandler(BaseHTTPRequestHandler):
             params = f'has_step={process.id_}&title_b64={title_b64_encoded}'
         return f'/process?{params}'
 
+    def do_POST_condition_descriptions(self) -> str:
+        """Update history timestamps for Condition.description."""
+        return self._do_POST_versioned_timestamps(Condition, 'description')
+
+    def do_POST_condition_titles(self) -> str:
+        """Update history timestamps for Condition.title."""
+        return self._do_POST_versioned_timestamps(Condition, 'title')
+
     def do_POST_condition(self) -> str:
         """Update/insert Condition of ?id= and fields defined in postvars."""
         id_ = self.params.get_int_or_none('id')
index b3442d7df1030b72b2990a21e12ffa12302aa9d1..d3c364942e3d76edf0eb9618df8366866c603709 100644 (file)
@@ -4,6 +4,7 @@ from typing import Any
 from sqlite3 import Row
 from time import sleep
 from plomtask.db import DatabaseConnection
+from plomtask.exceptions import HandledException, BadFormatException
 
 TIMESTAMP_FMT = '%Y-%m-%d %H:%M:%S.%f'
 
@@ -30,6 +31,32 @@ class VersionedAttribute:
             return self.default
         return self.history[self._newest_timestamp]
 
+    def reset_timestamp(self, old_str: str, new_str: str) -> None:
+        """Rename self.history key (timestamp) old to new.
+
+        Chronological sequence of keys must be preserved, i.e. cannot move
+        key before earlier or after later timestamp.
+        """
+        try:
+            new = datetime.strptime(new_str, TIMESTAMP_FMT)
+            old = datetime.strptime(old_str, TIMESTAMP_FMT)
+        except ValueError as exc:
+            raise BadFormatException('Timestamp of illegal format.') from exc
+        timestamps = list(self.history.keys())
+        if old_str not in timestamps:
+            raise HandledException(f'Timestamp {old} not found in history.')
+        sorted_timestamps = sorted([datetime.strptime(t, TIMESTAMP_FMT)
+                                    for t in timestamps])
+        expected_position = sorted_timestamps.index(old)
+        sorted_timestamps.remove(old)
+        sorted_timestamps += [new]
+        sorted_timestamps.sort()
+        if sorted_timestamps.index(new) != expected_position:
+            raise HandledException('Timestamp not respecting chronology.')
+        value = self.history[old_str]
+        del self.history[old_str]
+        self.history[new_str] = value
+
     def set(self, value: str | float) -> None:
         """Add to self.history if and only if not same value as newest one.
 
index 14395914052efd6511eddc6cc82bbe947dda62c4..a19c06381320461baf7b1622b475d7d0d244440b 100644 (file)
@@ -39,6 +39,7 @@
 
 {% macro history_page(item_name, item, attribute_name, attribute, as_pre=false) %}
 <h3>{{item_name}} {{attribute_name}} history</h3>
+<form action="{{item_name}}_{{attribute_name}}s?id={{item.id_}}" method="POST">
 <table>
 
 <tr>
 <td><a href="{{item_name}}?id={{item.id_}}">{{item.title.newest|e}}</a></td>
 </tr>
 
+
 {% for date in attribute.history.keys() | sort(reverse=True) %}
 <tr>
-<th>{{date | truncate(19, True, '') }}</th>
+<td><input name="at:{{date}}" class="timestamp" value="{{date|truncate(19, True, '', 0)}}"></td>
 <td>{% if as_pre %}<pre>{% endif %}{{attribute.history[date]}}{% if as_pre %}</pre>{% endif %}</td>
 </tr>
 {% endfor %}
 
 </table>
+<input class="btn-harmless" type="submit" name="update" value="update" />
+</form>
 {% endmacro %}