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

Source Code for Module Gnumed.wxpython.gmDemographicsWidgets

   1  """Widgets dealing with patient demographics.""" 
   2  #============================================================ 
   3  # $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmDemographicsWidgets.py,v $ 
   4  # $Id: gmDemographicsWidgets.py,v 1.175 2010/02/07 15:13:02 ncq Exp $ 
   5  __version__ = "$Revision: 1.175 $" 
   6  __author__ = "R.Terry, SJ Tan, I Haywood, Carlos Moro <cfmoro1976@yahoo.es>" 
   7  __license__ = 'GPL (details at http://www.gnu.org)' 
   8   
   9  # standard library 
  10  import time, string, sys, os, datetime as pyDT, csv, codecs, re as regex, psycopg2, logging 
  11   
  12   
  13  import wx 
  14  import wx.wizard 
  15   
  16   
  17  # GNUmed specific 
  18  if __name__ == '__main__': 
  19          sys.path.insert(0, '../../') 
  20  from Gnumed.pycommon import gmDispatcher, gmI18N, gmMatchProvider, gmPG2, gmTools, gmCfg 
  21  from Gnumed.pycommon import gmDateTime, gmShellAPI 
  22  from Gnumed.business import gmDemographicRecord, gmPerson, gmSurgery 
  23  from Gnumed.wxpython import gmPlugin, gmPhraseWheel, gmGuiHelpers, gmDateTimeInput 
  24  from Gnumed.wxpython import gmRegetMixin, gmDataMiningWidgets, gmListWidgets, gmEditArea 
  25  from Gnumed.wxpython import gmAuthWidgets, gmCfgWidgets 
  26  from Gnumed.wxGladeWidgets import wxgGenericAddressEditAreaPnl, wxgPersonContactsManagerPnl, wxgPersonIdentityManagerPnl 
  27  from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl, wxgCommChannelEditAreaPnl, wxgExternalIDEditAreaPnl 
  28   
  29   
  30  # constant defs 
  31  _log = logging.getLogger('gm.ui') 
  32   
  33   
  34  try: 
  35          _('dummy-no-need-to-translate-but-make-epydoc-happy') 
  36  except NameError: 
  37          _ = lambda x:x 
  38   
  39  #============================================================ 
  40  # country related widgets / functions 
  41  #============================================================ 
