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

Source Code for Module Gnumed.wxpython.gmDemographicsWidgets

   1  """Widgets dealing with patient demographics.""" 
   2  #============================================================ 
   3  __version__ = "$Revision: 1.175 $" 
   4  __author__ = "R.Terry, SJ Tan, I Haywood, Carlos Moro <cfmoro1976@yahoo.es>" 
   5  __license__ = 'GPL (details at http://www.gnu.org)' 
   6   
   7  # standard library 
   8  import time, string, sys, os, datetime as pyDT, csv, codecs, re as regex, psycopg2, logging 
   9   
  10   
  11  import wx 
  12  import wx.wizard 
  13   
  14   
  15  # GNUmed specific 
  16  if __name__ == '__main__': 
  17          sys.path.insert(0, '../../') 
  18  from Gnumed.pycommon import gmDispatcher, gmI18N, gmMatchProvider, gmPG2, gmTools, gmCfg 
  19  from Gnumed.pycommon import gmDateTime, gmShellAPI 
  20  from Gnumed.business import gmDemographicRecord, gmPerson, gmSurgery 
  21  from Gnumed.wxpython import gmPlugin, gmPhraseWheel, gmGuiHelpers, gmDateTimeInput 
  22  from Gnumed.wxpython import gmRegetMixin, gmDataMiningWidgets, gmListWidgets, gmEditArea 
  23  from Gnumed.wxpython import gmAuthWidgets, gmCfgWidgets 
  24  from Gnumed.wxGladeWidgets import wxgGenericAddressEditAreaPnl, wxgPersonContactsManagerPnl, wxgPersonIdentityManagerPnl 
  25  from Gnumed.wxGladeWidgets import wxgCommChannelEditAreaPnl, wxgExternalIDEditAreaPnl 
  26   
  27   
  28  # constant defs 
  29  _log = logging.getLogger('gm.ui') 
  30   
  31   
  32  try: 
  33          _('dummy-no-need-to-translate-but-make-epydoc-happy') 
  34  except NameError: 
  35          _ = lambda x:x 
  36   
  37  #============================================================ 
  38  # country related widgets / functions 
  39  #============================================================ 
