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 (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, webbrowser, 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_%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'salaam.homeunix.com' 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 280 # username is provided through the web interface 281 # password is provided 282 # we need the profile 283 284 """convenience function for compatibility with gmLoginInfo.LoginInfo""" 285 #if not self.cancelled: 286 # FIXME: do not assume conf file is latin1 ! 287 #profile = self.__backend_profiles[self._CBOX_profile.GetValue().encode('latin1').strip()] 288 #self.__backend_profiles = self.__get_backend_profiles() 289 __backend_profiles = __get_backend_profiles() 290 profile = __backend_profiles[backend.encode('utf8').strip()] 291 292 _log.debug(u'backend profile "%s" selected', profile.name) 293 _log.debug(u' details: <%s> on %s@%s:%s (%s, %s)', 294 username, 295 profile.database, 296 profile.host, 297 profile.port, 298 profile.encoding, 299 gmTools.bool2subst(profile.public_db, u'public', u'private') 300 ) 301 #_log.debug(u' helpdesk: "%s"', profile.helpdesk) 302 login = gmLoginInfo.LoginInfo ( 303 user = username, 304 password = password, 305 host = profile.host, 306 database = profile.database, 307 port = profile.port 308 ) 309 #login.public_db = profile.public_db 310 #login.helpdesk = profile.helpdesk 311 return login
312
313 #---------------------------------------------- 314 -def _signal_debugging_monitor(*args, **kwargs):
315 try: 316 kwargs['originated_in_database'] 317 print '==> got notification from database "%s":' % kwargs['signal'] 318 except KeyError: 319 print '==> received signal from client: "%s"' % kwargs['signal'] 320 321 del kwargs['signal'] 322 for key in kwargs.keys(): 323 print ' [%s]: %s' % (key, kwargs[key])
324
325 #================================================================ 326 -class cBackendProfile:
327 pass
328
329 #================================================================ 330 331 -def jsonrpchdl():
332 print "before_handler jsonrpc" 333 # note: wheter req.body is a string or file depends on the content-type! 334 req = cherrypy.request 335 try: 336 size = int(req.headers["Content-Length"]) 337 except: 338 size = 1 339 try: 340 json_string = req.body.read() 341 print "json_string [%s]" % json_string 342 obj = loads(json_string) 343 myparams = {} 344 for key, val in obj.items(): 345 mykey = str(key) 346 myparams[mykey] = val 347 req.params = myparams 348 except: 349 pass
350 cherrypy.tools.jsonrpchdl = cherrypy.Tool('before_handler',jsonrpchdl) 351 352 PYJSDIR = sys._getframe().f_code.co_filename 353 PYJSDIR = os.path.split(os.path.dirname(PYJSDIR))[0] 354 PYJSDIR = os.path.join(PYJSDIR, 'pyjamas') 355 356 DEFAULT_BACKEND = "GNUmed database on this machine (Linux/Mac) (gnumed_v15@)"
357 358 -class gmApp:
359 360 @cherrypy.expose
361 - def default(self,*args):
362 fname = os.path.join(PYJSDIR,args[0]) 363 print "try to return contents of file %s" % fname 364 f = file(fname) 365 s = f.read() 366 return s
367 368 @cherrypy.expose 369 @cherrypy.tools.jsonrpchdl()
370 - def services(self, *args, **kwargs):
371 print "echo service" 372 print args 373 print kwargs 374 method = kwargs['method'] 375 f = getattr(self,method) 376 res = f(*kwargs['params']) 377 return dumps({'id':kwargs['id'],'result':res,'error':None})
378 - def echo(self, text):
379 return text
380 - def reverse(self, text):
381 return text[::-1]
382 - def uppercase(self, text):
383 return text.upper()
384 - def lowercase(self,text):
385 return text.lower()
386
387 - def get_schema_version(self):
389
390 - def get_doc_types(self):
391 res = [] 392 for item in gmDocuments.get_document_types(): 393 res.append(str(item)) 394 return res
395
396 - def doSomething(self):
397 msg = 'schema version is:' + self.get_schema_version() +'\n\n' 398 msg2 ='' 399 for item in gmDocuments.get_document_types(): 400 msg2 = msg2 +'\n' + str(item) 401 msg = msg + msg2 402 return "<pre>%s</pre>" %msg
403
404 - def login(self, username=None, password=None, backend=None):
405 if backend is None: 406 backend = DEFAULT_BACKEND 407 login_info = GetLoginInfo(username, password, backend) 408 override = _cfg.get(option = '--override-schema-check', 409 source_order = [('cli', 'return')]) 410 cb = _cfg.get(option = 'client_branch') 411 expected_version = gmPG2.map_client_branch2required_db_version[cb] 412 connected = connect_to_database ( 413 login_info, 414 expected_version = expected_version, 415 require_version = not override 416 ) 417 return connected
418
419 - def doLogin(self, username=None, password=None, backend=None):
420 login_info = GetLoginInfo(username, password, backend) 421 override = _cfg.get(option = '--override-schema-check', 422 source_order = [('cli', 'return')]) 423 cb = _cfg.get(option = 'client_branch') 424 expected_version = gmPG2.map_client_branch2required_db_version[cb] 425 connected = connect_to_database ( 426 login_info, 427 expected_version = expected_version, 428 require_version = not override 429 ) 430 if connected: 431 msg = self.doSomething() 432 return msg 433 else: 434 return 'something went wrong'
435 436 doLogin.exposed = True 437 438 # ------------------------------------------------------------
439 - def index(self):
440 # backend is hardcoded for now, make it use drop down list later 441 # building the html out of the Cheetah Template 442 t = Template( file="CherryPy/templates/index.tmpl" 443 # a dictionnary containing values that is going to be inserted in the Template 444 , searchList = { 445 "title" : "Welcome to GNUmed - Login" 446 , "cssFiles" : ["css/ext-all.css", "css/xtheme-gray.css"] 447 , "jsFiles" : ["ext/ext-base.js", "ext/ext-core.js"] 448 , "backend" : "GNUmed database on this machine (Linux/Mac) (gnumed_v15@)" 449 } 450 ) 451 return str( t ) # returning a string representation of the Template. CherryPy will only let you return strings with an exposed function
452 453 454 #return """ 455 #<form action="doLogin" method="post"> 456 # <p>Backend</p> 457 # <input type="text" name="backend" value="GNUmed database on this machine (Linux/Mac) (gnumed_v15@)" 458 # size="15" maxlength="40"/> 459 # <p>Username</p> 460 # <input type="text" name="username" value="" 461 # size="15" maxlength="40"/> 462 # <p>Password</p> 463 # <input type="password" name="password" value="" 464 # size="10" maxlength="40"/> 465 # <p><input type="submit" value="Login"/></p> 466 # <p><input type="reset" value="Clear"/></p> 467 #</form> 468 #""" 469 index.exposed = True
470
471 #========================================================== 472 # main - launch the GNUmed web client 473 #---------------------------------------------------------- 474 475 -def main():
476 477 if _cfg.get(option = 'debug'): 478 gmDispatcher.connect(receiver = _signal_debugging_monitor) 479 _log.debug('gmDispatcher signal monitor activated') 480 481 cherrypy.quickstart(gmApp(), "/", 482 {'global':{'server.socket_port':8080,'log.screen':True}})
483