# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
##
## Copyright (C) 2012 Async Open Source
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU Lesser General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., or visit: http://www.gnu.org/.
##
## Author(s): Stoq Team <stoq-devel@async.com.br>
##
import os
import platform
import weasyprint
from kiwi.accessor import kgetattr
from kiwi.environ import environ
from stoqlib.database.runtime import get_default_store
from stoqlib.lib.template import render_template
from stoqlib.lib.translation import stoqlib_gettext, stoqlib_ngettext
from stoqlib.lib.formatters import (get_formatted_price, get_formatted_cost,
format_quantity, format_phone_number,
get_formatted_percentage)
from stoqlib.reporting.utils import get_logo_data
_ = stoqlib_gettext
[docs]class HTMLReport(object):
template_filename = None
title = ''
complete_header = True
def __init__(self, filename):
self.filename = filename
self.logo_data = get_logo_data(get_default_store())
def _get_formatters(self):
return {
'format_price': get_formatted_price,
'format_cost': get_formatted_cost,
'format_quantity': format_quantity,
'format_percentage': get_formatted_percentage,
'format_phone': format_phone_number,
'format_date': lambda d: d and d.strftime('%x') or '',
}
[docs] def get_html(self):
assert self.title
namespace = self.get_namespace()
# Set some defaults if the report did not provide one
namespace.setdefault('subtitle', '')
namespace.setdefault('notes', [])
# Add some filters commonly used in stoq
namespace.update(self._get_formatters())
return render_template(self.template_filename,
report=self,
title=self.title,
complete_header=self.complete_header,
_=stoqlib_gettext,
stoqlib_ngettext=stoqlib_ngettext,
**namespace)
[docs] def save_html(self, filename):
html = open(filename, 'w')
html.write(self.get_html())
html.flush()
[docs] def render(self, stylesheet=None):
template_dir = environ.get_resource_filename('stoq', 'template')
if platform.system() == 'Windows':
# FIXME: Figure out why this is breaking
# On windows, weasyprint is eating the last directory of the path
template_dir = os.path.join(template_dir, 'foobar')
html = weasyprint.HTML(string=self.get_html(),
base_url=template_dir)
return html.render(stylesheets=[weasyprint.CSS(string=stylesheet)])
[docs] def save(self):
document = self.render(stylesheet='')
document.write_pdf(self.filename)
#
# Hook methods
#
[docs] def get_namespace(self):
"""This hook method can be implemented by children and should return
parameters that will be passed to report template in form of a dict.
"""
return {}
[docs] def adjust_for_test(self):
"""This hook method must be implemented by children that generates
reports with data that change with the workstation or point in time.
This allows for the test reports to be always generated with the same
data.
"""
self.logo_data = 'logo.png'
[docs]class TableReport(HTMLReport):
"""A report that contains a single table.
Subclasses must implement get_columns and get_row, and can optionaly
implement accumulate, reset and get_summary_row.
"""
#: The title of the report. Will be present in the header.
title = None
#:
subtitle_template = _("Listing {rows} of a total of {total_rows} {item}")
#:
main_object_name = (_("item"), _("items"))
#:
filter_format_string = ""
#:
complete_header = False
#:
template_filename = "objectlist.html"
def __init__(self, filename, data, title=None, blocked_records=0,
status_name=None, filter_strings=None, status=None):
self.title = title or self.title
self.blocked_records = blocked_records
self.status_name = status_name
self.status = status
if filter_strings is None:
filter_strings = []
self.filter_strings = filter_strings
self.data = data
self.columns = self.get_columns()
self._setup_details()
HTMLReport.__init__(self, filename)
def _setup_details(self):
""" This method build the report title based on the arguments sent
by SearchBar to its class constructor.
"""
rows = len(self.data)
total_rows = rows + self.blocked_records
item = stoqlib_ngettext(self.main_object_name[0],
self.main_object_name[1], total_rows)
self.subtitle = self.subtitle_template.format(rows=rows,
total_rows=total_rows, item=item)
base_note = ""
if self.filter_format_string and self.status_name:
base_note += self.filter_format_string % self.status_name.lower()
notes = []
for filter_string in self.filter_strings:
if base_note:
notes.append('%s %s' % (base_note, filter_string))
elif filter_string:
notes.append(filter_string)
self.notes = notes
[docs] def get_data(self):
self.reset()
for obj in self.data:
self.accumulate(obj)
yield self.get_row(obj)
[docs] def accumulate(self, row):
"""This method is called once for each row in the report.
Here you can create summaries (like the sum of all payments) for the
report, that will be added in the last row of the table
"""
pass
[docs] def reset(self):
"""This is called when the iteration on all the rows starts.
Use this to setup or reset any necesary data (like the summaries)
"""
pass
[docs] def get_summary_row(self):
"""If the table needs a summary row in the end, this method should
return the list of values that will be in this last row.
The values should already be formatted for presentation.
"""
return []
[docs] def get_columns(self):
"""Get the columns for this table report.
This should return a list of dictionaries defining each column in the
table. The dictionaries should define the keys 'title', with the string
that will be in the header of the table and 'align', for adjusting the
alignment of the column ('left', 'right' or 'center')
"""
raise NotImplementedError
[docs] def get_row(self, obj):
"""Returns the data to be displayed in the row.
Subclaases must implement this method and return a list of value for
each cell in the row. This values should already be formatted correctly
(ie, a date should already be converted to a string in the desired
format).
"""
raise NotImplementedError
[docs]class ObjectListReport(TableReport):
"""Creates an pdf report from an objectlist and its current state
This report will only show the columns that are visible, in the order they
are visible. It will also show the filters that were enabled when the report
was generated.
"""
#: Defines the columns that should have a summary in the last row of the
#: report. This is a list of strings defining the attribute of the
#: respective column. Currently, only numeric values are supported (Decimal,
#: currenty, etc..).
summary = []
def __init__(self, filename, objectlist, data, *args, **kwargs):
self._objectlist = objectlist
TableReport.__init__(self, filename, data, *args, **kwargs)
[docs] def get_columns(self):
import gtk
alignments = {
gtk.JUSTIFY_LEFT: 'left',
gtk.JUSTIFY_RIGHT: 'right',
gtk.JUSTIFY_CENTER: 'center',
}
# The real columns from the objectlist
self._columns = []
columns = []
for c in self._objectlist.get_columns():
if not c.treeview_column.get_visible():
continue
if c.data_type == gtk.gdk.Pixbuf:
continue
self._columns.append(c)
columns.append(dict(title=c.title, align=alignments.get(c.justify)))
return columns
[docs] def get_cell(self, obj, column):
#XXX Maybe the default value should be ''
return column.as_string(kgetattr(obj, column.attribute, None), obj)
[docs] def get_row(self, obj):
row = []
for c in self._columns:
row.append(self.get_cell(obj, c))
return row
[docs] def accumulate(self, row):
"""This method is called once for each row in the report.
Here you can create summaries (like the sum of all payments) for the
report, that will be added in the last row of the table
"""
for i in self.summary:
self._summary[i] += getattr(row, i, 0) or 0
[docs] def reset(self):
"""This is called when the iteration on all the rows starts.
Use this to setup or reset any necesary data (like the summaries)
"""
self._summary = {}
for i in self.summary:
self._summary[i] = 0
[docs] def get_summary_row(self):
if not self.summary:
return []
row = []
for column in self._columns:
value = self._summary.get(column.attribute, '')
if value:
value = column.as_string(value)
row.append(value)
return row
[docs]class ObjectTreeReport(ObjectListReport):
"""Creates an pdf report from an objecttree and its current state
This report will only show the columns that are visible, in the order they
are visible. It will also show the filters that were enabled when the report
was generated. And finnally display parent row in bold and children row
shifted a little bit to the right
"""
template_filename = "objecttree.html"
[docs] def get_row(self, obj):
row = []
for c in self._columns:
row.append(self.get_cell(obj, c))
return self.has_parent(obj), row
[docs] def has_parent(self, obj):
raise NotImplementedError