comparison MoinMoin/action/AttachFile.py @ 0:77665d8e2254

tag of nonpublic@localhost--archive/moin--enterprise--1.5--base-0 (automatically generated log message) imported from: moin--main--1.5--base-0
author Thomas Waldmann <tw-public@gmx.de>
date Thu, 22 Sep 2005 15:09:50 +0000
parents
children 55ff4feb0f59
comparison
equal deleted inserted replaced
-1:000000000000 0:77665d8e2254
1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - AttachFile action
4
5 This action lets a page have multiple attachment files.
6 It creates a folder <data>/pages/<pagename>/attachments
7 and keeps everything in there.
8
9 Form values: action=Attachment
10 1. with no 'do' key: returns file upload form
11 2. do=attach: accept file upload and saves the file in
12 ../attachment/pagename/
13 3. /pagename/fname?action=Attachment&do=get[&mimetype=type]:
14 return contents of the attachment file with the name fname.
15 4. /pathname/fname, do=view[&mimetype=type]:create a page
16 to view the content of the file
17
18 To insert an attachment into the page, use the "attachment:" pseudo
19 schema.
20
21 @copyright: 2001 by Ken Sugino (sugino@mediaone.net)
22 @copyright: 2001-2004 by Jürgen Hermann <jh@web.de>
23 @license: GNU GPL, see COPYING for details.
24 """
25
26 import os, mimetypes, time, urllib
27 from MoinMoin import config, user, util, wikiutil
28 from MoinMoin.Page import Page
29 from MoinMoin.util import MoinMoinNoFooter, filesys, web
30
31 action_name = __name__.split('.')[-1]
32
33 def htdocs_access(request):
34 return isinstance(request.cfg.attachments, type({}))
35
36
37 #############################################################################
38 ### External interface - these are called from the core code
39 #############################################################################
40
41 def getBasePath(request):
42 """ Get base path where page dirs for attachments are stored.
43 """
44 if htdocs_access(request):
45 return request.cfg.attachments['dir']
46 else:
47 return request.rootpage.getPagePath('pages')
48
49
50 def getAttachDir(request, pagename, create=0):
51 """ Get directory where attachments for page `pagename` are stored.
52 """
53 if htdocs_access(request):
54 # direct file access via webserver, from public htdocs area
55 pagename = wikiutil.quoteWikinameFS(pagename)
56 attach_dir = os.path.join(request.cfg.attachments['dir'], pagename, "attachments")
57 if create and not os.path.isdir(attach_dir):
58 filesys.makeDirs(attach_dir)
59 else:
60 # send file via CGI, from page storage area
61 attach_dir = Page(request, pagename).getPagePath("attachments", check_create=create)
62
63 return attach_dir
64
65
66 def getAttachUrl(pagename, filename, request, addts=0, escaped=0):
67 """ Get URL that points to attachment `filename` of page `pagename`.
68
69 If 'addts' is true, a timestamp with the file's modification time
70 is added, so that browsers reload a changed file.
71 """
72 if htdocs_access(request):
73 # direct file access via webserver
74 timestamp = ''
75 if addts:
76 try:
77 timestamp = '?ts=%s' % os.path.getmtime(
78 getFilename(request, pagename, filename))
79 except IOError:
80 pass
81
82 url = "%s/%s/attachments/%s%s" % (
83 request.cfg.attachments['url'], wikiutil.quoteWikinameFS(pagename),
84 urllib.quote(filename.encode(config.charset)), timestamp)
85 else:
86 # send file via CGI
87 url = "%s/%s?action=%s&do=get&target=%s" % (
88 request.getScriptname(), wikiutil.quoteWikinameURL(pagename),
89 action_name, urllib.quote_plus(filename.encode(config.charset)))
90 if escaped:
91 url = wikiutil.escape(url)
92 return url
93
94 def getIndicator(request, pagename):
95 """ Get an attachment indicator for a page (linked clip image) or
96 an empty string if not attachments exist.
97 """
98 _ = request.getText
99 attach_dir = getAttachDir(request, pagename)
100 if not os.path.exists(attach_dir): return ''
101
102 files = os.listdir(attach_dir)
103 if not files: return ''
104
105 attach_count = _('[%d attachments]') % len(files)
106 attach_icon = request.theme.make_icon('attach', vars={ 'attach_count': attach_count })
107 attach_link = wikiutil.link_tag(request,
108 "%s?action=AttachFile" % wikiutil.quoteWikinameURL(pagename),
109 attach_icon)
110
111 return attach_link
112
113
114 def getFilename(request, pagename, name):
115 return os.path.join(getAttachDir(request, pagename), name).encode(config.charset)
116
117
118 def info(pagename, request):
119 """ Generate snippet with info on the attachment for page `pagename`.
120 """
121 _ = request.getText
122
123 attach_dir = getAttachDir(request, pagename)
124 files = []
125 if os.path.isdir(attach_dir):
126 files = os.listdir(attach_dir)
127 page = Page(request, pagename)
128 # TODO: remove escape=0 in 1.4
129 link = page.url(request, {'action': 'AttachFile'}, escape=0)
130 attach_info = _('There are <a href="%(link)s">%(count)s attachment(s)</a> stored for this page.', formatted=False) % {
131 'count': len(files),
132 'link': wikiutil.escape(link)
133 }
134 return "\n<p>\n%s\n</p>\n" % attach_info
135
136
137 #############################################################################
138 ### Internal helpers
139 #############################################################################
140
141 def _addLogEntry(request, action, pagename, filename):
142 """ Add an entry to the edit log on uploads and deletes.
143
144 `action` should be "ATTNEW" or "ATTDEL"
145 """
146 from MoinMoin.logfile import editlog
147 t = wikiutil.timestamp2version(time.time())
148 # urllib always return ascii
149 fname = unicode(urllib.quote(filename.encode(config.charset)))
150
151 # TODO: for now we simply write 2 logs, maybe better use some multilog stuff
152 # Write to global log
153 log = editlog.EditLog(request)
154 log.add(request, t, 99999999, action, pagename, request.remote_addr, fname)
155
156 # Write to local log
157 log = editlog.EditLog(request, rootpagename=pagename)
158 log.add(request, t, 99999999, action, pagename, request.remote_addr, fname)
159
160
161 def _access_file(pagename, request):
162 """ Check form parameter `target` and return a tuple of
163 `(filename, filepath)` for an existing attachment.
164
165 Return `(None, None)` if an error occurs.
166 """
167 _ = request.getText
168
169 error = None
170 if not request.form.get('target', [''])[0]:
171 error = _("Filename of attachment not specified!")
172 else:
173 filename = wikiutil.taintfilename(request.form['target'][0])
174 fpath = getFilename(request, pagename, filename)
175
176 if os.path.isfile(fpath):
177 return (filename, fpath)
178 error = _("Attachment '%(filename)s' does not exist!") % {'filename': filename}
179
180 error_msg(pagename, request, error)
181 return (None, None)
182
183
184 def _build_filelist(request, pagename, showheader, readonly):
185 _ = request.getText
186
187 # access directory
188 attach_dir = getAttachDir(request, pagename)
189 files = _get_files(request, pagename)
190
191 str = ""
192 if files:
193 if showheader:
194 str = str + _(
195 "To refer to attachments on a page, use '''{{{attachment:filename}}}''', \n"
196 "as shown below in the list of files. \n"
197 "Do '''NOT''' use the URL of the {{{[get]}}} link, \n"
198 "since this is subject to change and can break easily."
199 )
200 str = str + "<ul>"
201
202 label_del = _("del")
203 label_get = _("get")
204 label_edit = _("edit")
205 label_view = _("view")
206
207 for file in files:
208 fsize = float(os.stat(os.path.join(attach_dir,file).encode(config.charset))[6]) # in byte
209 fsize = "%.1f" % (fsize / 1024)
210 baseurl = request.getScriptname()
211 action = action_name
212 urlpagename = wikiutil.quoteWikinameURL(pagename)
213 urlfile = urllib.quote_plus(file.encode(config.charset))
214
215 base, ext = os.path.splitext(file)
216 get_url = getAttachUrl(pagename, file, request, escaped=1)
217 parmdict = {'baseurl': baseurl, 'urlpagename': urlpagename, 'action': action,
218 'urlfile': urlfile, 'label_del': label_del,
219 'base': base, 'label_edit': label_edit,
220 'label_view': label_view,
221 'get_url': get_url, 'label_get': label_get,
222 'file': wikiutil.escape(file), 'fsize': fsize,
223 'pagename': pagename}
224
225 del_link = ''
226 if request.user.may.delete(pagename) and not readonly:
227 del_link = '<a href="%(baseurl)s/%(urlpagename)s' \
228 '?action=%(action)s&amp;do=del&amp;target=%(urlfile)s">%(label_del)s</a>&nbsp;| ' % parmdict
229
230 if ext == '.draw':
231 viewlink = '<a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;drawing=%(base)s">%(label_edit)s</a>' % parmdict
232 else:
233 viewlink = '<a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;do=view&amp;target=%(urlfile)s">%(label_view)s</a>' % parmdict
234
235 parmdict['viewlink'] = viewlink
236 parmdict['del_link'] = del_link
237 str = str + ('<li>[%(del_link)s'
238 '<a href="%(get_url)s">%(label_get)s</a>&nbsp;| %(viewlink)s]'
239 ' (%(fsize)s KB) attachment:<strong>%(file)s</strong></li>') % parmdict
240 str = str + "</ul>"
241 else:
242 if showheader:
243 str = '%s<p>%s</p>' % (str, _("No attachments stored for %(pagename)s") % {'pagename': pagename})
244
245 return str
246
247
248 def _get_files(request, pagename):
249 attach_dir = getAttachDir(request, pagename)
250 if os.path.isdir(attach_dir):
251 files = map(lambda a: a.decode(config.charset), os.listdir(attach_dir))
252 files.sort()
253 return files
254 return []
255
256
257 def _get_filelist(request, pagename):
258 return _build_filelist(request, pagename, 1, 0)
259
260
261 def error_msg(pagename, request, msg):
262 Page(request, pagename).send_page(request, msg=msg)
263
264
265 #############################################################################
266 ### Create parts of the Web interface
267 #############################################################################
268
269 def send_link_rel(request, pagename):
270 files = _get_files(request, pagename)
271 if len(files) > 0 and not htdocs_access(request):
272 scriptName = request.getScriptname()
273 pagename_quoted = wikiutil.quoteWikinameURL(pagename)
274
275 for file in files:
276 url = "%s/%s?action=%s&do=view&target=%s" % (
277 scriptName, pagename_quoted,
278 action_name, urllib.quote_plus(file.encode(config.charset)))
279
280 request.write(u'<link rel="Appendix" title="%s" href="%s">\n' % (
281 wikiutil.escape(file), wikiutil.escape(url)))
282
283
284 def send_hotdraw(pagename, request):
285 _ = request.getText
286
287 now = time.time()
288 pubpath = request.cfg.url_prefix + "/applets/TWikiDrawPlugin"
289 basename = request.form['drawing'][0]
290 drawpath = getAttachUrl(pagename, basename + '.draw', request, escaped=1)
291 pngpath = getAttachUrl(pagename, basename + '.png', request, escaped=1)
292 querystr = {'action': 'AttachFile', 'ts': now}
293 querystr = wikiutil.escape(web.makeQueryString(querystr))
294 pagelink = '%s/%s?%s' % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), querystr)
295 helplink = Page(request, "HelpOnActions/AttachFile").url(request)
296 savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted
297 # request, {'savename': request.form['drawing'][0]+'.draw'}
298 #savelink = '/cgi-bin/dumpform.bat'
299
300 if htdocs_access(request):
301 timestamp = '?ts=%s' % now
302 else:
303 timestamp = '&amp;ts=%s' % now
304
305 request.write('<h2>' + _("Edit drawing") + '</h2>')
306 request.write("""
307 <p>
308 <img src="%(pngpath)s%(timestamp)s">
309 <applet code="CH.ifa.draw.twiki.TWikiDraw.class"
310 archive="%(pubpath)s/twikidraw.jar" width="640" height="480">
311 <param name="drawpath" value="%(drawpath)s">
312 <param name="pngpath" value="%(pngpath)s">
313 <param name="savepath" value="%(savelink)s">
314 <param name="basename" value="%(basename)s">
315 <param name="viewpath" value="%(pagelink)s">
316 <param name="helppath" value="%(helplink)s">
317 <strong>NOTE:</strong> You need a Java enabled browser to edit the drawing example.
318 </applet>
319 </p>""" % {
320 'pngpath': pngpath, 'timestamp': timestamp,
321 'pubpath': pubpath, 'drawpath': drawpath,
322 'savelink': savelink, 'pagelink': pagelink, 'helplink': helplink,
323 'basename': basename
324 })
325
326
327 def send_uploadform(pagename, request):
328 """ Send the HTML code for the list of already stored attachments and
329 the file upload form.
330 """
331 _ = request.getText
332
333 if not request.user.may.read(pagename):
334 request.write('<p>%s</p>' % _('You are not allowed to view this page.'))
335 return
336
337 request.write('<h2>' + _("Attached Files") + '</h2>')
338 request.write(_get_filelist(request, pagename))
339
340 if not request.user.may.write(pagename):
341 request.write('<p>%s</p>' % _('You are not allowed to attach a file to this page.'))
342 return
343
344 if request.form.get('drawing', [None])[0]:
345 send_hotdraw(pagename, request)
346 return
347
348 request.write('<h2>' + _("New Attachment") + '</h2><p>' +
349 _("""An upload will never overwrite an existing file. If there is a name
350 conflict, you have to rename the file that you want to upload.
351 Otherwise, if "Rename to" is left blank, the original filename will be used.""") + '</p>')
352 request.write("""
353 <form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
354 <dl>
355 <dt>%(upload_label_file)s</dt>
356 <dd><input type="file" name="file" size="50"></dd>
357 <dt>%(upload_label_rename)s</dt>
358 <dd><input type="text" name="rename" size="50" value="%(rename)s"></dd>
359 </dl>
360 <p>
361 <input type="hidden" name="action" value="%(action_name)s">
362 <input type="hidden" name="do" value="upload">
363 <input type="submit" value="%(upload_button)s">
364 </p>
365 </form>
366 """ % {
367 'baseurl': request.getScriptname(),
368 'pagename': wikiutil.quoteWikinameURL(pagename),
369 'action_name': action_name,
370 'upload_label_file': _('File to upload'),
371 'upload_label_rename': _('Rename to'),
372 'rename': request.form.get('rename', [''])[0],
373 'upload_button': _('Upload'),
374 })
375
376 #<dt>%(upload_label_mime)s</dt>
377 #<dd><input type="text" name="mime" size="50"></dd>
378 # 'upload_label_mime': _('MIME Type (optional)'),
379
380
381 #############################################################################
382 ### Web interface for file upload, viewing and deletion
383 #############################################################################
384
385 def execute(pagename, request):
386 """ Main dispatcher for the 'AttachFile' action.
387 """
388 _ = request.getText
389
390 msg = None
391 if action_name in request.cfg.actions_excluded:
392 msg = _('File attachments are not allowed in this wiki!')
393 elif request.form.has_key('filepath'):
394 if request.user.may.write(pagename):
395 save_drawing(pagename, request)
396 request.http_headers()
397 request.write("OK")
398 else:
399 msg = _('You are not allowed to save a drawing on this page.')
400 elif not request.form.has_key('do'):
401 upload_form(pagename, request)
402 elif request.form['do'][0] == 'upload':
403 if request.user.may.write(pagename):
404 if request.form.has_key('file'):
405 do_upload(pagename, request)
406 else:
407 # This might happen when trying to upload file names
408 # with non-ascii characters on Safari.
409 msg = _("No file content. Delete non ASCII characters from the file name and try again.")
410 else:
411 msg = _('You are not allowed to attach a file to this page.')
412 elif request.form['do'][0] == 'del':
413 if request.user.may.delete(pagename):
414 del_file(pagename, request)
415 else:
416 msg = _('You are not allowed to delete attachments on this page.')
417 elif request.form['do'][0] == 'get':
418 if request.user.may.read(pagename):
419 get_file(pagename, request)
420 else:
421 msg = _('You are not allowed to get attachments from this page.')
422 elif request.form['do'][0] == 'view':
423 if request.user.may.read(pagename):
424 view_file(pagename, request)
425 else:
426 msg = _('You are not allowed to view attachments of this page.')
427 else:
428 msg = _('Unsupported upload action: %s') % (request.form['do'][0],)
429
430 if msg:
431 error_msg(pagename, request, msg)
432
433
434 def upload_form(pagename, request, msg=''):
435 _ = request.getText
436
437 request.http_headers()
438 # Use user interface language for this generated page
439 request.setContentLanguage(request.lang)
440 wikiutil.send_title(request, _('Attachments for "%(pagename)s"') % {'pagename': pagename}, pagename=pagename, msg=msg)
441 request.write('<div id="content">\n') # start content div
442 send_uploadform(pagename, request)
443 request.write('</div>\n') # end content div
444 wikiutil.send_footer(request, pagename, showpage=1)
445
446
447 def do_upload(pagename, request):
448 _ = request.getText
449
450 # make filename
451 filename = None
452 if request.form.has_key('file__filename__'):
453 filename = request.form['file__filename__']
454 rename = None
455 if request.form.has_key('rename'):
456 rename = request.form['rename'][0].strip()
457
458 # if we use twisted, "rename" field is NOT optional, because we
459 # can't access the client filename
460 if rename:
461 target = rename
462 elif filename:
463 target = filename
464 else:
465 error_msg(pagename, request, _("Filename of attachment not specified!"))
466 return
467
468 # get file content
469 filecontent = request.form['file'][0]
470
471 # preprocess the filename
472 # 1. strip leading drive and path (IE misbehaviour)
473 if len(target) > 1 and (target[1] == ':' or target[0] == '\\'): # C:.... or \path... or \\server\...
474 bsindex = target.rfind('\\')
475 if bsindex >= 0:
476 target = target[bsindex+1:]
477
478 # 2. replace illegal chars
479 target = wikiutil.taintfilename(target)
480
481 # set mimetype from extension, or from given mimetype
482 #type, encoding = mimetypes.guess_type(target)
483 #if not type:
484 # ext = None
485 # if request.form.has_key('mime'):
486 # ext = mimetypes.guess_extension(request.form['mime'][0])
487 # if not ext:
488 # type, encoding = mimetypes.guess_type(filename)
489 # if type:
490 # ext = mimetypes.guess_extension(type)
491 # else:
492 # ext = ''
493 # target = target + ext
494
495 # get directory, and possibly create it
496 attach_dir = getAttachDir(request, pagename, create=1)
497 # save file
498 fpath = os.path.join(attach_dir, target).encode(config.charset)
499 if os.path.exists(fpath):
500 msg = _("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % {
501 'target': target, 'filename': filename}
502 else:
503 stream = open(fpath, 'wb')
504 try:
505 stream.write(filecontent)
506 finally:
507 stream.close()
508 os.chmod(fpath, 0666 & config.umask)
509
510 bytes = len(filecontent)
511 msg = _("Attachment '%(target)s' (remote name '%(filename)s')"
512 " with %(bytes)d bytes saved.") % {
513 'target': target, 'filename': filename, 'bytes': bytes}
514 _addLogEntry(request, 'ATTNEW', pagename, target)
515
516 # return attachment list
517 upload_form(pagename, request, msg)
518
519
520 def save_drawing(pagename, request):
521
522 filename = request.form['filename'][0]
523 filecontent = request.form['filepath'][0]
524
525 # there should be no difference in filename parsing with or without
526 # htdocs_access, cause the filename param is used
527 basepath, basename = os.path.split(filename)
528 basename, ext = os.path.splitext(basename)
529
530 # get directory, and possibly create it
531 attach_dir = getAttachDir(request, pagename, create=1)
532
533 if ext == '.draw':
534 _addLogEntry(request, 'ATTDRW', pagename, basename + ext)
535 filecontent = filecontent.replace("\r","")
536
537 savepath = os.path.join(getAttachDir(request, pagename), basename + ext)
538 if ext == '.map' and filecontent.strip()=='':
539 # delete map file if it is empty
540 os.unlink(savepath)
541 else:
542 file = open(savepath, 'wb')
543 try:
544 file.write(filecontent)
545 finally:
546 file.close()
547
548 # touch attachment directory to invalidate cache if new map is saved
549 if ext == '.map':
550 os.utime(getAttachDir(request, pagename), None)
551
552 def del_file(pagename, request):
553 _ = request.getText
554
555 filename, fpath = _access_file(pagename, request)
556 if not filename: return # error msg already sent in _access_file
557
558 # delete file
559 os.remove(fpath)
560 _addLogEntry(request, 'ATTDEL', pagename, filename)
561
562 upload_form(pagename, request, msg=_("Attachment '%(filename)s' deleted.") % {'filename': filename})
563
564
565 def get_file(pagename, request):
566 import shutil
567
568 filename, fpath = _access_file(pagename, request)
569 if not filename: return # error msg already sent in _access_file
570
571 # get mimetype
572 type, enc = mimetypes.guess_type(filename)
573 if not type:
574 type = "application/octet-stream"
575
576 # send header
577 request.http_headers([
578 "Content-Type: %s" % type,
579 "Content-Length: %d" % os.path.getsize(fpath),
580 # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs
581 # There is no solution that is compatible to IE except stripping non-ascii chars
582 "Content-Disposition: inline; filename=\"%s\"" % filename.encode(config.charset),
583 ])
584
585 # send data
586 shutil.copyfileobj(open(fpath, 'rb'), request, 8192)
587
588 raise MoinMoinNoFooter
589
590 def send_viewfile(pagename, request):
591 _ = request.getText
592
593 filename, fpath = _access_file(pagename, request)
594 if not filename: return
595
596 request.write('<h2>' + _("Attachment '%(filename)s'") % {'filename': filename} + '</h2>')
597
598 type, enc = mimetypes.guess_type(filename)
599 if type:
600 if type[:5] == 'image':
601 timestamp = htdocs_access(request) and "?%s" % time.time() or ''
602 request.write('<img src="%s%s" alt="%s">' % (
603 getAttachUrl(pagename, filename, request, escaped=1), timestamp, wikiutil.escape(filename, 1)))
604 return
605 elif type[:4] == 'text':
606 # TODO: should use formatter here!
607 request.write("<pre>")
608 # Try to decode file contents. It may return junk, but we
609 # don't have enough information on attachments.
610 content = open(fpath, 'r').read()
611 content = wikiutil.decodeUnknownInput(content)
612 content = wikiutil.escape(content)
613 request.write(content)
614 request.write("</pre>")
615 return
616
617 request.write('<p>' + _("Unknown file type, cannot display this attachment inline.") + '</p>')
618 request.write('<a href="%s">%s</a>' % (
619 getAttachUrl(pagename, filename, request, escaped=1), wikiutil.escape(filename)))
620
621
622 def view_file(pagename, request):
623 _ = request.getText
624
625 filename, fpath = _access_file(pagename, request)
626 if not filename: return
627
628 # send header & title
629 request.http_headers()
630 # Use user interface language for this generated page
631 request.setContentLanguage(request.lang)
632 title = _('attachment:%(filename)s of %(pagename)s', formatted=True) % {
633 'filename': filename, 'pagename': pagename}
634 wikiutil.send_title(request, title, pagename=pagename)
635
636 # send body
637 # TODO: use formatter startContent?
638 request.write('<div id="content">\n') # start content div
639 send_viewfile(pagename, request)
640 send_uploadform(pagename, request)
641 request.write('</div>\n') # end content div
642
643 # send footer
644 wikiutil.send_footer(request, pagename)
645
646
647 #############################################################################
648 ### File attachment administration
649 #############################################################################
650
651 def do_admin_browser(request):
652 """ Browser for SystemAdmin macro.
653 """
654 from MoinMoin.util.dataset import TupleDataset, Column
655 _ = request.getText
656
657 data = TupleDataset()
658 data.columns = [
659 Column('page', label=('Page')),
660 Column('file', label=('Filename')),
661 Column('size', label=_('Size'), align='right'),
662 #Column('action', label=_('Action')),
663 ]
664
665 # iterate over pages that might have attachments
666 pages = request.rootpage.getPageList()
667 for pagename in pages:
668 # check for attachments directory
669 page_dir = getAttachDir(request, pagename)
670 if os.path.isdir(page_dir):
671 # iterate over files of the page
672 files = os.listdir(page_dir)
673 for filename in files:
674 filepath = os.path.join(page_dir, filename)
675 data.addRow((
676 Page(request, pagename).link_to(request, querystr="action=AttachFile"),
677 wikiutil.escape(filename.decode(config.charset)),
678 os.path.getsize(filepath),
679 # '',
680 ))
681
682 if data:
683 from MoinMoin.widget.browser import DataBrowserWidget
684
685 browser = DataBrowserWidget(request)
686 browser.setData(data)
687 return browser.toHTML()
688
689 return ''
690