home · contact · privacy
Move view context generations to allow refactoring of testing. master
authorChristian Heller <c.heller@plomlompom.de>
Tue, 13 Jan 2026 23:36:21 +0000 (00:36 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Tue, 13 Jan 2026 23:36:21 +0000 (00:36 +0100)
src/ledgplom/http.py
src/ledgplom/ledger.py
src/ledgplom/testing.py
src/templates/balance.tmpl
src/tests/full.balance

index 3306a8df34cd47968c9ac57f2f23a77ba4277cc1..ce4bfdde10510f0a1201223a67a1661737617d15 100644 (file)
@@ -5,7 +5,7 @@ from typing import Any
 # plomlib
 from plomlib.web import PlomHttpHandler, PlomHttpServer, PlomQueryMap
 # ourselves
-from ledgplom.ledger import Account, DatBlock, DEFAULT_INDENT, Ledger
+from ledgplom.ledger import DEFAULT_INDENT, Ledger
 
 
 PATH_TEMPLATES = Path('templates')
@@ -108,8 +108,6 @@ class _Handler(PlomHttpHandler):
             self.redirect(
                 Path('/', _PAGENAME_EDIT_STRUCTURED, self.path_toks[2]))
             return
-        ### from time import time_ns
-        ### start = time_ns()
         ctx = {'unsaved_changes': self.server.ledger.tainted,
                'path': self.path}
         if self.pagename == PAGENAME_BALANCE:
@@ -120,78 +118,24 @@ class _Handler(PlomHttpHandler):
             self.get_ledger(ctx, self.pagename == PAGENAME_LEDGER_RAW)
         else:
             self.get_ledger(ctx, False)
-        ### end = time_ns()
-        ### duration = (end - start) / (10 ** 9)
-        ### print("DEBUG GET", self.pagename,  f'{duration:8.5f}')
 
     def get_balance(self, ctx) -> None:
         'Display tree of calculated Accounts over blocks up_incl+1.'
-        id_ = int(self.params.first('up_incl')
-                  or str(len(self.server.ledger.blocks) - 1))
-        roots = [ac for ac in self.server.ledger.calc_accounts().values()
-                 if not ac.parent]
-        ctx['roots'] = sorted(roots, key=lambda r: r.basename)
-        ctx['valid'] = self.server.ledger.blocks_valid_up_incl(id_)
-        ctx['block'] = self.server.ledger.blocks[id_]
-        ctx['path_up_incl'] = f'{self.path_toks[1]}?up_incl='
-        self._send_rendered(PAGENAME_BALANCE, ctx)
+        id_str = self.params.first('up_incl')
+        self._send_rendered(
+                PAGENAME_BALANCE,
+                ctx | self.server.ledger.view_ctx_balance(int(id_str) if id_str
+                                                          else -1))
 
     def get_edit(self, ctx, raw: bool) -> None:
         'Display edit form for individual Booking.'
-        def make_balance_roots(b: DatBlock) -> list[dict[str, Any]]:
-            if not b.booking:
-                return []
-            observed_tree: list[dict[str, Any]] = []
-            for full_path in sorted(b.booking.diffs_targeted.keys()):
-                node_children: list[dict[str, Any]] = observed_tree
-                for path, _ in Account.path_to_steps(full_path):
-                    already_listed = False
-                    for n in [n for n in node_children if path == n['name']]:
-                        node_children = n['children']
-                        already_listed = True
-                        break
-                    if already_listed:
-                        continue
-                    diff = b.booking.diffs_inheriting[path].moneys
-                    if (targeted := full_path == path) or diff:
-                        pre, post = accounts[path].get_wealth_at_excl_incl(id_)
-                        displayed_currs = set(diff.keys())
-                        for wealth in pre, post:
-                            wealth.ensure_currencies(displayed_currs)
-                            wealth.purge_currencies_except(displayed_currs)
-                        node: dict[str, Any] = {
-                                'name': path,
-                                'direct_target': targeted,
-                                'wealth_before': pre.moneys,
-                                'wealth_diff': diff,
-                                'wealth_after': post.moneys,
-                                'children': []}
-                        node_children += [node]
-                        node_children = node['children']
-            return observed_tree
-
-        accounts = self.server.ledger.calc_accounts()
-        id_ = int(self.path_toks[2])
-        block = self.server.ledger.blocks[id_]
-        ctx['block'] = block
-        ctx['valid'] = self.server.ledger.blocks_valid_up_incl(id_)
-        ctx['roots'] = make_balance_roots(block)
-        if raw:
-            self._send_rendered(_PAGENAME_EDIT_RAW, ctx)
-        else:
-            ctx['raw_gap_lines'] = [dl.raw for dl in block.gap.lines]
-            ctx['all_accounts'] = sorted(accounts.keys())
-            ctx['booking_lines'] = []
-            if block.booking:
-                ctx['booking_lines'] += [block.booking.intro_line.as_dict]
-                ctx['booking_lines'] += [tf_line.as_dict for tf_line
-                                         in block.booking.transfer_lines]
-            self._send_rendered(_PAGENAME_EDIT_STRUCTURED, ctx)
+        self._send_rendered(
+                _PAGENAME_EDIT_RAW if raw else _PAGENAME_EDIT_STRUCTURED,
+                ctx | self.server.ledger.view_ctx_edit(int(self.path_toks[2]),
+                                                       raw))
 
     def get_ledger(self, ctx: dict[str, Any], raw: bool) -> None:
         'Display ledger of all Bookings.'
-        ctx['blocks'] = self.server.ledger.blocks
-        ctx['has_redundant_empty_lines'] =\
-            self.server.ledger.has_redundant_empty_lines
         self._send_rendered(
-            PAGENAME_LEDGER_RAW if raw else PAGENAME_LEDGER_STRUCTURED, ctx)
+                PAGENAME_LEDGER_RAW if raw else PAGENAME_LEDGER_STRUCTURED,
+                ctx | self.server.ledger.view_ctx_ledger())
index d7b13dae1532e8c6168569c2f8a812b04f94b87b..a286640de29047deed5cf6b6a1d4f233cec0ecd8 100644 (file)
@@ -67,10 +67,10 @@ class _Wealth():
         return sink
 
 
