Package Gnumed :: Package wxpython :: Module gmAuthWidgets |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmAuthWidgets

      1  # -*- coding: utf8 -*-
      2  """GNUmed authentication widgets.
      3
      4  This module contains widgets and GUI
      5  functions for authenticating users.
      6  """
      7  #================================================================
      8  __author__ = "karsten.hilbert@gmx.net, H.Herb, H.Berger, R.Terry"
      9  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
     10
     11
     12  # stdlib
     13  import sys
     14  import os.path
     15  import logging
     16  import re as regex
     17
     18
     19  # 3rd party
     20  import wx
     21
     22
     23  # GNUmed
     24  if __name__ == '__main__':
     25          sys.path.insert(0, '../../')
     26  from Gnumed.pycommon import gmLoginInfo
     27  from Gnumed.pycommon import gmPG2
     28  from Gnumed.pycommon import gmBackendListener
     29  from Gnumed.pycommon import gmTools
     30  from Gnumed.pycommon import gmCfg2
     31  from Gnumed.pycommon import gmI18N
     32
     33  from Gnumed.business import gmPraxis
     34
     35  from Gnumed.wxpython import gmGuiHelpers
     36  from Gnumed.wxpython import gmExceptionHandlingWidgets
     37
     38
     39  _log = logging.getLogger('gm.ui')
     40  _cfg = gmCfg2.gmCfgData()
     41
     42  try:
     43          _('dummy-no-need-to-translate-but-make-epydoc-happy')
     44  except NameError:
     45          _ = lambda x:x
     46
     47
     48  msg_generic = _("""
     49  GNUmed database version mismatch.
     50
     51  This database version cannot be used with this client:
     52
     53   client version: %s
     54   database version detected: %s
     55   database version needed: %s
     56
     57  Currently connected to database:
     58
     59   host: %s
     60   database: %s
     61   user: %s
     62  """)
     63
     64  msg_time_skew_fail = _("""\
     65  The server and client clocks are off
     66  by more than %s minutes !
     67
     68  You must fix the time settings before
     69  you can use this database with this
     70  client.
     71
     72  You may have to contact your
     73  administrator for help.""")
     74
     75  msg_time_skew_warn = _("""\
     76  The server and client clocks are off
     77  by more than %s minutes !
     78
     79  You should fix the time settings.
     80  Otherwise clinical data may appear to
     81  have been entered at the wrong time.
     82
     83  You may have to contact your
     84  administrator for help.""")
     85
     86  msg_insanity = _("""
     87  There is a serious problem with the database settings:
     88
     89  %s
     90
     91  You may have to contact your administrator for help.""")
     92
     93  msg_fail = _("""
     94  You must connect to a different database in order
     95  to use the GNUmed client. You may have to contact
     96  your administrator for help.""")
     97
     98  msg_override = _("""
     99  The client will, however, continue to start up because
    100  you are running a development/test version of GNUmed.
    101
    102  There may be schema related errors. Please report and/or
    103  fix them. Do not rely on this database to work properly
    104  in all cases !""")
    105
    106  #================================================================
    107  # convenience functions
    108  #----------------------------------------------------------------
    109 -def connect_to_database(max_attempts=3, expected_version=None, require_version=True):
    110          """Display the login dialog and try to log into the backend.
    111
    112          - up to max_attempts times
    113          - returns True/False
    114          """
    115          # force programmer to set a valid expected_version
    116          expected_hash = gmPG2.known_schema_hashes[expected_version]
    117          client_version = _cfg.get(option = u'client_version')
    118          global current_db_name
    119          current_db_name = u'gnumed_v%s' % expected_version
    120
    121          attempt = 0
    122
    123          dlg = cLoginDialog(None, -1, client_version = client_version)
    124          dlg.Centre(wx.BOTH)
    125
    126          while attempt < max_attempts:
    127
    128                  _log.debug('login attempt %s of %s', (attempt+1), max_attempts)
    129
    130                  connected = False
    131
    132                  dlg.ShowModal()
    133                  login = dlg.panel.GetLoginInfo()
    134                  if login is None:
    135                          _log.info("user cancelled login dialog")
    136                          break
    137
    138                  # try getting a connection to verify the DSN works
    139                  dsn = gmPG2.make_psycopg2_dsn (
    140                          database = login.database,
    141                          host = login.host,
    142                          port = login.port,
    143                          user = login.user,
    144                          password = login.password
    145                  )
    146                  try:
    147                          conn = gmPG2.get_raw_connection(dsn = dsn, verbose = True, readonly = True)
    148                          connected = True
    149
    150                  except gmPG2.cAuthenticationError, e:
    151                          attempt += 1
    152                          _log.error(u"login attempt failed: %s", e)
    153                          if attempt < max_attempts:
    154                                  if (u'host=127.0.0.1' in (u'%s' % e)) or (u'host=' not in (u'%s' % e)):
    155                                          msg = _(
    156                                                  'Unable to connect to database:\n\n'
    157                                                  '%s\n\n'
    158                                                  "Are you sure you have got a local database installed ?\n"
    159                                                  '\n'
    160                                                  "Please retry with proper credentials or cancel.\n"
    161                                                  '\n'
    162                                                  ' (for the public and any new GNUmed data-\n'
    163                                                  '  bases the default user name and password\n'
    164                                                  '  are {any-doc, any-doc})\n'
    165                                                  '\n'
    166                                                  'You may also need to check the PostgreSQL client\n'
    167                                                  'authentication configuration in pg_hba.conf. For\n'
    168                                                  'details see:\n'
    169                                                  '\n'
    170                                                  'wiki.gnumed.de/bin/view/Gnumed/ConfigurePostgreSQL'
    171                                          )
    172                                  else:
    173                                          msg = _(
    174                                                  "Unable to connect to database:\n\n"
    175                                                  "%s\n\n"
    176                                                  "Please retry with proper credentials or cancel.\n"
    177                                                  "\n"
    178                                                  "For the public and any new GNUmed databases the\n"
    179                                                  "default user name and password are {any-doc, any-doc}.\n"
    180                                                  "\n"
    181                                                  'You may also need to check the PostgreSQL client\n'
    182                                                  'authentication configuration in pg_hba.conf. For\n'
    183                                                  'details see:\n'
    184                                                  '\n'
    185                                                  'wiki.gnumed.de/bin/view/Gnumed/ConfigurePostgreSQL'
    186                                          )
    187                                  msg = msg % e
    188                                  msg = regex.sub(r'password=[^\s]+', u'password=%s' % gmTools.u_replacement_character, msg)
    189                                  gmGuiHelpers.gm_show_error (
    190                                          msg,
    191                                          _('Connecting to backend')
    192                                  )
    193                          del e
    194                          continue
    195
    196                  except gmPG2.dbapi.OperationalError, e:
    197                          _log.error(u"login attempt failed: %s", e)
    198                          msg = _(
    199                                  "Unable to connect to database:\n\n"
    200                                  "%s\n\n"
    201                                  "Please retry another backend / user / password combination !\n"
    202                                  "\n"
    203                                  " (for the public and any new GNUmed databases\n"
    204                                  "  the default user name and password are\n"
    205                                  "  {any-doc, any-doc})\n"
    206                                  "\n"
    207                          ) % gmPG2.extract_msg_from_pg_exception(e)
    208                          msg = regex.sub(r'password=[^\s]+', u'password=%s' % gmTools.u_replacement_character, msg)
    209                          gmGuiHelpers.gm_show_error (
    210                                  msg,
    211                                  _('Connecting to backend')
    212                          )
    213                          del e
    214                          continue
    215
    216                  # connect was successful
    217                  gmPG2.set_default_login(login = login)
    218                  gmPG2.set_default_client_encoding(encoding = dlg.panel.backend_profile.encoding)
    219
    220                  seems_bootstrapped = gmPG2.schema_exists(schema = 'gm')
    221                  if not seems_bootstrapped:
    222                          _log.error('schema [gm] does not exist - database not bootstrapped ?')
    223                          msg = _(
    224                                  'The database you connected to does not seem\n'
    225                                  'to have been boostrapped properly.\n'
    226                                  '\n'
    227                                  'Make sure you have run the GNUmed database\n'
    228                                  'bootstrapper tool to create a new database.\n'
    229                                  '\n'
    230                                  'Further help can be found on the website at\n'
    231                                  '\n'
    232                                  '  http://wiki.gnumed.de\n'
    233                                  '\n'
    234                                  'or on the GNUmed mailing list.'
    235                          )
    236                          gmGuiHelpers.gm_show_error(msg, _('Verifying database'))
    237                          connected = False
    238                          break
    239
    240                  compatible = gmPG2.database_schema_compatible(version = expected_version)
    241                  if compatible or not require_version:
    242                          dlg.panel.save_state()
    243
    244                  if not compatible:
    245                          connected_db_version = gmPG2.get_schema_version()
    246                          msg = msg_generic % (
    247                                  client_version,
    248                                  connected_db_version,
    249                                  expected_version,
    250                                  gmTools.coalesce(login.host, '<localhost>'),
    251                                  login.database,
    252                                  login.user
    253                          )
    254                          if require_version:
    255                                  gmGuiHelpers.gm_show_error(msg + msg_fail, _('Verifying database version'))
    256                                  connected = False
    257                                  continue
    258                          gmGuiHelpers.gm_show_info(msg + msg_override, _('Verifying database version'))
    259
    260                  # FIXME: make configurable
    261                  max_skew = 1            # minutes
    262                  if _cfg.get(option = 'debug'):
    263                          max_skew = 10
    264                  if not gmPG2.sanity_check_time_skew(tolerance = (max_skew * 60)):
    265                          if _cfg.get(option = 'debug'):
    266                                  gmGuiHelpers.gm_show_warning(msg_time_skew_warn % max_skew, _('Verifying database settings'))
    267                          else:
    268                                  gmGuiHelpers.gm_show_error(msg_time_skew_fail % max_skew, _('Verifying database settings'))
    269                                  connected = False
    270                                  continue
    271
    272                  sanity_level, message = gmPG2.sanity_check_database_settings()
    273                  if sanity_level != 0:
    274                          gmGuiHelpers.gm_show_error((msg_insanity % message), _('Verifying database settings'))
    275                          if sanity_level == 2:
    276                                  connected = False
    277                                  continue
    278
    279                  gmExceptionHandlingWidgets.set_is_public_database(login.public_db)
    280                  gmExceptionHandlingWidgets.set_helpdesk(login.helpdesk)
    281
    282                  listener = gmBackendListener.gmBackendListener(conn = conn)
    283                  break
    284
    285          dlg.Destroy()
    286
    287          return connected
    288  #================================================================
    289 -def get_dbowner_connection(procedure=None, dbo_password=None, dbo_account=u'gm-dbo'):
    290          if procedure is None:
    291                  procedure = _('<restricted procedure>')
    292
    293          # 1) get password for gm-dbo
    294          if dbo_password is None:
    295                  dbo_password = wx.GetPasswordFromUser (
    296                          message = _("""
    297   [%s]
    298
    299  This is a restricted procedure. We need the
    300  current password for the GNUmed database owner.
    301
    302  Please enter the current password for <%s>:""") % (
    303                                  procedure,
    304                                  dbo_account
    305                          ),
    306                          caption = procedure
    307                  )
    308                  if dbo_password == '':
    309                          return None
    310
    311          # 2) connect as gm-dbo
    312          login = gmPG2.get_default_login()
    313          dsn = gmPG2.make_psycopg2_dsn (
    314                  database = login.database,
    315                  host = login.host,
    316                  port = login.port,
    317                  user = dbo_account,
    318                  password = dbo_password
    319          )
    320          try:
    321                  conn = gmPG2.get_connection (
    322                          dsn = dsn,
    323                          readonly = False,
    324                          verbose = True,
    325                          pooled = False
    326                  )
    327          except:
    328                  _log.exception('cannot connect')
    329                  gmGuiHelpers.gm_show_error (
    330                          aMessage = _('Cannot connect as the GNUmed database owner <%s>.') % dbo_account,
    331                          aTitle = procedure
    332                  )
    333                  gmPG2.log_database_access(action = u'failed to connect as database owner for [%s]' % procedure)
    334                  return None
    335
    336          return conn
    337  #================================================================
    338 -def change_gmdbowner_password():
    339
    340          title = _(u'Changing GNUmed database owner password')
    341
    342          dbo_account = wx.GetTextFromUser (
    343                  message = _(u"Enter the account name of the GNUmed database owner:"),
    344                  caption = title,
    345                  default_value = u''
    346          )
    347
    348          if dbo_account.strip() == u'':
    349                  return False
    350
    351          dbo_conn = get_dbowner_connection (
    352                  procedure = title,
    353                  dbo_account = dbo_account
    354          )
    355          if dbo_conn is None:
    356                  return False
    357
    358          dbo_pwd_new_1 = wx.GetPasswordFromUser (
    359                  message = _(u"Enter the NEW password for the GNUmed database owner:"),
    360                  caption = title
    361          )
    362          if dbo_pwd_new_1.strip() == u'':
    363                  return False
    364
    365          dbo_pwd_new_2 = wx.GetPasswordFromUser (
    366                  message = _(u"""Enter the NEW password for the GNUmed database owner, again.
    367
    368  (This will protect you from typos.)
    369                  """),
    370                  caption = title
    371          )
    372          if dbo_pwd_new_2.strip() == u'':
    373                  return False
    374
    375          if dbo_pwd_new_1 != dbo_pwd_new_2:
    376                  return False
    377
    378          cmd = u"""ALTER ROLE "%s" ENCRYPTED PASSWORD '%s';""" % (
    379                  dbo_account,
    380                  dbo_pwd_new_2
    381          )
    382          gmPG2.run_rw_queries(link_obj = dbo_conn, queries = [{'cmd': cmd}], end_tx = True)
    383
    384          return True
    385  #================================================================
    386 -class cBackendProfile:
    387          pass
    388  #================================================================
    389 -class cLoginDialog(wx.Dialog):
    390          """cLoginDialog - window holding cLoginPanel"""
    391
    392 -        def __init__(self, parent, id, title = _("Welcome to"), client_version = u'*** unknown ***'):
    393                  wx.Dialog.__init__(self, parent, id, title)
    394                  self.panel = cLoginPanel(self, -1, isDialog=1, client_version = client_version)
    395                  self.Fit() # needed for Windoze.
    396                  self.Centre()
    397
    398                  self.SetIcon(gmTools.get_icon(wx = wx))
    399  #================================================================
    400 -class cLoginPanel(wx.Panel):
    401          """GUI panel class that interactively gets Postgres login parameters.
    402
    403                  It features combo boxes which "remember" any number of
    404                  previously entered settings.
    405          """
    406 -        def __init__(self, parent, id,
    407                                          pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.TAB_TRAVERSAL,
    408                                          isDialog = 0, client_version = u'*** unknown ***'):
    409                  """Create login panel.
    410
    411                  isDialog:       if this panel is the main panel of a dialog, the panel will
    412                                          resize the dialog automatically to display everything neatly
    413                                          if isDialog is set to True
    414                  """
    415                  wx.Panel.__init__(self, parent, id, pos, size, style)
    416                  self.parent = parent
    417
    418                  #True if dialog was cancelled by user
    419                  #if the dialog is closed manually, login should be cancelled
    420                  self.cancelled = True
    421
    422                  # True if this panel is displayed within a dialog (will resize the dialog automatically then)
    423                  self.isDialog = isDialog
    424
    425                  self.topsizer = wx.BoxSizer(wx.VERTICAL)
    426
    427                  # find bitmap
    428                  paths = gmTools.gmPaths(app_name = u'gnumed', wx = wx)
    429                  bitmap = os.path.join(paths.system_app_data_dir, 'bitmaps', 'gnumedlogo.png')
    430                  try:
    431                          png = wx.Image(bitmap, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
    432                          bmp = wx.StaticBitmap(self, -1, png, wx.Point(10, 10), wx.Size(png.GetWidth(), png.GetHeight()))
    433                          self.topsizer.Add (
    434                                  bmp,
    435                                  proportion = 0,
    436                                  flag = wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL,
    437                                  border = 10
    438                          )
    439                  except:
    440                          self.topsizer.Add (
    441                                  wx.StaticText (
    442                                          self,
    443                                          -1,
    444                                          label = _("Cannot find image") + bitmap,
    445                                          style = wx.ALIGN_CENTRE
    446                                  ),
    447                                  proportion = 0,
    448                                  flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
    449                                  border = 10
    450                          )
    451
    452                  paramsbox_caption = _('Workplace "%s" (version %s)') % (gmPraxis.gmCurrentPraxisBranch().active_workplace, client_version)
    453
    454                  # FIXME: why doesn't this align in the centre ?
    455                  self.paramsbox = wx.StaticBox( self, -1, paramsbox_caption, style = wx.ALIGN_CENTRE_HORIZONTAL)
    456                  self.paramsboxsizer = wx.StaticBoxSizer( self.paramsbox, wx.VERTICAL )
    457                  self.paramsbox.SetForegroundColour(wx.Colour(35, 35, 142))
    458                  self.paramsbox.SetFont(wx.Font(
    459                          pointSize = 12,
    460                          family = wx.SWISS,
    461                          style = wx.NORMAL,
    462                          weight = wx.BOLD,
    463                          underline = False
    464                  ))
    465                  self.pboxgrid = wx.FlexGridSizer(5, 2, 5, 5)
    466                  self.pboxgrid.AddGrowableCol(1)
    467
    468                  # PROFILE COMBO
    469                  label = wx.StaticText( self, -1, _('Log into'), wx.DefaultPosition, wx.DefaultSize, 0)
    470                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    471                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
    472                  self.__backend_profiles = self.__get_backend_profiles()
    473                  self._CBOX_profile = wx.ComboBox (
    474                          self,
    475                          -1,
    476                          self.__backend_profiles.keys()[0],
    477                          wx.DefaultPosition,
    478                          size = wx.Size(550,-1),
    479                          choices = self.__backend_profiles.keys(),
    480                          style = wx.CB_READONLY
    481                  )
    482                  self.pboxgrid.Add (self._CBOX_profile, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    483
    484                  # USER NAME COMBO
    485                  label = wx.StaticText( self, -1, _("Username"), wx.DefaultPosition, wx.DefaultSize, 0 )
    486                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    487                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    488                  self.__previously_used_accounts = self.__get_previously_used_accounts()
    489                  self._CBOX_user = wx.ComboBox (
    490                          self,
    491                          -1,
    492                          self.__previously_used_accounts[0],
    493                          wx.DefaultPosition,
    494                          wx.Size(150,-1),
    495                          self.__previously_used_accounts,
    496                          wx.CB_DROPDOWN
    497                  )
    498                  self.pboxgrid.Add( self._CBOX_user, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    499
    500                  #PASSWORD TEXT ENTRY
    501                  label = wx.StaticText( self, -1, _("Password"), wx.DefaultPosition, wx.DefaultSize, 0 )
    502                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    503                  self.pboxgrid.Add( label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    504                  self.pwdentry = wx.TextCtrl( self, 1, '', wx.DefaultPosition, wx.Size(80,-1), wx.TE_PASSWORD )
    505                  # set focus on password entry
    506                  self.pwdentry.SetFocus()
    507                  self.pboxgrid.Add( self.pwdentry, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    508
    509                  # --debug checkbox
    510                  label = wx.StaticText(self, -1, _('Options'), wx.DefaultPosition, wx.DefaultSize, 0)
    511                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    512                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    513                  self._CHBOX_debug = wx.CheckBox(self, -1, _('&Debug mode'))
    514                  self._CHBOX_debug.SetToolTipString(_('Check this to run GNUmed client in debugging mode.'))
    515                  self.pboxgrid.Add(self._CHBOX_debug, 0, wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
    516
    517                  # --slave checkbox
    518                  label = wx.StaticText(self, -1, '', wx.DefaultPosition, wx.DefaultSize, 0)
    519                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    520                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    521                  self._CHBOX_slave = wx.CheckBox(self, -1, _('Enable &remote control'))
    522                  self._CHBOX_slave.SetToolTipString(_('Check this to run GNUmed client in slave mode for remote control.'))
    523                  self.pboxgrid.Add(self._CHBOX_slave, 0, wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
    524
    525                  #----------------------------------------------------------------------
    526                  #new button code inserted rterry 06Sept02
    527                  #button order re-arraged to make it consistant with usual dialog format
    528                  #in most operating systems ie  btns ok and cancel are standard and
    529                  #in that order
    530                  #ie Order is now help, ok and cancel
    531                  #The order of creation is the tab order
    532                  #login-ok button automatically is the default when tabbing (or <enter>)
    533                  #from password
    534                  #this eliminates the heavy border when you use the default
    535                  #?is the default word needed for any other reason?
    536                  #----------------------------------------------------------------------
    537                  self.button_gridsizer = wx.GridSizer(1,3,0,0)
    538                  #---------------------
    539                  #3:create login ok button
    540                  #---------------------
    541                  ID_BUTTON_LOGIN = wx.NewId()
    542                  button_login_ok = wx.Button(self, ID_BUTTON_LOGIN, _("&Ok"), wx.DefaultPosition, wx.DefaultSize, 0 )
    543                  button_login_ok.SetToolTip(wx.ToolTip(_("Proceed with login.")) )
    544                  button_login_ok.SetDefault()
    545
    546                  #---------------------
    547                  #3:create cancel button
    548                  #---------------------
    549                  ID_BUTTON_CANCEL = wx.NewId()
    550                  button_cancel = wx.Button(self, ID_BUTTON_CANCEL, _("&Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 )
    551                  button_cancel.SetToolTip(wx.ToolTip(_("Cancel Login.")) )
    552                  #---------------------
    553                  #2:create Help button
    554                  #---------------------
    555                  ID_BUTTON_HELP = wx.NewId()
    556                  button_help = wx.Button(self, ID_BUTTON_HELP, _("&Help"), wx.DefaultPosition, wx.DefaultSize, 0 )
    557                  button_help.SetToolTip(wx.ToolTip(_("Help for login screen")))
    558                  #----------------------------
    559                  #Add buttons to the gridsizer
    560                  #----------------------------
    561                  self.button_gridsizer.Add (button_help,0,wx.EXPAND|wx.ALL,5)
    562                  self.button_gridsizer.Add (button_login_ok,0,wx.EXPAND|wx.ALL,5)
    563                  self.button_gridsizer.Add (button_cancel,0,wx.EXPAND|wx.ALL,5)
    564
    565                  self.paramsboxsizer.Add(self.pboxgrid, 1, wx.GROW|wx.ALL, 10)
    566                  self.topsizer.Add(self.paramsboxsizer, 1, wx.GROW|wx.ALL, 10)
    567                  self.topsizer.Add( self.button_gridsizer, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    568
    569                  self.__load_state()
    570
    571                  self.SetAutoLayout(True)
    572                  self.SetSizer( self.topsizer)
    573                  self.topsizer.Fit( self )
    574                  if self.isDialog:
    575                          self.topsizer.SetSizeHints(parent)
    576
    577                  wx.EVT_BUTTON(self, ID_BUTTON_HELP, self.OnHelp)
    578                  wx.EVT_BUTTON(self, ID_BUTTON_LOGIN, self.__on_login_button_pressed)
    579                  wx.EVT_BUTTON(self, ID_BUTTON_CANCEL, self.OnCancel)
    580
    581          #----------------------------------------------------------
    582          # internal helper methods
    583          #----------------------------------------------------------
    584 -        def __get_previously_used_accounts(self):
    585
    586                  accounts = gmTools.coalesce (
    587                          _cfg.get (
    588                                  group = u'backend',
    589                                  option = u'logins',
    590                                  source_order = [
    591                                          (u'explicit', u'extend'),
    592                                          (u'user', u'extend'),
    593                                          (u'workbase', u'extend')
    594                                  ]
    595                          ),
    596                          ['any-doc']
    597                  )
    598                  # FIXME: make unique
    599
    600                  return accounts
    601          #----------------------------------------------------
    602 -        def __get_backend_profiles(self):
    603                  """Get server profiles from the configuration files.
    604
    605                  1) from system-wide file
    606                  2) from user file
    607
    608                  Profiles in the user file which have the same name
    609                  as a profile in the system file will override the
    610                  system file.
    611                  """
    612                  # find active profiles
    613                  src_order = [
    614                          (u'explicit', u'extend'),
    615                          (u'system', u'extend'),
    616                          (u'user', u'extend'),
    617                          (u'workbase', u'extend')
    618                  ]
    619
    620                  profile_names = gmTools.coalesce (
    621                          _cfg.get(group = u'backend', option = u'profiles', source_order = src_order),
    622                          []
    623                  )
    624
    625                  # find data for active profiles
    626                  src_order = [
    627                          (u'explicit', u'return'),
    628                          (u'workbase', u'return'),
    629                          (u'user', u'return'),
    630                          (u'system', u'return')
    631                  ]
    632
    633                  profiles = {}
    634
    635                  for profile_name in profile_names:
    636                          # FIXME: once the profile has been found always use the corresponding source !
    637                          # FIXME: maybe not or else we cannot override parts of the profile
    638                          profile = cBackendProfile()
    639                          profile_section = 'profile %s' % profile_name
    640
    641                          profile.name = profile_name
    642                          profile.host = gmTools.coalesce(_cfg.get(profile_section, u'host', src_order), u'').strip()
    643                          port = gmTools.coalesce(_cfg.get(profile_section, u'port', src_order), 5432)
    644                          try:
    645                                  profile.port = int(port)
    646                                  if profile.port < 1024:
    647                                          raise ValueError('refusing to use priviledged port (< 1024)')
    648                          except ValueError:
    649                                  _log.warning('invalid port definition: [%s], skipping profile [%s]', port, profile_name)
    650                                  continue
    651                          profile.database = gmTools.coalesce(_cfg.get(profile_section, u'database', src_order), u'').strip()
    652                          if profile.database == u'':
    653                                  _log.warning('database name not specified, skipping profile [%s]', profile_name)
    654                                  continue
    655                          profile.encoding = gmTools.coalesce(_cfg.get(profile_section, u'encoding', src_order), u'UTF8')
    656                          profile.public_db = bool(_cfg.get(profile_section, u'public/open access', src_order))
    657                          profile.helpdesk = _cfg.get(profile_section, u'help desk', src_order)
    658
    659                          label = u'%s (%s@%s)' % (profile_name, profile.database, profile.host)
    660                          profiles[label] = profile
    661
    662                  # sort out profiles with incompatible database versions if not --debug
    663                  # NOTE: this essentially hardcodes the database name in production ...
    664                  if not (_cfg.get(option = 'debug') or current_db_name.endswith('_devel')):
    665                          profiles2remove = []
    666                          for label in profiles:
    667                                  if profiles[label].database != current_db_name:
    668                                          profiles2remove.append(label)
    669                          for label in profiles2remove:
    670                                  del profiles[label]
    671
    672                  if len(profiles) == 0:
    673                          host = u'publicdb.gnumed.de'
    674                          label = u'public GNUmed database (%s@%s)' % (current_db_name, host)
    675                          profiles[label] = cBackendProfile()
    676                          profiles[label].name = label
    677                          profiles[label].host = host
    678                          profiles[label].port = 5432
    679                          profiles[label].database = current_db_name
    680                          profiles[label].encoding = u'UTF8'
    681                          profiles[label].public_db = True
    682                          profiles[label].helpdesk = u'http://wiki.gnumed.de'
    683
    684                  return profiles
    685          #----------------------------------------------------------
    686 -        def __load_state(self):
    687
    688                  src_order = [
    689                          (u'explicit', u'return'),
    690                          (u'user', u'return'),
    691                  ]
    692
    693                  self._CBOX_user.SetValue (
    694                          gmTools.coalesce (
    695                                  _cfg.get(u'preferences', u'login', src_order),
    696                                  self.__previously_used_accounts[0]
    697                          )
    698                  )
    699
    700                  last_used_profile_label = _cfg.get(u'preferences', u'profile', src_order)
    701                  if last_used_profile_label in self.__backend_profiles.keys():
    702                          self._CBOX_profile.SetValue(last_used_profile_label)
    703                  else:
    704                          self._CBOX_profile.SetValue(self.__backend_profiles.keys()[0])
    705
    706                  self._CHBOX_debug.SetValue(_cfg.get(option = 'debug'))
    707                  self._CHBOX_slave.SetValue(_cfg.get(option = 'slave'))
    708          #----------------------------------------------------
    709 -        def save_state(self):
    710                  """Save parameter settings to standard configuration file"""
    711                  prefs_name = _cfg.get(option = 'user_preferences_file')
    712                  _log.debug(u'saving login preferences in [%s]', prefs_name)
    713
    714                  gmCfg2.set_option_in_INI_file (
    715                          filename = prefs_name,
    716                          group = 'preferences',
    717                          option = 'login',
    718                          value = self._CBOX_user.GetValue()
    719                  )
    720
    721                  gmCfg2.set_option_in_INI_file (
    722                          filename = prefs_name,
    723                          group = 'preferences',
    724                          option = 'profile',
    725                          value = self._CBOX_profile.GetValue()
    726                  )
    727          #############################################################################
    728          # Retrieve current settings from user interface widgets
    729          #############################################################################
    730 -        def GetLoginInfo(self):
    731                  """convenience function for compatibility with gmLoginInfo.LoginInfo"""
    732                  if self.cancelled:
    733                          return None
    734
    735                  # FIXME: do not assume conf file is latin1 !
    736                  #profile = self.__backend_profiles[self._CBOX_profile.GetValue().encode('latin1').strip()]
    737                  profile = self.__backend_profiles[self._CBOX_profile.GetValue().encode('utf8').strip()]
    738                  _log.info(u'backend profile "%s" selected', profile.name)
    739                  _log.info(u' details: <%s> on %s@%s:%s (%s, %s)',
    740                          self._CBOX_user.GetValue(),
    741                          profile.database,
    742                          profile.host,
    743                          profile.port,
    744                          profile.encoding,
    745                          gmTools.bool2subst(profile.public_db, u'public', u'private')
    746                  )
    747                  _log.info(u' helpdesk: "%s"', profile.helpdesk)
    748                  login = gmLoginInfo.LoginInfo (
    749                          user = self._CBOX_user.GetValue(),
    750                          password = self.pwdentry.GetValue(),
    751                          host = profile.host,
    752                          database = profile.database,
    753                          port = profile.port
    754                  )
    755                  login.public_db = profile.public_db
    756                  login.helpdesk = profile.helpdesk
    757                  login.backend_profile = profile.name
    758                  return login
    759          #----------------------------
    760          # event handlers
    761          #----------------------------
    762 -        def OnHelp(self, event):
    763                  praxis = gmPraxis.gmCurrentPraxisBranch()
    764                  wx.MessageBox(_(
    765  u"""Unable to connect to the database ?
    766
    767   "PostgreSQL: FATAL:  password authentication failed ..."
    768
    769  The default user name and password are {any-doc, any-doc}
    770  for the public and any new GNUmed databases.
    771
    772   "... could not connect to server ..."
    773
    774  Mostly this is a case of new users who did not yet install
    775  or configure a PostgreSQL server and/or a GNUmed database
    776  of their own, which you must do before you can connect to
    777  anything other than the public demonstration database, see
    778
    779   http://wiki.gnumed.de/bin/view/Gnumed/GmManualServerInstall
    780
    781  For assistance on using GNUmed please consult the wiki:
    782
    783   http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual
    784
    785  For more help than the above, please contact:
    786
    787   GNUmed Development List <gnumed-bugs@gnu.org>
    788
    789  For local assistance please contact:
    790
    791   %s""") % praxis.helpdesk,
    792                  caption = _('HELP for GNUmed main login screen'))
    793
    794          #----------------------------
    795 -        def __on_login_button_pressed(self, event):
    796
    797                  root_logger = logging.getLogger()
    798                  if self._CHBOX_debug.GetValue():
    799                          _log.info('debug mode enabled')
    800                          _cfg.set_option(option = 'debug', value = True)
    801                          root_logger.setLevel(logging.DEBUG)
    802                  else:
    803                          _log.info('debug mode disabled')
    804                          _cfg.set_option(option = 'debug', value = False)
    805                          if _cfg.get(option = '--quiet', source_order = [('cli', 'return')]):
    806                                  root_logger.setLevel(logging.ERROR)
    807                          else:
    808                                  root_logger.setLevel(logging.WARNING)
    809
    810                  if self._CHBOX_slave.GetValue():
    811                          _log.info('slave mode enabled')
    812                          _cfg.set_option(option = 'slave', value = True)
    813                  else:
    814                          _log.info('slave mode disabled')
    815                          _cfg.set_option(option = 'slave', value = False)
    816
    817                  self.backend_profile = self.__backend_profiles[self._CBOX_profile.GetValue().encode('latin1').strip()]
    818  #               self.user = self._CBOX_user.GetValue().strip()
    819  #               self.password = self.GetPassword()
    820                  self.cancelled = False
    821                  self.parent.Close()
    822          #----------------------------
    823 -        def OnCancel(self, event):
    824                  self.cancelled = True
    825                  self.parent.Close()
    826
    827  #================================================================
    828  # main
    829  #----------------------------------------------------------------
    830  if __name__ == "__main__":
    831
    832          if len(sys.argv) < 2:
    833                  sys.exit()
    834
    835          if sys.argv[1] != 'test':
    836                  sys.exit()
    837
    838          # we don't have tests yet
    839          sys.exit()
    840
    841          from Gnumed.pycommon import gmI18N
    842
    843          logging.basicConfig(level = logging.DEBUG)
    844
    845          gmI18N.activate_locale()
    846          gmI18N.install_domain(domain='gnumed')
    847          #-----------------------------------------------
    848          #-----------------------------------------------
    849 -        def test():
    850                  app = wx.PyWidgetTester(size = (300,400))
    851                  #show the login panel in a main window
    852  #               app.SetWidget(cLoginPanel, -1)
    853                  #and pop the login dialog up modally
    854                  dlg = cLoginDialog(None, -1) #, png_bitmap = 'bitmaps/gnumedlogo.png')
    855                  dlg.ShowModal()
    856                  #demonstration how to access the login dialog values
    857                  lp = dlg.panel.GetLoginInfo()
    858                  if lp is None:
    859                          wx.MessageBox(_("Dialog was cancelled by user"))
    860                  else:
    861                          wx.MessageBox(_("You tried to log in as [%s] with password [%s].\nHost:%s, DB: %s, Port: %s") % (lp.GetUser(),lp.GetPassword(),lp.GetHost(),lp.GetDatabase(),lp.GetPort()))
    862                  dlg.Destroy()
    863  #               app.MainLoop()
    864
    865  #================================================================
    866