40 -def configure_default_country(parent=None):
41 42 if parent is None: 43 parent = wx.GetApp().GetTopWindow() 44 45 countries = gmDemographicRecord.get_countries() 46 47 gmCfgWidgets.configure_string_from_list_option ( 48 parent = parent, 49 message = _('Select the default country for new persons.\n'), 50 option = 'person.create.default_country', 51 bias = 'user', 52 choices = [ (c['l10n_country'], c['code']) for c in countries ], 53 columns = [_('Country'), _('Code')], 54 data = [ c['name'] for c in countries ] 55 )
56 #============================================================
57 -class cCountryPhraseWheel(gmPhraseWheel.cPhraseWheel):
58
59 - def __init__(self, *args, **kwargs):
60 61 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 62 63 context = { 64 u'ctxt_zip': { 65 u'where_part': u'and zip ilike %(zip)s', 66 u'placeholder': u'zip' 67 } 68 } 69 query = u""" 70 select code, name from ( 71 select distinct on (code, name) code, (name || ' (' || code || ')') as name, rank from ( 72 73 -- localized to user 74 75 select 76 code_country as code, l10n_country as name, 1 as rank 77 from dem.v_zip2data 78 where 79 l10n_country %(fragment_condition)s 80 %(ctxt_zip)s 81 82 union all 83 84 select 85 code as code, _(name) as name, 2 as rank 86 from dem.country 87 where 88 _(name) %(fragment_condition)s 89 90 union all 91 92 -- non-localized 93 94 select 95 code_country as code, country as name, 3 as rank 96 from dem.v_zip2data 97 where 98 country %(fragment_condition)s 99 %(ctxt_zip)s 100 101 union all 102 103 select 104 code as code, name as name, 4 as rank 105 from dem.country 106 where 107 name %(fragment_condition)s 108 109 union all 110 111 -- abbreviation 112 113 select 114 code as code, name as name, 5 as rank 115 from dem.country 116 where 117 code %(fragment_condition)s 118 119 ) as q2 120 ) as q1 order by rank, name limit 25""" 121 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 122 mp.setThresholds(2, 5, 9) 123 self.matcher = mp 124 125 self.unset_context(context = u'zip') 126 self.SetToolTipString(_('Type or select a country.')) 127 self.capitalisation_mode = gmTools.CAPS_FIRST 128 self.selection_only = True
129 130 #============================================================ 131 # province related widgets / functions 132 #============================================================
133 -def configure_default_region(parent=None):
134 135 if parent is None: 136 parent = wx.GetApp().GetTopWindow() 137 138 provs = gmDemographicRecord.get_provinces() 139 140 gmCfgWidgets.configure_string_from_list_option ( 141 parent = parent, 142 message = _('Select the default region/province/state/territory for new persons.\n'), 143 option = 'person.create.default_region', 144 bias = 'user', 145 choices = [ (p['l10n_country'], p['l10n_state'], p['code_state']) for p in provs ], 146 columns = [_('Country'), _('Region'), _('Code')], 147 data = [ p['state'] for p in provs ] 148 )
149 #============================================================
150 -def edit_province(parent=None, province=None):
151 ea = cProvinceEAPnl(parent = parent, id = -1, province = province) 152 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = (province is not None)) 153 dlg.SetTitle(gmTools.coalesce(province, _('Adding province'), _('Editing province'))) 154 result = dlg.ShowModal() 155 dlg.Destroy() 156 return (result == wx.ID_OK)
157 #============================================================
158 -def delete_province(parent=None, province=None):
159 160 msg = _( 161 'Are you sure you want to delete this province ?\n' 162 '\n' 163 'Deletion will only work if this province is not\n' 164 'yet in use in any patient addresses.' 165 ) 166 167 tt = _( 168 'Also delete any towns/cities/villages known\n' 169 'to be situated in this state as long as\n' 170 'no patients are recorded to live there.' 171 ) 172 173 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 174 parent, 175 -1, 176 caption = _('Deleting province'), 177 question = msg, 178 show_checkbox = True, 179 checkbox_msg = _('delete related townships'), 180 checkbox_tooltip = tt, 181 button_defs = [ 182 {'label': _('Yes, delete'), 'tooltip': _('Delete province and possibly related townships.'), 'default': False}, 183 {'label': _('No'), 'tooltip': _('No, do NOT delete anything.'), 'default': True} 184 ] 185 ) 186 187 decision = dlg.ShowModal() 188 if decision != wx.ID_YES: 189 dlg.Destroy() 190 return False 191 192 include_urbs = dlg.checkbox_is_checked() 193 dlg.Destroy() 194 195 return gmDemographicRecord.delete_province(province = province, delete_urbs = include_urbs)
196 #============================================================
197 -def manage_provinces(parent=None):
198 199 if parent is None: 200 parent = wx.GetApp().GetTopWindow() 201 202 #------------------------------------------------------------ 203 def delete(province=None): 204 return delete_province(parent = parent, province = province['pk_state'])
205 #------------------------------------------------------------ 206 def edit(province=None): 207 return edit_province(parent = parent, province = province) 208 #------------------------------------------------------------ 209 def refresh(lctrl): 210 wx.BeginBusyCursor() 211 provinces = gmDemographicRecord.get_provinces() 212 lctrl.set_string_items([ (p['l10n_country'], p['l10n_state']) for p in provinces ]) 213 lctrl.set_data(provinces) 214 wx.EndBusyCursor() 215 #------------------------------------------------------------ 216 msg = _( 217 '\n' 218 'This list shows the provinces known to GNUmed.\n' 219 '\n' 220 'In your jurisdiction "province" may correspond to either of "state",\n' 221 '"county", "region", "territory", or some such term.\n' 222 '\n' 223 'Select the province you want to edit !\n' 224 ) 225 226 gmListWidgets.get_choices_from_list ( 227 parent = parent, 228 msg = msg, 229 caption = _('Editing provinces ...'), 230 columns = [_('Country'), _('Province')], 231 single_selection = True, 232 new_callback = edit, 233 #edit_callback = edit, 234 delete_callback = delete, 235 refresh_callback = refresh 236 ) 237 #============================================================
238 -class cStateSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
239
240 - def __init__(self, *args, **kwargs):
241 242 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 243 244 context = { 245 u'ctxt_country_name': { 246 u'where_part': u'and l10n_country ilike %(country_name)s or country ilike %(country_name)s', 247 u'placeholder': u'country_name' 248 }, 249 u'ctxt_zip': { 250 u'where_part': u'and zip ilike %(zip)s', 251 u'placeholder': u'zip' 252 }, 253 u'ctxt_country_code': { 254 u'where_part': u'and country in (select code from dem.country where _(name) ilike %(country_name)s or name ilike %(country_name)s)', 255 u'placeholder': u'country_name' 256 } 257 } 258 259 query = u""" 260 select code, name from ( 261 select distinct on (name) code, name, rank from ( 262 -- 1: find states based on name, context: zip and country name 263 select 264 code_state as code, state as name, 1 as rank 265 from dem.v_zip2data 266 where 267 state %(fragment_condition)s 268 %(ctxt_country_name)s 269 %(ctxt_zip)s 270 271 union all 272 273 -- 2: find states based on code, context: zip and country name 274 select 275 code_state as code, state as name, 2 as rank 276 from dem.v_zip2data 277 where 278 code_state %(fragment_condition)s 279 %(ctxt_country_name)s 280 %(ctxt_zip)s 281 282 union all 283 284 -- 3: find states based on name, context: country 285 select 286 code as code, name as name, 3 as rank 287 from dem.state 288 where 289 name %(fragment_condition)s 290 %(ctxt_country_code)s 291 292 union all 293 294 -- 4: find states based on code, context: country 295 select 296 code as code, name as name, 3 as rank 297 from dem.state 298 where 299 code %(fragment_condition)s 300 %(ctxt_country_code)s 301 302 ) as q2 303 ) as q1 order by rank, name limit 50""" 304 305 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 306 mp.setThresholds(2, 5, 6) 307 mp.word_separators = u'[ \t]+' 308 self.matcher = mp 309 310 self.unset_context(context = u'zip') 311 self.unset_context(context = u'country_name') 312 self.SetToolTipString(_('Type or select a state/region/province/territory.')) 313 self.capitalisation_mode = gmTools.CAPS_FIRST 314 self.selection_only = True
315 #==================================================================== 316 from Gnumed.wxGladeWidgets import wxgProvinceEAPnl 317
318 -class cProvinceEAPnl(wxgProvinceEAPnl.wxgProvinceEAPnl, gmEditArea.cGenericEditAreaMixin):
319
320 - def __init__(self, *args, **kwargs):
321 322 try: 323 data = kwargs['province'] 324 del kwargs['province'] 325 except KeyError: 326 data = None 327 328 wxgProvinceEAPnl.wxgProvinceEAPnl.__init__(self, *args, **kwargs) 329 gmEditArea.cGenericEditAreaMixin.__init__(self) 330 331 self.mode = 'new' 332 self.data = data 333 if data is not None: 334 self.mode = 'edit' 335 336 self.__init_ui()
337 #----------------------------------------------------------------
338 - def __init_ui(self):
339 self._PRW_province.selection_only = False
340 #---------------------------------------------------------------- 341 # generic Edit Area mixin API 342 #----------------------------------------------------------------
343 - def _valid_for_save(self):
344 345 validity = True 346 347 if self._PRW_province.GetData() is None: 348 if self._PRW_province.GetValue().strip() == u'': 349 validity = False 350 self._PRW_province.display_as_valid(False) 351 else: 352 self._PRW_province.display_as_valid(True) 353 else: 354 self._PRW_province.display_as_valid(True) 355 356 if self._PRW_province.GetData() is None: 357 if self._TCTRL_code.GetValue().strip() == u'': 358 validity = False 359 self._TCTRL_code.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 360 else: 361 self._TCTRL_code.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 362 363 if self._PRW_country.GetData() is None: 364 validity = False 365 self._PRW_country.display_as_valid(False) 366 else: 367 self._PRW_country.display_as_valid(True) 368 369 return validity
370 #----------------------------------------------------------------
371 - def _save_as_new(self):
372 gmDemographicRecord.create_province ( 373 name = self._PRW_province.GetValue().strip(), 374 code = self._TCTRL_code.GetValue().strip(), 375 country = self._PRW_country.GetData() 376 ) 377 378 # EA is refreshed automatically after save, so need this ... 379 self.data = { 380 'l10n_state' : self._PRW_province.GetValue().strip(), 381 'code_state' : self._TCTRL_code.GetValue().strip(), 382 'l10n_country' : self._PRW_country.GetValue().strip() 383 } 384 385 return True
386 #----------------------------------------------------------------
387 - def _save_as_update(self):
388 # update self.data and save the changes 389 #self.data[''] = 390 #self.data[''] = 391 #self.data[''] = 392 #self.data.save() 393 394 # do nothing for now (IOW, don't support updates) 395 return True
396 #----------------------------------------------------------------
397 - def _refresh_as_new(self):
398 self._PRW_province.SetText() 399 self._TCTRL_code.SetValue(u'') 400 self._PRW_country.SetText() 401 402 self._PRW_province.SetFocus()
403 #----------------------------------------------------------------
404 - def _refresh_from_existing(self):
405 self._PRW_province.SetText(self.data['l10n_state'], self.data['code_state']) 406 self._TCTRL_code.SetValue(self.data['code_state']) 407 self._PRW_country.SetText(self.data['l10n_country'], self.data['code_country']) 408 409 self._PRW_province.SetFocus()
410 #----------------------------------------------------------------
412 self._PRW_province.SetText() 413 self._TCTRL_code.SetValue(u'') 414 self._PRW_country.SetText(self.data['l10n_country'], self.data['code_country']) 415 416 self._PRW_province.SetFocus()
417 #============================================================ 418 #============================================================
419 -class cKOrganizerSchedulePnl(gmDataMiningWidgets.cPatientListingPnl):
420
421 - def __init__(self, *args, **kwargs):
422 423 kwargs['message'] = _("Today's KOrganizer appointments ...") 424 kwargs['button_defs'] = [ 425 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')}, 426 {'label': u''}, 427 {'label': u''}, 428 {'label': u''}, 429 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')} 430 ] 431 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs) 432 433 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv')) 434 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
435 436 #--------------------------------------------------------
437 - def _on_BTN_1_pressed(self, event):
438 """Reload appointments from KOrganizer.""" 439 self.reload_appointments()
440 #--------------------------------------------------------
441 - def _on_BTN_5_pressed(self, event):
442 """Reload appointments from KOrganizer.""" 443 found, cmd = gmShellAPI.detect_external_binary(binary = 'korganizer') 444 445 if not found: 446 gmDispatcher.send(signal = 'statustext', msg = _('KOrganizer is not installed.'), beep = True) 447 return 448 449 gmShellAPI.run_command_in_shell(command = cmd, blocking = False)
450 #--------------------------------------------------------
451 - def reload_appointments(self):
452 try: os.remove(self.fname) 453 except OSError: pass 454 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True) 455 try: 456 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace') 457 except IOError: 458 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True) 459 return 460 461 csv_lines = gmTools.unicode_csv_reader ( 462 csv_file, 463 delimiter = ',' 464 ) 465 # start_date, start_time, end_date, end_time, title (patient), ort, comment, UID 466 self._LCTRL_items.set_columns ([ 467 _('Place'), 468 _('Start'), 469 u'', 470 u'', 471 _('Patient'), 472 _('Comment') 473 ]) 474 items = [] 475 data = [] 476 for line in csv_lines: 477 items.append([line[5], line[0], line[1], line[3], line[4], line[6]]) 478 data.append([line[4], line[7]]) 479 480 self._LCTRL_items.set_string_items(items = items) 481 self._LCTRL_items.set_column_widths() 482 self._LCTRL_items.set_data(data = data) 483 self._LCTRL_items.patient_key = 0
484 #-------------------------------------------------------- 485 # notebook plugins API 486 #--------------------------------------------------------
487 - def repopulate_ui(self):
488 self.reload_appointments()
489 #============================================================
490 -def edit_occupation():
491 492 pat = gmPerson.gmCurrentPatient() 493 curr_jobs = pat.get_occupations() 494 if len(curr_jobs) > 0: 495 old_job = curr_jobs[0]['l10n_occupation'] 496 update = curr_jobs[0]['modified_when'].strftime('%m/%Y') 497 else: 498 old_job = u'' 499 update = u'' 500 501 msg = _( 502 'Please enter the primary occupation of the patient.\n' 503 '\n' 504 'Currently recorded:\n' 505 '\n' 506 ' %s (last updated %s)' 507 ) % (old_job, update) 508 509 new_job = wx.GetTextFromUser ( 510 message = msg, 511 caption = _('Editing primary occupation'), 512 default_value = old_job, 513 parent = None 514 ) 515 if new_job.strip() == u'': 516 return 517 518 for job in curr_jobs: 519 # unlink all but the new job 520 if job['l10n_occupation'] != new_job: 521 pat.unlink_occupation(occupation = job['l10n_occupation']) 522 # and link the new one 523 pat.link_occupation(occupation = new_job)
524 #============================================================
525 -def disable_identity(identity=None):
526 # ask user for assurance 527 go_ahead = gmGuiHelpers.gm_show_question ( 528 _('Are you sure you really, positively want\n' 529 'to disable the following person ?\n' 530 '\n' 531 ' %s %s %s\n' 532 ' born %s\n' 533 '\n' 534 '%s\n' 535 ) % ( 536 identity['firstnames'], 537 identity['lastnames'], 538 identity['gender'], 539 identity['dob'], 540 gmTools.bool2subst ( 541 identity.is_patient, 542 _('This patient DID receive care.'), 543 _('This person did NOT receive care.') 544 ) 545 ), 546 _('Disabling person') 547 ) 548 if not go_ahead: 549 return True 550 551 # get admin connection 552 conn = gmAuthWidgets.get_dbowner_connection ( 553 procedure = _('Disabling patient') 554 ) 555 # - user cancelled 556 if conn is False: 557 return True 558 # - error 559 if conn is None: 560 return False 561 562 # now disable patient 563 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}]) 564 565 return True
566 #============================================================ 567 # address phrasewheels and widgets 568 #============================================================
569 -class cPersonAddressesManagerPnl(gmListWidgets.cGenericListManagerPnl):
570 """A list for managing a person's addresses. 571 572 Does NOT act on/listen to the current patient. 573 """
574 - def __init__(self, *args, **kwargs):
575 576 try: 577 self.__identity = kwargs['identity'] 578 del kwargs['identity'] 579 except KeyError: 580 self.__identity = None 581 582 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 583 584 self.new_callback = self._add_address 585 self.edit_callback = self._edit_address 586 self.delete_callback = self._del_address 587 self.refresh_callback = self.refresh 588 589 self.__init_ui() 590 self.refresh()
591 #-------------------------------------------------------- 592 # external API 593 #--------------------------------------------------------
594 - def refresh(self, *args, **kwargs):
595 if self.__identity is None: 596 self._LCTRL_items.set_string_items() 597 return 598 599 adrs = self.__identity.get_addresses() 600 self._LCTRL_items.set_string_items ( 601 items = [ [ 602 a['l10n_address_type'], 603 a['street'], 604 gmTools.coalesce(a['notes_street'], u''), 605 a['number'], 606 gmTools.coalesce(a['subunit'], u''), 607 a['postcode'], 608 a['urb'], 609 gmTools.coalesce(a['suburb'], u''), 610 a['l10n_state'], 611 a['l10n_country'], 612 gmTools.coalesce(a['notes_subunit'], u'') 613 ] for a in adrs 614 ] 615 ) 616 self._LCTRL_items.set_column_widths() 617 self._LCTRL_items.set_data(data = adrs)
618 #-------------------------------------------------------- 619 # internal helpers 620 #--------------------------------------------------------
621 - def __init_ui(self):
622 self._LCTRL_items.SetToolTipString(_('List of known addresses.')) 623 self._LCTRL_items.set_columns(columns = [ 624 _('Type'), 625 _('Street'), 626 _('Street info'), 627 _('Number'), 628 _('Subunit'), 629 _('Postal code'), 630 _('Place'), 631 _('Suburb'), 632 _('Region'), 633 _('Country'), 634 _('Comment') 635 ])
636 #--------------------------------------------------------
637 - def _add_address(self):
638 ea = cAddressEditAreaPnl(self, -1) 639 ea.identity = self.__identity 640 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 641 dlg.SetTitle(_('Adding new address')) 642 if dlg.ShowModal() == wx.ID_OK: 643 return True 644 return False
645 #--------------------------------------------------------
646 - def _edit_address(self, address):
647 ea = cAddressEditAreaPnl(self, -1, address = address) 648 ea.identity = self.__identity 649 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 650 dlg.SetTitle(_('Editing address')) 651 if dlg.ShowModal() == wx.ID_OK: 652 # did we add an entirely new address ? 653 # if so then unlink the old one as implied by "edit" 654 if ea.address['pk_address'] != address['pk_address']: 655 self.__identity.unlink_address(address = address) 656 return True 657 return False
658 #--------------------------------------------------------
659 - def _del_address(self, address):
660 go_ahead = gmGuiHelpers.gm_show_question ( 661 _( 'Are you sure you want to remove this\n' 662 "address from the patient's addresses ?\n" 663 '\n' 664 'The address itself will not be deleted\n' 665 'but it will no longer be associated with\n' 666 'this patient.' 667 ), 668 _('Removing address') 669 ) 670 if not go_ahead: 671 return False 672 self.__identity.unlink_address(address = address) 673 return True
674 #-------------------------------------------------------- 675 # properties 676 #--------------------------------------------------------
677 - def _get_identity(self):
678 return self.__identity
679
680 - def _set_identity(self, identity):
681 self.__identity = identity 682 self.refresh()
683 684 identity = property(_get_identity, _set_identity)
685 #============================================================
686 -class cPersonContactsManagerPnl(wxgPersonContactsManagerPnl.wxgPersonContactsManagerPnl):
687 """A panel for editing contact data for a person. 688 689 - provides access to: 690 - addresses 691 - communication paths 692 693 Does NOT act on/listen to the current patient. 694 """
695 - def __init__(self, *args, **kwargs):
696 697 wxgPersonContactsManagerPnl.wxgPersonContactsManagerPnl.__init__(self, *args, **kwargs) 698 699 self.__identity = None 700 self.refresh()
701 #-------------------------------------------------------- 702 # external API 703 #--------------------------------------------------------
704 - def refresh(self):
705 self._PNL_addresses.identity = self.__identity 706 self._PNL_comms.identity = self.__identity
707 #-------------------------------------------------------- 708 # properties 709 #--------------------------------------------------------
710 - def _get_identity(self):
711 return self.__identity
712
713 - def _set_identity(self, identity):
714 self.__identity = identity 715 self.refresh()
716 717 identity = property(_get_identity, _set_identity)
718 #============================================================
719 -class cAddressEditAreaPnl(wxgGenericAddressEditAreaPnl.wxgGenericAddressEditAreaPnl):
720 """An edit area for editing/creating an address. 721 722 Does NOT act on/listen to the current patient. 723 """
724 - def __init__(self, *args, **kwargs):
725 try: 726 self.address = kwargs['address'] 727 del kwargs['address'] 728 except KeyError: 729 self.address = None 730 731 wxgGenericAddressEditAreaPnl.wxgGenericAddressEditAreaPnl.__init__(self, *args, **kwargs) 732 733 self.identity = None 734 735 self.__register_interests() 736 self.refresh()
737 #-------------------------------------------------------- 738 # external API 739 #--------------------------------------------------------
740 - def refresh(self, address = None):
741 if address is not None: 742 self.address = address 743 744 if self.address is not None: 745 self._PRW_type.SetText(self.address['l10n_address_type']) 746 self._PRW_zip.SetText(self.address['postcode']) 747 self._PRW_street.SetText(self.address['street'], data = self.address['street']) 748 self._TCTRL_notes_street.SetValue(gmTools.coalesce(self.address['notes_street'], '')) 749 self._TCTRL_number.SetValue(self.address['number']) 750 self._TCTRL_subunit.SetValue(gmTools.coalesce(self.address['subunit'], '')) 751 self._PRW_suburb.SetText(gmTools.coalesce(self.address['suburb'], '')) 752 self._PRW_urb.SetText(self.address['urb'], data = self.address['urb']) 753 self._PRW_state.SetText(self.address['l10n_state'], data = self.address['code_state']) 754 self._PRW_country.SetText(self.address['l10n_country'], data = self.address['code_country']) 755 self._TCTRL_notes_subunit.SetValue(gmTools.coalesce(self.address['notes_subunit'], ''))
756 # FIXME: clear fields 757 # else: 758 # pass 759 #--------------------------------------------------------
760 - def save(self):
761 """Links address to patient, creating new address if necessary""" 762 763 if not self.__valid_for_save(): 764 return False 765 766 # link address to patient 767 try: 768 adr = self.identity.link_address ( 769 number = self._TCTRL_number.GetValue().strip(), 770 street = self._PRW_street.GetValue().strip(), 771 postcode = self._PRW_zip.GetValue().strip(), 772 urb = self._PRW_urb.GetValue().strip(), 773 state = self._PRW_state.GetData(), 774 country = self._PRW_country.GetData(), 775 subunit = gmTools.none_if(self._TCTRL_subunit.GetValue().strip(), u''), 776 suburb = gmTools.none_if(self._PRW_suburb.GetValue().strip(), u''), 777 id_type = self._PRW_type.GetData() 778 ) 779 except: 780 _log.exception('cannot save address') 781 gmGuiHelpers.gm_show_error ( 782 _('Cannot save address.\n\n' 783 'Does the state [%s]\n' 784 'exist in country [%s] ?' 785 ) % ( 786 self._PRW_state.GetValue().strip(), 787 self._PRW_country.GetValue().strip() 788 ), 789 _('Saving address') 790 ) 791 return False 792 793 notes = self._TCTRL_notes_street.GetValue().strip() 794 if notes != u'': 795 adr['notes_street'] = notes 796 notes = self._TCTRL_notes_subunit.GetValue().strip() 797 if notes != u'': 798 adr['notes_subunit'] = notes 799 adr.save_payload() 800 801 self.address = adr 802 803 return True
804 #-------------------------------------------------------- 805 # event handling 806 #--------------------------------------------------------
807 - def __register_interests(self):
808 self._PRW_zip.add_callback_on_lose_focus(self._on_zip_set) 809 self._PRW_country.add_callback_on_lose_focus(self._on_country_set)
810 #--------------------------------------------------------
811 - def _on_zip_set(self):
812 """Set the street, town, state and country according to entered zip code.""" 813 zip_code = self._PRW_zip.GetValue() 814 if zip_code.strip() == u'': 815 self._PRW_street.unset_context(context = u'zip') 816 self._PRW_urb.unset_context(context = u'zip') 817 self._PRW_state.unset_context(context = u'zip') 818 self._PRW_country.unset_context(context = u'zip') 819 else: 820 self._PRW_street.set_context(context = u'zip', val = zip_code) 821 self._PRW_urb.set_context(context = u'zip', val = zip_code) 822 self._PRW_state.set_context(context = u'zip', val = zip_code) 823 self._PRW_country.set_context(context = u'zip', val = zip_code)
824 #--------------------------------------------------------
825 - def _on_country_set(self):
826 """Set the states according to entered country.""" 827 country = self._PRW_country.GetData() 828 if country is None: 829 self._PRW_state.unset_context(context = 'country') 830 else: 831 self._PRW_state.set_context(context = 'country', val = country)
832 #-------------------------------------------------------- 833 # internal helpers 834 #--------------------------------------------------------
835 - def __valid_for_save(self):
836 837 # validate required fields 838 is_any_field_filled = False 839 840 required_fields = ( 841 self._PRW_type, 842 self._PRW_zip, 843 self._PRW_street, 844 self._TCTRL_number, 845 self._PRW_urb 846 ) 847 for field in required_fields: 848 if len(field.GetValue().strip()) == 0: 849 if is_any_field_filled: 850 field.SetBackgroundColour('pink') 851 field.SetFocus() 852 field.Refresh() 853 gmGuiHelpers.gm_show_error ( 854 _('Address details must be filled in completely or not at all.'), 855 _('Saving contact data') 856 ) 857 return False 858 else: 859 is_any_field_filled = True 860 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 861 field.Refresh() 862 863 required_fields = ( 864 self._PRW_state, 865 self._PRW_country 866 ) 867 for field in required_fields: 868 if field.GetData() is None: 869 if is_any_field_filled: 870 field.SetBackgroundColour('pink') 871 field.SetFocus() 872 field.Refresh() 873 gmGuiHelpers.gm_show_error ( 874 _('Address details must be filled in completely or not at all.'), 875 _('Saving contact data') 876 ) 877 return False 878 else: 879 is_any_field_filled = True 880 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 881 field.Refresh() 882 883 return True
884 #============================================================
885 -class cAddressMatchProvider(gmMatchProvider.cMatchProvider_SQL2):
886
887 - def __init__(self):
888 889 query = u""" 890 select * from ( 891 (select 892 pk_address, 893 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', ' 894 || urb || coalesce(' (' || suburb || ')', '') || ', ' 895 || postcode 896 || coalesce(', ' || notes_street, '') 897 || coalesce(', ' || notes_subunit, '') 898 ) as address 899 from 900 dem.v_address 901 where 902 street %(fragment_condition)s 903 904 ) union ( 905 906 select 907 pk_address, 908 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', ' 909 || urb || coalesce(' (' || suburb || ')', '') || ', ' 910 || postcode 911 || coalesce(', ' || notes_street, '') 912 || coalesce(', ' || notes_subunit, '') 913 ) as address 914 from 915 dem.v_address 916 where 917 postcode_street %(fragment_condition)s 918 919 ) union ( 920 921 select 922 pk_address, 923 (street || ' ' || number || coalesce(' (' || subunit || ')', '') || ', ' 924 || urb || coalesce(' (' || suburb || ')', '') || ', ' 925 || postcode 926 || coalesce(', ' || notes_street, '') 927 || coalesce(', ' || notes_subunit, '') 928 ) as address 929 from 930 dem.v_address 931 where 932 postcode_urb %(fragment_condition)s 933 ) 934 ) as union_result 935 order by union_result.address limit 50""" 936 937 gmMatchProvider.cMatchProvider_SQL2.__init__(self, queries = query) 938 939 self.setThresholds(2, 4, 6)
940 # self.word_separators = u'[ \t]+' 941 942 #============================================================
943 -class cAddressPhraseWheel(gmPhraseWheel.cPhraseWheel):
944
945 - def __init__(self, *args, **kwargs):
946 947 mp = cAddressMatchProvider() 948 gmPhraseWheel.cPhraseWheel.__init__ ( 949 self, 950 *args, 951 **kwargs 952 ) 953 self.matcher = cAddressMatchProvider() 954 self.SetToolTipString(_('Select an address by postcode or street name.')) 955 self.selection_only = True 956 self.__address = None 957 self.__old_pk = None
958 #--------------------------------------------------------
959 - def get_address(self):
960 961 pk = self.GetData() 962 963 if pk is None: 964 self.__address = None 965 return None 966 967 if self.__address is None: 968 self.__old_pk = pk 969 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk) 970 else: 971 if pk != self.__old_pk: 972 self.__old_pk = pk 973 self.__address = gmDemographicRecord.cAddress(aPK_obj = pk) 974 975 return self.__address
976 #============================================================
977 -class cAddressTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
978
979 - def __init__(self, *args, **kwargs):
980 981 query = u""" 982 select id, type from (( 983 select id, _(name) as type, 1 as rank 984 from dem.address_type 985 where _(name) %(fragment_condition)s 986 ) union ( 987 select id, name as type, 2 as rank 988 from dem.address_type 989 where name %(fragment_condition)s 990 )) as ur 991 order by 992 ur.rank, ur.type 993 """ 994 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 995 mp.setThresholds(1, 2, 4) 996 mp.word_separators = u'[ \t]+' 997 gmPhraseWheel.cPhraseWheel.__init__ ( 998 self, 999 *args, 1000 **kwargs 1001 ) 1002 self.matcher = mp 1003 self.SetToolTipString(_('Select the type of address.')) 1004 # self.capitalisation_mode = gmTools.CAPS_FIRST 1005 self.selection_only = True
1006 #-------------------------------------------------------- 1007 # def GetData(self, can_create=False): 1008 # if self.data is None: 1009 # if can_create: 1010 # self.data = gmDocuments.create_document_type(self.GetValue().strip())['pk_doc_type'] # FIXME: error handling 1011 # return self.data 1012 #============================================================
1013 -class cZipcodePhraseWheel(gmPhraseWheel.cPhraseWheel):
1014
1015 - def __init__(self, *args, **kwargs):
1016 # FIXME: add possible context 1017 query = u""" 1018 (select distinct postcode, postcode from dem.street where postcode %(fragment_condition)s limit 20) 1019 union 1020 (select distinct postcode, postcode from dem.urb where postcode %(fragment_condition)s limit 20)""" 1021 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1022 mp.setThresholds(2, 3, 15) 1023 gmPhraseWheel.cPhraseWheel.__init__ ( 1024 self, 1025 *args, 1026 **kwargs 1027 ) 1028 self.SetToolTipString(_("Type or select a zip code (postcode).")) 1029 self.matcher = mp
1030 #============================================================
1031 -class cStreetPhraseWheel(gmPhraseWheel.cPhraseWheel):
1032
1033 - def __init__(self, *args, **kwargs):
1034 context = { 1035 u'ctxt_zip': { 1036 u'where_part': u'and zip ilike %(zip)s', 1037 u'placeholder': u'zip' 1038 } 1039 } 1040 query = u""" 1041 select s1, s2 from ( 1042 select s1, s2, rank from ( 1043 select distinct on (street) 1044 street as s1, street as s2, 1 as rank 1045 from dem.v_zip2data 1046 where 1047 street %(fragment_condition)s 1048 %(ctxt_zip)s 1049 1050 union all 1051 1052 select distinct on (name) 1053 name as s1, name as s2, 2 as rank 1054 from dem.street 1055 where 1056 name %(fragment_condition)s 1057 1058 ) as q2 1059 ) as q1 order by rank, s2 limit 50""" 1060 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 1061 mp.setThresholds(3, 5, 8) 1062 gmPhraseWheel.cPhraseWheel.__init__ ( 1063 self, 1064 *args, 1065 **kwargs 1066 ) 1067 self.unset_context(context = u'zip') 1068 1069 self.SetToolTipString(_('Type or select a street.')) 1070 self.capitalisation_mode = gmTools.CAPS_FIRST 1071 self.matcher = mp
1072 #============================================================
1073 -class cSuburbPhraseWheel(gmPhraseWheel.cPhraseWheel):
1074
1075 - def __init__(self, *args, **kwargs):
1076 1077 query = """ 1078 select distinct on (suburb) suburb, suburb 1079 from dem.street 1080 where suburb %(fragment_condition)s 1081 order by suburb 1082 limit 50 1083 """ 1084 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1085 mp.setThresholds(2, 3, 6) 1086 gmPhraseWheel.cPhraseWheel.__init__ ( 1087 self, 1088 *args, 1089 **kwargs 1090 ) 1091 1092 self.SetToolTipString(_('Type or select the suburb.')) 1093 self.capitalisation_mode = gmTools.CAPS_FIRST 1094 self.matcher = mp
1095 #============================================================
1096 -class cUrbPhraseWheel(gmPhraseWheel.cPhraseWheel):
1097
1098 - def __init__(self, *args, **kwargs):
1099 context = { 1100 u'ctxt_zip': { 1101 u'where_part': u'and zip ilike %(zip)s', 1102 u'placeholder': u'zip' 1103 } 1104 } 1105 query = u""" 1106 select u1, u2 from ( 1107 select distinct on (rank, u1) 1108 u1, u2, rank 1109 from ( 1110 select 1111 urb as u1, urb as u2, 1 as rank 1112 from dem.v_zip2data 1113 where 1114 urb %(fragment_condition)s 1115 %(ctxt_zip)s 1116 1117 union all 1118 1119 select 1120 name as u1, name as u2, 2 as rank 1121 from dem.urb 1122 where 1123 name %(fragment_condition)s 1124 ) as union_result 1125 order by rank, u1 1126 ) as distincted_union 1127 limit 50 1128 """ 1129 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query, context=context) 1130 mp.setThresholds(3, 5, 7) 1131 gmPhraseWheel.cPhraseWheel.__init__ ( 1132 self, 1133 *args, 1134 **kwargs 1135 ) 1136 self.unset_context(context = u'zip') 1137 1138 self.SetToolTipString(_('Type or select a city/town/village/dwelling.')) 1139 self.capitalisation_mode = gmTools.CAPS_FIRST 1140 self.matcher = mp
1141 #============================================================ 1142 # communications channel related widgets 1143 #============================================================
1144 -class cCommChannelTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
1145
1146 - def __init__(self, *args, **kwargs):
1147 1148 query = u""" 1149 select pk, type from (( 1150 select pk, _(description) as type, 1 as rank 1151 from dem.enum_comm_types 1152 where _(description) %(fragment_condition)s 1153 ) union ( 1154 select pk, description as type, 2 as rank 1155 from dem.enum_comm_types 1156 where description %(fragment_condition)s 1157 )) as ur 1158 order by 1159 ur.rank, ur.type 1160 """ 1161 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1162 mp.setThresholds(1, 2, 4) 1163 mp.word_separators = u'[ \t]+' 1164 gmPhraseWheel.cPhraseWheel.__init__ ( 1165 self, 1166 *args, 1167 **kwargs 1168 ) 1169 self.matcher = mp 1170 self.SetToolTipString(_('Select the type of communications channel.')) 1171 self.selection_only = True
1172 #------------------------------------------------------------
1173 -class cCommChannelEditAreaPnl(wxgCommChannelEditAreaPnl.wxgCommChannelEditAreaPnl):
1174 """An edit area for editing/creating a comms channel. 1175 1176 Does NOT act on/listen to the current patient. 1177 """
1178 - def __init__(self, *args, **kwargs):
1179 try: 1180 self.channel = kwargs['comm_channel'] 1181 del kwargs['comm_channel'] 1182 except KeyError: 1183 self.channel = None 1184 1185 wxgCommChannelEditAreaPnl.wxgCommChannelEditAreaPnl.__init__(self, *args, **kwargs) 1186 1187 self.identity = None 1188 1189 self.refresh()
1190 #-------------------------------------------------------- 1191 # external API 1192 #--------------------------------------------------------
1193 - def refresh(self, comm_channel = None):
1194 if comm_channel is not None: 1195 self.channel = comm_channel 1196 1197 if self.channel is None: 1198 self._PRW_type.SetText(u'') 1199 self._TCTRL_url.SetValue(u'') 1200 self._PRW_address.SetText(value = u'', data = None) 1201 self._CHBOX_confidential.SetValue(False) 1202 else: 1203 self._PRW_type.SetText(self.channel['l10n_comm_type']) 1204 self._TCTRL_url.SetValue(self.channel['url']) 1205 self._PRW_address.SetData(data = self.channel['pk_address']) 1206 self._CHBOX_confidential.SetValue(self.channel['is_confidential'])
1207 #--------------------------------------------------------
1208 - def save(self):
1209 """Links comm channel to patient.""" 1210 if self.channel is None: 1211 if not self.__valid_for_save(): 1212 return False 1213 try: 1214 self.channel = self.identity.link_comm_channel ( 1215 pk_channel_type = self._PRW_type.GetData(), 1216 url = self._TCTRL_url.GetValue().strip(), 1217 is_confidential = self._CHBOX_confidential.GetValue(), 1218 ) 1219 except psycopg2.IntegrityError: 1220 _log.exception('error saving comm channel') 1221 gmDispatcher.send(signal = u'statustext', msg = _('Cannot save communications channel.'), beep = True) 1222 return False 1223 else: 1224 comm_type = self._PRW_type.GetValue().strip() 1225 if comm_type != u'': 1226 self.channel['comm_type'] = comm_type 1227 url = self._TCTRL_url.GetValue().strip() 1228 if url != u'': 1229 self.channel['url'] = url 1230 self.channel['is_confidential'] = self._CHBOX_confidential.GetValue() 1231 1232 self.channel['pk_address'] = self._PRW_address.GetData() 1233 self.channel.save_payload() 1234 1235 return True
1236 #-------------------------------------------------------- 1237 # internal helpers 1238 #--------------------------------------------------------
1239 - def __valid_for_save(self):
1240 1241 no_errors = True 1242 1243 if self._PRW_type.GetData() is None: 1244 self._PRW_type.SetBackgroundColour('pink') 1245 self._PRW_type.SetFocus() 1246 self._PRW_type.Refresh() 1247 no_errors = False 1248 else: 1249 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1250 self._PRW_type.Refresh() 1251 1252 if self._TCTRL_url.GetValue().strip() == u'': 1253 self._TCTRL_url.SetBackgroundColour('pink') 1254 self._TCTRL_url.SetFocus() 1255 self._TCTRL_url.Refresh() 1256 no_errors = False 1257 else: 1258 self._TCTRL_url.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1259 self._TCTRL_url.Refresh() 1260 1261 return no_errors
1262 #------------------------------------------------------------
1263 -class cPersonCommsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1264 """A list for managing a person's comm channels. 1265 1266 Does NOT act on/listen to the current patient. 1267 """
1268 - def __init__(self, *args, **kwargs):
1269 1270 try: 1271 self.__identity = kwargs['identity'] 1272 del kwargs['identity'] 1273 except KeyError: 1274 self.__identity = None 1275 1276 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1277 1278 self.new_callback = self._add_comm 1279 self.edit_callback = self._edit_comm 1280 self.delete_callback = self._del_comm 1281 self.refresh_callback = self.refresh 1282 1283 self.__init_ui() 1284 self.refresh()
1285 #-------------------------------------------------------- 1286 # external API 1287 #--------------------------------------------------------
1288 - def refresh(self, *args, **kwargs):
1289 if self.__identity is None: 1290 self._LCTRL_items.set_string_items() 1291 return 1292 1293 comms = self.__identity.get_comm_channels() 1294 self._LCTRL_items.set_string_items ( 1295 items = [ [ gmTools.bool2str(c['is_confidential'], u'X', u''), c['l10n_comm_type'], c['url'] ] for c in comms ] 1296 ) 1297 self._LCTRL_items.set_column_widths() 1298 self._LCTRL_items.set_data(data = comms)
1299 #-------------------------------------------------------- 1300 # internal helpers 1301 #--------------------------------------------------------
1302 - def __init_ui(self):
1303 self._LCTRL_items.SetToolTipString(_('List of known communication channels.')) 1304 self._LCTRL_items.set_columns(columns = [ 1305 _('confidential'), 1306 _('Type'), 1307 _('Value') 1308 ])
1309 #--------------------------------------------------------
1310 - def _add_comm(self):
1311 ea = cCommChannelEditAreaPnl(self, -1) 1312 ea.identity = self.__identity 1313 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1314 dlg.SetTitle(_('Adding new communications channel')) 1315 if dlg.ShowModal() == wx.ID_OK: 1316 return True 1317 return False
1318 #--------------------------------------------------------
1319 - def _edit_comm(self, comm_channel):
1320 ea = cCommChannelEditAreaPnl(self, -1, comm_channel = comm_channel) 1321 ea.identity = self.__identity 1322 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1323 dlg.SetTitle(_('Editing communications channel')) 1324 if dlg.ShowModal() == wx.ID_OK: 1325 return True 1326 return False
1327 #--------------------------------------------------------
1328 - def _del_comm(self, comm):
1329 go_ahead = gmGuiHelpers.gm_show_question ( 1330 _( 'Are you sure this patient can no longer\n' 1331 "be contacted via this channel ?" 1332 ), 1333 _('Removing communication channel') 1334 ) 1335 if not go_ahead: 1336 return False 1337 self.__identity.unlink_comm_channel(comm_channel = comm) 1338 return True
1339 #-------------------------------------------------------- 1340 # properties 1341 #--------------------------------------------------------
1342 - def _get_identity(self):
1343 return self.__identity
1344
1345 - def _set_identity(self, identity):
1346 self.__identity = identity 1347 self.refresh()
1348 1349 identity = property(_get_identity, _set_identity)
1350 #============================================================ 1351 # identity widgets 1352 #============================================================ 1353 # phrasewheels 1354 #------------------------------------------------------------
1355 -class cLastnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
1356
1357 - def __init__(self, *args, **kwargs):
1358 query = u"select distinct lastnames, lastnames from dem.names where lastnames %(fragment_condition)s order by lastnames limit 25" 1359 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1360 mp.setThresholds(3, 5, 9) 1361 gmPhraseWheel.cPhraseWheel.__init__ ( 1362 self, 1363 *args, 1364 **kwargs 1365 ) 1366 self.SetToolTipString(_("Type or select a last name (family name/surname).")) 1367 self.capitalisation_mode = gmTools.CAPS_NAMES 1368 self.matcher = mp
1369 #------------------------------------------------------------
1370 -class cFirstnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
1371
1372 - def __init__(self, *args, **kwargs):
1373 query = u""" 1374 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 1375 union 1376 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 1377 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1378 mp.setThresholds(3, 5, 9) 1379 gmPhraseWheel.cPhraseWheel.__init__ ( 1380 self, 1381 *args, 1382 **kwargs 1383 ) 1384 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name).")) 1385 self.capitalisation_mode = gmTools.CAPS_NAMES 1386 self.matcher = mp
1387 #------------------------------------------------------------
1388 -class cNicknamePhraseWheel(gmPhraseWheel.cPhraseWheel):
1389
1390 - def __init__(self, *args, **kwargs):
1391 query = u""" 1392 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20) 1393 union 1394 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 1395 union 1396 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 1397 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1398 mp.setThresholds(3, 5, 9) 1399 gmPhraseWheel.cPhraseWheel.__init__ ( 1400 self, 1401 *args, 1402 **kwargs 1403 ) 1404 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name).")) 1405 # nicknames CAN start with lower case ! 1406 #self.capitalisation_mode = gmTools.CAPS_NAMES 1407 self.matcher = mp
1408 #------------------------------------------------------------
1409 -class cTitlePhraseWheel(gmPhraseWheel.cPhraseWheel):
1410
1411 - def __init__(self, *args, **kwargs):
1412 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s" 1413 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1414 mp.setThresholds(1, 3, 9) 1415 gmPhraseWheel.cPhraseWheel.__init__ ( 1416 self, 1417 *args, 1418 **kwargs 1419 ) 1420 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !")) 1421 self.matcher = mp
1422 #------------------------------------------------------------
1423 -class cGenderSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
1424 """Let user select a gender.""" 1425 1426 _gender_map = None 1427
1428 - def __init__(self, *args, **kwargs):
1429 1430 if cGenderSelectionPhraseWheel._gender_map is None: 1431 cmd = u""" 1432 select tag, l10n_label, sort_weight 1433 from dem.v_gender_labels 1434 order by sort_weight desc""" 1435 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 1436 cGenderSelectionPhraseWheel._gender_map = {} 1437 for gender in rows: 1438 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = { 1439 'data': gender[idx['tag']], 1440 'label': gender[idx['l10n_label']], 1441 'weight': gender[idx['sort_weight']] 1442 } 1443 1444 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values()) 1445 mp.setThresholds(1, 1, 3) 1446 1447 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1448 self.selection_only = True 1449 self.matcher = mp 1450 self.picklist_delay = 50
1451 #------------------------------------------------------------
1452 -class cOccupationPhraseWheel(gmPhraseWheel.cPhraseWheel):
1453
1454 - def __init__(self, *args, **kwargs):
1455 query = u"select distinct name, _(name) from dem.occupation where _(name) %(fragment_condition)s" 1456 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1457 mp.setThresholds(1, 3, 5) 1458 gmPhraseWheel.cPhraseWheel.__init__ ( 1459 self, 1460 *args, 1461 **kwargs 1462 ) 1463 self.SetToolTipString(_("Type or select an occupation.")) 1464 self.capitalisation_mode = gmTools.CAPS_FIRST 1465 self.matcher = mp
1466 #------------------------------------------------------------
1467 -class cExternalIDTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
1468
1469 - def __init__(self, *args, **kwargs):
1470 query = u""" 1471 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label 1472 from dem.enum_ext_id_types 1473 where name %%(fragment_condition)s 1474 order by label limit 25""" % _('issued by') 1475 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1476 mp.setThresholds(1, 3, 5) 1477 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1478 self.SetToolTipString(_("Enter or select a type for the external ID.")) 1479 self.matcher = mp
1480 #------------------------------------------------------------
1481 -class cExternalIDIssuerPhraseWheel(gmPhraseWheel.cPhraseWheel):
1482
1483 - def __init__(self, *args, **kwargs):
1484 query = u""" 1485 select distinct issuer, issuer 1486 from dem.enum_ext_id_types 1487 where issuer %(fragment_condition)s 1488 order by issuer limit 25""" 1489 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 1490 mp.setThresholds(1, 3, 5) 1491 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 1492 self.SetToolTipString(_("Type or select an occupation.")) 1493 self.capitalisation_mode = gmTools.CAPS_FIRST 1494 self.matcher = mp
1495 #------------------------------------------------------------ 1496 # edit areas 1497 #------------------------------------------------------------
1498 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl):
1499 """An edit area for editing/creating external IDs. 1500 1501 Does NOT act on/listen to the current patient. 1502 """
1503 - def __init__(self, *args, **kwargs):
1504 1505 try: 1506 self.ext_id = kwargs['external_id'] 1507 del kwargs['external_id'] 1508 except: 1509 self.ext_id = None 1510 1511 wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs) 1512 1513 self.identity = None 1514 1515 self.__register_events() 1516 1517 self.refresh()
1518 #-------------------------------------------------------- 1519 # external API 1520 #--------------------------------------------------------
1521 - def refresh(self, ext_id=None):
1522 if ext_id is not None: 1523 self.ext_id = ext_id 1524 1525 if self.ext_id is not None: 1526 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type']) 1527 self._TCTRL_value.SetValue(self.ext_id['value']) 1528 self._PRW_issuer.SetText(self.ext_id['issuer']) 1529 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
1530 # FIXME: clear fields 1531 # else: 1532 # pass 1533 #--------------------------------------------------------
1534 - def save(self):
1535 1536 if not self.__valid_for_save(): 1537 return False 1538 1539 # strip out " (issued by ...)" added by phrasewheel 1540 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0] 1541 1542 # add new external ID 1543 if self.ext_id is None: 1544 self.identity.add_external_id ( 1545 type_name = type, 1546 value = self._TCTRL_value.GetValue().strip(), 1547 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''), 1548 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1549 ) 1550 # edit old external ID 1551 else: 1552 self.identity.update_external_id ( 1553 pk_id = self.ext_id['pk_id'], 1554 type = type, 1555 value = self._TCTRL_value.GetValue().strip(), 1556 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''), 1557 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1558 ) 1559 1560 return True
1561 #-------------------------------------------------------- 1562 # internal helpers 1563 #--------------------------------------------------------
1564 - def __register_events(self):
1565 self._PRW_type.add_callback_on_lose_focus(self._on_type_set)
1566 #--------------------------------------------------------
1567 - def _on_type_set(self):
1568 """Set the issuer according to the selected type. 1569 1570 Matches are fetched from existing records in backend. 1571 """ 1572 pk_curr_type = self._PRW_type.GetData() 1573 if pk_curr_type is None: 1574 return True 1575 rows, idx = gmPG2.run_ro_queries(queries = [{ 1576 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s", 1577 'args': [pk_curr_type] 1578 }]) 1579 if len(rows) == 0: 1580 return True 1581 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0]) 1582 return True
1583 #--------------------------------------------------------
1584 - def __valid_for_save(self):
1585 1586 no_errors = True 1587 1588 # do not test .GetData() because adding external IDs 1589 # will create types if necessary 1590 # if self._PRW_type.GetData() is None: 1591 if self._PRW_type.GetValue().strip() == u'': 1592 self._PRW_type.SetBackgroundColour('pink') 1593 self._PRW_type.SetFocus() 1594 self._PRW_type.Refresh() 1595 else: 1596 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1597 self._PRW_type.Refresh() 1598 1599 if self._TCTRL_value.GetValue().strip() == u'': 1600 self._TCTRL_value.SetBackgroundColour('pink') 1601 self._TCTRL_value.SetFocus() 1602 self._TCTRL_value.Refresh() 1603 no_errors = False 1604 else: 1605 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1606 self._TCTRL_value.Refresh() 1607 1608 return no_errors
1609 #------------------------------------------------------------ 1610 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl 1611
1612 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
1613 """An edit area for editing/creating title/gender/dob/dod etc.""" 1614
1615 - def __init__(self, *args, **kwargs):
1616 1617 try: 1618 data = kwargs['identity'] 1619 del kwargs['identity'] 1620 except KeyError: 1621 data = None 1622 1623 wxgIdentityEAPnl.wxgIdentityEAPnl.__init__(self, *args, **kwargs) 1624 gmEditArea.cGenericEditAreaMixin.__init__(self) 1625 1626 self.mode = 'new' 1627 self.data = data 1628 if data is not None: 1629 self.mode = 'edit'
1630 1631 # self.__init_ui() 1632 #---------------------------------------------------------------- 1633 # def __init_ui(self): 1634 # # adjust phrasewheels etc 1635 #---------------------------------------------------------------- 1636 # generic Edit Area mixin API 1637 #----------------------------------------------------------------
1638 - def _valid_for_save(self):
1639 1640 has_error = False 1641 1642 if self._PRW_gender.GetData() is None: 1643 self._PRW_gender.SetFocus() 1644 has_error = True 1645 1646 if not self._PRW_dob.is_valid_timestamp(): 1647 val = self._PRW_dob.GetValue().strip() 1648 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 1649 self._PRW_dob.SetBackgroundColour('pink') 1650 self._PRW_dob.Refresh() 1651 self._PRW_dob.SetFocus() 1652 has_error = True 1653 else: 1654 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1655 self._PRW_dob.Refresh() 1656 1657 if not self._DP_dod.is_valid_timestamp(allow_none=True): 1658 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 1659 self._DP_dod.SetFocus() 1660 has_error = True 1661 1662 return (has_error is False)
1663 #----------------------------------------------------------------
1664 - def _save_as_new(self):
1665 # save the data as a new instance 1666 # data = 1 1667 1668 # data[''] = 1 1669 # data[''] = 1 1670 1671 # data.save() 1672 1673 # must be done very late or else the property access 1674 # will refresh the display such that later field 1675 # access will return empty values 1676 # self.data = data 1677 return False 1678 return True
1679 #----------------------------------------------------------------
1680 - def _save_as_update(self):
1681 1682 self.data['gender'] = self._PRW_gender.GetData() 1683 1684 if self._PRW_dob.GetValue().strip() == u'': 1685 self.data['dob'] = None 1686 else: 1687 self.data['dob'] = self._PRW_dob.GetData().get_pydt() 1688 1689 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 1690 self.data['deceased'] = self._DP_dod.GetValue(as_pydt = True) 1691 1692 self.data.save() 1693 return True
1694 #----------------------------------------------------------------
1695 - def _refresh_as_new(self):
1696 pass
1697 #----------------------------------------------------------------
1698 - def _refresh_from_existing(self):
1699 1700 self._LBL_info.SetLabel(u'ID: #%s' % ( 1701 self.data.ID 1702 # FIXME: add 'deleted' status 1703 )) 1704 self._PRW_dob.SetText ( 1705 value = self.data.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()), 1706 data = self.data['dob'] 1707 ) 1708 self._DP_dod.SetValue(self.data['deceased']) 1709 self._PRW_gender.SetData(self.data['gender']) 1710 #self._PRW_ethnicity.SetValue() 1711 self._PRW_title.SetText(gmTools.coalesce(self.data['title'], u''))
1712 #----------------------------------------------------------------
1714 pass
1715 #---------------------------------------------------------------- 1716 1717 #------------------------------------------------------------ 1718 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl 1719
1720 -class cNameGenderDOBEditAreaPnl(wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl):
1721 """An edit area for editing/creating name/gender/dob. 1722 1723 Does NOT act on/listen to the current patient. 1724 """
1725 - def __init__(self, *args, **kwargs):
1726 1727 self.__name = kwargs['name'] 1728 del kwargs['name'] 1729 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity']) 1730 1731 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs) 1732 1733 self.__register_interests() 1734 self.refresh()
1735 #-------------------------------------------------------- 1736 # external API 1737 #--------------------------------------------------------
1738 - def refresh(self):
1739 if self.__name is None: 1740 return 1741 1742 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u'')) 1743 self._PRW_firstname.SetText(self.__name['firstnames']) 1744 self._PRW_lastname.SetText(self.__name['lastnames']) 1745 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u'')) 1746 self._PRW_dob.SetText ( 1747 value = self.__identity.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()), 1748 data = self.__identity['dob'] 1749 ) 1750 self._PRW_gender.SetData(self.__name['gender']) 1751 self._CHBOX_active.SetValue(self.__name['active_name']) 1752 self._DP_dod.SetValue(self.__identity['deceased']) 1753 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
1754 # FIXME: clear fields 1755 # else: 1756 # pass 1757 #--------------------------------------------------------
1758 - def save(self):
1759 1760 if not self.__valid_for_save(): 1761 return False 1762 1763 self.__identity['gender'] = self._PRW_gender.GetData() 1764 if self._PRW_dob.GetValue().strip() == u'': 1765 self.__identity['dob'] = None 1766 else: 1767 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt() 1768 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 1769 self.__identity['deceased'] = self._DP_dod.GetValue(as_pydt = True) 1770 self.__identity.save_payload() 1771 1772 active = self._CHBOX_active.GetValue() 1773 first = self._PRW_firstname.GetValue().strip() 1774 last = self._PRW_lastname.GetValue().strip() 1775 old_nick = self.__name['preferred'] 1776 1777 # is it a new name ? 1778 old_name = self.__name['firstnames'] + self.__name['lastnames'] 1779 if (first + last) != old_name: 1780 self.__name = self.__identity.add_name(first, last, active) 1781 1782 self.__name['active_name'] = active 1783 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'') 1784 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1785 1786 self.__name.save_payload() 1787 1788 return True
1789 #-------------------------------------------------------- 1790 # event handling 1791 #--------------------------------------------------------
1792 - def __register_interests(self):
1793 self._PRW_firstname.add_callback_on_lose_focus(self._on_name_set)
1794 #--------------------------------------------------------
1795 - def _on_name_set(self):
1796 """Set the gender according to entered firstname. 1797 1798 Matches are fetched from existing records in backend. 1799 """ 1800 firstname = self._PRW_firstname.GetValue().strip() 1801 if firstname == u'': 1802 return True 1803 rows, idx = gmPG2.run_ro_queries(queries = [{ 1804 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 1805 'args': [firstname] 1806 }]) 1807 if len(rows) == 0: 1808 return True 1809 wx.CallAfter(self._PRW_gender.SetData, rows[0][0]) 1810 return True
1811 #-------------------------------------------------------- 1812 # internal helpers 1813 #--------------------------------------------------------
1814 - def __valid_for_save(self):
1815 1816 has_error = False 1817 1818 if self._PRW_gender.GetData() is None: 1819 self._PRW_gender.SetBackgroundColour('pink') 1820 self._PRW_gender.Refresh() 1821 self._PRW_gender.SetFocus() 1822 has_error = True 1823 else: 1824 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1825 self._PRW_gender.Refresh() 1826 1827 if not self._PRW_dob.is_valid_timestamp(): 1828 val = self._PRW_dob.GetValue().strip() 1829 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 1830 self._PRW_dob.SetBackgroundColour('pink') 1831 self._PRW_dob.Refresh() 1832 self._PRW_dob.SetFocus() 1833 has_error = True 1834 else: 1835 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1836 self._PRW_dob.Refresh() 1837 1838 if not self._DP_dod.is_valid_timestamp(): 1839 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 1840 self._DP_dod.SetBackgroundColour('pink') 1841 self._DP_dod.Refresh() 1842 self._DP_dod.SetFocus() 1843 has_error = True 1844 else: 1845 self._DP_dod.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1846 self._DP_dod.Refresh() 1847 1848 if self._PRW_lastname.GetValue().strip() == u'': 1849 self._PRW_lastname.SetBackgroundColour('pink') 1850 self._PRW_lastname.Refresh() 1851 self._PRW_lastname.SetFocus() 1852 has_error = True 1853 else: 1854 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1855 self._PRW_lastname.Refresh() 1856 1857 if self._PRW_firstname.GetValue().strip() == u'': 1858 self._PRW_firstname.SetBackgroundColour('pink') 1859 self._PRW_firstname.Refresh() 1860 self._PRW_firstname.SetFocus() 1861 has_error = True 1862 else: 1863 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1864 self._PRW_firstname.Refresh() 1865 1866 return (has_error is False)
1867 #------------------------------------------------------------ 1868 # list manager 1869 #------------------------------------------------------------
1870 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
1871 """A list for managing a person's names. 1872 1873 Does NOT act on/listen to the current patient. 1874 """
1875 - def __init__(self, *args, **kwargs):
1876 1877 try: 1878 self.__identity = kwargs['identity'] 1879 del kwargs['identity'] 1880 except KeyError: 1881 self.__identity = None 1882 1883 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1884 1885 self.new_callback = self._add_name 1886 self.edit_callback = self._edit_name 1887 self.delete_callback = self._del_name 1888 self.refresh_callback = self.refresh 1889 1890 self.__init_ui() 1891 self.refresh()
1892 #-------------------------------------------------------- 1893 # external API 1894 #--------------------------------------------------------
1895 - def refresh(self, *args, **kwargs):
1896 if self.__identity is None: 1897 self._LCTRL_items.set_string_items() 1898 return 1899 1900 names = self.__identity.get_names() 1901 self._LCTRL_items.set_string_items ( 1902 items = [ [ 1903 gmTools.bool2str(n['active_name'], 'X', ''), 1904 n['lastnames'], 1905 n['firstnames'], 1906 gmTools.coalesce(n['preferred'], u''), 1907 gmTools.coalesce(n['comment'], u'') 1908 ] for n in names ] 1909 ) 1910 self._LCTRL_items.set_column_widths() 1911 self._LCTRL_items.set_data(data = names)
1912 #-------------------------------------------------------- 1913 # internal helpers 1914 #--------------------------------------------------------
1915 - def __init_ui(self):
1916 self._LCTRL_items.set_columns(columns = [ 1917 _('Active'), 1918 _('Lastname'), 1919 _('Firstname(s)'), 1920 _('Preferred Name'), 1921 _('Comment') 1922 ])
1923 #--------------------------------------------------------
1924 - def _add_name(self):
1925 ea = cNameGenderDOBEditAreaPnl(self, -1, name = self.__identity.get_active_name()) 1926 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1927 dlg.SetTitle(_('Adding new name')) 1928 if dlg.ShowModal() == wx.ID_OK: 1929 dlg.Destroy() 1930 return True 1931 dlg.Destroy() 1932 return False
1933 #--------------------------------------------------------
1934 - def _edit_name(self, name):
1935 ea = cNameGenderDOBEditAreaPnl(self, -1, name = name) 1936 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1937 dlg.SetTitle(_('Editing name')) 1938 if dlg.ShowModal() == wx.ID_OK: 1939 dlg.Destroy() 1940 return True 1941 dlg.Destroy() 1942 return False
1943 #--------------------------------------------------------
1944 - def _del_name(self, name):
1945 1946 if len(self.__identity.get_names()) == 1: 1947 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True) 1948 return False 1949 1950 go_ahead = gmGuiHelpers.gm_show_question ( 1951 _( 'It is often advisable to keep old names around and\n' 1952 'just create a new "currently active" name.\n' 1953 '\n' 1954 'This allows finding the patient by both the old\n' 1955 'and the new name (think before/after marriage).\n' 1956 '\n' 1957 'Do you still want to really delete\n' 1958 "this name from the patient ?" 1959 ), 1960 _('Deleting name') 1961 ) 1962 if not go_ahead: 1963 return False 1964 1965 self.__identity.delete_name(name = name) 1966 return True
1967 #-------------------------------------------------------- 1968 # properties 1969 #--------------------------------------------------------
1970 - def _get_identity(self):
1971 return self.__identity
1972
1973 - def _set_identity(self, identity):
1974 self.__identity = identity 1975 self.refresh()
1976 1977 identity = property(_get_identity, _set_identity)
1978 #------------------------------------------------------------
1979 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1980 """A list for managing a person's external IDs. 1981 1982 Does NOT act on/listen to the current patient. 1983 """
1984 - def __init__(self, *args, **kwargs):
1985 1986 try: 1987 self.__identity = kwargs['identity'] 1988 del kwargs['identity'] 1989 except KeyError: 1990 self.__identity = None 1991 1992 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1993 1994 self.new_callback = self._add_id 1995 self.edit_callback = self._edit_id 1996 self.delete_callback = self._del_id 1997 self.refresh_callback = self.refresh 1998 1999 self.__init_ui() 2000 self.refresh()
2001 #-------------------------------------------------------- 2002 # external API 2003 #--------------------------------------------------------
2004 - def refresh(self, *args, **kwargs):
2005 if self.__identity is None: 2006 self._LCTRL_items.set_string_items() 2007 return 2008 2009 ids = self.__identity.get_external_ids() 2010 self._LCTRL_items.set_string_items ( 2011 items = [ [ 2012 i['name'], 2013 i['value'], 2014 gmTools.coalesce(i['issuer'], u''), 2015 i['context'], 2016 gmTools.coalesce(i['comment'], u'') 2017 ] for i in ids 2018 ] 2019 ) 2020 self._LCTRL_items.set_column_widths() 2021 self._LCTRL_items.set_data(data = ids)
2022 #-------------------------------------------------------- 2023 # internal helpers 2024 #--------------------------------------------------------
2025 - def __init_ui(self):
2026 self._LCTRL_items.set_columns(columns = [ 2027 _('ID type'), 2028 _('Value'), 2029 _('Issuer'), 2030 _('Context'), 2031 _('Comment') 2032 ])
2033 #--------------------------------------------------------
2034 - def _add_id(self):
2035 ea = cExternalIDEditAreaPnl(self, -1) 2036 ea.identity = self.__identity 2037 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 2038 dlg.SetTitle(_('Adding new external ID')) 2039 if dlg.ShowModal() == wx.ID_OK: 2040 dlg.Destroy() 2041 return True 2042 dlg.Destroy() 2043 return False
2044 #--------------------------------------------------------
2045 - def _edit_id(self, ext_id):
2046 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id) 2047 ea.identity = self.__identity 2048 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 2049 dlg.SetTitle(_('Editing external ID')) 2050 if dlg.ShowModal() == wx.ID_OK: 2051 dlg.Destroy() 2052 return True 2053 dlg.Destroy() 2054 return False
2055 #--------------------------------------------------------
2056 - def _del_id(self, ext_id):
2057 go_ahead = gmGuiHelpers.gm_show_question ( 2058 _( 'Do you really want to delete this\n' 2059 'external ID from the patient ?'), 2060 _('Deleting external ID') 2061 ) 2062 if not go_ahead: 2063 return False 2064 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id']) 2065 return True
2066 #-------------------------------------------------------- 2067 # properties 2068 #--------------------------------------------------------
2069 - def _get_identity(self):
2070 return self.__identity
2071
2072 - def _set_identity(self, identity):
2073 self.__identity = identity 2074 self.refresh()
2075 2076 identity = property(_get_identity, _set_identity)
2077 #------------------------------------------------------------ 2078 # integrated panels 2079 #------------------------------------------------------------
2080 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
2081 """A panel for editing identity data for a person. 2082 2083 - provides access to: 2084 - name 2085 - external IDs 2086 2087 Does NOT act on/listen to the current patient. 2088 """
2089 - def __init__(self, *args, **kwargs):
2090 2091 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs) 2092 2093 self.__identity = None 2094 self.refresh()
2095 #-------------------------------------------------------- 2096 # external API 2097 #--------------------------------------------------------
2098 - def refresh(self):
2099 self._PNL_names.identity = self.__identity 2100 self._PNL_ids.identity = self.__identity 2101 # this is an Edit Area: 2102 self._PNL_identity.mode = 'new' 2103 self._PNL_identity.data = self.__identity 2104 if self.__identity is not None: 2105 self._PNL_identity.mode = 'edit'
2106 #-------------------------------------------------------- 2107 # properties 2108 #--------------------------------------------------------
2109 - def _get_identity(self):
2110 return self.__identity
2111
2112 - def _set_identity(self, identity):
2113 self.__identity = identity 2114 self.refresh()
2115 2116 identity = property(_get_identity, _set_identity) 2117 #-------------------------------------------------------- 2118 # event handlers 2119 #--------------------------------------------------------
2121 if not self._PNL_identity.save(): 2122 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save identity. Incomplete information'), beep = True)
2123 #--------------------------------------------------------
2124 - def _on_reload_identity_button_pressed(self, event):
2125 self._PNL_identity.refresh()
2126 #============================================================ 2127 # new-patient widgets 2128 #============================================================
2129 -def create_new_person(parent=None, activate=False):
2130 2131 dbcfg = gmCfg.cCfgSQL() 2132 2133 def_region = dbcfg.get2 ( 2134 option = u'person.create.default_region', 2135 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2136 bias = u'user' 2137 ) 2138 2139 if def_region is None: 2140 def_country = dbcfg.get2 ( 2141 option = u'person.create.default_country', 2142 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2143 bias = u'user' 2144 ) 2145 else: 2146 countries = gmDemographicRecord.get_country_for_region(region = def_region) 2147 if len(countries) == 1: 2148 def_country = countries[0]['l10n_country'] 2149 2150 if parent is None: 2151 parent = wx.GetApp().GetTopWindow() 2152 2153 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region) 2154 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 2155 dlg.SetTitle(_('Adding new person')) 2156 ea._PRW_lastname.SetFocus() 2157 result = dlg.ShowModal() 2158 pat = ea.data 2159 dlg.Destroy() 2160 2161 if result != wx.ID_OK: 2162 return False 2163 2164 if activate: 2165 from Gnumed.wxpython import gmPatSearchWidgets 2166 gmPatSearchWidgets.set_active_patient(patient = pat) 2167 2168 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 2169 2170 return True
2171 #============================================================ 2172 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 2173
2174 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
2175
2176 - def __init__(self, *args, **kwargs):
2177 2178 try: 2179 self.default_region = kwargs['region'] 2180 del kwargs['region'] 2181 except KeyError: 2182 self.default_region = None 2183 2184 try: 2185 self.default_country = kwargs['country'] 2186 del kwargs['country'] 2187 except KeyError: 2188 self.default_country = None 2189 2190 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 2191 gmEditArea.cGenericEditAreaMixin.__init__(self) 2192 2193 self.mode = 'new' 2194 self.data = None 2195 self._address = None 2196 2197 self.__init_ui() 2198 self.__register_interests()
2199 #---------------------------------------------------------------- 2200 # internal helpers 2201 #----------------------------------------------------------------
2202 - def __init_ui(self):
2203 self._PRW_lastname.final_regex = '.+' 2204 self._PRW_firstnames.final_regex = '.+' 2205 self._PRW_address_searcher.selection_only = False 2206 low = wx.DateTimeFromDMY(1,0,1900) 2207 hi = wx.DateTime() 2208 self._DP_dob.SetRange(low, hi.SetToCurrent()) 2209 # only if we would support None on selection_only's 2210 #self._PRW_external_id_type.selection_only = True 2211 2212 if self.default_country is not None: 2213 self._PRW_country.SetText(value = self.default_country) 2214 2215 if self.default_region is not None: 2216 self._PRW_region.SetText(value = self.default_region)
2217 #----------------------------------------------------------------
2218 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
2219 2220 adr = self._PRW_address_searcher.get_address() 2221 if adr is None: 2222 return True 2223 2224 if ctrl.GetValue().strip() != adr[field]: 2225 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None) 2226 return True 2227 2228 return False
2229 #----------------------------------------------------------------
2231 adr = self._PRW_address_searcher.get_address() 2232 if adr is None: 2233 return True 2234 2235 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 2236 2237 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 2238 self._PRW_street.set_context(context = u'zip', val = adr['postcode']) 2239 2240 self._TCTRL_number.SetValue(adr['number']) 2241 2242 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 2243 self._PRW_urb.set_context(context = u'zip', val = adr['postcode']) 2244 2245 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state']) 2246 self._PRW_region.set_context(context = u'zip', val = adr['postcode']) 2247 2248 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 2249 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2250 #----------------------------------------------------------------
2251 - def __identity_valid_for_save(self):
2252 error = False 2253 2254 # name fields 2255 if self._PRW_lastname.GetValue().strip() == u'': 2256 error = True 2257 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2258 self._PRW_lastname.display_as_valid(False) 2259 else: 2260 self._PRW_lastname.display_as_valid(True) 2261 2262 if self._PRW_firstnames.GetValue().strip() == '': 2263 error = True 2264 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2265 self._PRW_firstnames.display_as_valid(False) 2266 else: 2267 self._PRW_firstnames.display_as_valid(True) 2268 2269 # gender 2270 if self._PRW_gender.GetData() is None: 2271 error = True 2272 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2273 self._PRW_gender.display_as_valid(False) 2274 else: 2275 self._PRW_gender.display_as_valid(True) 2276 2277 # dob validation 2278 if not self._DP_dob.is_valid_timestamp(): 2279 2280 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?')) 2281 2282 do_it_anyway = gmGuiHelpers.gm_show_question ( 2283 _( 2284 'Are you sure you want to register this person\n' 2285 'without a valid date of birth ?\n' 2286 '\n' 2287 'This can be useful for temporary staff members\n' 2288 'but will provoke nag screens if this person\n' 2289 'becomes a patient.\n' 2290 '\n' 2291 'Note that the date of birth cannot technically\n' 2292 'be before 1900, either :-(\n' 2293 ), 2294 _('Registering new person') 2295 ) 2296 2297 if not do_it_anyway: 2298 error = True 2299 2300 if self._DP_dob.GetValue() is None: 2301 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2302 elif self._DP_dob.GetValue().GetYear() < 1900: 2303 error = True 2304 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True) 2305 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2306 self._DP_dob.SetFocus() 2307 else: 2308 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2309 self._DP_dob.Refresh() 2310 2311 # TOB validation if non-empty 2312 # if self._TCTRL_tob.GetValue().strip() != u'': 2313 2314 return (not error)
2315 #----------------------------------------------------------------
2316 - def __address_valid_for_save(self, empty_address_is_valid=False):
2317 2318 # existing address ? if so set other fields 2319 if self._PRW_address_searcher.GetData() is not None: 2320 wx.CallAfter(self.__set_fields_from_address_searcher) 2321 return True 2322 2323 # must either all contain something or none of them 2324 fields_to_fill = ( 2325 self._TCTRL_number, 2326 self._PRW_zip, 2327 self._PRW_street, 2328 self._PRW_urb, 2329 self._PRW_region, 2330 self._PRW_country 2331 ) 2332 no_of_filled_fields = 0 2333 2334 for field in fields_to_fill: 2335 if field.GetValue().strip() != u'': 2336 no_of_filled_fields += 1 2337 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2338 field.Refresh() 2339 2340 # empty address ? 2341 if no_of_filled_fields == 0: 2342 if empty_address_is_valid: 2343 return True 2344 else: 2345 return None 2346 2347 # incompletely filled address ? 2348 if no_of_filled_fields != len(fields_to_fill): 2349 for field in fields_to_fill: 2350 if field.GetValue().strip() == u'': 2351 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2352 field.SetFocus() 2353 field.Refresh() 2354 msg = _('To properly create an address, all the related fields must be filled in.') 2355 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2356 return False 2357 2358 # fields which must contain a selected item 2359 # FIXME: they must also contain an *acceptable combination* which 2360 # FIXME: can only be tested against the database itself ... 2361 strict_fields = ( 2362 self._PRW_region, 2363 self._PRW_country 2364 ) 2365 error = False 2366 for field in strict_fields: 2367 if field.GetData() is None: 2368 error = True 2369 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2370 field.SetFocus() 2371 else: 2372 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2373 field.Refresh() 2374 2375 if error: 2376 msg = _('This field must contain an item selected from the dropdown list.') 2377 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2378 return False 2379 2380 return True
2381 #----------------------------------------------------------------
2382 - def __register_interests(self):
2383 2384 # identity 2385 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 2386 2387 # address 2388 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 2389 2390 # invalidate address searcher when any field edited 2391 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 2392 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher) 2393 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 2394 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 2395 2396 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 2397 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
2398 #---------------------------------------------------------------- 2399 # event handlers 2400 #----------------------------------------------------------------
2401 - def _on_leaving_firstname(self):
2402 """Set the gender according to entered firstname. 2403 2404 Matches are fetched from existing records in backend. 2405 """ 2406 # only set if not already set so as to not 2407 # overwrite a change by the user 2408 if self._PRW_gender.GetData() is not None: 2409 return True 2410 2411 firstname = self._PRW_firstnames.GetValue().strip() 2412 if firstname == u'': 2413 return True 2414 2415 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 2416 if gender is None: 2417 return True 2418 2419 wx.CallAfter(self._PRW_gender.SetData, gender) 2420 return True
2421 #----------------------------------------------------------------
2422 - def _on_leaving_zip(self):
2423 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 2424 2425 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'') 2426 self._PRW_street.set_context(context = u'zip', val = zip_code) 2427 self._PRW_urb.set_context(context = u'zip', val = zip_code) 2428 self._PRW_region.set_context(context = u'zip', val = zip_code) 2429 self._PRW_country.set_context(context = u'zip', val = zip_code) 2430 2431 return True
2432 #----------------------------------------------------------------
2433 - def _on_leaving_country(self):
2434 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 2435 2436 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'') 2437 self._PRW_region.set_context(context = u'country', val = country) 2438 2439 return True
2440 #----------------------------------------------------------------
2441 - def _invalidate_address_searcher(self, *args, **kwargs):
2442 mapping = [ 2443 (self._PRW_street, 'street'), 2444 (self._TCTRL_number, 'number'), 2445 (self._PRW_urb, 'urb'), 2446 (self._PRW_region, 'l10n_state') 2447 ] 2448 2449 # loop through fields and invalidate address searcher if different 2450 for ctrl, field in mapping: 2451 if self.__perhaps_invalidate_address_searcher(ctrl, field): 2452 return True 2453 2454 return True
2455 #----------------------------------------------------------------
2457 adr = self._PRW_address_searcher.get_address() 2458 if adr is None: 2459 return True 2460 2461 wx.CallAfter(self.__set_fields_from_address_searcher) 2462 return True
2463 #---------------------------------------------------------------- 2464 # generic Edit Area mixin API 2465 #----------------------------------------------------------------
2466 - def _valid_for_save(self):
2467 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2468 #----------------------------------------------------------------
2469 - def _save_as_new(self):
2470 2471 # identity 2472 new_identity = gmPerson.create_identity ( 2473 gender = self._PRW_gender.GetData(), 2474 dob = self._DP_dob.get_pydt(), 2475 lastnames = self._PRW_lastname.GetValue().strip(), 2476 firstnames = self._PRW_firstnames.GetValue().strip() 2477 ) 2478 _log.debug('identity created: %s' % new_identity) 2479 2480 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 2481 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u'')) 2482 #TOB 2483 new_identity.save() 2484 2485 name = new_identity.get_active_name() 2486 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 2487 name.save() 2488 2489 # address 2490 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 2491 if is_valid is True: 2492 # because we currently only check for non-emptiness 2493 # we must still deal with database errors 2494 try: 2495 new_identity.link_address ( 2496 number = self._TCTRL_number.GetValue().strip(), 2497 street = self._PRW_street.GetValue().strip(), 2498 postcode = self._PRW_zip.GetValue().strip(), 2499 urb = self._PRW_urb.GetValue().strip(), 2500 state = self._PRW_region.GetData(), 2501 country = self._PRW_country.GetData() 2502 ) 2503 except psycopg2.InternalError: 2504 #except StandardError: 2505 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 2506 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 2507 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 2508 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 2509 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip()) 2510 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 2511 _log.exception('cannot link address') 2512 gmGuiHelpers.gm_show_error ( 2513 aTitle = _('Saving address'), 2514 aMessage = _( 2515 'Cannot save this address.\n' 2516 '\n' 2517 'You will have to add it via the Demographics plugin.\n' 2518 ) 2519 ) 2520 elif is_valid is False: 2521 gmGuiHelpers.gm_show_error ( 2522 aTitle = _('Saving address'), 2523 aMessage = _( 2524 'Address not saved.\n' 2525 '\n' 2526 'You will have to add it via the Demographics plugin.\n' 2527 ) 2528 ) 2529 # else it is None which means empty address which we ignore 2530 2531 # phone 2532 new_identity.link_comm_channel ( 2533 comm_medium = u'homephone', 2534 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''), 2535 is_confidential = False 2536 ) 2537 2538 # external ID 2539 pk_type = self._PRW_external_id_type.GetData() 2540 id_value = self._TCTRL_external_id_value.GetValue().strip() 2541 if (pk_type is not None) and (id_value != u''): 2542 new_identity.add_external_id(value = id_value, pk_type = pk_type) 2543 2544 # occupation 2545 new_identity.link_occupation ( 2546 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'') 2547 ) 2548 2549 self.data = new_identity 2550 return True
2551 #----------------------------------------------------------------
2552 - def _save_as_update(self):
2553 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2554 #----------------------------------------------------------------
2555 - def _refresh_as_new(self):
2556 # FIXME: button "empty out" 2557 return
2558 #----------------------------------------------------------------
2559 - def _refresh_from_existing(self):
2560 return # there is no forward button so nothing to do here
2561 #----------------------------------------------------------------
2563 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2564 #============================================================ 2565 # new-patient wizard classes 2566 #============================================================
2567 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2568 """ 2569 Wizard page for entering patient's basic demographic information 2570 """ 2571 2572 form_fields = ( 2573 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation', 2574 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment' 2575 ) 2576
2577 - def __init__(self, parent, title):
2578 """ 2579 Creates a new instance of BasicPatDetailsPage 2580 @param parent - The parent widget 2581 @type parent - A wx.Window instance 2582 @param tile - The title of the page 2583 @type title - A StringType instance 2584 """ 2585 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson')) 2586 self.__title = title 2587 self.__do_layout() 2588 self.__register_interests()
2589 #--------------------------------------------------------
2590 - def __do_layout(self):
2591 PNL_form = wx.Panel(self, -1) 2592 2593 # last name 2594 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name')) 2595 STT_lastname.SetForegroundColour('red') 2596 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1) 2597 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)')) 2598 2599 # first name 2600 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)')) 2601 STT_firstname.SetForegroundColour('red') 2602 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1) 2603 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name')) 2604 2605 # nickname 2606 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name')) 2607 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1) 2608 2609 # DOB 2610 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth')) 2611 STT_dob.SetForegroundColour('red') 2612 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1) 2613 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one")) 2614 2615 # gender 2616 STT_gender = wx.StaticText(PNL_form, -1, _('Gender')) 2617 STT_gender.SetForegroundColour('red') 2618 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1) 2619 self.PRW_gender.SetToolTipString(_("Required: gender of patient")) 2620 2621 # title 2622 STT_title = wx.StaticText(PNL_form, -1, _('Title')) 2623 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1) 2624 2625 # zip code 2626 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code')) 2627 STT_zip_code.SetForegroundColour('orange') 2628 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1) 2629 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code")) 2630 2631 # street 2632 STT_street = wx.StaticText(PNL_form, -1, _('Street')) 2633 STT_street.SetForegroundColour('orange') 2634 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1) 2635 self.PRW_street.SetToolTipString(_("primary/home address: name of street")) 2636 2637 # address number 2638 STT_address_number = wx.StaticText(PNL_form, -1, _('Number')) 2639 STT_address_number.SetForegroundColour('orange') 2640 self.TTC_address_number = wx.TextCtrl(PNL_form, -1) 2641 self.TTC_address_number.SetToolTipString(_("primary/home address: address number")) 2642 2643 # town 2644 STT_town = wx.StaticText(PNL_form, -1, _('Place')) 2645 STT_town.SetForegroundColour('orange') 2646 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1) 2647 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/...")) 2648 2649 # state 2650 STT_state = wx.StaticText(PNL_form, -1, _('Region')) 2651 STT_state.SetForegroundColour('orange') 2652 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1) 2653 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/...")) 2654 2655 # country 2656 STT_country = wx.StaticText(PNL_form, -1, _('Country')) 2657 STT_country.SetForegroundColour('orange') 2658 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1) 2659 self.PRW_country.SetToolTipString(_("primary/home address: country")) 2660 2661 # phone 2662 STT_phone = wx.StaticText(PNL_form, -1, _('Phone')) 2663 self.TTC_phone = wx.TextCtrl(PNL_form, -1) 2664 self.TTC_phone.SetToolTipString(_("phone number at home")) 2665 2666 # occupation 2667 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 2668 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 2669 2670 # comment 2671 STT_comment = wx.StaticText(PNL_form, -1, _('Comment')) 2672 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1) 2673 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.')) 2674 2675 # form main validator 2676 self.form_DTD = cFormDTD(fields = self.__class__.form_fields) 2677 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD)) 2678 2679 # layout input widgets 2680 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4) 2681 SZR_input.AddGrowableCol(1) 2682 SZR_input.Add(STT_lastname, 0, wx.SHAPED) 2683 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND) 2684 SZR_input.Add(STT_firstname, 0, wx.SHAPED) 2685 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND) 2686 SZR_input.Add(STT_nick, 0, wx.SHAPED) 2687 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND) 2688 SZR_input.Add(STT_dob, 0, wx.SHAPED) 2689 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND) 2690 SZR_input.Add(STT_gender, 0, wx.SHAPED) 2691 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND) 2692 SZR_input.Add(STT_title, 0, wx.SHAPED) 2693 SZR_input.Add(self.PRW_title, 1, wx.EXPAND) 2694 SZR_input.Add(STT_zip_code, 0, wx.SHAPED) 2695 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND) 2696 SZR_input.Add(STT_street, 0, wx.SHAPED) 2697 SZR_input.Add(self.PRW_street, 1, wx.EXPAND) 2698 SZR_input.Add(STT_address_number, 0, wx.SHAPED) 2699 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND) 2700 SZR_input.Add(STT_town, 0, wx.SHAPED) 2701 SZR_input.Add(self.PRW_town, 1, wx.EXPAND) 2702 SZR_input.Add(STT_state, 0, wx.SHAPED) 2703 SZR_input.Add(self.PRW_state, 1, wx.EXPAND) 2704 SZR_input.Add(STT_country, 0, wx.SHAPED) 2705 SZR_input.Add(self.PRW_country, 1, wx.EXPAND) 2706 SZR_input.Add(STT_phone, 0, wx.SHAPED) 2707 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND) 2708 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 2709 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 2710 SZR_input.Add(STT_comment, 0, wx.SHAPED) 2711 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND) 2712 2713 PNL_form.SetSizerAndFit(SZR_input) 2714 2715 # layout page 2716 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title) 2717 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2718 #-------------------------------------------------------- 2719 # event handling 2720 #--------------------------------------------------------
2721 - def __register_interests(self):
2722 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set) 2723 self.PRW_country.add_callback_on_selection(self.on_country_selected) 2724 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
2725 #--------------------------------------------------------
2726 - def on_country_selected(self, data):
2727 """Set the states according to entered country.""" 2728 self.PRW_state.set_context(context=u'country', val=data) 2729 return True
2730 #--------------------------------------------------------
2731 - def on_name_set(self):
2732 """Set the gender according to entered firstname. 2733 2734 Matches are fetched from existing records in backend. 2735 """ 2736 firstname = self.PRW_firstname.GetValue().strip() 2737 rows, idx = gmPG2.run_ro_queries(queries = [{ 2738 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 2739 'args': [firstname] 2740 }]) 2741 if len(rows) == 0: 2742 return True 2743 wx.CallAfter(self.PRW_gender.SetData, rows[0][0]) 2744 return True
2745 #--------------------------------------------------------
2746 - def on_zip_set(self):
2747 """Set the street, town, state and country according to entered zip code.""" 2748 zip_code = self.PRW_zip_code.GetValue().strip() 2749 self.PRW_street.set_context(context=u'zip', val=zip_code) 2750 self.PRW_town.set_context(context=u'zip', val=zip_code) 2751 self.PRW_state.set_context(context=u'zip', val=zip_code) 2752 self.PRW_country.set_context(context=u'zip', val=zip_code) 2753 return True
2754 #============================================================
2755 -class cNewPatientWizard(wx.wizard.Wizard):
2756 """ 2757 Wizard to create a new patient. 2758 2759 TODO: 2760 - write pages for different "themes" of patient creation 2761 - make it configurable which pages are loaded 2762 - make available sets of pages that apply to a country 2763 - make loading of some pages depend upon values in earlier pages, eg 2764 when the patient is female and older than 13 include a page about 2765 "female" data (number of kids etc) 2766 2767 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable() 2768 """ 2769 #--------------------------------------------------------
2770 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2771 """ 2772 Creates a new instance of NewPatientWizard 2773 @param parent - The parent widget 2774 @type parent - A wx.Window instance 2775 """ 2776 id_wiz = wx.NewId() 2777 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap() 2778 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY) 2779 self.__subtitle = subtitle 2780 self.__do_layout()
2781 #--------------------------------------------------------
2782 - def RunWizard(self, activate=False):
2783 """Create new patient. 2784 2785 activate, too, if told to do so (and patient successfully created) 2786 """ 2787 while True: 2788 2789 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details): 2790 return False 2791 2792 try: 2793 # retrieve DTD and create patient 2794 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD) 2795 except: 2796 _log.exception('cannot add new patient - missing identity fields') 2797 gmGuiHelpers.gm_show_error ( 2798 _('Cannot create new patient.\n' 2799 'Missing parts of the identity.' 2800 ), 2801 _('Adding new patient') 2802 ) 2803 continue 2804 2805 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2806 2807 try: 2808 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2809 except: 2810 _log.exception('cannot finalize new patient - missing address fields') 2811 gmGuiHelpers.gm_show_error ( 2812 _('Cannot add address for the new patient.\n' 2813 'You must either enter all of the address fields or\n' 2814 'none at all. The relevant fields are marked in yellow.\n' 2815 '\n' 2816 'You will need to add the address details in the\n' 2817 'demographics module.' 2818 ), 2819 _('Adding new patient') 2820 ) 2821 break 2822 2823 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2824 2825 break 2826 2827 if activate: 2828 from Gnumed.wxpython import gmPatSearchWidgets 2829 gmPatSearchWidgets.set_active_patient(patient = ident) 2830 2831 return ident
2832 #-------------------------------------------------------- 2833 # internal helpers 2834 #--------------------------------------------------------
2835 - def __do_layout(self):
2836 """Arrange widgets. 2837 """ 2838 # Create the wizard pages 2839 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle ) 2840 self.FitToPage(self.basic_pat_details)
2841 #============================================================
2842 -class cBasicPatDetailsPageValidator(wx.PyValidator):
2843 """ 2844 This validator is used to ensure that the user has entered all 2845 the required conditional values in the page (eg., to properly 2846 create an address, all the related fields must be filled). 2847 """ 2848 #--------------------------------------------------------
2849 - def __init__(self, dtd):
2850 """ 2851 Validator initialization. 2852 @param dtd The object containing the data model. 2853 @type dtd A cFormDTD instance 2854 """ 2855 # initialize parent class 2856 wx.PyValidator.__init__(self) 2857 # validator's storage object 2858 self.form_DTD = dtd
2859 #--------------------------------------------------------
2860 - def Clone(self):
2861 """ 2862 Standard cloner. 2863 Note that every validator must implement the Clone() method. 2864 """ 2865 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
2866 #--------------------------------------------------------
2867 - def Validate(self, parent = None):
2868 """ 2869 Validate the contents of the given text control. 2870 """ 2871 _pnl_form = self.GetWindow().GetParent() 2872 2873 error = False 2874 2875 # name fields 2876 if _pnl_form.PRW_lastname.GetValue().strip() == '': 2877 error = True 2878 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2879 _pnl_form.PRW_lastname.SetBackgroundColour('pink') 2880 _pnl_form.PRW_lastname.Refresh() 2881 else: 2882 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2883 _pnl_form.PRW_lastname.Refresh() 2884 2885 if _pnl_form.PRW_firstname.GetValue().strip() == '': 2886 error = True 2887 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2888 _pnl_form.PRW_firstname.SetBackgroundColour('pink') 2889 _pnl_form.PRW_firstname.Refresh() 2890 else: 2891 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2892 _pnl_form.PRW_firstname.Refresh() 2893 2894 # gender 2895 if _pnl_form.PRW_gender.GetData() is None: 2896 error = True 2897 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2898 _pnl_form.PRW_gender.SetBackgroundColour('pink') 2899 _pnl_form.PRW_gender.Refresh() 2900 else: 2901 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2902 _pnl_form.PRW_gender.Refresh() 2903 2904 # dob validation 2905 if ( 2906 (_pnl_form.PRW_dob.GetValue().strip() == u'') 2907 or (not _pnl_form.PRW_dob.is_valid_timestamp()) 2908 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900) 2909 ): 2910 error = True 2911 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue() 2912 gmDispatcher.send(signal = 'statustext', msg = msg) 2913 _pnl_form.PRW_dob.SetBackgroundColour('pink') 2914 _pnl_form.PRW_dob.Refresh() 2915 else: 2916 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2917 _pnl_form.PRW_dob.Refresh() 2918 2919 # address 2920 is_any_field_filled = False 2921 address_fields = ( 2922 _pnl_form.TTC_address_number, 2923 _pnl_form.PRW_zip_code, 2924 _pnl_form.PRW_street, 2925 _pnl_form.PRW_town 2926 ) 2927 for field in address_fields: 2928 if field.GetValue().strip() == u'': 2929 if is_any_field_filled: 2930 error = True 2931 msg = _('To properly create an address, all the related fields must be filled in.') 2932 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2933 field.SetBackgroundColour('pink') 2934 field.SetFocus() 2935 field.Refresh() 2936 else: 2937 is_any_field_filled = True 2938 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2939 field.Refresh() 2940 2941 address_fields = ( 2942 _pnl_form.PRW_state, 2943 _pnl_form.PRW_country 2944 ) 2945 for field in address_fields: 2946 if field.GetData() is None: 2947 if is_any_field_filled: 2948 error = True 2949 msg = _('To properly create an address, all the related fields must be filled in.') 2950 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2951 field.SetBackgroundColour('pink') 2952 field.SetFocus() 2953 field.Refresh() 2954 else: 2955 is_any_field_filled = True 2956 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2957 field.Refresh() 2958 2959 return (not error)
2960 #--------------------------------------------------------
2961 - def TransferToWindow(self):
2962 """ 2963 Transfer data from validator to window. 2964 The default implementation returns False, indicating that an error 2965 occurred. We simply return True, as we don't do any data transfer. 2966 """ 2967 _pnl_form = self.GetWindow().GetParent() 2968 # fill in controls with values from self.form_DTD 2969 _pnl_form.PRW_gender.SetData(self.form_DTD['gender']) 2970 _pnl_form.PRW_dob.SetText(self.form_DTD['dob']) 2971 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames']) 2972 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames']) 2973 _pnl_form.PRW_title.SetText(self.form_DTD['title']) 2974 _pnl_form.PRW_nick.SetText(self.form_DTD['nick']) 2975 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation']) 2976 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number']) 2977 _pnl_form.PRW_street.SetText(self.form_DTD['street']) 2978 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code']) 2979 _pnl_form.PRW_town.SetText(self.form_DTD['town']) 2980 _pnl_form.PRW_state.SetData(self.form_DTD['state']) 2981 _pnl_form.PRW_country.SetData(self.form_DTD['country']) 2982 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone']) 2983 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment']) 2984 return True # Prevent wxDialog from complaining
2985 #--------------------------------------------------------
2986 - def TransferFromWindow(self):
2987 """ 2988 Transfer data from window to validator. 2989 The default implementation returns False, indicating that an error 2990 occurred. We simply return True, as we don't do any data transfer. 2991 """ 2992 # FIXME: should be called automatically 2993 if not self.GetWindow().GetParent().Validate(): 2994 return False 2995 try: 2996 _pnl_form = self.GetWindow().GetParent() 2997 # fill in self.form_DTD with values from controls 2998 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData() 2999 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData() 3000 3001 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue() 3002 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue() 3003 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue() 3004 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue() 3005 3006 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue() 3007 3008 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue() 3009 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue() 3010 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue() 3011 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue() 3012 self.form_DTD['state'] = _pnl_form.PRW_state.GetData() 3013 self.form_DTD['country'] = _pnl_form.PRW_country.GetData() 3014 3015 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue() 3016 3017 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue() 3018 except: 3019 return False 3020 return True
3021 #============================================================
3022 -class cFormDTD:
3023 """ 3024 Simple Data Transfer Dictionary class to make easy the trasfer of 3025 data between the form (view) and the business logic. 3026 3027 Maybe later consider turning this into a standard dict by 3028 {}.fromkeys([key, key, ...], default) when it becomes clear that 3029 we really don't need the added potential of a full-fledged class. 3030 """
3031 - def __init__(self, fields):
3032 """ 3033 Initialize the DTD with the supplied field names. 3034 @param fields The names of the fields. 3035 @type fields A TupleType instance. 3036 """ 3037 self.data = {} 3038 for a_field in fields: 3039 self.data[a_field] = ''
3040
3041 - def __getitem__(self, attribute):
3042 """ 3043 Retrieve the value of the given attribute (key) 3044 @param attribute The attribute (key) to retrieve its value for. 3045 @type attribute a StringType instance. 3046 """ 3047 if not self.data[attribute]: 3048 return '' 3049 return self.data[attribute]
3050
3051 - def __setitem__(self, attribute, value):
3052 """ 3053 Set the value of a given attribute (key). 3054 @param attribute The attribute (key) to set its value for. 3055 @type attribute a StringType instance. 3056 @param avaluee The value to set. 3057 @rtpe attribute a StringType instance. 3058 """ 3059 self.data[attribute] = value
3060
3061 - def __str__(self):
3062 """ 3063 Print string representation of the DTD object. 3064 """ 3065 return str(self.data)
3066 #============================================================ 3067 # patient demographics editing classes 3068 #============================================================
3069 -class cPersonDemographicsEditorNb(wx.Notebook):
3070 """Notebook displaying demographics editing pages: 3071 3072 - Identity 3073 - Contacts (addresses, phone numbers, etc) 3074 - Social Net 3075 3076 Does NOT act on/listen to the current patient. 3077 """ 3078 #--------------------------------------------------------
3079 - def __init__(self, parent, id):
3080 3081 wx.Notebook.__init__ ( 3082 self, 3083 parent = parent, 3084 id = id, 3085 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 3086 name = self.__class__.__name__ 3087 ) 3088 3089 self.__identity = None 3090 self.__do_layout() 3091 self.SetSelection(0)
3092 #-------------------------------------------------------- 3093 # public API 3094 #--------------------------------------------------------
3095 - def refresh(self):
3096 """Populate fields in pages with data from model.""" 3097 for page_idx in range(self.GetPageCount()): 3098 page = self.GetPage(page_idx) 3099 page.identity = self.__identity 3100 3101 return True
3102 #-------------------------------------------------------- 3103 # internal API 3104 #--------------------------------------------------------
3105 - def __do_layout(self):
3106 """Build patient edition notebook pages.""" 3107 # contacts page 3108 new_page = cPersonContactsManagerPnl(self, -1) 3109 new_page.identity = self.__identity 3110 self.AddPage ( 3111 page = new_page, 3112 text = _('Contacts'), 3113 select = True 3114 ) 3115 3116 # identity page 3117 new_page = cPersonIdentityManagerPnl(self, -1) 3118 new_page.identity = self.__identity 3119 self.AddPage ( 3120 page = new_page, 3121 text = _('Identity'), 3122 select = False 3123 )
3124 #-------------------------------------------------------- 3125 # properties 3126 #--------------------------------------------------------
3127 - def _get_identity(self):
3128 return self.__identity
3129
3130 - def _set_identity(self, identity):
3131 self.__identity = identity
3132 3133 identity = property(_get_identity, _set_identity)
3134 #============================================================ 3135 # FIXME: support multiple occupations 3136 # FIXME: redo with wxGlade 3137
3138 -class cPatOccupationsPanel(wx.Panel):
3139 """Page containing patient occupations edition fields. 3140 """
3141 - def __init__(self, parent, id, ident=None):
3142 """ 3143 Creates a new instance of BasicPatDetailsPage 3144 @param parent - The parent widget 3145 @type parent - A wx.Window instance 3146 @param id - The widget id 3147 @type id - An integer 3148 """ 3149 wx.Panel.__init__(self, parent, id) 3150 self.__ident = ident 3151 self.__do_layout()
3152 #--------------------------------------------------------
3153 - def __do_layout(self):
3154 PNL_form = wx.Panel(self, -1) 3155 # occupation 3156 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 3157 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 3158 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient")) 3159 # known since 3160 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 3161 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 3162 3163 # layout input widgets 3164 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 3165 SZR_input.AddGrowableCol(1) 3166 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 3167 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 3168 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 3169 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 3170 PNL_form.SetSizerAndFit(SZR_input) 3171 3172 # layout page 3173 SZR_main = wx.BoxSizer(wx.VERTICAL) 3174 SZR_main.Add(PNL_form, 1, wx.EXPAND) 3175 self.SetSizer(SZR_main)
3176 #--------------------------------------------------------
3177 - def set_identity(self, identity):
3178 return self.refresh(identity=identity)
3179 #--------------------------------------------------------
3180 - def refresh(self, identity=None):
3181 if identity is not None: 3182 self.__ident = identity 3183 jobs = self.__ident.get_occupations() 3184 if len(jobs) > 0: 3185 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 3186 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 3187 return True
3188 #--------------------------------------------------------
3189 - def save(self):
3190 if self.PRW_occupation.IsModified(): 3191 new_job = self.PRW_occupation.GetValue().strip() 3192 jobs = self.__ident.get_occupations() 3193 for job in jobs: 3194 if job['l10n_occupation'] == new_job: 3195 continue 3196 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 3197 self.__ident.link_occupation(occupation = new_job) 3198 return True
3199 #============================================================
3200 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
3201 """Patient demographics plugin for main notebook. 3202 3203 Hosts another notebook with pages for Identity, Contacts, etc. 3204 3205 Acts on/listens to the currently active patient. 3206 """ 3207 #--------------------------------------------------------
3208 - def __init__(self, parent, id):
3209 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 3210 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 3211 self.__do_layout() 3212 self.__register_interests()
3213 #-------------------------------------------------------- 3214 # public API 3215 #-------------------------------------------------------- 3216 #-------------------------------------------------------- 3217 # internal helpers 3218 #--------------------------------------------------------
3219 - def __do_layout(self):
3220 """Arrange widgets.""" 3221 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 3222 3223 szr_main = wx.BoxSizer(wx.VERTICAL) 3224 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 3225 self.SetSizerAndFit(szr_main)
3226 #-------------------------------------------------------- 3227 # event handling 3228 #--------------------------------------------------------
3229 - def __register_interests(self):
3230 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 3231 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3232 #--------------------------------------------------------
3233 - def _on_pre_patient_selection(self):
3234 self._schedule_data_reget()
3235 #--------------------------------------------------------
3236 - def _on_post_patient_selection(self):
3237 self._schedule_data_reget()
3238 #-------------------------------------------------------- 3239 # reget mixin API 3240 #--------------------------------------------------------
3241 - def _populate_with_data(self):
3242 """Populate fields in pages with data from model.""" 3243 pat = gmPerson.gmCurrentPatient() 3244 if pat.connected: 3245 self.__patient_notebook.identity = pat 3246 else: 3247 self.__patient_notebook.identity = None 3248 self.__patient_notebook.refresh() 3249 return True
3250 #============================================================
3251 -def create_identity_from_dtd(dtd=None):
3252 """ 3253 Register a new patient, given the data supplied in the 3254 Data Transfer Dictionary object. 3255 3256 @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3257 supplied data. 3258 @type basic_details_DTD A cFormDTD instance. 3259 """ 3260 new_identity = gmPerson.create_identity ( 3261 gender = dtd['gender'], 3262 dob = dtd['dob'].get_pydt(), 3263 lastnames = dtd['lastnames'], 3264 firstnames = dtd['firstnames'] 3265 ) 3266 if new_identity is None: 3267 _log.error('cannot create identity from %s' % str(dtd)) 3268 return None 3269 _log.debug('identity created: %s' % new_identity) 3270 3271 if dtd['comment'] is not None: 3272 if dtd['comment'].strip() != u'': 3273 name = new_identity.get_active_name() 3274 name['comment'] = dtd['comment'] 3275 name.save_payload() 3276 3277 return new_identity
3278 #============================================================
3279 -def update_identity_from_dtd(identity, dtd=None):
3280 """ 3281 Update patient details with data supplied by 3282 Data Transfer Dictionary object. 3283 3284 @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3285 supplied data. 3286 @type basic_details_DTD A cFormDTD instance. 3287 """ 3288 # identity 3289 if identity['gender'] != dtd['gender']: 3290 identity['gender'] = dtd['gender'] 3291 if identity['dob'] != dtd['dob'].get_pydt(): 3292 identity['dob'] = dtd['dob'].get_pydt() 3293 if len(dtd['title']) > 0 and identity['title'] != dtd['title']: 3294 identity['title'] = dtd['title'] 3295 # FIXME: error checking 3296 # FIXME: we need a trigger to update the values of the 3297 # view, identity['keys'], eg. lastnames and firstnames 3298 # are not refreshed. 3299 identity.save_payload() 3300 3301 # names 3302 # FIXME: proper handling of "active" 3303 if identity['firstnames'] != dtd['firstnames'] or identity['lastnames'] != dtd['lastnames']: 3304 new_name = identity.add_name(firstnames = dtd['firstnames'], lastnames = dtd['lastnames'], active = True) 3305 # nickname 3306 if len(dtd['nick']) > 0 and identity['preferred'] != dtd['nick']: 3307 identity.set_nickname(nickname = dtd['nick']) 3308 3309 return True
3310 #============================================================ 3354 #============================================================ 3367 #============================================================
3368 -class TestWizardPanel(wx.Panel):
3369 """ 3370 Utility class to test the new patient wizard. 3371 """ 3372 #--------------------------------------------------------
3373 - def __init__(self, parent, id):
3374 """ 3375 Create a new instance of TestPanel. 3376 @param parent The parent widget 3377 @type parent A wx.Window instance 3378 """ 3379 wx.Panel.__init__(self, parent, id) 3380 wizard = cNewPatientWizard(self) 3381 print wizard.RunWizard()
3382 #============================================================ 3383 if __name__ == "__main__": 3384 3385 #--------------------------------------------------------
3386 - def test_zipcode_prw():
3387 app = wx.PyWidgetTester(size = (200, 50)) 3388 pw = cZipcodePhraseWheel(app.frame, -1) 3389 app.frame.Show(True) 3390 app.MainLoop()
3391 #--------------------------------------------------------
3392 - def test_state_prw():
3393 app = wx.PyWidgetTester(size = (200, 50)) 3394 pw = cStateSelectionPhraseWheel(app.frame, -1) 3395 # pw.set_context(context = u'zip', val = u'04318') 3396 # pw.set_context(context = u'country', val = u'Deutschland') 3397 app.frame.Show(True) 3398 app.MainLoop()
3399 #--------------------------------------------------------
3400 - def test_urb_prw():
3401 app = wx.PyWidgetTester(size = (200, 50)) 3402 pw = cUrbPhraseWheel(app.frame, -1) 3403 app.frame.Show(True) 3404 pw.set_context(context = u'zip', val = u'04317') 3405 app.MainLoop()
3406 #--------------------------------------------------------
3407 - def test_suburb_prw():
3408 app = wx.PyWidgetTester(size = (200, 50)) 3409 pw = cSuburbPhraseWheel(app.frame, -1) 3410 app.frame.Show(True) 3411 app.MainLoop()
3412 #--------------------------------------------------------
3413 - def test_address_type_prw():
3414 app = wx.PyWidgetTester(size = (200, 50)) 3415 pw = cAddressTypePhraseWheel(app.frame, -1) 3416 app.frame.Show(True) 3417 app.MainLoop()
3418 #--------------------------------------------------------
3419 - def test_address_prw():
3420 app = wx.PyWidgetTester(size = (200, 50)) 3421 pw = cAddressPhraseWheel(app.frame, -1) 3422 app.frame.Show(True) 3423 app.MainLoop()
3424 #--------------------------------------------------------
3425 - def test_street_prw():
3426 app = wx.PyWidgetTester(size = (200, 50)) 3427 pw = cStreetPhraseWheel(app.frame, -1) 3428 # pw.set_context(context = u'zip', val = u'04318') 3429 app.frame.Show(True) 3430 app.MainLoop()
3431 #--------------------------------------------------------
3432 - def test_organizer_pnl():
3433 app = wx.PyWidgetTester(size = (600, 400)) 3434 app.SetWidget(cKOrganizerSchedulePnl) 3435 app.MainLoop()
3436 #--------------------------------------------------------
3437 - def test_person_names_pnl():
3438 app = wx.PyWidgetTester(size = (600, 400)) 3439 widget = cPersonNamesManagerPnl(app.frame, -1) 3440 widget.identity = activate_patient() 3441 app.frame.Show(True) 3442 app.MainLoop()
3443 #--------------------------------------------------------
3444 - def test_person_ids_pnl():
3445 app = wx.PyWidgetTester(size = (600, 400)) 3446 widget = cPersonIDsManagerPnl(app.frame, -1) 3447 widget.identity = activate_patient() 3448 app.frame.Show(True) 3449 app.MainLoop()
3450 #--------------------------------------------------------
3451 - def test_pat_ids_pnl():
3452 app = wx.PyWidgetTester(size = (600, 400)) 3453 widget = cPersonIdentityManagerPnl(app.frame, -1) 3454 widget.identity = activate_patient() 3455 app.frame.Show(True) 3456 app.MainLoop()
3457 #--------------------------------------------------------
3458 - def test_name_ea_pnl():
3459 app = wx.PyWidgetTester(size = (600, 400)) 3460 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name()) 3461 app.MainLoop()
3462 #--------------------------------------------------------
3463 - def test_address_ea_pnl():
3464 app = wx.PyWidgetTester(size = (600, 400)) 3465 app.SetWidget(cAddressEditAreaPnl, address = gmDemographicRecord.cAddress(aPK_obj = 1)) 3466 app.MainLoop()
3467 #--------------------------------------------------------
3468 - def test_person_adrs_pnl():
3469 app = wx.PyWidgetTester(size = (600, 400)) 3470 widget = cPersonAddressesManagerPnl(app.frame, -1) 3471 widget.identity = activate_patient() 3472 app.frame.Show(True) 3473 app.MainLoop()
3474 #--------------------------------------------------------
3475 - def test_person_comms_pnl():
3476 app = wx.PyWidgetTester(size = (600, 400)) 3477 widget = cPersonCommsManagerPnl(app.frame, -1) 3478 widget.identity = activate_patient() 3479 app.frame.Show(True) 3480 app.MainLoop()
3481 #--------------------------------------------------------
3482 - def test_pat_contacts_pnl():
3483 app = wx.PyWidgetTester(size = (600, 400)) 3484 widget = cPersonContactsManagerPnl(app.frame, -1) 3485 widget.identity = activate_patient() 3486 app.frame.Show(True) 3487 app.MainLoop()
3488 #--------------------------------------------------------
3489 - def test_cPersonDemographicsEditorNb():
3490 app = wx.PyWidgetTester(size = (600, 400)) 3491 widget = cPersonDemographicsEditorNb(app.frame, -1) 3492 widget.identity = activate_patient() 3493 widget.refresh() 3494 app.frame.Show(True) 3495 app.MainLoop()
3496 #--------------------------------------------------------
3497 - def activate_patient():
3498 patient = gmPerson.ask_for_patient() 3499 if patient is None: 3500 print "No patient. Exiting gracefully..." 3501 sys.exit(0) 3502 from Gnumed.wxpython import gmPatSearchWidgets 3503 gmPatSearchWidgets.set_active_patient(patient=patient) 3504 return patient
3505 #-------------------------------------------------------- 3506 if len(sys.argv) > 1 and sys.argv[1] == 'test': 3507 3508 gmI18N.activate_locale() 3509 gmI18N.install_domain(domain='gnumed') 3510 gmPG2.get_connection() 3511 3512 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields) 3513 3514 # app = wx.PyWidgetTester(size = (400, 300)) 3515 # app.SetWidget(cNotebookedPatEditionPanel, -1) 3516 # app.SetWidget(TestWizardPanel, -1) 3517 # app.frame.Show(True) 3518 # app.MainLoop() 3519 3520 # phrasewheels 3521 # test_zipcode_prw() 3522 # test_state_prw() 3523 # test_street_prw() 3524 # test_organizer_pnl() 3525 #test_address_type_prw() 3526 #test_suburb_prw() 3527 test_urb_prw() 3528 #test_address_prw() 3529 3530 # contacts related widgets 3531 #test_address_ea_pnl() 3532 #test_person_adrs_pnl() 3533 #test_person_comms_pnl() 3534 #test_pat_contacts_pnl() 3535 3536 # identity related widgets 3537 #test_person_names_pnl() 3538 #test_person_ids_pnl() 3539 #test_pat_ids_pnl() 3540 #test_name_ea_pnl() 3541 3542 #test_cPersonDemographicsEditorNb() 3543 3544 #============================================================ 3545