2 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
3 # This module is free software, and you may redistribute it and/or modify
4 # under the same terms as Python, so long as this copyright message and
5 # disclaimer are retained in their original form.
7 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
8 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
9 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
10 # POSSIBILITY OF SUCH DAMAGE.
12 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
19 """Tracker handling (open tracker).
21 Backwards compatibility for the old-style "imported" trackers.
23 __docformat__
= 'restructuredtext'
27 from roundup
import configuration
, mailgw
28 from roundup
import hyperdb
, backends
, actions
29 from roundup
.cgi
import client
, templating
30 from roundup
.cgi
import actions
as cgi_actions
33 def __init__(self
, vars):
34 self
.__dict__
.update(vars)
37 def __init__(self
, tracker_home
, optimize
=0):
38 """New-style tracker instance constructor
42 tracker home directory
44 if set, precompile html templates
47 self
.tracker_home
= tracker_home
48 self
.optimize
= optimize
49 # if set, call schema_hook after executing schema.py will get
50 # same variables (in particular db) as schema.py main purpose is
51 # for regression tests
52 self
.schema_hook
= None
53 self
.config
= configuration
.CoreConfig(tracker_home
)
56 self
.templating_utils
= {}
57 self
.load_interfaces()
58 self
.templates
= templating
.Templates(self
.config
["TEMPLATES"])
59 self
.backend
= backends
.get_backend(self
.get_backend_name())
61 libdir
= os
.path
.join(self
.tracker_home
, 'lib')
62 if os
.path
.isdir(libdir
):
63 sys
.path
.insert(1, libdir
)
64 self
.templates
.precompileTemplates()
65 # initialize tracker extensions
66 for extension
in self
.get_extensions('extensions'):
68 # load database schema
69 schemafilename
= os
.path
.join(self
.tracker_home
, 'schema.py')
70 # Note: can't use built-in open()
71 # because of the global function with the same name
72 schemafile
= file(schemafilename
, 'rt')
73 self
.schema
= compile(schemafile
.read(), schemafilename
, 'exec')
75 # load database detectors
76 self
.detectors
= self
.get_extensions('detectors')
77 # db_open is set to True after first open()
79 if libdir
in sys
.path
:
80 sys
.path
.remove(libdir
)
82 def get_backend_name(self
):
83 o
= __builtins__
['open']
84 f
= o(os
.path
.join(self
.config
.DATABASE
, 'backend_name'))
85 name
= f
.readline().strip()
89 def open(self
, name
=None):
90 # load the database schema
91 # we cannot skip this part even if self.optimize is set
92 # because the schema has security settings that must be
93 # applied to each database instance
94 backend
= self
.backend
96 'Class': backend
.Class
,
97 'FileClass': backend
.FileClass
,
98 'IssueClass': backend
.IssueClass
,
99 'String': hyperdb
.String
,
100 'Password': hyperdb
.Password
,
101 'Date': hyperdb
.Date
,
102 'Link': hyperdb
.Link
,
103 'Multilink': hyperdb
.Multilink
,
104 'Interval': hyperdb
.Interval
,
105 'Boolean': hyperdb
.Boolean
,
106 'Number': hyperdb
.Number
,
107 'db': backend
.Database(self
.config
, name
)
111 # execute preloaded schema object
112 exec(self
.schema
, vars)
113 if callable (self
.schema_hook
):
114 self
.schema_hook(**vars)
115 # use preloaded detectors
116 detectors
= self
.detectors
118 libdir
= os
.path
.join(self
.tracker_home
, 'lib')
119 if os
.path
.isdir(libdir
):
120 sys
.path
.insert(1, libdir
)
121 # execute the schema file
122 self
._load_python('schema.py', vars)
123 if callable (self
.schema_hook
):
124 self
.schema_hook(**vars)
125 # reload extensions and detectors
126 for extension
in self
.get_extensions('extensions'):
128 detectors
= self
.get_extensions('detectors')
129 if libdir
in sys
.path
:
130 sys
.path
.remove(libdir
)
132 # apply the detectors
133 for detector
in detectors
:
135 # if we are running in debug mode
136 # or this is the first time the database is opened,
137 # do database upgrade checks
138 if not (self
.optimize
and self
.db_open
):
139 # As a consistency check, ensure that every link property is
140 # pointing at a defined class. Otherwise, the schema is
141 # internally inconsistent. This is an important safety
142 # measure as it protects against an accidental schema change
143 # dropping a table while there are still links to the table;
144 # once the table has been dropped, there is no way to get it
145 # back, so it is important to drop it only if we are as sure
146 # as possible that it is no longer needed.
147 classes
= db
.getclasses()
148 for classname
in classes
:
149 cl
= db
.getclass(classname
)
150 for propname
, prop
in cl
.getprops().iteritems():
151 if not isinstance(prop
, (hyperdb
.Link
,
154 linkto
= prop
.classname
155 if linkto
not in classes
:
157 ("property %s.%s links to non-existent class %s"
158 % (classname
, propname
, linkto
))
164 def load_interfaces(self
):
165 """load interfaces.py (if any), initialize Client and MailGW attrs"""
167 if os
.path
.isfile(os
.path
.join(self
.tracker_home
, 'interfaces.py')):
168 self
._load_python('interfaces.py', vars)
169 self
.Client
= vars.get('Client', client
.Client
)
170 self
.MailGW
= vars.get('MailGW', mailgw
.MailGW
)
172 def get_extensions(self
, dirname
):
173 """Load python extensions
177 extension directory name relative to tracker home
180 list of init() functions for each extension
184 dirpath
= os
.path
.join(self
.tracker_home
, dirname
)
185 if os
.path
.isdir(dirpath
):
186 sys
.path
.insert(1, dirpath
)
187 for name
in os
.listdir(dirpath
):
188 if not name
.endswith('.py'):
191 self
._load_python(os
.path
.join(dirname
, name
), vars)
192 extensions
.append(vars['init'])
193 sys
.path
.remove(dirpath
)
196 def init(self
, adminpw
):
197 db
= self
.open('admin')
198 self
._load_python('initial_data.py', {'db': db
, 'adminpw': adminpw
,
199 'admin_email': self
.config
['ADMIN_EMAIL']})
204 return self
.backend
.db_exists(self
.config
)
207 self
.backend
.db_nuke(self
.config
)
209 def _load_python(self
, file, vars):
210 file = os
.path
.join(self
.tracker_home
, file)
214 def registerAction(self
, name
, action
):
216 # The logic here is this:
217 # * if `action` derives from actions.Action,
218 # it is executable as a generic action.
219 # * if, moreover, it also derives from cgi.actions.Bridge,
220 # it may in addition be called via CGI
221 # * in all other cases we register it as a CGI action, without
222 # any check (for backward compatibility).
223 if issubclass(action
, actions
.Action
):
224 self
.actions
[name
] = action
225 if issubclass(action
, cgi_actions
.Bridge
):
226 self
.cgi_actions
[name
] = action
228 self
.cgi_actions
[name
] = action
230 def registerUtil(self
, name
, function
):
231 self
.templating_utils
[name
] = function
233 class TrackerError(Exception):
237 class OldStyleTrackers
:
242 def open(self
, tracker_home
, optimize
=0):
247 tracker home directory
249 if set, precompile html templates
251 Raise ValueError if the tracker home doesn't exist.
255 # sanity check existence of tracker home
256 if not os
.path
.exists(tracker_home
):
257 raise ValueError, 'no such directory: "%s"'%tracker_home
259 # sanity check tracker home contents
260 for reqd
in 'config dbinit select_db interfaces'.split():
261 if not os
.path
.exists(os
.path
.join(tracker_home
, '%s.py'%reqd
)):
262 raise TrackerError
, 'File "%s.py" missing from tracker '\
263 'home "%s"'%(reqd
, tracker_home
)
265 if self
.trackers
.has_key(tracker_home
):
266 return imp
.load_package(self
.trackers
[tracker_home
],
268 # register all available backend modules
269 backends
.list_backends()
270 self
.number
= self
.number
+ 1
271 modname
= '_roundup_tracker_%s'%self
.number
272 self
.trackers
[tracker_home
] = modname
275 tracker
= imp
.load_package(modname
, tracker_home
)
277 # ensure the tracker has all the required bits
278 for required
in 'open init Client MailGW'.split():
279 if not hasattr(tracker
, required
):
280 raise TrackerError
, \
281 'Required tracker attribute "%s" missing'%required
283 # load and apply the config
284 tracker
.config
= configuration
.CoreConfig(tracker_home
)
285 tracker
.dbinit
.config
= tracker
.config
287 tracker
.optimize
= optimize
288 tracker
.templates
= templating
.Templates(tracker
.config
["TEMPLATES"])
290 tracker
.templates
.precompileTemplates()
294 OldStyleTrackers
= OldStyleTrackers()
295 def open(tracker_home
, optimize
=0):
296 if os
.path
.exists(os
.path
.join(tracker_home
, 'dbinit.py')):
297 # user should upgrade...
298 return OldStyleTrackers
.open(tracker_home
, optimize
=optimize
)
300 return Tracker(tracker_home
, optimize
=optimize
)
302 # vim: set filetype=python sts=4 sw=4 et si :