Package orm2 :: Package ui :: Package ORMProcedureModule :: Module ORMProcedureModule
[hide private]
[frames] | no frames]

Source Code for Module orm2.ui.ORMProcedureModule.ORMProcedureModule

  1  #!/usr/bin/env python 
  2  # -*- coding: iso-8859-1 -*- 
  3   
  4  ##  This file is part of orm, The Object Relational Membrane Version 2. 
  5  ## 
  6  ##  Copyright 2002-2006 by Diedrich Vorberg <diedrich@tux4web.de> 
  7  ## 
  8  ##  All Rights Reserved 
  9  ## 
 10  ##  For more Information on orm see the README file. 
 11  ## 
 12  ##  This program is free software; you can redistribute it and/or modify 
 13  ##  it under the terms of the GNU General Public License as published by 
 14  ##  the Free Software Foundation; either version 2 of the License, or 
 15  ##  (at your option) any later version. 
 16  ## 
 17  ##  This program is distributed in the hope that it will be useful, 
 18  ##  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 19  ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 20  ##  GNU General Public License for more details. 
 21  ## 
 22  ##  You should have received a copy of the GNU General Public License 
 23  ##  along with this program; if not, write to the Free Software 
 24  ##  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 25  ## 
 26  ##  I have added a copy of the GPL in the file gpl.txt. 
 27  # 
 28  # 
 29  # $Log: ORMProcedureModule.py,v $ 
 30  # Revision 1.13  2006/10/10 15:49:14  diedrich 
 31  # - Opt in procedure parameters. (3) Oink! 
 32  # - Fixed datetime handling. OinkOink! 
 33  # 
 34  # Revision 1.12  2006/10/10 15:21:29  diedrich 
 35  # Opt in procedure parameters. (2) 
 36  # 
 37  # Revision 1.11  2006/10/10 15:19:43  diedrich 
 38  # Opt in procedure parameters. 
 39  # 
 40  # Revision 1.10  2006/10/08 15:27:01  diedrich 
 41  # Made security measures work. 
 42  # 
 43  # Revision 1.9  2006/10/07 22:08:18  diedrich 
 44  # If no param names are provided by a procedure class, all params 
 45  # available in the request will be passed. 
 46  # 
 47  # Revision 1.8  2006/09/04 15:57:49  diedrich 
 48  # The anage_addORMProcedureModule() function now uses a redirect to go 
 49  # to the management screen rather than executing its Zope object and 
 50  # returning the result. 
 51  # 
 52  # Revision 1.7  2006/07/08 17:11:08  diedrich 
 53  # Errors on importing modules are printed to sys.stderr now 
 54  # 
 55  # Revision 1.6  2006/07/04 22:49:47  diedrich 
 56  # Fixed(?) rollback behaviour 
 57  # 
 58  # Revision 1.5  2006/06/12 08:16:11  diedrich 
 59  # rollback the ds before running a procedure 
 60  # 
 61  # Revision 1.4  2006/06/11 23:47:50  diedrich 
 62  # - Fixed param handling 
 63  # - Fixed procedure_url()'s param handling 
 64  # 
 65  # Revision 1.3  2006/06/10 18:06:53  diedrich 
 66  # - Be folderish 
 67  # - Better error handling 
 68  # 
 69  # Revision 1.2  2006/06/09 21:52:33  diedrich 
 70  # Fixed bug in error handling of the code that loads modules. 
 71  # 
 72  # Revision 1.1  2006/06/09 15:37:39  diedrich 
 73  # Initial commit 
 74  # 
 75  # 
 76   
 77   
 78   
 79  # Python 
 80  import sys, os, re, imp, urllib 
 81  from string import split, join 
 82  from types import * 
 83  from cStringIO import StringIO 
 84  from traceback import print_tb 
 85   
 86  # Zope 
 87  from Acquisition import Implicit, Acquired 
 88  from Persistence import Persistent 
 89  from AccessControl.Role import RoleManager 
 90  from AccessControl import ClassSecurityInfo, getSecurityManager 
 91  from AccessControl.unauthorized import Unauthorized 
 92  from OFS.SimpleItem import Item 
 93  from Globals import MessageDialog, DevelopmentMode 
 94  from Products.PageTemplates.PageTemplateFile import PageTemplateFile 
 95  from App.Management import Navigation 
 96  from OFS.Cache import Cacheable 
 97  from OFS.Folder import Folder 
 98   
 99  # orm 
