From: Christian Heller Date: Sun, 9 Jun 2024 01:30:48 +0000 (+0200) Subject: Allow editing of VersionedAttributes timestamps. X-Git-Url: https://plomlompom.com/repos/%7B%7Bdb.prefix%7D%7D/%7B%7B%20web_path%20%7D%7D/static/%7Broute%7D?a=commitdiff_plain;h=1a9178b2b7bb66e77f3df01a0c7b2812839637ba;p=plomtask Allow editing of VersionedAttributes timestamps. --- diff --git a/plomtask/http.py b/plomtask/http.py index bb05fa9..cf8a829 100644 --- a/plomtask/http.py +++ b/plomtask/http.py @@ -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') diff --git a/plomtask/versioned_attributes.py b/plomtask/versioned_attributes.py index b3442d7..d3c3649 100644 --- a/plomtask/versioned_attributes.py +++ b/plomtask/versioned_attributes.py @@ -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. diff --git a/templates/_macros.html b/templates/_macros.html index 1439591..a19c063 100644 --- a/templates/_macros.html +++ b/templates/_macros.html @@ -39,6 +39,7 @@ {% macro history_page(item_name, item, attribute_name, attribute, as_pre=false) %}

{{item_name}} {{attribute_name}} history

+
@@ -46,12 +47,15 @@ + {% for date in attribute.history.keys() | sort(reverse=True) %} - + {% endfor %}
{{item.title.newest|e}}
{{date | truncate(19, True, '') }} {% if as_pre %}
{% endif %}{{attribute.history[date]}}{% if as_pre %}
{% endif %}
+ +
{% endmacro %}