Annotation of documentViewer/documentViewer.py, revision 1.18
1.18 ! dwinter 1:
! 2:
1.1 dwinter 3: from OFS.Folder import Folder
4: from Products.PageTemplates.ZopePageTemplate import ZopePageTemplate
5: from Products.PageTemplates.PageTemplateFile import PageTemplateFile
6: from AccessControl import ClassSecurityInfo
1.8 casties 7: from AccessControl import getSecurityManager
1.1 dwinter 8: from Globals import package_home
9:
10: from Ft.Xml.Domlette import NonvalidatingReader
11: from Ft.Xml.Domlette import PrettyPrint, Print
1.11 casties 12: from Ft.Xml import EMPTY_NAMESPACE, Parse
1.1 dwinter 13:
14: import Ft.Xml.XPath
15:
16: import os.path
1.7 casties 17: import sys
1.1 dwinter 18: import cgi
19: import urllib
1.3 casties 20: import zLOG
1.18 ! dwinter 21: import urlparse
1.1 dwinter 22:
1.4 casties 23: def getInt(number, default=0):
24: """returns always an int (0 in case of problems)"""
25: try:
26: return int(number)
27: except:
28: return default
29:
1.1 dwinter 30: def getTextFromNode(nodename):
1.18 ! dwinter 31: """get the cdata content of a node"""
1.8 casties 32: if nodename is None:
33: return ""
1.1 dwinter 34: nodelist=nodename.childNodes
35: rc = ""
36: for node in nodelist:
37: if node.nodeType == node.TEXT_NODE:
38: rc = rc + node.data
39: return rc
40:
1.9 casties 41:
42: def getParentDir(path):
43: """returns pathname shortened by one"""
44: return '/'.join(path.split('/')[0:-1])
45:
46:
1.1 dwinter 47: import socket
48:
1.8 casties 49: def urlopen(url,timeout=2):
1.1 dwinter 50: """urlopen mit timeout"""
1.8 casties 51: socket.setdefaulttimeout(timeout)
1.1 dwinter 52: ret=urllib.urlopen(url)
53: socket.setdefaulttimeout(5)
54: return ret
55:
56:
1.3 casties 57: ##
58: ## documentViewer class
59: ##
60: class documentViewer(Folder):
1.1 dwinter 61: """document viewer"""
1.18 ! dwinter 62: textViewerUrl="http://127.0.0.1:8080/HFQP/testXSLT/getPage?"
! 63:
1.1 dwinter 64: meta_type="Document viewer"
65:
66: security=ClassSecurityInfo()
1.3 casties 67: manage_options=Folder.manage_options+(
1.1 dwinter 68: {'label':'main config','action':'changeDocumentViewerForm'},
69: )
70:
1.3 casties 71: # templates and forms
72: viewer_main = PageTemplateFile('zpt/viewer_main', globals())
73: thumbs_main = PageTemplateFile('zpt/thumbs_main', globals())
74: image_main = PageTemplateFile('zpt/image_main', globals())
75: head_main = PageTemplateFile('zpt/head_main', globals())
76: docuviewer_css = PageTemplateFile('css/docuviewer.css', globals())
77:
78: security.declareProtected('View management screens','changeDocumentViewerForm')
79: changeDocumentViewerForm = PageTemplateFile('zpt/changeDocumentViewer', globals())
80:
1.1 dwinter 81:
1.18 ! dwinter 82: def __init__(self,id,imageViewerUrl,textViewerUrl=None,title="",digilibBaseUrl=None,thumbcols=2,thumbrows=10,authgroups="mpiwg"):
1.1 dwinter 83: """init document viewer"""
84: self.id=id
85: self.title=title
86: self.imageViewerUrl=imageViewerUrl
1.18 ! dwinter 87: self.textViewerUrl=textViewerUrl
! 88:
1.4 casties 89: if not digilibBaseUrl:
1.3 casties 90: self.digilibBaseUrl = self.findDigilibUrl()
1.4 casties 91: else:
92: self.digilibBaseUrl = digilibBaseUrl
93: self.thumbcols = thumbcols
94: self.thumbrows = thumbrows
1.8 casties 95: # authgroups is list of authorized groups (delimited by ,)
96: self.authgroups = [s.strip().lower() for s in authgroups.split(',')]
1.3 casties 97: # add template folder so we can always use template.something
98: self.manage_addFolder('template')
99:
100:
101: security.declareProtected('View','index_html')
1.18 ! dwinter 102: def index_html(self,mode,url,viewMode="images",start=None,pn=1):
1.3 casties 103: '''
104: view it
105: @param mode: defines which type of document is behind url
106: @param url: url which contains display information
1.18 ! dwinter 107: @param viewMode: if images display images, if text display text, default is images
! 108:
1.3 casties 109: '''
110:
111: zLOG.LOG("documentViewer (index)", zLOG.INFO, "mode: %s url:%s start:%s pn:%s"%(mode,url,start,pn))
1.1 dwinter 112:
1.3 casties 113: if not hasattr(self, 'template'):
114: # create template folder if it doesn't exist
115: self.manage_addFolder('template')
116:
117: if not self.digilibBaseUrl:
118: self.digilibBaseUrl = self.findDigilibUrl() or "http://nausikaa.mpiwg-berlin.mpg.de/digitallibrary"
119:
1.4 casties 120: docinfo = self.getDocinfo(mode=mode,url=url)
121: pageinfo = self.getPageinfo(start=start,current=pn,docinfo=docinfo)
1.3 casties 122: pt = getattr(self.template, 'viewer_main')
1.18 ! dwinter 123: return pt(docinfo=docinfo,pageinfo=pageinfo,viewMode=viewMode)
1.1 dwinter 124:
125:
1.4 casties 126: def getLink(self,param=None,val=None):
127: """link to documentviewer with parameter param set to val"""
1.9 casties 128: params=self.REQUEST.form.copy()
1.4 casties 129: if param is not None:
1.7 casties 130: if val is None:
131: if params.has_key(param):
132: del params[param]
1.4 casties 133: else:
1.9 casties 134: params[param] = str(val)
1.7 casties 135:
1.9 casties 136: # quote values and assemble into query string
137: ps = "&".join(["%s=%s"%(k,urllib.quote(v)) for (k, v) in params.items()])
138: url=self.REQUEST['URL1']+"?"+ps
1.4 casties 139: return url
140:
141:
1.3 casties 142: def getStyle(self, idx, selected, style=""):
1.4 casties 143: """returns a string with the given style and append 'sel' if path == selected."""
1.3 casties 144: #zLOG.LOG("documentViewer (getstyle)", zLOG.INFO, "idx: %s selected: %s style: %s"%(idx,selected,style))
145: if idx == selected:
146: return style + 'sel'
147: else:
1.9 casties 148: return style
149:
1.2 dwinter 150:
1.9 casties 151: def isAccessible(self, docinfo):
1.8 casties 152: """returns if access to the resource is granted"""
153: access = docinfo.get('accessType', None)
1.14 casties 154: zLOG.LOG("documentViewer (accessOK)", zLOG.INFO, "access type %s"%access)
1.17 casties 155: if access is not None and access == 'free':
1.15 casties 156: zLOG.LOG("documentViewer (accessOK)", zLOG.INFO, "access is free")
1.8 casties 157: return True
1.17 casties 158: elif access is None or access in self.authgroups:
1.9 casties 159: # only local access -- only logged in users
160: user = getSecurityManager().getUser()
161: if user is not None:
162: #print "user: ", user
163: return (user.getUserName() != "Anonymous User")
164: else:
165: return False
1.8 casties 166:
1.9 casties 167: zLOG.LOG("documentViewer (accessOK)", zLOG.INFO, "unknown access type %s"%access)
1.8 casties 168: return False
1.9 casties 169:
1.8 casties 170:
1.7 casties 171: def getDirinfoFromDigilib(self,path,docinfo=None):
1.6 casties 172: """gibt param von dlInfo aus"""
1.13 casties 173: num_retries = 3
1.7 casties 174: if docinfo is None:
175: docinfo = {}
176:
1.13 casties 177: infoUrl=self.digilibBaseUrl+"/dirInfo-xml.jsp?mo=dir&fn="+path
1.6 casties 178:
1.13 casties 179: zLOG.LOG("documentViewer (getparamfromdigilib)", zLOG.INFO, "dirInfo from %s"%(infoUrl))
1.6 casties 180:
1.13 casties 181: for cnt in range(num_retries):
1.9 casties 182: try:
1.13 casties 183: # dom = NonvalidatingReader.parseUri(imageUrl)
184: txt=urllib.urlopen(infoUrl).read()
185: dom = Parse(txt)
1.9 casties 186: break
187: except:
1.13 casties 188: zLOG.LOG("documentViewer (getdirinfofromdigilib)", zLOG.ERROR, "error reading %s (try %d)"%(infoUrl,cnt))
1.9 casties 189: else:
1.13 casties 190: raise IOError("Unable to get dir-info from %s"%(infoUrl))
1.6 casties 191:
1.10 casties 192: sizes=dom.xpath("//dir/size")
193: zLOG.LOG("documentViewer (getparamfromdigilib)", zLOG.INFO, "dirInfo:size"%sizes)
1.6 casties 194:
1.10 casties 195: if sizes:
196: docinfo['numPages'] = int(getTextFromNode(sizes[0]))
1.7 casties 197: else:
198: docinfo['numPages'] = 0
199:
200: return docinfo
1.8 casties 201:
1.6 casties 202:
1.9 casties 203: def getIndexMeta(self, url):
204: """returns dom of index.meta document at url"""
1.12 casties 205: num_retries = 3
1.9 casties 206: dom = None
1.12 casties 207: metaUrl = None
1.9 casties 208: if url.startswith("http://"):
209: # real URL
1.12 casties 210: metaUrl = url
1.9 casties 211: else:
212: # online path
213: server=self.digilibBaseUrl+"/servlet/Texter?fn="
1.13 casties 214: metaUrl=server+url.replace("/mpiwg/online","")
1.9 casties 215: if not metaUrl.endswith("index.meta"):
216: metaUrl += "/index.meta"
1.18 ! dwinter 217: print metaUrl
1.13 casties 218: for cnt in range(num_retries):
1.9 casties 219: try:
1.12 casties 220: # patch dirk encoding fehler treten dann nicht mehr auf
1.11 casties 221: # dom = NonvalidatingReader.parseUri(metaUrl)
1.12 casties 222: txt=urllib.urlopen(metaUrl).read()
223: dom = Parse(txt)
1.13 casties 224: break
1.9 casties 225: except:
1.12 casties 226: zLOG.LOG("ERROR documentViewer (getIndexMata)", zLOG.INFO,"%s (%s)"%sys.exc_info()[0:2])
227:
228: if dom is None:
229: raise IOError("Unable to read index meta from %s"%(url))
1.9 casties 230:
231: return dom
232:
233:
1.8 casties 234: def getAuthinfoFromIndexMeta(self,path,docinfo=None,dom=None):
1.9 casties 235: """gets authorization info from the index.meta file at path or given by dom"""
1.10 casties 236: zLOG.LOG("documentViewer (getauthinfofromindexmeta)", zLOG.INFO,"path: %s"%(path))
1.8 casties 237:
238: access = None
239:
240: if docinfo is None:
241: docinfo = {}
242:
243: if dom is None:
1.9 casties 244: dom = self.getIndexMeta(getParentDir(path))
1.18 ! dwinter 245:
1.8 casties 246: acctype = dom.xpath("//access-conditions/access/@type")
247: if acctype and (len(acctype)>0):
248: access=acctype[0].value
1.9 casties 249: if access in ['group', 'institution']:
1.8 casties 250: access = getTextFromNode(dom.xpath("//access-conditions/access/name")[0]).lower()
251:
252: docinfo['accessType'] = access
253: return docinfo
1.6 casties 254:
1.8 casties 255:
1.3 casties 256: def getBibinfoFromIndexMeta(self,path,docinfo=None,dom=None):
1.9 casties 257: """gets bibliographical info from the index.meta file at path or given by dom"""
1.3 casties 258: zLOG.LOG("documentViewer (getbibinfofromindexmeta)", zLOG.INFO,"path: %s"%(path))
1.2 dwinter 259:
1.3 casties 260: if docinfo is None:
261: docinfo = {}
262:
263: if dom is None:
1.9 casties 264: dom = self.getIndexMeta(getParentDir(path))
265:
1.4 casties 266: metaData=self.metadata.main.meta.bib
267: bibtype=dom.xpath("//bib/@type")
268: if bibtype and (len(bibtype)>0):
269: bibtype=bibtype[0].value
1.2 dwinter 270: else:
1.4 casties 271: bibtype="generic"
272: bibtype=bibtype.replace("-"," ") # wrong typesiin index meta "-" instead of " " (not wrong! ROC)
273: bibmap=metaData.generateMappingForType(bibtype)
1.9 casties 274: #print "bibmap: ", bibmap, " for: ", bibtype
1.8 casties 275: # if there is no mapping bibmap is empty (mapping sometimes has empty fields)
1.7 casties 276: if len(bibmap) > 0 and len(bibmap['author'][0]) > 0:
1.4 casties 277: docinfo['author']=getTextFromNode(dom.xpath("//bib/%s"%bibmap['author'][0])[0])
278: docinfo['title']=getTextFromNode(dom.xpath("//bib/%s"%bibmap['title'][0])[0])
279: docinfo['year']=getTextFromNode(dom.xpath("//bib/%s"%bibmap['year'][0])[0])
1.3 casties 280:
281: return docinfo
282:
283:
1.8 casties 284: def getDocinfoFromTextTool(self,url,dom=None,docinfo=None):
1.3 casties 285: """parse texttool tag in index meta"""
286: zLOG.LOG("documentViewer (getdocinfofromtexttool)", zLOG.INFO,"url: %s"%(url))
287: if docinfo is None:
288: docinfo = {}
289:
1.8 casties 290: if dom is None:
1.9 casties 291: dom = self.getIndexMeta(url)
1.8 casties 292:
1.16 casties 293: archivePath = None
294: archiveName = None
295:
1.8 casties 296: archiveNames=dom.xpath("//resource/name")
297: if archiveNames and (len(archiveNames)>0):
298: archiveName=getTextFromNode(archiveNames[0])
1.16 casties 299: else:
300: zLOG.LOG("documentViewer (getdocinfofromtexttool)", zLOG.WARNING,"resource/name missing in: %s"%(url))
1.3 casties 301:
302: archivePaths=dom.xpath("//resource/archive-path")
303: if archivePaths and (len(archivePaths)>0):
304: archivePath=getTextFromNode(archivePaths[0])
1.8 casties 305: # clean up archive path
306: if archivePath[0] != '/':
307: archivePath = '/' + archivePath
1.16 casties 308: if archiveName and (not archivePath.endswith(archiveName)):
1.8 casties 309: archivePath += "/" + archiveName
1.3 casties 310: else:
1.16 casties 311: # try to get archive-path from url
312: zLOG.LOG("documentViewer (getdocinfofromtexttool)", zLOG.WARNING,"resource/archive-path missing in: %s"%(url))
313: if (not url.startswith('http')):
314: archivePath = url.replace('index.meta', '')
315:
316: if archivePath is None:
317: # we balk without archive-path
318: raise IOError("Missing archive-path (for text-tool) in %s"%(url))
1.3 casties 319:
1.9 casties 320: imageDirs=dom.xpath("//texttool/image")
321: if imageDirs and (len(imageDirs)>0):
322: imageDir=getTextFromNode(imageDirs[0])
1.3 casties 323: else:
1.10 casties 324: # we balk with no image tag
325: raise IOError("No text-tool info in %s"%(url))
1.3 casties 326:
1.9 casties 327: if imageDir and archivePath:
328: #print "image: ", imageDir, " archivepath: ", archivePath
329: imageDir=os.path.join(archivePath,imageDir)
330: imageDir=imageDir.replace("/mpiwg/online",'')
331: docinfo=self.getDirinfoFromDigilib(imageDir,docinfo=docinfo)
332: docinfo['imagePath'] = imageDir
333: docinfo['imageURL'] = self.digilibBaseUrl+"/servlet/Scaler?fn="+imageDir
1.3 casties 334:
335: viewerUrls=dom.xpath("//texttool/digiliburlprefix")
336: if viewerUrls and (len(viewerUrls)>0):
337: viewerUrl=getTextFromNode(viewerUrls[0])
1.7 casties 338: docinfo['viewerURL'] = viewerUrl
1.3 casties 339:
340: textUrls=dom.xpath("//texttool/text")
341: if textUrls and (len(textUrls)>0):
342: textUrl=getTextFromNode(textUrls[0])
1.7 casties 343: docinfo['textURL'] = textUrl
1.3 casties 344:
345: docinfo = self.getBibinfoFromIndexMeta(url,docinfo=docinfo,dom=dom)
1.8 casties 346: docinfo = self.getAuthinfoFromIndexMeta(url,docinfo=docinfo,dom=dom)
1.3 casties 347: return docinfo
348:
349:
350: def getDocinfoFromImagePath(self,path,docinfo=None):
351: """path ist the path to the images it assumes that the index.meta file is one level higher."""
352: zLOG.LOG("documentViewer (getdocinfofromimagepath)", zLOG.INFO,"path: %s"%(path))
353: if docinfo is None:
354: docinfo = {}
1.6 casties 355: path=path.replace("/mpiwg/online","")
1.3 casties 356: docinfo['imagePath'] = path
1.7 casties 357: docinfo=self.getDirinfoFromDigilib(path,docinfo=docinfo)
358: imageUrl=self.digilibBaseUrl+"/servlet/Scaler?fn="+path
1.3 casties 359: docinfo['imageURL'] = imageUrl
360:
361: docinfo = self.getBibinfoFromIndexMeta(path,docinfo=docinfo)
1.8 casties 362: docinfo = self.getAuthinfoFromIndexMeta(path,docinfo=docinfo)
1.3 casties 363: return docinfo
364:
1.2 dwinter 365:
1.3 casties 366: def getDocinfo(self, mode, url):
367: """returns docinfo depending on mode"""
368: zLOG.LOG("documentViewer (getdocinfo)", zLOG.INFO,"mode: %s, url: %s"%(mode,url))
369: # look for cached docinfo in session
370: if self.REQUEST.SESSION.has_key('docinfo'):
371: docinfo = self.REQUEST.SESSION['docinfo']
372: # check if its still current
373: if docinfo is not None and docinfo.get('mode') == mode and docinfo.get('url') == url:
374: zLOG.LOG("documentViewer (getdocinfo)", zLOG.INFO,"docinfo in session: %s"%docinfo)
375: return docinfo
376: # new docinfo
377: docinfo = {'mode': mode, 'url': url}
378: if mode=="texttool": #index.meta with texttool information
379: docinfo = self.getDocinfoFromTextTool(url, docinfo=docinfo)
380: elif mode=="imagepath":
381: docinfo = self.getDocinfoFromImagePath(url, docinfo=docinfo)
382: else:
383: zLOG.LOG("documentViewer (getdocinfo)", zLOG.ERROR,"unknown mode!")
1.10 casties 384: raise ValueError("Unknown mode %s"%(mode))
385:
1.3 casties 386: zLOG.LOG("documentViewer (getdocinfo)", zLOG.INFO,"docinfo: %s"%docinfo)
387: self.REQUEST.SESSION['docinfo'] = docinfo
388: return docinfo
1.2 dwinter 389:
390:
1.4 casties 391: def getPageinfo(self, current, start=None, rows=None, cols=None, docinfo=None):
1.3 casties 392: """returns pageinfo with the given parameters"""
393: pageinfo = {}
1.4 casties 394: current = getInt(current)
395: pageinfo['current'] = current
396: rows = int(rows or self.thumbrows)
397: pageinfo['rows'] = rows
398: cols = int(cols or self.thumbcols)
399: pageinfo['cols'] = cols
400: grpsize = cols * rows
401: pageinfo['groupsize'] = grpsize
402: start = getInt(start, default=(int(current / grpsize) * grpsize +1))
1.3 casties 403: pageinfo['start'] = start
1.4 casties 404: pageinfo['end'] = start + grpsize
405: if docinfo is not None:
406: np = int(docinfo['numPages'])
407: pageinfo['end'] = min(pageinfo['end'], np)
408: pageinfo['numgroups'] = int(np / grpsize)
409: if np % grpsize > 0:
410: pageinfo['numgroups'] += 1
411:
1.3 casties 412: return pageinfo
413:
1.1 dwinter 414: def text(self,mode,url,pn):
415: """give text"""
416: if mode=="texttool": #index.meta with texttool information
417: (viewerUrl,imagepath,textpath)=parseUrlTextTool(url)
418:
1.9 casties 419: #print textpath
1.1 dwinter 420: try:
421: dom = NonvalidatingReader.parseUri(textpath)
422: except:
423: return None
424:
425: list=[]
426: nodes=dom.xpath("//pb")
427:
428: node=nodes[int(pn)-1]
429:
430: p=node
431:
432: while p.tagName!="p":
433: p=p.parentNode
434:
435:
436: endNode=nodes[int(pn)]
437:
438:
439: e=endNode
440:
441: while e.tagName!="p":
442: e=e.parentNode
443:
444:
445: next=node.parentNode
446:
447: #sammle s
448: while next and (next!=endNode.parentNode):
449: list.append(next)
450: next=next.nextSibling
451: list.append(endNode.parentNode)
452:
453: if p==e:# beide im selben paragraphen
1.2 dwinter 454: pass
455: # else:
456: # next=p
457: # while next!=e:
458: # print next,e
459: # list.append(next)
460: # next=next.nextSibling
461: #
462: # for x in list:
463: # PrettyPrint(x)
464: #
465: # return list
1.3 casties 466: #
467:
468: def findDigilibUrl(self):
469: """try to get the digilib URL from zogilib"""
470: url = self.imageViewerUrl[:-1] + "/getScalerUrl"
1.18 ! dwinter 471: print urlparse.urlparse(url)[0]
! 472: print urlparse.urljoin(self.absolute_url(),url)
1.3 casties 473: try:
1.18 ! dwinter 474: if urlparse.urlparse(url)[0]=='': #relative path
! 475: url=urlparse.urljoin(self.absolute_url()+"/",url)
! 476:
1.3 casties 477: scaler = urlopen(url).read()
478: return scaler.replace("/servlet/Scaler?", "")
479: except:
480: return None
481:
1.18 ! dwinter 482: def changeDocumentViewer(self,imageViewerUrl,textViewerUrl,title="",digilibBaseUrl=None,thumbrows=2,thumbcols=10,authgroups='mpiwg',RESPONSE=None):
1.3 casties 483: """init document viewer"""
484: self.title=title
485: self.imageViewerUrl=imageViewerUrl
1.18 ! dwinter 486: self.textViewerUrl=textViewerUrl
1.3 casties 487: self.digilibBaseUrl = digilibBaseUrl
1.4 casties 488: self.thumbrows = thumbrows
489: self.thumbcols = thumbcols
1.8 casties 490: self.authgroups = [s.strip().lower() for s in authgroups.split(',')]
1.3 casties 491: if RESPONSE is not None:
492: RESPONSE.redirect('manage_main')
1.1 dwinter 493:
494:
495:
496:
497: # security.declareProtected('View management screens','renameImageForm')
498:
499: def manage_AddDocumentViewerForm(self):
500: """add the viewer form"""
1.3 casties 501: pt=PageTemplateFile('zpt/addDocumentViewer', globals()).__of__(self)
1.1 dwinter 502: return pt()
503:
1.18 ! dwinter 504: def manage_AddDocumentViewer(self,id,imageViewerUrl="",textViewerUrl="",title="",RESPONSE=None):
1.1 dwinter 505: """add the viewer"""
1.18 ! dwinter 506: newObj=documentViewer(id,imageViewerUrl,title=title,textViewerUrl=textViewerUrl)
1.1 dwinter 507: self._setObject(id,newObj)
508:
509: if RESPONSE is not None:
510: RESPONSE.redirect('manage_main')
1.3 casties 511:
512:
513: ##
514: ## DocumentViewerTemplate class
515: ##
516: class DocumentViewerTemplate(ZopePageTemplate):
517: """Template for document viewer"""
518: meta_type="DocumentViewer Template"
519:
520:
521: def manage_addDocumentViewerTemplateForm(self):
522: """Form for adding"""
523: pt=PageTemplateFile('zpt/addDocumentViewerTemplate', globals()).__of__(self)
524: return pt()
525:
526: def manage_addDocumentViewerTemplate(self, id='viewer_main', title=None, text=None,
527: REQUEST=None, submit=None):
528: "Add a Page Template with optional file content."
529:
530: self._setObject(id, DocumentViewerTemplate(id))
531: ob = getattr(self, id)
532: ob.pt_edit(open(os.path.join(package_home(globals()),'zpt/viewer_main.zpt')).read(),None)
533: if title:
534: ob.pt_setTitle(title)
535: try:
536: u = self.DestinationURL()
537: except AttributeError:
538: u = REQUEST['URL1']
539:
540: u = "%s/%s" % (u, urllib.quote(id))
541: REQUEST.RESPONSE.redirect(u+'/manage_main')
542: return ''
543:
544:
1.14 casties 545:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>