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

# Source Code for Module Gnumed.wxpython.gmAuthWidgets

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