reports: Add BaseODS.meta_links_cell() method.

This commit is contained in:
Brett Smith 2020-06-12 17:10:25 -04:00
parent 17c5468a7d
commit aff1fc537d
4 changed files with 75 additions and 24 deletions

View file

@ -71,7 +71,6 @@ import datetime
import enum import enum
import logging import logging
import sys import sys
import urllib.parse as urlparse
from pathlib import Path from pathlib import Path
@ -293,8 +292,7 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]):
date: datetime.date, date: datetime.date,
logger: logging.Logger, logger: logging.Logger,
) -> None: ) -> None:
super().__init__() super().__init__(rt_wrapper)
self.rt_wrapper = rt_wrapper
self.date = date self.date = date
self.logger = logger self.logger = logger
@ -388,20 +386,6 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]):
self.balance_cell(total_balance), self.balance_cell(total_balance),
) )
def _link_seq(self, row: AccrualPostings, key: MetaKey) -> Iterator[Tuple[str, str]]:
for href in row.all_meta_links(key):
rt_ids = self.rt_wrapper.parse(href)
rt_href = rt_ids and self.rt_wrapper.url(*rt_ids)
if rt_ids is None or rt_href is None:
# '..' pops the ODS filename off the link path. In other words,
# make the link relative to the directory the ODS is in.
href_path = Path('..', urlparse.urlparse(href).path)
href = str(href_path)
text = urlparse.unquote(href_path.name)
else:
text = self.rt_wrapper.unparse(*rt_ids)
yield (href, text)
def write_row(self, row: AccrualPostings) -> None: def write_row(self, row: AccrualPostings) -> None:
age = (self.date - row[0].meta.date).days age = (self.date - row[0].meta.date).days
if row.end_balance.ge_zero(): if row.end_balance.ge_zero():
@ -426,11 +410,11 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]):
amount_cell, amount_cell,
self.balance_cell(row.end_balance), self.balance_cell(row.end_balance),
self.multiline_cell(sorted(projects)), self.multiline_cell(sorted(projects)),
self.multilink_cell(self._link_seq(row, 'rt-id')), self.meta_links_cell(row.all_meta_links('rt-id')),
self.multilink_cell(self._link_seq(row, 'invoice')), self.meta_links_cell(row.all_meta_links('invoice')),
self.multilink_cell(self._link_seq(row, 'approval')), self.meta_links_cell(row.all_meta_links('approval')),
self.multilink_cell(self._link_seq(row, 'contract')), self.meta_links_cell(row.all_meta_links('contract')),
self.multilink_cell(self._link_seq(row, 'purchase-order')), self.meta_links_cell(row.all_meta_links('purchase-order')),
) )

View file

