Package Gnumed :: Package CherryPy :: Module gmGuiWeb
[frames] | no frames]

Source Code for Module Gnumed.CherryPy.gmGuiWeb

  1  #!/usr/bin/env python 
  2   
  3  __doc__ = """GNUmed web client launcher. 
  4  """ 
  5  #========================================================== 
  6  # $Source: /home/ncq/Projekte/cvs2git/vcs-mirror/gnumed/gnumed/client/wxpython/gnumed.py,v $ 
  7  # $Id: gnumed.py,v 1.169 2010-01-31 18:20:41 ncq Exp $ 
  8  __version__ = "$Revision: 1 $" 
  9  __author__  = "S. Hilbert <Sebastian.Hilbert@gmx.net>" 
 10  __license__ = "GPL v2 or later (details at http://www.gnu.org)" 
 11   
 12  import cherrypy                         # importing the CherryPy server library 
 13  from Cheetah.Template import Template   # importing the Cheetah Template engine 
 14   
 15  try: 
 16          from json import loads, dumps 
 17  except ImportError: 
 18          from simplejson import loads, dumps 
 19   
 20   
 21   
 22  # stdlib 
 23  import sys, time, os, cPickle, zlib, locale, os.path, datetime as pyDT, shutil, logging, urllib2, re as regex 
 24   
 25  # GNUmed libs 
 26  from Gnumed.pycommon import gmI18N, gmTools, gmDateTime, gmHooks 
 27  from Gnumed.pycommon import gmLoginInfo, gmPG2, gmBackendListener, gmTools, gmCfg2, gmI18N, gmDispatcher 
 28   
 29  from Gnumed.business import gmDocuments 
 30  from Gnumed.CherryPy import gmGuiHelpersWeb  
 31   
 32  #try: 
 33  #       _('dummy-no-need-to-translate-but-make-epydoc-happy') 
 34  #except NameError: 
 35  #       _ = lambda x:x 
 36   
 37  _cfg = gmCfg2.gmCfgData() 
 38  _provider = None 
 39  _scripting_listener = None 
 40   
 41  _log = logging.getLogger('gm.main') 
 42  _log.info(__version__) 
 43  _log.info('web GUI framework') 
