Changeset - 760e0a8cd9c7
[Not reviewed]
0 3 0
Brett Smith - 4 years ago 2020-06-14 12:53:27
brettcsmith@brettcsmith.org
reports: Add column width styles to BaseODS.
3 files changed with 45 insertions and 36 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/accrual.py
Show inline comments
...
 
@@ -278,71 +278,59 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]):
 
        'invoice',
 
        'approval',
 
        'contract',
 
        'purchase-order',
 
    ]
 
    COLUMNS = [
 
        'Date',
 
        data.Metadata.human_name('entity'),
 
        'Invoice Amount',
 
        'Booked Amount',
 
        data.Metadata.human_name('project'),
 
        *(data.Metadata.human_name(key) for key in DOC_COLUMNS),
 
    ]
 
    COL_COUNT = len(COLUMNS)
 

	
 
    def __init__(self,
 
                 rt_wrapper: rtutil.RT,
 
                 date: datetime.date,
 
                 logger: logging.Logger,
 
    ) -> None:
 
        super().__init__(rt_wrapper)
 
        self.date = date
 
        self.logger = logger
 

	
 
    def init_styles(self) -> None:
 
        super().init_styles()
 
        self.style_widecol = self.replace_child(
 
            self.document.automaticstyles,
 
            odf.style.Style,
 
            name='WideCol',
 
        )
 
        self.style_widecol.setAttribute('family', 'table-column')
 
        self.style_widecol.addElement(odf.style.TableColumnProperties(
 
            columnwidth='1.25in',
 
        ))
 

	
 
    def section_key(self, row: AccrualPostings) -> Optional[data.Account]:
 
        if isinstance(row.account, str):
 
            return row.account
 
        else:
 
            return None
 

	
 
    def start_spreadsheet(self) -> None:
 
        for accrual_type in AccrualAccount:
 
            self.use_sheet(accrual_type.name.title())
 
            for index in range(self.COL_COUNT):
 
                stylename = self.style_widecol if index else ''
 
                stylename = self.style_col1_25 if index else ''
 
                self.sheet.addElement(odf.table.TableColumn(stylename=stylename))
 
            self.add_row(*(
 
                self.string_cell(name, stylename=self.style_bold)
 
                for name in self.COLUMNS
 
            ))
 
            self.lock_first_row()
 

	
 
    def start_section(self, key: Optional[data.Account]) -> None:
 
        if key is None:
 
            return
 
        self.age_thresholds = list(AccrualAccount.by_account(key).value.aging_thresholds)
 
        self.age_balances = [core.MutableBalance() for _ in self.age_thresholds]
 
        accrual_date = self.date - datetime.timedelta(days=self.age_thresholds[-1])
 
        acct_parts = key.slice_parts()
 
        self.use_sheet(acct_parts[1])
 
        self.add_row()
 
        self.add_row(self.string_cell(
 
            f"{' '.join(acct_parts[2:])} {acct_parts[1]} Aging Report"
 
            f" Accrued by {accrual_date.isoformat()} Unpaid by {self.date.isoformat()}",
 
            stylename=self.merge_styles(self.style_bold, self.style_centertext),
 
            numbercolumnsspanned=self.COL_COUNT,
 
        ))
 
        self.add_row()
 

	
conservancy_beancount/reports/core.py
Show inline comments
...
 
