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