@ -20,6 +20,7 @@ import datetime
import itertools import itertools
import operator import operator
import re import re
import urllib.parse as urlparse
import babel.core # type:ignore[import] import babel.core # type:ignore[import]
import babel.numbers # type:ignore[import] import babel.numbers # type:ignore[import]
@ -39,6 +40,7 @@ from beancount.core import amount as bc_amount
from .. import data from .. import data
from .. import filters from .. import filters
from .. import rtutil
from typing import ( from typing import (
cast, cast,
@ -457,7 +459,8 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
See also the BaseSpreadsheet base class for additional documentation about See also the BaseSpreadsheet base class for additional documentation about
methods you must and can define, the definition of RT and ST, etc. methods you must and can define, the definition of RT and ST, etc.
""" """
def __init__(self) -> None: def __init__(self, rt_wrapper: Optional[rtutil.RT]=None) -> None:
self.rt_wrapper = rt_wrapper
self.locale = babel.core.Locale.default('LC_MONETARY') self.locale = babel.core.Locale.default('LC_MONETARY')
self.currency_fmt_key = 'accounting' self.currency_fmt_key = 'accounting'
self._name_counter = itertools.count(1) self._name_counter = itertools.count(1)
@ -912,6 +915,34 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
cell.addElement(odf.text.P(text=str(value))) cell.addElement(odf.text.P(text=str(value)))
return cell return cell
def _meta_link_pairs(self, links: Iterable[Optional[str]]) -> Iterator[Tuple[str, str]]:
for href in links:
if href is None:
continue
elif self.rt_wrapper is not None:
rt_ids = self.rt_wrapper.parse(href)
rt_href = rt_ids and self.rt_wrapper.url(*rt_ids)
else:
rt_ids = None
rt_href = None
if rt_ids is None or rt_href is None:
# '..' pops the ODS filename off the link path. In other words,
# make the link relative to the directory the ODS is in.
href_path = Path('..', href)
href = str(href_path)
text = href_path.name
else:
rt_path = urlparse.urlparse(rt_href).path
if rt_path.endswith('/Ticket/Display.html'):
text = rtutil.RT.unparse(*rt_ids)
else:
text = urlparse.unquote(Path(rt_path).name)
href = rt_href
yield (href, text)
def meta_links_cell(self, links: Iterable[Optional[str]], **attrs: Any) -> odf.table.TableCell:
return self.multilink_cell(self._meta_link_pairs(links), **attrs)
def multiline_cell(self, lines: Iterable[Any], **attrs: Any) -> odf.table.TableCell: def multiline_cell(self, lines: Iterable[Any], **attrs: Any) -> odf.table.TableCell:
cell = odf.table.TableCell(valuetype='string', **attrs) cell = odf.table.TableCell(valuetype='string', **attrs)
for line in lines: for line in lines:

View file

@ -5,7 +5,7 @@ from setuptools import setup
setup( setup(
name='conservancy_beancount', name='conservancy_beancount',
description="Plugin, library, and reports for reading Conservancy's books", description="Plugin, library, and reports for reading Conservancy's books",
version='1.1.12', version='1.1.13',
author='Software Freedom Conservancy', author='Software Freedom Conservancy',
author_email='info@sfconservancy.org', author_email='info@sfconservancy.org',
license='GNU AGPLv3+', license='GNU AGPLv3+',

View file

@ -32,6 +32,7 @@ from decimal import Decimal
from . import testutil from . import testutil
from conservancy_beancount import rtutil
from conservancy_beancount.reports import core from conservancy_beancount.reports import core
EN_US = babel.core.Locale('en', 'US') EN_US = babel.core.Locale('en', 'US')
@ -493,6 +494,41 @@ def test_ods_writer_float_cell(ods_writer, cell_source, style_name):
assert cell.getAttribute('value') == expected assert cell.getAttribute('value') == expected
assert get_text(cell) == expected assert get_text(cell) == expected
def test_ods_writer_meta_links_cell(ods_writer):
rt_client = testutil.RTClient()
ods_writer.rt_wrapper = rtutil.RT(rt_client)
rt_url = rt_client.DEFAULT_URL[:-10]
meta_links = [
'rt://ticket/1',
'rt://ticket/2/attachments/9',
'rt:1/5',
'Invoices/0123.pdf',
]
cell = ods_writer.meta_links_cell(meta_links, stylename='meta1')
assert cell.getAttribute('valuetype') == 'string'
assert cell.getAttribute('stylename') == 'meta1'
children = iter(get_children(cell, odf.text.A))
child = next(children)
assert child.getAttribute('type') == 'simple'
expect_url = f'{rt_url}/Ticket/Display.html?id=1'
assert child.getAttribute('href') == expect_url
assert get_text(child) == 'rt:1'
child = next(children)
assert child.getAttribute('type') == 'simple'
expect_url = f'{rt_url}/Ticket/Display.html?id=2#txn-7'
assert child.getAttribute('href') == expect_url
assert get_text(child) == 'rt:2/9'
child = next(children)
assert child.getAttribute('type') == 'simple'
expect_url = f'{rt_url}/Ticket/Attachment/1/5/photo.jpg'
assert child.getAttribute('href') == expect_url
assert get_text(child) == 'photo.jpg'
child = next(children)
assert child.getAttribute('type') == 'simple'
expect_url = f'../{meta_links[3]}'
assert child.getAttribute('href') == expect_url
assert get_text(child) == '0123.pdf'
def test_ods_writer_multiline_cell(ods_writer): def test_ods_writer_multiline_cell(ods_writer):
cell = ods_writer.multiline_cell(iter(STRING_CELL_DATA)) cell = ods_writer.multiline_cell(iter(STRING_CELL_DATA))
assert cell.getAttribute('valuetype') == 'string' assert cell.getAttribute('valuetype') == 'string'