changeset 506:b7a9843c5730

macro DictColumns:Collects definition list key and values pairs. Each key becomes a column with its values.
author Reimar Bauer <rb.proj AT googlemail DOT com>
date Thu, 18 Mar 2010 01:01:02 +0100
parents 944bccb3f184
children d0a99da9ad77
files data/plugin/macro/DictColumns.py data/plugin/macro/_tests/test_DictColumns.py
diffstat 2 files changed, 586 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/plugin/macro/DictColumns.py	Thu Mar 18 01:01:02 2010 +0100
@@ -0,0 +1,253 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - macro to collect data from definition lists on pages
+    into a data browser widget table
+
+    <<DictColumns(search_term=regex:title:^Examplepage/)>
+
+    @copyright: 2006 by michael cohen <scudette@users.sourceforge.net> (PageDicts)
+    @copyright: 2008-2010 by MoinMoin:ReimarBauer (completly rewritten)
+    @license: GNU GPL, see COPYING for details.
+"""
+import re
+from MoinMoin import wikiutil, search
+from MoinMoin.Page import Page
+from MoinMoin.util.dataset import TupleDataset, Column
+from MoinMoin.widget.browser import DataBrowserWidget
+
+Dependencies = ["pages"]
+
+def _csv2list(csv):
+    """
+    converts a string of comma separated values into a list
+    @param csv: string of comma separated values
+    @returns: list
+    """
+    csv_list = csv.split(',')
+    return [variable.strip() for variable in csv_list if variable.strip()]
+
+def _name2index(all_names, selected_names):
+    """
+    converts names to the index
+    @param all_names: all available names
+    @param selected_names: names to lookup index position of all_names
+    @return: list of indices
+    """
+    if selected_names:
+        try:
+            index = [all_names.index(name) for name in selected_names]
+        except ValueError:
+            return []
+        return index
+    return []
+
+class DictColumns(object):
+    """
+    Collects definition list key and values pairs.
+    Each key becomes a column with its values.
+    """
+    def __init__(self, macro, pagename=u'', title=u'', names=u'',
+                 sort=u'', reverse=u'',
+                 hide=u'', filter_name=u'NeverExistingDefaultFilter',
+                 filter_value=u'', template_page=u'', alias_page=u'',
+                 parser=u'text_moin_wiki', search_term=None):
+
+        self.formatter = macro.formatter
+        self.request = macro.request
+        self.pagename = pagename
+        if not pagename:
+            self.pagename = macro.formatter.page.page_name
+        self.title = title
+        if not title:
+            self.title = self.pagename
+        self.names = names
+        self.sort = sort
+        self.reverse = reverse
+        self.hide = hide
+        self.filter_name = filter_name
+        self.filter_value = filter_value
+        self.filter_key, self.filter_word = (u"", u"")
+        regex = re.compile(ur'(?P<key>\w*):: (?P<value>.*)', re.UNICODE)
+        try:
+            self.filter_key, self.filter_word = regex.search(filter_value).groups()
+        except AttributeError:
+            # Don't filter if syntax was wrong
+            self.filter_value = u""
+        self.template_page = template_page
+        self.alias_page = alias_page
+        try:
+            self.wiki_parser = wikiutil.importPlugin(
+                                    self.request.cfg, "parser",
+                                    parser, function="Parser")
+        except wikiutil.PluginMissingError:
+            self.wiki_parser = None
+        self.search_term = search_term
+        if search_term is None:
+            self.search_term = u'regex:title:^%s/' % self.pagename
+
+    def get_page_list(self):
+        """
+        selects the pages dependent on a search term,
+        without listing of template, dictionary pages and
+        the pagename itselfs.
+        """
+        request = self.request
+        search_term = self.search_term
+        search_result = search.searchPages(request, search_term)
+        pages = [title.page_name for title in search_result.hits]
+        if not pages:
+            return None
+        # exclude some_pages
+        filterfn = request.cfg.cache.page_template_regexact.search
+        template_pages = request.rootpage.getPageList(filter=filterfn)
+        excluded_pages = template_pages + [self.alias_page, self.pagename]
+        selected_pages = [page for page in pages if page not in excluded_pages]
+        selected_pages.sort()
+        return selected_pages
+
+    def get_names(self, selected_pages):
+        """
+        selects which column names should be used
+        @param selected_pages: list of page names
+        @return: list of names
+        """
+        request = self.request
+        # use selection and order
+        if self.names:
+            names = self.names
+        # use keys from template page, no order
+        elif Page(request, self.template_page).exists():
+            page_dict = request.dicts[self.template_page]
+            names = page_dict.keys()
+        else:
+            # fallback use the keys used on selected pages
+            names = []
+            for page_name in selected_pages:
+                page_dict = request.dicts[page_name]
+                keys = page_dict.keys()
+                names = names + keys
+        return list(set(names))
+
+    def dataset(self, names, selected_pages):
+        """
+        Sets the data for the data browser widget
+        @param names: column names
+        @param selected_pages: pages to read key value pairs from
+        """
+        assert isinstance(selected_pages, list)
+        request = self.request
+        hide_columns = self.hide
+        # default alias
+        alias_dict = {}
+        for name in names:
+            alias_dict[name] = name
+        if Page(request, self.alias_page).exists():
+            alias = request.dicts[self.alias_page]
+            for name in names:
+                alias_dict[name] = alias.get(name, name)
+
+        col = Column(self.title, label=self.title)
+        if self.title in hide_columns:
+            col.hidden = True
+
+        data = TupleDataset()
+        data.columns = []
+        data.columns.extend([col])
+
+        for page_name in selected_pages:
+            page = Page(request, page_name)
+            page_dict = request.dicts[page_name]
+            if self.filter_value and page_dict.get(self.filter_key, '') != self.filter_word:
+                continue
+
+            row = []
+            for name in names:
+                if name in page_dict.keys():
+                    value = page_dict.get(name, '')
+                    value = value.replace('attachment:', 'attachment:%s/' % name)
+                    if self.wiki_parser:
+                        row.append((wikiutil.renderText(request, self.wiki_parser, value),
+                                    wikiutil.escape(value, 1)))
+                else:
+                    row.append('')
+            try:
+                parent, child = page_name.split('/', 1)
+            except ValueError:
+                child = page_name
+            link = page.link_to(request, text="%s" % child)
+            data.addRow([link] + row)
+
+        if self.filter_name:
+            filtercols = self.filter_name
+            for name in names:
+                if self.filter_name != u'NeverExistingDefaultFilter' and name in filtercols:
+                    col = Column(alias_dict[name], autofilter=(name in filtercols))
+                    if name in hide_columns:
+                        col.hidden = True
+                    data.columns.append(col)
+                else:
+                    col = Column(alias_dict[name], label=alias_dict[name])
+                    if name in hide_columns:
+                        col.hidden = True
+                    data.columns.extend([col])
+
+        return data
+
+    def render(self):
+        """
+        renders output as widget data browser table
+        """
+        request = self.request
+        _ = request.getText
+
+        selected_pages = self.get_page_list()
+        if not selected_pages:
+            return _("""\
+Please use a more selective search term instead of search_term="%s"\
+""") % self.search_term
+
+        names = self.get_names(selected_pages)
+
+        data = self.dataset(names, selected_pages)
+        table = DataBrowserWidget(request)
+
+        names.insert(0, "__name__")
+        sort_columns = _name2index(names, self.sort)
+        sort_reverse_columns = _name2index(names, self.reverse) or False
+
+        table.setData(data, sort_columns, reverse=sort_reverse_columns)
+        html = ''.join(table.format(method='GET'))
+        return html
+
+def macro_DictColumns(macro, pagename=unicode, title=u'', names=u'', sort=u'', reverse=u'',
+                      hide=u'', filter_name=u'NeverExistingDefaultFilter',
+                      filter_value=u'', template_page=u'', alias_page=u'',
+                      parser=u'text_moin_wiki', search_term=None):
+    """
+    Creates a table by data browser widget from definition lists key value pairs.
+    @param pagename: name of the page
+    @param title: entry in upper left corner of the table
+    @param name: names of columns, key name of definition list (comma separated)
+    @param sort: name of columns to sort by
+    @param reverse: name of columns to reverse sort by
+    @param hide: name of columns to hide
+    @param filter_name: name of columns to filter by autofilter
+    @param filter_value: dict definition for value of column to filter by
+    @param template_page: pagename of the template for setting column names
+    @param alias_page: pagename of the page for setting aliases for column names
+    @param parser: name of the parser used to render markup
+    @param search_term: regex used to search for selecting pages
+    """
+    kw = locals()
+    #  wiki input can be a string with comma separated values.
+    kw["names"] = _csv2list(kw["names"])
+    kw["sort"] = _csv2list(kw["sort"])
+    kw["reverse"] = _csv2list(kw["reverse"])
+    kw["hide"] = _csv2list(kw["hide"])
+    kw["filter_name"] = _csv2list(kw["filter_name"])
+    html = DictColumns(**kw).render()
+    # works together with
+    # http://moinmo.in/FeatureRequests/SortableTables?action=AttachFile&do=view&target=common.js.patch
+    # html = html.replace('id="dbw.table', 'class="sortable" id="dbw.table')
+    return html
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/plugin/macro/_tests/test_DictColumns.py	Thu Mar 18 01:01:02 2010 +0100
@@ -0,0 +1,333 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - MoinMoin.macro DictColumns tests
+
+    @copyright: 2010 MoinMoin:ReimarBauer
+    @license: GNU GPL, see COPYING for details.
+"""
+import py
+
+from MoinMoin._tests import become_trusted, create_page, make_macro, nuke_page
+from MoinMoin.macro.DictColumns import _name2index, _csv2list, DictColumns
+
+class TestDictColumns(object):
+    """ setup of common vars for testing of DictColumns """
+    pagename = "DictColumns"
+    template_page = "BooksTemplate"
+    alias_page = "BookAlias"
+
+    page_dict = [{"book": "Gray Hat Python: Python Programming for Hackers and Reverse Engineers",
+                  "author": "Justin Seitz",
+                  "language": "english",
+                  "prize": "17.00",
+                  "in stock": "31",
+                 },
+                 {"book": "Python Cookbook",
+                  "author": "Alex Martelli",
+                  "language": "english",
+                  "prize": "21.00",
+                  "in stock": "37",
+                 },
+                 {"book": "High Performance Python",
+                  "author": "Anthony Lewis, Andrew Lewis, and Lewis Andrew",
+                  "language": "english",
+                  "prize": "24.90",
+                  "in stock": "70",
+                 },
+                 {"book": "Einstieg in Python 3",
+                  "author": "Thomas Theis",
+                  "language": "german",
+                  "prize": "24.90",
+                  "in stock": "20",
+                 },
+                 {"book": "Hacking mit Python: Fehlersuche, Programmanalyse, Reverse Engineering",
+                  "author": "Justin Seitz",
+                  "language": "german",
+                  "prize": "33.00",
+                  "in stock": "70",
+                 }]
+
+    text = """\
+ book:: %(book)s
+ author:: %(author)s
+ language:: %(language)s
+ prize:: %(prize)s
+ in stock:: %(in stock)s
+
+----
+CategoryTest%(language)s
+"""
+
+    template_dict = {"book": "", "author": "",
+                     "prize": "", "in stock": "", "publisher": ""}
+
+    template_text = """\
+ book:: %(book)s
+ author:: %(author)s
+ prize:: %(prize)s
+ in stock:: %(in stock)s
+ publisher:: %(publisher)s
+
+----
+CategoryTest
+"""
+    alias_text = " prize:: %(prize)s"
+    alias_dict = {"prize": "prize (euro)"}
+
+    def setup_class(self):
+        """ initializes content """
+        become_trusted(self.request)
+        template_text = self.template_text % self.template_dict
+        template_page = create_page(self.request, self.template_page, template_text)
+        alias_text = self.alias_text % self.alias_dict
+        alias_page = create_page(self.request, self.alias_page, alias_text)
+        for content in self.page_dict:
+            pagename = self.pagename + '/' + content["book"]
+            examples = create_page(self.request, pagename, self.text % content)
+        self.page = create_page(self.request, self.pagename, u'<<DictColumns>>')
+        self.macro = make_macro(self.request, self.page)
+
+    def teardown_class(self):
+        """ cleans everything """
+        for content in self.page_dict:
+            pagename = self.pagename + '/' + content["book"]
+            nuke_page(self.request, pagename)
+        nuke_page(self.request, self.pagename)
+        nuke_page(self.request, self.template_page)
+        nuke_page(self.request, self.alias_page)
+
+    def test_get_page_list(self):
+        """ tests selection of pages by the default regex search_term """
+        result = DictColumns(self.macro, pagename=self.pagename).get_page_list()
+        expected_result = [self.pagename + '/' + content["book"] for content in self.page_dict]
+        # needs to have same length
+        assert len(result) == len(expected_result)
+        # needs to have same elements (order is independent)
+        assert set(result) == set(expected_result)
+
+    def test_get_page_list_default_pagename(self):
+        """ tests selection of pages by the default regex search_term and default pagename """
+        result = DictColumns(self.macro).get_page_list()
+        expected_result = [self.pagename + '/' + content["book"] for content in self.page_dict]
+        # needs to have same length
+        assert len(result) == len(expected_result)
+        # needs to have same elements (order is independent)
+        assert set(result) == set(expected_result)
+
+    def test_get_page_list_search_term(self):
+        """ tests selection of pages by a search term of a category """
+        # we search for search_term="category::CategoryTestgerman"
+        result = DictColumns(self.macro, search_term="category:CategoryTestgerman").get_page_list()
+        assert result is not None
+        expected_result = [self.pagename + '/' + content["book"] for content in self.page_dict
+                           if content["language"] == "german"]
+        # needs to have same length
+        assert len(result) == len(expected_result)
+        # needs to have same elements (order is independent)
+        assert set(result) == set(expected_result)
+        # we search for search_term="category:CategoryTestenglish"
+        result = DictColumns(self.macro, search_term="category:CategoryTestenglish").get_page_list()
+        assert result is not None
+        expected_result = [self.pagename + '/' + content["book"] for content in self.page_dict
+                           if content["language"] == "english"]
+        # needs to have same length
+        assert len(result) == len(expected_result)
+        # needs to have same elements (order is independent)
+        assert set(result) == set(expected_result)
+
+    def test_get_column_names_by_name(self):
+        """ tests used column names based on given name list """
+        selected_pages = DictColumns(self.macro, pagename=self.pagename).get_page_list()
+        names = ['book', 'prize']
+        result = DictColumns(self.macro, pagename=self.pagename, names=names).get_names(selected_pages)
+        # needs to have same length
+        assert len(result) == len(names)
+        # needs to have same elements (order is independent)
+        assert set(result) == set(names)
+
+    def test_get_column_names_from_all_pages(self):
+        """ tests used column names based on all pages (page_dict) """
+        selected_pages = DictColumns(self.macro, pagename=self.pagename).get_page_list()
+        # template_page has an additional key and one other is missing
+        selected_pages.append(self.template_page)
+        result = DictColumns(self.macro, pagename=self.pagename).get_names(selected_pages)
+        # we have different and duplicated names on both list, we need unique names for the test
+        expected_result = set(list(self.page_dict[0]) + list(self.template_dict))
+        # needs to have same length
+        assert len(result) == len(expected_result)
+        # needs to have same elements (order is independent)
+        assert set(result) == set(expected_result)
+
+    def test_get_column_names_from_template_page(self):
+        """ tests used column names based on a template page (template_dict)"""
+        # you can use very different template pages for presenting the result
+        # also for creating the dict pages they are usefull too
+        selected_pages = DictColumns(self.macro, pagename=self.pagename).get_page_list()
+        result = DictColumns(self.macro, pagename=self.pagename,
+                             template_page=self.template_page).get_names(selected_pages)
+        expected_result = list(self.template_dict)
+        # needs to have same length
+        assert len(result) == len(expected_result)
+        # needs to have same elements (order is independent)
+        assert set(result) == set(expected_result)
+
+    def test_dataset_all_names(self):
+        """ tests dataset for all keys (names) given from pages """
+        selected_pages = DictColumns(self.macro, pagename=self.pagename).get_page_list()
+        names = DictColumns(self.macro, pagename=self.pagename).get_names(selected_pages)
+        result = DictColumns(self.macro, pagename=self.pagename).dataset(names, selected_pages)
+        # if something is broken we have no data but we can have result.columns
+        data = result.data
+        assert data != []
+        # we need as much lines in result as we have in self.page_dict
+        assert len(result) == len(self.page_dict)
+        # columns are independent from table data.
+        assert result.columns[0].name == 'DictColumns'
+        assert result.columns[0].label == 'DictColumns'
+        # on default nothing is hidden
+        for idx in range(len(result.columns)):
+            assert result.columns[idx].hidden == 0
+        # check for author entry
+        idx_author = names.index('author') + 1  # offset because of pagename in first column
+        # check that this is the right entry in result.columns too
+        assert result.columns[idx_author].name == 'author'
+        # all authors from the page dict as reference
+        author_list = [self.page_dict[ix]['author'] for ix in range(len(self.page_dict))]
+        # we check every row for some entries
+        for row in data:
+            #  line of data should have the length of names + 1 (for 'DictColumns' name)
+            assert len(row) == len(names) + 1
+            # check if the author is in the list
+            assert row[idx_author][1] in author_list
+            # check for pagename in alias of href
+            test_string = selected_pages[data.index(row)].split('/', 1)[1]
+            assert test_string in row[0]
+
+    def test_dataset_some_names(self):
+        """ tests setting of some names """
+        selected_pages = DictColumns(self.macro, pagename=self.pagename).get_page_list()
+        names = ['author', 'language', 'prize']
+        result = DictColumns(self.macro, pagename=self.pagename).dataset(names, selected_pages)
+        data = result.data
+        # if something is broken we have no data but we can have result.columns
+        assert data != []
+        assert len(result) == len(self.page_dict)
+        # needs to have same length
+        assert len(result.columns) == len(names) + 1
+        for name in names:
+            idx = names.index(name) + 1
+            assert result.columns[idx].name == name
+
+    def test_dataset_filter_name(self):
+        """ tests filtering of output by a given key value pair """
+        selected_pages = DictColumns(self.macro, pagename=self.pagename).get_page_list()
+        names = ["book", "author", "language", "prize"]
+        # filters for this condition
+        filter_name = ["prize", "language"]
+        hidden_columns = ["book", "language"]
+        result = DictColumns(self.macro, pagename=self.pagename, hide=hidden_columns, filter_name=filter_name).dataset(names, selected_pages)
+        # if something is broken we have no data but we can have result.columns
+        data = result.data
+        assert data != []
+        # we insert the self.pagename at position 0 because this makes comparison easier
+        names.insert(0, self.pagename)
+        for idx in range(len(result.columns)):
+            if names[idx] in filter_name:
+                assert result.columns[idx].name in filter_name
+                assert result.columns[idx].autofilter == 1
+            else:
+                assert result.columns[idx].name in set(names) - set(filter_name)
+                assert result.columns[idx].autofilter == 0
+            if names[idx] in hidden_columns:
+                assert result.columns[idx].name in hidden_columns
+                assert result.columns[idx].hidden == 1
+            else:
+                assert result.columns[idx].name in set(names) - set(hidden_columns)
+                assert result.columns[idx].hidden == 0
+
+    def test_dataset_filter_value(self):
+        """ tests filtering of output by a given key value pair """
+        selected_pages = DictColumns(self.macro, pagename=self.pagename).get_page_list()
+        names = ['author', 'language', 'prize']
+        # filters for this condition
+        filter_value = "prize:: 24.90"
+        result = DictColumns(self.macro, pagename=self.pagename, filter_value=filter_value).dataset(names, selected_pages)
+        assert len(result.columns) == len(names) + 1
+        idx = names.index("prize") + 1
+        # if something is broken we have no data but we can have result.columns
+        data = result.data
+        assert data != []
+        for row in data:
+            # there should be no other result
+            assert row[idx][1] == u"24.90"
+
+    def test_dataset_hide_names(self):
+        """ tests hiding columns from output """
+        selected_pages = DictColumns(self.macro, pagename=self.pagename).get_page_list()
+        names = list(self.page_dict[0])
+        hidden_columns = ["author", "language", "example"]
+        title = "example"
+        result = DictColumns(self.macro, pagename=self.pagename,
+                             hide=hidden_columns, title=title).dataset(names, selected_pages)
+        # if something is broken we have no data but we can have result.columns
+        data = result.data
+        assert data != []
+        # we insert the title at position 0 because this makes comparison easier
+        names.insert(0, title)
+        for idx in range(len(result.columns)):
+            if names[idx] in hidden_columns:
+                assert result.columns[idx].name in hidden_columns
+                assert result.columns[idx].hidden == 1
+            else:
+                assert result.columns[idx].name in set(names) - set(hidden_columns)
+                assert result.columns[idx].hidden == 0
+
+    def test_dataset_alias_page(self):
+        """ tests column header settings from an alias page """
+        selected_pages = DictColumns(self.macro, pagename=self.pagename).get_page_list()
+        names = list(self.page_dict[0])
+        result = DictColumns(self.macro, pagename=self.pagename,
+                             alias_page=self.alias_page).dataset(names, selected_pages)
+        data = result.data
+        # if something is broken we have no data but we can have result.columns
+        assert data != []
+        # get names from all columns
+        names = [result.columns[idx].name for idx in range(len(result.columns))]
+        # there should be nothing left over
+        assert set(self.alias_dict.values()) - set(names) == set([])
+
+    def test_render(self):
+        """ tests if the macro renders by default settings """
+        result = DictColumns(self.macro, pagename=self.pagename).render()
+        # if there is a table generated we have much output
+        assert len(result) > 2000
+
+    def test_render_no_result(self):
+        """ tests for no result """
+        result = DictColumns(self.macro,
+                             pagename="I'am pretty sure this does never exist").render()
+        # if there is not a table generated we get a hint what is wrong
+        assert len(result) < 200
+
+class TestDictColumns_name2index(object):
+    def test_name2index(self):
+        """ tests index of selected names in names """
+        names = ['A', 'B', 'C', 'D', 'E', ]
+        selected_names = ['E', 'A', 'B']
+        result = _name2index(names, selected_names)
+        assert result == [4, 0, 1]
+        # tests wrong selection (name not in names)
+        selected_names = ['E', 'A', 'B', 'G']
+        result = _name2index(names, selected_names)
+        assert result == []
+
+class TestDictColumns_csv2list(object):
+    def test_csv2list(self):
+        """ tests conversion from csv input to list value """
+        text = "A, B,C ,D,  E,"
+        result = _csv2list(text)
+        expected_result = ['A', 'B', 'C', 'D', 'E']
+        assert result == expected_result
+
+coverage_modules = ['MoinMoin.macro.DictColumns']
+