100  from orm2.adapters.pgsql.datasource import zpsycopg_db_conn 
101  import orm2.debug 
102  from orm2.ui.procedure import procedure as orm2_ui_procedure 
103   
104  if DevelopmentMode: 
105      orm2.debug.debug.verbose = True 
106      orm2.debug.sqllog.verbose = True 
107   
108 -class ORMModeException(Exception): pass
109 -class NoSuchModule(ORMModeException): pass
110 -class NoSuchProcedure(ORMModeException): pass
111 112 # see __call__ 113 unwanted_request_keys = { 114 'AUTHENTICATION_PATH' : 0, 'BASE1' : 0, 'BASE2' : 0, 'BASE3' : 0, 115 'BASE4' : 0, 'GATEWAY_INTERFACE' : 0, 116 'HTTP_PRAGMA' : 0, 'PARENTS' : 0, 117 'PATH_INFO' : 0, 'PATH_TRANSLATED' : 0, 'PUBLISHED' : 0, 118 'RESPONSE' : 0, 'SCRIPT_NAME' : 0, 119 'SERVER_NAME' : 0, 'SERVER_PORT' : 0, 'SERVER_PROTOCOL' : 0, 120 'SERVER_SOFTWARE' : 0, 'SERVER_URL' : 0, 'SESSION' : 0, 121 'TraversalRequestNameStack' : 0, 'URL' : 0, 'URL1' : 0, 'URL2' : 0, 122 'URL3':0 } 123 124 charset_re=re.compile(r'text/[0-9a-z]+\s*;\s*charset=([-_0-9a-z]+' + 125 r')(?:(?:\s*;)|\Z)', re.IGNORECASE) 126 127 doc_string_param_re=re.compile(r'@param\s*(.*?)\s*:') 128
129 -def manage_addORMProcedureModule(self, id, 130 model_name, procedures_name, 131 db_connection_name, session_on=False, 132 REQUEST=None):
133 """ 134 Add an ORMMode Object to the current Zope instance 135 """ 136 id = str(id) 137 model_name = str(model_name) 138 procedures_name = str(procedures_name) 139 db_connection = str(db_connection_name) 140 141 if str(session_on) == "on": 142 session_on = True 143 else: 144 session_on = False 145 146 obj = ORMProcedureModule(id, model_name, procedures_name, 147 db_connection_name, REQUEST) 148 self._setObject(id, obj) 149 150 if REQUEST is not None: 151 print self, repr(self.absolute_url()) 152 REQUEST.RESPONSE.redirect(self.absolute_url() + "/%s/manage_form"%id) 153 return None
154 # return self.manage_form(self, REQUEST) 155
156 -class module_container(Persistent):
157 - def __init__(self, name):
158 self.module_name = str(name) 159 self.last_error = None
160
161 - def module(self):
162 if not hasattr(self, "_v_module") or DevelopmentMode: 163 imp.acquire_lock() 164 try: 165 parts = split(self.module_name, ".") 166 167 path = None 168 name = [] 169 for package in parts[:-1]: 170 name.append(package) 171 file, filename, description = imp.find_module(package, 172 path) 173 module = imp.load_module(join(name, "."), 174 file, filename, 175 description) 176 path = module.__path__ 177 178 file, filename, description = imp.find_module(parts[-1], 179 path) 180 181 self._v_module = imp.load_module(self.module_name, file, 182 filename, description) 183 184 sys.modules[self.module_name] = self._v_module 185 self.last_error = [] 186 187 except Exception, e: 188 self._v_module = None 189 exception, desc, traceback = sys.exc_info() 190 f = StringIO() 191 print_tb(traceback, file=f) 192 traceback = f.getvalue() 193 194 print >> sys.stderr, "-" * 60 195 print traceback 196 print exception 197 print desc 198 print >> sys.stderr, "-" * 60 199 200 # remove the first two lines of the traceback, which always 201 # point to line 140 above. To debug the import mechanism itself 202 # this must probably be commented out. 203 #lines = split(traceback, "\n") 204 #if len(lines) > 2: 205 # traceback = join(lines[2:], "\n") 206 # lines.append("") 207 208 self.last_error = [ exception, desc, traceback, ] 209 #except: 210 # imp.release_lock() 211 # raise 212 213 imp.release_lock() 214 215 if hasattr(self._v_module, "__reload__"): 216 self._v_module.__reload__() 217 218 if hasattr(self._v_module, "__relationships__"): 219 self._v_module.__relationships__(self._v_module) 220 221 return self._v_module
222 223 224 225
226 - def set_name(self, name):
227 self.module_name = name 228 self.last_error = None 229 230 if hasattr(self, "_v_module"): 231 del self._v_module
232 233 234 manage_addORMProcedureModuleForm = PageTemplateFile( 235 "www/ORMProcedureModule.pt", globals()) 236
237 -class ORMProcedureModule(Folder, Implicit, Cacheable, Persistent):
238 """ 239 This object type allows you to call ORM 'mode' functions from within 240 your Zope application. 241 """ 242 243 meta_type = "ORM Procedure Module" 244 manage_form = PageTemplateFile("www/ORMProcedureModule.pt", globals()) 245 246 manage_options = ( 247 ( Folder.manage_options[0], 248 {"label": "Edit", "action": "manage_form"},) + 249 Folder.manage_options[1:] + 250 Cacheable.manage_options) 251 252 # The same permissions like External Methods 253 __ac_permissions__ = ( 254 ('View management screens', ('manage_form',)), 255 ('Change External Methods', ('manage_edit',)), 256 ('Call ORM mode function', ('__call__',)),) 257 258 ZopeTime=Acquired 259 HelpSys=Acquired 260 261 security = ClassSecurityInfo() 262
263 - def __init__(self, id, model_name, procedures_name, 264 db_connection_name, session_on, 265 REQUEST=None):
269
270 - def manage_edit(self, model_name, procedures_name, 271 db_connection_name, session_on, 272 REQUEST=None):
273 """ 274 Modify the ORMMode object. 275 """ 276 277 self._model = module_container(model_name) 278 self._procedures = module_container(procedures_name) 279 280 self._model.module() # To trigger any error message load the module 281 self._procedures.module() 282 283 self._db_connection_name = str(db_connection_name) 284 self._session_on = str(session_on) 285 286 if REQUEST is not None: 287 message="ORM Procedure Module updated." 288 return self.manage_form(self, REQUEST, manage_tabs_message=message)
289 290
291 - def __call__(self, *args, **kw):
292 """ 293 Call the mode function and do cache management. 294 """ 295 security=getSecurityManager() 296 user = security.getUser() 297 if not user.has_permission("View", self): 298 raise Unauthorized() 299 300 result = self.ZCacheable_get(default=None) 301 302 if result is None: 303 data = self.om_exec(*args, **kw) 304 mime_type = self.REQUEST.RESPONSE.headers.get( 305 "content-type", "text/plain") 306 self.ZCacheable_set(data=(data, mime_type)) 307 else: 308 data, mime_type = result 309 self.REQUEST.RESPONSE.setHeader("Content-Type", mime_type) 310 311 return data
312 313
314 - def om_exec(self, *args, **kw):
315 formdata = kw 316 317 # figure out which charset was used to post the formdata. 318 # This assumes, that the charset of the last REQUEST is the 319 # same as that of the RESPONSE. 320 # The data is actually sent url encoded and Zope converts it 321 # to a string with a specific charset somewhere. I was unable 322 # to figure out where and this is the best thing I came up 323 # with... :-( 324 325 # from ZPublisher/HTTPResponse.py 326 327 encoding = sys.getdefaultencoding() # reasonable default 328 329 # Try to figure out which encoding the request uses 330 if self.REQUEST.RESPONSE.headers.has_key('content-type'): 331 match = charset_re.match( 332 self.REQUEST.RESPONSE.headers['content-type']) 333 if match: 334 encoding = match.group(1) 335 336 # REQUEST contains tons of stuff that has not been passed by the 337 # browser but which needs to be calculated for each request. 338 # This is sorted out here. 339 for key in self.REQUEST.keys(): 340 if unwanted_request_keys.has_key(key): 341 continue 342 else: 343 value = self.REQUEST[key] 344 345 # convert to Unicode 346 if type(value) == StringType: 347 try: 348 value = unicode(value, encoding) 349 except UnicodeDecodeError: 350 pass 351 352 formdata[key] = value 353 354 355 ds = self._ds() 356 357 # put together the stuff needed by the mode functions 358 formdata["ds"] = ds 359 formdata["base_url"] = self.absolute_url() 360 formdata["request"] = self.REQUEST 361 formdata["response"] = self.REQUEST.RESPONSE 362 formdata["REQUEST"] = self.REQUEST 363 formdata["RESPONSE"] = self.REQUEST.RESPONSE 364 formdata["context"] = self 365 366 if self.session_on(): 367 formdata["session"] = self.REQUEST.SESSION 368 369 #if self.mode_function_name(): 370 # mode = self.mode_function_name() 371 #else: 372 373 model = self._model.module() 374 formdata["model"] = model 375 376 procedure_class = self._procedure_class(formdata) 377 378 if procedure_class is None: 379 raise Exception("Illegal method name!") 380 381 procedure_instance = procedure_class(self, formdata) 382 383 if hasattr(procedure_class, "__call_param_names__"): 384 param_names = procedure_class.__call_param_names__ 385 elif procedure_instance.__call__.__doc__ is not None: 386 doc = procedure_instance.__call__.__doc__ 387 param_names = doc_string_param_re.findall(doc) 388 procedure_class.__call_param_names__ = param_names 389 else: 390 param_names = [] 391 392 kw = {} 393 if param_names != []: 394 for param_name in param_names: 395 if formdata.has_key(param_name): 396 kw[param_name] = formdata[param_name] 397 398 try: 399 ret = procedure_instance(**kw) 400 except: 401 if ds is not None: ds.rollback() 402 raise 403 404 # dispose any uncommitted transactions from the current ds 405 # that have not been commited. 406 if ds is not None: ds.rollback() 407 408 return ret
409
410 - def _procedure_class(self, formdata):
411 return formdata.get("orm_procedure_class", None)
412
413 - def session_on(self):
414 """ 415 Return True if this adapter provied the mode functions with a 416 session object 417 """ 418 return self._session_on
419
420 - def model_name(self):
421 """ 422 Accessor. Return the name of the Python module where the 423 mode function resides. 424 """ 425 return self._model.module_name
426
427 - def model_error(self):
428 """ 429 Return the exception text of the last error loading the model module, 430 otherwise an empty string. 431 """ 432 return self._model.last_error
433
434 - def procedures_name(self):
435 """ 436 Accessor. Return the name of the Python module with the 437 procedures in it. 438 """ 439 return self._procedures.module_name
440
441 - def procedures_error(self):
442 """ 443 Return the exception text of the last error loading the procedures 444 module, otherwise an empty string. 445 """ 446 return self._procedures.last_error
447
448 - def db_connection_name(self):
449 """ 450 Return the name of the databse connection used for this 451 mode. 452 """ 453 return self._db_connection_name
454
455 - def _ds(self):
456 """ 457 Return an ORM datasource object or None if self._db_connection_name 458 is not set. 459 """ 460 if self._db_connection_name: 461 # FIXME: Currently this only works with PostgreSQL. 462 # We need to figure out a way of telling what kind of 463 # database connection we're dealing with so we know what 464 # kind of orm.datasource we need to create. 465 466 ds = zpsycopg_db_conn(self, self.db_connection_name()) 467 return ds 468 else: 469 return None
470 471
472 - def _module_mtime(self, module):
473 """ 474 Return the modification time of the module's source(!) file. 475 The time is returned as the number of seconds since the epoch. 476 """ 477 module_file = module.__file__ 478 479 # look at the .py file instead of .pyc 480 parts = split(module_file, ".") 481 fname = join(parts[:-1], ".") 482 try: 483 py_file = fname + ".py" 484 py_mtime = os.stat(py_file).st_mtime 485 except OSError: 486 py_mtime = 0 487 488 try: 489 pyc_file = fname + ".pyc" 490 pyc_mtime = os.stat(pyc_file).st_mtime 491 except OSError: 492 pyc_mtime = 0 493 494 return max(py_mtime, pyc_mtime)
495 496
497 - def __before_publishing_traverse__(self, object, REQUEST):
498 if len(REQUEST.path) > 0: 499 path = REQUEST.path 500 else: 501 path = ["index_html"] 502 503 if path[0].startswith("manage_"): 504 return 505 else: 506 procedure_name = path[0] 507 if len(path) > 1: 508 path_info = path[1:] 509 else: 510 path_info = [] 511 512 module = self._procedures.module() 513 if module is None: return 514 515 procedure_class = getattr(module, procedure_name, None) 516 517 if type(procedure_class) == ClassType and \ 518 issubclass(procedure_class, orm2_ui_procedure): 519 REQUEST.set("orm_procedure_class", procedure_class) 520 REQUEST.set("path_info", path_info) 521 522 # Stop traversal of the path 523 REQUEST['TraversalRequestNameStack'] = []
524
525 - def index_html(self, *args, **kw):
526 "Just call self" 527 if not self.REQUEST.has_key("orm_procedure_class"): 528 return self.aq_parent.index_html() 529 else: 530 return self(*args, **kw)
531
532 - def procedure_url(self, procedure_name, **kw):
533 """ 534 Return an absolute_url to this object + procedure_name + params in kw 535 """ 536 if not hasattr(self._procedures.module(), procedure_name): 537 raise Exception("Illegal procedure name: %s" % procedure_name) 538 539 for name, value in kw.items(): 540 if type(value) == UnicodeType: 541 kw[name] = value.encode() 542 543 params = urllib.urlencode(kw) 544 545 if params: 546 params = "?" + params 547 else: 548 params = "" 549 550 url = "%s/%s%s" % ( self.absolute_url(), procedure_name, params, ) 551 552 return url
553