Attachment 'overlay-1.5.6.diff'
Download 1 Index: script/maint/mkpagepacks.py
2 ===================================================================
3 --- script/maint/mkpagepacks.py (.../tags/MoinMoin-1.5.6/MoinMoin) (revision 237)
4 +++ script/maint/mkpagepacks.py (.../trunk/MoinMoin) (revision 237)
5 @@ -110,7 +110,7 @@
6 pagename = pagename.strip()
7 page = Page(request, pagename)
8 try:
9 - underlay, path = page.getPageBasePath(-1)
10 + path = page.getPageBasePath()
11 shutil.rmtree(path)
12 except:
13 pass
14 Index: multiconfig.py
15 ===================================================================
16 --- multiconfig.py (.../tags/MoinMoin-1.5.6/MoinMoin) (revision 237)
17 +++ multiconfig.py (.../trunk/MoinMoin) (revision 237)
18 @@ -612,17 +614,28 @@
19 Both data and underlay should exists and allow read, write and
20 execute.
21 """
22 + if not hasattr(self, 'page_data_dirs'):
23 + data_dir = getattr(self, 'data_dir')
24 + underlay_dir = getattr(self, 'data_underlay_dir')
25 + self.page_data_dirs = [ data_dir, ]
26 + if underlay_dir:
27 + self.page_data_dirs = [ data_dir, underlay_dir, ]
28 +
29 mode = os.F_OK | os.R_OK | os.W_OK | os.X_OK
30 - for attr in ('data_dir', 'data_underlay_dir'):
31 - path = getattr(self, attr)
32 + for attr in ('data_dir', 'data_underlay_dir', 'page_data_dirs'):
33 + if attr == 'data_dir' or attr == 'data_underlay_dir':
34 + paths_to_check = ( getattr(self, attr), )
35 + else:
36 + paths_to_check = getattr(self, attr)
37
38 # allow an empty underlay path or None
39 - if attr == 'data_underlay_dir' and not path:
40 + if attr == 'data_underlay_dir' and not paths_to_check:
41 continue
42
43 - path_pages = os.path.join(path, "pages")
44 - if not (os.path.isdir(path_pages) and os.access(path_pages, mode)):
45 - msg = '''
46 + for path in paths_to_check:
47 + path_pages = os.path.join(path, "pages")
48 + if not (os.path.isdir(path_pages) and os.access(path_pages, mode)):
49 + msg = '''
50 %(attr)s "%(path)s" does not exists, or has incorrect ownership or
51 permissions.
52
53 Index: PageEditor.py
54 ===================================================================
55 --- PageEditor.py (.../tags/MoinMoin-1.5.6/MoinMoin) (revision 237)
56 +++ PageEditor.py (.../trunk/MoinMoin) (revision 237)
57 @@ -804,7 +839,10 @@
58 llog = editlog.EditLog(self.request, filename=pagelog,
59 uid_override=self.uid_override)
60 # Open the global log
61 - glog = editlog.EditLog(self.request, uid_override=self.uid_override)
62 + root_dir = os.path.dirname( os.path.dirname( pagedir ) )
63 + gpagelog = os.path.join( root_dir, 'edit-log')
64 + glog = editlog.EditLog(self.request, filename=gpagelog,
65 + uid_override=self.uid_override)
66
67 if not os.path.exists(pagedir): # new page, create and init pagedir
68 os.mkdir(pagedir)
69 @@ -894,9 +894,6 @@
70 # set in-memory content
71 self.set_raw_body(text)
72
73 - # reset page object
74 - self.reset()
75 -
76 # write the editlog entry
77 # for now simply make 2 logs, better would be some multilog stuff maybe
78 if self.do_revision_backup:
79 @@ -910,6 +907,9 @@
80 if got_lock:
81 filesys.rename(clfn, cfn)
82
83 + # reset page object
84 + self.reset()
85 +
86 # add event log entry
87 elog = eventlog.EventLog(self.request)
88 elog.add(self.request, 'SAVEPAGE', {'pagename': self.page_name}, 1, mtime_usecs)
89 @@ -916,7 +955,7 @@
90 other = False
91 pagelog = self.getPagePath('edit-log', use_underlay=0, isfile=1)
92 next_line = None
93 - for line in editlog.EditLog(self.request, pagelog).reverse():
94 + for line in editlog.EditLog(self.request, filename=pagelog).reverse():
95 if int(line.rev)==int(rev):
96 break
97 if not line.is_from_current_user(self.request):
98 Index: wikiaction.py
99 ===================================================================
100 --- wikiaction.py (.../tags/MoinMoin-1.5.6/MoinMoin) (revision 237)
101 +++ wikiaction.py (.../trunk/MoinMoin) (revision 237)
102 @@ -213,6 +215,15 @@
103 # show page size
104 request.write(("<p>%s</p>" % _("Page size: %d")) % page.size())
105
106 + page_location = 'Overlay'
107 + page_path = page.getPageBasePath()
108 + if request.cfg.data_underlay_dir and \
109 + request.cfg.data_underlay_dir == os.path.commonprefix( [ request.cfg.data_underlay_dir, page_path ] ):
110 + page_location = 'Underlay'
111 + elif request.cfg.data_dir == os.path.commonprefix( [ request.cfg.data_dir, page_path ] ):
112 + page_location = 'Standard'
113 + request.write(("<p>%s</p>" % _("Page Location: %s")) % page_location)
114 +
115 # show SHA digest fingerprint
116 import sha
117 digest = sha.new(page.get_raw_body().encode(config.charset)).hexdigest().upper()
118 Index: Page.py
119 ===================================================================
120 --- Page.py (.../tags/MoinMoin-1.5.6/MoinMoin) (revision 237)
121 +++ Page.py (.../trunk/MoinMoin) (revision 237)
122 @@ -24,7 +24,7 @@
123 # Header regular expression, used to get header boundaries
124 header_re = r'(^#+.*(?:\n\s*)+)+'
125
126 - def __init__(self, request, page_name, **keywords):
127 + def __init__(self, request, page_name, **kw):
128 """
129 Create page object.
130
131 @@ -37,13 +37,12 @@
132 @keyword formatter: formatter instance
133 @keyword include_self: if 1, include current user (default: 0)
134 """
135 - self.rev = keywords.get('rev', 0) # revision of this page
136 - self.is_rootpage = keywords.get('is_rootpage', 0) # is this __init__ of rootpage?
137 - self.include_self = keywords.get('include_self', 0)
138 self.request = request
139 self.cfg = request.cfg
140 -
141 self.page_name = page_name
142 + self.rev = kw.get('rev', 0) # revision of this page
143 + self.is_rootpage = kw.get('is_rootpage', 0) # is this __init__ of rootpage?
144 + self.include_self = kw.get('include_self', 0)
145
146 # XXX uncomment to see how many pages we create....
147 #import sys, traceback
148 @@ -51,8 +50,8 @@
149 #traceback.print_stack(limit=4, file=sys.stderr)
150
151
152 - if keywords.has_key('formatter'):
153 - self.formatter = keywords.get('formatter')
154 + if kw.has_key('formatter'):
155 + self.formatter = kw.get('formatter')
156 self.default_formatter = 0
157 else:
158 self.default_formatter = 1
159 @@ -67,39 +66,41 @@
160
161 def reset(self):
162 """ Reset page state """
163 - page_name = self.page_name
164 # page_name quoted for file system usage, needs to be reset to
165 # None when pagename changes
166
167 - qpagename = wikiutil.quoteWikinameFS(page_name)
168 - self.page_name_fs = qpagename
169 + self.page_name_fs = wikiutil.quoteWikinameFS(self.page_name)
170
171 # the normal and the underlay path used for this page
172 + self.underlaypath = None
173 if not self.cfg.data_underlay_dir is None:
174 - underlaypath = os.path.join(self.cfg.data_underlay_dir, "pages", qpagename)
175 - else:
176 - underlaypath = None
177 - if self.is_rootpage: # we have no request.rootpage yet!
178 - if not page_name:
179 - normalpath = self.cfg.data_dir
180 - else:
181 - raise NotImplementedError(
182 - "TODO: handle other values of rootpage (not used yet)")
183 - else:
184 - normalpath = self.request.rootpage.getPagePath("pages", qpagename,
185 - check_create=0, use_underlay=0)
186 + self.underlaypath = os.path.join(self.cfg.data_underlay_dir, "pages", self.page_name_fs)
187
188 + self.default_base_path = os.path.join(self.cfg.data_dir, "pages", self.page_name_fs)
189 +
190 + pos = self.page_name.rfind('/')
191 + if pos > 0:
192 + parent = Page(self.request, self.page_name[:pos])
193 + parent_base = os.path.dirname(parent.getPageBasePath())
194 + self.default_base_path = os.path.join(parent_base, self.page_name_fs)
195 +
196 + if self.is_rootpage:
197 + self.default_base_path = os.path.dirname( os.path.dirname( self.default_base_path ) )
198 +
199 + # reset the page path
200 + self.pagepath = None
201 + self.getPageBasePath()
202 +
203 # TUNING - remember some essential values
204
205 # does the page come from normal page storage (0) or from
206 # underlay dir (1) (can be used as index into following lists)
207 - self._underlay = None
208 + self._underlay = 0
209 + if self.underlaypath == self.pagepath:
210 + self._underlay = 1
211
212 - # path to normal / underlay page dir
213 - self._pagepath = [normalpath, underlaypath]
214 -
215 # path to normal / underlay page file
216 - self._pagefile = [normalpath, underlaypath]
217 + self._pagefile = [None, None]
218
219 # *latest* revision of this page XXX needs to be reset to None
220 # when current rev changes
221 @@ -185,15 +186,19 @@
222 int realrevint,
223 bool exists)
224 """
225 +
226 # Figure out if we should use underlay or not, if needed.
227 - if use_underlay == -1:
228 - if self._underlay is not None and self._pagepath[self._underlay] is not None:
229 - underlay = self._underlay
230 - pagedir = self._pagepath[underlay]
231 - else:
232 - underlay, pagedir = self.getPageStatus(check_create=0)
233 + if use_underlay == 1:
234 + pagedir = self.underlaypath
235 + underlay = 1
236 else:
237 - underlay, pagedir = use_underlay, self._pagepath[use_underlay]
238 + pagedir = self.getPageBasePath( )
239 + underlay = 0
240 + if pagedir == self.underlaypath :
241 + underlay = 1
242 + if use_underlay == 0:
243 + pagedir = self.default_base_path
244 + underlay = 0
245
246 # Find current revision, if automatic selection is requested.
247 if rev == 0:
248 @@ -224,14 +229,21 @@
249
250 def current_rev(self):
251 """Return number of current revision.
252 -
253 +
254 This is the same as get_rev()[1].
255 -
256 +
257 @return: int revision
258 """
259 - pagefile, rev, exists = self.get_rev()
260 - return rev
261 + if self.rev:
262 + return self.rev
263
264 + page_path = self.getPageBasePath( )
265 + pagefile, realrev, exists = self.get_rev_dir(page_path)
266 + #if realrev != 99999999:
267 + # self.rev = realrev
268 +
269 + return realrev
270 +
271 def get_real_rev(self):
272 """Returns the real revision number of this page. A rev=0 is
273 translated to the current revision.
274 @@ -243,73 +255,38 @@
275 return self.current_rev()
276 return self.rev
277
278 - def getPageBasePath(self, use_underlay):
279 + def _find_page(self, use_underlay=-1):
280 """
281 - Get full path to a page-specific storage area. `args` can
282 - contain additional path components that are added to the base path.
283 + Find the full path to a page-specific storage area.
284
285 - @param use_underlay: force using a specific pagedir, default '-1'
286 - '-1' = automatically choose page dir
287 - '1' = use underlay page dir
288 - '0' = use standard page dir
289 + @param use_underlay: 0 == no underlay, 1 == only underlay, other values default to look everywhere
290 +
291 @rtype: string
292 - @return: int underlay,
293 - str the full path to the storage area
294 + @return: str the full path to the storage area, '' if no page not found
295 """
296 - standardpath, underlaypath = self._pagepath
297 - if underlaypath is None:
298 - use_underlay = 0
299
300 - # self is a NORMAL page
301 - if not self is self.request.rootpage:
302 - if use_underlay == -1: # automatic
303 - if self._underlay is None:
304 - underlay, path = 0, standardpath
305 - pagefile, rev, exists = self.get_rev(use_underlay=0)
306 - if not exists:
307 - pagefile, rev, exists = self.get_rev(use_underlay=1)
308 - if exists:
309 - underlay, path = 1, underlaypath
310 - self._underlay = underlay # XXX XXX
311 - else:
312 - underlay = self._underlay
313 - path = self._pagepath[underlay]
314 - else: # normal or underlay
315 - underlay, path = use_underlay, self._pagepath[use_underlay]
316 + search_path = self.cfg.page_data_dirs
317
318 - # self is rootpage
319 - else:
320 - # our current rootpage is not a toplevel, but under another page
321 - if self.page_name:
322 - # this assumes flat storage of pages and sub pages on same level
323 - if use_underlay == -1: # automatic
324 - if self._underlay is None:
325 - underlay, path = 0, standardpath
326 - pagefile, rev, exists = self.get_rev(use_underlay=0)
327 - if not exists:
328 - pagefile, rev, exists = self.get_rev(use_underlay=1)
329 - if exists:
330 - underlay, path = 1, underlaypath
331 - self._underlay = underlay # XXX XXX
332 - else:
333 - underlay = self._underlay
334 - path = self._pagepath[underlay]
335 - else: # normal or underlay
336 - underlay, path = use_underlay, self._pagepath[use_underlay]
337 + if self.request.cfg.data_underlay_dir:
338 + if use_underlay == 1: # only look in the underlay dir
339 + search_path = [ self.request.cfg.data_underlay_dir ]
340 + elif use_underlay == 0: #dont look in underlay dir
341 + search_path = []
342 + for dir in self.cfg.page_data_dirs:
343 + if not dir == self.request.cfg.data_underlay_dir:
344 + search_path.append( dir )
345
346 - # our current rootpage is THE virtual rootpage, really at top of all
347 + for dir in search_path:
348 + if self.page_name_fs:
349 + fullpath = os.path.join( dir, "pages", self.page_name_fs)
350 + if os.path.exists(fullpath) :
351 + return fullpath
352 else:
353 - # 'auto' doesn't make sense here. maybe not even 'underlay':
354 - if use_underlay == 1:
355 - underlay, path = 1, self.cfg.data_underlay_dir
356 - # no need to check 'standard' case, we just use path in that case!
357 - else:
358 - # this is the location of the virtual root page
359 - underlay, path = 0, self.cfg.data_dir
360 + return dir
361
362 - return underlay, path
363 + return ''
364
365 - def getPageStatus(self, *args, **kw):
366 + def getPagePath(self, *args, **kw):
367 """
368 Get full path to a page-specific storage area. `args` can
369 contain additional path components that are added to the base path.
370 @@ -324,28 +301,79 @@
371 (default true)
372 @keyword isfile: is the last component in args a filename? (default is false)
373 @rtype: string
374 - @return: (int underlay (1 if using underlay, 0 otherwise),
375 - str the full path to the storage area )
376 + @return: str the full path to the storage area
377 """
378 +
379 check_create = kw.get('check_create', 1)
380 - isfile = kw.get('isfile', 0)
381 use_underlay = kw.get('use_underlay', -1)
382 - underlay, path = self.getPageBasePath(use_underlay)
383 - fullpath = os.path.join(*((path,) + args))
384 +
385 + if use_underlay == 1:
386 + page_path = self.underlaypath
387 + else:
388 + page_path = self.getPageBasePath( )
389 + if use_underlay == 0 and page_path == self.underlaypath:
390 + page_path = self.default_base_path
391 +
392 + fullpath = os.path.join(*((page_path,) + args))
393 +
394 if check_create:
395 + isfile = kw.get('isfile', 0)
396 if isfile:
397 dirname, filename = os.path.split(fullpath)
398 else:
399 dirname = fullpath
400 if not os.path.exists(dirname):
401 filesys.makeDirs(dirname)
402 - return underlay, fullpath
403 + if hasattr(self.request.cfg, 'uime') and self.request.cfg.uime['enable']:
404 + from MoinMoin.action import AttachFile
405 + AttachFile.getAttachDir(self, self.page_name, create=1)
406 + return fullpath
407
408 - def getPagePath(self, *args, **kw):
409 - """Return path to the page storage area."""
410
411 - return self.getPageStatus(*args, **kw)[1]
412 + def getPageBasePath(self):
413 + """
414 + Get full path to a page-specific storage area.
415
416 + @rtype: string
417 + @return: str the full path to the storage area
418 + """
419 +
420 + if hasattr(self, 'pagepath') and self.pagepath:
421 + return self.pagepath
422 +
423 + self.pagepath = self._find_current_page(rev=self.rev)
424 + if not self.pagepath:
425 + self.pagepath = self.default_base_path
426 +
427 + return self.pagepath
428 +
429 + def _find_current_page(self, rev=0, use_underlay=-1):
430 + """
431 + Find the current full path to a page-specific storage area.
432 +
433 + @param use_underlay: -1 == auto, 0 == normal, 1 == underlay
434 + @param rev: int revision to get (default is 0 and means the current
435 + revision )
436 +
437 + @rtype: string
438 + @return: str the full path to the storage area, '' if no page not found
439 + """
440 +
441 + if not rev and self.rev:
442 + rev = self.rev
443 +
444 + fullpath = self._find_page(use_underlay)
445 + if fullpath :
446 + pagefile, realrev, exists = self.get_rev_dir(fullpath, rev)
447 + if exists:
448 + return fullpath
449 + return ''
450 +
451 + return ''
452 +
453 + def getPageUnderlayPath(self):
454 + return self.underlaypath
455 +
456 def split_title(self, request, force=0):
457 """
458 Return a string with the page name split by spaces, if
459 @@ -397,7 +425,8 @@
460 def _last_edited(self, request):
461 from MoinMoin.logfile import editlog
462 try:
463 - logfile = editlog.EditLog(request, self.getPagePath('edit-log', check_create=0, isfile=1))
464 + logfile_name = os.path.join( self.getPageBasePath(), 'edit-log')
465 + logfile = editlog.EditLog(request, filename=logfile_name)
466 logfile.to_end()
467 log = logfile.previous()
468 except StopIteration:
469 @@ -486,8 +515,24 @@
470 @rtype: bool
471 @return: true if page lives in the underlay dir
472 """
473 - return self.exists(domain='underlay', includeDeleted=includeDeleted)
474 + # page cant be an underlay page
475 + if not self.request.cfg.data_underlay_dir:
476 + return False
477
478 + if includeDeleted:
479 + page_dir = self._find_page(use_underlay=1)
480 + else:
481 + page_dir = self._find_current_page(use_underlay=1)
482 +
483 + if page_dir:
484 + underlay_dir = self.request.cfg.data_underlay_dir
485 +
486 + # we found the page in a dir that is the underlay dir
487 + if underlay_dir == os.path.commonprefix( [ underlay_dir, page_dir ] ):
488 + return True
489 +
490 + return False
491 +
492 def isStandardPage(self, includeDeleted=True):
493 """ Does this page live in the data dir?
494
495 @@ -498,9 +543,25 @@
496 @rtype: bool
497 @return: true if page lives in the data dir
498 """
499 - return self.exists(domain='standard', includeDeleted=includeDeleted)
500 + # page cant be an underlay page
501 + if not self.request.cfg.data_underlay_dir:
502 + return True
503
504 - def exists(self, rev=0, domain=None, includeDeleted=False):
505 + if includeDeleted:
506 + page_dir = self._find_page()
507 + else:
508 + page_dir = self._find_current_page()
509 +
510 + if page_dir:
511 + underlay_dir = self.request.cfg.data_underlay_dir
512 +
513 + # we found the page in a dir that is not the underlay dir
514 + if not underlay_dir == os.path.commonprefix( [ underlay_dir, page_dir ] ):
515 + return True
516 +
517 + return False
518 +
519 + def exists(self, rev=0, includeDeleted=False):
520 """ Does this page exist?
521
522 This is the lower level method for checking page existence. Use
523 @@ -508,38 +569,19 @@
524 cleaner code.
525
526 @param rev: revision to look for. Default check current
527 - @param domain: where to look for the page. Default look in all,
528 - available values: 'underlay', 'standard'
529 @param includeDeleted: ignore page state, just check its pagedir
530 @rtype: bool
531 @return: true, if page exists
532 """
533 - # Edge cases
534 - if domain == 'underlay' and not self.request.cfg.data_underlay_dir:
535 - return False
536
537 if includeDeleted:
538 - # Look for page directory, ignore page state
539 - if domain is None:
540 - checklist = [0, 1]
541 - else:
542 - checklist = [domain == 'underlay']
543 - for use_underlay in checklist:
544 - pagedir = self.getPagePath(use_underlay=use_underlay, check_create=0)
545 - if os.path.exists(pagedir):
546 - return True
547 - return False
548 + page_dir = self._find_page()
549 else:
550 - # Look for non-deleted pages only, using get_rev
551 - if not rev and self.rev:
552 - rev = self.rev
553 + page_dir = self._find_current_page(rev)
554
555 - if domain is None:
556 - use_underlay = -1
557 - else:
558 - use_underlay = domain == 'underlay'
559 - d, d, exists = self.get_rev(use_underlay, rev)
560 - return exists
561 + if page_dir:
562 + return True
563 + return False
564
565 def size(self, rev=0):
566 """ Get Page size.
567 @@ -626,7 +668,8 @@
568
569 return count
570
571 - def getPageList(self, user=None, exists=1, filter=None):
572 + def getPageList(self, user=None, exists=1, filter=None,
573 + return_objects=False):
574 """ List user readable pages under current page
575
576 Currently only request.rootpage is used to list pages, but if we
577 @@ -652,6 +695,8 @@
578 @param user: the user requesting the pages (MoinMoin.user.User)
579 @param filter: filter function
580 @param exists: filter existing pages
581 + @param return_objects: lets it return a list of Page objects instead of
582 + names
583 @rtype: list of unicode strings
584 @return: user readable wiki page names
585 """
586 @@ -683,15 +728,20 @@
587 if filter and not filter(name):
588 continue
589
590 + page = Page(request, name)
591 +
592 # Filter deleted pages
593 - if exists and not Page(request, name).exists():
594 + if exists and not page.exists():
595 continue
596
597 # Filter out page user may not read.
598 if user and not user.may.read(name):
599 continue
600
601 - pages.append(name)
602 + if return_objects:
603 + pages.append(page)
604 + else:
605 + pages.append(name)
606 else:
607 pages = cache.keys()
608
609 @@ -726,16 +776,12 @@
610 @rtype: dict
611 @return: dict of page names using file system encoding
612 """
613 - # Get pages in standard dir
614 - path = self.getPagePath('pages')
615 - pages = self._listPageInPath(path)
616
617 - if self.cfg.data_underlay_dir is not None:
618 - # Merge with pages from underlay
619 - path = self.getPagePath('pages', use_underlay=1)
620 - underlay = self._listPageInPath(path)
621 - pages.update(underlay)
622 -
623 + pages = {}
624 + for dir in self.cfg.page_data_dirs:
625 + path = os.path.join( dir, 'pages' )
626 + more_pages = self._listPageInPath(path)
627 + pages.update(more_pages)
628 return pages
629
630 def _listPageInPath(self, path):
631 @@ -1171,7 +1235,7 @@
632 wikiutil.url_quote_plus(full_text_query))
633
634 title = self.split_title(request)
635 - if self.rev:
636 + if self.rev and self.rev != 99999999:
637 msg = "<strong>%s</strong><br>%s" % (
638 _('Revision %(rev)d as of %(date)s') % {
639 'rev': self.rev,
640 @@ -1292,7 +1363,7 @@
641 @rtype: string
642 @return: formatter name as used in caching
643 """
644 - if not hasattr(self, 'formatter'):
645 + if not hasattr(self, 'formatter') or self.formatter is None:
646 return ''
647 module = self.formatter.__module__
648 return module[module.rfind('.') + 1:]
649 @@ -1377,10 +1448,10 @@
650 def loadCache(self, request):
651 """ Return page content cache or raises 'CacheNeedsUpdate' """
652 cache = caching.CacheEntry(request, self, self.getFormatterName())
653 - attachmentsPath = self.getPagePath('attachments', check_create=0)
654 + attachmentsPath = os.path.join( self.getPageBasePath(), 'attachments')
655 if cache.needsUpdate(self._text_filename(), attachmentsPath):
656 raise Exception('CacheNeedsUpdate')
657 -
658 +
659 import marshal
660 try:
661 return marshal.loads(cache.content())
662 @@ -1443,7 +1514,7 @@
663 import dircache
664 revisions = []
665 if self.page_name:
666 - rev_dir = self.getPagePath('revisions', check_create=0)
667 + rev_dir = os.path.join( self.getPageBasePath(), 'revisions')
668 if os.path.isdir(rev_dir):
669 for rev in dircache.listdir(rev_dir):
670 try:
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.