Code

svn repository setup
[roundup.git] / frontends / ZRoundup / ZRoundup.py
1 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
2 # This module is free software, and you may redistribute it and/or modify
3 # under the same terms as Python, so long as this copyright message and
4 # disclaimer are retained in their original form.
5 #
6 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
7 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
8 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
9 # POSSIBILITY OF SUCH DAMAGE.
10 #
11 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
12 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
13 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
14 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
15 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
16
17 # $Id: ZRoundup.py,v 1.23 2008-02-07 01:03:39 richard Exp $
18 #
19 ''' ZRoundup module - exposes the roundup web interface to Zope
21 This frontend works by providing a thin layer that sits between Zope and the
22 regular CGI interface of roundup, providing the web frontend with the minimum
23 of effort.
25 This means that the regular CGI interface does all authentication quite
26 independently of Zope. The roundup code is kept in memory though, and it
27 runs in the same server as all your other Zope stuff, so it does have _some_
28 advantages over regular CGI :)
29 '''
31 import urlparse
33 from Globals import InitializeClass, HTMLFile
34 from OFS.SimpleItem import Item
35 from OFS.PropertyManager import PropertyManager
36 from Acquisition import Explicit, Implicit
37 from Persistence import Persistent
38 from AccessControl import ClassSecurityInfo
39 from AccessControl import ModuleSecurityInfo
40 modulesecurity = ModuleSecurityInfo()
42 import roundup.instance
43 from roundup.cgi import client
45 modulesecurity.declareProtected('View management screens',
46     'manage_addZRoundupForm')
47 manage_addZRoundupForm = HTMLFile('dtml/manage_addZRoundupForm', globals())
49 modulesecurity.declareProtected('Add Z Roundups', 'manage_addZRoundup')
50 def manage_addZRoundup(self, id, instance_home, REQUEST):
51     """Add a ZRoundup product """
52     # validate the instance_home
53     roundup.instance.open(instance_home)
54     self._setObject(id, ZRoundup(id, instance_home))
55     return self.manage_main(self, REQUEST)
57 class RequestWrapper:
58     '''Make the Zope RESPONSE look like a BaseHTTPServer
59     '''
60     def __init__(self, RESPONSE):
61         self.RESPONSE = RESPONSE
62         self.wfile = self.RESPONSE
63     def send_response(self, status):
64         self.RESPONSE.setStatus(status)
65     def send_header(self, header, value):
66         self.RESPONSE.addHeader(header, value)
67     def end_headers(self):
68         # not needed - the RESPONSE object handles this internally on write()
69         pass
70     def start_response(self, headers, response):
71         self.send_response(response)
72         for key, value in headers:
73             self.send_header(key, value)
74         self.end_headers()
76 class FormItem:
77     '''Make a Zope form item look like a cgi.py one
78     '''
79     def __init__(self, value):
80         self.value = value
81         if hasattr(self.value, 'filename'):
82             self.filename = self.value.filename
83             self.value = self.value.read()
85 class FormWrapper:
86     '''Make a Zope form dict look like a cgi.py one
87     '''
88     def __init__(self, form):
89         self.__form = form
90     def __getitem__(self, item):
91         entry = self.__form[item]
92         if isinstance(entry, type([])):
93             entry = map(FormItem, entry)
94         else:
95             entry = FormItem(entry)
96         return entry
97     def __iter__(self):
98         return iter(self.__form)
99     def getvalue(self, key, default=None):
100         if self.__form.has_key(key):
101             return self.__form[key]
102         else:
103             return default
104     def has_key(self, item):
105         return self.__form.has_key(item)
106     def keys(self):
107         return self.__form.keys()
109     def __repr__(self):
110         return '<ZRoundup.FormWrapper %r>'%self.__form
112 class ZRoundup(Item, PropertyManager, Implicit, Persistent):
113     '''An instance of this class provides an interface between Zope and
114        roundup for one roundup instance
115     '''
116     meta_type =  'Z Roundup'
117     security = ClassSecurityInfo()
119     def __init__(self, id, instance_home):
120         self.id = id
121         self.instance_home = instance_home
123     # define the properties that define this object
124     _properties = (
125         {'id':'id', 'type': 'string', 'mode': 'w'},
126         {'id':'instance_home', 'type': 'string', 'mode': 'w'},
127     )
128     property_extensible_schema__ = 0
130     # define the tabs for the management interface
131     manage_options= PropertyManager.manage_options + (
132         {'label': 'View', 'action':'index_html'},
133     ) + Item.manage_options
135     icon = "misc_/ZRoundup/icon"
137     security.declarePrivate('roundup_opendb')
138     def roundup_opendb(self):
139         '''Open the roundup instance database for a transaction.
140         '''
141         tracker = roundup.instance.open(self.instance_home)
142         request = RequestWrapper(self.REQUEST['RESPONSE'])
143         env = self.REQUEST.environ
145         # figure out the path components to set
146         url = urlparse.urlparse( self.absolute_url() )
147         path = url[2]
148         path_components = path.split( '/' )
150         # special case when roundup is '/' in this virtual host,
151         if path == "/" :
152             env['SCRIPT_NAME'] = "/"
153             env['TRACKER_NAME'] = ''
154         else :
155             # all but the last element is the path
156             env['SCRIPT_NAME'] = '/'.join( path_components[:-1] )
157             # the last element is the name
158             env['TRACKER_NAME'] = path_components[-1]
160         form = FormWrapper(self.REQUEST.form)
161         if hasattr(tracker, 'Client'):
162             return tracker.Client(tracker, request, env, form)
163         return client.Client(tracker, request, env, form)
165     security.declareProtected('View', 'index_html')
166     def index_html(self):
167         '''Alias index_html to roundup's index
168         '''
169         # Redirect misdirected requests -- bugs 558867 , 565992
170         # PATH_INFO, as defined by the CGI spec, has the *real* request path
171         orig_path = self.REQUEST.environ['PATH_INFO']
172         if orig_path[-1] != '/' : 
173             url = urlparse.urlparse( self.absolute_url() )
174             url = list( url ) # make mutable
175             url[2] = url[2]+'/' # patch
176             url = urlparse.urlunparse( url ) # reassemble
177             RESPONSE = self.REQUEST.RESPONSE
178             RESPONSE.setStatus( "MovedPermanently" ) # 301
179             RESPONSE.setHeader( "Location" , url )
180             return RESPONSE
182         client = self.roundup_opendb()
183         # fake the path that roundup should use
184         client.split_path = ['index']
185         return client.main()
187     def __getitem__(self, item):
188         '''All other URL accesses are passed throuh to roundup
189         '''
190         return PathElement(self, item).__of__(self)
192 class PathElement(Item, Implicit):
193     def __init__(self, zr, path):
194         self.zr = zr
195         self.path = path
197     def __getitem__(self, item):
198         ''' Get a subitem.
199         '''
200         return PathElement(self.zr, self.path + '/' + item).__of__(self)
202     def index_html(self, REQUEST=None):
203         ''' Actually call through to roundup to handle the request.
204         '''
205         try:
206             client = self.zr.roundup_opendb()
207             # fake the path that roundup should use
208             client.path = self.path
209             # and call roundup to do something 
210             client.main()
211             return ''
212         except client.NotFound:
213             raise 'NotFound', REQUEST.URL
214             pass
215         except:
216             import traceback
217             traceback.print_exc()
218             # all other exceptions in roundup are valid
219             raise
221 InitializeClass(ZRoundup)
222 modulesecurity.apply(globals())
225 # vim: set filetype=python ts=4 sw=4 et si