{ "info": { "author": "Stephan Richter, Roger Ineichen and the Zope Community", "author_email": "zope-dev@zope.org", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: Zope :: 3", "Intended Audience :: Developers", "License :: OSI Approved :: Zope Public License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP" ], "description": ".. image:: https://img.shields.io/pypi/v/z3c.table.svg\n :target: https://pypi.python.org/pypi/z3c.table/\n :alt: Latest release\n\n.. image:: https://img.shields.io/pypi/pyversions/z3c.table.svg\n :target: https://pypi.org/project/z3c.table/\n :alt: Supported Python versions\n\n.. image:: https://travis-ci.org/zopefoundation/z3c.table.svg?branch=master\n :target: https://travis-ci.org/zopefoundation/z3c.table\n\n.. image:: https://coveralls.io/repos/github/zopefoundation/z3c.table/badge.svg\n :target: https://coveralls.io/github/zopefoundation/z3c.table\n\n\nThis package provides a modular table rendering implementation for Zope3.\n\n\n\n=========\nz3c Table\n=========\n\n.. contents::\n\nThe goal of this package is to offer a modular table rendering library. We use\nthe content provider pattern and the column are implemented as adapters which\nwill give us a powerful base concept.\n\nSome important concepts we use\n------------------------------\n\n- separate implementation in update render parts, This allows to manipulate\n data after update call and before we render them.\n\n- allow to use page templates if needed. By default all is done in python.\n\n- allow to use the rendered batch outside the existing table HTML part.\n\nNo skins\n--------\n\nThis package does not provide any kind of template or skin support. Most the\ntime if you need to render a table, you will use your own skin concept. This means\nyou can render the table or batch within your own templates. This will ensure\nthat we have as few dependencies as possible in this package and the package\ncan get reused with any skin concept.\n\nNote\n----\n\nAs you probably know, batching is only possible after sorting columns. This is\na nightmare if it comes to performance. The reason is, all data need to get\nsorted before the batch can start at the given position. And sorting can most\nof the time only be done by touching each object. This means you have to be careful\nif you are using a large set of data, even if you use batching.\n\nSample data setup\n-----------------\n\nLet's create a sample container which we can use as our iterable context:\n\n >>> from zope.container import btree\n >>> class Container(btree.BTreeContainer):\n ... \"\"\"Sample container.\"\"\"\n ... __name__ = u'container'\n >>> container = Container()\n\nand set a parent for the container:\n\n >>> root['container'] = container\n\nand create a sample content object which we use as container item:\n\n >>> class Content(object):\n ... \"\"\"Sample content.\"\"\"\n ... def __init__(self, title, number):\n ... self.title = title\n ... self.number = number\n\nNow setup some items:\n\n >>> container[u'first'] = Content('First', 1)\n >>> container[u'second'] = Content('Second', 2)\n >>> container[u'third'] = Content('Third', 3)\n\n\nTable\n-----\n\nCreate a test request and represent the table:\n\n >>> from zope.publisher.browser import TestRequest\n >>> from z3c.table import table\n >>> request = TestRequest()\n >>> plainTable = table.Table(container, request)\n >>> plainTable.cssClassSortedOn = None\n\nNow we can update and render the table. As you can see with an empty container\nwe will not get anything that looks like a table. We just get an empty string:\n\n >>> plainTable.update()\n >>> plainTable.render()\n u''\n\n\nColumn Adapter\n--------------\n\nWe can create a column for our table:\n\n >>> import zope.component\n >>> from z3c.table import interfaces\n >>> from z3c.table import column\n\n >>> class TitleColumn(column.Column):\n ...\n ... weight = 10\n ... header = u'Title'\n ...\n ... def renderCell(self, item):\n ... return u'Title: %s' % item.title\n\nNow we can register the column:\n\n >>> zope.component.provideAdapter(TitleColumn,\n ... (None, None, interfaces.ITable), provides=interfaces.IColumn,\n ... name='firstColumn')\n\nNow we can render the table again:\n\n >>> plainTable.update()\n >>> print(plainTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Title
Title: First
Title: Second
Title: Third
\n\nWe can also use the predefined name column:\n\n >>> zope.component.provideAdapter(column.NameColumn,\n ... (None, None, interfaces.ITable), provides=interfaces.IColumn,\n ... name='secondColumn')\n\nNow we will get an additional column:\n\n >>> plainTable.update()\n >>> print(plainTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NameTitle
firstTitle: First
secondTitle: Second
thirdTitle: Third
\n\n\nColspan\n-------\n\nNow let's show how we can define a colspan condition of 2 for a column:\n\n >>> class ColspanColumn(column.NameColumn):\n ...\n ... weight = 999\n ...\n ... def getColspan(self, item):\n ... # colspan condition\n ... if item.__name__ == 'first':\n ... return 2\n ... else:\n ... return 0\n ...\n ... def renderHeadCell(self):\n ... return u'Colspan'\n ...\n ... def renderCell(self, item):\n ... return u'colspan: %s' % item.title\n\nNow we register this column adapter as colspanColumn:\n\n >>> zope.component.provideAdapter(ColspanColumn,\n ... (None, None, interfaces.ITable), provides=interfaces.IColumn,\n ... name='colspanColumn')\n\nNow you can see that the colspan of the ColspanAdapter is larger than the table.\nThis will raise a ValueError:\n\n >>> plainTable.update()\n Traceback (most recent call last):\n ...\n ValueError: Colspan for column '' is larger than the table.\n\nBut if we set the column as first row, it will render the colspan correctly:\n\n >>> class CorrectColspanColumn(ColspanColumn):\n ... \"\"\"Colspan with correct weight.\"\"\"\n ...\n ... weight = -1 # NameColumn is 0\n\nRegister and render the table again:\n\n >>> zope.component.provideAdapter(CorrectColspanColumn,\n ... (None, None, interfaces.ITable), provides=interfaces.IColumn,\n ... name='colspanColumn')\n\n >>> plainTable.update()\n >>> print(plainTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
ColspanNameTitle
colspan: FirstTitle: First
colspan: SecondsecondTitle: Second
colspan: ThirdthirdTitle: Third
\n\nSetup columns\n-------------\n\nThe existing implementation allows us to define a table in a class without\nusing the modular adapter pattern for columns.\n\nFirst we need to define a column which can render a value for our items:\n\n >>> class SimpleColumn(column.Column):\n ...\n ... weight = 0\n ...\n ... def renderCell(self, item):\n ... return item.title\n\nLet's define our table which defines the columns explicitly. you can also see\nthat we do not return the columns in the correct order:\n\n >>> class PrivateTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... firstColumn = TitleColumn(self.context, self.request, self)\n ... firstColumn.__name__ = u'title'\n ... firstColumn.weight = 1\n ... secondColumn = SimpleColumn(self.context, self.request, self)\n ... secondColumn.__name__ = u'simple'\n ... secondColumn.weight = 2\n ... secondColumn.header = u'The second column'\n ... return [secondColumn, firstColumn]\n\nNow we can create, update and render the table and see that this renders a nice\ntable too:\n\n >>> privateTable = PrivateTable(container, request)\n >>> privateTable.update()\n >>> print(privateTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleThe second column
Title: FirstFirst
Title: SecondSecond
Title: ThirdThird
\n\n\nCascading Style Sheet\n---------------------\n\nOur table and column implementation supports css class assignment. Let's define\na table and columns with some css class values:\n\n >>> class CSSTable(table.Table):\n ...\n ... cssClasses = {'table': 'table',\n ... 'thead': 'thead',\n ... 'tbody': 'tbody',\n ... 'th': 'th',\n ... 'tr': 'tr',\n ... 'td': 'td'}\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... firstColumn = TitleColumn(self.context, self.request, self)\n ... firstColumn.__name__ = u'title'\n ... firstColumn.__parent__ = self\n ... firstColumn.weight = 1\n ... firstColumn.cssClasses = {'th':'thCol', 'td':'tdCol'}\n ... secondColumn = SimpleColumn(self.context, self.request, self)\n ... secondColumn.__name__ = u'simple'\n ... secondColumn.__parent__ = self\n ... secondColumn.weight = 2\n ... secondColumn.header = u'The second column'\n ... return [secondColumn, firstColumn]\n\nNow let's see if we got the css class assigned which we defined in the table and\ncolumn. Note that the ``th`` and ``td`` got CSS declarations from the table and\nfrom the column:\n\n >>> cssTable = CSSTable(container, request)\n >>> cssTable.update()\n >>> print(cssTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleThe second column
Title: FirstFirst
Title: SecondSecond
Title: ThirdThird
\n\n\nAlternating table\n-----------------\n\nWe offer built in support for alternating table rows based on even and odd CSS\nclasses. Let's define a table including other CSS classes. For even/odd support,\nwe only need to define the ``cssClassEven`` and ``cssClassOdd`` CSS classes:\n\n >>> class AlternatingTable(table.Table):\n ...\n ... cssClasses = {'table': 'table',\n ... 'thead': 'thead',\n ... 'tbody': 'tbody',\n ... 'th': 'th',\n ... 'tr': 'tr',\n ... 'td': 'td'}\n ...\n ... cssClassEven = u'even'\n ... cssClassOdd = u'odd'\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... firstColumn = TitleColumn(self.context, self.request, self)\n ... firstColumn.__name__ = u'title'\n ... firstColumn.__parent__ = self\n ... firstColumn.weight = 1\n ... firstColumn.cssClasses = {'th':'thCol', 'td':'tdCol'}\n ... secondColumn = SimpleColumn(self.context, self.request, self)\n ... secondColumn.__name__ = u'simple'\n ... secondColumn.__parent__ = self\n ... secondColumn.weight = 2\n ... secondColumn.header = u'The second column'\n ... return [secondColumn, firstColumn]\n\nNow update and render the new table. As you can see the given ``tr`` class is\nadded to the even and odd classes:\n\n >>> alternatingTable = AlternatingTable(container, request)\n >>> alternatingTable.update()\n >>> print(alternatingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleThe second column
Title: FirstFirst
Title: SecondSecond
Title: ThirdThird
\n\n\nClass based Table setup\n-----------------------\n\nThere is a more elegant way to define table rows at class level. We offer\na method which you can use if you need to define some columns called\n``addColumn``. Before we define the table. let's define some cell renderer:\n\n >>> def headCellRenderer():\n ... return u'My items'\n\n >>> def cellRenderer(item):\n ... return u'%s item' % item.title\n\nNow we can define our table and use the custom cell renderer:\n\n >>> class AddColumnTable(table.Table):\n ...\n ... cssClasses = {'table': 'table',\n ... 'thead': 'thead',\n ... 'tbody': 'tbody',\n ... 'th': 'th',\n ... 'tr': 'tr',\n ... 'td': 'td'}\n ...\n ... cssClassEven = u'even'\n ... cssClassOdd = u'odd'\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, TitleColumn, u'title',\n ... cellRenderer=cellRenderer,\n ... headCellRenderer=headCellRenderer,\n ... weight=1, colspan=0),\n ... column.addColumn(self, SimpleColumn, name=u'simple',\n ... weight=2, header=u'The second column',\n ... cssClasses = {'th':'thCol', 'td':'tdCol'})\n ... ]\n\nAdd some more content::\n\n >>> container[u'fourth'] = Content('Fourth', 4)\n >>> container[u'zero'] = Content('Zero', 0)\n\n >>> addColumnTable = AddColumnTable(container, request)\n >>> addColumnTable.update()\n >>> print(addColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsThe second column
First itemFirst
Fourth itemFourth
Second itemSecond
Third itemThird
Zero itemZero
\n\nAs you can see the table columns provide all attributes we set in the addColumn\nmethod:\n\n >>> titleColumn = addColumnTable.rows[0][0][1]\n >>> titleColumn\n \n\n >>> titleColumn.__name__\n u'title'\n\n >>> titleColumn.__parent__\n \n\n >>> titleColumn.colspan\n 0\n\n >>> titleColumn.weight\n 1\n\n >>> titleColumn.header\n u'Title'\n\n >>> titleColumn.cssClasses\n {}\n\nand the second column:\n\n >>> simpleColumn = addColumnTable.rows[0][1][1]\n >>> simpleColumn\n \n\n >>> simpleColumn.__name__\n u'simple'\n\n >>> simpleColumn.__parent__\n \n\n >>> simpleColumn.colspan\n 0\n\n >>> simpleColumn.weight\n 2\n\n >>> simpleColumn.header\n u'The second column'\n\n >>> sorted(simpleColumn.cssClasses.items())\n [('td', 'tdCol'), ('th', 'thCol')]\n\n\nHeaders\n-------\n\nWe can change the rendering of the header of, e.g, the Title column by\nregistering a IHeaderColumn adapter. This may be useful for adding links to\ncolumn headers for an existing table implementation.\n\nWe'll use a fresh almost empty container.:\n\n >>> container = Container()\n >>> root['container-1'] = container\n >>> container[u'first'] = Content('First', 1)\n >>> container[u'second'] = Content('Second', 2)\n >>> container[u'third'] = Content('Third', 3)\n\n >>> class myTableClass(table.Table):\n ... cssClassSortedOn = None\n\n >>> myTable = myTableClass(container, request)\n\n >>> class TitleColumn(column.Column):\n ...\n ... header = u'Title'\n ... weight = -2\n ...\n ... def renderCell(self, item):\n ... return item.title\n\nNow we can register a column adapter directly to our table class:\n\n >>> zope.component.provideAdapter(TitleColumn,\n ... (None, None, myTableClass), provides=interfaces.IColumn,\n ... name='titleColumn')\n\nAnd add a registration for a column header - we'll use here the provided generic\nsorting header implementation:\n\n >>> from z3c.table.header import SortingColumnHeader\n >>> zope.component.provideAdapter(SortingColumnHeader,\n ... (None, None, interfaces.ITable, interfaces.IColumn),\n ... provides=interfaces.IColumnHeader)\n\nNow we can render the table and we shall see a link in the header. Note that it\nis set to switch to descending as the table initially will display the first\ncolumn as ascending:\n\n >>> myTable.update()\n >>> print(myTable.render())\n \n \n \n \n ...\n
Title
\n\nIf the table is initially set to descending, the link should allow to switch to\nascending again:\n\n >>> myTable.sortOrder = 'descending'\n >>> print(myTable.render())\n \n \n \n \n ...\n
Title
\n\nIf the table is ascending but the request was descending,\nthe link should allow to switch again to ascending:\n\n >>> descendingRequest = TestRequest(form={'table-sortOn': 'table-titleColumn-0',\n ... 'table-sortOrder':'descending'})\n >>> myTable = myTableClass(container, descendingRequest)\n >>> myTable.sortOrder = 'ascending'\n >>> myTable.update()\n >>> print(myTable.render())\n \n \n \n \n ...\n
Title
\n\n\nSorting Table\n-------------\n\nAnother table feature is the support for sorting data given from columns. Since\nsorting table data is an important feature, we offer this by default. But it\nonly gets used if there is a ``sortOn`` value set. You can set this value at\nclass level by adding a ``defaultSortOn`` value or set it as a request value.\nWe show you how to do this later. We also need a columns which allows us to do\na better sort sample. Our new sorting column will use the content items number\nvalue for sorting:\n\n >>> from z3c.table import column, table\n >>> class NumberColumn(column.Column):\n ...\n ... header = u'Number'\n ... weight = 20\n ...\n ... def getSortKey(self, item):\n ... return item.number\n ...\n ... def renderCell(self, item):\n ... return 'number: %s' % item.number\n\n\nNow let's set up a table:\n\n >>> from z3c.table.testing import TitleColumn\n >>> class SortingTable(table.Table):\n ...\n ... def setUpColumns(self):\n ... firstColumn = TitleColumn(self.context, self.request, self)\n ... firstColumn.__name__ = u'title'\n ... firstColumn.__parent__ = self\n ... secondColumn = NumberColumn(self.context, self.request, self)\n ... secondColumn.__name__ = u'number'\n ... secondColumn.__parent__ = self\n ... return [firstColumn, secondColumn]\n\nCreate a container:\n\n >>> from z3c.table.testing import OrderedContainer\n >>> container = OrderedContainer()\n\nWe also need some container items that we can use for sorting:\n\n >>> from z3c.table.testing import Content\n >>> container[u'first'] = Content('First', 1)\n >>> container[u'second'] = Content('Second', 2)\n >>> container[u'third'] = Content('Third', 3)\n >>> container[u'fourth'] = Content('Fourth', 4)\n >>> container[u'zero'] = Content('Zero', 0)\n\nAnd render them without set a ``sortOn`` value:\n\n >>> from zope.publisher.browser import TestRequest\n >>> request = TestRequest()\n >>> sortingTable = SortingTable(container, request)\n >>> sortingTable.update()\n >>> print(sortingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleNumber
Title: Firstnumber: 1
Title: Fourthnumber: 4
Title: Secondnumber: 2
Title: Thirdnumber: 3
Title: Zeronumber: 0
\n\nOoops, well, by default the table is sorted on the first column, ascending.\n\n >>> sortingTable.sortOn\n 0\n\nNow switch off sorting, now we get the original order:\n\n >>> sortingTable.sortOn = None\n >>> sortingTable.update()\n >>> print(sortingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleNumber
Title: Firstnumber: 1
Title: Secondnumber: 2
Title: Thirdnumber: 3
Title: Fourthnumber: 4
Title: Zeronumber: 0
\n\n\nAs you can see this table doesn't provide any explicit order. Let's find out\nthe index of our column that we like to sort on:\n\n >>> sortOnId = sortingTable.rows[0][1][1].id\n >>> sortOnId\n u'table-number-1'\n\nAnd let's use this id as ``sortOn`` value:\n\n >>> sortingTable.sortOn = sortOnId\n\nAn important thing is to update the table after set an ``sortOn`` value:\n\n >>> sortingTable.update()\n >>> print(sortingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleNumber
Title: Zeronumber: 0
Title: Firstnumber: 1
Title: Secondnumber: 2
Title: Thirdnumber: 3
Title: Fourthnumber: 4
\n\nWe can also reverse the sorting order:\n\n >>> sortingTable.sortOrder = 'reverse'\n >>> sortingTable.update()\n >>> print(sortingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleNumber
Title: Fourthnumber: 4
Title: Thirdnumber: 3
Title: Secondnumber: 2
Title: Firstnumber: 1
Title: Zeronumber: 0
\n\nThe table implementation is also able to get the sorting criteria given from a\nrequest. Let's setup such a request:\n\n >>> sorterRequest = TestRequest(form={'table-sortOn': 'table-number-1',\n ... 'table-sortOrder':'descending'})\n\nand another time, update and render. As you can see the new table gets sorted\nby the second column and ordered in reverse order:\n\n >>> requestSortedTable = SortingTable(container, sorterRequest)\n >>> requestSortedTable.update()\n >>> print(requestSortedTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleNumber
Title: Fourthnumber: 4
Title: Thirdnumber: 3
Title: Secondnumber: 2
Title: Firstnumber: 1
Title: Zeronumber: 0
\n\nThere's a header renderer, which provides a handy link rendering for sorting:\n\n >>> import zope.component\n >>> from z3c.table import interfaces\n >>> from z3c.table.header import SortingColumnHeader\n >>> zope.component.provideAdapter(SortingColumnHeader,\n ... (None, None, interfaces.ITable, interfaces.IColumn),\n ... provides=interfaces.IColumnHeader)\n\nLet's see now various sortings:\n\n >>> request = TestRequest()\n >>> sortingTable = SortingTable(container, request)\n >>> sortingTable.update()\n >>> sortingTable.sortOn\n 0\n >>> sortingTable.sortOrder\n u'ascending'\n >>> print(sortingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleNumber
Title: Firstnumber: 1
Title: Fourthnumber: 4
Title: Secondnumber: 2
Title: Thirdnumber: 3
Title: Zeronumber: 0
\n\nLet's see the `number` column:\n\n >>> sortingTable.sortOn = u'table-number-1'\n\n >>> sortingTable.update()\n >>> print(sortingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleNumber
Title: Zeronumber: 0
Title: Firstnumber: 1
Title: Secondnumber: 2
Title: Thirdnumber: 3
Title: Fourthnumber: 4
\n\nLet's see the `title` column but descending:\n\n >>> sortingTable.sortOn = u'table-title-0'\n >>> sortingTable.sortOrder = 'descending'\n\n >>> sortingTable.update()\n >>> print(sortingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
TitleNumber
Title: Zeronumber: 0
Title: Thirdnumber: 3
Title: Secondnumber: 2
Title: Fourthnumber: 4
Title: Firstnumber: 1
\n\n\nBatching\n--------\n\nOur table implements batching out of the box. If the amount of\nrow items is smaller than the given ``startBatchingAt`` size, the table starts\nto batch at this size. Let's define a new Table.\n\nWe need to configure our batch provider for the next step first. See the\nsection ``BatchProvider`` below for more infos about batch rendering:\n\n >>> from zope.configuration.xmlconfig import XMLConfig\n >>> import z3c.table\n >>> import zope.component\n >>> XMLConfig('meta.zcml', zope.component)()\n >>> XMLConfig('configure.zcml', z3c.table)()\n\nNow we can create our table:\n\n >>> from zope.publisher.browser import TestRequest\n >>> from z3c.table.testing import Container, Content, SimpleTable\n >>> container = Container()\n >>> root['container-1'] = container\n >>> request = TestRequest()\n >>> batchingTable = SimpleTable(container, request)\n >>> batchingTable.cssClassSortedOn = None\n\nWe also need to give the table a location and a name like we normally setup\nin traversing:\n\n >>> batchingTable.__parent__ = container\n >>> batchingTable.__name__ = u'batchingTable.html'\n\nNow setup some items:\n\n >>> container[u'zero'] = Content('Zero', 0)\n >>> container[u'first'] = Content('First', 1)\n >>> container[u'second'] = Content('Second', 2)\n >>> container[u'third'] = Content('Third', 3)\n >>> container[u'fourth'] = Content('Fourth', 4)\n >>> container[u'sixth'] = Content('Sixth', 6)\n >>> container[u'seventh'] = Content('Seventh', 7)\n >>> container[u'eighth'] = Content('Eighth', 8)\n >>> container[u'ninth'] = Content('Ninth', 9)\n >>> container[u'tenth'] = Content('Tenth', 10)\n >>> container[u'eleventh'] = Content('Eleventh', 11)\n >>> container[u'twelfth '] = Content('Twelfth', 12)\n >>> container[u'thirteenth'] = Content('Thirteenth', 13)\n >>> container[u'fourteenth'] = Content('Fourteenth', 14)\n >>> container[u'fifteenth '] = Content('Fifteenth', 15)\n >>> container[u'sixteenth'] = Content('Sixteenth', 16)\n >>> container[u'seventeenth'] = Content('Seventeenth', 17)\n >>> container[u'eighteenth'] = Content('Eighteenth', 18)\n >>> container[u'nineteenth'] = Content('Nineteenth', 19)\n >>> container[u'twentieth'] = Content('Twentieth', 20)\n\nNow let's show the full table without batching:\n\n >>> batchingTable.update()\n >>> print(batchingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
Eighteenth itemnumber: 18
Eighth itemnumber: 8
Eleventh itemnumber: 11
Fifteenth itemnumber: 15
First itemnumber: 1
Fourteenth itemnumber: 14
Fourth itemnumber: 4
Nineteenth itemnumber: 19
Ninth itemnumber: 9
Second itemnumber: 2
Seventeenth itemnumber: 17
Seventh itemnumber: 7
Sixteenth itemnumber: 16
Sixth itemnumber: 6
Tenth itemnumber: 10
Third itemnumber: 3
Thirteenth itemnumber: 13
Twelfth itemnumber: 12
Twentieth itemnumber: 20
Zero itemnumber: 0
\n\nAs you can see, the table is not ordered and it uses all items. If we like\nto use the batch, we need to set the startBatchingAt size to a lower value than\nit is set by default.\nThe default value which a batch is used is set to ``50``:\n\n >>> batchingTable.startBatchingAt\n 50\n\nWe will set the batch start to ``5`` for now. This means the first 5 items\ndo not get used:\n\n >>> batchingTable.startBatchingAt = 5\n >>> batchingTable.startBatchingAt\n 5\n\nThere is also a ``batchSize`` value which we need to set to ``5``. By default\nthe value gets initialized by the ``batchSize`` value:\n\n >>> batchingTable.batchSize\n 50\n\n >>> batchingTable.batchSize = 5\n >>> batchingTable.batchSize\n 5\n\nNow we can update and render the table again. But you will see that we only get\na table size of 5 rows, which is correct. But the order doesn't depend on the\nnumbers we see in cells:\n\n >>> batchingTable.update()\n >>> print(batchingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
Eighteenth itemnumber: 18
Eighth itemnumber: 8
Eleventh itemnumber: 11
Fifteenth itemnumber: 15
First itemnumber: 1
\n\nI think we should order the table by the second column before we show the next\nbatch values. We do this by simply set the ``defaultSortOn``:\n\n >>> batchingTable.sortOn = u'table-number-1'\n\nNow we should see a nice ordered and batched table:\n\n >>> batchingTable.update()\n >>> print(batchingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
Zero itemnumber: 0
First itemnumber: 1
Second itemnumber: 2
Third itemnumber: 3
Fourth itemnumber: 4
\n\nThe batch concept allows us to choose from all batches and render the rows\nfor this batched items. We can do this by set any batch as rows. as you can see\nwe have ``4`` batched row data available:\n\n >>> len(batchingTable.rows.batches)\n 4\n\nWe can set such a batch as row values, then this batch data are used for\nrendering. But take care, if we update the table, our rows get overridden\nand reset to the previous values. this means you can set any batch as rows\ndata and only render them. This is possible since the update method sorted all\nitems and all batch contain ready-to-use data. This concept could be important\nif you need to cache batches etc. :\n\n >>> batchingTable.rows = batchingTable.rows.batches[1]\n >>> print(batchingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
Sixth itemnumber: 6
Seventh itemnumber: 7
Eighth itemnumber: 8
Ninth itemnumber: 9
Tenth itemnumber: 10
\n\nAnd like described above, if you call ``update`` our batch to rows setup get\nreset:\n\n >>> batchingTable.update()\n >>> print(batchingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
Zero itemnumber: 0
First itemnumber: 1
Second itemnumber: 2
Third itemnumber: 3
Fourth itemnumber: 4
\n\nThis means you can probably update all batches, cache them and use them after.\nBut this is not useful for normal usage in a page without an enhanced concept\nwhich is not a part of this implementation. This also means, there must be\nanother way to set the batch index. Yes there is, there are two other ways how\nwe can set the batch position. We can set a batch position by setting the\n``batchStart`` value in our table or we can use a request variable. Let's show\nthe first one first:\n\n >>> batchingTable.batchStart = 6\n >>> batchingTable.update()\n >>> print(batchingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
Seventh itemnumber: 7
Eighth itemnumber: 8
Ninth itemnumber: 9
Tenth itemnumber: 10
Eleventh itemnumber: 11
\n\nWe can also set the batch position by using the batchStart value in a request.\nNote that we need the table ``prefix`` and column ``__name__`` like we use in\nthe sorting concept:\n\n >>> batchingRequest = TestRequest(form={'table-batchStart': '11',\n ... 'table-batchSize': '5',\n ... 'table-sortOn': 'table-number-1'})\n >>> requestBatchingTable = SimpleTable(container, batchingRequest)\n >>> requestBatchingTable.cssClassSortedOn = None\n\nWe also need to give the table a location and a name like we normally set up\nin traversing:\n\n >>> requestBatchingTable.__parent__ = container\n >>> requestBatchingTable.__name__ = u'requestBatchingTable.html'\n\nNote: our table needs to start batching at smaller amount of items than we\nhave by default otherwise we don't get a batch:\n\n >>> requestBatchingTable.startBatchingAt = 5\n >>> requestBatchingTable.update()\n >>> print(requestBatchingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
Twelfth itemnumber: 12
Thirteenth itemnumber: 13
Fourteenth itemnumber: 14
Fifteenth itemnumber: 15
Sixteenth itemnumber: 16
\n\n\nBatchProvider\n-------------\n\nThe batch provider allows us to render the batch HTML independently of our\ntable. This means by default the batch gets not rendered in the render method.\nYou can change this in your custom table implementation and return the batch\nand the table in the render method.\n\nAs we can see, our table rows provides IBatch if it comes to batching:\n\n >>> from z3c.batching.interfaces import IBatch\n >>> IBatch.providedBy(requestBatchingTable.rows)\n True\n\nLet's check some batch variables before we render our test. This let us compare\nthe rendered result. For more information about batching see the README.txt in\nz3c.batching:\n\n >>> requestBatchingTable.rows.start\n 11\n\n >>> requestBatchingTable.rows.index\n 2\n\n >>> requestBatchingTable.rows.batches\n \n\n >>> len(requestBatchingTable.rows.batches)\n 4\n\nWe use our previous batching table and render the batch with the built-in\n``renderBatch`` method:\n\n >>> requestBatchingTable.update()\n >>> print(requestBatchingTable.renderBatch())\n 1\n 2\n 3\n 4\n\nNow let's add more items so that we can test the skipped links in large\nbatches:\n\n >>> for i in range(1000):\n ... idx = i+20\n ... container[str(idx)] = Content(str(idx), idx)\n\nNow let's test the batching table again with the new amount of items and\nthe same ``startBatchingAt`` of 5 but starting the batch at item ``100``\nand sorted on the second numbered column:\n\n >>> batchingRequest = TestRequest(form={'table-batchStart': '100',\n ... 'table-batchSize': '5',\n ... 'table-sortOn': 'table-number-1'})\n >>> requestBatchingTable = SimpleTable(container, batchingRequest)\n >>> requestBatchingTable.startBatchingAt = 5\n >>> requestBatchingTable.cssClassSortedOn = None\n\nWe also need to give the table a location and a name like we normally setup\nin traversing:\n\n >>> requestBatchingTable.__parent__ = container\n >>> requestBatchingTable.__name__ = u'requestBatchingTable.html'\n\n >>> requestBatchingTable.update()\n >>> print(requestBatchingTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
100 itemnumber: 100
101 itemnumber: 101
102 itemnumber: 102
103 itemnumber: 103
104 itemnumber: 104
\n\nAnd test the batch. Note the three dots between the links are rendered by the\nbatch provider and are not a part of the doctest:\n\n >>> print(requestBatchingTable.renderBatch())\n 1\n ...\n 18\n 19\n 20\n 21\n 22\n 23\n 24\n ...\n 204\n\nYou can change the spacer in the batch provider if you set the ``batchSpacer``\nvalue:\n\n >>> from z3c.table.batch import BatchProvider\n >>> from z3c.table.interfaces import IBatchProvider\n >>> from zope.interface import implementer\n >>> @implementer(IBatchProvider)\n ... class XBatchProvider(BatchProvider):\n ... \"\"\"Just another batch provider.\"\"\"\n ... batchSpacer = u'xxx'\n\nNow register the new batch provider for our batching table:\n\n >>> import zope.publisher.interfaces.browser\n >>> from zope.component import getSiteManager\n >>> sm = getSiteManager(container)\n >>> sm.registerAdapter(XBatchProvider,\n ... (zope.interface.Interface,\n ... zope.publisher.interfaces.browser.IBrowserRequest,\n ... SimpleTable), name='batch')\n\nIf we update and render our table, the new batch provider should get used.\nAs you can see the spacer get changed now:\n\n >>> requestBatchingTable.update()\n >>> requestBatchingTable.batchProvider\n <...XBatchProvider object at ...>\n >>> print(requestBatchingTable.renderBatch())\n 1\n xxx\n 18\n 19\n 20\n 21\n 22\n 23\n 24\n xxx\n 204\n\n\nNow test the extremities, need to define a new batchingRequest:\nBeginning by the left end point:\n\n >>> leftBatchingRequest = TestRequest(form={'table-batchStart': '10',\n ... 'table-batchSize': '5',\n ... 'table-sortOn': 'table-number-1'})\n >>> leftRequestBatchingTable = SimpleTable(container, leftBatchingRequest)\n >>> leftRequestBatchingTable.__parent__ = container\n >>> leftRequestBatchingTable.__name__ = u'leftRequestBatchingTable.html'\n >>> leftRequestBatchingTable.update()\n >>> print(leftRequestBatchingTable.renderBatch())\n 1\n 2\n 3\n 4\n 5\n 6\n xxx\n 204\n\nGo on with the right extremity:\n\n >>> rightBatchingRequest = TestRequest(form={'table-batchStart': '1005',\n ... 'table-batchSize': '5',\n ... 'table-sortOn': 'table-number-1'})\n >>> rightRequestBatchingTable = SimpleTable(container, rightBatchingRequest)\n >>> rightRequestBatchingTable.__parent__ = container\n >>> rightRequestBatchingTable.__name__ = u'rightRequestBatchingTable.html'\n >>> rightRequestBatchingTable.update()\n >>> print(rightRequestBatchingTable.renderBatch())\n 1\n xxx\n 199\n 200\n 201\n 202\n 203\n 204\n\n\nNone previous and next batch size. Probably it doesn't make sense but let's\nshow what happens if we set the previous and next batch size to 0 (zero):\n\n >>> from z3c.table.batch import BatchProvider\n >>> class ZeroBatchProvider(BatchProvider):\n ... \"\"\"Just another batch provider.\"\"\"\n ... batchSpacer = u'xxx'\n ... previousBatchSize = 0\n ... nextBatchSize = 0\n\nNow register the new batch provider for our batching table:\n\n >>> import zope.publisher.interfaces.browser\n >>> sm.registerAdapter(ZeroBatchProvider,\n ... (zope.interface.Interface,\n ... zope.publisher.interfaces.browser.IBrowserRequest,\n ... SimpleTable), name='batch')\n\nUpdate the table and render the batch:\n\n >>> requestBatchingTable.update()\n >>> print(requestBatchingTable.renderBatch())\n 1\n xxx\n 21\n xxx\n 204\n\n\nSequenceTable\n-------------\n\nA sequence table can be used if we need to provide a table for a sequence\nof items instead of a mapping. Define the same sequence of items we used before\nwe added the other 1000 items:\n\n >>> from z3c.table.testing import Content\n >>> dataSequence = []\n >>> dataSequence.append(Content('Zero', 0))\n >>> dataSequence.append(Content('First', 1))\n >>> dataSequence.append(Content('Second', 2))\n >>> dataSequence.append(Content('Third', 3))\n >>> dataSequence.append(Content('Fourth', 4))\n >>> dataSequence.append(Content('Fifth', 5))\n >>> dataSequence.append(Content('Sixth', 6))\n >>> dataSequence.append(Content('Seventh', 7))\n >>> dataSequence.append(Content('Eighth', 8))\n >>> dataSequence.append(Content('Ninth', 9))\n >>> dataSequence.append(Content('Tenth', 10))\n >>> dataSequence.append(Content('Eleventh', 11))\n >>> dataSequence.append(Content('Twelfth', 12))\n >>> dataSequence.append(Content('Thirteenth', 13))\n >>> dataSequence.append(Content('Fourteenth', 14))\n >>> dataSequence.append(Content('Fifteenth', 15))\n >>> dataSequence.append(Content('Sixteenth', 16))\n >>> dataSequence.append(Content('Seventeenth', 17))\n >>> dataSequence.append(Content('Eighteenth', 18))\n >>> dataSequence.append(Content('Nineteenth', 19))\n >>> dataSequence.append(Content('Twentieth', 20))\n\nNow let's define a new SequenceTable:\n\n >>> from z3c.table import table, column\n >>> from z3c.table.testing import (TitleColumn, NumberColumn, cellRenderer,\n ... headCellRenderer)\n >>> class SequenceTable(table.SequenceTable):\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, TitleColumn, u'title',\n ... cellRenderer=cellRenderer,\n ... headCellRenderer=headCellRenderer,\n ... weight=1),\n ... column.addColumn(self, NumberColumn, name=u'number',\n ... weight=2, header=u'Number')\n ... ]\n\nNow we can create our table adapting our sequence:\n\n >>> from zope.publisher.browser import TestRequest\n >>> sequenceRequest = TestRequest(form={'table-batchStart': '0',\n ... 'table-sortOn': 'table-number-1'})\n >>> sequenceTable = SequenceTable(dataSequence, sequenceRequest)\n >>> sequenceTable.cssClassSortedOn = None\n\nWe also need to give the table a location and a name like we normally setup\nin traversing:\n\n >>> from z3c.table.testing import Container\n >>> container = Container()\n >>> root['container-1'] = container\n >>> sequenceTable.__parent__ = container\n >>> sequenceTable.__name__ = u'sequenceTable.html'\n\nWe need to configure our batch provider for the next step first. See the\nsection ``BatchProvider`` below for more infos about batch rendering:\n\n >>> from zope.configuration.xmlconfig import XMLConfig\n >>> import z3c.table\n >>> import zope.component\n >>> XMLConfig('meta.zcml', zope.component)()\n >>> XMLConfig('configure.zcml', z3c.table)()\n\nAnd update and render the sequence table:\n\n >>> sequenceTable.update()\n >>> print(sequenceTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
Zero itemnumber: 0
First itemnumber: 1
Second itemnumber: 2
Third itemnumber: 3
Fourth itemnumber: 4
Fifth itemnumber: 5
Sixth itemnumber: 6
Seventh itemnumber: 7
Eighth itemnumber: 8
Ninth itemnumber: 9
Tenth itemnumber: 10
Eleventh itemnumber: 11
Twelfth itemnumber: 12
Thirteenth itemnumber: 13
Fourteenth itemnumber: 14
Fifteenth itemnumber: 15
Sixteenth itemnumber: 16
Seventeenth itemnumber: 17
Eighteenth itemnumber: 18
Nineteenth itemnumber: 19
Twentieth itemnumber: 20
\n\nAs you can see, the items get rendered based on a data sequence. Now we set\nthe ``start batch at`` size to ``5``:\n\n >>> sequenceTable.startBatchingAt = 5\n\nAnd the ``batchSize`` to ``5``:\n\n >>> sequenceTable.batchSize = 5\n\nNow we can update and render the table again. But you will see that we only get\na table size of 5 rows:\n\n >>> sequenceTable.update()\n >>> print(sequenceTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
Zero itemnumber: 0
First itemnumber: 1
Second itemnumber: 2
Third itemnumber: 3
Fourth itemnumber: 4
\n\nAnd we set the sort order to ``reverse`` even if we use batching:\n\n >>> sequenceTable.sortOrder = u'reverse'\n >>> sequenceTable.update()\n >>> print(sequenceTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
My itemsNumber
Twentieth itemnumber: 20
Nineteenth itemnumber: 19
Eighteenth itemnumber: 18
Seventeenth itemnumber: 17
Sixteenth itemnumber: 16
\n\n\n=============\nTable Columns\n=============\n\nLet's show the different columns we offer by default. But first take a look at\nthe README.txt which explains the Table and Column concepts.\n\n\nSample data setup\n-----------------\n\nLet's create a sample container that we can use as our iterable context:\n\n >>> from zope.container import btree\n >>> class Container(btree.BTreeContainer):\n ... \"\"\"Sample container.\"\"\"\n >>> container = Container()\n >>> root['container'] = container\n\nand create a sample content object that we use as container item:\n\n >>> class Content(object):\n ... \"\"\"Sample content.\"\"\"\n ... def __init__(self, title, number, email):\n ... self.title = title\n ... self.number = number\n ... self.email = email\n\nNow setup some items:\n\n >>> container[u'zero'] = Content('Zero', 0, 'zero@example.com')\n >>> container[u'first'] = Content('First', 1, 'first@example.com')\n >>> container[u'second'] = Content('Second', 2, 'second@example.com')\n >>> container[u'third'] = Content('Third', 3, 'third@example.com')\n >>> container[u'fourth'] = Content('Fourth', 4, None)\n\nLet's also create a simple number sortable column:\n\n >>> from z3c.table import column\n >>> class NumberColumn(column.Column):\n ...\n ... header = u'Number'\n ... weight = 20\n ...\n ... def getSortKey(self, item):\n ... return item.number\n ...\n ... def renderCell(self, item):\n ... return 'number: %s' % item.number\n\n\nNameColumn\n----------\n\nLet's define a table using the NameColumn:\n\n >>> from z3c.table import table\n >>> class NameTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.NameColumn, u'name',\n ... weight=1),\n ... column.addColumn(self, NumberColumn, name=u'number',\n ... weight=2, header=u'Number')\n ... ]\n\nNow create, update and render our table and you can see that the NameColumn\nrenders the name of the item using the zope.traversing.api.getName() concept:\n\n >>> from zope.publisher.browser import TestRequest\n >>> request = TestRequest()\n >>> nameTable = NameTable(container, request)\n >>> nameTable.update()\n >>> print(nameTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NameNumber
firstnumber: 1
fourthnumber: 4
secondnumber: 2
thirdnumber: 3
zeronumber: 0
\n\n\nRadioColumn\n-----------\n\nLet's define a table using the RadioColumn:\n\n >>> class RadioTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.RadioColumn, u'radioColumn',\n ... weight=1),\n ... column.addColumn(self, NumberColumn, name=u'number',\n ... weight=2, header=u'Number')\n ... ]\n\nNow create, update and render our table:\n\n >>> request = TestRequest()\n >>> radioTable = RadioTable(container, request)\n >>> radioTable.update()\n >>> print(radioTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
XNumber
number: 1
number: 4
number: 2
number: 3
number: 0
\n\nAs you can see, we can force to render the radio input field as selected with a\ngiven request value:\n\n >>> radioRequest = TestRequest(form={'table-radioColumn-0-selectedItem': 'third'})\n >>> radioTable = RadioTable(container, radioRequest)\n >>> radioTable.update()\n >>> print(radioTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
XNumber
number: 1
number: 4
number: 2
number: 3
number: 0
\n\n\nCheckBoxColumn\n--------------\n\nLet's define a table using the RadioColumn:\n\n >>> class CheckBoxTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.CheckBoxColumn, u'checkBoxColumn',\n ... weight=1),\n ... column.addColumn(self, NumberColumn, name=u'number',\n ... weight=2, header=u'Number')\n ... ]\n\nNow create, update and render our table:\n\n\n >>> request = TestRequest()\n >>> checkBoxTable = CheckBoxTable(container, request)\n >>> checkBoxTable.update()\n >>> print(checkBoxTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
XNumber
number: 1
number: 4
number: 2
number: 3
number: 0
\n\nAnd again you can set force to render the checkbox input field as selected with\na given request value:\n\n >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':\n ... ['first', 'third']})\n >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)\n >>> checkBoxTable.update()\n >>> print(checkBoxTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
XNumber
number: 1
number: 4
number: 2
number: 3
number: 0
\n\nIf you select a row, you can also give them an additional CSS style. This could\nbe used in combination with alternating ``even`` and ``odd`` styles:\n\n >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':\n ... ['first', 'third']})\n >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)\n >>> checkBoxTable.cssClasses = {'tr': 'tr'}\n >>> checkBoxTable.cssClassSelected = u'selected'\n >>> checkBoxTable.cssClassEven = u'even'\n >>> checkBoxTable.cssClassOdd = u'odd'\n >>> checkBoxTable.update()\n >>> print(checkBoxTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
XNumber
number: 1
number: 4
number: 2
number: 3
number: 0
\n\nLet's test the ``cssClassSelected`` without any other css class:\n\n >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':\n ... ['first', 'third']})\n >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)\n >>> checkBoxTable.cssClassSelected = u'selected'\n >>> checkBoxTable.update()\n >>> print(checkBoxTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
XNumber
number: 1
number: 4
number: 2
number: 3
number: 0
\n\n\nCreatedColumn\n-------------\n\nLet's define a table using the CreatedColumn:\n\n >>> class CreatedColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.CreatedColumn, u'createdColumn',\n ... weight=1),\n ... ]\n\nNow create, update and render our table. Note, we use a Dublin Core stub\nadapter which only returns ``01/01/01 01:01`` as created date:\n\n >>> request = TestRequest()\n >>> createdColumnTable = CreatedColumnTable(container, request)\n >>> createdColumnTable.update()\n >>> print(createdColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Created
01/01/01 01:01
01/01/01 01:01
01/01/01 01:01
01/01/01 01:01
01/01/01 01:01
\n\n\nModifiedColumn\n--------------\n\nLet's define a table using the CreatedColumn:\n\n >>> class ModifiedColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.ModifiedColumn,\n ... u'modifiedColumn', weight=1),\n ... ]\n\nNow create, update and render our table. Note, we use a Dublin Core stub\nadapter which only returns ``02/02/02 02:02`` as modified date:\n\n >>> request = TestRequest()\n >>> modifiedColumnTable = ModifiedColumnTable(container, request)\n >>> modifiedColumnTable.update()\n >>> print(modifiedColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Modified
02/02/02 02:02
02/02/02 02:02
02/02/02 02:02
02/02/02 02:02
02/02/02 02:02
\n\n\nGetAttrColumn\n-------------\n\nThe ``GetAttrColumn`` column is a handy column that retrieves the value from\nthe item by attribute access.\nIt also provides a ``defaultValue`` in case an exception happens.\n\n >>> class GetTitleColumn(column.GetAttrColumn):\n ...\n ... attrName = 'title'\n ... defaultValue = u'missing'\n\n >>> class GetAttrColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, GetTitleColumn, u'title'),\n ... ]\n\nRender and update the table:\n\n >>> request = TestRequest()\n >>> getAttrColumnTable = GetAttrColumnTable(container, request)\n >>> getAttrColumnTable.update()\n >>> print(getAttrColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
First
Fourth
Second
Third
Zero
\n\nIf we use a non-existing Attribute, we do not raise an AttributeError, we will\nget the default value:\n\n >>> class UndefinedAttributeColumn(column.GetAttrColumn):\n ...\n ... attrName = 'undefined'\n ... defaultValue = u'missing'\n\n >>> class GetAttrColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, UndefinedAttributeColumn, u'missing'),\n ... ]\n\nRender and update the table:\n\n >>> request = TestRequest()\n >>> getAttrColumnTable = GetAttrColumnTable(container, request)\n >>> getAttrColumnTable.update()\n >>> print(getAttrColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
missing
missing
missing
missing
missing
\n\nA missing ``attrName`` in ``GetAttrColumn`` would also end in return the\n``defaultValue``:\n\n >>> class BadAttributeColumn(column.GetAttrColumn):\n ...\n ... defaultValue = u'missing'\n\n >>> firstItem = container[u'first']\n >>> simpleTable = table.Table(container, request)\n >>> badColumn = column.addColumn(simpleTable, BadAttributeColumn, u'bad')\n >>> badColumn.renderCell(firstItem)\n u'missing'\n\nIf we try to access a protected attribute the object raises an ``Unauthorized``.\nIn this case we also return the defaultValue. Let's setup an object which\nraises such an error if we access the title:\n\n >>> from zope.security.interfaces import Unauthorized\n >>> class ProtectedItem(object):\n ...\n ... @property\n ... def forbidden(self):\n ... raise Unauthorized('forbidden')\n\nSetup and test the item:\n\n >>> protectedItem = ProtectedItem()\n >>> protectedItem.forbidden\n Traceback (most recent call last):\n ...\n Unauthorized: forbidden\n\nNow define a column:\n\n >>> class ForbiddenAttributeColumn(column.GetAttrColumn):\n ...\n ... attrName = 'forbidden'\n ... defaultValue = u'missing'\n\nAnd test the attribute access:\n\n >>> simpleTable = table.Table(container, request)\n >>> badColumn = column.addColumn(simpleTable, ForbiddenAttributeColumn, u'x')\n >>> badColumn.renderCell(protectedItem)\n u'missing'\n\n\nGetItemColumn\n-------------\n\nThe ``GetItemColumn`` column is a handy column that retrieves the value from\nthe item by index or key access. That means the item can be a tuple, list, dict\nor anything that implements that.\nIt also provides a ``defaultValue`` in case an exception happens.\n\nDict-ish\n.........\n\n >>> sampleDictData = [\n ... dict(name='foo', value=1),\n ... dict(name='bar', value=7),\n ... dict(name='moo', value=42),]\n\n >>> class GetDictColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.GetItemColumn, u'name',\n ... header=u'Name',\n ... idx='name', defaultValue='missing'),\n ... column.addColumn(self, column.GetItemColumn, u'value',\n ... header=u'Value',\n ... idx='value', defaultValue='missing'),\n ... ]\n ... @property\n ... def values(self):\n ... return sampleDictData\n\nRender and update the table:\n\n >>> request = TestRequest()\n >>> getDictColumnTable = GetDictColumnTable(sampleDictData, request)\n >>> getDictColumnTable.update()\n >>> print(getDictColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NameValue
bar7
foo1
moo42
\n\nIf we use a non-existing index/key, we do not raise an exception, we will\nget the default value:\n\n >>> class GetDictColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.GetItemColumn, u'name',\n ... idx='not-existing', defaultValue='missing'),\n ... ]\n ... @property\n ... def values(self):\n ... return sampleDictData\n\nRender and update the table:\n\n >>> request = TestRequest()\n >>> getDictColumnTable = GetDictColumnTable(container, request)\n >>> getDictColumnTable.update()\n >>> print(getDictColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
missing
missing
missing
\n\nA missing ``idx`` in ``GetItemColumn`` would also end in return the\n``defaultValue``:\n\n >>> class BadIdxColumn(column.GetItemColumn):\n ...\n ... defaultValue = u'missing'\n\n >>> firstItem = sampleDictData[0]\n >>> simpleTable = table.Table(sampleDictData, request)\n >>> badColumn = column.addColumn(simpleTable, BadIdxColumn, u'bad')\n >>> badColumn.renderCell(firstItem)\n u'missing'\n\nTuple/List-ish\n...............\n\n >>> sampleTupleData = [\n ... (50, 'bar'),\n ... (42, 'cent'),\n ... (7, 'bild'),]\n\n >>> class GetTupleColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.GetItemColumn, u'name',\n ... header=u'Name',\n ... idx=1, defaultValue='missing'),\n ... column.addColumn(self, column.GetItemColumn, u'value',\n ... header=u'Value',\n ... idx=0, defaultValue='missing'),\n ... ]\n ... @property\n ... def values(self):\n ... return sampleTupleData\n\nRender and update the table:\n\n >>> request = TestRequest()\n >>> getTupleColumnTable = GetTupleColumnTable(sampleTupleData, request)\n >>> getTupleColumnTable.update()\n >>> print(getTupleColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NameValue
bar50
bild7
cent42
\n\nIf we use a non-existing index/key, we do not raise an exception, we will\nget the default value:\n\n >>> class GetTupleColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.GetItemColumn, u'name',\n ... idx=42, defaultValue='missing'),\n ... ]\n ... @property\n ... def values(self):\n ... return sampleTupleData\n\nRender and update the table:\n\n >>> request = TestRequest()\n >>> getTupleColumnTable = GetTupleColumnTable(container, request)\n >>> getTupleColumnTable.update()\n >>> print(getTupleColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
missing
missing
missing
\n\nA missing ``idx`` in ``GetItemColumn`` would also end in return the\n``defaultValue``:\n\n >>> class BadIdxColumn(column.GetItemColumn):\n ...\n ... defaultValue = u'missing'\n\n >>> firstItem = sampleTupleData[0]\n >>> simpleTable = table.Table(sampleTupleData, request)\n >>> badColumn = column.addColumn(simpleTable, BadIdxColumn, u'bad')\n >>> badColumn.renderCell(firstItem)\n u'missing'\n\n\nGetAttrFormatterColumn\n----------------------\n\nThe ``GetAttrFormatterColumn`` column is a get attr column which is able to\nformat the value. Let's use the Dublin Core adapter for our sample:\n\n >>> from zope.dublincore.interfaces import IZopeDublinCore\n >>> class GetCreatedColumn(column.GetAttrFormatterColumn):\n ...\n ... def getValue(self, item):\n ... dc = IZopeDublinCore(item, None)\n ... return dc.created\n\n >>> class GetAttrFormatterColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, GetCreatedColumn, u'created'),\n ... ]\n\nRender and update the table:\n\n >>> request = TestRequest()\n >>> getAttrFormatterColumnTable = GetAttrFormatterColumnTable(container,\n ... request)\n >>> getAttrFormatterColumnTable.update()\n >>> print(getAttrFormatterColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
2001 1 1 01:01:01
2001 1 1 01:01:01
2001 1 1 01:01:01
2001 1 1 01:01:01
2001 1 1 01:01:01
\n\n\nWe can also change the formatter settings in such a column:\n\n >>> class LongCreatedColumn(column.GetAttrFormatterColumn):\n ...\n ... formatterCategory = u'dateTime'\n ... formatterLength = u'long'\n ... formatterCalendar = u'gregorian'\n ...\n ... def getValue(self, item):\n ... dc = IZopeDublinCore(item, None)\n ... return dc.created\n\n >>> class LongFormatterColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, LongCreatedColumn, u'created'),\n ... ]\n\nRender and update the table:\n\n >>> request = TestRequest()\n >>> longFormatterColumnTable = LongFormatterColumnTable(container,\n ... request)\n >>> longFormatterColumnTable.update()\n >>> print(longFormatterColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
2001 1 1 01:01:01 +000
2001 1 1 01:01:01 +000
2001 1 1 01:01:01 +000
2001 1 1 01:01:01 +000
2001 1 1 01:01:01 +000
\n\n\nEMailColumn\n-----------\n\nThe ``EMailColumn`` column is ``GetAttrColumn`` which is used to\ndisplay a mailto link. By default in the link content the e-mail\naddress is displayed, too.\n\n\n >>> class EMailColumn(column.EMailColumn):\n ...\n ... attrName = 'email'\n ... defaultValue = u'missing'\n\n >>> class EMailColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, EMailColumn, u'email'),\n ... ]\n\nWhen a cell does not contain an e-mail address, the ``defaultValue``\nis rendered:\n\n >>> request = TestRequest()\n >>> eMailColumnTable = EMailColumnTable(container, request)\n >>> eMailColumnTable.update()\n >>> print(eMailColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
E-Mail
first@example.com
second@example.com
third@example.com
zero@example.com
missing
\n\nThe link content can be overwriten by setting the ``linkContent`` attribute:\n\n >>> class StaticEMailColumn(column.EMailColumn):\n ...\n ... attrName = 'email'\n ... defaultValue = u'missing'\n ... linkContent = 'Mail me'\n\n >>> class StaticEMailColumnTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, StaticEMailColumn, u'mail'),\n ... ]\n\nRender and update the table:\n\n >>> request = TestRequest()\n >>> staticEMailColumnTable = StaticEMailColumnTable(container, request)\n >>> staticEMailColumnTable.update()\n >>> print(staticEMailColumnTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
E-Mail
Mail me
Mail me
Mail me
Mail me
missing
\n\n\nLinkColumn\n----------\n\nLet's define a table using the LinkColumn. This column allows us to write\ncolumns which can point to a page with the item as context:\n\n >>> class MyLinkColumns(column.LinkColumn):\n ... linkName = 'myLink.html'\n ... linkTarget = '_blank'\n ... linkCSS = 'myClass'\n ... linkTitle = 'Click >'\n\n >>> class MyLinkTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, MyLinkColumns, u'link',\n ... weight=1),\n ... column.addColumn(self, NumberColumn, name=u'number',\n ... weight=2, header=u'Number')\n ... ]\n\nNow create, update and render our table:\n\n >>> from zope.publisher.browser import TestRequest\n >>> request = TestRequest()\n >>> myLinkTable = MyLinkTable(container, request)\n >>> myLinkTable.__parent__ = container\n >>> myLinkTable.__name__ = u'myLinkTable.html'\n >>> myLinkTable.update()\n >>> print(myLinkTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NameNumber
firstnumber: 1
fourthnumber: 4
secondnumber: 2
thirdnumber: 3
zeronumber: 0
\n\n\nContentsLinkColumn\n------------------\n\nThere are some predefined link columns available. This one will generate a\n``contents.html`` link for each item:\n\n >>> class ContentsLinkTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.ContentsLinkColumn, u'link',\n ... weight=1),\n ... column.addColumn(self, NumberColumn, name=u'number',\n ... weight=2, header=u'Number')\n ... ]\n\n >>> contentsLinkTable = ContentsLinkTable(container, request)\n >>> contentsLinkTable.__parent__ = container\n >>> contentsLinkTable.__name__ = u'contentsLinkTable.html'\n >>> contentsLinkTable.update()\n >>> print(contentsLinkTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NameNumber
firstnumber: 1
fourthnumber: 4
secondnumber: 2
thirdnumber: 3
zeronumber: 0
\n\n\nIndexLinkColumn\n---------------\n\nThis one will generate a ``index.html`` link for each item:\n\n >>> class IndexLinkTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.IndexLinkColumn, u'link',\n ... weight=1),\n ... column.addColumn(self, NumberColumn, name=u'number',\n ... weight=2, header=u'Number')\n ... ]\n\n >>> indexLinkTable = IndexLinkTable(container, request)\n >>> indexLinkTable.__parent__ = container\n >>> indexLinkTable.__name__ = u'indexLinkTable.html'\n >>> indexLinkTable.update()\n >>> print(indexLinkTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NameNumber
firstnumber: 1
fourthnumber: 4
secondnumber: 2
thirdnumber: 3
zeronumber: 0
\n\n\nEditLinkColumn\n--------------\n\nAnd this one will generate a ``edit.html`` link for each item:\n\n >>> class EditLinkTable(table.Table):\n ... cssClassSortedOn = None\n ...\n ... def setUpColumns(self):\n ... return [\n ... column.addColumn(self, column.EditLinkColumn, u'link',\n ... weight=1),\n ... column.addColumn(self, NumberColumn, name=u'number',\n ... weight=2, header=u'Number')\n ... ]\n\n >>> editLinkTable = EditLinkTable(container, request)\n >>> editLinkTable.__parent__ = container\n >>> editLinkTable.__name__ = u'editLinkTable.html'\n >>> editLinkTable.update()\n >>> print(editLinkTable.render())\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
NameNumber
firstnumber: 1
fourthnumber: 4
secondnumber: 2
thirdnumber: 3
zeronumber: 0
\n\n\nMiscellaneous\n-------------\n\nMake coverage report happy and test different things.\n\nTest if the getWeight method returns 0 (zero) on AttributeError:\n\n >>> from z3c.table.table import getWeight\n >>> getWeight(None)\n 0\n\nCreate a container:\n\n >>> from z3c.table.testing import Container\n >>> container = Container()\n\nTry to call a simple table and call renderBatch which should return an empty\nstring:\n\n >>> from z3c.table import table\n >>> from zope.publisher.browser import TestRequest\n >>> request = TestRequest()\n >>> simpleTable = table.Table(container, request)\n >>> simpleTable.renderBatch()\n u''\n\nTry to render an empty table adapting an empty mapping:\n\n >>> simpleTable = table.Table({}, request)\n >>> simpleTable.cssClassSortedOn = None\n >>> simpleTable.render()\n u''\n\nSince we register an adapter for IColumn on None (IOW on an empty mapping).\n\n >>> from zope.component import provideAdapter\n >>> from z3c.table import column\n >>> from z3c.table import interfaces\n >>> provideAdapter(column.NameColumn,\n ... (None, None, interfaces.ITable), provides=interfaces.IColumn,\n ... name='secondColumn')\n\nInitializing rows definitions for the empty table initializes the columns\nattribute list.\n\n >>> simpleTable.columns\n\n >>> simpleTable.initColumns()\n >>> simpleTable.columns\n []\n\nRendering the empty table now return the string:\n\n >>> print(simpleTable.render())\n \n \n \n \n \n \n \n \n
Name
\n\n\nLet's see if the addColumn raises a ValueError if there is no Column class:\n\n >>> column.addColumn(simpleTable, column.Column, u'dummy')\n \n\n >>> column.addColumn(simpleTable, None, u'dummy')\n Traceback (most recent call last):\n ...\n ValueError: class_ None must implement IColumn.\n\nTest if we can set additional kws in addColumn:\n\n >>> simpleColumn = column.addColumn(simpleTable, column.Column, u'dummy',\n ... foo='foo value', bar=u'something else', counter=99)\n >>> simpleColumn.foo\n 'foo value'\n\n >>> simpleColumn.bar\n u'something else'\n\n >>> simpleColumn.counter\n 99\n\nThe NoneCell class provides some methods which never get called. But these\nare defined in the interface. Let's test the default values\nand make coverage report happy.\n\nLet's get an container item first:\n\n >>> from z3c.table.testing import Content\n >>> firstItem = Content('First', 1)\n >>> noneCellColumn = column.addColumn(simpleTable, column.NoneCell, u'none')\n >>> noneCellColumn.renderCell(firstItem)\n u''\n\n >>> noneCellColumn.getColspan(firstItem)\n 0\n\n >>> noneCellColumn.renderHeadCell()\n u''\n\n >>> noneCellColumn.renderCell(firstItem)\n u''\n\nThe default ``Column`` implementation raises an NotImplementedError if we\ndo not override the renderCell method:\n\n >>> defaultColumn = column.addColumn(simpleTable, column.Column, u'default')\n >>> defaultColumn.renderCell(firstItem)\n Traceback (most recent call last):\n ...\n NotImplementedError: Subclass must implement renderCell\n\n\n=======\nCHANGES\n=======\n\n2.1.1 (2019-03-26)\n------------------\n\n- Fix: escape special HTML characters at ``Column.renderHeadCell``, \n ``NameColumn.getName``, ``CheckBoxColumn`` name and value,\n ``RadioColumn`` name and value, ``LinkColumn`` href and link content.\n\n\n2.1 (2019-01-27)\n----------------\n\n- Added support for Python 3.7 and PyPy3.\n\n- Dropped support for running the tests using `python setup.py test`.\n\n- Reformatted the code using black and flake8.\n\n\n2.0.1 (2017-04-19)\n------------------\n\n- Required future>=0.14.0 so `html` package is available in Python 2.7.\n\n\n2.0.0 (2017-04-17)\n------------------\n\n- Updated to support Python 2.7, 3.5, and 3.6 only.\n\n- Added html title attribute on LinkColumn\n\n\n2.0.0a1 (2013-02-26)\n--------------------\n\n- Added support for Python 3.3, dropped support for Python 2.5 and below.\n\n- Got rid of testing dependencies on z3.testing and zope.app.testing.\n\n\n1.0.0 (2012-08-09)\n------------------\n\n- Added sorting (``cssClassSortedOn`` and ``getCSSSortClass``) CSS options\n\n- Added cell highlight (``getCSSHighlightClass``) CSS option\n\n- Added ``GetItemColumn`` which gets the value by index/key access.\n\n0.9.1 (2011-08-03)\n------------------\n\n- Fixed SelectedItemColumn.update when just one item was selected\n\n\n0.9.0 (2010-08-09)\n------------------\n\n- Added ``EMailColumn`` which can be used to display mailto links.\n\n- Fixed the default BatchProvider not to lose table sorting query arguments\n from the generated links; now batching and sorting play with each other\n nicely.\n\n- Split single doctest file (README.txt) into different files\n\n\n0.8.1 (2010-07-31)\n------------------\n\n- Added translation for the link title in the column header of the\n sortable table.\n\n\n0.8.0 (2009-12-29)\n------------------\n\n- Added translation for ``LinkColumn.linkContent``.\n\n- Added ``I18nGetAttrColumn`` which translates its content.\n\n\n0.7.0 (2009-12-29)\n------------------\n\n- Allow to initialze the column definitions without requiring an\n entire table update.\n\n- Fixed tests, so they no longer use ``zope.app.container`` (which was\n even not declared as test dependency).\n\n- Head cell contents are now translated.\n\n0.6.1 (2009-02-22)\n------------------\n\n- Be smart to not ``IPhysicallyLocatable`` objects if we lookup the\n ``__name__`` value in columns.\n\n\n0.6.0 (2008-11-12)\n------------------\n\n- Bugfix: Allow to switch the sort order on the header link. This was\n blocked to descending after the first click\n\n- Bugfix: CheckBoxColumn, ensure that we allways use a list for compare\n selected items. It was possible that if only one item get selected\n we compared a string. If this string was a sub string of another existing\n item the other item get selected too.\n\n- Moved advanced batching implementation into z3c.batching\n\n- Implemented GetAttrFormatterColumn. This column can be used for simple\n value formatting columns.\n\n- Bad typo in columns.py: Renamed ``getLinkConent`` to ``getLinkContent``\n\n- Bug: Changed return string in getLinkCSS. It was using css=\"\" instead of\n class=\"\" for CSS classes. Thanks to Dan for reporting this bugs.\n\n- Implemented SelectedItemColumn\n\n- Fix CheckBoxColumn, use always the correct selectedItems. Use always real\n selectedItems form the table\n\n- Fix RadioColumn, use always the correct selectedItem from the selectedItems\n list. Use always the first selectedItems form the tables selectedItems\n\n\n0.5.0 (2008-04-13)\n------------------\n\n- Initial Release.\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/zopefoundation/z3c.table", "keywords": "zope3 z3c table content provider", "license": "ZPL 2.1", "maintainer": "", "maintainer_email": "", "name": "z3c.table", "package_url": "https://pypi.org/project/z3c.table/", "platform": "", "project_url": "https://pypi.org/project/z3c.table/", "project_urls": { "Homepage": "https://github.com/zopefoundation/z3c.table" }, "release_url": "https://pypi.org/project/z3c.table/2.1.1/", "requires_dist": [ "setuptools", "future (>=0.14.0)", "z3c.batching (>=1.1.0)", "zope.component", "zope.contentprovider", "zope.dublincore", "zope.i18nmessageid", "zope.i18n", "zope.interface", "zope.location", "zope.schema", "zope.security", "zope.traversing", "zope.container ; extra == 'test'", "zope.publisher ; extra == 'test'", "zope.site ; extra == 'test'", "zope.testing ; extra == 'test'", "zope.testrunner ; extra == 'test'" ], "requires_python": "", "summary": "Modular table rendering implementation for Zope3", "version": "2.1.1" }, "last_serial": 4987342, "releases": { "0.5.0": [ { "comment_text": "", "digests": { "md5": "33afc5e943e87fe6bd160dd8533f317d", "sha256": "cbda29818ce92a27185cec77b8519f4fd6920d57debd6815cc8111fbe10ce4a7" }, "downloads": -1, "filename": "z3c.table-0.5.0.zip", "has_sig": false, "md5_digest": "33afc5e943e87fe6bd160dd8533f317d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33733, "upload_time": "2008-04-13T00:16:38", "url": "https://files.pythonhosted.org/packages/d2/36/c323a6ba36e5cb7696070b6f3de2dca930fc7327d2c1f91479ee65dc20e7/z3c.table-0.5.0.zip" } ], "0.6.0": [ { "comment_text": "", "digests": { "md5": "bba1c37c32dfce87ce0a2fa2dce3e4d2", "sha256": "8e8776926f662e4a0c196645e738c2d7ca03fb25e34ea5318b42a86b65de9ab6" }, "downloads": -1, "filename": "z3c.table-0.6.0.tar.gz", "has_sig": false, "md5_digest": "bba1c37c32dfce87ce0a2fa2dce3e4d2", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50661, "upload_time": "2008-10-12T14:55:12", "url": "https://files.pythonhosted.org/packages/64/62/fcee6c75ae57b2ce3cf39d1b9994f863f115fb75b753620b1d32ee7e50d2/z3c.table-0.6.0.tar.gz" } ], "0.6.1": [ { "comment_text": "", "digests": { "md5": "a1409d6d6abcf9e3fd006b5f083a52b5", "sha256": "66804be5918daa81e113c0a602599a4624798746d49fdeef755fbf8444ed5fba" }, "downloads": -1, "filename": "z3c.table-0.6.1.zip", "has_sig": false, "md5_digest": "a1409d6d6abcf9e3fd006b5f083a52b5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 60902, "upload_time": "2009-02-22T04:47:18", "url": "https://files.pythonhosted.org/packages/b0/b5/802d9c97ac44d4593c1b969ee6e365853bb41508efe8fe48dcfb550a192e/z3c.table-0.6.1.zip" } ], "0.7.0": [ { "comment_text": "", "digests": { "md5": "3efa3e155ace6a68d6b7a8091e840712", "sha256": "a790f474d89f9779d0ff8094cb4f74c2675cf7890472726eab51e3078b47846a" }, "downloads": -1, "filename": "z3c.table-0.7.0.tar.gz", "has_sig": false, "md5_digest": "3efa3e155ace6a68d6b7a8091e840712", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51321, "upload_time": "2009-12-29T11:09:46", "url": "https://files.pythonhosted.org/packages/aa/ee/7f9649332f601355880691e01568a14ec95d347f25115316c372e629bd22/z3c.table-0.7.0.tar.gz" } ], "0.8.0": [ { "comment_text": "", "digests": { "md5": "d3baa0b2f938ef1c61e2d19d69f0463c", "sha256": "421506e1dc4356ba8e5ce5452bcf23c0f397a8bfaf01e9fc1002d899b019f9d5" }, "downloads": -1, "filename": "z3c.table-0.8.0.tar.gz", "has_sig": false, "md5_digest": "d3baa0b2f938ef1c61e2d19d69f0463c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51496, "upload_time": "2009-12-29T15:22:18", "url": "https://files.pythonhosted.org/packages/b6/f7/23e129b5434ae7076c16b3046c5ab8e3e932e73b68bca2868d99b0673d53/z3c.table-0.8.0.tar.gz" } ], "0.8.1": [ { "comment_text": "", "digests": { "md5": "f2e8e46bd365e07d95df450b5e818a99", "sha256": "3ac77d0ccf52876a951b59da29a27807af149dcb9cc0074ba439e194fa1a30bf" }, "downloads": -1, "filename": "z3c.table-0.8.1.tar.gz", "has_sig": false, "md5_digest": "f2e8e46bd365e07d95df450b5e818a99", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51672, "upload_time": "2010-07-31T14:33:33", "url": "https://files.pythonhosted.org/packages/32/cc/4a1ec25970828dff6f686fed93deea1e59ff9e1ae3ff8f64f0851e6949e5/z3c.table-0.8.1.tar.gz" } ], "0.9.0": [ { "comment_text": "", "digests": { "md5": "177636806b5f440feca6cf8e2f628db7", "sha256": "a9070e67c03cdb5bb8075c1ba947a9434c473ac358d4ca35613f5cbdeb6cfe73" }, "downloads": -1, "filename": "z3c.table-0.9.0.tar.gz", "has_sig": false, "md5_digest": "177636806b5f440feca6cf8e2f628db7", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 54859, "upload_time": "2010-08-09T18:37:12", "url": "https://files.pythonhosted.org/packages/04/2d/e986a6fdb92dd6c941a38e0e2e17da1c17b0df58866662d521d5430ff66e/z3c.table-0.9.0.tar.gz" } ], "0.9.1": [ { "comment_text": "", "digests": { "md5": "8544c00d110e73639ca46c929d8b4de0", "sha256": "6f58f93377656066f65f94c87f47dd953517d92d9485f630172c2164590aa10e" }, "downloads": -1, "filename": "z3c.table-0.9.1.tar.gz", "has_sig": false, "md5_digest": "8544c00d110e73639ca46c929d8b4de0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 55378, "upload_time": "2011-08-03T13:01:29", "url": "https://files.pythonhosted.org/packages/26/40/99012236b31c74edf76702309ad217a522fb95815bc583db86363dd94934/z3c.table-0.9.1.tar.gz" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "f3c16eb68181dd0ae67fa39fa89d6567", "sha256": "2775a8edc67f35ceb7036529068349c6e3c65cee6efb578b34d8505174348599" }, "downloads": -1, "filename": "z3c.table-1.0.0.zip", "has_sig": false, "md5_digest": "f3c16eb68181dd0ae67fa39fa89d6567", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 72969, "upload_time": "2012-08-09T11:42:23", "url": "https://files.pythonhosted.org/packages/f3/36/ecb1923437e58120c838889f94009d2a2036fe20c388d100e860e6fab90f/z3c.table-1.0.0.zip" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "1447c97536fc9737de1df2c821fb6662", "sha256": "a1b769acd0a78f340de0c5df5075d0923572837343a731f574b1c9b28d37c15e" }, "downloads": -1, "filename": "z3c.table-2.0.0.tar.gz", "has_sig": false, "md5_digest": "1447c97536fc9737de1df2c821fb6662", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 62073, "upload_time": "2017-04-17T18:37:11", "url": "https://files.pythonhosted.org/packages/86/81/8620288c4239049b7d09ccf14de663f84195d8992f72324feb995c00d39b/z3c.table-2.0.0.tar.gz" } ], "2.0.0a1": [ { "comment_text": "", "digests": { "md5": "06978a2abd7775997a6fbdc6dbfe0da3", "sha256": "94ba37343ea06b55bb7dbbfcdce0a51d65d88519c73241b6811703d4b8c261d0" }, "downloads": -1, "filename": "z3c.table-2.0.0a1.zip", "has_sig": false, "md5_digest": "06978a2abd7775997a6fbdc6dbfe0da3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 75898, "upload_time": "2013-02-26T08:41:17", "url": "https://files.pythonhosted.org/packages/ad/15/7f043a8dbe1283ba7f8de91be138bf2d564698192932056715bc629d6a30/z3c.table-2.0.0a1.zip" } ], "2.0.1": [ { "comment_text": "", "digests": { "md5": "e0447902ccf62dbc9ccf158ee0df5f36", "sha256": "5b1029805e694218bc6b852056d25e3938e86479fbe894a6305f6992042b0494" }, "downloads": -1, "filename": "z3c.table-2.0.1.tar.gz", "has_sig": false, "md5_digest": "e0447902ccf62dbc9ccf158ee0df5f36", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 62169, "upload_time": "2017-04-19T13:19:05", "url": "https://files.pythonhosted.org/packages/d6/b9/17831890e84a314ee1fc6444d5d5b44b5a55d7354c6f8017be16b910f304/z3c.table-2.0.1.tar.gz" } ], "2.1": [ { "comment_text": "", "digests": { "md5": "7db30524d7091c9c9a74085ebf1be72f", "sha256": "4bb4f975e37695adbbc2d7a9bf6e3cfdbe146258b37869d39528be5fc99bd0b1" }, "downloads": -1, "filename": "z3c.table-2.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "7db30524d7091c9c9a74085ebf1be72f", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 67016, "upload_time": "2019-01-27T16:17:24", "url": "https://files.pythonhosted.org/packages/89/d8/e26fb7945f5075fb05bb2bd2930cf7a2424e5fd74d005026e09169d32533/z3c.table-2.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "483fa90c92c8a8d3ba81de628632d70a", "sha256": "d615ba19f9f992692589f68f3c74c6edc7f01ca0ec538864e054898f5eee36fb" }, "downloads": -1, "filename": "z3c.table-2.1.tar.gz", "has_sig": false, "md5_digest": "483fa90c92c8a8d3ba81de628632d70a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 62891, "upload_time": "2019-01-27T16:17:39", "url": "https://files.pythonhosted.org/packages/2b/63/60b296403c46f0276d8f058cfbb4ec66c1cd5339961184167f28570c4b27/z3c.table-2.1.tar.gz" } ], "2.1.1": [ { "comment_text": "", "digests": { "md5": "31d260c44573119c004bd0431d1828b0", "sha256": "465ce3597b5620383c40667eb76c3ad18be17187a9405a2ca534de1b33b48b5a" }, "downloads": -1, "filename": "z3c.table-2.1.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "31d260c44573119c004bd0431d1828b0", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 53526, "upload_time": "2019-03-26T12:00:28", "url": "https://files.pythonhosted.org/packages/f6/e9/89a7bbce7edf9d54a307f39a26e9b6e815707df7be69bd54d6b53705de75/z3c.table-2.1.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "28729aabbb43facb5a0ab6bf2bc68abe", "sha256": "b15a1753f02e2c9ecea14e6b9838eb27fd4951dca2a12f0fca82946afd6379cd" }, "downloads": -1, "filename": "z3c.table-2.1.1.tar.gz", "has_sig": false, "md5_digest": "28729aabbb43facb5a0ab6bf2bc68abe", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 65052, "upload_time": "2019-03-26T12:00:30", "url": "https://files.pythonhosted.org/packages/40/93/323ae55e92abc8dbdd5e6407ed8d886495e687ad9f68c6d4fda203c5e461/z3c.table-2.1.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "31d260c44573119c004bd0431d1828b0", "sha256": "465ce3597b5620383c40667eb76c3ad18be17187a9405a2ca534de1b33b48b5a" }, "downloads": -1, "filename": "z3c.table-2.1.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "31d260c44573119c004bd0431d1828b0", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 53526, "upload_time": "2019-03-26T12:00:28", "url": "https://files.pythonhosted.org/packages/f6/e9/89a7bbce7edf9d54a307f39a26e9b6e815707df7be69bd54d6b53705de75/z3c.table-2.1.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "28729aabbb43facb5a0ab6bf2bc68abe", "sha256": "b15a1753f02e2c9ecea14e6b9838eb27fd4951dca2a12f0fca82946afd6379cd" }, "downloads": -1, "filename": "z3c.table-2.1.1.tar.gz", "has_sig": false, "md5_digest": "28729aabbb43facb5a0ab6bf2bc68abe", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 65052, "upload_time": "2019-03-26T12:00:30", "url": "https://files.pythonhosted.org/packages/40/93/323ae55e92abc8dbdd5e6407ed8d886495e687ad9f68c6d4fda203c5e461/z3c.table-2.1.1.tar.gz" } ] }