Attachment 'DictColumns-1.1.py'

Download

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

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2018-11-26 01:53:32, 12.9 KB) [[attachment:DictColumns-1.1.py]]
  • [get | view] (2010-03-18 00:42:04, 53.5 KB) [[attachment:DictColumns1.png]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.