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_v17@)"
    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):
    387          return gmPG2.get_schema_version()
    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_v17@)"
    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_v17@)"
    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