@@ -816,79 +816,95 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
 
        """
 
        view_settings = self.ensure_child(
 
            self.document.settings, odf.config.ConfigItemSet, name='ooo:view-settings',
 
        )
 
        views = self.ensure_child(
 
            view_settings, odf.config.ConfigItemMapIndexed, name='Views',
 
        )
 
        self.view = self.ensure_child(views, odf.config.ConfigItemMapEntry)
 
        self.set_config(self.view, 'ViewId', 'view1')
 

	
 
    def init_styles(self) -> None:
 
        """Hook called to initialize settings
 

	
 
        This method is called by __init__ to populate
 
        ``self.document.styles``. This implementation creates basic building
 
        block cell styles often used in financial reports.
 
        """
 
        styles = self.document.styles
 
        self.style_bold = self.ensure_child(
 
            styles, odf.style.Style, name='Bold', family='table-cell',
 
        )
 
        self.ensure_child(
 
            self.style_bold, odf.style.TextProperties, fontweight='bold',
 
        )
 
        self.style_starttext: odf.style.Style
 
        self.style_centertext: odf.style.Style
 
        self.style_endtext: odf.style.Style
 
        for textalign in ['start', 'center', 'end']:
 
            aligned_style = self.replace_child(
 
                styles, odf.style.Style, name=f'{textalign.title()}Text',
 
            )
 
            aligned_style.setAttribute('family', 'table-cell')
 
            aligned_style.addElement(odf.style.ParagraphProperties(textalign=textalign))
 
            setattr(self, f'style_{textalign}text', aligned_style)
 
        self.style_dividerline = self.ensure_child(
 
            styles, odf.style.Style, name='DividerLine', family='table-cell',
 
        )
 
        self.ensure_child(
 
            self.style_dividerline,
 
            odf.style.TableCellProperties,
 
            borderbottom='1pt solid #0000ff',
 
        )
 

	
 
        date_style = self.replace_child(styles, odf.number.DateStyle, name='ISODate')
 
        date_style.addElement(odf.number.Year(style='long'))
 
        date_style.addElement(odf.number.Text(text='-'))
 
        date_style.addElement(odf.number.Month(style='long'))
 
        date_style.addElement(odf.number.Text(text='-'))
 
        date_style.addElement(odf.number.Day(style='long'))
 
        self.style_date = self.ensure_child(
 
            styles,
 
            odf.style.Style,
 
            name=f'{date_style.getAttribute("name")}Cell',
 
            family='table-cell',
 
            datastylename=date_style,
 
        )
 
        self.style_dividerline = self.ensure_child(
 
            styles, odf.style.Style, name='DividerLine', family='table-cell',
 
        )
 
        self.ensure_child(
 
            self.style_dividerline,
 
            odf.style.TableCellProperties,
 
            borderbottom='1pt solid #0000ff',
 
        )
 

	
 
        self.style_starttext: odf.style.Style
 
        self.style_centertext: odf.style.Style
 
        self.style_endtext: odf.style.Style
 
        for textalign in ['start', 'center', 'end']:
 
            aligned_style = self.replace_child(
 
                styles, odf.style.Style, name=f'{textalign.title()}Text',
 
            )
 
            aligned_style.setAttribute('family', 'table-cell')
 
            aligned_style.addElement(odf.style.ParagraphProperties(textalign=textalign))
 
            setattr(self, f'style_{textalign}text', aligned_style)
 

	
 
        self.style_col1: odf.style.Style
 
        self.style_col1_25: odf.style.Style
 
        self.style_col1_5: odf.style.Style
 
        self.style_col1_75: odf.style.Style
 
        self.style_col2: odf.style.Style
 
        for width in ['1', '1.25', '1.5', '1.75', '2']:
 
            width_name = width.replace('.', '_')
 
            column_style = self.replace_child(
 
                self.document.automaticstyles, odf.style.Style, name=f'col_{width_name}',
 
            )
 
            column_style.setAttribute('family', 'table-column')
 
            column_style.addElement(odf.style.TableColumnProperties(columnwidth=f'{width}in'))
 
            setattr(self, f'style_col{width_name}', column_style)
 

	
 
    ### Rows and cells
 

	
 
    def add_row(self, *cells: odf.table.TableCell, **attrs: Any) -> odf.table.TableRow:
 
        row = odf.table.TableRow(**attrs)
 
        for cell in cells:
 
            row.addElement(cell)
 
        self.sheet.addElement(row)
 
        return row
 

	
 
    def balance_cell(self, balance: Balance, **attrs: Any) -> odf.table.TableCell:
 
        if balance.is_zero():
 
            return self.float_cell(0, **attrs)
 
        elif len(balance) == 1:
 
            amount = next(iter(balance.values()))
 
            attrs['stylename'] = self.merge_styles(
 
                attrs.get('stylename'), self.currency_style(amount.currency),
 
            )
 
            return self.currency_cell(amount, **attrs)
 
        else:
 
            lines = [babel.numbers.format_currency(
 
                number, currency, locale=self.locale, format_type=self.currency_fmt_key,
 
            ) for number, currency in balance.values()]
 
            attrs['stylename'] = self.merge_styles(
tests/test_reports_spreadsheet.py
Show inline comments
...
 
@@ -240,61 +240,66 @@ def test_ods_currency_style(ods_writer, currency_code):
 
    mappings = get_children(curr_style, odf.style.Map)
 
    assert mappings
 
    for mapping in mappings:
 
        check_currency_style(get_child(
 
            ods_writer.document.styles,
 
            odf.number.CurrencyStyle,
 
            name=mapping.getAttribute('applystylename'),
 
        ))
 

	
 
def test_ods_currency_style_caches(ods_writer):
 
    expected = ods_writer.currency_style('USD')
 
    _ = ods_writer.currency_style('EUR')
 
    actual = ods_writer.currency_style('USD')
 
    assert actual is expected
 

	
 
def test_ods_currency_style_cache_considers_properties(ods_writer):
 
    bold_text = odf.style.TextProperties(fontweight='bold')
 
    plain = ods_writer.currency_style('USD')
 
    bold = ods_writer.currency_style('USD', positive_properties=bold_text)
 
    assert plain is not bold
 
    assert plain.getAttribute('name') != bold.getAttribute('name')
 
    assert plain.getAttribute('datastylename') != bold.getAttribute('datastylename')
 

	
 
@pytest.mark.parametrize('attr_name,child_type,checked_attr', [
 
    ('style_col1', odf.style.TableColumnProperties, 'columnwidth'),
 
    ('style_col1_25', odf.style.TableColumnProperties, 'columnwidth'),
 
    ('style_col1_5', odf.style.TableColumnProperties, 'columnwidth'),
 
    ('style_col1_75', odf.style.TableColumnProperties, 'columnwidth'),
 
    ('style_col2', odf.style.TableColumnProperties, 'columnwidth'),
 
    ('style_bold', odf.style.TextProperties, 'fontweight'),
 
    ('style_centertext', odf.style.ParagraphProperties, 'textalign'),
 
    ('style_dividerline', odf.style.TableCellProperties, 'borderbottom'),
 
    ('style_endtext', odf.style.ParagraphProperties, 'textalign'),
 
    ('style_starttext', odf.style.ParagraphProperties, 'textalign'),
 
])
 
def test_ods_writer_style(ods_writer, attr_name, child_type, checked_attr):
 
    if child_type is odf.style.TableColumnProperties:
 
        root = ods_writer.document.automaticstyles
 
    else:
 
        root = ods_writer.document.styles
 
    style = getattr(ods_writer, attr_name)
 
    actual = get_child(
 
        ods_writer.document.styles,
 
        odf.style.Style,
 
        name=style.getAttribute('name'),
 
    )
 
    actual = get_child(root, odf.style.Style, name=style.getAttribute('name'))
 
    assert actual is style
 
    child = get_child(actual, child_type)
 
    assert child.getAttribute(checked_attr)
 

	
 
def test_ods_writer_merge_styles(ods_writer):
 
    style = ods_writer.merge_styles(ods_writer.style_bold, ods_writer.style_dividerline)
 
    actual = get_child(
 
        ods_writer.document.styles,
 
        odf.style.Style,
 
        name=style.getAttribute('name'),
 
    )
 
    assert actual is style
 
    assert actual.getAttribute('family') == 'table-cell'
 
    text_props = get_child(actual, odf.style.TextProperties)
 
    assert text_props.getAttribute('fontweight') == 'bold'
 
    cell_props = get_child(actual, odf.style.TableCellProperties)
 
    assert cell_props.getAttribute('borderbottom')
 

	
 
def test_ods_writer_merge_styles_with_children_and_attributes(ods_writer):
 
    jpy_style = ods_writer.currency_style('JPY')
 
    style = ods_writer.merge_styles(ods_writer.style_bold, jpy_style)
 
    actual = get_child(
 
        ods_writer.document.styles,
 
        odf.style.Style,
0 comments (0 inline, 0 general)