42 -def configure_default_country(parent=None):
43 44 if parent is None: 45 parent = wx.GetApp().GetTopWindow() 46 47 countries = gmDemographicRecord.get_countries() 48 49 gmCfgWidgets.configure_string_from_list_option ( 50 parent = parent, 51 message = _('Select the default region for new persons.\n'), 52 option = 'person.create.default_country', 53 bias = 'user', 54 choices = [ (c['l10n_country'], c['code']) for c in countries ], 55 columns = [_('Country'), _('Code')], 56 data = [ c['country'] for c in countries ] 57 )
58 #============================================================
59 -class cCountryPhraseWheel(gmPhraseWheel.cPhraseWheel):
60
61 - def __init__(self, *args, **kwargs):
62 63 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 64 65 context = { 66 u'ctxt_zip': { 67 u'where_part': u'and zip ilike %(zip)s', 68 u'placeholder': u'zip' 69 } 70 } 71 query = u""" 72 select code, name from ( 73 select distinct on (code, name) code, (name || ' (' || code || ')') as name, rank from ( 74 75 -- localized to user 76 77 select 78 code_country as code, l10n_country as name, 1 as rank 79 from dem.v_zip2data 80 where 81 l10n_country %(fragment_condition)s 82 %(ctxt_zip)s 83 84 union all 85 86 select 87 code as code, _(name) as name, 2 as rank 88 from dem.country 89 where 90 _(name) %(fragment_condition)s 91 92 union all 93 94 -- non-localized 95 96 select 97 code_country as code, country as name, 3 as rank 98 from dem.v_zip2data 99 where 100 country %(fragment_condition)s 101 %(ctxt_zip)s 102 103 union all 104 105 select 106 code as code, name as name, 4 as rank 107 from dem.country 108 where 109 name %(fragment_condition)s 110 111 union all 112 113 -- abbreviation 114 115 select 116 code as code, name as name, 5 as rank 117 from dem.country 118 where 119 code %(fragment_condition)s 120 121 ) as q2 122 ) as q1 order by rank, name limit 25""" 123 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 124 mp.setThresholds(2, 5, 9) 125 self.matcher = mp 126 127 self.unset_context(context = u'zip') 128 self.SetToolTipString(_('Type or select a country.')) 129 self.capitalisation_mode = gmTools.CAPS_FIRST 130 self.selection_only = True
131 132 #============================================================ 133 # province related widgets / functions 134 #============================================================
135 -def configure_default_region(parent=None):
136 137 if parent is None: 138 parent = wx.GetApp().GetTopWindow() 139 140 provs = gmDemographicRecord.get_provinces() 141 142 gmCfgWidgets.configure_string_from_list_option ( 143 parent = parent, 144 message = _('Select the default region/province/state/territory for new persons.\n'), 145 option = 'person.create.default_region', 146 bias = 'user', 147 choices = [ (p['l10n_country'], p['l10n_state'], p['code_state']) for p in provs ], 148 columns = [_('Country'), _('Region'), _('Code')], 149 data = [ p['state'] for p in provs ] 150 )
151 #============================================================
152 -def edit_province(parent=None, province=None):
153 ea = cProvinceEAPnl(parent = parent, id = -1, province = province) 154 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (province is not None)) 155 dlg.SetTitle(gmTools.coalesce(province, _('Adding province'), _('Editing province'))) 156 result = dlg.ShowModal() 157 dlg.Destroy() 158 return (result == wx.ID_OK)
159 #============================================================
160 -def delete_province(parent=None, province=None):
161 162 msg = _( 163 'Are you sure you want to delete this province ?\n' 164 '\n' 165 'Deletion will only work if this province is not\n' 166 'yet in use in any patient addresses.' 167 ) 168 169 tt = _( 170 'Also delete any towns/cities/villages known\n' 171 'to be situated in this state as long as\n' 172 'no patients are recorded to live there.' 173 ) 174 175 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 176 parent, 177 -1, 178 caption = _('Deleting province'), 179 question = msg, 180 show_checkbox = True, 181 checkbox_msg = _('delete related townships'), 182 checkbox_tooltip = tt, 183 button_defs = [ 184 {'label': _('Yes, delete'), 'tooltip': _('Delete province and possibly related townships.'), 'default': False}, 185 {'label': _('No'), 'tooltip': _('No, do NOT delete anything.'), 'default': True} 186 ] 187 ) 188 189 decision = dlg.ShowModal() 190 if decision != wx.ID_YES: 191 dlg.Destroy() 192 return False 193 194 include_urbs = dlg.checkbox_is_checked() 195 dlg.Destroy() 196 197 return gmDemographicRecord.delete_province(province = province, delete_urbs = include_urbs)
198 #============================================================
199 -def manage_provinces(parent=None):
200 201 if parent is None: 202 parent = wx.GetApp().GetTopWindow() 203 204 #------------------------------------------------------------ 205 def delete(province=None): 206 return delete_province(parent = parent, province = province['pk_state'])
207 #------------------------------------------------------------ 208 def edit(province=None): 209 return edit_province(parent = parent, province = province) 210 #------------------------------------------------------------ 211 def refresh(lctrl): 212 wx.BeginBusyCursor() 213 provinces = gmDemographicRecord.get_provinces() 214 lctrl.set_string_items([ (p['l10n_country'], p['l10n_state']) for p in provinces ]) 215 lctrl.set_data(provinces) 216 wx.EndBusyCursor() 217 #------------------------------------------------------------ 218 msg = _( 219 '\n' 220 'This list shows the provinces known to GNUmed.\n' 221 '\n' 222 'In your jurisdiction "province" may correspond to either of "state",\n' 223 '"county", "region", "territory", or some such term.\n' 224 '\n' 225 'Select the province you want to edit !\n' 226 ) 227 228 gmListWidgets.get_choices_from_list ( 229 parent = parent, 230 msg = msg, 231 caption = _('Editing provinces ...'), 232 columns = [_('Country'), _('Province')], 233 single_selection = True, 234 new_callback = edit, 235 #edit_callback = edit, 236 delete_callback = delete, 237 refresh_callback = refresh 238 ) 239 #============================================================
240 -class cStateSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
241
242 - def __init__(self, *args, **kwargs):
243 244 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 245 246 context = { 247 u'ctxt_country_name': { 248 u'where_part': u'and l10n_country ilike %(country_name)s or country ilike %(country_name)s', 249 u'placeholder': u'country_name' 250 }, 251 u'ctxt_zip': { 252 u'where_part': u'and zip ilike %(zip)s', 253 u'placeholder': u'zip' 254 }, 255 u'ctxt_country_code': { 256 u'where_part': u'and country in (select code from dem.country where _(name) ilike %(country_name)s or name ilike %(country_name)s)', 257 u'placeholder': u'country_name' 258 } 259 } 260 261 query = u""" 262 select code, name from ( 263 select distinct on (name) code, name, rank from ( 264 -- 1: find states based on name, context: zip and country name 265 select 266 code_state as code, state as name, 1 as rank 267 from dem.v_zip2data 268 where 269 state %(fragment_condition)s 270 %(ctxt_country_name)s 271 %(ctxt_zip)s 272 273 union all 274 275 -- 2: find states based on code, context: zip and country name 276 select 277 code_state as code, state as name, 2 as rank 278 from dem.v_zip2data 279 where 280 code_state %(fragment_condition)s 281 %(ctxt_country_name)s 282 %(ctxt_zip)s 283 284 union all 285 286 -- 3: find states based on name, context: country 287 select 288 code as code, name as name, 3 as rank 289 from dem.state 290 where 291 name %(fragment_condition)s 292 %(ctxt_country_code)s 293 294 union all 295 296 -- 4: find states based on code, context: country 297 select 298 code as code, name as name, 3 as rank 299 from dem.state 300 where 301 code %(fragment_condition)s 302 %(ctxt_country_code)s 303 304 ) as q2 305 ) as q1 order by rank, name limit 50""" 306 307 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 308 mp.setThresholds(2, 5, 6) 309 mp.word_separators = u'[ \t]+' 310 self.matcher = mp 311 312 self.unset_context(context = u'zip') 313 self.unset_context(context = u'country_name') 314 self.SetToolTipString(_('Type or select a state/region/province/territory.')) 315 self.capitalisation_mode = gmTools.CAPS_FIRST 316 self.selection_only = True
317 #==================================================================== 318 from Gnumed.wxGladeWidgets import wxgProvinceEAPnl 319
320 -class cProvinceEAPnl(wxgProvinceEAPnl.wxgProvinceEAPnl, gmEditArea.cGenericEditAreaMixin):
321
322 - def __init__(self, *args, **kwargs):
323 324 try: 325 data = kwargs['province'] 326 del kwargs['province'] 327 except KeyError: 328 data = None 329 330 wxgProvinceEAPnl.wxgProvinceEAPnl.__init__(self, *args, **kwargs) 331 gmEditArea.cGenericEditAreaMixin.__init__(self) 332 333 self.mode = 'new' 334 self.data = data 335 if data is not None: 336 self.mode = 'edit' 337 338 self.__init_ui()
339 #----------------------------------------------------------------
340 - def __init_ui(self):
341 self._PRW_province.selection_only = False
342 #---------------------------------------------------------------- 343 # generic Edit Area mixin API 344 #----------------------------------------------------------------
345 - def _valid_for_save(self):
346 347 validity = True 348 349 if self._PRW_province.GetData() is None: 350 if self._PRW_province.GetValue().strip() == u'': 351 validity = False 352 self._PRW_province.display_as_valid(False) 353 else: 354 self._PRW_province.display_as_valid(True) 355 else: 356 self._PRW_province.display_as_valid(True) 357 358 if self._PRW_province.GetData() is None: 359 if self._TCTRL_code.GetValue().strip() == u'': 360 validity = False 361 self._TCTRL_code.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 362 else: 363 self._TCTRL_code.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 364 365 if self._PRW_country.GetData() is None: 366 validity = False 367 self._PRW_country.display_as_valid(False) 368 else: 369 self._PRW_country.display_as_valid(True) 370 371 return validity
372 #----------------------------------------------------------------
373 - def _save_as_new(self):
374 gmDemographicRecord.create_province ( 375 name = self._PRW_province.GetValue().strip(), 376 code = self._TCTRL_code.GetValue().strip(), 377 country = self._PRW_country.GetData() 378 ) 379 380 # EA is refreshed automatically after save, so need this ... 381 self.data = { 382 'l10n_state' : self._PRW_province.GetValue().strip(), 383 'code_state' : self._TCTRL_code.GetValue().strip(), 384 'l10n_country' : self._PRW_country.GetValue().strip() 385 } 386 387 return True
388 #----------------------------------------------------------------
389 - def _save_as_update(self):
390 # update self.data and save the changes 391 #self.data[''] = 392 #self.data[''] = 393 #self.data[''] = 394 #self.data.save() 395 396 # do nothing for now (IOW, don't support updates) 397 return True
398 #----------------------------------------------------------------
399 - def _refresh_as_new(self):
400 self._PRW_province.SetText() 401 self._TCTRL_code.SetValue(u'') 402 self._PRW_country.SetText() 403 404 self._PRW_province.SetFocus()
405 #----------------------------------------------------------------
406 - def _refresh_from_existing(self):
407 self._PRW_province.SetText(self.data['l10n_state'], self.data['code_state']) 408 self._TCTRL_code.SetValue(self.data['code_state']) 409 self._PRW_country.SetText(self.data['l10n_country'], self.data['code_country']) 410 411 self._PRW_province.SetFocus()
412 #----------------------------------------------------------------
414 self._PRW_province.SetText() 415 self._TCTRL_code.SetValue(u'') 416 self._PRW_country.SetText(self.data['l10n_country'], self.data['code_country']) 417 418 self._PRW_province.SetFocus()
419 #============================================================ 420 #============================================================
421 -class cKOrganizerSchedulePnl(gmDataMiningWidgets.cPatientListingPnl):
422
423 - def __init__(self, *args, **kwargs):
424 425 kwargs['message'] = _("Today's KOrganizer appointments ...") 426 kwargs['button_defs'] = [ 427 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')}, 428 {'label': u''}, 429 {'label': u''}, 430 {'label': u''}, 431 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')} 432 ] 433 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs) 434 435 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv')) 436 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
437 438 #--------------------------------------------------------
439 - def _on_BTN_1_pressed(self, event):
440 """Reload appointments from KOrganizer.""" 441 self.reload_appointments()
442 #--------------------------------------------------------
443 - def _on_BTN_5_pressed(self, event):
444 """Reload appointments from KOrganizer.""" 445 found, cmd = gmShellAPI.detect_external_binary(binary = 'korganizer') 446 447 if not found: 448 gmDispatcher.send(signal = 'statustext', msg = _('KOrganizer is not installed.'), beep = True) 449 return 450 451 gmShellAPI.run_command_in_shell(command = cmd, blocking = False)
452 #--------------------------------------------------------
453 - def reload_appointments(self):
454 try: os.remove(self.fname) 455 except OSError: pass 456 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True) 457 try: 458 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace') 459 except IOError: 460 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True) 461 return 462 463 csv_lines = gmTools.unicode_csv_reader ( 464 csv_file, 465 delimiter = ',' 466 ) 467 # start_date, start_time, end_date, end_time, title (patient), ort, comment, UID 468 self._LCTRL_items.set_columns ([ 469 _('Place'), 470 _('Start'), 471 u'', 472 u'', 473 _('Patient'), 474 _('Comment') 475 ]) 476 items = [] 477 data = [] 478 for line in csv_lines: 479 items.append([line[5], line[0], line[1], line[3], line[4], line[6]]) 480 data.append([line[4], line[7]]) 481 482 self._LCTRL_items.set_string_items(items = items) 483 self._LCTRL_items.set_column_widths() 484 self._LCTRL_items.set_data(data = data) 485 self._LCTRL_items.patient_key = 0
486 #-------------------------------------------------------- 487 # notebook plugins API 488 #--------------------------------------------------------
489 - def repopulate_ui(self):
490 self.reload_appointments()
491 #============================================================
492 -def edit_occupation():
493 494 pat = gmPerson.gmCurrentPatient() 495 curr_jobs = pat.get_occupations() 496 if len(curr_jobs) > 0: 497 old_job = curr_jobs[0]['l10n_occupation'] 498 update = curr_jobs[0]['modified_when'].strftime('%m/%Y') 499 else: 500 old_job = u'' 501 update = u'' 502 503 msg = _( 504 'Please enter the primary occupation of the patient.\n' 505 '\n' 506 'Currently recorded:\n' 507 '\n' 508 ' %s (last updated %s)' 509 ) % (old_job, update) 510 511 new_job = wx.GetTextFromUser ( 512 message = msg, 513 caption = _('Editing primary occupation'), 514 default_value = old_job, 515 parent = None 516 ) 517 if new_job.strip() == u'': 518 return 519 520 for job in curr_jobs: 521 # unlink all but the new job 522 if job['l10n_occupation'] != new_job: 523 pat.unlink_occupation(occupation = job['l10n_occupation']) 524 # and link the new one 525 pat.link_occupation(occupation = new_job)
526 #============================================================
527 -def disable_identity(identity=None):
528 # ask user for assurance 529 go_ahead = gmGuiHelpers.gm_show_question ( 530 _('Are you sure you really, positively want\n' 531 'to disable the following person ?\n' 532 '\n' 533 ' %s %s %s\n' 534 ' born %s\n' 535 '\n' 536 '%s\n' 537 ) % ( 538 identity['firstnames'], 539 identity['lastnames'], 540 identity['gender'], 541 identity['dob'], 542 gmTools.bool2subst ( 543 identity.is_patient, 544 _('This patient DID receive care.'), 545 _('This person did NOT receive care.') 546 ) 547 ), 548 _('Disabling person') 549 ) 550 if not go_ahead: 551 return True 552 553 # get admin connection 554 conn = gmAuthWidgets.get_dbowner_connection ( 555 procedure = _('Disabling patient') 556 ) 557 # - user cancelled 558 if conn is False: 559 return True 560 # - error 561 if conn is None: 562 return False 563 564 # now disable patient 565 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}]) 566 567 return True
568 #============================================================ 569 # address phrasewheels and widgets 570 #============================================================
571 -class cPersonAddressesManagerPnl(gmListWidgets.cGenericListManagerPnl):
572 """A list for managing a person's addresses. 573 574 Does NOT act on/listen to the current patient. 575 """
576 - def __init__(self, *args, **kwargs):
577 578 try: 579 self.__identity = kwargs['identity'] 580 del kwargs['identity'] 581 except KeyError: 582 self.__identity = None 583 584 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 585 586 self.new_callback = self._add_address 587 self.edit_callback = self._edit_address 588 self.delete_callback = self._del_address 589 self.refresh_callback = self.refresh 590 591 self.__init_ui() 592 self.refresh()
593 #-------------------------------------------------------- 594 # external API 595 #--------------------------------------------------------
596 - def refresh(self, *args, **kwargs):
597 if self.__identity is None: 598 self._LCTRL_items.set_string_items() 599 return 600 601 adrs = self.__identity.get_addresses() 602 self._LCTRL_items.set_string_items ( 603 items = [ [ 604 a['l10n_address_type'], 605 a['street'], 606 gmTools.coalesce(a['notes_street'], u''), 607 a['number'], 608 gmTools.coalesce(a['subunit'], u''), 609 a['postcode'], 610 a['urb'], 611 gmTools.coalesce(a['suburb'], u''), 612 a['l10n_state'], 613 a['l10n_country'], 614 gmTools.coalesce(a['notes_subunit'], u'') 615 ] for a in adrs 616 ] 617 ) 618 self._LCTRL_items.set_column_widths() 619 self._LCTRL_items.set_data(data = adrs)
620 #-------------------------------------------------------- 621 # internal helpers 622 #--------------------------------------------------------
623 - def __init_ui(self):
624 self._LCTRL_items.SetToolTipString(_('List of known addresses.')) 625 self._LCTRL_items.set_columns(columns = [ 626 _('Type'), 627 _('Street'), 628 _('Street info'), 629 _('Number'), 630 _('Subunit'), 631 _('Postal code'), 632 _('Place'), 633 _('Suburb'), 634 _('Region'), 635 _('Country'), 636 _('Comment') 637 ])
638 #--------------------------------------------------------
639 - def _add_address(self):
640 ea = cAddressEditAreaPnl(self, -1) 641 ea.identity = self.__identity 642 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 643 dlg.SetTitle(_('Adding new address')) 644 if dlg.ShowModal() == wx.ID_OK: 645 return True 646 return False
647 #--------------------------------------------------------
648 - def _edit_address(self, address):
649 ea = cAddressEditAreaPnl(self, -1, address = address) 650 ea.identity = self.__identity 651 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 652 dlg.SetTitle(_('Editing address')) 653 if dlg.ShowModal() == wx.ID_OK: 654 # did we add an entirely new address ? 655 # if so then unlink the old one as implied by "edit" 656 if ea.address['pk_address'] != address['pk_address']: 657 self.__identity.unlink_address(address = address) 658 return True 659 return False
660 #--------------------------------------------------------
661 - def _del_address(self, address):
662 go_ahead = gmGuiHelpers.gm_show_question ( 663 _( 'Are you sure you want to remove this\n' 664 "address from the patient's addresses ?\n" 665 '\n' 666 'The address itself will not be deleted\n' 667 'but it will no longer be associated with\n' 668 'this patient.' 669 ), 670 _('Removing address') 671 ) 672 if not go_ahead: 673 return False 674 self.__identity.unlink_address(address = address) 675 return True
676 #-------------------------------------------------------- 677 # properties 678 #--------------------------------------------------------
679 - def _get_identity(self):
680 return self.__identity
681
682 - def _set_identity(self, identity):
683 self.__identity = identity 684 self.refresh()
685 686 identity = property(_get_identity, _set_identity)
687 #============================================================
688 -class cPersonContactsManagerPnl(wxgPersonContactsManagerPnl.wxgPersonContactsManagerPnl):
689 """A panel for editing contact data for a person. 690 691 - provides access to: 692 - addresses 693 - communication paths 694 695 Does NOT act on/listen to the current patient. 696 """
697 - def __init__(self, *args, **kwargs):
698 699 wxgPersonContactsManagerPnl.wxgPersonContactsManagerPnl.__init__(self, *args, **kwargs) 700 701 self.__identity = None 702 self.refresh()
703 #-------------------------------------------------------- 704 # external API 705 #--------------------------------------------------------
706 - def refresh(self):
707 self._PNL_addresses.identity = self.__identity 708 self._PNL_comms.identity = self.__identity
709 #-------------------------------------------------------- 710 # properties 711 #--------------------------------------------------------
712 - def _get_identity(self):
713 return self.__identity
714
715 - def _set_identity(self, identity):
716 self.__identity = identity 717 self.refresh()
718 719 identity = property(_get_identity, _set_identity)
720 #============================================================
721 -class cAddressEditAreaPnl(wxgGenericAddressEditAreaPnl.wxgGenericAddressEditAreaPnl):
722 """An edit area for editing/creating an address. 723 724 Does NOT act on/listen to the current patient. 725 """
726 - def __init__(self, *args, **kwargs):
727 try: 728 self.address = kwargs['address'] 729 del kwargs['address'] 730 except KeyError: 731 self.address = None 732 733 wxgGenericAddressEditAreaPnl.wxgGenericAddressEditAreaPnl.__init__(self, *args, **kwargs) 734 735 self.identity = None 736 737 self.__register_interests() 738 self.refresh()
739 #-------------------------------------------------------- 740 # external API 741 #--------------------------------------------------------
742 - def refresh(self, address = None):
743 if address is not None: 744 self.address = address 745 746 if self.address is not None: 747 self._PRW_type.SetText(self.address['l10n_address_type']) 748 self._PRW_zip.SetText(self.address['postcode']) 749 self._PRW_street.SetText(self.address['street'], data = self.address['street']) 750 self._TCTRL_notes_street.SetValue(gmTools.coalesce(self.address['notes_street'], '')) 751 self._TCTRL_number.SetValue(self.address['number']) 752 self._TCTRL_subunit.SetValue(gmTools.coalesce(self.address['subunit'], '')) 753 self._PRW_suburb.SetText(gmTools.coalesce(self.address['suburb'], '')) 754 self._PRW_urb.SetText(self.address['urb'], data = self.address['urb']) 755 self._PRW_state.SetText(self.address['l10n_state'], data = self.address['code_state']) 756 self._PRW_country.SetText(self.address['l10n_country'], data = self.address['code_country']) 757 self._TCTRL_notes_subunit.SetValue(gmTools.coalesce(self.address['notes_subunit'], ''))
758 # FIXME: clear fields 759 # else: 760 # pass 761 #--------------------------------------------------------
762 - def save(self):
763 """Links address to patient, creating new address if necessary""" 764 765 if not self.__valid_for_save(): 766 return False 767 768 # link address to patient 769 try: 770 adr = self.identity.link_address ( 771 number = self._TCTRL_number.GetValue().strip(), 772 street = self._PRW_street.GetValue().strip(), 773 postcode = self._PRW_zip.GetValue().strip(), 774 urb = self._PRW_urb.GetValue().strip(), 775 state = self._PRW_state.GetData(), 776 country = self._PRW_country.GetData(), 777 subunit = gmTools.none_if(self._TCTRL_subunit.GetValue().strip(), u''), 778 suburb = gmTools.none_if(self._PRW_suburb.GetValue().strip(), u''), 779 id_type = self._PRW_type.GetData() 780 ) 781 except: 782 _log.exception('cannot save address') 783 gmGuiHelpers.gm_show_error ( 784 _('Cannot save address.\n\n' 785 'Does the state [%s]\n' 786 'exist in country [%s] ?' 787 ) % ( 788 self._PRW_state.GetValue().strip(), 789 self._PRW_country.GetValue().strip() 790 ), 791 _('Saving address') 792 ) 793 return False 794 795 notes = self._TCTRL_notes_street.GetValue().strip() 796 if notes != u'': 797 adr['notes_street'] = notes 798 notes = self._TCTRL_notes_subunit.GetValue().strip() 799 if notes != u'': 800 adr['notes_subunit'] = notes 801 adr.save_payload() 802 803 self.address = adr 804 805 return True
806 #-------------------------------------------------------- 807 # event handling 808 #--------------------------------------------------------
809 - def __register_interests(self):
810 self._PRW_zip.add_callback_on_lose_focus(self._on_zip_set) 811 self._PRW_country.add_callback_on_lose_focus(self._on_country_set)
812 #--------------------------------------------------------
813 - def _on_zip_set(self):
814 """Set the street, town, state and country according to entered zip code.""" 815 zip_code = self._PRW_zip.GetValue() 816 if zip_code.strip() == u'': 817 self._PRW_street.unset_context(context = u'zip') 818 self._PRW_urb.unset_context(context = u'zip') 819 self._PRW_state.unset_context(context = u'zip') 820 self._PRW_country.unset_context(context = u'zip') 821 else: 822 self._PRW_street.set_context(context = u'zip', val = zip_code) 823 self._PRW_urb.set_context(context = u'zip', val = zip_code) 824 self._PRW_state.set_context(context = u'zip', val = zip_code) 825 self._PRW_country.set_context(context = u'zip', val = zip_code)
826 #--------------------------------------------------------
827 - def _on_country_set(self):
828 """Set the states according to entered country.""" 829 country = self._PRW_country.GetData() 830 if country is None: 831 self._PRW_state.unset_context(context = 'country') 832 else: 833 self._PRW_state.set_context(context = 'country', val = country)
834 #-------------------------------------------------------- 835 # internal helpers 836 #--------------------------------------------------------
837 - def __valid_for_save(self):
838 839 # validate required fields 840 is_any_field_filled = False 841 842 required_fields = ( 843 self._PRW_type, 844 self._PRW_zip, 845 self._PRW_street, 846 self._TCTRL_number, 847 self._PRW_urb 848 ) 849 for field in required_fields: 850 if len(field.GetValue().strip()) == 0: 851 if is_any_field_filled: 852 field.SetBackgroundColour('pink') 853 field.SetFocus() 854 field.Refresh() 855 gmGuiHelpers.gm_show_error ( 856 _('Address details must be filled in completely or not at all.'), 857 _('Saving contact data') 858 ) 859 return False 860 else: 861 is_any_field_filled = True 862 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 863 field.Refresh() 864 865 required_fields = ( 866 self._PRW_state, 867 self._PRW_country 868 ) 869 for field in required_fields: 870 if field.GetData() is None: 871 if is_any_field_filled: 872 field.SetBackgroundColour('pink') 873 field.SetFocus() 874 field.Refresh() 875 gmGuiHelpers.gm_show_error ( 876 _('Address details must be filled in completely or not at all.'), 877 _('Saving contact data') 878 ) 879 return False 880 else: 881 is_any_field_filled = True 882 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 883 field.Refresh() 884 885 return True
886 #============================================================
887 -class cAddressMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
888
889 - def __init__(self):
890 891 query = u""" 892 select * from ( 893 (select 894 pk_address, 895 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', ' 896 || urb || coalesce(' (' || suburb || ')', '') || ', ' 897 || postcode 898 || coalesce(', ' || notes_street, '') 899 || coalesce(', ' || notes_subunit, '') 900 ) as address 901 from 902 dem.v_address 903 where 904 street %(fragment_condition)s 905 906 ) union ( 907 908 select 909 pk_address, 910 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', ' 911 || urb || coalesce(' (' || suburb || ')', '') || ', ' 912 || postcode 913 || coalesce(', ' || notes_street, '') 914 || coalesce(', ' || notes_subunit, '') 915 ) as address 916 from 917 dem.v_address 918 where 919 postcode_street %(fragment_condition)s 920 921 ) union ( 922 923 select 924 pk_address, 925 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', ' 926 || urb || coalesce(' (' || suburb || ')', '') || ', ' 927 || postcode 928 || coalesce(', ' || notes_street, '') 929 || coalesce(', ' || notes_subunit, '') 930 ) as address 931 from 932 dem.v_address 933 where 934 postcode_urb %(fragment_condition)s 935 ) 936 ) as union_result 937 order by union_result.address limit 50""" 938 939 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = query) 940 941 self.setThresholds(2, 4, 6)
942 # self.word_separators = u'[ \t]+' 943 944 #============================================================
945 -class cAddressPhraseWheel(gmPhraseWheel.cPhraseWheel):
946
947 - def __init__(self, *args, **kwargs):
948 949 mp = cAddressMatchProvider() 950 gmPhraseWheel.cPhraseWheel.__init__ ( 951 self, 952 *args, 953 **kwargs 954 ) 955 self.matcher = cAddressMatchProvider() 956 self.SetToolTipString(_('Select an address by postcode or street name.')) 957 self.selection_only = True 958 self.__address = None 959 self.__old_pk = None
960 #--------------------------------------------------------
961 - def get_address(self):
962 963 pk = self.GetData() 964 965 if pk is None: 966 self.__address = None 967 return None 968 969 if self.__address is None: 970 self.__old_pk = pk 971 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk) 972 else: 973 if pk != self.__old_pk: 974 self.__old_pk = pk 975 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk) 976 977 return self.__address
978 #============================================================
979 -class cAddressTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
980
981 - def __init__(self, *args, **kwargs):
982 983 query = u""" 984 select id, type from (( 985 select id, _(name) as type, 1 as rank 986 from dem.address_type 987 where _(name) %(fragment_condition)s 988 ) union ( 989 select id, name as type, 2 as rank 990 from dem.address_type 991 where name %(fragment_condition)s 992 )) as ur 993 order by 994 ur.rank, ur.type 995 """ 996 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 997 mp.setThresholds(1, 2, 4) 998 mp.word_separators = u'[ \t]+' 999 gmPhraseWheel.cPhraseWheel.__init__ ( 1000 self, 1001 *args, 1002 **kwargs 1003 ) 1004 self.matcher = mp 1005 self.SetToolTipString(_('Select the type of address.')) 1006 # self.capitalisation_mode = gmTools.CAPS_FIRST 1007 self.selection_only = True
1008 #-------------------------------------------------------- 1009 # def GetData(self, can_create=False): 1010 # if self.data is None: 1011 # if can_create: 1012 # self.data = gmMedDoc.create_document_type(self.GetValue().strip())['pk_doc_type'] # FIXME: error handling 1013 # return self.data 1014 #============================================================
1015 -class cZipcodePhraseWheel(gmPhraseWheel.cPhraseWheel):
1016
1017 - def __init__(self, *args, **kwargs):
1018 # FIXME: add possible context 1019 query = u""" 1020 (select distinct postcode, postcode from dem.street where postcode %(fragment_condition)s limit 20) 1021 union 1022 (select distinct postcode, postcode from dem.urb where postcode %(fragment_condition)s limit 20)""" 1023 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1024 mp.setThresholds(2, 3, 15) 1025 gmPhraseWheel.cPhraseWheel.__init__ ( 1026 self, 1027 *args, 1028 **kwargs 1029 ) 1030 self.SetToolTipString(_("Type or select a zip code (postcode).")) 1031 self.matcher = mp
1032 #============================================================
1033 -class cStreetPhraseWheel(gmPhraseWheel.cPhraseWheel):
1034
1035 - def __init__(self, *args, **kwargs):
1036 context = { 1037 u'ctxt_zip': { 1038 u'where_part': u'and zip ilike %(zip)s', 1039 u'placeholder': u'zip' 1040 } 1041 } 1042 query = u""" 1043 select s1, s2 from ( 1044 select s1, s2, rank from ( 1045 select distinct on (street) 1046 street as s1, street as s2, 1 as rank 1047 from dem.v_zip2data 1048 where 1049 street %(fragment_condition)s 1050 %(ctxt_zip)s 1051 1052 union all 1053 1054 select distinct on (name) 1055 name as s1, name as s2, 2 as rank 1056 from dem.street 1057 where 1058 name %(fragment_condition)s 1059 1060 ) as q2 1061 ) as q1 order by rank, s2 limit 50""" 1062 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 1063 mp.setThresholds(3, 5, 8) 1064 gmPhraseWheel.cPhraseWheel.__init__ ( 1065 self, 1066 *args, 1067 **kwargs 1068 ) 1069 self.unset_context(context = u'zip') 1070 1071 self.SetToolTipString(_('Type or select a street.')) 1072 self.capitalisation_mode = gmTools.CAPS_FIRST 1073 self.matcher = mp
1074 #============================================================
1075 -class cSuburbPhraseWheel(gmPhraseWheel.cPhraseWheel):
1076
1077 - def __init__(self, *args, **kwargs):
1078 1079 query = """ 1080 select distinct on (suburb) suburb, suburb 1081 from dem.street 1082 where suburb %(fragment_condition)s 1083 order by suburb 1084 limit 50 1085 """ 1086 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1087 mp.setThresholds(2, 3, 6) 1088 gmPhraseWheel.cPhraseWheel.__init__ ( 1089 self, 1090 *args, 1091 **kwargs 1092 ) 1093 1094 self.SetToolTipString(_('Type or select the suburb.')) 1095 self.capitalisation_mode = gmTools.CAPS_FIRST 1096 self.matcher = mp
1097 #============================================================
1098 -class cUrbPhraseWheel(gmPhraseWheel.cPhraseWheel):
1099
1100 - def __init__(self, *args, **kwargs):
1101 context = { 1102 u'ctxt_zip': { 1103 u'where_part': u'and zip ilike %(zip)s', 1104 u'placeholder': u'zip' 1105 } 1106 } 1107 query = u""" 1108 select u1, u2 from ( 1109 select distinct on (rank, u1) 1110 u1, u2, rank 1111 from ( 1112 select 1113 urb as u1, urb as u2, 1 as rank 1114 from dem.v_zip2data 1115 where 1116 urb %(fragment_condition)s 1117 %(ctxt_zip)s 1118 1119 union all 1120 1121 select 1122 name as u1, name as u2, 2 as rank 1123 from dem.urb 1124 where 1125 name %(fragment_condition)s 1126 ) as union_result 1127 order by rank, u1 1128 ) as distincted_union 1129 limit 50 1130 """ 1131 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 1132 mp.setThresholds(3, 5, 7) 1133 gmPhraseWheel.cPhraseWheel.__init__ ( 1134 self, 1135 *args, 1136 **kwargs 1137 ) 1138 self.unset_context(context = u'zip') 1139 1140 self.SetToolTipString(_('Type or select a city/town/village/dwelling.')) 1141 self.capitalisation_mode = gmTools.CAPS_FIRST 1142 self.matcher = mp
1143 #============================================================ 1144 # communications channel related widgets 1145 #============================================================
1146 -class cCommChannelTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
1147
1148 - def __init__(self, *args, **kwargs):
1149 1150 query = u""" 1151 select pk, type from (( 1152 select pk, _(description) as type, 1 as rank 1153 from dem.enum_comm_types 1154 where _(description) %(fragment_condition)s 1155 ) union ( 1156 select pk, description as type, 2 as rank 1157 from dem.enum_comm_types 1158 where description %(fragment_condition)s 1159 )) as ur 1160 order by 1161 ur.rank, ur.type 1162 """ 1163 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1164 mp.setThresholds(1, 2, 4) 1165 mp.word_separators = u'[ \t]+' 1166 gmPhraseWheel.cPhraseWheel.__init__ ( 1167 self, 1168 *args, 1169 **kwargs 1170 ) 1171 self.matcher = mp 1172 self.SetToolTipString(_('Select the type of communications channel.')) 1173 self.selection_only = True
1174 #------------------------------------------------------------
1175 -class cCommChannelEditAreaPnl(wxgCommChannelEditAreaPnl.wxgCommChannelEditAreaPnl):
1176 """An edit area for editing/creating a comms channel. 1177 1178 Does NOT act on/listen to the current patient. 1179 """
1180 - def __init__(self, *args, **kwargs):
1181 try: 1182 self.channel = kwargs['comm_channel'] 1183 del kwargs['comm_channel'] 1184 except KeyError: 1185 self.channel = None 1186 1187 wxgCommChannelEditAreaPnl.wxgCommChannelEditAreaPnl.__init__(self, *args, **kwargs) 1188 1189 self.identity = None 1190 1191 self.refresh()
1192 #-------------------------------------------------------- 1193 # external API 1194 #--------------------------------------------------------
1195 - def refresh(self, comm_channel = None):
1196 if comm_channel is not None: 1197 self.channel = comm_channel 1198 1199 if self.channel is None: 1200 self._PRW_type.SetText(u'') 1201 self._TCTRL_url.SetValue(u'') 1202 self._PRW_address.SetText(value = u'', data = None) 1203 self._CHBOX_confidential.SetValue(False) 1204 else: 1205 self._PRW_type.SetText(self.channel['l10n_comm_type']) 1206 self._TCTRL_url.SetValue(self.channel['url']) 1207 self._PRW_address.SetData(data = self.channel['pk_address']) 1208 self._CHBOX_confidential.SetValue(self.channel['is_confidential'])
1209 #--------------------------------------------------------
1210 - def save(self):
1211 """Links comm channel to patient.""" 1212 if self.channel is None: 1213 if not self.__valid_for_save(): 1214 return False 1215 try: 1216 self.channel = self.identity.link_comm_channel ( 1217 pk_channel_type = self._PRW_type.GetData(), 1218 url = self._TCTRL_url.GetValue().strip(), 1219 is_confidential = self._CHBOX_confidential.GetValue(), 1220 ) 1221 except psycopg2.IntegrityError: 1222 _log.exception('error saving comm channel') 1223 gmDispatcher.send(signal = u'statustext', msg = _('Cannot save communications channel.'), beep = True) 1224 return False 1225 else: 1226 comm_type = self._PRW_type.GetValue().strip() 1227 if comm_type != u'': 1228 self.channel['comm_type'] = comm_type 1229 url = self._TCTRL_url.GetValue().strip() 1230 if url != u'': 1231 self.channel['url'] = url 1232 self.channel['is_confidential'] = self._CHBOX_confidential.GetValue() 1233 1234 self.channel['pk_address'] = self._PRW_address.GetData() 1235 self.channel.save_payload() 1236 1237 return True
1238 #-------------------------------------------------------- 1239 # internal helpers 1240 #--------------------------------------------------------
1241 - def __valid_for_save(self):
1242 1243 no_errors = True 1244 1245 if self._PRW_type.GetData() is None: 1246 self._PRW_type.SetBackgroundColour('pink') 1247 self._PRW_type.SetFocus() 1248 self._PRW_type.Refresh() 1249 no_errors = False 1250 else: 1251 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1252 self._PRW_type.Refresh() 1253 1254 if self._TCTRL_url.GetValue().strip() == u'': 1255 self._TCTRL_url.SetBackgroundColour('pink') 1256 self._TCTRL_url.SetFocus() 1257 self._TCTRL_url.Refresh() 1258 no_errors = False 1259 else: 1260 self._TCTRL_url.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1261 self._TCTRL_url.Refresh() 1262 1263 return no_errors
1264 #------------------------------------------------------------
1265 -class cPersonCommsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1266 """A list for managing a person's comm channels. 1267 1268 Does NOT act on/listen to the current patient. 1269 """
1270 - def __init__(self, *args, **kwargs):
1271 1272 try: 1273 self.__identity = kwargs['identity'] 1274 del kwargs['identity'] 1275 except KeyError: 1276 self.__identity = None 1277 1278 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1279 1280 self.new_callback = self._add_comm 1281 self.edit_callback = self._edit_comm 1282 self.delete_callback = self._del_comm 1283 self.refresh_callback = self.refresh 1284 1285 self.__init_ui() 1286 self.refresh()
1287 #-------------------------------------------------------- 1288 # external API 1289 #--------------------------------------------------------
1290 - def refresh(self, *args, **kwargs):
1291 if self.__identity is None: 1292 self._LCTRL_items.set_string_items() 1293 return 1294 1295 comms = self.__identity.get_comm_channels() 1296 self._LCTRL_items.set_string_items ( 1297 items = [ [ gmTools.bool2str(c['is_confidential'], u'X', u''), c['l10n_comm_type'], c['url'] ] for c in comms ] 1298 ) 1299 self._LCTRL_items.set_column_widths() 1300 self._LCTRL_items.set_data(data = comms)
1301 #-------------------------------------------------------- 1302 # internal helpers 1303 #--------------------------------------------------------
1304 - def __init_ui(self):
1305 self._LCTRL_items.SetToolTipString(_('List of known communication channels.')) 1306 self._LCTRL_items.set_columns(columns = [ 1307 _('confidential'), 1308 _('Type'), 1309 _('Value') 1310 ])
1311 #--------------------------------------------------------
1312 - def _add_comm(self):
1313 ea = cCommChannelEditAreaPnl(self, -1) 1314 ea.identity = self.__identity 1315 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1316 dlg.SetTitle(_('Adding new communications channel')) 1317 if dlg.ShowModal() == wx.ID_OK: 1318 return True 1319 return False
1320 #--------------------------------------------------------
1321 - def _edit_comm(self, comm_channel):
1322 ea = cCommChannelEditAreaPnl(self, -1, comm_channel = comm_channel) 1323 ea.identity = self.__identity 1324 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1325 dlg.SetTitle(_('Editing communications channel')) 1326 if dlg.ShowModal() == wx.ID_OK: 1327 return True 1328 return False
1329 #--------------------------------------------------------
1330 - def _del_comm(self, comm):
1331 go_ahead = gmGuiHelpers.gm_show_question ( 1332 _( 'Are you sure this patient can no longer\n' 1333 "be contacted via this channel ?" 1334 ), 1335 _('Removing communication channel') 1336 ) 1337 if not go_ahead: 1338 return False 1339 self.__identity.unlink_comm_channel(comm_channel = comm) 1340 return True
1341 #-------------------------------------------------------- 1342 # properties 1343 #--------------------------------------------------------
1344 - def _get_identity(self):
1345 return self.__identity
1346
1347 - def _set_identity(self, identity):
1348 self.__identity = identity 1349 self.refresh()
1350 1351 identity = property(_get_identity, _set_identity)
1352 #============================================================ 1353 # identity widgets 1354 #============================================================ 1355 # phrasewheels 1356 #------------------------------------------------------------
1357 -class cLastnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
1358
1359 - def __init__(self, *args, **kwargs):
1360 query = u"select distinct lastnames, lastnames from dem.names where lastnames %(fragment_condition)s order by lastnames limit 25" 1361 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1362 mp.setThresholds(3, 5, 9) 1363 gmPhraseWheel.cPhraseWheel.__init__ ( 1364 self, 1365 *args, 1366 **kwargs 1367 ) 1368 self.SetToolTipString(_("Type or select a last name (family name/surname).")) 1369 self.capitalisation_mode = gmTools.CAPS_NAMES 1370 self.matcher = mp
1371 #------------------------------------------------------------
1372 -class cFirstnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
1373
1374 - def __init__(self, *args, **kwargs):
1375 query = u""" 1376 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 1377 union 1378 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 1379 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1380 mp.setThresholds(3, 5, 9) 1381 gmPhraseWheel.cPhraseWheel.__init__ ( 1382 self, 1383 *args, 1384 **kwargs 1385 ) 1386 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name).")) 1387 self.capitalisation_mode = gmTools.CAPS_NAMES 1388 self.matcher = mp
1389 #------------------------------------------------------------
1390 -class cNicknamePhraseWheel(gmPhraseWheel.cPhraseWheel):
1391
1392 - def __init__(self, *args, **kwargs):
1393 query = u""" 1394 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20) 1395 union 1396 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 1397 union 1398 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 1399 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1400 mp.setThresholds(3, 5, 9) 1401 gmPhraseWheel.cPhraseWheel.__init__ ( 1402 self, 1403 *args, 1404 **kwargs 1405 ) 1406 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name).")) 1407 # nicknames CAN start with lower case ! 1408 #self.capitalisation_mode = gmTools.CAPS_NAMES 1409 self.matcher = mp
1410 #------------------------------------------------------------
1411 -class cTitlePhraseWheel(gmPhraseWheel.cPhraseWheel):
1412
1413 - def __init__(self, *args, **kwargs):
1414 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s" 1415 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1416 mp.setThresholds(1, 3, 9) 1417 gmPhraseWheel.cPhraseWheel.__init__ ( 1418 self, 1419 *args, 1420 **kwargs 1421 ) 1422 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !")) 1423 self.matcher = mp
1424 #------------------------------------------------------------
1425 -class cGenderSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
1426 """Let user select a gender.""" 1427 1428 _gender_map = None 1429
1430 - def __init__(self, *args, **kwargs):
1431 1432 if cGenderSelectionPhraseWheel._gender_map is None: 1433 cmd = u""" 1434 select tag, l10n_label, sort_weight 1435 from dem.v_gender_labels 1436 order by sort_weight desc""" 1437 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 1438 cGenderSelectionPhraseWheel._gender_map = {} 1439 for gender in rows: 1440 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = { 1441 'data': gender[idx['tag']], 1442 'label': gender[idx['l10n_label']], 1443 'weight': gender[idx['sort_weight']] 1444 } 1445 1446 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values()) 1447 mp.setThresholds(1, 1, 3) 1448 1449 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1450 self.selection_only = True 1451 self.matcher = mp 1452 self.picklist_delay = 50
1453 #------------------------------------------------------------
1454 -class cOccupationPhraseWheel(gmPhraseWheel.cPhraseWheel):
1455
1456 - def __init__(self, *args, **kwargs):
1457 query = u"select distinct name, _(name) from dem.occupation where _(name) %(fragment_condition)s" 1458 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1459 mp.setThresholds(1, 3, 5) 1460 gmPhraseWheel.cPhraseWheel.__init__ ( 1461 self, 1462 *args, 1463 **kwargs 1464 ) 1465 self.SetToolTipString(_("Type or select an occupation.")) 1466 self.capitalisation_mode = gmTools.CAPS_FIRST 1467 self.matcher = mp
1468 #------------------------------------------------------------
1469 -class cExternalIDTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
1470
1471 - def __init__(self, *args, **kwargs):
1472 query = u""" 1473 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label 1474 from dem.enum_ext_id_types 1475 where name %%(fragment_condition)s 1476 order by label limit 25""" % _('issued by') 1477 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1478 mp.setThresholds(1, 3, 5) 1479 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1480 self.SetToolTipString(_("Enter or select a type for the external ID.")) 1481 self.matcher = mp
1482 #------------------------------------------------------------
1483 -class cExternalIDIssuerPhraseWheel(gmPhraseWheel.cPhraseWheel):
1484
1485 - def __init__(self, *args, **kwargs):
1486 query = u""" 1487 select distinct issuer, issuer 1488 from dem.enum_ext_id_types 1489 where issuer %(fragment_condition)s 1490 order by issuer limit 25""" 1491 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1492 mp.setThresholds(1, 3, 5) 1493 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1494 self.SetToolTipString(_("Type or select an occupation.")) 1495 self.capitalisation_mode = gmTools.CAPS_FIRST 1496 self.matcher = mp
1497 #------------------------------------------------------------ 1498 # edit areas 1499 #------------------------------------------------------------
1500 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl):
1501 """An edit area for editing/creating external IDs. 1502 1503 Does NOT act on/listen to the current patient. 1504 """
1505 - def __init__(self, *args, **kwargs):
1506 1507 try: 1508 self.ext_id = kwargs['external_id'] 1509 del kwargs['external_id'] 1510 except: 1511 self.ext_id = None 1512 1513 wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs) 1514 1515 self.identity = None 1516 1517 self.__register_events() 1518 1519 self.refresh()
1520 #-------------------------------------------------------- 1521 # external API 1522 #--------------------------------------------------------
1523 - def refresh(self, ext_id=None):
1524 if ext_id is not None: 1525 self.ext_id = ext_id 1526 1527 if self.ext_id is not None: 1528 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type']) 1529 self._TCTRL_value.SetValue(self.ext_id['value']) 1530 self._PRW_issuer.SetText(self.ext_id['issuer']) 1531 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
1532 # FIXME: clear fields 1533 # else: 1534 # pass 1535 #--------------------------------------------------------
1536 - def save(self):
1537 1538 if not self.__valid_for_save(): 1539 return False 1540 1541 # strip out " (issued by ...)" added by phrasewheel 1542 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0] 1543 1544 # add new external ID 1545 if self.ext_id is None: 1546 self.identity.add_external_id ( 1547 type_name = type, 1548 value = self._TCTRL_value.GetValue().strip(), 1549 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''), 1550 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1551 ) 1552 # edit old external ID 1553 else: 1554 self.identity.update_external_id ( 1555 pk_id = self.ext_id['pk_id'], 1556 type = type, 1557 value = self._TCTRL_value.GetValue().strip(), 1558 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''), 1559 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1560 ) 1561 1562 return True
1563 #-------------------------------------------------------- 1564 # internal helpers 1565 #--------------------------------------------------------
1566 - def __register_events(self):
1567 self._PRW_type.add_callback_on_lose_focus(self._on_type_set)
1568 #--------------------------------------------------------
1569 - def _on_type_set(self):
1570 """Set the issuer according to the selected type. 1571 1572 Matches are fetched from existing records in backend. 1573 """ 1574 pk_curr_type = self._PRW_type.GetData() 1575 if pk_curr_type is None: 1576 return True 1577 rows, idx = gmPG2.run_ro_queries(queries = [{ 1578 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s", 1579 'args': [pk_curr_type] 1580 }]) 1581 if len(rows) == 0: 1582 return True 1583 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0]) 1584 return True
1585 #--------------------------------------------------------
1586 - def __valid_for_save(self):
1587 1588 no_errors = True 1589 1590 # do not test .GetData() because adding external IDs 1591 # will create types if necessary 1592 # if self._PRW_type.GetData() is None: 1593 if self._PRW_type.GetValue().strip() == u'': 1594 self._PRW_type.SetBackgroundColour('pink') 1595 self._PRW_type.SetFocus() 1596 self._PRW_type.Refresh() 1597 else: 1598 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1599 self._PRW_type.Refresh() 1600 1601 if self._TCTRL_value.GetValue().strip() == u'': 1602 self._TCTRL_value.SetBackgroundColour('pink') 1603 self._TCTRL_value.SetFocus() 1604 self._TCTRL_value.Refresh() 1605 no_errors = False 1606 else: 1607 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1608 self._TCTRL_value.Refresh() 1609 1610 return no_errors
1611 #------------------------------------------------------------
1612 -class cNameGenderDOBEditAreaPnl(wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl):
1613 """An edit area for editing/creating name/gender/dob. 1614 1615 Does NOT act on/listen to the current patient. 1616 """
1617 - def __init__(self, *args, **kwargs):
1618 1619 self.__name = kwargs['name'] 1620 del kwargs['name'] 1621 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity']) 1622 1623 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs) 1624 1625 self.__register_interests() 1626 self.refresh()
1627 #-------------------------------------------------------- 1628 # external API 1629 #--------------------------------------------------------
1630 - def refresh(self):
1631 if self.__name is None: 1632 return 1633 1634 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u'')) 1635 self._PRW_firstname.SetText(self.__name['firstnames']) 1636 self._PRW_lastname.SetText(self.__name['lastnames']) 1637 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u'')) 1638 dob = self.__identity['dob'] 1639 self._PRW_dob.SetText(value = dob.strftime('%Y-%m-%d %H:%M'), data = dob) 1640 self._PRW_gender.SetData(self.__name['gender']) 1641 self._CHBOX_active.SetValue(self.__name['active_name']) 1642 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
1643 # FIXME: clear fields 1644 # else: 1645 # pass 1646 #--------------------------------------------------------
1647 - def save(self):
1648 1649 if not self.__valid_for_save(): 1650 return False 1651 1652 self.__identity['gender'] = self._PRW_gender.GetData() 1653 if self._PRW_dob.GetValue().strip() == u'': 1654 self.__identity['dob'] = None 1655 else: 1656 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt() 1657 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 1658 self.__identity.save_payload() 1659 1660 active = self._CHBOX_active.GetValue() 1661 first = self._PRW_firstname.GetValue().strip() 1662 last = self._PRW_lastname.GetValue().strip() 1663 old_nick = self.__name['preferred'] 1664 1665 # is it a new name ? 1666 old_name = self.__name['firstnames'] + self.__name['lastnames'] 1667 if (first + last) != old_name: 1668 self.__name = self.__identity.add_name(first, last, active) 1669 1670 self.__name['active_name'] = active 1671 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'') 1672 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1673 1674 self.__name.save_payload() 1675 1676 return True
1677 #-------------------------------------------------------- 1678 # event handling 1679 #--------------------------------------------------------
1680 - def __register_interests(self):
1681 self._PRW_firstname.add_callback_on_lose_focus(self._on_name_set)
1682 #--------------------------------------------------------
1683 - def _on_name_set(self):
1684 """Set the gender according to entered firstname. 1685 1686 Matches are fetched from existing records in backend. 1687 """ 1688 firstname = self._PRW_firstname.GetValue().strip() 1689 if firstname == u'': 1690 return True 1691 rows, idx = gmPG2.run_ro_queries(queries = [{ 1692 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 1693 'args': [firstname] 1694 }]) 1695 if len(rows) == 0: 1696 return True 1697 wx.CallAfter(self._PRW_gender.SetData, rows[0][0]) 1698 return True
1699 #-------------------------------------------------------- 1700 # internal helpers 1701 #--------------------------------------------------------
1702 - def __valid_for_save(self):
1703 1704 error_found = True 1705 1706 if self._PRW_gender.GetData() is None: 1707 self._PRW_gender.SetBackgroundColour('pink') 1708 self._PRW_gender.Refresh() 1709 self._PRW_gender.SetFocus() 1710 error_found = False 1711 else: 1712 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1713 self._PRW_gender.Refresh() 1714 1715 if not self._PRW_dob.is_valid_timestamp(): 1716 val = self._PRW_dob.GetValue().strip() 1717 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 1718 self._PRW_dob.SetBackgroundColour('pink') 1719 self._PRW_dob.Refresh() 1720 self._PRW_dob.SetFocus() 1721 error_found = False 1722 else: 1723 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1724 self._PRW_dob.Refresh() 1725 1726 if self._PRW_lastname.GetValue().strip() == u'': 1727 self._PRW_lastname.SetBackgroundColour('pink') 1728 self._PRW_lastname.Refresh() 1729 self._PRW_lastname.SetFocus() 1730 error_found = False 1731 else: 1732 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1733 self._PRW_lastname.Refresh() 1734 1735 if self._PRW_firstname.GetValue().strip() == u'': 1736 self._PRW_firstname.SetBackgroundColour('pink') 1737 self._PRW_firstname.Refresh() 1738 self._PRW_firstname.SetFocus() 1739 error_found = False 1740 else: 1741 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1742 self._PRW_firstname.Refresh() 1743 1744 return error_found
1745 #------------------------------------------------------------ 1746 # list manager 1747 #------------------------------------------------------------
1748 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
1749 """A list for managing a person's names. 1750 1751 Does NOT act on/listen to the current patient. 1752 """
1753 - def __init__(self, *args, **kwargs):
1754 1755 try: 1756 self.__identity = kwargs['identity'] 1757 del kwargs['identity'] 1758 except KeyError: 1759 self.__identity = None 1760 1761 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1762 1763 self.new_callback = self._add_name 1764 self.edit_callback = self._edit_name 1765 self.delete_callback = self._del_name 1766 self.refresh_callback = self.refresh 1767 1768 self.__init_ui() 1769 self.refresh()
1770 #-------------------------------------------------------- 1771 # external API 1772 #--------------------------------------------------------
1773 - def refresh(self, *args, **kwargs):
1774 if self.__identity is None: 1775 self._LCTRL_items.set_string_items() 1776 return 1777 1778 names = self.__identity.get_names() 1779 self._LCTRL_items.set_string_items ( 1780 items = [ [ 1781 gmTools.bool2str(n['active_name'], 'X', ''), 1782 gmTools.coalesce(n['title'], gmPerson.map_gender2salutation(n['gender'])), 1783 n['lastnames'], 1784 n['firstnames'], 1785 gmTools.coalesce(n['preferred'], u''), 1786 gmTools.coalesce(n['comment'], u'') 1787 ] for n in names ] 1788 ) 1789 self._LCTRL_items.set_column_widths() 1790 self._LCTRL_items.set_data(data = names)
1791 #-------------------------------------------------------- 1792 # internal helpers 1793 #--------------------------------------------------------
1794 - def __init_ui(self):
1795 self._LCTRL_items.set_columns(columns = [ 1796 _('Active'), 1797 _('Title'), 1798 _('Lastname'), 1799 _('Firstname(s)'), 1800 _('Preferred Name'), 1801 _('Comment') 1802 ])
1803 #--------------------------------------------------------
1804 - def _add_name(self):
1805 ea = cNameGenderDOBEditAreaPnl(self, -1, name = self.__identity.get_active_name()) 1806 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1807 dlg.SetTitle(_('Adding new name')) 1808 if dlg.ShowModal() == wx.ID_OK: 1809 dlg.Destroy() 1810 return True 1811 dlg.Destroy() 1812 return False
1813 #--------------------------------------------------------
1814 - def _edit_name(self, name):
1815 ea = cNameGenderDOBEditAreaPnl(self, -1, name = name) 1816 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1817 dlg.SetTitle(_('Editing name')) 1818 if dlg.ShowModal() == wx.ID_OK: 1819 dlg.Destroy() 1820 return True 1821 dlg.Destroy() 1822 return False
1823 #--------------------------------------------------------
1824 - def _del_name(self, name):
1825 1826 if len(self.__identity.get_names()) == 1: 1827 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True) 1828 return False 1829 1830 go_ahead = gmGuiHelpers.gm_show_question ( 1831 _( 'It is often advisable to keep old names around and\n' 1832 'just create a new "currently active" name.\n' 1833 '\n' 1834 'This allows finding the patient by both the old\n' 1835 'and the new name (think before/after marriage).\n' 1836 '\n' 1837 'Do you still want to really delete\n' 1838 "this name from the patient ?" 1839 ), 1840 _('Deleting name') 1841 ) 1842 if not go_ahead: 1843 return False 1844 1845 self.__identity.delete_name(name = name) 1846 return True
1847 #-------------------------------------------------------- 1848 # properties 1849 #--------------------------------------------------------
1850 - def _get_identity(self):
1851 return self.__identity
1852
1853 - def _set_identity(self, identity):
1854 self.__identity = identity 1855 self.refresh()
1856 1857 identity = property(_get_identity, _set_identity)
1858 #------------------------------------------------------------
1859 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1860 """A list for managing a person's external IDs. 1861 1862 Does NOT act on/listen to the current patient. 1863 """
1864 - def __init__(self, *args, **kwargs):
1865 1866 try: 1867 self.__identity = kwargs['identity'] 1868 del kwargs['identity'] 1869 except KeyError: 1870 self.__identity = None 1871 1872 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1873 1874 self.new_callback = self._add_id 1875 self.edit_callback = self._edit_id 1876 self.delete_callback = self._del_id 1877 self.refresh_callback = self.refresh 1878 1879 self.__init_ui() 1880 self.refresh()
1881 #-------------------------------------------------------- 1882 # external API 1883 #--------------------------------------------------------
1884 - def refresh(self, *args, **kwargs):
1885 if self.__identity is None: 1886 self._LCTRL_items.set_string_items() 1887 return 1888 1889 ids = self.__identity.get_external_ids() 1890 self._LCTRL_items.set_string_items ( 1891 items = [ [ 1892 i['name'], 1893 i['value'], 1894 gmTools.coalesce(i['issuer'], u''), 1895 i['context'], 1896 gmTools.coalesce(i['comment'], u'') 1897 ] for i in ids 1898 ] 1899 ) 1900 self._LCTRL_items.set_column_widths() 1901 self._LCTRL_items.set_data(data = ids)
1902 #-------------------------------------------------------- 1903 # internal helpers 1904 #--------------------------------------------------------
1905 - def __init_ui(self):
1906 self._LCTRL_items.set_columns(columns = [ 1907 _('ID type'), 1908 _('Value'), 1909 _('Issuer'), 1910 _('Context'), 1911 _('Comment') 1912 ])
1913 #--------------------------------------------------------
1914 - def _add_id(self):
1915 ea = cExternalIDEditAreaPnl(self, -1) 1916 ea.identity = self.__identity 1917 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1918 dlg.SetTitle(_('Adding new external ID')) 1919 if dlg.ShowModal() == wx.ID_OK: 1920 dlg.Destroy() 1921 return True 1922 dlg.Destroy() 1923 return False
1924 #--------------------------------------------------------
1925 - def _edit_id(self, ext_id):
1926 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id) 1927 ea.identity = self.__identity 1928 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1929 dlg.SetTitle(_('Editing external ID')) 1930 if dlg.ShowModal() == wx.ID_OK: 1931 dlg.Destroy() 1932 return True 1933 dlg.Destroy() 1934 return False
1935 #--------------------------------------------------------
1936 - def _del_id(self, ext_id):
1937 go_ahead = gmGuiHelpers.gm_show_question ( 1938 _( 'Do you really want to delete this\n' 1939 'external ID from the patient ?'), 1940 _('Deleting external ID') 1941 ) 1942 if not go_ahead: 1943 return False 1944 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id']) 1945 return True
1946 #-------------------------------------------------------- 1947 # properties 1948 #--------------------------------------------------------
1949 - def _get_identity(self):
1950 return self.__identity
1951
1952 - def _set_identity(self, identity):
1953 self.__identity = identity 1954 self.refresh()
1955 1956 identity = property(_get_identity, _set_identity)
1957 #------------------------------------------------------------ 1958 # integrated panels 1959 #------------------------------------------------------------
1960 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
1961 """A panel for editing identity data for a person. 1962 1963 - provides access to: 1964 - name 1965 - external IDs 1966 1967 Does NOT act on/listen to the current patient. 1968 """
1969 - def __init__(self, *args, **kwargs):
1970 1971 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs) 1972 1973 self.__identity = None 1974 self.refresh()
1975 #-------------------------------------------------------- 1976 # external API 1977 #--------------------------------------------------------
1978 - def refresh(self):
1979 self._PNL_names.identity = self.__identity 1980 self._PNL_ids.identity = self.__identity
1981 #-------------------------------------------------------- 1982 # properties 1983 #--------------------------------------------------------
1984 - def _get_identity(self):
1985 return self.__identity
1986
1987 - def _set_identity(self, identity):
1988 self.__identity = identity 1989 self.refresh()
1990 1991 identity = property(_get_identity, _set_identity)
1992 #============================================================ 1993 # new-patient widgets 1994 #============================================================
1995 -def create_new_person(parent=None, activate=False):
1996 1997 dbcfg = gmCfg.cCfgSQL() 1998 1999 def_region = dbcfg.get2 ( 2000 option = u'person.create.default_region', 2001 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2002 bias = u'user' 2003 ) 2004 2005 if def_region is None: 2006 def_country = dbcfg.get2 ( 2007 option = u'person.create.default_country', 2008 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2009 bias = u'user' 2010 ) 2011 else: 2012 countries = gmDemographicRecord.get_country_for_region(region = def_region) 2013 if len(countries) == 1: 2014 def_country = countries[0]['l10n_country'] 2015 2016 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region) 2017 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 2018 dlg.SetTitle(_('Adding new person')) 2019 ea._PRW_lastname.SetFocus() 2020 result = dlg.ShowModal() 2021 pat = ea.data 2022 dlg.Destroy() 2023 2024 if result != wx.ID_OK: 2025 return False 2026 2027 if activate: 2028 from Gnumed.wxpython import gmPatSearchWidgets 2029 gmPatSearchWidgets.set_active_patient(patient = pat) 2030 2031 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 2032 2033 return True
2034 #============================================================ 2035 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 2036
2037 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
2038
2039 - def __init__(self, *args, **kwargs):
2040 2041 try: 2042 self.default_region = kwargs['region'] 2043 del kwargs['region'] 2044 except KeyError: 2045 self.default_region = None 2046 2047 try: 2048 self.default_country = kwargs['country'] 2049 del kwargs['country'] 2050 except KeyError: 2051 self.default_country = None 2052 2053 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 2054 gmEditArea.cGenericEditAreaMixin.__init__(self) 2055 2056 self.mode = 'new' 2057 self.data = None 2058 self._address = None 2059 2060 self.__init_ui() 2061 self.__register_interests()
2062 #---------------------------------------------------------------- 2063 # internal helpers 2064 #----------------------------------------------------------------
2065 - def __init_ui(self):
2066 self._PRW_lastname.final_regex = '.+' 2067 self._PRW_firstnames.final_regex = '.+' 2068 self._PRW_address_searcher.selection_only = False 2069 low = wx.DateTimeFromDMY(1,0,1900) 2070 hi = wx.DateTime() 2071 self._DP_dob.SetRange(low, hi.SetToCurrent()) 2072 # only if we would support None on selection_only's 2073 #self._PRW_external_id_type.selection_only = True 2074 2075 if self.default_country is not None: 2076 self._PRW_country.SetText(value = self.default_country) 2077 2078 if self.default_region is not None: 2079 self._PRW_region.SetText(value = self.default_region)
2080 #----------------------------------------------------------------
2081 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
2082 2083 adr = self._PRW_address_searcher.get_address() 2084 if adr is None: 2085 return True 2086 2087 if ctrl.GetValue().strip() != adr[field]: 2088 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None) 2089 return True 2090 2091 return False
2092 #----------------------------------------------------------------
2094 adr = self._PRW_address_searcher.get_address() 2095 if adr is None: 2096 return True 2097 2098 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 2099 2100 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 2101 self._PRW_street.set_context(context = u'zip', val = adr['postcode']) 2102 2103 self._TCTRL_number.SetValue(adr['number']) 2104 2105 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 2106 self._PRW_urb.set_context(context = u'zip', val = adr['postcode']) 2107 2108 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state']) 2109 self._PRW_region.set_context(context = u'zip', val = adr['postcode']) 2110 2111 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 2112 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2113 #----------------------------------------------------------------
2114 - def __identity_valid_for_save(self):
2115 error = False 2116 2117 # name fields 2118 if self._PRW_lastname.GetValue().strip() == u'': 2119 error = True 2120 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2121 self._PRW_lastname.display_as_valid(False) 2122 else: 2123 self._PRW_lastname.display_as_valid(True) 2124 2125 if self._PRW_firstnames.GetValue().strip() == '': 2126 error = True 2127 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2128 self._PRW_firstnames.display_as_valid(False) 2129 else: 2130 self._PRW_firstnames.display_as_valid(True) 2131 2132 # gender 2133 if self._PRW_gender.GetData() is None: 2134 error = True 2135 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2136 self._PRW_gender.display_as_valid(False) 2137 else: 2138 self._PRW_gender.display_as_valid(True) 2139 2140 # dob validation 2141 if not self._DP_dob.is_valid_timestamp(): 2142 2143 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?')) 2144 2145 do_it_anyway = gmGuiHelpers.gm_show_question ( 2146 _( 2147 'Are you sure you want to register this person\n' 2148 'without a valid date of birth ?\n' 2149 '\n' 2150 'This can be useful for temporary staff members\n' 2151 'but will provoke nag screens if this person\n' 2152 'becomes a patient.\n' 2153 '\n' 2154 'Note that the date of birth cannot technically\n' 2155 'be before 1900, either :-(\n' 2156 ), 2157 _('Registering new person') 2158 ) 2159 2160 if not do_it_anyway: 2161 error = True 2162 2163 if self._DP_dob.GetValue().GetYear() < 1900: 2164 error = True 2165 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True) 2166 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2167 self._DP_dob.SetFocus() 2168 else: 2169 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2170 self._DP_dob.Refresh() 2171 2172 # TOB validation if non-empty 2173 # if self._TCTRL_tob.GetValue().strip() != u'': 2174 2175 return (not error)
2176 #----------------------------------------------------------------
2177 - def __address_valid_for_save(self, empty_address_is_valid=False):
2178 2179 # existing address ? if so set other fields 2180 if self._PRW_address_searcher.GetData() is not None: 2181 wx.CallAfter(self.__set_fields_from_address_searcher) 2182 return True 2183 2184 # must either all contain something or none of them 2185 fields_to_fill = ( 2186 self._TCTRL_number, 2187 self._PRW_zip, 2188 self._PRW_street, 2189 self._PRW_urb, 2190 self._PRW_region, 2191 self._PRW_country 2192 ) 2193 no_of_filled_fields = 0 2194 2195 for field in fields_to_fill: 2196 if field.GetValue().strip() != u'': 2197 no_of_filled_fields += 1 2198 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2199 field.Refresh() 2200 2201 # empty address ? 2202 if no_of_filled_fields == 0: 2203 if empty_address_is_valid: 2204 return True 2205 else: 2206 return None 2207 2208 # incompletely filled address ? 2209 if no_of_filled_fields != len(fields_to_fill): 2210 for field in fields_to_fill: 2211 if field.GetValue().strip() == u'': 2212 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2213 field.SetFocus() 2214 field.Refresh() 2215 msg = _('To properly create an address, all the related fields must be filled in.') 2216 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2217 return False 2218 2219 # fields which must contain a selected item 2220 # FIXME: they must also contain an *acceptable combination* which 2221 # FIXME: can only be tested against the database itself ... 2222 strict_fields = ( 2223 self._PRW_region, 2224 self._PRW_country 2225 ) 2226 error = False 2227 for field in strict_fields: 2228 if field.GetData() is None: 2229 error = True 2230 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2231 field.SetFocus() 2232 else: 2233 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2234 field.Refresh() 2235 2236 if error: 2237 msg = _('This field must contain an item selected from the dropdown list.') 2238 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2239 return False 2240 2241 return True
2242 #----------------------------------------------------------------
2243 - def __register_interests(self):
2244 2245 # identity 2246 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 2247 2248 # address 2249 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 2250 2251 # invalidate address searcher when any field edited 2252 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 2253 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher) 2254 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 2255 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 2256 2257 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 2258 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
2259 #---------------------------------------------------------------- 2260 # event handlers 2261 #----------------------------------------------------------------
2262 - def _on_leaving_firstname(self):
2263 """Set the gender according to entered firstname. 2264 2265 Matches are fetched from existing records in backend. 2266 """ 2267 # only set if not already set so as to not 2268 # overwrite a change by the user 2269 if self._PRW_gender.GetData() is not None: 2270 return True 2271 2272 firstname = self._PRW_firstnames.GetValue().strip() 2273 if firstname == u'': 2274 return True 2275 2276 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 2277 if gender is None: 2278 return True 2279 2280 wx.CallAfter(self._PRW_gender.SetData, gender) 2281 return True
2282 #----------------------------------------------------------------
2283 - def _on_leaving_zip(self):
2284 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 2285 2286 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'') 2287 self._PRW_street.set_context(context = u'zip', val = zip_code) 2288 self._PRW_urb.set_context(context = u'zip', val = zip_code) 2289 self._PRW_region.set_context(context = u'zip', val = zip_code) 2290 self._PRW_country.set_context(context = u'zip', val = zip_code) 2291 2292 return True
2293 #----------------------------------------------------------------
2294 - def _on_leaving_country(self):
2295 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 2296 2297 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'') 2298 self._PRW_region.set_context(context = u'country', val = country) 2299 2300 return True
2301 #----------------------------------------------------------------
2302 - def _invalidate_address_searcher(self, *args, **kwargs):
2303 mapping = [ 2304 (self._PRW_street, 'street'), 2305 (self._TCTRL_number, 'number'), 2306 (self._PRW_urb, 'urb'), 2307 (self._PRW_region, 'l10n_state') 2308 ] 2309 2310 # loop through fields and invalidate address searcher if different 2311 for ctrl, field in mapping: 2312 if self.__perhaps_invalidate_address_searcher(ctrl, field): 2313 return True 2314 2315 return True
2316 #----------------------------------------------------------------
2318 adr = self._PRW_address_searcher.get_address() 2319 if adr is None: 2320 return True 2321 2322 wx.CallAfter(self.__set_fields_from_address_searcher) 2323 return True
2324 #---------------------------------------------------------------- 2325 # generic Edit Area mixin API 2326 #----------------------------------------------------------------
2327 - def _valid_for_save(self):
2328 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2329 #----------------------------------------------------------------
2330 - def _save_as_new(self):
2331 2332 # identity 2333 new_identity = gmPerson.create_identity ( 2334 gender = self._PRW_gender.GetData(), 2335 dob = self._DP_dob.get_pydt(), 2336 lastnames = self._PRW_lastname.GetValue().strip(), 2337 firstnames = self._PRW_firstnames.GetValue().strip() 2338 ) 2339 _log.debug('identity created: %s' % new_identity) 2340 2341 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 2342 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u'')) 2343 #TOB 2344 new_identity.save() 2345 2346 name = new_identity.get_active_name() 2347 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 2348 name.save() 2349 2350 # address 2351 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 2352 if is_valid is True: 2353 # because we currently only check for non-emptiness 2354 # we must still deal with database errors 2355 try: 2356 new_identity.link_address ( 2357 number = self._TCTRL_number.GetValue().strip(), 2358 street = self._PRW_street.GetValue().strip(), 2359 postcode = self._PRW_zip.GetValue().strip(), 2360 urb = self._PRW_urb.GetValue().strip(), 2361 state = self._PRW_region.GetData(), 2362 country = self._PRW_country.GetData() 2363 ) 2364 except psycopg2.InternalError: 2365 #except StandardError: 2366 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 2367 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 2368 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 2369 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 2370 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip()) 2371 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 2372 _log.exception('cannot link address') 2373 gmGuiHelpers.gm_show_error ( 2374 aTitle = _('Saving address'), 2375 aMessage = _( 2376 'Cannot save this address.\n' 2377 '\n' 2378 'You will have to add it via the Demographics plugin.\n' 2379 ) 2380 ) 2381 elif is_valid is False: 2382 gmGuiHelpers.gm_show_error ( 2383 aTitle = _('Saving address'), 2384 aMessage = _( 2385 'Address not saved.\n' 2386 '\n' 2387 'You will have to add it via the Demographics plugin.\n' 2388 ) 2389 ) 2390 # else it is None which means empty address which we ignore 2391 2392 # phone 2393 new_identity.link_comm_channel ( 2394 comm_medium = u'homephone', 2395 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''), 2396 is_confidential = False 2397 ) 2398 2399 # external ID 2400 pk_type = self._PRW_external_id_type.GetData() 2401 id_value = self._TCTRL_external_id_value.GetValue().strip() 2402 if (pk_type is not None) and (id_value != u''): 2403 new_identity.add_external_id(value = id_value, pk_type = pk_type) 2404 2405 # occupation 2406 new_identity.link_occupation ( 2407 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'') 2408 ) 2409 2410 self.data = new_identity 2411 return True
2412 #----------------------------------------------------------------
2413 - def _save_as_update(self):
2414 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2415 #----------------------------------------------------------------
2416 - def _refresh_as_new(self):
2417 # FIXME: button "empty out" 2418 return
2419 #----------------------------------------------------------------
2420 - def _refresh_from_existing(self):
2421 return # there is no forward button so nothing to do here
2422 #----------------------------------------------------------------
2424 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2425 #============================================================ 2426 # new-patient wizard classes 2427 #============================================================
2428 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2429 """ 2430 Wizard page for entering patient's basic demographic information 2431 """ 2432 2433 form_fields = ( 2434 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation', 2435 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment' 2436 ) 2437
2438 - def __init__(self, parent, title):
2439 """ 2440 Creates a new instance of BasicPatDetailsPage 2441 @param parent - The parent widget 2442 @type parent - A wx.Window instance 2443 @param tile - The title of the page 2444 @type title - A StringType instance 2445 """ 2446 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson')) 2447 self.__title = title 2448 self.__do_layout() 2449 self.__register_interests()
2450 #--------------------------------------------------------
2451 - def __do_layout(self):
2452 PNL_form = wx.Panel(self, -1) 2453 2454 # last name 2455 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name')) 2456 STT_lastname.SetForegroundColour('red') 2457 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1) 2458 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)')) 2459 2460 # first name 2461 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)')) 2462 STT_firstname.SetForegroundColour('red') 2463 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1) 2464 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name')) 2465 2466 # nickname 2467 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name')) 2468 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1) 2469 2470 # DOB 2471 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth')) 2472 STT_dob.SetForegroundColour('red') 2473 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1) 2474 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one")) 2475 2476 # gender 2477 STT_gender = wx.StaticText(PNL_form, -1, _('Gender')) 2478 STT_gender.SetForegroundColour('red') 2479 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1) 2480 self.PRW_gender.SetToolTipString(_("Required: gender of patient")) 2481 2482 # title 2483 STT_title = wx.StaticText(PNL_form, -1, _('Title')) 2484 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1) 2485 2486 # zip code 2487 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code')) 2488 STT_zip_code.SetForegroundColour('orange') 2489 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1) 2490 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code")) 2491 2492 # street 2493 STT_street = wx.StaticText(PNL_form, -1, _('Street')) 2494 STT_street.SetForegroundColour('orange') 2495 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1) 2496 self.PRW_street.SetToolTipString(_("primary/home address: name of street")) 2497 2498 # address number 2499 STT_address_number = wx.StaticText(PNL_form, -1, _('Number')) 2500 STT_address_number.SetForegroundColour('orange') 2501 self.TTC_address_number = wx.TextCtrl(PNL_form, -1) 2502 self.TTC_address_number.SetToolTipString(_("primary/home address: address number")) 2503 2504 # town 2505 STT_town = wx.StaticText(PNL_form, -1, _('Place')) 2506 STT_town.SetForegroundColour('orange') 2507 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1) 2508 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/...")) 2509 2510 # state 2511 STT_state = wx.StaticText(PNL_form, -1, _('Region')) 2512 STT_state.SetForegroundColour('orange') 2513 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1) 2514 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/...")) 2515 2516 # country 2517 STT_country = wx.StaticText(PNL_form, -1, _('Country')) 2518 STT_country.SetForegroundColour('orange') 2519 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1) 2520 self.PRW_country.SetToolTipString(_("primary/home address: country")) 2521 2522 # phone 2523 STT_phone = wx.StaticText(PNL_form, -1, _('Phone')) 2524 self.TTC_phone = wx.TextCtrl(PNL_form, -1) 2525 self.TTC_phone.SetToolTipString(_("phone number at home")) 2526 2527 # occupation 2528 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 2529 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 2530 2531 # comment 2532 STT_comment = wx.StaticText(PNL_form, -1, _('Comment')) 2533 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1) 2534 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.')) 2535 2536 # form main validator 2537 self.form_DTD = cFormDTD(fields = self.__class__.form_fields) 2538 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD)) 2539 2540 # layout input widgets 2541 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4) 2542 SZR_input.AddGrowableCol(1) 2543 SZR_input.Add(STT_lastname, 0, wx.SHAPED) 2544 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND) 2545 SZR_input.Add(STT_firstname, 0, wx.SHAPED) 2546 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND) 2547 SZR_input.Add(STT_nick, 0, wx.SHAPED) 2548 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND) 2549 SZR_input.Add(STT_dob, 0, wx.SHAPED) 2550 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND) 2551 SZR_input.Add(STT_gender, 0, wx.SHAPED) 2552 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND) 2553 SZR_input.Add(STT_title, 0, wx.SHAPED) 2554 SZR_input.Add(self.PRW_title, 1, wx.EXPAND) 2555 SZR_input.Add(STT_zip_code, 0, wx.SHAPED) 2556 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND) 2557 SZR_input.Add(STT_street, 0, wx.SHAPED) 2558 SZR_input.Add(self.PRW_street, 1, wx.EXPAND) 2559 SZR_input.Add(STT_address_number, 0, wx.SHAPED) 2560 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND) 2561 SZR_input.Add(STT_town, 0, wx.SHAPED) 2562 SZR_input.Add(self.PRW_town, 1, wx.EXPAND) 2563 SZR_input.Add(STT_state, 0, wx.SHAPED) 2564 SZR_input.Add(self.PRW_state, 1, wx.EXPAND) 2565 SZR_input.Add(STT_country, 0, wx.SHAPED) 2566 SZR_input.Add(self.PRW_country, 1, wx.EXPAND) 2567 SZR_input.Add(STT_phone, 0, wx.SHAPED) 2568 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND) 2569 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 2570 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 2571 SZR_input.Add(STT_comment, 0, wx.SHAPED) 2572 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND) 2573 2574 PNL_form.SetSizerAndFit(SZR_input) 2575 2576 # layout page 2577 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title) 2578 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2579 #-------------------------------------------------------- 2580 # event handling 2581 #--------------------------------------------------------
2582 - def __register_interests(self):
2583 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set) 2584 self.PRW_country.add_callback_on_selection(self.on_country_selected) 2585 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
2586 #--------------------------------------------------------
2587 - def on_country_selected(self, data):
2588 """Set the states according to entered country.""" 2589 self.PRW_state.set_context(context=u'country', val=data) 2590 return True
2591 #--------------------------------------------------------
2592 - def on_name_set(self):
2593 """Set the gender according to entered firstname. 2594 2595 Matches are fetched from existing records in backend. 2596 """ 2597 firstname = self.PRW_firstname.GetValue().strip() 2598 rows, idx = gmPG2.run_ro_queries(queries = [{ 2599 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 2600 'args': [firstname] 2601 }]) 2602 if len(rows) == 0: 2603 return True 2604 wx.CallAfter(self.PRW_gender.SetData, rows[0][0]) 2605 return True
2606 #--------------------------------------------------------
2607 - def on_zip_set(self):
2608 """Set the street, town, state and country according to entered zip code.""" 2609 zip_code = self.PRW_zip_code.GetValue().strip() 2610 self.PRW_street.set_context(context=u'zip', val=zip_code) 2611 self.PRW_town.set_context(context=u'zip', val=zip_code) 2612 self.PRW_state.set_context(context=u'zip', val=zip_code) 2613 self.PRW_country.set_context(context=u'zip', val=zip_code) 2614 return True
2615 #============================================================
2616 -class cNewPatientWizard(wx.wizard.Wizard):
2617 """ 2618 Wizard to create a new patient. 2619 2620 TODO: 2621 - write pages for different "themes" of patient creation 2622 - make it configurable which pages are loaded 2623 - make available sets of pages that apply to a country 2624 - make loading of some pages depend upon values in earlier pages, eg 2625 when the patient is female and older than 13 include a page about 2626 "female" data (number of kids etc) 2627 2628 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable() 2629 """ 2630 #--------------------------------------------------------
2631 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2632 """ 2633 Creates a new instance of NewPatientWizard 2634 @param parent - The parent widget 2635 @type parent - A wx.Window instance 2636 """ 2637 id_wiz = wx.NewId() 2638 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap() 2639 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY) 2640 self.__subtitle = subtitle 2641 self.__do_layout()
2642 #--------------------------------------------------------
2643 - def RunWizard(self, activate=False):
2644 """Create new patient. 2645 2646 activate, too, if told to do so (and patient successfully created) 2647 """ 2648 while True: 2649 2650 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details): 2651 return False 2652 2653 try: 2654 # retrieve DTD and create patient 2655 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD) 2656 except: 2657 _log.exception('cannot add new patient - missing identity fields') 2658 gmGuiHelpers.gm_show_error ( 2659 _('Cannot create new patient.\n' 2660 'Missing parts of the identity.' 2661 ), 2662 _('Adding new patient') 2663 ) 2664 continue 2665 2666 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2667 2668 try: 2669 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2670 except: 2671 _log.exception('cannot finalize new patient - missing address fields') 2672 gmGuiHelpers.gm_show_error ( 2673 _('Cannot add address for the new patient.\n' 2674 'You must either enter all of the address fields or\n' 2675 'none at all. The relevant fields are marked in yellow.\n' 2676 '\n' 2677 'You will need to add the address details in the\n' 2678 'demographics module.' 2679 ), 2680 _('Adding new patient') 2681 ) 2682 break 2683 2684 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2685 2686 break 2687 2688 if activate: 2689 from Gnumed.wxpython import gmPatSearchWidgets 2690 gmPatSearchWidgets.set_active_patient(patient = ident) 2691 2692 return ident
2693 #-------------------------------------------------------- 2694 # internal helpers 2695 #--------------------------------------------------------
2696 - def __do_layout(self):
2697 """Arrange widgets. 2698 """ 2699 # Create the wizard pages 2700 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle ) 2701 self.FitToPage(self.basic_pat_details)
2702 #============================================================
2703 -class cBasicPatDetailsPageValidator(wx.PyValidator):
2704 """ 2705 This validator is used to ensure that the user has entered all 2706 the required conditional values in the page (eg., to properly 2707 create an address, all the related fields must be filled). 2708 """ 2709 #--------------------------------------------------------
2710 - def __init__(self, dtd):
2711 """ 2712 Validator initialization. 2713 @param dtd The object containing the data model. 2714 @type dtd A cFormDTD instance 2715 """ 2716 # initialize parent class 2717 wx.PyValidator.__init__(self) 2718 # validator's storage object 2719 self.form_DTD = dtd
2720 #--------------------------------------------------------
2721 - def Clone(self):
2722 """ 2723 Standard cloner. 2724 Note that every validator must implement the Clone() method. 2725 """ 2726 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
2727 #--------------------------------------------------------
2728 - def Validate(self, parent = None):
2729 """ 2730 Validate the contents of the given text control. 2731 """ 2732 _pnl_form = self.GetWindow().GetParent() 2733 2734 error = False 2735 2736 # name fields 2737 if _pnl_form.PRW_lastname.GetValue().strip() == '': 2738 error = True 2739 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2740 _pnl_form.PRW_lastname.SetBackgroundColour('pink') 2741 _pnl_form.PRW_lastname.Refresh() 2742 else: 2743 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2744 _pnl_form.PRW_lastname.Refresh() 2745 2746 if _pnl_form.PRW_firstname.GetValue().strip() == '': 2747 error = True 2748 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2749 _pnl_form.PRW_firstname.SetBackgroundColour('pink') 2750 _pnl_form.PRW_firstname.Refresh() 2751 else: 2752 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2753 _pnl_form.PRW_firstname.Refresh() 2754 2755 # gender 2756 if _pnl_form.PRW_gender.GetData() is None: 2757 error = True 2758 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2759 _pnl_form.PRW_gender.SetBackgroundColour('pink') 2760 _pnl_form.PRW_gender.Refresh() 2761 else: 2762 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2763 _pnl_form.PRW_gender.Refresh() 2764 2765 # dob validation 2766 if ( 2767 (_pnl_form.PRW_dob.GetValue().strip() == u'') 2768 or (not _pnl_form.PRW_dob.is_valid_timestamp()) 2769 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900) 2770 ): 2771 error = True 2772 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue() 2773 gmDispatcher.send(signal = 'statustext', msg = msg) 2774 _pnl_form.PRW_dob.SetBackgroundColour('pink') 2775 _pnl_form.PRW_dob.Refresh() 2776 else: 2777 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2778 _pnl_form.PRW_dob.Refresh() 2779 2780 # address 2781 is_any_field_filled = False 2782 address_fields = ( 2783 _pnl_form.TTC_address_number, 2784 _pnl_form.PRW_zip_code, 2785 _pnl_form.PRW_street, 2786 _pnl_form.PRW_town 2787 ) 2788 for field in address_fields: 2789 if field.GetValue().strip() == u'': 2790 if is_any_field_filled: 2791 error = True 2792 msg = _('To properly create an address, all the related fields must be filled in.') 2793 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2794 field.SetBackgroundColour('pink') 2795 field.SetFocus() 2796 field.Refresh() 2797 else: 2798 is_any_field_filled = True 2799 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2800 field.Refresh() 2801 2802 address_fields = ( 2803 _pnl_form.PRW_state, 2804 _pnl_form.PRW_country 2805 ) 2806 for field in address_fields: 2807 if field.GetData() is None: 2808 if is_any_field_filled: 2809 error = True 2810 msg = _('To properly create an address, all the related fields must be filled in.') 2811 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2812 field.SetBackgroundColour('pink') 2813 field.SetFocus() 2814 field.Refresh() 2815 else: 2816 is_any_field_filled = True 2817 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2818 field.Refresh() 2819 2820 return (not error)
2821 #--------------------------------------------------------
2822 - def TransferToWindow(self):
2823 """ 2824 Transfer data from validator to window. 2825 The default implementation returns False, indicating that an error 2826 occurred. We simply return True, as we don't do any data transfer. 2827 """ 2828 _pnl_form = self.GetWindow().GetParent() 2829 # fill in controls with values from self.form_DTD 2830 _pnl_form.PRW_gender.SetData(self.form_DTD['gender']) 2831 _pnl_form.PRW_dob.SetText(self.form_DTD['dob']) 2832 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames']) 2833 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames']) 2834 _pnl_form.PRW_title.SetText(self.form_DTD['title']) 2835 _pnl_form.PRW_nick.SetText(self.form_DTD['nick']) 2836 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation']) 2837 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number']) 2838 _pnl_form.PRW_street.SetText(self.form_DTD['street']) 2839 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code']) 2840 _pnl_form.PRW_town.SetText(self.form_DTD['town']) 2841 _pnl_form.PRW_state.SetData(self.form_DTD['state']) 2842 _pnl_form.PRW_country.SetData(self.form_DTD['country']) 2843 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone']) 2844 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment']) 2845 return True # Prevent wxDialog from complaining
2846 #--------------------------------------------------------
2847 - def TransferFromWindow(self):
2848 """ 2849 Transfer data from window to validator. 2850 The default implementation returns False, indicating that an error 2851 occurred. We simply return True, as we don't do any data transfer. 2852 """ 2853 # FIXME: should be called automatically 2854 if not self.GetWindow().GetParent().Validate(): 2855 return False 2856 try: 2857 _pnl_form = self.GetWindow().GetParent() 2858 # fill in self.form_DTD with values from controls 2859 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData() 2860 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData() 2861 2862 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue() 2863 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue() 2864 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue() 2865 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue() 2866 2867 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue() 2868 2869 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue() 2870 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue() 2871 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue() 2872 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue() 2873 self.form_DTD['state'] = _pnl_form.PRW_state.GetData() 2874 self.form_DTD['country'] = _pnl_form.PRW_country.GetData() 2875 2876 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue() 2877 2878 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue() 2879 except: 2880 return False 2881 return True
2882 #============================================================
2883 -class cFormDTD:
2884 """ 2885 Simple Data Transfer Dictionary class to make easy the trasfer of 2886 data between the form (view) and the business logic. 2887 2888 Maybe later consider turning this into a standard dict by 2889 {}.fromkeys([key, key, ...], default) when it becomes clear that 2890 we really don't need the added potential of a full-fledged class. 2891 """
2892 - def __init__(self, fields):
2893 """ 2894 Initialize the DTD with the supplied field names. 2895 @param fields The names of the fields. 2896 @type fields A TupleType instance. 2897 """ 2898 self.data = {} 2899 for a_field in fields: 2900 self.data[a_field] = ''
2901
2902 - def __getitem__(self, attribute):
2903 """ 2904 Retrieve the value of the given attribute (key) 2905 @param attribute The attribute (key) to retrieve its value for. 2906 @type attribute a StringType instance. 2907 """ 2908 if not self.data[attribute]: 2909 return '' 2910 return self.data[attribute]
2911
2912 - def __setitem__(self, attribute, value):
2913 """ 2914 Set the value of a given attribute (key). 2915 @param attribute The attribute (key) to set its value for. 2916 @type attribute a StringType instance. 2917 @param avaluee The value to set. 2918 @rtpe attribute a StringType instance. 2919 """ 2920 self.data[attribute] = value
2921
2922 - def __str__(self):
2923 """ 2924 Print string representation of the DTD object. 2925 """ 2926 return str(self.data)
2927 #============================================================ 2928 # patient demographics editing classes 2929 #============================================================
2930 -class cPersonDemographicsEditorNb(wx.Notebook):
2931 """Notebook displaying demographics editing pages: 2932 2933 - Identity 2934 - Contacts (addresses, phone numbers, etc) 2935 2936 Does NOT act on/listen to the current patient. 2937 """ 2938 #--------------------------------------------------------
2939 - def __init__(self, parent, id):
2940 2941 wx.Notebook.__init__ ( 2942 self, 2943 parent = parent, 2944 id = id, 2945 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 2946 name = self.__class__.__name__ 2947 ) 2948 2949 self.__identity = None 2950 self.__do_layout() 2951 self.SetSelection(0)
2952 #-------------------------------------------------------- 2953 # public API 2954 #--------------------------------------------------------
2955 - def refresh(self):
2956 """Populate fields in pages with data from model.""" 2957 for page_idx in range(self.GetPageCount()): 2958 page = self.GetPage(page_idx) 2959 page.identity = self.__identity 2960 2961 return True
2962 #-------------------------------------------------------- 2963 # internal API 2964 #--------------------------------------------------------
2965 - def __do_layout(self):
2966 """Build patient edition notebook pages.""" 2967 # contacts page 2968 new_page = cPersonContactsManagerPnl(self, -1) 2969 new_page.identity = self.__identity 2970 self.AddPage ( 2971 page = new_page, 2972 text = _('Contacts'), 2973 select = True 2974 ) 2975 2976 # identity page 2977 new_page = cPersonIdentityManagerPnl(self, -1) 2978 new_page.identity = self.__identity 2979 self.AddPage ( 2980 page = new_page, 2981 text = _('Identity'), 2982 select = False 2983 )
2984 #-------------------------------------------------------- 2985 # properties 2986 #--------------------------------------------------------
2987 - def _get_identity(self):
2988 return self.__identity
2989
2990 - def _set_identity(self, identity):
2991 self.__identity = identity
2992 2993 identity = property(_get_identity, _set_identity)
2994 #============================================================ 2995 # FIXME: support multiple occupations 2996 # FIXME: redo with wxGlade 2997
2998 -class cPatOccupationsPanel(wx.Panel):
2999 """Page containing patient occupations edition fields. 3000 """
3001 - def __init__(self, parent, id, ident=None):
3002 """ 3003 Creates a new instance of BasicPatDetailsPage 3004 @param parent - The parent widget 3005 @type parent - A wx.Window instance 3006 @param id - The widget id 3007 @type id - An integer 3008 """ 3009 wx.Panel.__init__(self, parent, id) 3010 self.__ident = ident 3011 self.__do_layout()
3012 #--------------------------------------------------------
3013 - def __do_layout(self):
3014 PNL_form = wx.Panel(self, -1) 3015 # occupation 3016 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 3017 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 3018 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient")) 3019 # known since 3020 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 3021 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 3022 3023 # layout input widgets 3024 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 3025 SZR_input.AddGrowableCol(1) 3026 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 3027 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 3028 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 3029 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 3030 PNL_form.SetSizerAndFit(SZR_input) 3031 3032 # layout page 3033 SZR_main = wx.BoxSizer(wx.VERTICAL) 3034 SZR_main.Add(PNL_form, 1, wx.EXPAND) 3035 self.SetSizer(SZR_main)
3036 #--------------------------------------------------------
3037 - def set_identity(self, identity):
3038 return self.refresh(identity=identity)
3039 #--------------------------------------------------------
3040 - def refresh(self, identity=None):
3041 if identity is not None: 3042 self.__ident = identity 3043 jobs = self.__ident.get_occupations() 3044 if len(jobs) > 0: 3045 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 3046 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 3047 return True
3048 #--------------------------------------------------------
3049 - def save(self):
3050 if self.PRW_occupation.IsModified(): 3051 new_job = self.PRW_occupation.GetValue().strip() 3052 jobs = self.__ident.get_occupations() 3053 for job in jobs: 3054 if job['l10n_occupation'] == new_job: 3055 continue 3056 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 3057 self.__ident.link_occupation(occupation = new_job) 3058 return True
3059 #============================================================
3060 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
3061 """Patient demographics plugin for main notebook. 3062 3063 Hosts another notebook with pages for Identity, Contacts, etc. 3064 3065 Acts on/listens to the currently active patient. 3066 """ 3067 #--------------------------------------------------------
3068 - def __init__(self, parent, id):
3069 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 3070 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 3071 self.__do_layout() 3072 self.__register_interests()
3073 #-------------------------------------------------------- 3074 # public API 3075 #-------------------------------------------------------- 3076 #-------------------------------------------------------- 3077 # internal helpers 3078 #--------------------------------------------------------
3079 - def __do_layout(self):
3080 """Arrange widgets.""" 3081 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 3082 3083 szr_main = wx.BoxSizer(wx.VERTICAL) 3084 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 3085 self.SetSizerAndFit(szr_main)
3086 #-------------------------------------------------------- 3087 # event handling 3088 #--------------------------------------------------------
3089 - def __register_interests(self):
3090 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 3091 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3092 #--------------------------------------------------------
3093 - def _on_pre_patient_selection(self):
3094 self._schedule_data_reget()
3095 #--------------------------------------------------------
3096 - def _on_post_patient_selection(self):
3097 self._schedule_data_reget()
3098 #-------------------------------------------------------- 3099 # reget mixin API 3100 #--------------------------------------------------------
3101 - def _populate_with_data(self):
3102 """Populate fields in pages with data from model.""" 3103 pat = gmPerson.gmCurrentPatient() 3104 if pat.connected: 3105 self.__patient_notebook.identity = pat 3106 else: 3107 self.__patient_notebook.identity = None 3108 self.__patient_notebook.refresh() 3109 return True
3110 #============================================================
3111 -def create_identity_from_dtd(dtd=None):
3112 """ 3113 Register a new patient, given the data supplied in the 3114 Data Transfer Dictionary object. 3115 3116 @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3117 supplied data. 3118 @type basic_details_DTD A cFormDTD instance. 3119 """ 3120 new_identity = gmPerson.create_identity ( 3121 gender = dtd['gender'], 3122 dob = dtd['dob'].get_pydt(), 3123 lastnames = dtd['lastnames'], 3124 firstnames = dtd['firstnames'] 3125 ) 3126 if new_identity is None: 3127 _log.error('cannot create identity from %s' % str(dtd)) 3128 return None 3129 _log.debug('identity created: %s' % new_identity) 3130 3131 if dtd['comment'] is not None: 3132 if dtd['comment'].strip() != u'': 3133 name = new_identity.get_active_name() 3134 name['comment'] = dtd['comment'] 3135 name.save_payload() 3136 3137 return new_identity
3138 #============================================================
3139 -def update_identity_from_dtd(identity, dtd=None):
3140 """ 3141 Update patient details with data supplied by 3142 Data Transfer Dictionary object. 3143 3144 @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3145 supplied data. 3146 @type basic_details_DTD A cFormDTD instance. 3147 """ 3148 # identity 3149 if identity['gender'] != dtd['gender']: 3150 identity['gender'] = dtd['gender'] 3151 if identity['dob'] != dtd['dob'].get_pydt(): 3152 identity['dob'] = dtd['dob'].get_pydt() 3153 if len(dtd['title']) > 0 and identity['title'] != dtd['title']: 3154 identity['title'] = dtd['title'] 3155 # FIXME: error checking 3156 # FIXME: we need a trigger to update the values of the 3157 # view, identity['keys'], eg. lastnames and firstnames 3158 # are not refreshed. 3159 identity.save_payload() 3160 3161 # names 3162 # FIXME: proper handling of "active" 3163 if identity['firstnames'] != dtd['firstnames'] or identity['lastnames'] != dtd['lastnames']: 3164 new_name = identity.add_name(firstnames = dtd['firstnames'], lastnames = dtd['lastnames'], active = True) 3165 # nickname 3166 if len(dtd['nick']) > 0 and identity['preferred'] != dtd['nick']: 3167 identity.set_nickname(nickname = dtd['nick']) 3168 3169 return True
3170 #============================================================ 3214 #============================================================ 3227 #============================================================
3228 -class TestWizardPanel(wx.Panel):
3229 """ 3230 Utility class to test the new patient wizard. 3231 """ 3232 #--------------------------------------------------------
3233 - def __init__(self, parent, id):
3234 """ 3235 Create a new instance of TestPanel. 3236 @param parent The parent widget 3237 @type parent A wx.Window instance 3238 """ 3239 wx.Panel.__init__(self, parent, id) 3240 wizard = cNewPatientWizard(self) 3241 print wizard.RunWizard()
3242 #============================================================ 3243 if __name__ == "__main__": 3244 3245 #--------------------------------------------------------
3246 - def test_zipcode_prw():
3247 app = wx.PyWidgetTester(size = (200, 50)) 3248 pw = cZipcodePhraseWheel(app.frame, -1) 3249 app.frame.Show(True) 3250 app.MainLoop()
3251 #--------------------------------------------------------
3252 - def test_state_prw():
3253 app = wx.PyWidgetTester(size = (200, 50)) 3254 pw = cStateSelectionPhraseWheel(app.frame, -1) 3255 # pw.set_context(context = u'zip', val = u'04318') 3256 # pw.set_context(context = u'country', val = u'Deutschland') 3257 app.frame.Show(True) 3258 app.MainLoop()
3259 #--------------------------------------------------------
3260 - def test_urb_prw():
3261 app = wx.PyWidgetTester(size = (200, 50)) 3262 pw = cUrbPhraseWheel(app.frame, -1) 3263 app.frame.Show(True) 3264 pw.set_context(context = u'zip', val = u'04317') 3265 app.MainLoop()
3266 #--------------------------------------------------------
3267 - def test_suburb_prw():
3268 app = wx.PyWidgetTester(size = (200, 50)) 3269 pw = cSuburbPhraseWheel(app.frame, -1) 3270 app.frame.Show(True) 3271 app.MainLoop()
3272 #--------------------------------------------------------
3273 - def test_address_type_prw():
3274 app = wx.PyWidgetTester(size = (200, 50)) 3275 pw = cAddressTypePhraseWheel(app.frame, -1) 3276 app.frame.Show(True) 3277 app.MainLoop()
3278 #--------------------------------------------------------
3279 - def test_address_prw():
3280 app = wx.PyWidgetTester(size = (200, 50)) 3281 pw = cAddressPhraseWheel(app.frame, -1) 3282 app.frame.Show(True) 3283 app.MainLoop()
3284 #--------------------------------------------------------
3285 - def test_street_prw():
3286 app = wx.PyWidgetTester(size = (200, 50)) 3287 pw = cStreetPhraseWheel(app.frame, -1) 3288 # pw.set_context(context = u'zip', val = u'04318') 3289 app.frame.Show(True) 3290 app.MainLoop()
3291 #--------------------------------------------------------
3292 - def test_organizer_pnl():
3293 app = wx.PyWidgetTester(size = (600, 400)) 3294 app.SetWidget(cKOrganizerSchedulePnl) 3295 app.MainLoop()
3296 #--------------------------------------------------------
3297 - def test_person_names_pnl():
3298 app = wx.PyWidgetTester(size = (600, 400)) 3299 widget = cPersonNamesManagerPnl(app.frame, -1) 3300 widget.identity = activate_patient() 3301 app.frame.Show(True) 3302 app.MainLoop()
3303 #--------------------------------------------------------
3304 - def test_person_ids_pnl():
3305 app = wx.PyWidgetTester(size = (600, 400)) 3306 widget = cPersonIDsManagerPnl(app.frame, -1) 3307 widget.identity = activate_patient() 3308 app.frame.Show(True) 3309 app.MainLoop()
3310 #--------------------------------------------------------
3311 - def test_pat_ids_pnl():
3312 app = wx.PyWidgetTester(size = (600, 400)) 3313 widget = cPersonIdentityManagerPnl(app.frame, -1) 3314 widget.identity = activate_patient() 3315 app.frame.Show(True) 3316 app.MainLoop()
3317 #--------------------------------------------------------
3318 - def test_name_ea_pnl():
3319 app = wx.PyWidgetTester(size = (600, 400)) 3320 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name()) 3321 app.MainLoop()
3322 #--------------------------------------------------------
3323 - def test_address_ea_pnl():
3324 app = wx.PyWidgetTester(size = (600, 400)) 3325 app.SetWidget(cAddressEditAreaPnl, address = gmDemographicRecord.cAddress(aPK_obj = 1)) 3326 app.MainLoop()
3327 #--------------------------------------------------------
3328 - def test_person_adrs_pnl():
3329 app = wx.PyWidgetTester(size = (600, 400)) 3330 widget = cPersonAddressesManagerPnl(app.frame, -1) 3331 widget.identity = activate_patient() 3332 app.frame.Show(True) 3333 app.MainLoop()
3334 #--------------------------------------------------------
3335 - def test_person_comms_pnl():
3336 app = wx.PyWidgetTester(size = (600, 400)) 3337 widget = cPersonCommsManagerPnl(app.frame, -1) 3338 widget.identity = activate_patient() 3339 app.frame.Show(True) 3340 app.MainLoop()
3341 #--------------------------------------------------------
3342 - def test_pat_contacts_pnl():
3343 app = wx.PyWidgetTester(size = (600, 400)) 3344 widget = cPersonContactsManagerPnl(app.frame, -1) 3345 widget.identity = activate_patient() 3346 app.frame.Show(True) 3347 app.MainLoop()
3348 #--------------------------------------------------------
3349 - def test_cPersonDemographicsEditorNb():
3350 app = wx.PyWidgetTester(size = (600, 400)) 3351 widget = cPersonDemographicsEditorNb(app.frame, -1) 3352 widget.identity = activate_patient() 3353 widget.refresh() 3354 app.frame.Show(True) 3355 app.MainLoop()
3356 #--------------------------------------------------------
3357 - def activate_patient():
3358 patient = gmPerson.ask_for_patient() 3359 if patient is None: 3360 print "No patient. Exiting gracefully..." 3361 sys.exit(0) 3362 from Gnumed.wxpython import gmPatSearchWidgets 3363 gmPatSearchWidgets.set_active_patient(patient=patient) 3364 return patient
3365 #-------------------------------------------------------- 3366 if len(sys.argv) > 1 and sys.argv[1] == 'test': 3367 3368 gmI18N.activate_locale() 3369 gmI18N.install_domain(domain='gnumed') 3370 gmPG2.get_connection() 3371 3372 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields) 3373 3374 # app = wx.PyWidgetTester(size = (400, 300)) 3375 # app.SetWidget(cNotebookedPatEditionPanel, -1) 3376 # app.SetWidget(TestWizardPanel, -1) 3377 # app.frame.Show(True) 3378 # app.MainLoop() 3379 3380 # phrasewheels 3381 # test_zipcode_prw() 3382 # test_state_prw() 3383 # test_street_prw() 3384 # test_organizer_pnl() 3385 #test_address_type_prw() 3386 #test_suburb_prw() 3387 test_urb_prw() 3388 #test_address_prw() 3389 3390 # contacts related widgets 3391 #test_address_ea_pnl() 3392 #test_person_adrs_pnl() 3393 #test_person_comms_pnl() 3394 #test_pat_contacts_pnl() 3395 3396 # identity related widgets 3397 #test_person_names_pnl() 3398 #test_person_ids_pnl() 3399 #test_pat_ids_pnl() 3400 #test_name_ea_pnl() 3401 3402 #test_cPersonDemographicsEditorNb() 3403 3404 #============================================================ 3405 # $Log: gmDemographicsWidgets.py,v $ 3406 # Revision 1.175 2010/02/07 15:13:02 ncq 3407 # - patient -> person 3408 # 3409 # Revision 1.174 2010/01/31 18:14:40 ncq 3410 # - configure-default-region/country() 3411 # - improved province management list layout 3412 # - use default region/country in new patient creation 3413 # 3414 # Revision 1.173 2010/01/08 14:39:44 ncq 3415 # - support NULLing the dob 3416 # 3417 # Revision 1.172 2010/01/08 13:54:19 ncq 3418 # - support external ID in new-patient widget 3419 # 3420 # Revision 1.171 2009/11/29 15:58:18 ncq 3421 # - cleanup 3422 # 3423 # Revision 1.170 2009/11/18 16:10:58 ncq 3424 # - sufficiently complete provinces management 3425 # 3426 # Revision 1.169 2009/11/17 19:42:12 ncq 3427 # - further implement province management 3428 # 3429 # Revision 1.168 2009/07/23 16:38:33 ncq 3430 # - cleanup 3431 # - rewrite address valid for save and use it better 3432 # - catch link_address exceptions 3433 # 3434 # Revision 1.167 2009/06/29 15:05:24 ncq 3435 # - new person widget: 3436 # - set focus to last name 3437 # - raise demographics plugin after adding new person 3438 # - improved DOB validation 3439 # - improved address validation 3440 # 3441 # Revision 1.166 2009/06/20 22:33:32 ncq 3442 # - improved warning on disabling person 3443 # 3444 # Revision 1.165 2009/06/20 12:35:49 ncq 3445 # - switch Identity and Contacts page as per list discussion 3446 # 3447 # Revision 1.164 2009/06/04 15:22:35 ncq 3448 # - re-import allowing saving person w/o DOB and 3449 # use appropriate set_active_patient() 3450 # 3451 # Revision 1.164 2009/05/28 10:53:16 ncq 3452 # - allow saving person without DOB 3453 # 3454 # Revision 1.163 2009/04/24 13:01:13 ncq 3455 # - need to use code on state/country 3456 # 3457 # Revision 1.162 2009/04/24 12:32:38 ncq 3458 # - fix a typo 3459 # 3460 # Revision 1.161 2009/04/24 12:08:42 ncq 3461 # - factor out address match provider to eventually make it smarter 3462 # - apply final regex to first/lastnames PRW 3463 # - implement validity check/saving for new patient EA 3464 # 3465 # Revision 1.160 2009/04/21 16:58:48 ncq 3466 # - address phrasewheel label improvement and get_address 3467 # - create_new_patient 3468 # - new-patient edit area 3469 # 3470 # Revision 1.159 2009/02/25 21:07:41 ncq 3471 # - catch exception when failing to save address 3472 # 3473 # Revision 1.158 2009/02/05 14:29:09 ncq 3474 # - verify DOB > 1900 3475 # 3476 # Revision 1.157 2009/01/15 11:35:41 ncq 3477 # - cleanup 3478 # 3479 # Revision 1.156 2008/11/21 13:05:48 ncq 3480 # - disallow deleting the only name of a person 3481 # 3482 # Revision 1.155 2008/11/20 18:48:55 ncq 3483 # - fix overly zealous validation when creating external IDs 3484 # 3485 # Revision 1.154 2008/08/28 18:33:02 ncq 3486 # - inform user on KOrganizer not being callable 3487 # 3488 # Revision 1.153 2008/08/15 16:01:06 ncq 3489 # - start managing provinces 3490 # - orange-mark address fields in wizard 3491 # - better save error handling in wizard 3492 # 3493 # Revision 1.152 2008/07/07 13:43:16 ncq 3494 # - current patient .connected 3495 # 3496 # Revision 1.151 2008/06/15 20:34:31 ncq 3497 # - adjust to match provider properties 3498 # 3499 # Revision 1.150 2008/06/09 15:33:31 ncq 3500 # - much improved sanity check when saving/editing patient address 3501 # 3502 # Revision 1.149 2008/05/20 16:43:25 ncq 3503 # - improve match provider SQL for urb phrasewheel 3504 # 3505 # Revision 1.148 2008/05/14 13:45:48 ncq 3506 # - Directions -> Street info 3507 # - Postcode -> Postal code 3508 # - Town -> Place 3509 # - State -> Region 3510 # - fix phrasewheel SQL 3511 # - test for urb phrasewheel 3512 # 3513 # Revision 1.147 2008/05/13 14:11:21 ncq 3514 # - support comment on new patient 3515 # 3516 # Revision 1.146 2008/03/05 22:30:13 ncq 3517 # - new style logging 3518 # 3519 # Revision 1.145 2008/02/26 16:26:05 ncq 3520 # - actually fail on detecting error on saving comm channel 3521 # - add some tooltips 3522 # 3523 # Revision 1.144 2008/02/25 17:39:48 ncq 3524 # - improve error checking for comm channel saving 3525 # 3526 # Revision 1.143 2008/01/27 21:13:50 ncq 3527 # - change a few labels per Jim 3528 # 3529 # Revision 1.142 2008/01/14 20:40:09 ncq 3530 # - don't crash on missing korganizer transfer file 3531 # 3532 # Revision 1.141 2008/01/07 19:52:26 ncq 3533 # - enable editing comm channels 3534 # 3535 # Revision 1.140 2008/01/05 16:41:27 ncq 3536 # - remove logging from gm_show_*() 3537 # 3538 # Revision 1.139 2007/12/23 12:10:30 ncq 3539 # - cleanup 3540 # 3541 # Revision 1.138 2007/12/11 12:49:25 ncq 3542 # - explicit signal handling 3543 # 3544 # Revision 1.137 2007/12/06 10:46:05 ncq 3545 # - improve external ID type phrasewheel 3546 # - in edit area on setting ext id type pre-set corresponding issuer if any 3547 # 3548 # Revision 1.136 2007/12/06 08:41:31 ncq 3549 # - improve address display 3550 # - better layout 3551 # - external ID phrasewheels and edit area 3552 # 3553 # Revision 1.135 2007/12/04 18:37:15 ncq 3554 # - edit_occupation() 3555 # - cleanup 3556 # 3557 # Revision 1.134 2007/12/04 16:16:27 ncq 3558 # - use gmAuthWidgets 3559 # 3560 # Revision 1.133 2007/12/03 20:44:14 ncq 3561 # - use delete_name() 3562 # 3563 # Revision 1.132 2007/12/02 21:00:45 ncq 3564 # - cAddressPhraseWheel 3565 # - cCommChannelTypePhraseWheel 3566 # - cCommChannelEditAreaPnl 3567 # - use thereof 3568 # - more tests 3569 # 3570 # Revision 1.131 2007/12/02 11:35:19 ncq 3571 # - in edit unlink old address if new one created 3572 # 3573 # Revision 1.130 2007/11/28 22:35:58 ncq 3574 # - make empty == None == NULL on nick/title/comment 3575 # 3576 # Revision 1.129 2007/11/28 14:00:10 ncq 3577 # - fix a few typos 3578 # - set titles on generic edit areas 3579 # 3580 # Revision 1.128 2007/11/28 11:56:13 ncq 3581 # - comments/wording improved, cleanup 3582 # - name/gender/dob edit area and use in person identity panel/notebook plugin 3583 # - more tests 3584 # 3585 # Revision 1.127 2007/11/17 16:36:59 ncq 3586 # - cPersonAddressesManagerPnl 3587 # - cPersonContactsManagerPnl 3588 # - cPersonCommsManagerPnl 3589 # - cAddressEditAreaPnl 3590 # - cAddressTypePhraseWheel 3591 # - cSuburbPhraseWheel 3592 # - more tests 3593 # 3594 # Revision 1.126 2007/08/28 14:18:12 ncq 3595 # - no more gm_statustext() 3596 # 3597 # Revision 1.125 2007/08/12 00:09:07 ncq 3598 # - no more gmSignals.py 3599 # 3600 # Revision 1.124 2007/07/22 09:04:44 ncq 3601 # - tmp/ now in .gnumed/ 3602 # 3603 # Revision 1.123 2007/07/10 20:28:36 ncq 3604 # - consolidate install_domain() args 3605 # 3606 # Revision 1.122 2007/07/09 12:42:48 ncq 3607 # - KOrganizer panel 3608 # 3609 # Revision 1.121 2007/07/03 16:00:12 ncq 3610 # - nickname MAY start with lower case 3611 # 3612 # Revision 1.120 2007/05/21 22:30:12 ncq 3613 # - cleanup 3614 # - don't try to store empty address in link_contacts_from_dtd() 3615 # 3616 # Revision 1.119 2007/05/14 13:11:24 ncq 3617 # - use statustext() signal 3618 # 3619 # Revision 1.118 2007/04/02 18:39:52 ncq 3620 # - gmFuzzyTimestamp -> gmDateTime 3621 # 3622 # Revision 1.117 2007/03/31 21:34:11 ncq 3623 # - use gmPerson.set_active_patient() 3624 # 3625 # Revision 1.116 2007/02/22 17:41:13 ncq 3626 # - adjust to gmPerson changes 3627 # 3628 # Revision 1.115 2007/02/17 13:59:20 ncq 3629 # - honor entered occupation in new patient wizard 3630 # 3631 # Revision 1.114 2007/02/06 13:43:40 ncq 3632 # - no more aDelay in __init__() 3633 # 3634 # Revision 1.113 2007/02/05 12:15:23 ncq 3635 # - no more aMatchProvider/selection_only in cPhraseWheel.__init__() 3636 # 3637 # Revision 1.112 2007/02/04 15:52:10 ncq 3638 # - set proper CAPS modes on phrasewheels 3639 # - use SetText() 3640 # - remove HSCROLL/VSCROLL so we run on Mac 3641 # 3642 # Revision 1.111 2006/11/28 20:43:26 ncq 3643 # - remove lots of debugging prints 3644 # 3645 # Revision 1.110 2006/11/26 14:23:09 ncq 3646 # - add cOccupationPhraseWheel and use it 3647 # - display last modified on occupation entry 3648 # 3649 # Revision 1.109 2006/11/24 10:01:31 ncq 3650 # - gm_beep_statustext() -> gm_statustext() 3651 # 3652 # Revision 1.108 2006/11/20 16:01:35 ncq 3653 # - use gmTools.coalesce() 3654 # - some SetValue() -> SetData() fixes 3655 # - massively cleanup demographics edit notebook and consolidate save 3656 # logic, remove validator use as it was more pain than gain 3657 # - we now do not lower() inside strings anymore 3658 # - we now take a lot of care not to invalidate the DOB 3659 # 3660 # Revision 1.107 2006/11/07 23:53:30 ncq 3661 # - be ever more careful in handling DOBs, use get_pydt() on fuzzy timestamps 3662 # 3663 # Revision 1.106 2006/11/06 12:51:53 ncq 3664 # - a few u''s 3665 # - actually need to *pass* context to match providers, too 3666 # - adjust a few thresholds 3667 # - improved test suite 3668 # 3669 # Revision 1.105 2006/11/06 10:28:49 ncq 3670 # - zipcode/street/urb/country/lastname/firstname/nickname/title phrasewheels 3671 # - use them 3672 # 3673 # Revision 1.104 2006/11/05 17:55:33 ncq 3674 # - dtd['dob'] already is a timestamp 3675 # 3676 # Revision 1.103 2006/11/05 16:18:29 ncq 3677 # - cleanup, _() handling in test mode, sys.path handling in CVS mode 3678 # - add cStateSelectionPhraseWheel and use it 3679 # - try being more careful in contacts/identity editing such as not 3680 # to change gender/state/dob behind the back of the user 3681 # 3682 # Revision 1.102 2006/10/31 12:38:30 ncq 3683 # - stop improper capitalize_first() 3684 # - more gmPG -> gmPG2 3685 # - remove get_name_gender_map() 3686 # 3687 # Revision 1.101 2006/10/25 07:46:44 ncq 3688 # - Format() -> strftime() since datetime.datetime does not have .Format() 3689 # 3690 # Revision 1.100 2006/10/24 13:21:53 ncq 3691 # - gmPG -> gmPG2 3692 # - cMatchProvider_SQL2() does not need service name anymore 3693 # 3694 # Revision 1.99 2006/08/10 07:19:05 ncq 3695 # - remove import of gmPatientHolder 3696 # 3697 # Revision 1.98 2006/08/01 22:03:18 ncq 3698 # - cleanup 3699 # - add disable_identity() 3700 # 3701 # Revision 1.97 2006/07/21 21:34:04 ncq 3702 # - proper header/subheader for new *person* wizard (not *patient*) 3703 # 3704 # Revision 1.96 2006/07/19 20:29:50 ncq 3705 # - import cleanup 3706 # 3707 # Revision 1.95 2006/07/04 14:12:48 ncq 3708 # - add some phrasewheel sanity LIMITs 3709 # - use gender phrasewheel in pat modify, too 3710 # 3711 # Revision 1.94 2006/06/28 22:15:01 ncq 3712 # - make cGenderSelectionPhraseWheel self-sufficient and use it, too 3713 # 3714 # Revision 1.93 2006/06/28 14:09:17 ncq 3715 # - more cleanup 3716 # - add cGenderSelectionPhraseWheel() and start using it 3717 # 3718 # Revision 1.92 2006/06/20 10:04:40 ncq 3719 # - removed reams of crufty code 3720 # 3721 # Revision 1.91 2006/06/20 09:42:42 ncq 3722 # - cTextObjectValidator -> cTextWidgetValidator 3723 # - add custom invalid message to text widget validator 3724 # - variable renaming, cleanup 3725 # - fix demographics validation 3726 # 3727 # Revision 1.90 2006/06/15 15:37:55 ncq 3728 # - properly handle DOB in new-patient wizard 3729 # 3730 # Revision 1.89 2006/06/12 18:31:31 ncq 3731 # - must create *patient* not person from new patient wizard 3732 # if to be activated as patient :-) 3733 # 3734 # Revision 1.88 2006/06/09 14:40:24 ncq 3735 # - use fuzzy.timestamp for create_identity() 3736 # 3737 # Revision 1.87 2006/06/05 21:33:03 ncq 3738 # - Sebastian is too good at finding bugs, so fix them: 3739 # - proper queries for new-patient wizard phrasewheels 3740 # - properly validate timestamps 3741 # 3742 # Revision 1.86 2006/06/04 22:23:03 ncq 3743 # - consistently use l10n_country 3744 # 3745 # Revision 1.85 2006/06/04 21:38:49 ncq 3746 # - make state red as it's mandatory 3747 # 3748 # Revision 1.84 2006/06/04 21:31:44 ncq 3749 # - allow characters in phone URL 3750 # 3751 # Revision 1.83 2006/06/04 21:16:27 ncq 3752 # - fix missing dem. prefixes 3753 # 3754 # Revision 1.82 2006/05/28 20:49:44 ncq 3755 # - gmDateInput -> cFuzzyTimestampInput 3756 # 3757 # Revision 1.81 2006/05/15 13:35:59 ncq 3758 # - signal cleanup: 3759 # - activating_patient -> pre_patient_selection 3760 # - patient_selected -> post_patient_selection 3761 # 3762 # Revision 1.80 2006/05/14 21:44:22 ncq 3763 # - add get_workplace() to gmPerson.gmCurrentProvider and make use thereof 3764 # - remove use of gmWhoAmI.py 3765 # 3766 # Revision 1.79 2006/05/12 12:18:11 ncq 3767 # - whoami -> whereami cleanup 3768 # - use gmCurrentProvider() 3769 # 3770 # Revision 1.78 2006/05/04 09:49:20 ncq 3771 # - get_clinical_record() -> get_emr() 3772 # - adjust to changes in set_active_patient() 3773 # - need explicit set_active_patient() after ask_for_patient() if wanted 3774 # 3775 # Revision 1.77 2006/01/18 14:14:39 sjtan 3776 # 3777 # make reusable 3778 # 3779 # Revision 1.76 2006/01/10 14:22:24 sjtan 3780 # 3781 # movement to schema dem 3782 # 3783 # Revision 1.75 2006/01/09 10:46:18 ncq 3784 # - yet more schema quals 3785 # 3786 # Revision 1.74 2006/01/07 17:52:38 ncq 3787 # - several schema qualifications 3788 # 3789 # Revision 1.73 2005/10/19 09:12:40 ncq 3790 # - cleanup 3791 # 3792 # Revision 1.72 2005/10/09 08:10:22 ihaywood 3793 # ok, re-order the address widgets "the hard way" so tab-traversal works correctly. 3794 # 3795 # minor bugfixes so saving address actually works now 3796 # 3797 # Revision 1.71 2005/10/09 02:19:40 ihaywood 3798 # the address widget now has the appropriate widget order and behaviour for australia 3799 # when os.environ["LANG"] == 'en_AU' (is their a more graceful way of doing this?) 3800 # 3801 # Remember our postcodes work very differently. 3802 # 3803 # Revision 1.70 2005/09/28 21:27:30 ncq 3804 # - a lot of wx2.6-ification 3805 # 3806 # Revision 1.69 2005/09/28 19:47:01 ncq 3807 # - runs until login dialog 3808 # 3809 # Revision 1.68 2005/09/28 15:57:48 ncq 3810 # - a whole bunch of wx.Foo -> wx.Foo 3811 # 3812 # Revision 1.67 2005/09/27 20:44:58 ncq 3813 # - wx.wx* -> wx.* 3814 # 3815 # Revision 1.66 2005/09/26 18:01:50 ncq 3816 # - use proper way to import wx26 vs wx2.4 3817 # - note: THIS WILL BREAK RUNNING THE CLIENT IN SOME PLACES 3818 # - time for fixup 3819 # 3820 # Revision 1.65 2005/09/25 17:30:58 ncq 3821 # - revert back to wx2.4 style import awaiting "proper" wx2.6 importing 3822 # 3823 # Revision 1.64 2005/09/25 01:00:47 ihaywood 3824 # bugfixes 3825 # 3826 # remember 2.6 uses "import wx" not "from wxPython import wx" 3827 # removed not null constraint on clin_encounter.rfe as has no value on instantiation 3828 # client doesn't try to set clin_encounter.description as it doesn't exist anymore 3829 # 3830 # Revision 1.63 2005/09/24 09:17:27 ncq 3831 # - some wx2.6 compatibility fixes 3832 # 3833 # Revision 1.62 2005/09/12 15:09:00 ncq 3834 # - make first tab display first in demographics editor 3835 # 3836 # Revision 1.61 2005/09/04 07:29:53 ncq 3837 # - allow phrasewheeling states by abbreviation in new-patient wizard 3838 # 3839 # Revision 1.60 2005/08/14 15:36:54 ncq 3840 # - fix phrasewheel queries for country matching 3841 # 3842 # Revision 1.59 2005/08/08 08:08:35 ncq 3843 # - cleanup 3844 # 3845 # Revision 1.58 2005/07/31 14:48:44 ncq 3846 # - catch exceptions in TransferToWindow 3847 # 3848 # Revision 1.57 2005/07/24 18:54:18 ncq 3849 # - cleanup 3850 # 3851 # Revision 1.56 2005/07/04 11:26:50 ncq 3852 # - re-enable auto-setting gender from firstname, and speed it up, too 3853 # 3854 # Revision 1.55 2005/07/02 18:20:22 ncq 3855 # - allow English input of country as well, regardless of locale 3856 # 3857 # Revision 1.54 2005/06/29 15:03:32 ncq 3858 # - some cleanup 3859 # 3860 # Revision 1.53 2005/06/28 14:38:21 cfmoro 3861 # Integration fixes 3862 # 3863 # Revision 1.52 2005/06/28 14:12:55 cfmoro 3864 # Integration in space fixes 3865 # 3866 # Revision 1.51 2005/06/28 13:11:05 cfmoro 3867 # Fixed bug: when updating patient details the dob was converted from date to str type 3868 # 3869 # Revision 1.50 2005/06/14 19:51:27 cfmoro 3870 # auto zip in patient wizard and minor cleanups 3871 # 3872 # Revision 1.49 2005/06/14 00:34:14 cfmoro 3873 # Matcher provider queries revisited 3874 # 3875 # Revision 1.48 2005/06/13 01:18:24 cfmoro 3876 # Improved input system support by zip, country 3877 # 3878 # Revision 1.47 2005/06/12 22:12:35 ncq 3879 # - prepare for staged (constrained) queries in demographics 3880 # 3881 # Revision 1.46 2005/06/10 23:22:43 ncq 3882 # - SQL2 match provider now requires query *list* 3883 # 3884 # Revision 1.45 2005/06/09 01:56:41 cfmoro 3885 # Initial code on zip -> (auto) address 3886 # 3887 # Revision 1.44 2005/06/09 00:26:07 cfmoro 3888 # PhraseWheels in patient editor. Tons of cleanups and validator fixes 3889 # 3890 # Revision 1.43 2005/06/08 22:03:02 cfmoro 3891 # Restored phrasewheel gender in wizard 3892 # 3893 # Revision 1.42 2005/06/08 01:25:42 cfmoro 3894 # PRW in wizards state and country. Validator fixes 3895 # 3896 # Revision 1.41 2005/06/04 10:17:51 ncq 3897 # - cleanup, cSmartCombo, some comments 3898 # 3899 # Revision 1.40 2005/06/03 15:50:38 cfmoro 3900 # State and country combos y patient edition 3901 # 3902 # Revision 1.39 2005/06/03 13:37:45 cfmoro 3903 # States and country combo selection. SmartCombo revamped. Passing country and state codes instead of names 3904 # 3905 # Revision 1.38 2005/06/03 00:56:19 cfmoro 3906 # Validate dob in patient wizard 3907 # 3908 # Revision 1.37 2005/06/03 00:37:33 cfmoro 3909 # Validate dob in patient identity page 3910 # 3911 # Revision 1.36 2005/06/03 00:01:41 cfmoro 3912 # Key fixes in new patient wizard 3913 # 3914 # Revision 1.35 2005/06/02 23:49:21 cfmoro 3915 # Gender use SmartCombo, several fixes 3916 # 3917 # Revision 1.34 2005/06/02 23:26:41 cfmoro 3918 # Name auto-selection in new patient wizard 3919 # 3920 # Revision 1.33 2005/06/02 12:17:25 cfmoro 3921 # Auto select gender according to firstname 3922 # 3923 # Revision 1.32 2005/05/28 12:18:01 cfmoro 3924 # Capitalize name, street, etc 3925 # 3926 # Revision 1.31 2005/05/28 12:00:53 cfmoro 3927 # Trigger FIXME to reflect changes in v_basic_person 3928 # 3929 # Revision 1.30 2005/05/28 11:45:19 cfmoro 3930 # Retrieve names from identity cache, so refreshing will be reflected 3931 # 3932 # Revision 1.29 2005/05/25 23:03:02 cfmoro 3933 # Minor fixes 3934 # 3935 # Revision 1.28 2005/05/24 19:57:14 ncq 3936 # - cleanup 3937 # - make cNotebookedPatEditionPanel a gmRegetMixin child instead of cPatEditionNotebook 3938 # 3939 # Revision 1.27 2005/05/23 12:01:08 cfmoro 3940 # Create/update comms 3941 # 3942 # Revision 1.26 2005/05/23 11:16:18 cfmoro 3943 # More cleanups and test functional fixes 3944 # 3945 # Revision 1.25 2005/05/23 09:20:37 cfmoro 3946 # More cleaning up 3947 # 3948 # Revision 1.24 2005/05/22 22:12:06 ncq 3949 # - cleaning up patient edition notebook 3950 # 3951 # Revision 1.23 2005/05/19 16:06:50 ncq 3952 # - just silly cleanup, as usual 3953 # 3954 # Revision 1.22 2005/05/19 15:25:53 cfmoro 3955 # Initial logic to update patient details. Needs fixing. 3956 # 3957 # Revision 1.21 2005/05/17 15:09:28 cfmoro 3958 # Reloading values from backend in repopulate to properly reflect patient activated 3959 # 3960 # Revision 1.20 2005/05/17 14:56:02 cfmoro 3961 # Restore values from model to window action function 3962 # 3963 # Revision 1.19 2005/05/17 14:41:36 cfmoro 3964 # Notebooked patient editor initial code 3965 # 3966 # Revision 1.18 2005/05/17 08:04:28 ncq 3967 # - some cleanup 3968 # 3969 # Revision 1.17 2005/05/14 14:56:41 ncq 3970 # - add Carlos' DTD code 3971 # - numerous fixes/robustification 3972 # move occupation down based on user feedback 3973 # 3974 # Revision 1.16 2005/05/05 06:25:56 ncq 3975 # - cleanup, remove _() in log statements 3976 # - re-ordering in new patient wizard due to user feedback 3977 # - add <activate> to RunWizard(): if true activate patient after creation 3978 # 3979 # Revision 1.15 2005/04/30 20:31:03 ncq 3980 # - first-/lastname were switched around when saving identity into backend 3981 # 3982 # Revision 1.14 2005/04/28 19:21:18 cfmoro 3983 # zip code streamlining 3984 # 3985 # Revision 1.13 2005/04/28 16:58:45 cfmoro 3986 # Removed fixme, was dued to log buffer 3987 # 3988 # Revision 1.12 2005/04/28 16:24:47 cfmoro 3989 # Remove last references to town zip code 3990 # 3991 # Revision 1.11 2005/04/28 16:21:17 cfmoro 3992 # Leave town zip code out and street zip code optional as in schema 3993 # 3994 # Revision 1.10 2005/04/25 21:22:17 ncq 3995 # - some cleanup 3996 # - make cNewPatientWizard inherit directly from wxWizard as it should IMO 3997 # 3998 # Revision 1.9 2005/04/25 16:59:11 cfmoro 3999 # Implemented patient creation. Added conditional validator 4000 # 4001 # Revision 1.8 2005/04/25 08:29:24 ncq 4002 # - combobox items must be strings 4003 # 4004 # Revision 1.7 2005/04/23 06:34:11 cfmoro 4005 # Added address number and street zip code missing fields 4006 # 4007 # Revision 1.6 2005/04/18 19:19:54 ncq 4008 # - wrong field order in some match providers 4009 # 4010 # Revision 1.5 2005/04/14 18:26:19 ncq 4011 # - turn gender input into phrase wheel with fixed list 4012 # - some cleanup 4013 # 4014 # Revision 1.4 2005/04/14 08:53:56 ncq 4015 # - cIdentity moved 4016 # - improved tooltips and phrasewheel thresholds 4017 # 4018 # Revision 1.3 2005/04/12 18:49:04 cfmoro 4019 # Added missing fields and matcher providers 4020 # 4021 # Revision 1.2 2005/04/12 16:18:00 ncq 4022 # - match firstnames against name_gender_map, too 4023 # 4024 # Revision 1.1 2005/04/11 18:09:55 ncq 4025 # - offers demographic widgets 4026 # 4027 # Revision 1.62 2005/04/11 18:03:32 ncq 4028 # - attach some match providers to first new-patient wizard page 4029 # 4030 # Revision 1.61 2005/04/10 12:09:17 cfmoro 4031 # GUI implementation of the first-basic (wizard) page for patient details input 4032 # 4033 # Revision 1.60 2005/03/20 17:49:45 ncq 4034 # - improve split window handling, cleanup 4035 # 4036 # Revision 1.59 2005/03/06 09:21:08 ihaywood 4037 # stole a couple of icons from Richard's demo code 4038 # 4039 # Revision 1.58 2005/03/06 08:17:02 ihaywood 4040 # forms: back to the old way, with support for LaTeX tables 4041 # 4042 # business objects now support generic linked tables, demographics 4043 # uses them to the same functionality as before (loading, no saving) 4044 # They may have no use outside of demographics, but saves much code already. 4045 # 4046 # Revision 1.57 2005/02/22 10:21:33 ihaywood 4047 # new patient 4048 # 4049 # Revision 1.56 2005/02/20 10:45:49 sjtan 4050 # 4051 # kwargs syntax error. 4052 # 4053 # Revision 1.55 2005/02/20 10:15:16 ihaywood 4054 # some tidying up 4055 # 4056 # Revision 1.54 2005/02/20 09:46:08 ihaywood 4057 # demographics module with load a patient with no exceptions 4058 # 4059 # Revision 1.53 2005/02/18 11:16:41 ihaywood 4060 # new demographics UI code won't crash the whole client now ;-) 4061 # still needs much work 4062 # RichardSpace working 4063 # 4064 # Revision 1.52 2005/02/03 20:19:16 ncq 4065 # - get_demographic_record() -> get_identity() 4066 # 4067 # Revision 1.51 2005/02/01 10:16:07 ihaywood 4068 # refactoring of gmDemographicRecord and follow-on changes as discussed. 4069 # 4070 # gmTopPanel moves to gmHorstSpace 4071 # gmRichardSpace added -- example code at present, haven't even run it myself 4072 # (waiting on some icon .pngs from Richard) 4073 # 4074 # Revision 1.50 2005/01/31 10:37:26 ncq 4075 # - gmPatient.py -> gmPerson.py 4076 # 4077 # Revision 1.49 2004/12/18 13:45:51 sjtan 4078 # 4079 # removed timer. 4080 # 4081 # Revision 1.48 2004/10/20 11:20:10 sjtan 4082 # restore imports. 4083 # 4084 # Revision 1.47 2004/10/19 21:34:25 sjtan 4085 # dir is direction, and this is checked 4086 # 4087 # Revision 1.46 2004/10/19 21:29:25 sjtan 4088 # remove division by zero problem, statement occurs later after check for non-zero. 4089 # 4090 # Revision 1.45 2004/10/17 23:49:21 sjtan 4091 # 4092 # the timer autoscroll idea. 4093 # 4094 # Revision 1.44 2004/10/17 22:26:42 sjtan 4095 # 4096 # split window new look Richard's demographics ( his eye for gui design is better 4097 # than most of ours). Rollback if vote no. 4098 # 4099 # Revision 1.43 2004/10/16 22:42:12 sjtan 4100 # 4101 # script for unitesting; guard for unit tests where unit uses gmPhraseWheel; fixup where version of wxPython doesn't allow 4102 # a child widget to be multiply inserted (gmDemographics) ; try block for later versions of wxWidgets that might fail 4103 # the Add (.. w,h, ... ) because expecting Add(.. (w,h) ...) 4104 # 4105 # Revision 1.42 2004/09/10 10:51:14 ncq 4106 # - improve previous checkin comment 4107 # 4108 # Revision 1.41 2004/09/10 10:41:38 ncq 4109 # - remove dead import 4110 # - lots of cleanup (whitespace, indention, style, local vars instead of instance globals) 4111 # - remove an extra sizer, waste less space 4112 # - translate strings 4113 # - from wxPython.wx import * -> from wxPython import wx 4114 # Why ? Because we can then do a simple replace wx. -> wx. for 2.5 code. 4115 # 4116 # Revision 1.40 2004/08/24 14:29:58 ncq 4117 # - some cleanup, not there yet, though 4118 # 4119 # Revision 1.39 2004/08/23 10:25:36 ncq 4120 # - Richards work, removed pat photo, store column sizes 4121 # 4122 # Revision 1.38 2004/08/20 13:34:48 ncq 4123 # - getFirstMatchingDBSet() -> getDBParam() 4124 # 4125 # Revision 1.37 2004/08/18 08:15:21 ncq 4126 # - check if column size for patient list is missing 4127 # 4128 # Revision 1.36 2004/08/16 13:32:19 ncq 4129 # - rework of GUI layout by R.Terry 4130 # - save patient list column width from right click popup menu 4131 # 4132 # Revision 1.35 2004/07/30 13:43:33 sjtan 4133 # 4134 # update import 4135 # 4136 # Revision 1.34 2004/07/26 12:04:44 sjtan 4137 # 4138 # character level immediate validation , as per Richard's suggestions. 4139 # 4140 # Revision 1.33 2004/07/20 01:01:46 ihaywood 4141 # changing a patients name works again. 4142 # Name searching has been changed to query on names rather than v_basic_person. 4143 # This is so the old (inactive) names are still visible to the search. 4144 # This is so when Mary Smith gets married, we can still find her under Smith. 4145 # [In Australia this odd tradition is still the norm, even female doctors 4146 # have their medical registration documents updated] 4147 # 4148 # SOAPTextCtrl now has popups, but the cursor vanishes (?) 4149 # 4150 # Revision 1.32 2004/07/18 20:30:53 ncq 4151 # - wxPython.true/false -> Python.True/False as Python tells us to do 4152 # 4153 # Revision 1.31 2004/06/30 15:09:47 shilbert 4154 # - more wxMAC fixes 4155 # 4156 # Revision 1.30 2004/06/29 22:48:47 shilbert 4157 # - one more wxMAC fix 4158 # 4159 # Revision 1.29 2004/06/27 13:42:26 ncq 4160 # - further Mac fixes - maybe 2.5 issues ? 4161 # 4162 # Revision 1.28 2004/06/23 21:26:28 ncq 4163 # - kill dead code, fixup for Mac 4164 # 4165 # Revision 1.27 2004/06/20 17:28:34 ncq 4166 # - The Great Butchering begins 4167 # - remove dead plugin code 4168 # - rescue binoculars xpm to artworks/ 4169 # 4170 # Revision 1.26 2004/06/17 11:43:12 ihaywood 4171 # Some minor bugfixes. 4172 # My first experiments with wxGlade 4173 # changed gmPhraseWheel so the match provider can be added after instantiation 4174 # (as wxGlade can't do this itself) 4175 # 4176 # Revision 1.25 2004/06/13 22:31:48 ncq 4177 # - gb['main.toolbar'] -> gb['main.top_panel'] 4178 # - self.internal_name() -> self.__class__.__name__ 4179 # - remove set_widget_reference() 4180 # - cleanup 4181 # - fix lazy load in _on_patient_selected() 4182 # - fix lazy load in ReceiveFocus() 4183 # - use self._widget in self.GetWidget() 4184 # - override populate_with_data() 4185 # - use gb['main.notebook.raised_plugin'] 4186 # 4187 # Revision 1.24 2004/05/27 13:40:22 ihaywood 4188 # more work on referrals, still not there yet 4189 # 4190 # Revision 1.23 2004/05/25 16:18:12 sjtan 4191 # 4192 # move methods for postcode -> urb interaction to gmDemographics so gmContacts can use it. 4193 # 4194 # Revision 1.22 2004/05/25 16:00:34 sjtan 4195 # 4196 # move common urb/postcode collaboration to business class. 4197 # 4198 # Revision 1.21 2004/05/23 11:13:59 sjtan 4199 # 4200 # some data fields not in self.input_fields , so exclude them 4201 # 4202 # Revision 1.20 2004/05/19 11:16:09 sjtan 4203 # 4204 # allow selecting the postcode for restricting the urb's picklist, and resetting 4205 # the postcode for unrestricting the urb picklist. 4206 # 4207 # Revision 1.19 2004/03/27 04:37:01 ihaywood 4208 # lnk_person2address now lnk_person_org_address 4209 # sundry bugfixes 4210 # 4211 # Revision 1.18 2004/03/25 11:03:23 ncq 4212 # - getActiveName -> get_names 4213 # 4214 # Revision 1.17 2004/03/15 15:43:17 ncq 4215 # - cleanup imports 4216 # 4217 # Revision 1.16 2004/03/09 07:34:51 ihaywood 4218 # reactivating plugins 4219 # 4220 # Revision 1.15 2004/03/04 11:19:05 ncq 4221 # - put a comment as to where to handle result from setCOB 4222 # 4223 # Revision 1.14 2004/03/03 23:53:22 ihaywood 4224 # GUI now supports external IDs, 4225 # Demographics GUI now ALPHA (feature-complete w.r.t. version 1.0) 4226 # but happy to consider cosmetic changes 4227 # 4228 # Revision 1.13 2004/03/03 05:24:01 ihaywood 4229 # patient photograph support 4230 # 4231 # Revision 1.12 2004/03/02 23:57:59 ihaywood 4232 # Support for full range of backend genders 4233 # 4234 # Revision 1.11 2004/03/02 10:21:10 ihaywood 4235 # gmDemographics now supports comm channels, occupation, 4236 # country of birth and martial status 4237 # 4238 # Revision 1.10 2004/02/25 09:46:21 ncq 4239 # - import from pycommon now, not python-common 4240 # 4241 # Revision 1.9 2004/02/18 06:30:30 ihaywood 4242 # Demographics editor now can delete addresses 4243 # Contacts back up on screen. 4244 # 4245 # Revision 1.8 2004/01/18 21:49:18 ncq 4246 # - comment out debugging code 4247 # 4248 # Revision 1.7 2004/01/04 09:33:32 ihaywood 4249 # minor bugfixes, can now create new patients, but doesn't update properly 4250 # 4251 # Revision 1.6 2003/11/22 14:47:24 ncq 4252 # - use addName instead of setActiveName 4253 # 4254 # Revision 1.5 2003/11/22 12:29:16 sjtan 4255 # 4256 # minor debugging; remove _newPatient flag attribute conflict with method name newPatient. 4257 # 4258 # Revision 1.4 2003/11/20 02:14:42 sjtan 4259 # 4260 # use global module function getPostcodeByUrbId() , and renamed MP_urb_by_zip. 4261 # 4262 # Revision 1.3 2003/11/19 23:11:58 sjtan 4263 # 4264 # using local time tuple conversion function; mxDateTime object sometimes can't convert to int. 4265 # Changed to global module.getAddressTypes(). To decide: mechanism for postcode update when 4266 # suburb selected ( not back via gmDemographicRecord.getPostcodeForUrbId(), ? via linked PhraseWheel matchers ?) 4267 # 4268 # Revision 1.2 2003/11/18 16:46:02 ncq 4269 # - sync with method name changes 4270 # 4271 # Revision 1.1 2003/11/17 11:04:34 sjtan 4272 # 4273 # added. 4274 # 4275 # Revision 1.1 2003/10/23 06:02:40 sjtan 4276 # 4277 # manual edit areas modelled after r.terry's specs. 4278 # 4279 # Revision 1.26 2003/04/28 12:14:40 ncq 4280 # - use .internal_name() 4281 # 4282 # Revision 1.25 2003/04/25 11:15:58 ncq 4283 # cleanup 4284 # 4285 # Revision 1.24 2003/04/05 00:39:23 ncq 4286 # - "patient" is now "clinical", changed all the references 4287 # 4288 # Revision 1.23 2003/04/04 20:52:44 ncq 4289 # - start disentanglement with top pane: 4290 # - remove patient search/age/allergies/patient details 4291 # 4292 # Revision 1.22 2003/03/29 18:27:14 ncq 4293 # - make age/allergies read-only, cleanup 4294 # 4295 # Revision 1.21 2003/03/29 13:50:09 ncq 4296 # - adapt to new "top row" panel 4297 # 4298 # Revision 1.20 2003/03/28 16:43:12 ncq 4299 # - some cleanup in preparation of inserting the patient searcher 4300 # 4301 # Revision 1.19 2003/02/09 23:42:50 ncq 4302 # - date time conversion to age string does not work, set to 20 for now, fix soon 4303 # 4304 # Revision 1.18 2003/02/09 12:05:02 sjtan 4305 # 4306 # 4307 # wx.BasePlugin is unnecessarily specific. 4308 # 4309 # Revision 1.17 2003/02/09 11:57:42 ncq 4310 # - cleanup, cvs keywords 4311 # 4312 # old change log: 4313 # 10.06.2002 rterry initial implementation, untested 4314 # 30.07.2002 rterry images put in file 4315