diff --git a/browser/loops/loops_layout.css b/browser/loops/loops_layout.css index f18c4bc..4cc4ab1 100644 --- a/browser/loops/loops_layout.css +++ b/browser/loops/loops_layout.css @@ -489,15 +489,26 @@ div.row div.label { clear: both; } -div.row span.error { - background: red; +div.error { + background: #ff80a0; color: white; + font-weight: bold; padding: 0.1em 0.5em 0.1em 0.5em; /* Same as .itemViews */ border: 1px solid red; /* Same as .itemViews */ margin: 0; float: left; clear: both; } + +div.summary { + background: #c8c4ff; + font-weight: bold; + padding: 0.1em 0.5em 0.1em 0.5em; /* Same as .itemViews */ + border: 1px solid #6478b0; /* Same as .itemViews */ + margin: 0; + margin-top: 1em; +} + /* div.row div.error:before { content: "\2190 "; /* Left pointing arrow */ diff --git a/reporter/README.txt b/reporter/README.txt index 4ded08c..b07ec57 100644 --- a/reporter/README.txt +++ b/reporter/README.txt @@ -12,15 +12,47 @@ TO DO... A Basic API for Reports and Listings ==================================== +Batching +-------- + +We'll use a fairly simple Iterable: + + >>> it = xrange(14) + + >>> from cybertools.reporter.batch import Batch + >>> b = Batch(it, size=5, overlap=1, orphan=2) + >>> b.items + [0, 1, 2, 3, 4] + >>> b.getIndexRelative(1) + 1 + >>> b.getIndexAbsolute(-1) + 2 + + >>> b = Batch(it, 2, size=5, overlap=1, orphan=2) + >>> b.items + [8, 9, 10, 11, 12, 13] + +We are now ready to use the corresponding browser view: + + >>> from zope.publisher.browser import TestRequest + >>> form = dict(b_page=1, b_size=4) + >>> request = TestRequest(form=form) + >>> from cybertools.reporter.browser.batch import BatchView + >>> bview = BatchView(it, request) + +The real reporting stuff +------------------------ + >>> from cybertools.reporter.data import DataSource >>> from cybertools.reporter.interfaces import IResultSet + >>> from cybertools.reporter.example.interfaces import IContactsDataSource + >>> from cybertools.reporter.example.contact import Contacts + Let's start with the Person class from the example package - we will then provide a listing of persons... >>> from cybertools.organize.party import Person - >>> from cybertools.reporter.example.interfaces import IContactsDataSource - >>> from cybertools.reporter.example.contact import Contacts >>> from datetime import date >>> pdata = ((u'John', u'Smith', '1956-08-01'), @@ -47,6 +79,4 @@ result set and splitting a result into batches. Sorting ------- -Batching --------- diff --git a/reporter/batch.py b/reporter/batch.py new file mode 100644 index 0000000..11d0615 --- /dev/null +++ b/reporter/batch.py @@ -0,0 +1,78 @@ +# +# Copyright (c) 2006 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +Batching implementation. + +$Id$ +""" + +import itertools +from zope.interface import implements +from interfaces import IBatch + + +class Batch(object): + + lastPage = False + + def __init__(self, iterable, pageIndex=0, size=20, overlap=0, orphan=0): + self.iterable = list(iterable) + self.pageIndex = pageIndex + self.start = pageIndex * (size - overlap) + self.size = self.actualSize = size + self.overlap = overlap + self.orphan = orphan + length = len(self.iterable) + self.pages = range(0, length, size-overlap) + lastPage = self.pages[-1] + lastLen = length - lastPage + if lastLen <= orphan + overlap: + del self.pages[-1] + if pageIndex == len(self.pages) - 1: #we're on the last page + self.actualSize = size + lastLen #take over the orphans + if pageIndex >= len(self.pages) or pageIndex < 0: + self.items = [] + else: + self.items = self.iterable[self.start:self.start+self.actualSize] + + def __getitem__(self, idx): + return self.items[idx] + + def next(self): + for item in self.items: + yield item + + def __len__(self): + return len(self.items) + + def getIndexRelative(self, ridx=1): + idx = self.pageIndex + ridx + if idx < 0: + return 0 + if idx >= len(self.pages): + return len(self.pages) - 1 + return idx + + def getIndexAbsolute(self, idx=0): + if idx < 0: + idx = len(self.pages) + idx + if idx < 0 or idx >= len(self.pages): + return None + return idx + diff --git a/reporter/browser/batch.py b/reporter/browser/batch.py new file mode 100644 index 0000000..3998c2a --- /dev/null +++ b/reporter/browser/batch.py @@ -0,0 +1,60 @@ +# +# Copyright (c) 2006 Helmut Merz helmutm@cy55.de +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +""" +A browser view class for batching to be used by a macro or some other +HTML providing template. + +$Id$ +""" + +from cybertools.reporter.batch import Batch + + +class BatchView(object): + + def __init__(self, context, request): + self.context = context + self.request = request + form = request.form + page = int(form.get('b_page', 1)) + size = int(form.get('b_size', 20)) + overlap = int(form.get('b_overlap', 0)) + orphan = int(form.get('b_orphan', 0)) + self.batch = Batch(context, page-1, size, overlap, orphan) + + def items(self): + return setupUrlParams(self.batch.items) + + def first(self): + return setupUrlParams(self.batch.getIndexAbsolute(0)) + + def last(self): + return setupUrlParams(self.batch.getIndexAbsolute(-1)) + + def previous(self): + return setupUrlParams(self.batch.getIndexRelative(1)) + + def next(self): + return setupUrlParams(self.batch.getIndexRelative(-1)) + + def setupUrlParams(self, page): + return ('?b_page=%i&b_size=%i&b_overlap=%i&b_orphan=%i' + % (page, self.size, self.overlap, self.orphan) ) + + diff --git a/reporter/interfaces.py b/reporter/interfaces.py index a16bd20..8ff7523 100644 --- a/reporter/interfaces.py +++ b/reporter/interfaces.py @@ -26,7 +26,47 @@ import zope from zope.interface import Interface, Attribute -# result set +# iterable stuff for batching, sorting, filtering of results + + +class IBatch(Interface): + """ Represents a part (sort of a slice) of an iterable. + """ + + iterable = Attribute(u'The iterable this batch belongs to') + + start = Attribute(u'The current start index of the batch in the parent iterable') + + def __getitem__(idx): + """ Return the item at index idx. + """ + + def next(): + """ Return the next item in the batch. Raise StopIteration if + the end of the batch is reached. + """ + + def __len__(): + """ Return the number of items in the batch. + """ + + def getIndexRelative(relativePageNumber): + """ Return the absolute page index based on the current page of + the batch object. + Using +1 or -1 retrieves the next or previous batch. + If a page before the first one is addressed return 0, + if a page after the last one is addressed return the index + of the last page. + """ + + def getIndexAbsolute(pageNumber): + """ Return the absolute page index. + 0 addresses the first batch page, -1 the last. + Return None if the corresponding page does not exist. + """ + + +# result sets, rows, cells... class IResultSet(Interface): """A sequence of rows provided by a data source, report or diff --git a/reporter/tests.py b/reporter/tests.py index 77abd1f..72aa389 100755 --- a/reporter/tests.py +++ b/reporter/tests.py @@ -1,6 +1,6 @@ # $Id$ -import unittest +import unittest, doctest from zope.testing.doctestunit import DocFileSuite from zope.app.testing import ztapi from zope.interface.verify import verifyClass @@ -18,9 +18,10 @@ class TestReporter(unittest.TestCase): def test_suite(): + flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS return unittest.TestSuite(( unittest.makeSuite(TestReporter), - DocFileSuite('README.txt'), + DocFileSuite('README.txt', optionflags=flags), )) if __name__ == '__main__':