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.You are not allowed to attach a file to this page.