From 3a7c0b8b73dbd4f543fbad5d2df86e258f5e3163 Mon Sep 17 00:00:00 2001 From: Christian Heller <c.heller@plomlompom.de> Date: Sun, 26 Jan 2025 12:20:45 +0100 Subject: [PATCH] Add per-line structured editing of Bookings. --- ledger.py | 72 +++++++++++++------ templates/_base.tmpl | 2 +- templates/_macros.tmpl | 18 ++--- templates/booking.tmpl | 13 ---- templates/edit_raw.tmpl | 18 +++++ templates/edit_structured.tmpl | 37 ++++++++++ templates/{raw.tmpl => ledger_raw.tmpl} | 2 +- .../{index.tmpl => ledger_structured.tmpl} | 2 +- 8 files changed, 117 insertions(+), 47 deletions(-) delete mode 100644 templates/booking.tmpl create mode 100644 templates/edit_raw.tmpl create mode 100644 templates/edit_structured.tmpl rename templates/{raw.tmpl => ledger_raw.tmpl} (71%) rename templates/{index.tmpl => ledger_structured.tmpl} (69%) diff --git a/ledger.py b/ledger.py index 016e087..87f58f5 100755 --- a/ledger.py +++ b/ledger.py @@ -221,30 +221,52 @@ class Handler(PlomHttpHandler): def do_POST(self) -> None: # pylint: disable=invalid-name,missing-function-docstring + redir_path = Path('/') + if self.pagename.startswith('edit_'): + id_ = int(self.path_toks[2]) + redir_path = Path('/').joinpath('booking').joinpath(str(id_)) if self.pagename == 'file': if 'reload' in self.postvars.as_dict: self.server.load() elif 'save' in self.postvars.as_dict: self.server.save() - elif self.pagename == 'edit': - id_ = int(self.path_toks[2]) - old_booking = self.server.bookings[id_] - start_idx = self.server.dat_lines.index(old_booking.dat_lines[0]) - end_idx = self.server.dat_lines.index(old_booking.dat_lines[-1]) + elif self.pagename == 'edit_structured': + line_keys = self.postvars.keys_prefixed('line_') + lineno_to_inputs: dict[int, list[str]] = {} + for key in line_keys: + toks = key.split('_', maxsplit=2) + lineno = int(toks[1]) + if lineno not in lineno_to_inputs: + lineno_to_inputs[lineno] = [] + lineno_to_inputs[lineno] += [toks[2]] + new_dat_lines = [] + indent = ' ' + for lineno, input_names in lineno_to_inputs.items(): + data = '' + comment = self.postvars.first(f'line_{lineno}_comment') + for name in input_names: + input_ = self.postvars.first(f'line_{lineno}_{name}') + if name == 'intro': + data = input_ + elif name == 'error': + data = f'{indent}{input_}' + elif name == 'account': + data = f'{indent}{input_}' + elif name in {'amount', 'currency'}: + data += f' {input_}' + new_dat_lines += [ + DatLine(f'{data} ; {comment}' if comment else data)] + self.server.rewrite_booking(id_, new_dat_lines) + elif self.pagename == 'edit_raw': new_dat_lines = [DatLine(line) for line in self.postvars.first('booking').splitlines()] - self.server.dat_lines = (self.server.dat_lines[:start_idx] - + new_dat_lines - + self.server.dat_lines[end_idx+1:]) - self.server.load_bookings() - self.redirect(Path('/').joinpath('booking').joinpath(str(id_))) - return - self.redirect(Path('/')) + self.server.rewrite_booking(id_, new_dat_lines) + self.redirect(redir_path) def do_GET(self) -> None: # pylint: disable=invalid-name,missing-function-docstring ctx = {'tainted': self.server.tainted} - if self.pagename in {'booking', 'edit'}: + if self.pagename == 'booking' or self.pagename.startswith('edit_'): ctx['id'] = int(self.path_toks[2]) ctx['dat_lines'] = self.server.bookings[ctx['id']].dat_lines if self.pagename == 'balance': @@ -252,22 +274,23 @@ class Handler(PlomHttpHandler): int(self.params.first('cutoff') or '0')) self.send_rendered(Path('balance.tmpl'), ctx | {'roots': balance_roots, 'valid': valid}) - elif self.pagename == 'booking': - self.send_rendered(Path('booking.tmpl'), ctx) - elif self.pagename == 'edit': - self.send_rendered(Path('edit.tmpl'), ctx) - elif self.pagename == 'raw': - self.send_rendered(Path('raw.tmpl'), + elif self.pagename in {'booking', 'edit_structured'}: + self.send_rendered(Path('edit_structured.tmpl'), ctx) + elif self.pagename == 'edit_raw': + self.send_rendered(Path('edit_raw.tmpl'), ctx) + elif self.pagename == 'ledger_raw': + self.send_rendered(Path('ledger_raw.tmpl'), ctx | {'dat_lines': self.server.dat_lines}) else: self.send_rendered( - Path('index.tmpl'), + Path('ledger_structured.tmpl'), ctx | {'dat_lines': self.server.dat_lines_sans_empty}) class Server(PlomHttpServer): """Extends parent by loading .dat file into database for Handler.""" bookings: list[Booking] + dat_lines: list[DatLine] def __init__(self, path_dat: Path, *args, **kwargs) -> None: super().__init__(PATH_TEMPLATES, (SERVER_HOST, SERVER_PORT), Handler) @@ -313,6 +336,15 @@ class Server(PlomHttpServer): '\n'.join([line.raw for line in self.dat_lines]), encoding='utf8') self.load() + def rewrite_booking(self, id_: int, new_dat_lines: list[DatLine]) -> None: + """Rewrite .dat_lines for Booking of .id_ with new_dat_lines.""" + old_booking = self.bookings[id_] + start_idx = self.dat_lines.index(old_booking.dat_lines[0]) + end_idx = self.dat_lines.index(old_booking.dat_lines[-1]) + self.dat_lines = (self.dat_lines[:start_idx] + + new_dat_lines + self.dat_lines[end_idx+1:]) + self.load_bookings() + @property def dat_lines_sans_empty(self) -> list[DatLine]: """Return only those .data_lines with .code or .comment.""" diff --git a/templates/_base.tmpl b/templates/_base.tmpl index a613a1b..d6881d2 100644 --- a/templates/_base.tmpl +++ b/templates/_base.tmpl @@ -13,7 +13,7 @@ span.warning, table.warning tbody tr td, tr.warning td { background-color: #ff88 </head> <body> <form action="/file" method="POST"> -<a href="/">home</a> · <a href="/raw">raw</a> · <a href="/balance">balance</a> · <input type="submit" name="reload" value="reload" />{% if tainted %} · <span class="warning">unsaved changes: <input type="submit" name="save" value="save"></span>{% endif %} +ledger <a href="/ledger_structured">structured</a> / <a href="/ledger_raw">raw</a> · <a href="/balance">balance</a> · <input type="submit" name="reload" value="reload" />{% if tainted %} · <span class="warning">unsaved changes: <input type="submit" name="save" value="save"></span>{% endif %} </form> <hr /> {% block content %}{% endblock %} diff --git a/templates/_macros.tmpl b/templates/_macros.tmpl index 850f4c5..1412c84 100644 --- a/templates/_macros.tmpl +++ b/templates/_macros.tmpl @@ -7,19 +7,17 @@ td.amt, td.curr { font-family: monospace; font-size: 1.3em; } td.invalid, tr.warning td.invalid { background-color: #ff0000; } {% endmacro %} -{% macro table_dat_lines(dat_lines, single, raw) %} +{% macro table_dat_lines(dat_lines, raw) %} <table> {% for dat_line in dat_lines %} - {% if (not (raw or single)) and dat_line.is_intro and loop.index > 1 %} + {% if (not raw) and dat_line.is_intro and loop.index > 1 %} <tr><td colspan=5> </td></tr> {% endif %} <tr{% if dat_line.is_questionable %} class="warning"{% endif %}> - {% if not single %} - {% if dat_line.is_intro %} - <td id="{{dat_line.booking_id}}"><a href="#{{dat_line.booking_id}}">#</a>/<a href="/balance?cutoff={{dat_line.booking_id+1}}">b</a></td> - {% else %} - <td></td> - {% endif %} + {% if dat_line.is_intro %} + <td id="{{dat_line.booking_id}}"><a href="#{{dat_line.booking_id}}">#</a>/<a href="/balance?cutoff={{dat_line.booking_id+1}}">b</a></td> + {% else %} + <td></td> {% endif %} {% if raw %} <td{% if dat_line.error %} class="invalid"{% endif %}> @@ -44,9 +42,7 @@ td.invalid, tr.warning td.invalid { background-color: #ff0000; } </tr> {% if dat_line.error and not raw %} <tr class="warning"> - {% if not single %} - <td></td> - {% endif %} + <td></td> <td class="invalid" colspan=3>{{dat_line.error}}</td> <td></td> </tr> diff --git a/templates/booking.tmpl b/templates/booking.tmpl deleted file mode 100644 index 9470c27..0000000 --- a/templates/booking.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -{% extends '_base.tmpl' %} - - -{% block css %} -{{ macros.css_td_money() }} -{{ macros.css_errors() }} -{% endblock %} - -{% block content %} -<a href="/edit/{{id}}">edit</a> -<hr /> -{{ macros.table_dat_lines(dat_lines, single=true, raw=false) }} -{% endblock %} diff --git a/templates/edit_raw.tmpl b/templates/edit_raw.tmpl new file mode 100644 index 0000000..ad344a8 --- /dev/null +++ b/templates/edit_raw.tmpl @@ -0,0 +1,18 @@ +{% extends '_base.tmpl' %} + + +{% block css %} +{{ macros.css_td_money() }} +{{ macros.css_errors() }} +{% endblock %} + +{% block content %} +<a href="/edit_structured/{{id}}">edit structured</a> +<hr /> +<form action="/edit_raw/{{id}}" method="POST"> +<textarea name="booking" cols=100 rows=100> +{% for dat_line in dat_lines %}{{ dat_line.raw }} +{% endfor %}</textarea> +<input type="submit" value="update" /> +</form> +{% endblock %} diff --git a/templates/edit_structured.tmpl b/templates/edit_structured.tmpl new file mode 100644 index 0000000..6e13a9a --- /dev/null +++ b/templates/edit_structured.tmpl @@ -0,0 +1,37 @@ +{% extends '_base.tmpl' %} + + +{% block css %} +{{ macros.css_td_money() }} +{{ macros.css_errors() }} +{% endblock %} + +{% block content %} +<a href="/edit_raw/{{id}}">edit raw</a> +<hr /> +<form action="/edit_structured/{{id}}" method="POST"> +<table> +{% for dat_line in dat_lines %} + <tr> + {% if dat_line.is_intro %} + <td{% if dat_line.error %} class="invalid"{% endif %} colspan=3><input name="line_{{loop.index0}}_intro" value="{{dat_line.code}}"/></td> + {% elif not dat_line.error %} + <td><input name="line_{{loop.index0}}_account" value="{{dat_line.booking_line.account}}" /></td> + <td><input name="line_{{loop.index0}}_amount" value="{{dat_line.booking_line.amount or ''}}" /></td> + <td><input name="line_{{loop.index0}}_currency" value="{{dat_line.booking_line.currency or ''}}" /></td> + {% else %} + <td class="invalid" colspan=3><input name="line_{{loop.index0}}_error" value="{{dat_line.code|trim}}" /></td> + {% endif %} + <td><input name="line_{{loop.index0}}_comment" value="{{dat_line.comment|trim}}" /></td> + </tr> + {% if dat_line.error %} + <tr class="warning"> + <td class="invalid" colspan=3>{{dat_line.error}}</td> + <td></td> + </tr> + {% endif %} +{% endfor %} +</table> +<input type="submit" value="update" /> +</form> +{% endblock %} diff --git a/templates/raw.tmpl b/templates/ledger_raw.tmpl similarity index 71% rename from templates/raw.tmpl rename to templates/ledger_raw.tmpl index 8b2d621..d09e722 100644 --- a/templates/raw.tmpl +++ b/templates/ledger_raw.tmpl @@ -7,6 +7,6 @@ table { font-family: monospace; } {% endblock %} {% block content %} -{{ macros.table_dat_lines(dat_lines, single=false, raw=true) }} +{{ macros.table_dat_lines(dat_lines, raw=true) }} {% endblock %} diff --git a/templates/index.tmpl b/templates/ledger_structured.tmpl similarity index 69% rename from templates/index.tmpl rename to templates/ledger_structured.tmpl index 886a01c..1669ce9 100644 --- a/templates/index.tmpl +++ b/templates/ledger_structured.tmpl @@ -7,5 +7,5 @@ {% endblock %} {% block content %} -{{ macros.table_dat_lines(dat_lines, single=false, raw=false) }} +{{ macros.table_dat_lines(dat_lines, raw=false) }} {% endblock %} -- 2.30.2