Code

8cd92ab0a3ebb6eb4e69203e3bff56444361bdaf
[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.7 2002-06-14 01:25:46 dman13 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 :)
30 It also means that any requests which specify :filter, :columns or :sort
31 _must_ be done using a GET, so that this interface can re-parse the
32 QUERY_STRING. Zope interprets the ':' as a special character, and the special
33 args are lost to it.
34 '''
36 import urlparse
38 from Globals import InitializeClass, HTMLFile
39 from OFS.SimpleItem import Item
40 from OFS.PropertyManager import PropertyManager
41 from Acquisition import Implicit
42 from Persistence import Persistent
43 from AccessControl import ClassSecurityInfo
44 from AccessControl import ModuleSecurityInfo
45 modulesecurity = ModuleSecurityInfo()
47 import roundup.instance
48 from roundup import cgi_client
50 modulesecurity.declareProtected('View management screens',
51     'manage_addZRoundupForm')
52 manage_addZRoundupForm = HTMLFile('dtml/manage_addZRoundupForm', globals())
54 modulesecurity.declareProtected('Add Z Roundups', 'manage_addZRoundup')
55 def manage_addZRoundup(self, id, instance_home, REQUEST):
56     """Add a ZRoundup product """
57     # validate the instance_home
58     roundup.instance.open(instance_home)
59     self._setObject(id, ZRoundup(id, instance_home))
60     return self.manage_main(self, REQUEST)
62 class RequestWrapper:
63     '''Make the Zope RESPONSE look like a BaseHTTPServer
64     '''
65     def __init__(self, RESPONSE):
66         self.RESPONSE = RESPONSE
67         self.wfile = self.RESPONSE
68     def send_response(self, status):
69         self.RESPONSE.setStatus(status)
70     def send_header(self, header, value):
71         self.RESPONSE.addHeader(header, value)
72     def end_headers(self):
73         # not needed - the RESPONSE object handles this internally on write()
74         pass
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.file = self.value
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         return FormItem(self.form[item])
92     def has_key(self, item):
93         return self.form.has_key(item)
94     def keys(self):
95         return self.form.keys()
97 class ZRoundup(Item, PropertyManager, Implicit, Persistent):
98     '''An instance of this class provides an interface between Zope and
99        roundup for one roundup instance
100     '''
101     meta_type =  'Z Roundup'
102     security = ClassSecurityInfo()
104     def __init__(self, id, instance_home):
105         self.id = id
106         self.instance_home = instance_home
108     # define the properties that define this object
109     _properties = (
110         {'id':'id', 'type': 'string', 'mode': 'w'},
111         {'id':'instance_home', 'type': 'string', 'mode': 'w'},
112     )
113     property_extensible_schema__ = 0
115     # define the tabs for the management interface
116     manage_options= PropertyManager.manage_options + (
117         {'label': 'View', 'action':'index_html'},
118     ) + Item.manage_options
120     icon = "misc_/ZRoundup/icon"
122     security.declarePrivate('_opendb')
123     def _opendb(self):
124         '''Open the roundup instance database for a transaction.
125         '''
126         instance = roundup.instance.open(self.instance_home)
127         request = RequestWrapper(self.REQUEST['RESPONSE'])
128         env = self.REQUEST.environ
130         # figure out the path components to set
131         url = urlparse.urlparse( self.absolute_url() )
132         path_components = url[2].split( '/' )
133                                                 
134         # special case when roundup is '/' in this virtual host,
135         if path == "/" :
136             env['SCRIPT_NAME'] = "/"
137             env['INSTANCE_NAME'] = ''
138         else :
139             # all but the last element is the path
140             env['SCRIPT_NAME'] = '/'.join( path_components[:-1] )
141             # the last element is the name
142             env['INSTANCE_NAME'] = path_components[-1]
144         if env['REQUEST_METHOD'] == 'GET':
145             # force roundup to re-parse the request because Zope fiddles
146             # with it and we lose all the :filter, :columns, etc goodness
147             form = None
148         else:
149             form = FormWrapper(self.REQUEST.form)
150         return instance.Client(instance, request, env, form)
153     security.declareProtected('View', 'index_html')
154     def index_html(self):
155         '''Alias index_html to roundup's index
156         '''
158         # Redirect misdirected requests -- bugs 558867 , 565992
159        
160         # PATH_INFO, as defined by the CGI spec, has the *real* request path
161         orig_path = self.REQUEST.environ[ 'PATH_INFO' ]
162         if orig_path[-1] != '/' : 
163             url = urlparse.urlparse( self.absolute_url() )
164             url = list( url ) # make mutable
165             url[2] = url[2]+'/' # patch
166             url = urlparse.urlunparse( url ) # reassemble
167             RESPONSE = self.REQUEST.RESPONSE
168             RESPONSE.setStatus( "MovedPermanently" ) # 301
169             RESPONSE.setHeader( "Location" , url )
170             return RESPONSE
172         client = self._opendb()
173         # fake the path that roundup should use
174         client.split_path = ['index']
175         return client.main()
177     def __getitem__(self, item):
178         '''All other URL accesses are passed throuh to roundup
179         '''
180         try:
181             client = self._opendb()
182             # fake the path that roundup should use
183             client.split_path = [item]
184             # and call roundup to do something 
185             client.main()
186             return ''
187         except cgi_client.NotFound:
188             raise 'NotFound', self.REQUEST.URL
189             pass
190         except:
191             import traceback
192             traceback.print_exc()
193             # all other exceptions in roundup are valid
194             raise
195         raise KeyError, item
198 InitializeClass(ZRoundup)
199 modulesecurity.apply(globals())
203 # $Log: not supported by cvs2svn $
204 # Revision 1.6  2002/06/12 00:59:44  dman13
205 # Fixed the logic for determing the cookie path.  (Closes #562130.)
207 # Revision 1.5  2002/05/14 23:36:25  richard
208 #  . fixed SCRIPT_NAME in ZRoundup for instances not at top level of Zope
209 #    (thanks dman)
211 # Revision 1.4  2002/01/10 03:38:16  richard
212 # reformatting for 80 cols
214 # Revision 1.3  2001/12/12 23:55:00  richard
215 # Fixed some problems with user editing
217 # Revision 1.2  2001/12/12 23:33:58  richard
218 # added some implementation notes
220 # Revision 1.1  2001/12/12 23:27:13  richard
221 # Added a Zope frontend for roundup.
225 # vim: set filetype=python ts=4 sw=4 et si