-class Account:
+class _Account:
     'Combine name, position in tree of owner, and wealth of self + children.'
 
-    def __init__(self, parent: Optional['Account'], basename: str) -> None:
+    def __init__(self, parent: Optional[Self], basename: str) -> None:
         self._wealth_diffs: dict[int, _Wealth] = {}
         self.basename = basename
         self.desc = ''
@@ -165,7 +165,7 @@ class _DatLine:
 
     @property
     def comment_instructions(self) -> dict[str, str]:
-        'Parse .comment into Account modification instructions.'
+        'Parse .comment into _Account modification instructions.'
         instructions = {}
         if self.comment.startswith(_PREFIX_DEF):
             parts = [part.strip() for part
@@ -405,7 +405,7 @@ class _Booking(_LinesBlock[_BookingLine]):
         'All accounts affected by .diffs_targeted with calculated totals.'
         acc_diffs: dict[str, _Wealth] = {}
         for full_path, moneys in self.diffs_targeted.items():
-            for path, _ in Account.path_to_steps(full_path):
+            for path, _ in _Account.path_to_steps(full_path):
                 if path not in acc_diffs:
                     acc_diffs[path] = _Wealth()
                 acc_diffs[path] += moneys
@@ -443,7 +443,7 @@ class _Booking(_LinesBlock[_BookingLine]):
         return copy
 
 
-class DatBlock:
+class _DatBlock:
     'Unit of lines with optional .booking, and possibly empty .gap.'
 
     def __init__(
@@ -460,7 +460,7 @@ class DatBlock:
     def id_(self) -> int:
         'Return index in chain.'
         count = -1
-        block_iterated: Optional[DatBlock] = self
+        block_iterated: Optional[_DatBlock] = self
         while block_iterated:
             block_iterated = block_iterated.prev
             count += 1
@@ -496,21 +496,21 @@ class DatBlock:
         setattr(self, f'_{this}', new_this)
 
     @property
-    def next(self) -> Optional['DatBlock']:
+    def next(self) -> Optional['_DatBlock']:
         'Successor in chain.'
         return self._next
 
     @next.setter
-    def next(self, new_next: Optional['DatBlock']) -> None:
+    def next(self, new_next: Optional['_DatBlock']) -> None:
         self._set_neighbor(new_next, 'next', 'prev')
 
     @property
-    def prev(self) -> Optional['DatBlock']:
+    def prev(self) -> Optional['_DatBlock']:
         'Predecessor in chain.'
         return self._prev
 
     @prev.setter
-    def prev(self, new_prev: Optional['DatBlock']):
+    def prev(self, new_prev: Optional['_DatBlock']):
         self._set_neighbor(new_prev, 'prev', 'next')
 
     @property
@@ -565,9 +565,9 @@ class DatBlock:
             self.next.prev = new_block
         new_block.fix_position()
 
-    def copy_to_current_date(self) -> 'DatBlock':
+    def copy_to_current_date(self) -> '_DatBlock':
         'Make copy of same lines but now as date, position accordingly.'
-        copy = DatBlock(
+        copy = _DatBlock(
                 self.booking.copy_to_current_date() if self.booking else None,
                 self.gap.copy())
         if self.next:
@@ -578,8 +578,8 @@ class DatBlock:
 
 
 class Ledger:
-    'Collection of DatBlocks, _Bookings and Accounts derived from them.'
-    _blocks_start: Optional[DatBlock]
+    'Collection of _DatBlocks, _Bookings and _Accounts derived from them.'
+    _blocks_start: Optional[_DatBlock]
 
     def __init__(self, path_dat: Path) -> None:
         self._path_dat = path_dat
@@ -593,7 +593,7 @@ class Ledger:
         if (not dat_lines) or dat_lines[-1].code:  # ensure final gap line so
             dat_lines += [_DatLine()]              # last booking gets finished
         booking_lines: list[_BookingLine] = []
-        i_block = DatBlock(None, _Gap())
+        i_block = _DatBlock(None, _Gap())
         self._blocks_start = i_block
         for dat_line in dat_lines:
             if bool(dat_line.code):
@@ -603,14 +603,14 @@ class Ledger:
                     booking_lines += [_TransferLine.from_dat(dat_line)]
             else:  # enter new gap -> ready to start next block
                 if booking_lines:
-                    i_block.next = DatBlock(_Booking(booking_lines))
+                    i_block.next = _DatBlock(_Booking(booking_lines))
                     i_block = i_block.next
                     booking_lines = []
                 i_block.gap.add([_GapLine.from_dat(dat_line)])
         self.last_save_hash = self._hash_dat_lines()
 
     @property
-    def blocks(self) -> list[DatBlock]:
+    def _blocks(self) -> list[_DatBlock]:
         'Return blocks chain as list.'
         blocks = []
         block = self._blocks_start
@@ -621,21 +621,21 @@ class Ledger:
 
     @property
     def _dat_lines(self) -> list[_DatLine]:
-        'From .blocks build list of current _DatLines.'
+        'From ._blocks build list of current _DatLines.'
         lines = []
-        for block in self.blocks:
+        for block in self._blocks:
             lines += block.lines
         return lines
 
-    def calc_accounts(self) -> dict[str, Account]:
-        'Build mapping of account names to Accounts.'
-        accounts: dict[str, Account] = {}
+    def _calc_accounts(self) -> dict[str, _Account]:
+        'Build mapping of account names to _Accounts.'
+        accounts: dict[str, _Account] = {}
 
         def ensure_accounts(full_path: str) -> None:
             parent_path = ''
-            for path, step_name in Account.path_to_steps(full_path):
+            for path, step_name in _Account.path_to_steps(full_path):
                 if path not in accounts:
-                    accounts[path] = Account(
+                    accounts[path] = _Account(
                         accounts[parent_path] if parent_path else None,
                         step_name)
                 parent_path = path
@@ -644,7 +644,7 @@ class Ledger:
             for acc_name, desc in dat_line.comment_instructions.items():
                 ensure_accounts(acc_name)
                 accounts[acc_name].desc = desc
-        for block in [b for b in self.blocks if b.booking]:
+        for block in [b for b in self._blocks if b.booking]:
             assert block.booking is not None
             for acc_name, wealth in block.booking.diffs_targeted.items():
                 ensure_accounts(acc_name)
@@ -660,9 +660,9 @@ class Ledger:
     def _hash_dat_lines(self) -> int:
         return hash(tuple(dl.raw for dl in self._dat_lines))
 
-    def blocks_valid_up_incl(self, block_id: int) -> bool:
+    def _blocks_valid_up_incl(self, block_id: int) -> bool:
         'Whether nothing questionable about blocks until block_id.'
-        for block in self.blocks[:block_id]:
+        for block in self._blocks[:block_id]:
             if block.booking:
                 if block.booking.sink_error:
                     return False
@@ -673,13 +673,14 @@ class Ledger:
         return True
 
     @property
-    def has_redundant_empty_lines(self) -> bool:
+    def _has_redundant_empty_lines(self) -> bool:
         'If any gaps have redunant empty lines.'
-        return bool([b for b in self.blocks if b.gap.redundant_empty_lines])
+        return bool([b for b in self._blocks if b.gap.redundant_empty_lines])
 
     def remove_redundant_empty_lines(self) -> None:
-        'From all .blocks remove redundant empty lines.'
-        for gap in [b.gap for b in self.blocks if b.gap.redundant_empty_lines]:
+        'From all ._blocks remove redundant empty lines.'
+        for gap in [b.gap for b in self._blocks
+                    if b.gap.redundant_empty_lines]:
             gap.remove_redundant_empty_lines()
 
     @property
@@ -688,8 +689,8 @@ class Ledger:
         return self._hash_dat_lines() != self.last_save_hash
 
     def move_block(self, idx_from: int, up: bool) -> int:
-        'Move DatBlock of idx_from step up or downwards.'
-        block = self.blocks[idx_from]
+        'Move _DatBlock of idx_from step up or downwards.'
+        block = self._blocks[idx_from]
         block.move(up)
         return block.id_
 
@@ -711,7 +712,7 @@ class Ledger:
                     lines_gap_pre_booking += [_GapLine.from_dat(dat_line)]
                 else:
                     lines_gap_post_booking += [_GapLine.from_dat(dat_line)]
-        old_block = self.blocks[old_id]
+        old_block = self._blocks[old_id]
         if not lines_booking:
             if old_block.prev:
                 old_block.prev.gap.add(lines_gap_pre_booking)
@@ -720,11 +721,11 @@ class Ledger:
                 old_block.booking = None
                 old_block.gap.add(lines_gap_pre_booking)
             return max(0, old_id - 1)
-        new_block = DatBlock(_Booking(lines_booking),
-                             _Gap(lines_gap_post_booking))
-        self.blocks[old_id].replace_with(new_block)
+        new_block = _DatBlock(_Booking(lines_booking),
+                              _Gap(lines_gap_post_booking))
+        self._blocks[old_id].replace_with(new_block)
         if not new_block.prev:
-            self._blocks_start = DatBlock(None, _Gap())
+            self._blocks_start = _DatBlock(None, _Gap())
             self._blocks_start.next = new_block
             assert new_block.prev is not None
         if lines_gap_pre_booking:
@@ -732,14 +733,74 @@ class Ledger:
         return new_block.id_
 
     def add_empty_block(self) -> int:
-        'Add new DatBlock of empty _Booking to end of ledger.'
-        new_block = DatBlock(
+        'Add new _DatBlock of empty _Booking to end of ledger.'
+        new_block = _DatBlock(
                 _Booking([_IntroLine(dt_date.today().isoformat(), '?')]))
-        self.blocks[-1].next = new_block
+        self._blocks[-1].next = new_block
         new_block.fix_position()
         return new_block.id_
 
     def copy_block(self, id_: int) -> int:
-        'Add copy DatBlock of id_ but with current date.'
-        copy = self.blocks[id_].copy_to_current_date()
+        'Add copy _DatBlock of id_ but with current date.'
+        copy = self._blocks[id_].copy_to_current_date()
         return copy.id_
+
+    def view_ctx_balance(self, id_: int = -1) -> dict[str, Any]:
+        'All context data relevant for rendering a balance view.'
+        if id_ < 0:
+            id_ = len(self._blocks) - 1
+        return {'valid': self._blocks_valid_up_incl(id_),
+                'block': self._blocks[id_],
+                'roots': sorted([ac for ac in self._calc_accounts().values()
+                                 if not ac.parent],
+                                key=lambda root: root.basename)}
+
+    def view_ctx_edit(self, id_: int, raw: bool) -> dict[str, Any]:
+        'All context data relevant for rendering an edit view.'
+        accounts = self._calc_accounts()
+        block = self._blocks[id_]
+        roots: list[dict[str, Any]] = []
+        for full_path in sorted(block.booking.diffs_targeted.keys()
+                                if block.booking else []):
+            assert block.booking is not None
+            node_children: list[dict[str, Any]] = roots
+            for path, _ in _Account.path_to_steps(full_path):
+                already_listed = False
+                for n in [n for n in node_children if path == n['name']]:
+                    node_children = n['children']
+                    already_listed = True
+                    break
+                if already_listed:
+                    continue
+                diff = block.booking.diffs_inheriting[path].moneys
+                if (targeted := full_path == path) or diff:
+                    pre, post = accounts[path].get_wealth_at_excl_incl(id_)
+                    displayed_currs = set(diff.keys())
+                    for wealth in pre, post:
+                        wealth.ensure_currencies(displayed_currs)
+                        wealth.purge_currencies_except(displayed_currs)
+                    node: dict[str, Any] = {
+                            'name': path,
+                            'direct_target': targeted,
+                            'wealth_before': pre.moneys,
+                            'wealth_diff': diff,
+                            'wealth_after': post.moneys,
+                            'children': []}
+                    node_children += [node]
+                    node_children = node['children']
+        ctx = {'block': block,
+               'valid': self._blocks_valid_up_incl(id_),
+               'roots': roots}
+        if not raw:
+            ctx['raw_gap_lines'] = [dl.raw for dl in block.gap.lines]
+            ctx['all_accounts'] = sorted(accounts.keys())
+            ctx['booking_lines'] = (
+                [block.booking.intro_line.as_dict]
+                + [tf_line.as_dict for tf_line in block.booking.transfer_lines]
+                ) if block.booking else []
+        return ctx
+
+    def view_ctx_ledger(self) -> dict[str, Any]:
+        'All context data relevant for rendering a ledger view.'
+        return {key: getattr(self, f'_{key}')
+                for key in ('blocks', 'has_redundant_empty_lines')}
index 8e7a53aa22b37627c63a46b5cc52d1514d332ebb..3c7d576d43f70af2e6d90c8fd8eaa268ad975dae 100644 (file)
@@ -42,18 +42,9 @@ def run_tests() -> None:
                                   for line in f.readlines()]
             ctx: dict[str, Any] = {}
             if key == PAGENAME_BALANCE:
-                id_ = len(ledger.blocks) - 1
-                ctx['roots'] = sorted([ac for
-                                       ac in ledger.calc_accounts().values()
-                                       if not ac.parent],
-                                      key=lambda r: r.basename)
-                ctx['valid'] = ledger.blocks_valid_up_incl(id_)
-                ctx['block'] = ledger.blocks[id_]
-                ctx['path_up_incl'] = f'{PAGENAME_BALANCE}?up_incl='
+                ctx |= ledger.view_ctx_balance(-1)
             else:
-                ctx['blocks'] = ledger.blocks
-                ctx['has_redundant_empty_lines'] =\
-                    ledger.has_redundant_empty_lines
+                ctx |= ledger.view_ctx_ledger()
             lines_rendered = template.render(**ctx).split('\n')
             msg_prefix = f'test for {test_path}:'
             for idx0, line in enumerate(lines_rendered):
index 1812ce9fdf863891f08c9d21783dae7e1f68d378..4b71c7adc49f5a572672e920d927938467c2e607 100644 (file)
@@ -90,8 +90,8 @@ span.indent {
 
 {% block content %}
 <p>
-    {{ macros.conditional_block_nav(path_up_incl, 'prev', block) -}}{##}
-    {{ macros.conditional_block_nav(path_up_incl, 'next', block) }}{##}
+    {{ macros.conditional_block_nav('/balance?up_incl=', 'prev', block) -}}{##}
+    {{ macros.conditional_block_nav('/balance?up_incl=', 'next', block) }}{##}
     |
     balance after {# -#}
     <a href="/blocks/{{block.id_}}">{# -#}
index c348be1cde77ebd2e77327f98d4d975c6ef74b63..92a0264b616edb753aa99634ac2f46df04ae5661 100644 (file)
@@ -82,7 +82,7 @@ span.indent {
     </form>
 </div>
 <p>
-    <a href="balance?up_incl=3">prev</a>
+    <a href="/balance?up_incl=3">prev</a>
     <del>next</del>
     |
     balance after <a href="/blocks/4">booking 4 (2001-01-01: test)</a>