44 45 #================================================================ 46 # convenience functions 47 #---------------------------------------------------------------- 48 -def connect_to_database(login_info=None, max_attempts=3, expected_version=None, require_version=True):
49 """Display the login dialog and try to log into the backend. 50 51 - up to max_attempts times 52 - returns True/False 53 """ 54 # force programmer to set a valid expected_version 55 expected_hash = gmPG2.known_schema_hashes[expected_version] 56 client_version = _cfg.get(option = u'client_version') 57 global current_db_name 58 current_db_name = u'gnumed_v%s' % expected_version 59 60 attempt = 0 61 62 while attempt < max_attempts: 63 64 _log.debug('login attempt %s of %s', (attempt+1), max_attempts) 65 66 connected = False 67 68 login = login_info 69 if login is None: 70 _log.info("did not provide a login information") 71 72 # try getting a connection to verify the DSN works 73 dsn = gmPG2.make_psycopg2_dsn ( 74 database = login.database, 75 host = login.host, 76 port = login.port, 77 user = login.user, 78 password = login.password 79 ) 80 try: 81 conn = gmPG2.get_raw_connection(dsn = dsn, verbose = True, readonly = True) 82 connected = True 83 84 except gmPG2.cAuthenticationError, e: 85 attempt += 1 86 _log.error(u"login attempt failed: %s", e) 87 if attempt < max_attempts: 88 if (u'host=127.0.0.1' in (u'%s' % e)) or (u'host=' not in (u'%s' % e)): 89 msg = _( 90 'Unable to connect to database:\n\n' 91 '%s\n\n' 92 "Are you sure you have got a local database installed ?\n" 93 '\n' 94 "Please retry with proper credentials or cancel.\n" 95 '\n' 96 'You may also need to check the PostgreSQL client\n' 97 'authentication configuration in pg_hba.conf. For\n' 98 'details see:\n' 99 '\n' 100 'wiki.gnumed.de/bin/view/Gnumed/ConfigurePostgreSQL' 101 ) 102 else: 103 msg = _( 104 "Unable to connect to database:\n\n" 105 "%s\n\n" 106 "Please retry with proper credentials or cancel.\n" 107 "\n" 108 'You may also need to check the PostgreSQL client\n' 109 'authentication configuration in pg_hba.conf. For\n' 110 'details see:\n' 111 '\n' 112 'wiki.gnumed.de/bin/view/Gnumed/ConfigurePostgreSQL' 113 ) 114 msg = msg % e 115 msg = regex.sub(r'password=[^\s]+', u'password=%s' % gmTools.u_replacement_character, msg) 116 gmGuiHelpersWeb.gm_show_error ( 117 msg, 118 _('Connecting to backend') 119 ) 120 del e 121 continue 122 123 except gmPG2.dbapi.OperationalError, e: 124 _log.error(u"login attempt failed: %s", e) 125 msg = _( 126 "Unable to connect to database:\n\n" 127 "%s\n\n" 128 "Please retry another backend / user / password combination !\n" 129 ) % gmPG2.extract_msg_from_pg_exception(e) 130 msg = regex.sub(r'password=[^\s]+', u'password=%s' % gmTools.u_replacement_character, msg) 131 gmGuiHelpersWeb.gm_show_error ( 132 msg, 133 _('Connecting to backend') 134 ) 135 del e 136 continue 137 138 # connect was successful 139 gmPG2.set_default_login(login = login) 140 #gmPG2.set_default_client_encoding(encoding = dlg.panel.backend_profile.encoding) 141 142 # compatible = gmPG2.database_schema_compatible(version = expected_version) 143 # if compatible or not require_version: 144 #dlg.panel.save_state() 145 # continue 146 147 # if not compatible: 148 # connected_db_version = gmPG2.get_schema_version() 149 # msg = msg_generic % ( 150 # client_version, 151 # connected_db_version, 152 # expected_version, 153 # gmTools.coalesce(login.host, '<localhost>'), 154 # login.database, 155 # login.user 156 # ) 157 158 # if require_version: 159 # gmGuiHelpersWeb.gm_show_error(msg + msg_fail, _('Verifying database version')) 160 # pass 161 #gmGuiHelpersWeb.gm_show_info(msg + msg_override, _('Verifying database version')) 162 163 # # FIXME: make configurable 164 # max_skew = 1 # minutes 165 # if _cfg.get(option = 'debug'): 166 # max_skew = 10 167 # if not gmPG2.sanity_check_time_skew(tolerance = (max_skew * 60)): 168 # if _cfg.get(option = 'debug'): 169 # gmGuiHelpersWeb.gm_show_warning(msg_time_skew_warn % max_skew, _('Verifying database settings')) 170 # else: 171 # gmGuiHelpersWeb.gm_show_error(msg_time_skew_fail % max_skew, _('Verifying database settings')) 172 # continue 173 174 # sanity_level, message = gmPG2.sanity_check_database_settings() 175 # if sanity_level != 0: 176 # gmGuiHelpersWeb.gm_show_error((msg_insanity % message), _('Verifying database settings')) 177 # if sanity_level == 2: 178 # continue 179 180 # gmExceptionHandlingWidgets.set_is_public_database(login.public_db) 181 # gmExceptionHandlingWidgets.set_helpdesk(login.helpdesk) 182 183 listener = gmBackendListener.gmBackendListener(conn = conn) 184 break 185 186 #dlg.Destroy() 187 188 return connected
189
190 #---------------------------------------------------------------------------- 191 #internal helper functions 192 #---------------------------------------------------- 193 -def __get_backend_profiles():
194 """Get server profiles from the configuration files. 195 196 1) from system-wide file 197 2) from user file 198 199 Profiles in the user file which have the same name 200 as a profile in the system file will override the 201 system file. 202 """ 203 # find active profiles 204 src_order = [ 205 (u'explicit', u'extend'), 206 (u'system', u'extend'), 207 (u'user', u'extend'), 208 (u'workbase', u'extend') 209 ] 210 211 profile_names = gmTools.coalesce ( 212 _cfg.get(group = u'backend', option = u'profiles', source_order = src_order), 213 [] 214 ) 215 216 # find data for active profiles 217 src_order = [ 218 (u'explicit', u'return'), 219 (u'workbase', u'return'), 220 (u'user', u'return'), 221 (u'system', u'return') 222 ] 223 224 profiles = {} 225 226 for profile_name in profile_names: 227 # FIXME: once the profile has been found always use the corresponding source ! 228 # FIXME: maybe not or else we cannot override parts of the profile 229 profile = cBackendProfile() 230 profile_section = 'profile %s' % profile_name 231 232 profile.name = profile_name 233 profile.host = gmTools.coalesce(_cfg.get(profile_section, u'host', src_order), u'').strip() 234 port = gmTools.coalesce(_cfg.get(profile_section, u'port', src_order), 5432) 235 try: 236 profile.port = int(port) 237 if profile.port < 1024: 238 raise ValueError('refusing to use priviledged port (< 1024)') 239 except ValueError: 240 _log.warning('invalid port definition: [%s], skipping profile [%s]', port, profile_name) 241 continue 242 profile.database = gmTools.coalesce(_cfg.get(profile_section, u'database', src_order), u'').strip() 243 if profile.database == u'': 244 _log.warning('database name not specified, skipping profile [%s]', profile_name) 245 continue 246 profile.encoding = gmTools.coalesce(_cfg.get(profile_section, u'encoding', src_order), u'UTF8') 247 profile.public_db = bool(_cfg.get(profile_section, u'public/open access', src_order)) 248 profile.helpdesk = _cfg.get(profile_section, u'help desk', src_order) 249 250 label = u'%s (%s@%s)' % (profile_name, profile.database, profile.host) 251 profiles[label] = profile 252 253 # sort out profiles with incompatible database versions if not --debug 254 # NOTE: this essentially hardcodes the database name in production ... 255 if not (_cfg.get(option = 'debug') or current_db_name.endswith('_devel')): 256 profiles2remove = [] 257 for label in profiles: 258 if profiles[label].database != current_db_name: 259 profiles2remove.append(label) 260 for label in profiles2remove: 261 del profiles[label] 262 263 if len(profiles) == 0: 264 host = u'publicdb.gnumed.de' 265 label = u'public GNUmed database (%s@%s)' % (current_db_name, host) 266 profiles[label] = cBackendProfile() 267 profiles[label].name = label 268 profiles[label].host = host 269 profiles[label].port = 5432 270 profiles[label].database = current_db_name 271 profiles[label].encoding = u'UTF8' 272 profiles[label].public_db = True 273 profiles[label].helpdesk = u'http://wiki.gnumed.de' 274 275 return profiles
276
277 # ------------------------------------------------------------ 278 -def GetLoginInfo(username=None, password=None, backend=None ):
279 # username is provided through the web interface 280 # password is provided 281 # we need the profile 282 283 """convenience function for compatibility with gmLoginInfo.LoginInfo""" 284 #if not self.cancelled: 285 # FIXME: do not assume conf file is latin1 ! 286 #profile = self.__backend_profiles[self._CBOX_profile.GetValue().encode('latin1').strip()] 287 #self.__backend_profiles = self.__get_backend_profiles() 288 __backend_profiles = __get_backend_profiles() 289 profile = __backend_profiles[backend.encode('utf8').strip()] 290 291 _log.debug(u'backend profile "%s" selected', profile.name) 292 _log.debug(u' details: <%s> on %s@%s:%s (%s, %s)', 293 username, 294 profile.database, 295 profile.host, 296 profile.port, 297 profile.encoding, 298 gmTools.bool2subst(profile.public_db, u'public', u'private') 299 ) 300 #_log.debug(u' helpdesk: "%s"', profile.helpdesk) 301 login = gmLoginInfo.LoginInfo ( 302 user = username, 303 password = password, 304 host = profile.host, 305 database = profile.database, 306 port = profile.port 307 ) 308 #login.public_db = profile.public_db 309 #login.helpdesk = profile.helpdesk 310 return login
311
312 #---------------------------------------------- 313 -def _signal_debugging_monitor(*args, **kwargs):
314 try: 315 kwargs['originated_in_database'] 316 print '==> got notification from database "%s":' % kwargs['signal'] 317 except KeyError: 318 print '==> received signal from client: "%s"' % kwargs['signal'] 319 320 del kwargs['signal'] 321 for key in kwargs.keys(): 322 print ' [%s]: %s' % (key, kwargs[key])
323
324 #================================================================ 325 -class cBackendProfile:
326 pass
327
328 #================================================================ 329 330 -def jsonrpchdl():
331 print "before_handler jsonrpc" 332 # note: wheter req.body is a string or file depends on the content-type! 333 req = cherrypy.request 334 try: 335 size = int(req.headers["Content-Length"]) 336 except: 337 size = 1 338 try: 339 json_string = req.body.read() 340 print "json_string [%s]" % json_string 341 obj = loads(json_string) 342 myparams = {} 343 for key, val in obj.items(): 344 mykey = str(key) 345 myparams[mykey] = val 346 req.params = myparams 347 except: 348 pass
349 cherrypy.tools.jsonrpchdl = cherrypy.Tool('before_handler',jsonrpchdl) 350 351 PYJSDIR = sys._getframe().f_code.co_filename 352 PYJSDIR = os.path.split(os.path.dirname(PYJSDIR))[0] 353 PYJSDIR = os.path.join(PYJSDIR, 'pyjamas') 354 355 DEFAULT_BACKEND = "GNUmed database on this machine (Linux/Mac) (gnumed_v19@)"
356 357 -class gmApp:
358 359 @cherrypy.expose
360 - def default(self,*args):
361 fname = os.path.join(PYJSDIR,args[0]) 362 print "try to return contents of file %s" % fname 363 f = file(fname) 364 s = f.read() 365 return s
366 367 @cherrypy.expose 368 @cherrypy.tools.jsonrpchdl()
369 - def services(self, *args, **kwargs):
370 print "echo service" 371 print args 372 print kwargs 373 method = kwargs['method'] 374 f = getattr(self,method) 375 res = f(*kwargs['params']) 376 return dumps({'id':kwargs['id'],'result':res,'error':None})
377 - def echo(self, text):
378 return text
379 - def reverse(self, text):
380 return text[::-1]
381 - def uppercase(self, text):
382 return text.upper()
383 - def lowercase(self,text):
384 return text.lower()
385
386 - def get_schema_version(self):
388
389 - def get_doc_types(self):
390 res = [] 391 for item in gmDocuments.get_document_types(): 392 res.append(str(item)) 393 return res
394
395 - def doSomething(self):
396 msg = 'schema version is:' + self.get_schema_version() +'\n\n' 397 msg2 ='' 398 for item in gmDocuments.get_document_types(): 399 msg2 = msg2 +'\n' + str(item) 400 msg = msg + msg2 401 return "<pre>%s</pre>" %msg
402
403 - def login(self, username=None, password=None, backend=None):
404 if backend is None: 405 backend = DEFAULT_BACKEND 406 login_info = GetLoginInfo(username, password, backend) 407 override = _cfg.get(option = '--override-schema-check', 408 source_order = [('cli', 'return')]) 409 cb = _cfg.get(option = 'client_branch') 410 expected_version = gmPG2.map_client_branch2required_db_version[cb] 411 connected = connect_to_database ( 412 login_info, 413 expected_version = expected_version, 414 require_version = not override 415 ) 416 return connected
417
418 - def doLogin(self, username=None, password=None, backend=None):
419 login_info = GetLoginInfo(username, password, backend) 420 override = _cfg.get(option = '--override-schema-check', 421 source_order = [('cli', 'return')]) 422 cb = _cfg.get(option = 'client_branch') 423 expected_version = gmPG2.map_client_branch2required_db_version[cb] 424 connected = connect_to_database ( 425 login_info, 426 expected_version = expected_version, 427 require_version = not override 428 ) 429 if connected: 430 msg = self.doSomething() 431 return msg 432 else: 433 return 'something went wrong'
434 435 doLogin.exposed = True 436 437 # ------------------------------------------------------------
438 - def index(self):
439 # backend is hardcoded for now, make it use drop down list later 440 # building the html out of the Cheetah Template 441 t = Template( file="CherryPy/templates/index.tmpl" 442 # a dictionnary containing values that is going to be inserted in the Template 443 , searchList = { 444 "title" : "Welcome to GNUmed - Login" 445 , "cssFiles" : ["css/ext-all.css", "css/xtheme-gray.css"] 446 , "jsFiles" : ["ext/ext-base.js", "ext/ext-core.js"] 447 , "backend" : "GNUmed database on this machine (Linux/Mac) (gnumed_v19@)" 448 } 449 ) 450 return str( t ) # returning a string representation of the Template. CherryPy will only let you return strings with an exposed function
451 452 453 #return """ 454 #<form action="doLogin" method="post"> 455 # <p>Backend</p> 456 # <input type="text" name="backend" value="GNUmed database on this machine (Linux/Mac) (gnumed_v19@)" 457 # size="15" maxlength="40"/> 458 # <p>Username</p> 459 # <input type="text" name="username" value="" 460 # size="15" maxlength="40"/> 461 # <p>Password</p> 462 # <input type="password" name="password" value="" 463 # size="10" maxlength="40"/> 464 # <p><input type="submit" value="Login"/></p> 465 # <p><input type="reset" value="Clear"/></p> 466 #</form> 467 #""" 468 index.exposed = True
469
470 #========================================================== 471 # main - launch the GNUmed web client 472 #---------------------------------------------------------- 473 474 -def main():
475 476 if _cfg.get(option = 'debug'): 477 gmDispatcher.connect(receiver = _signal_debugging_monitor) 478 _log.debug('gmDispatcher signal monitor activated') 479 480 cherrypy.quickstart(gmApp(), "/", 481 {'global':{'server.socket_port':8080,'log.screen':True}})
482