data/plugin/macro/DictColumns.py
author Reimar Bauer <rb.proj AT googlemail DOT com>
Mon, 16 Apr 2012 16:56:57 +0200
changeset 587 1b06afc3a430
parent 560 f502d6e75c07
permissions -rw-r--r--
merged main
     1 # -*- coding: iso-8859-1 -*-
     2 """
     3     MoinMoin - macro to collect data from definition lists on pages
     4     into a data browser widget table
     5 
     6     <<DictColumns(search_term=regex:title:^Examplepage/)>
     7 
     8     @copyright: 2006 by michael cohen <scudette@users.sourceforge.net> (PageDicts)
     9     @copyright: 2008-2011 by MoinMoin:ReimarBauer (completly rewritten)
    10     @license: GNU GPL, see COPYING for details.
    11 """
    12 import re
    13 
    14 from collections import defaultdict
    15 from MoinMoin import wikiutil, search
    16 from MoinMoin.Page import Page
    17 from MoinMoin.action.SlideShow import SlidePage
    18 from MoinMoin.util.dataset import TupleDataset, Column
    19 from MoinMoin.widget.browser import DataBrowserWidget
    20 from MoinMoin.datastruct.backends.wiki_dicts import WikiDicts
    21 
    22 Dependencies = ["pages"]
    23 
    24 
    25 def _csv2list(csv):
    26     """
    27     converts a string of comma separated values into a list
    28     @param csv: string of comma separated values
    29     @returns: list
    30     """
    31     csv_list = csv.split(',')
    32     return [variable.strip() for variable in csv_list if variable.strip()]
    33 
    34 def _name2index(all_names, selected_names):
    35     """
    36     converts names to the index
    37     @param all_names: all available names
    38     @param selected_names: names to lookup index position of all_names
    39     @return: list of indices
    40     """
    41     if selected_names:
    42         try:
    43             index = [all_names.index(name) for name in selected_names]
    44         except ValueError:
    45             return []
    46         return index
    47     return []
    48 
    49 class DictColumns(object):
    50     """
    51     Collects definition list key and values pairs.
    52     Each key becomes a column with its values.
    53     """
    54     def __init__(self, macro, pagename=u'', title=u'', names=u'',
    55                  sort=u'', reverse=u'',
    56                  hide=u'', filter_name=u'NeverExistingDefaultFilter',
    57                  filter_value=u'', template_page=u'', alias_page=u'',
    58                  parser=u'text_moin_wiki', markup="definition list",
    59                  search_term=None, comments=False, enumeration=False):
    60 
    61         self.formatter = macro.formatter
    62         self.request = macro.request
    63         self.pagename = pagename
    64         if not pagename:
    65             self.pagename = macro.formatter.page.page_name
    66         self.request.page = Page(self.request, self.pagename)
    67         self.title = title
    68         if not title:
    69             self.title = self.pagename
    70         self.names = names
    71         self.sort = sort
    72         self.reverse = reverse
    73         self.hide = hide
    74         self.filter_name = filter_name
    75         self.filter_value = filter_value
    76         self.filter_key, self.filter_word = (u"", u"")
    77         self.comments = comments
    78         self.enumeration = enumeration
    79         regex = re.compile(ur'(?P<key>\w*)=(?P<value>.*)', re.UNICODE)
    80         try:
    81             self.filter_key, self.filter_word = regex.search(filter_value).groups()
    82         except AttributeError:
    83             # Don't filter if syntax was wrong
    84             self.filter_value = u""
    85         self.template_page = template_page
    86         self.alias_page = alias_page
    87         try:
    88             self.wiki_parser = wikiutil.importPlugin(
    89                                     self.request.cfg, "parser",
    90                                     parser, function="Parser")
    91         except wikiutil.PluginMissingError:
    92             self.wiki_parser = None
    93         self.search_term = search_term
    94         self.markup = markup
    95         if search_term is None:
    96             self.search_term = u'regex:title:^%s/' % self.pagename
    97 
    98     def get_dict(self, dict_source):
    99         """
   100         gets the dictionary dependent of the markup
   101         @param dict_source: pagename to read dict data from
   102         """
   103         if self.markup in ("definition list", "dl"):
   104             return self.request.dicts[dict_source]
   105         elif self.markup in ("multiline definition list", "mdl"):
   106             return self.parse_multiline_dict(dict_source)
   107         elif self.markup in ("title", "t"):
   108             return self.parse_title(dict_source)
   109 
   110     def parse_multiline_dict(self, dict_source):
   111         """
   112         creates a dictionary based on a definition list with multiple entries of the same key. 
   113         The type of the value is a list
   114          a:: 1
   115          b:: 1
   116          b:: 2
   117          b:: 3
   118          c:: 4
   119         @param dict_source: pagename to read dict data from
   120         """
   121         body = Page(self.request, dict_source).get_raw_body()
   122         ddict = defaultdict(list)
   123 
   124         for match in WikiDicts._dict_page_parse_regex.finditer(body):
   125             key, value = match.groups()
   126             ddict[key].append(value)
   127         return ddict
   128     
   129     def parse_title(self, dict_source):
   130         """
   131         creates a dictionary based on page titles
   132         @param dict_source: pagename to read dict data from
   133         """
   134         body = Page(self.request, dict_source).get_raw_body()
   135         parser = SlidePage(self.request, dict_source).createSlideParser()
   136         ddict = {}
   137         for title, bodyStart, bodyEnd in parser.parse(body):
   138             ddict[title] = body[bodyStart:bodyEnd].strip()
   139         return ddict
   140 
   141     def get_page_list(self):
   142         """
   143         selects the pages dependent on a search term,
   144         without listing of template, dictionary pages and
   145         the pagename itselfs.
   146         """
   147         request = self.request
   148         search_term = self.search_term
   149         search_result = search.searchPages(request, search_term)
   150         pages = [title.page_name for title in search_result.hits]
   151         if not pages:
   152             return None
   153         # exclude some_pages
   154         filterfn = request.cfg.cache.page_template_regexact.search
   155         template_pages = request.rootpage.getPageList(filter=filterfn)
   156         excluded_pages = template_pages + [self.alias_page, self.pagename]
   157         selected_pages = [page for page in pages if page not in excluded_pages]
   158         selected_pages.sort()
   159         return selected_pages
   160 
   161     def get_names(self, selected_pages):
   162         """
   163         selects which column names should be used
   164         @param selected_pages: list of page names
   165         @return: list of names
   166         """
   167         request = self.request
   168         # use selection and order
   169         if self.names:
   170             return self.names
   171         # use keys from template page, no order
   172         elif Page(request, self.template_page).exists():
   173             page_dict = self.get_dict(self.template_page)
   174             names = page_dict.keys()
   175         else:
   176             # fallback use the keys used on selected pages
   177             names = []
   178             for page_name in selected_pages:
   179                 page_dict = self.get_dict(page_name)
   180                 keys = page_dict.keys()
   181                 names = names + keys
   182         return list(set(names))
   183 
   184     def dataset(self, names, selected_pages):
   185         """
   186         Sets the data for the data browser widget
   187         @param names: column names
   188         @param selected_pages: pages to read key value pairs from
   189         """
   190         _ = self.request.getText
   191         assert isinstance(selected_pages, list)
   192         request = self.request
   193         hide_columns = self.hide
   194         # default alias
   195         alias_dict = {}
   196         for name in names:
   197             alias_dict[name] = name
   198         if Page(request, self.alias_page).exists():
   199             alias = self.get_dict(self.alias_page)
   200             for name in names:
   201                 alias_dict[name] = alias.get(name, name)
   202 
   203         col = Column(self.title, label=self.title)
   204         if self.title in hide_columns:
   205             col.hidden = True
   206 
   207         data = TupleDataset()
   208         data.columns = []
   209         data.columns.extend([col])
   210 
   211         for page_name in selected_pages:
   212             page = Page(request, page_name)
   213             page_dict = self.get_dict(page_name)
   214             if self.filter_value and page_dict.get(self.filter_key, '') != self.filter_word:
   215                 continue
   216 
   217             row = []
   218             for name in names:
   219                 if name in page_dict.keys():
   220                     value = page_dict.get(name, '')
   221                     if isinstance(value, list) and len(value) > 1:
   222                         value = ' 1. %s' % '\n 1. '.join(value)
   223                     elif isinstance(value, list):
   224                         value = value[0]
   225 
   226                     if self.wiki_parser:
   227                         row.append((wikiutil.renderText(request, self.wiki_parser, value),
   228                                     wikiutil.escape(value, 1)))
   229                 else:
   230                     row.append('')
   231             if self.comments:
   232                 row.append('')
   233             try:
   234                 parent, child = page_name.split('/', 1)
   235             except ValueError:
   236                 child = page_name
   237             link = page.link_to(request, text="%s" % child)
   238             data.addRow([link] + row)
   239 
   240         if self.filter_name:
   241             filtercols = self.filter_name
   242             for name in names:
   243                 if self.filter_name != u'NeverExistingDefaultFilter' and name in filtercols:
   244                     col = Column(alias_dict[name], autofilter=(name in filtercols))
   245                     if name in hide_columns:
   246                         col.hidden = True
   247                     data.columns.append(col)
   248                 else:
   249                     col = Column(alias_dict[name], label=alias_dict[name])
   250                     if name in hide_columns:
   251                         col.hidden = True
   252                     data.columns.extend([col])
   253             if self.comments:
   254                 col = Column("Comment", label=_("Comment:"))
   255                 data.columns.extend([col])
   256         return data
   257 
   258     def render(self):
   259         """
   260         renders output as widget data browser table
   261         """
   262         request = self.request
   263         _ = request.getText
   264 
   265         selected_pages = self.get_page_list()
   266         if not selected_pages:
   267             return _("""\
   268 Please use a more selective search term instead of search_term="%s"\
   269 """) % self.search_term
   270 
   271         names = self.get_names(selected_pages)
   272 
   273         data = self.dataset(names, selected_pages)
   274         table = DataBrowserWidget(request)
   275 
   276         names.insert(0, "__name__")
   277         sort_columns = _name2index(names, self.sort)
   278         sort_reverse_columns = _name2index(names, self.reverse) or False
   279 
   280         table.setData(data, sort_columns, reverse=sort_reverse_columns)
   281         if self.enumeration:
   282             idx = 0
   283             for line in data.data:
   284                 line.insert(0, unicode(idx + 1))
   285                 data.data[idx] = line
   286                 idx += 1
   287             col = Column(" ", label=" ")
   288             data.columns.insert(0, col)
   289 
   290         html = ''.join(table.format(method='GET'))
   291         return html
   292 
   293 def macro_DictColumns(macro, pagename=unicode, title=u'', names=u'', sort=u'', reverse=u'',
   294                       hide=u'', filter_name=u'NeverExistingDefaultFilter',
   295                       filter_value=u'', template_page=u'', alias_page=u'',
   296                       parser=u'text_moin_wiki',
   297                       markup=("definition list", "title",
   298                               "multiline definition list",
   299                               "dl", "mdl", "t"),
   300                       comments=False,
   301                       enumeration=False,
   302                       search_term=None):
   303     """
   304     Creates a table by data browser widget from definition lists key value pairs.
   305     @param pagename: name of the page
   306     @param title: entry in upper left corner of the table
   307     @param name: names of columns, key name of definition list (comma separated)
   308     @param sort: name of columns to sort by
   309     @param reverse: name of columns to reverse sort by
   310     @param hide: name of columns to hide
   311     @param filter_name: name of columns to filter by autofilter
   312     @param filter_value: dict definition for value of column to filter by
   313     @param template_page: pagename of the template for setting column names
   314     @param alias_page: pagename of the page for setting aliases for column names
   315     @param parser: name of the parser used to render markup
   316     @param markup: type of markup for separating key value pairs
   317     @param search_term: regex used to search for selecting pages
   318     """
   319     kw = locals()
   320     #  wiki input can be a string with comma separated values.
   321     kw["names"] = _csv2list(kw["names"])
   322     kw["sort"] = _csv2list(kw["sort"])
   323     kw["reverse"] = _csv2list(kw["reverse"])
   324     kw["hide"] = _csv2list(kw["hide"])
   325     kw["filter_name"] = _csv2list(kw["filter_name"])
   326     html = DictColumns(**kw).render()
   327     # works together with
   328     # http://moinmo.in/FeatureRequests/SortableTables?action=AttachFile&do=view&target=common.js.patch
   329     # html = html.replace('id="dbw.table', 'class="sortable" id="dbw.table')
   330     return html
   331