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

Source Code for Module orm2.ui.ORMMode.ORMMode

  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  # Changelog (orm2) 
 29  # --------------- 
 30  # $Log: ORMMode.py,v $ 
 31  # Revision 1.2  2007/04/15 18:24:41  diedrich 
 32  # Pass a copy of the formdata as form and formdata attribute. 
 33  # 
 34  # Revision 1.1  2006/04/28 09:56:41  diedrich 
 35  # Initial commit 
 36  # 
 37  # 
 38   
 39  # Changelog (orm) 
 40  # --------------- 
 41  # 
 42  # Revision 1.26  2005/05/08 21:03:42  t4w00-diedrich 
 43  # Reverted to old bahaviour in __before_publishing_traverse__ 
 44  # 
 45  # Revision 1.25  2005/05/08 15:59:09  t4w00-diedrich 
 46  # __before_publishing_traverse__ uses __dict__.has_key instead of hasattr() 
 47  # to check whether one of its own methods is called 
 48  # 
 49  # Revision 1.24  2005/02/21 13:10:09  t4w00-diedrich 
 50  # HTTP_USER_AGENT and other client environment variables are passed to 
 51  # the mode now 
 52  # 
 53  # Revision 1.23  2005/01/11 18:12:07  t4w00-diedrich 
 54  # Added HTML PUT support (which just calls the __call__() method. The 
 55  # mode has to differntiate between GET and PUT calls by checking 
 56  # request["REQUEST_METHOD"] 
 57  # 
 58  # Revision 1.22  2004/12/12 18:25:55  t4w00-diedrich 
 59  # Uncought UnicodeException in __before_publishing_traverse__() has 
 60  # caused some trouble 
 61  # 
 62  # Revision 1.21  2004/11/16 16:45:33  t4w00-diedrich 
 63  # Updated to use new zpsycopg_db_conn class instead of the old mechanism 
 64  # 
 65  # Revision 1.20  2004/10/02 16:05:43  t4w00-diedrich 
 66  # New, much improved handling of module modificaton time 
 67  # 
 68  # Revision 1.19  2004/09/05 00:37:59  t4w00-diedrich 
 69  # Rollback the ds *after* it has been used by the mode function. If the 
 70  # mode function has not commited its changes they will be lost. 
 71  # 
 72  # Revision 1.18  2004/08/30 11:06:47  t4w00-diedrich 
 73  # - added Security tab 
 74  # - added "Call ORM mode function" Zope permission 
 75  # 
 76  # Revision 1.17  2004/08/28 20:23:01  t4w00-diedrich 
 77  # Eventually renamed ds() _ds() to avoid naming conflicts. 
 78  # 
 79  # Revision 1.16  2004/08/28 13:54:53  t4w00-diedrich 
 80  # Added processing of path variables. 
 81  # 
 82  # Revision 1.15  2004/08/27 01:46:45  t4w00-diedrich 
 83  # Enable orm logging if in DevelopmentMode 
 84  # 
 85  # Revision 1.14  2004/08/20 14:03:49  t4w00-diedrich 
 86  # A request is assumed to be encoded in Python's defaultencoding instead 
 87  # iso-8859-1 
 88  # 
 89  # Revision 1.13  2004/08/18 12:25:21  t4w00-diedrich 
 90  # - Let the mode parameter overwrite the mode_function_name 
 91  # - Fixed mode_function() 
 92  # 
 93  # Revision 1.12  2004/08/18 10:22:18  t4w00-diedrich 
 94  # Changed error message 
 95  # 
 96  # Revision 1.11  2004/08/03 16:34:51  t4w00-diedrich 
 97  # - __call__() now accepts arbitrary arguments and parameters and passes 
 98  #   them to the mode function as one might expect. This lets one use 
 99  #   mode functions just like External Methods 
