You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-lib/pym/calculate/contrib/spyne/protocol/html/table/column.py

337 lines
13 KiB

# -*- coding: utf-8 -*-
#
# spyne - Copyright (C) Spyne contributors.
#
# This library 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.1 of the License, or (at your option) any later version.
#
# This library 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 library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
#
import logging
logger = logging.getLogger(__name__)
from inspect import isgenerator
from lxml.html.builder import E
from spyne import ModelBase, ComplexModelBase, Array
from spyne.util import coroutine, Break, urlencode
from spyne.util.oset import oset
from spyne.protocol.html.table import HtmlTableBase
class HtmlColumnTableRowProtocol(object):
def column_table_gen_header(self, ctx, cls, parent, name, **kwargs):
return False
def column_table_before_row(self, ctx, cls, inst, parent, name, **kwargs):
pass
def column_table_after_row(self, ctx, cls, inst, parent, name, **kwargs):
pass
class HtmlColumnTable(HtmlTableBase, HtmlColumnTableRowProtocol):
"""Protocol that returns the response object as a html table.
Returns one record per table row in a table that has as many columns as
field names, just like a regular spreadsheet.
This is not quite unlike the HtmlMicroFormatprotocol, but returns data
as a html table using the <table> tag.
Generally used to serialize Array()'s of ComplexModel objects. If an
array has prot=HtmlColumnTable, its serializer (what's inside the Array( ))
must implement HtmlColumnTableRowProtocol interface.
:param app: A spyne.application.Application instance.
:param header: Boolean value to determine whether to show field
names in the beginning of the table or not. Defaults to True. Set to
False to skip headers.
:param table_name_attr: The name of the attribute that will contain the
response name of the complex object in the table tag. Set to None to
disable.
:param table_name: When not none, overrides what goes in `table_name_attr`.
:param table_class: When not none, specifies what goes in `class` attribute
in the `<table>` tag. Table name gets appended when
`table_name_attr == 'class'`
:param field_name_attr: The name of the attribute that will contain the
field names of the complex object children for every table cell. Set
to None to disable.
:param row_class: value that goes inside the <tr class="">
:param cell_class: value that goes inside the <td class="">
:param header_cell_class: value that goes inside the <th class="">
:param mrpc_delim_text: The text that goes between mrpc calls.
"""
def __init__(self, *args, **kwargs):
before_table = kwargs.pop('before_table', None)
super(HtmlColumnTable, self).__init__(*args, **kwargs)
self.serialization_handlers.update({
ModelBase: self.model_base_to_parent,
ComplexModelBase: self.complex_model_to_parent,
Array: self.array_to_parent,
})
if before_table is not None:
self.event_manager.add_listener("before_table", before_table)
def model_base_to_parent(self, ctx, cls, inst, parent, name,
from_arr=False, **kwargs):
inst_str = ''
if inst is not None:
inst_str = self.to_unicode(cls, inst)
if from_arr:
td_attrs = {}
self.add_field_attrs(td_attrs, name, cls)
parent.write(E.tr(E.td(inst_str, **td_attrs)))
else:
parent.write(inst_str)
@coroutine
def _gen_row(self, ctx, cls, inst, parent, name, from_arr=False,
array_index=None, **kwargs):
# because HtmlForm* protocols don't use the global null handler, it's
# possible for null values to reach here.
if inst is None:
return
logger.debug("Generate row for %r", cls)
mrpc_delim_elt = ''
if self.mrpc_delim_text is not None:
mrpc_delim_elt = E.span(self.mrpc_delim_text,
**{'class': 'mrpc-delimiter'})
mrpc_delim_elt.tail = ' '
with parent.element('tr'):
for k, v in self.sort_fields(cls):
cls_attr = self.get_cls_attrs(v)
if cls_attr.exc:
logger.debug("\tExclude table cell %r type %r for %r",
k, v, cls)
continue
try:
sub_value = getattr(inst, k, None)
except: # e.g. SQLAlchemy could throw NoSuchColumnError
sub_value = None
sub_name = cls_attr.sub_name
if sub_name is None:
sub_name = k
if self.hier_delim is not None:
if array_index is None:
sub_name = "%s%s%s" % (name, self.hier_delim, sub_name)
else:
sub_name = "%s[%d]%s%s" % (name, array_index,
self.hier_delim, sub_name)
logger.debug("\tGenerate table cell %r type %r for %r",
sub_name, v, cls)
td_attrs = {}
self.add_field_attrs(td_attrs, cls_attr.sub_name or k, v)
if cls_attr.hidden:
self.add_style(td_attrs, 'display:None')
with parent.element('td', td_attrs):
ret = self.to_parent(ctx, v, sub_value, parent, sub_name,
from_arr=from_arr, array_index=array_index, **kwargs)
if isgenerator(ret):
try:
while True:
sv2 = (yield)
ret.send(sv2)
except Break as b:
try:
ret.throw(b)
except StopIteration:
pass
m = cls.Attributes.methods
if m is not None and len(m) > 0:
td_attrs = {'class': 'mrpc-cell'}
with parent.element('td', td_attrs):
first = True
for mn, md in self._methods(ctx, cls, inst):
if first:
first = False
elif mrpc_delim_elt is not None:
parent.write(" ")
parent.write(mrpc_delim_elt)
pd = {}
for k, v in self.sort_fields(cls):
if getattr(v.Attributes, 'primary_key', None):
r = self.to_unicode(v, getattr(inst, k, None))
if r is not None:
pd[k] = r
params = urlencode(pd)
mdid2key = ctx.app.interface.method_descriptor_id_to_key
href = mdid2key[id(md)].rsplit("}", 1)[-1]
text = md.translate(ctx.locale,
md.in_message.get_type_name())
parent.write(E.a(
text,
href="%s?%s" % (href, params),
**{'class': 'mrpc-operation'}
))
logger.debug("Generate row for %r done.", cls)
self.extend_data_row(ctx, cls, inst, parent, name,
array_index=array_index, **kwargs)
def _gen_thead(self, ctx, cls, parent, name):
logger.debug("Generate header for %r", cls)
with parent.element('thead'):
with parent.element('tr'):
if issubclass(cls, ComplexModelBase):
fti = self.sort_fields(cls)
for k, v in fti:
cls_attr = self.get_cls_attrs(v)
if cls_attr.exc:
continue
th_attrs = {}
self.add_field_attrs(th_attrs, k, cls)
if cls_attr.hidden:
self.add_style(th_attrs, 'display:None')
header_name = self.trc(v, ctx.locale, k)
parent.write(E.th(header_name, **th_attrs))
m = cls.Attributes.methods
if m is not None and len(m) > 0:
th_attrs = {'class': 'mrpc-cell'}
parent.write(E.th(**th_attrs))
else:
th_attrs = {}
self.add_field_attrs(th_attrs, name, cls)
header_name = self.trc(cls, ctx.locale, name)
parent.write(E.th(header_name, **th_attrs))
self.extend_header_row(ctx, cls, parent, name)
@coroutine
def _gen_table(self, ctx, cls, inst, parent, name, gen_rows, **kwargs):
logger.debug("Generate table for %r", cls)
cls_attrs = self.get_cls_attrs(cls)
attrib = {}
table_class = oset()
if self.table_class is not None:
table_class.add(self.table_class)
if self.table_name_attr is not None:
tn = (self.table_name
if self.table_name is not None else cls.get_type_name())
if self.table_name_attr == 'class':
table_class.add(tn)
else:
attrib[self.table_name_attr] = tn
attrib['class'] = ' '.join(table_class)
if self.table_width is not None:
attrib['width'] = self.table_width
self.event_manager.fire_event('before_table', ctx, cls, inst, parent,
name, prot=self, **kwargs)
with parent.element('table', attrib):
write_header = self.header
if cls_attrs.header is False:
write_header = cls_attrs.header
if write_header:
ret = False
subprot = self.get_subprot(ctx, cls_attrs)
if subprot is not None:
ret = subprot.column_table_gen_header(ctx, cls, parent,
name)
if not ret:
self._gen_thead(ctx, cls, parent, name)
with parent.element('tbody'):
ret = gen_rows(ctx, cls, inst, parent, name, **kwargs)
if isgenerator(ret):
try:
while True:
sv2 = (yield)
ret.send(sv2)
except Break as b:
try:
ret.throw(b)
except StopIteration:
pass
self.extend_table(ctx, cls, parent, name, **kwargs)
def complex_model_to_parent(self, ctx, cls, inst, parent, name,
from_arr=False, **kwargs):
# If this is direct child of an array, table is already set up in
# array_to_parent.
if from_arr:
return self._gen_row(ctx, cls, inst, parent, name, **kwargs)
else:
return self.wrap_table(ctx, cls, inst, parent, name, self._gen_row,
**kwargs)
def array_to_parent(self, ctx, cls, inst, parent, name, **kwargs):
return self.wrap_table(ctx, cls, inst, parent, name,
super(HtmlColumnTable, self).array_to_parent, **kwargs)
def wrap_table(self, ctx, cls, inst, parent, name, gen_rows, **kwargs):
return self._gen_table(ctx, cls, inst, parent, name, gen_rows, **kwargs)
def extend_table(self, ctx, cls, parent, name, **kwargs):
"""This is called as the last operation during the table body generation
after all the <tr> tags are generated before exiting the <table> tag
which in turn is inside a <tbody> tag."""
def extend_data_row(self, ctx, cls, inst, parent, name, **kwargs):
"""This is called as the last operation during the row generation
after all the <td> tags are generated before exiting the <tr> tag which
in turn is inside a <tbody> tag."""
def extend_header_row(self, ctx, cls, parent, name, **kwargs):
"""This is called once as the last operation during the table header
generation after all the <th> tags are generated before exiting the <tr>
tag which in turn is inside a <thead> tag."""