100  # - context is passed to the mode function 
101  # 
102  # Revision 1.10  2004/08/03 12:07:19  t4w00-diedrich 
103  # Also cache the Content-Type header. 
104  # 
105  # Revision 1.9  2004/08/03 11:52:25  t4w00-diedrich 
106  # - add header to formdata 
107  # - call __reload__() function even if the modes module has not been 
108  #   modified 
109  # 
110  # Revision 1.8  2004/08/03 10:53:22  t4w00-diedrich 
111  # - Added db_charset parameter 
112  # - Get DPI database connection from Zope database connection object 
113  #   and wrap it in an ORM adapter (currently pgsql only) 
114  # 
115  # Revision 1.7  2004/08/03 09:23:26  t4w00-diedrich 
116  # Added caching support 
117  # 
118  # Revision 1.6  2004/08/03 09:11:33  t4w00-diedrich 
119  # Implemented reloading mechanism 
120  # 
121  # Revision 1.5  2004/08/03 07:39:12  t4w00-diedrich 
122  # Removed index_html() and renamed __call__() to om_exec() which is 
123  # called by a new __call__() function. 
124  # 
125  # Revision 1.4  2004/08/02 23:05:22  t4w00-diedrich 
126  # Both add and edit use the same form. Added App.Manage.Navigation to the 
127  # parent classes to provide ZMI  management form elements. 
128  # 
129  # Revision 1.3  2004/08/01 23:13:39  t4w00-diedrich 
130  # - optimzed session handling 
131  # - optimized performance 
132  # - wrote unicode conversion hack 
133  # 
134  # Revision 1.2  2004/08/01 18:31:55  t4w00-diedrich 
135  # Finished writing the mode function's import and call functionality 
136  # 
137  # Revision 1.1  2004/08/01 15:44:02  t4w00-diedrich 
138  # Initial commit. 
139  # 
140  # 
141  # 
142   
143   
144  # Python 
145  import sys, os 
146  from string import split, join 
147  from types import StringType 
148  import re 
149   
150  # Zope 
151  from Acquisition import Implicit, Explicit, Acquired 
152  from Persistence import Persistent 
153  from AccessControl.Role import RoleManager 
154  from AccessControl import ClassSecurityInfo 
155  from OFS.SimpleItem import Item 
156  from OFS import History 
157  from Globals import MessageDialog, DevelopmentMode 
158  from Products.PageTemplates.PageTemplateFile import PageTemplateFile 
159  from App.Management import Navigation 
160  from OFS.Cache import Cacheable 
161   
162  # orm 
163  import orm 
164  from orm.adapters.pgsql.datasource import zpsycopg_db_conn 
165  import orm.debug 
166   
167  if DevelopmentMode: 
168      orm.debug.debug.verbose = True 
169      orm.debug.sqllog.verbose = True 
170   
171 -class ORMModeException(Exception): pass
172 -class NoSuchModule(ORMModeException): pass
173 -class NoSuchFunction(ORMModeException): pass
174
175 -class _header:
176 """ 177 This is a port of the cgiutils header class to the Zope adapter. 178 You might not want to use it from within Zope, only if you'd like 179 to use you modes module with both Zope and cgiutils. 180 """
181 - def __init__(self, response):
182 self.response = response
183
184 - def set(self, field, value):
185 self.response.setHeader(field, value)
186
187 - def append(self, field, value):
188 self.response.setHeader(field, value)
189 190
191 - def redirect(self, url, **parameters):
192 extra = [] 193 for param, value in parameters.items(): 194 value = url_quote(value) 195 extra.append("%s=%s" % (param, value,) ) 196 197 extra = join(extra, "&") 198 if extra != "": extra = "?" + extra 199 200 content = "0; url=%s%s" % (url, extra, ) 201 202 self.set("refresh", content)
203 204 # see __call__ 205 unwanted_request_keys = { 206 'AUTHENTICATION_PATH' : 0, 'BASE1' : 0, 'BASE2' : 0, 'BASE3' : 0, 207 'BASE4' : 0, 'GATEWAY_INTERFACE' : 0, 208 'HTTP_PRAGMA' : 0, 'PARENTS' : 0, 209 'PATH_INFO' : 0, 'PATH_TRANSLATED' : 0, 'PUBLISHED' : 0, 210 'RESPONSE' : 0, 'SCRIPT_NAME' : 0, 211 'SERVER_NAME' : 0, 'SERVER_PORT' : 0, 'SERVER_PROTOCOL' : 0, 212 'SERVER_SOFTWARE' : 0, 'SERVER_URL' : 0, 'SESSION' : 0, 213 'TraversalRequestNameStack' : 0, 'URL' : 0, 'URL1' : 0, 'URL2' : 0, 214 'URL3':0 } 215 216 charset_re=re.compile(r'text/[0-9a-z]+\s*;\s*charset=([-_0-9a-z]+' + 217 r')(?:(?:\s*;)|\Z)', re.IGNORECASE) 218
219 -def manage_addORMMode(self, id, module_name, mode_function_name, 220 db_connection_name, db_charset, 221 session_on=False, 222 path_variables_on=False, path_variables="", 223 REQUEST=None):
224 """ 225 Add an ORMMode Object to the current Zope instance 226 """ 227 id = str(id) 228 module = str(module_name) 229 mode_function = str(mode_function_name) 230 db_connection = str(db_connection_name) 231 db_charset = str(db_charset) 232 233 if str(session_on) == "on": 234 session_on = True 235 else: 236 session_on = False 237 238 if str(path_variables_on == "on"): 239 path_variables_on = True 240 else: 241 path_variables_on = False 242 243 obj = ORMMode(id, module, mode_function, db_connection, db_charset, 244 session_on, path_variables_on, path_variables) 245 self._setObject(id, obj) 246 247 if REQUEST is not None: 248 return self.manage_main(self, REQUEST)
249 250 manage_addORMModeForm = PageTemplateFile("www/ORMMode.pt", globals()) 251
252 -class ORMMode(Explicit, Item, Persistent, RoleManager, Navigation, Cacheable):
253 """ 254 This object type allows you to call ORM 'mode' functions from within 255 your Zope application. 256 """ 257 258 meta_type = "ORM Mode" 259 manage_main = PageTemplateFile("www/ORMMode.pt", globals()) 260 261 manage_options = ( 262 ( 263 {"label": "Edit", "action": "manage_main"}, 264 {"label": "Test", "action": "test"}, 265 {"label": "Security", "action": "manage_access"} 266 ) + Item.manage_options + Cacheable.manage_options ) 267 268 # The same permissions like External Methods 269 __ac_permissions__ = ( 270 ('View management screens', ('manage_main',)), 271 ('Change External Methods', ('manage_edit',)), 272 ('Call ORM mode function', ('__call__','index_html')), 273 ) 274 275 ZopeTime=Acquired 276 HelpSys=Acquired 277 278 security = ClassSecurityInfo() 279
280 - def __init__(self, id, module_name, mode_function_name, 281 db_connection_name, db_charset, session_on, 282 path_variables_on, path_variables, 283 REQUEST=None):
289
290 - def manage_edit(self, module_name, mode_function_name, 291 db_connection_name, db_charset, session_on=False, 292 path_variables_on=False, path_variables="", 293 REQUEST=None):
294 """ 295 Modify the ORMMode object. 296 """ 297 298 if getattr(self, "_module_name", 299 None) != module_name: 300 self._module_name = module_name 301 self._v_module = None 302 self.module() 303 304 if getattr(self, "_mode_function_name", 305 None) != mode_function_name: 306 self._mode_function_name = mode_function_name 307 self._v_mode_functions = {} 308 self.mode_function() 309 310 self._db_connection_name = db_connection_name 311 self._db_charset = db_charset 312 313 self._session_on = session_on 314 315 self._path_variables_on = path_variables_on 316 self._path_variables = split(path_variables, "/") 317 318 self.ZCacheable_invalidate() 319 320 if REQUEST is not None: 321 message="ORM Mode updated." 322 return self.manage_main(self, REQUEST, manage_tabs_message=message)
323 324
325 - def __call__(self, *args, **kw):
326 """ 327 Call the mode function and do cache management. 328 """ 329 result = self.ZCacheable_get(default=None) 330 331 if result is None: 332 data = self.om_exec(*args, **kw) 333 mime_type = self.REQUEST.RESPONSE.headers.get( 334 "content-type", "text/plain") 335 self.ZCacheable_set(data=(data, mime_type)) 336 else: 337 data, mime_type = result 338 self.REQUEST.RESPONSE.setHeader("Content-Type", mime_type) 339 340 return data
341 342
343 - def om_exec(self, *args, **kw):
344 formdata = kw 345 346 # figure out which charset was used to post the formdata. 347 # This assumes, that the charset of the last REQUEST is the 348 # same as that of the RESPONSE. 349 # The data is actually sent url encoded and Zope converts it 350 # to a string with a specific charset somewhere. I was unable 351 # to figure out where and this is the best thing I came up 352 # with... :-( 353 354 # from ZPublisher/HTTPResponse.py 355 356 encoding = sys.getdefaultencoding() # reasonable default 357 358 # Try to figure out which encoding the request uses 359 if self.REQUEST.RESPONSE.headers.has_key('content-type'): 360 match = charset_re.match( 361 self.REQUEST.RESPONSE.headers['content-type']) 362 if match: 363 encoding = match.group(1) 364 365 # REQUEST contains tons of stuff that has not been passed by the 366 # browser but which needs to be calculated for each request. 367 # This is sorted out here. 368 for key in self.REQUEST.keys(): 369 if unwanted_request_keys.has_key(key): 370 continue 371 else: 372 value = self.REQUEST[key] 373 374 # convert to Unicode using sys.defaultencoding(). 375 # This must be set properly 376 if type(value) == StringType: 377 try: 378 value = unicode(value, encoding) 379 except UnicodeDecodeError: 380 pass 381 382 formdata[key] = value 383 384 385 ds = self._ds() 386 387 # put together the stuff needed by the mode functions 388 formdata["ds"] = ds 389 formdata["base_url"] = self.absolute_url() 390 formdata["request"] = self.REQUEST 391 formdata["response"] = self.REQUEST.RESPONSE 392 formdata["REQUEST"] = self.REQUEST 393 formdata["RESPONSE"] = self.REQUEST.RESPONSE 394 formdata["context"] = self.aq_parent 395 formdata["header"] = _header(self.REQUEST.RESPONSE) 396 formdata["form"] = formdata.copy() 397 formdata["formdata"] = formdata["form"] 398 399 if self.session_on(): 400 formdata["session"] = self.REQUEST.SESSION 401 402 #if self.mode_function_name(): 403 # mode = self.mode_function_name() 404 #else: 405 406 mode = formdata.get("mode", self.mode_function_name()) 407 408 function = self.mode_function(mode) 409 410 ret = function(*args, **formdata) 411 412 # dispose any uncommitted transactions from the current ds 413 # that hace not been commited. 414 if ds is not None: 415 ds.rollback() 416 417 return ret
418
419 - def session_on(self):
420 """ 421 Return True if this adapter provied the mode functions with a 422 session object 423 """ 424 return self._session_on
425
426 - def path_variables_on(self):
427 """ 428 Accessor. 429 """ 430 # use getattr() to ensure backward compatibility 431 # (I'd hate to redo all these objects! ;-) 432 return getattr(self, "_path_variables_on", False)
433
434 - def path_variables(self):
435 """ 436 Accessor. 437 """ 438 # use getattr() to ensure backward compatibility 439 # (I'd hate to redo all these objects! ;-) 440 return join(getattr(self, "_path_variables", []), "/")
441
442 - def module_name(self):
443 """ 444 Accessor. Return the name of the Python module where the 445 mode function resides. 446 """ 447 return self._module_name
448
449 - def mode_function_name(self):
450 """ 451 Accessor. Return the name of the mode function. 452 """ 453 return self._mode_function_name
454
455 - def db_connection_name(self):
456 """ 457 Return the name of the databse connection used for this 458 mode. 459 """ 460 return self._db_connection_name
461
462 - def db_charset(self):
463 """ 464 Accessor. 465 """ 466 return self._db_charset
467
468 - def _ds(self):
469 """ 470 Return an ORM datasource object or None if self._db_connection_name 471 is not set. 472 """ 473 if self._db_connection_name: 474 # FIXME: Currently this only works with PostgreSQL. 475 # We need to figure out a way of telling what kind of 476 # database connection we're dealing with so we know what 477 # kind of orm.datasource we need to create. 478 479 ds = zpsycopg_db_conn(self.aq_parent, 480 self.db_connection_name(), 481 self.db_charset()) 482 return ds 483 else: 484 return None
485
486 - def module(self):
487 """ 488 Return the module object of the modes module 489 """ 490 module = getattr(self, "_v_module", None) 491 if module is None: 492 name = self.module_name() 493 try: 494 module = __import__(name, globals(), locals(), 495 split(name, ".")[1:]) 496 497 # Save the modification time of the file, the module 498 # is loaded from 499 if DevelopmentMode: 500 self._v_module_mtime = self._module_mtime(module) 501 502 except ImportError, e: 503 msg = "Error importing module %s: %s" % (name, str(e)) 504 raise NoSuchModule(msg) 505 506 self._v_module = module 507 else: 508 # If the file the module has been loaded from has been modified 509 # reload the module and reset the mode functions dict. 510 if DevelopmentMode: 511 512 # print >> sys.stderr, "Module modification times", self._v_module_mtime, self._module_mtime(self._v_module) 513 514 if self._v_module_mtime != self._module_mtime(self._v_module): 515 print >> sys.stderr, "reloading ", self._v_module 516 reload(self._v_module) 517 self._v_module_mtime = self._module_mtime(self._v_module) 518 self._v_mode_functions = {} 519 520 # A modes module may provide a special function, 521 # __reload__(), which is called each time we reload the 522 # module. It may then reload modules the mode module 523 # is depending on. Use with care and watch recursion ;-) 524 # 525 # This calles for a generic mechanism to reload modules 526 # on demand i.e. when they have been modified. 527 reload_function = getattr(self._v_module, "__reload__", 528 None) 529 if reload_function is not None: 530 reload_function() 531 532 533 return self._v_module
534
535 - def mode_function(self, name=None):
536 """ 537 Return the mode_function function object 538 """ 539 if name is None: name = self.mode_function_name() 540 541 # Perform a check of the module's source file date if in 542 # development mode 543 if DevelopmentMode: self.module() 544 545 functions_dict = getattr(self, "_v_mode_functions", {}) 546 547 if functions_dict.has_key(name): 548 return functions_dict[name] 549 else: 550 module = self.module() 551 if not module.__dict__.has_key(name): 552 msg = "No Such function %s:" % (name) 553 raise NoSuchFunction(msg) 554 else: 555 functions_dict[name] = module.__dict__[name] 556 557 self._v_mode_functions = functions_dict 558 return functions_dict[name]
559 560
561 - def _module_mtime(self, module):
562 """ 563 Return the modification time of the module's source(!) file. 564 The time is returned as the number of seconds since the epoch. 565 """ 566 module_file = module.__file__ 567 568 # look at the .py file instead of .pyc 569 parts = split(module_file, ".") 570 fname = join(parts[:-1], ".") 571 try: 572 py_file = fname + ".py" 573 py_mtime = os.stat(py_file).st_mtime 574 except OSError: 575 py_mtime = 0 576 577 try: 578 pyc_file = fname + ".pyc" 579 pyc_mtime = os.stat(pyc_file).st_mtime 580 except OSError: 581 pyc_mtime = 0 582 583 return max(py_mtime, pyc_mtime)
584 585
586 - def __before_publishing_traverse__(self, object, REQUEST):
587 if self.path_variables_on(): 588 # if one of our mathods is called ... 589 if len(REQUEST.path) > 0 and \ 590 hasattr(object, REQUEST.path[0]): 591 return 592 else: 593 path = REQUEST.path[:] # a copy of the path. With the current 594 # implementation of Zope this is not 595 # necessary, but just in case the Zope 596 # team changes things... 597 # See: BaseRequest.traverse() method 598 599 for variable in self._path_variables: 600 try: 601 value = path.pop() 602 try: 603 value = unicode(value) 604 except UnicodeDecodeError: 605 try: 606 value = unicode(value, "iso-8859-1") 607 except UnicodeDecodeError: 608 value = unicode(repr(value)) 609 610 REQUEST.set(variable, value) 611 except IndexError: # IndexError: pop from empty list 612 pass 613 614 # Stop traversal of the path 615 REQUEST['TraversalRequestNameStack'] = []
616
617 - def index_html(self, *args, **kw):
618 "Just call self" 619 return self(*args, **kw)
620
621 - def PUT(self, *args, **kw):
622 "Just call self" 623 return self(*args, **kw)
624