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 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1692 1693 self.data.save() 1694 return True
1695 #----------------------------------------------------------------
1696 - def _refresh_as_new(self):
1697 pass
1698 #----------------------------------------------------------------
1699 - def _refresh_from_existing(self):
1700 1701 self._LBL_info.SetLabel(u'ID: #%s' % ( 1702 self.data.ID 1703 # FIXME: add 'deleted' status 1704 )) 1705 self._PRW_dob.SetText ( 1706 value = self.data.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()), 1707 data = self.data['dob'] 1708 ) 1709 self._DP_dod.SetValue(self.data['deceased']) 1710 self._PRW_gender.SetData(self.data['gender']) 1711 #self._PRW_ethnicity.SetValue() 1712 self._PRW_title.SetText(gmTools.coalesce(self.data['title'], u'')) 1713 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
1714 #----------------------------------------------------------------
1716 pass
1717 #---------------------------------------------------------------- 1718 1719 #------------------------------------------------------------ 1720 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl 1721
1722 -class cNameGenderDOBEditAreaPnl(wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl):
1723 """An edit area for editing/creating name/gender/dob. 1724 1725 Does NOT act on/listen to the current patient. 1726 """
1727 - def __init__(self, *args, **kwargs):
1728 1729 self.__name = kwargs['name'] 1730 del kwargs['name'] 1731 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity']) 1732 1733 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs) 1734 1735 self.__register_interests() 1736 self.refresh()
1737 #-------------------------------------------------------- 1738 # external API 1739 #--------------------------------------------------------
1740 - def refresh(self):
1741 if self.__name is None: 1742 return 1743 1744 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u'')) 1745 self._PRW_firstname.SetText(self.__name['firstnames']) 1746 self._PRW_lastname.SetText(self.__name['lastnames']) 1747 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u'')) 1748 self._PRW_dob.SetText ( 1749 value = self.__identity.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()), 1750 data = self.__identity['dob'] 1751 ) 1752 self._PRW_gender.SetData(self.__name['gender']) 1753 self._CHBOX_active.SetValue(self.__name['active_name']) 1754 self._DP_dod.SetValue(self.__identity['deceased']) 1755 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
1756 # FIXME: clear fields 1757 # else: 1758 # pass 1759 #--------------------------------------------------------
1760 - def save(self):
1761 1762 if not self.__valid_for_save(): 1763 return False 1764 1765 self.__identity['gender'] = self._PRW_gender.GetData() 1766 if self._PRW_dob.GetValue().strip() == u'': 1767 self.__identity['dob'] = None 1768 else: 1769 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt() 1770 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 1771 self.__identity['deceased'] = self._DP_dod.GetValue(as_pydt = True) 1772 self.__identity.save_payload() 1773 1774 active = self._CHBOX_active.GetValue() 1775 first = self._PRW_firstname.GetValue().strip() 1776 last = self._PRW_lastname.GetValue().strip() 1777 old_nick = self.__name['preferred'] 1778 1779 # is it a new name ? 1780 old_name = self.__name['firstnames'] + self.__name['lastnames'] 1781 if (first + last) != old_name: 1782 self.__name = self.__identity.add_name(first, last, active) 1783 1784 self.__name['active_name'] = active 1785 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'') 1786 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1787 1788 self.__name.save_payload() 1789 1790 return True
1791 #-------------------------------------------------------- 1792 # event handling 1793 #--------------------------------------------------------
1794 - def __register_interests(self):
1795 self._PRW_firstname.add_callback_on_lose_focus(self._on_name_set)
1796 #--------------------------------------------------------
1797 - def _on_name_set(self):
1798 """Set the gender according to entered firstname. 1799 1800 Matches are fetched from existing records in backend. 1801 """ 1802 firstname = self._PRW_firstname.GetValue().strip() 1803 if firstname == u'': 1804 return True 1805 rows, idx = gmPG2.run_ro_queries(queries = [{ 1806 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 1807 'args': [firstname] 1808 }]) 1809 if len(rows) == 0: 1810 return True 1811 wx.CallAfter(self._PRW_gender.SetData, rows[0][0]) 1812 return True
1813 #-------------------------------------------------------- 1814 # internal helpers 1815 #--------------------------------------------------------
1816 - def __valid_for_save(self):
1817 1818 has_error = False 1819 1820 if self._PRW_gender.GetData() is None: 1821 self._PRW_gender.SetBackgroundColour('pink') 1822 self._PRW_gender.Refresh() 1823 self._PRW_gender.SetFocus() 1824 has_error = True 1825 else: 1826 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1827 self._PRW_gender.Refresh() 1828 1829 if not self._PRW_dob.is_valid_timestamp(): 1830 val = self._PRW_dob.GetValue().strip() 1831 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 1832 self._PRW_dob.SetBackgroundColour('pink') 1833 self._PRW_dob.Refresh() 1834 self._PRW_dob.SetFocus() 1835 has_error = True 1836 else: 1837 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1838 self._PRW_dob.Refresh() 1839 1840 if not self._DP_dod.is_valid_timestamp(): 1841 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 1842 self._DP_dod.SetBackgroundColour('pink') 1843 self._DP_dod.Refresh() 1844 self._DP_dod.SetFocus() 1845 has_error = True 1846 else: 1847 self._DP_dod.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1848 self._DP_dod.Refresh() 1849 1850 if self._PRW_lastname.GetValue().strip() == u'': 1851 self._PRW_lastname.SetBackgroundColour('pink') 1852 self._PRW_lastname.Refresh() 1853 self._PRW_lastname.SetFocus() 1854 has_error = True 1855 else: 1856 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1857 self._PRW_lastname.Refresh() 1858 1859 if self._PRW_firstname.GetValue().strip() == u'': 1860 self._PRW_firstname.SetBackgroundColour('pink') 1861 self._PRW_firstname.Refresh() 1862 self._PRW_firstname.SetFocus() 1863 has_error = True 1864 else: 1865 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1866 self._PRW_firstname.Refresh() 1867 1868 return (has_error is False)
1869 #------------------------------------------------------------ 1870 # list manager 1871 #------------------------------------------------------------
1872 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
1873 """A list for managing a person's names. 1874 1875 Does NOT act on/listen to the current patient. 1876 """
1877 - def __init__(self, *args, **kwargs):
1878 1879 try: 1880 self.__identity = kwargs['identity'] 1881 del kwargs['identity'] 1882 except KeyError: 1883 self.__identity = None 1884 1885 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1886 1887 self.new_callback = self._add_name 1888 self.edit_callback = self._edit_name 1889 self.delete_callback = self._del_name 1890 self.refresh_callback = self.refresh 1891 1892 self.__init_ui() 1893 self.refresh()
1894 #-------------------------------------------------------- 1895 # external API 1896 #--------------------------------------------------------
1897 - def refresh(self, *args, **kwargs):
1898 if self.__identity is None: 1899 self._LCTRL_items.set_string_items() 1900 return 1901 1902 names = self.__identity.get_names() 1903 self._LCTRL_items.set_string_items ( 1904 items = [ [ 1905 gmTools.bool2str(n['active_name'], 'X', ''), 1906 n['lastnames'], 1907 n['firstnames'], 1908 gmTools.coalesce(n['preferred'], u''), 1909 gmTools.coalesce(n['comment'], u'') 1910 ] for n in names ] 1911 ) 1912 self._LCTRL_items.set_column_widths() 1913 self._LCTRL_items.set_data(data = names)
1914 #-------------------------------------------------------- 1915 # internal helpers 1916 #--------------------------------------------------------
1917 - def __init_ui(self):
1918 self._LCTRL_items.set_columns(columns = [ 1919 _('Active'), 1920 _('Lastname'), 1921 _('Firstname(s)'), 1922 _('Preferred Name'), 1923 _('Comment') 1924 ])
1925 #--------------------------------------------------------
1926 - def _add_name(self):
1927 ea = cNameGenderDOBEditAreaPnl(self, -1, name = self.__identity.get_active_name()) 1928 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1929 dlg.SetTitle(_('Adding new name')) 1930 if dlg.ShowModal() == wx.ID_OK: 1931 dlg.Destroy() 1932 return True 1933 dlg.Destroy() 1934 return False
1935 #--------------------------------------------------------
1936 - def _edit_name(self, name):
1937 ea = cNameGenderDOBEditAreaPnl(self, -1, name = name) 1938 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 1939 dlg.SetTitle(_('Editing name')) 1940 if dlg.ShowModal() == wx.ID_OK: 1941 dlg.Destroy() 1942 return True 1943 dlg.Destroy() 1944 return False
1945 #--------------------------------------------------------
1946 - def _del_name(self, name):
1947 1948 if len(self.__identity.get_names()) == 1: 1949 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True) 1950 return False 1951 1952 go_ahead = gmGuiHelpers.gm_show_question ( 1953 _( 'It is often advisable to keep old names around and\n' 1954 'just create a new "currently active" name.\n' 1955 '\n' 1956 'This allows finding the patient by both the old\n' 1957 'and the new name (think before/after marriage).\n' 1958 '\n' 1959 'Do you still want to really delete\n' 1960 "this name from the patient ?" 1961 ), 1962 _('Deleting name') 1963 ) 1964 if not go_ahead: 1965 return False 1966 1967 self.__identity.delete_name(name = name) 1968 return True
1969 #-------------------------------------------------------- 1970 # properties 1971 #--------------------------------------------------------
1972 - def _get_identity(self):
1973 return self.__identity
1974
1975 - def _set_identity(self, identity):
1976 self.__identity = identity 1977 self.refresh()
1978 1979 identity = property(_get_identity, _set_identity)
1980 #------------------------------------------------------------
1981 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
1982 """A list for managing a person's external IDs. 1983 1984 Does NOT act on/listen to the current patient. 1985 """
1986 - def __init__(self, *args, **kwargs):
1987 1988 try: 1989 self.__identity = kwargs['identity'] 1990 del kwargs['identity'] 1991 except KeyError: 1992 self.__identity = None 1993 1994 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 1995 1996 self.new_callback = self._add_id 1997 self.edit_callback = self._edit_id 1998 self.delete_callback = self._del_id 1999 self.refresh_callback = self.refresh 2000 2001 self.__init_ui() 2002 self.refresh()
2003 #-------------------------------------------------------- 2004 # external API 2005 #--------------------------------------------------------
2006 - def refresh(self, *args, **kwargs):
2007 if self.__identity is None: 2008 self._LCTRL_items.set_string_items() 2009 return 2010 2011 ids = self.__identity.get_external_ids() 2012 self._LCTRL_items.set_string_items ( 2013 items = [ [ 2014 i['name'], 2015 i['value'], 2016 gmTools.coalesce(i['issuer'], u''), 2017 i['context'], 2018 gmTools.coalesce(i['comment'], u'') 2019 ] for i in ids 2020 ] 2021 ) 2022 self._LCTRL_items.set_column_widths() 2023 self._LCTRL_items.set_data(data = ids)
2024 #-------------------------------------------------------- 2025 # internal helpers 2026 #--------------------------------------------------------
2027 - def __init_ui(self):
2028 self._LCTRL_items.set_columns(columns = [ 2029 _('ID type'), 2030 _('Value'), 2031 _('Issuer'), 2032 _('Context'), 2033 _('Comment') 2034 ])
2035 #--------------------------------------------------------
2036 - def _add_id(self):
2037 ea = cExternalIDEditAreaPnl(self, -1) 2038 ea.identity = self.__identity 2039 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 2040 dlg.SetTitle(_('Adding new external ID')) 2041 if dlg.ShowModal() == wx.ID_OK: 2042 dlg.Destroy() 2043 return True 2044 dlg.Destroy() 2045 return False
2046 #--------------------------------------------------------
2047 - def _edit_id(self, ext_id):
2048 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id) 2049 ea.identity = self.__identity 2050 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 2051 dlg.SetTitle(_('Editing external ID')) 2052 if dlg.ShowModal() == wx.ID_OK: 2053 dlg.Destroy() 2054 return True 2055 dlg.Destroy() 2056 return False
2057 #--------------------------------------------------------
2058 - def _del_id(self, ext_id):
2059 go_ahead = gmGuiHelpers.gm_show_question ( 2060 _( 'Do you really want to delete this\n' 2061 'external ID from the patient ?'), 2062 _('Deleting external ID') 2063 ) 2064 if not go_ahead: 2065 return False 2066 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id']) 2067 return True
2068 #-------------------------------------------------------- 2069 # properties 2070 #--------------------------------------------------------
2071 - def _get_identity(self):
2072 return self.__identity
2073
2074 - def _set_identity(self, identity):
2075 self.__identity = identity 2076 self.refresh()
2077 2078 identity = property(_get_identity, _set_identity)
2079 #------------------------------------------------------------ 2080 # integrated panels 2081 #------------------------------------------------------------
2082 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
2083 """A panel for editing identity data for a person. 2084 2085 - provides access to: 2086 - name 2087 - external IDs 2088 2089 Does NOT act on/listen to the current patient. 2090 """
2091 - def __init__(self, *args, **kwargs):
2092 2093 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs) 2094 2095 self.__identity = None 2096 self.refresh()
2097 #-------------------------------------------------------- 2098 # external API 2099 #--------------------------------------------------------
2100 - def refresh(self):
2101 self._PNL_names.identity = self.__identity 2102 self._PNL_ids.identity = self.__identity 2103 # this is an Edit Area: 2104 self._PNL_identity.mode = 'new' 2105 self._PNL_identity.data = self.__identity 2106 if self.__identity is not None: 2107 self._PNL_identity.mode = 'edit'
2108 #-------------------------------------------------------- 2109 # properties 2110 #--------------------------------------------------------
2111 - def _get_identity(self):
2112 return self.__identity
2113
2114 - def _set_identity(self, identity):
2115 self.__identity = identity 2116 self.refresh()
2117 2118 identity = property(_get_identity, _set_identity) 2119 #-------------------------------------------------------- 2120 # event handlers 2121 #--------------------------------------------------------
2123 if not self._PNL_identity.save(): 2124 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save identity. Incomplete information.'), beep = True)
2125 #--------------------------------------------------------
2126 - def _on_reload_identity_button_pressed(self, event):
2127 self._PNL_identity.refresh()
2128 2129 #============================================================ 2130 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl 2131
2132 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
2133 - def __init__(self, *args, **kwargs):
2134 2135 wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs) 2136 2137 self.__identity = None 2138 self.refresh()
2139 #-------------------------------------------------------- 2140 # external API 2141 #--------------------------------------------------------
2142 - def refresh(self):
2143 2144 tt = _("Link another person in this database as the emergency contact.") 2145 2146 if self.__identity is None: 2147 self._TCTRL_er_contact.SetValue(u'') 2148 self._TCTRL_person.person = None 2149 self._TCTRL_person.SetToolTipString(tt) 2150 return 2151 2152 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u'')) 2153 if self.__identity['pk_emergency_contact'] is not None: 2154 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact']) 2155 self._TCTRL_person.person = ident 2156 tt = u'%s\n\n%s\n\n%s' % ( 2157 tt, 2158 ident['description_gender'], 2159 u'\n'.join([ 2160 u'%s: %s%s' % ( 2161 c['l10n_comm_type'], 2162 c['url'], 2163 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'') 2164 ) 2165 for c in ident.get_comm_channels() 2166 ]) 2167 ) 2168 else: 2169 self._TCTRL_person.person = None 2170 2171 self._TCTRL_person.SetToolTipString(tt)
2172 #-------------------------------------------------------- 2173 # properties 2174 #--------------------------------------------------------
2175 - def _get_identity(self):
2176 return self.__identity
2177
2178 - def _set_identity(self, identity):
2179 self.__identity = identity 2180 self.refresh()
2181 2182 identity = property(_get_identity, _set_identity) 2183 #-------------------------------------------------------- 2184 # event handlers 2185 #--------------------------------------------------------
2186 - def _on_save_button_pressed(self, event):
2187 if self.__identity is not None: 2188 self.__identity['emergency_contact'] = self._TCTRL_er_contact.GetValue().strip() 2189 if self._TCTRL_person.person is not None: 2190 self.__identity['pk_emergency_contact'] = self._TCTRL_person.person.ID 2191 self.__identity.save() 2192 2193 event.Skip()
2194 #--------------------------------------------------------
2195 - def _on_remove_contact_button_pressed(self, event):
2196 event.Skip() 2197 2198 if self.__identity is None: 2199 return 2200 2201 self._TCTRL_person.person = None 2202 2203 self.__identity['pk_emergency_contact'] = None 2204 self.__identity.save()
2205 #--------------------------------------------------------
2206 - def _on_button_activate_contact_pressed(self, event):
2207 ident = self._TCTRL_person.person 2208 if ident is not None: 2209 from Gnumed.wxpython import gmPatSearchWidgets 2210 gmPatSearchWidgets.set_active_patient(patient = ident, forced_reload = False) 2211 2212 event.Skip()
2213 #============================================================ 2214 # new-patient widgets 2215 #============================================================
2216 -def create_new_person(parent=None, activate=False):
2217 2218 dbcfg = gmCfg.cCfgSQL() 2219 2220 def_region = dbcfg.get2 ( 2221 option = u'person.create.default_region', 2222 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2223 bias = u'user' 2224 ) 2225 2226 if def_region is None: 2227 def_country = dbcfg.get2 ( 2228 option = u'person.create.default_country', 2229 workplace = gmSurgery.gmCurrentPractice().active_workplace, 2230 bias = u'user' 2231 ) 2232 else: 2233 countries = gmDemographicRecord.get_country_for_region(region = def_region) 2234 if len(countries) == 1: 2235 def_country = countries[0]['l10n_country'] 2236 2237 if parent is None: 2238 parent = wx.GetApp().GetTopWindow() 2239 2240 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region) 2241 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 2242 dlg.SetTitle(_('Adding new person')) 2243 ea._PRW_lastname.SetFocus() 2244 result = dlg.ShowModal() 2245 pat = ea.data 2246 dlg.Destroy() 2247 2248 if result != wx.ID_OK: 2249 return False 2250 2251 if activate: 2252 from Gnumed.wxpython import gmPatSearchWidgets 2253 gmPatSearchWidgets.set_active_patient(patient = pat) 2254 2255 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 2256 2257 return True
2258 #============================================================ 2259 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 2260
2261 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
2262
2263 - def __init__(self, *args, **kwargs):
2264 2265 try: 2266 self.default_region = kwargs['region'] 2267 del kwargs['region'] 2268 except KeyError: 2269 self.default_region = None 2270 2271 try: 2272 self.default_country = kwargs['country'] 2273 del kwargs['country'] 2274 except KeyError: 2275 self.default_country = None 2276 2277 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 2278 gmEditArea.cGenericEditAreaMixin.__init__(self) 2279 2280 self.mode = 'new' 2281 self.data = None 2282 self._address = None 2283 2284 self.__init_ui() 2285 self.__register_interests()
2286 #---------------------------------------------------------------- 2287 # internal helpers 2288 #----------------------------------------------------------------
2289 - def __init_ui(self):
2290 self._PRW_lastname.final_regex = '.+' 2291 self._PRW_firstnames.final_regex = '.+' 2292 self._PRW_address_searcher.selection_only = False 2293 low = wx.DateTimeFromDMY(1,0,1900) 2294 hi = wx.DateTime() 2295 self._DP_dob.SetRange(low, hi.SetToCurrent()) 2296 # only if we would support None on selection_only's 2297 #self._PRW_external_id_type.selection_only = True 2298 2299 if self.default_country is not None: 2300 self._PRW_country.SetText(value = self.default_country) 2301 2302 if self.default_region is not None: 2303 self._PRW_region.SetText(value = self.default_region)
2304 #----------------------------------------------------------------
2305 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
2306 2307 adr = self._PRW_address_searcher.get_address() 2308 if adr is None: 2309 return True 2310 2311 if ctrl.GetValue().strip() != adr[field]: 2312 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None) 2313 return True 2314 2315 return False
2316 #----------------------------------------------------------------
2318 adr = self._PRW_address_searcher.get_address() 2319 if adr is None: 2320 return True 2321 2322 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 2323 2324 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 2325 self._PRW_street.set_context(context = u'zip', val = adr['postcode']) 2326 2327 self._TCTRL_number.SetValue(adr['number']) 2328 2329 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 2330 self._PRW_urb.set_context(context = u'zip', val = adr['postcode']) 2331 2332 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state']) 2333 self._PRW_region.set_context(context = u'zip', val = adr['postcode']) 2334 2335 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 2336 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
2337 #----------------------------------------------------------------
2338 - def __identity_valid_for_save(self):
2339 error = False 2340 2341 # name fields 2342 if self._PRW_lastname.GetValue().strip() == u'': 2343 error = True 2344 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2345 self._PRW_lastname.display_as_valid(False) 2346 else: 2347 self._PRW_lastname.display_as_valid(True) 2348 2349 if self._PRW_firstnames.GetValue().strip() == '': 2350 error = True 2351 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2352 self._PRW_firstnames.display_as_valid(False) 2353 else: 2354 self._PRW_firstnames.display_as_valid(True) 2355 2356 # gender 2357 if self._PRW_gender.GetData() is None: 2358 error = True 2359 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2360 self._PRW_gender.display_as_valid(False) 2361 else: 2362 self._PRW_gender.display_as_valid(True) 2363 2364 # dob validation 2365 if not self._DP_dob.is_valid_timestamp(): 2366 2367 gmDispatcher.send(signal = 'statustext', msg = _('Cannot use this date of birth. Does it lie before 1900 ?')) 2368 2369 do_it_anyway = gmGuiHelpers.gm_show_question ( 2370 _( 2371 'Are you sure you want to register this person\n' 2372 'without a valid date of birth ?\n' 2373 '\n' 2374 'This can be useful for temporary staff members\n' 2375 'but will provoke nag screens if this person\n' 2376 'becomes a patient.\n' 2377 '\n' 2378 'Note that the date of birth cannot technically\n' 2379 'be before 1900, either :-(\n' 2380 ), 2381 _('Registering new person') 2382 ) 2383 2384 if not do_it_anyway: 2385 error = True 2386 2387 if self._DP_dob.GetValue() is None: 2388 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2389 elif self._DP_dob.GetValue().GetYear() < 1900: 2390 error = True 2391 gmDispatcher.send(signal = 'statustext', msg = _('The year of birth must lie after 1900.'), beep = True) 2392 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2393 self._DP_dob.SetFocus() 2394 else: 2395 self._DP_dob.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2396 self._DP_dob.Refresh() 2397 2398 # TOB validation if non-empty 2399 # if self._TCTRL_tob.GetValue().strip() != u'': 2400 2401 return (not error)
2402 #----------------------------------------------------------------
2403 - def __address_valid_for_save(self, empty_address_is_valid=False):
2404 2405 # existing address ? if so set other fields 2406 if self._PRW_address_searcher.GetData() is not None: 2407 wx.CallAfter(self.__set_fields_from_address_searcher) 2408 return True 2409 2410 # must either all contain something or none of them 2411 fields_to_fill = ( 2412 self._TCTRL_number, 2413 self._PRW_zip, 2414 self._PRW_street, 2415 self._PRW_urb, 2416 self._PRW_region, 2417 self._PRW_country 2418 ) 2419 no_of_filled_fields = 0 2420 2421 for field in fields_to_fill: 2422 if field.GetValue().strip() != u'': 2423 no_of_filled_fields += 1 2424 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2425 field.Refresh() 2426 2427 # empty address ? 2428 if no_of_filled_fields == 0: 2429 if empty_address_is_valid: 2430 return True 2431 else: 2432 return None 2433 2434 # incompletely filled address ? 2435 if no_of_filled_fields != len(fields_to_fill): 2436 for field in fields_to_fill: 2437 if field.GetValue().strip() == u'': 2438 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2439 field.SetFocus() 2440 field.Refresh() 2441 msg = _('To properly create an address, all the related fields must be filled in.') 2442 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2443 return False 2444 2445 # fields which must contain a selected item 2446 # FIXME: they must also contain an *acceptable combination* which 2447 # FIXME: can only be tested against the database itself ... 2448 strict_fields = ( 2449 self._PRW_region, 2450 self._PRW_country 2451 ) 2452 error = False 2453 for field in strict_fields: 2454 if field.GetData() is None: 2455 error = True 2456 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 2457 field.SetFocus() 2458 else: 2459 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 2460 field.Refresh() 2461 2462 if error: 2463 msg = _('This field must contain an item selected from the dropdown list.') 2464 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2465 return False 2466 2467 return True
2468 #----------------------------------------------------------------
2469 - def __register_interests(self):
2470 2471 # identity 2472 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 2473 2474 # address 2475 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 2476 2477 # invalidate address searcher when any field edited 2478 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 2479 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher) 2480 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 2481 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 2482 2483 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 2484 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
2485 #---------------------------------------------------------------- 2486 # event handlers 2487 #----------------------------------------------------------------
2488 - def _on_leaving_firstname(self):
2489 """Set the gender according to entered firstname. 2490 2491 Matches are fetched from existing records in backend. 2492 """ 2493 # only set if not already set so as to not 2494 # overwrite a change by the user 2495 if self._PRW_gender.GetData() is not None: 2496 return True 2497 2498 firstname = self._PRW_firstnames.GetValue().strip() 2499 if firstname == u'': 2500 return True 2501 2502 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 2503 if gender is None: 2504 return True 2505 2506 wx.CallAfter(self._PRW_gender.SetData, gender) 2507 return True
2508 #----------------------------------------------------------------
2509 - def _on_leaving_zip(self):
2510 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 2511 2512 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'') 2513 self._PRW_street.set_context(context = u'zip', val = zip_code) 2514 self._PRW_urb.set_context(context = u'zip', val = zip_code) 2515 self._PRW_region.set_context(context = u'zip', val = zip_code) 2516 self._PRW_country.set_context(context = u'zip', val = zip_code) 2517 2518 return True
2519 #----------------------------------------------------------------
2520 - def _on_leaving_country(self):
2521 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 2522 2523 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'') 2524 self._PRW_region.set_context(context = u'country', val = country) 2525 2526 return True
2527 #----------------------------------------------------------------
2528 - def _invalidate_address_searcher(self, *args, **kwargs):
2529 mapping = [ 2530 (self._PRW_street, 'street'), 2531 (self._TCTRL_number, 'number'), 2532 (self._PRW_urb, 'urb'), 2533 (self._PRW_region, 'l10n_state') 2534 ] 2535 2536 # loop through fields and invalidate address searcher if different 2537 for ctrl, field in mapping: 2538 if self.__perhaps_invalidate_address_searcher(ctrl, field): 2539 return True 2540 2541 return True
2542 #----------------------------------------------------------------
2544 adr = self._PRW_address_searcher.get_address() 2545 if adr is None: 2546 return True 2547 2548 wx.CallAfter(self.__set_fields_from_address_searcher) 2549 return True
2550 #---------------------------------------------------------------- 2551 # generic Edit Area mixin API 2552 #----------------------------------------------------------------
2553 - def _valid_for_save(self):
2554 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
2555 #----------------------------------------------------------------
2556 - def _save_as_new(self):
2557 2558 # identity 2559 new_identity = gmPerson.create_identity ( 2560 gender = self._PRW_gender.GetData(), 2561 dob = self._DP_dob.get_pydt(), 2562 lastnames = self._PRW_lastname.GetValue().strip(), 2563 firstnames = self._PRW_firstnames.GetValue().strip() 2564 ) 2565 _log.debug('identity created: %s' % new_identity) 2566 2567 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 2568 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u'')) 2569 #TOB 2570 new_identity.save() 2571 2572 name = new_identity.get_active_name() 2573 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 2574 name.save() 2575 2576 # address 2577 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 2578 if is_valid is True: 2579 # because we currently only check for non-emptiness 2580 # we must still deal with database errors 2581 try: 2582 new_identity.link_address ( 2583 number = self._TCTRL_number.GetValue().strip(), 2584 street = self._PRW_street.GetValue().strip(), 2585 postcode = self._PRW_zip.GetValue().strip(), 2586 urb = self._PRW_urb.GetValue().strip(), 2587 state = self._PRW_region.GetData(), 2588 country = self._PRW_country.GetData() 2589 ) 2590 except psycopg2.InternalError: 2591 #except StandardError: 2592 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 2593 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 2594 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 2595 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 2596 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip()) 2597 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 2598 _log.exception('cannot link address') 2599 gmGuiHelpers.gm_show_error ( 2600 aTitle = _('Saving address'), 2601 aMessage = _( 2602 'Cannot save this address.\n' 2603 '\n' 2604 'You will have to add it via the Demographics plugin.\n' 2605 ) 2606 ) 2607 elif is_valid is False: 2608 gmGuiHelpers.gm_show_error ( 2609 aTitle = _('Saving address'), 2610 aMessage = _( 2611 'Address not saved.\n' 2612 '\n' 2613 'You will have to add it via the Demographics plugin.\n' 2614 ) 2615 ) 2616 # else it is None which means empty address which we ignore 2617 2618 # phone 2619 new_identity.link_comm_channel ( 2620 comm_medium = u'homephone', 2621 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''), 2622 is_confidential = False 2623 ) 2624 2625 # external ID 2626 pk_type = self._PRW_external_id_type.GetData() 2627 id_value = self._TCTRL_external_id_value.GetValue().strip() 2628 if (pk_type is not None) and (id_value != u''): 2629 new_identity.add_external_id(value = id_value, pk_type = pk_type) 2630 2631 # occupation 2632 new_identity.link_occupation ( 2633 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'') 2634 ) 2635 2636 self.data = new_identity 2637 return True
2638 #----------------------------------------------------------------
2639 - def _save_as_update(self):
2640 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2641 #----------------------------------------------------------------
2642 - def _refresh_as_new(self):
2643 # FIXME: button "empty out" 2644 return
2645 #----------------------------------------------------------------
2646 - def _refresh_from_existing(self):
2647 return # there is no forward button so nothing to do here
2648 #----------------------------------------------------------------
2650 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
2651 #============================================================ 2652 # new-patient wizard classes 2653 #============================================================
2654 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
2655 """ 2656 Wizard page for entering patient's basic demographic information 2657 """ 2658 2659 form_fields = ( 2660 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation', 2661 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment' 2662 ) 2663
2664 - def __init__(self, parent, title):
2665 """ 2666 Creates a new instance of BasicPatDetailsPage 2667 @param parent - The parent widget 2668 @type parent - A wx.Window instance 2669 @param tile - The title of the page 2670 @type title - A StringType instance 2671 """ 2672 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson')) 2673 self.__title = title 2674 self.__do_layout() 2675 self.__register_interests()
2676 #--------------------------------------------------------
2677 - def __do_layout(self):
2678 PNL_form = wx.Panel(self, -1) 2679 2680 # last name 2681 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name')) 2682 STT_lastname.SetForegroundColour('red') 2683 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1) 2684 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)')) 2685 2686 # first name 2687 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)')) 2688 STT_firstname.SetForegroundColour('red') 2689 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1) 2690 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name')) 2691 2692 # nickname 2693 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name')) 2694 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1) 2695 2696 # DOB 2697 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth')) 2698 STT_dob.SetForegroundColour('red') 2699 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1) 2700 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one")) 2701 2702 # gender 2703 STT_gender = wx.StaticText(PNL_form, -1, _('Gender')) 2704 STT_gender.SetForegroundColour('red') 2705 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1) 2706 self.PRW_gender.SetToolTipString(_("Required: gender of patient")) 2707 2708 # title 2709 STT_title = wx.StaticText(PNL_form, -1, _('Title')) 2710 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1) 2711 2712 # zip code 2713 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code')) 2714 STT_zip_code.SetForegroundColour('orange') 2715 self.PRW_zip_code = cZipcodePhraseWheel(parent = PNL_form, id = -1) 2716 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code")) 2717 2718 # street 2719 STT_street = wx.StaticText(PNL_form, -1, _('Street')) 2720 STT_street.SetForegroundColour('orange') 2721 self.PRW_street = cStreetPhraseWheel(parent = PNL_form, id = -1) 2722 self.PRW_street.SetToolTipString(_("primary/home address: name of street")) 2723 2724 # address number 2725 STT_address_number = wx.StaticText(PNL_form, -1, _('Number')) 2726 STT_address_number.SetForegroundColour('orange') 2727 self.TTC_address_number = wx.TextCtrl(PNL_form, -1) 2728 self.TTC_address_number.SetToolTipString(_("primary/home address: address number")) 2729 2730 # town 2731 STT_town = wx.StaticText(PNL_form, -1, _('Place')) 2732 STT_town.SetForegroundColour('orange') 2733 self.PRW_town = cUrbPhraseWheel(parent = PNL_form, id = -1) 2734 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/...")) 2735 2736 # state 2737 STT_state = wx.StaticText(PNL_form, -1, _('Region')) 2738 STT_state.SetForegroundColour('orange') 2739 self.PRW_state = cStateSelectionPhraseWheel(parent=PNL_form, id=-1) 2740 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/...")) 2741 2742 # country 2743 STT_country = wx.StaticText(PNL_form, -1, _('Country')) 2744 STT_country.SetForegroundColour('orange') 2745 self.PRW_country = cCountryPhraseWheel(parent = PNL_form, id = -1) 2746 self.PRW_country.SetToolTipString(_("primary/home address: country")) 2747 2748 # phone 2749 STT_phone = wx.StaticText(PNL_form, -1, _('Phone')) 2750 self.TTC_phone = wx.TextCtrl(PNL_form, -1) 2751 self.TTC_phone.SetToolTipString(_("phone number at home")) 2752 2753 # occupation 2754 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 2755 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 2756 2757 # comment 2758 STT_comment = wx.StaticText(PNL_form, -1, _('Comment')) 2759 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1) 2760 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.')) 2761 2762 # form main validator 2763 self.form_DTD = cFormDTD(fields = self.__class__.form_fields) 2764 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD)) 2765 2766 # layout input widgets 2767 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4) 2768 SZR_input.AddGrowableCol(1) 2769 SZR_input.Add(STT_lastname, 0, wx.SHAPED) 2770 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND) 2771 SZR_input.Add(STT_firstname, 0, wx.SHAPED) 2772 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND) 2773 SZR_input.Add(STT_nick, 0, wx.SHAPED) 2774 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND) 2775 SZR_input.Add(STT_dob, 0, wx.SHAPED) 2776 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND) 2777 SZR_input.Add(STT_gender, 0, wx.SHAPED) 2778 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND) 2779 SZR_input.Add(STT_title, 0, wx.SHAPED) 2780 SZR_input.Add(self.PRW_title, 1, wx.EXPAND) 2781 SZR_input.Add(STT_zip_code, 0, wx.SHAPED) 2782 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND) 2783 SZR_input.Add(STT_street, 0, wx.SHAPED) 2784 SZR_input.Add(self.PRW_street, 1, wx.EXPAND) 2785 SZR_input.Add(STT_address_number, 0, wx.SHAPED) 2786 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND) 2787 SZR_input.Add(STT_town, 0, wx.SHAPED) 2788 SZR_input.Add(self.PRW_town, 1, wx.EXPAND) 2789 SZR_input.Add(STT_state, 0, wx.SHAPED) 2790 SZR_input.Add(self.PRW_state, 1, wx.EXPAND) 2791 SZR_input.Add(STT_country, 0, wx.SHAPED) 2792 SZR_input.Add(self.PRW_country, 1, wx.EXPAND) 2793 SZR_input.Add(STT_phone, 0, wx.SHAPED) 2794 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND) 2795 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 2796 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 2797 SZR_input.Add(STT_comment, 0, wx.SHAPED) 2798 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND) 2799 2800 PNL_form.SetSizerAndFit(SZR_input) 2801 2802 # layout page 2803 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title) 2804 SZR_main.Add(PNL_form, 1, wx.EXPAND)
2805 #-------------------------------------------------------- 2806 # event handling 2807 #--------------------------------------------------------
2808 - def __register_interests(self):
2809 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set) 2810 self.PRW_country.add_callback_on_selection(self.on_country_selected) 2811 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
2812 #--------------------------------------------------------
2813 - def on_country_selected(self, data):
2814 """Set the states according to entered country.""" 2815 self.PRW_state.set_context(context=u'country', val=data) 2816 return True
2817 #--------------------------------------------------------
2818 - def on_name_set(self):
2819 """Set the gender according to entered firstname. 2820 2821 Matches are fetched from existing records in backend. 2822 """ 2823 firstname = self.PRW_firstname.GetValue().strip() 2824 rows, idx = gmPG2.run_ro_queries(queries = [{ 2825 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 2826 'args': [firstname] 2827 }]) 2828 if len(rows) == 0: 2829 return True 2830 wx.CallAfter(self.PRW_gender.SetData, rows[0][0]) 2831 return True
2832 #--------------------------------------------------------
2833 - def on_zip_set(self):
2834 """Set the street, town, state and country according to entered zip code.""" 2835 zip_code = self.PRW_zip_code.GetValue().strip() 2836 self.PRW_street.set_context(context=u'zip', val=zip_code) 2837 self.PRW_town.set_context(context=u'zip', val=zip_code) 2838 self.PRW_state.set_context(context=u'zip', val=zip_code) 2839 self.PRW_country.set_context(context=u'zip', val=zip_code) 2840 return True
2841 #============================================================
2842 -class cNewPatientWizard(wx.wizard.Wizard):
2843 """ 2844 Wizard to create a new patient. 2845 2846 TODO: 2847 - write pages for different "themes" of patient creation 2848 - make it configurable which pages are loaded 2849 - make available sets of pages that apply to a country 2850 - make loading of some pages depend upon values in earlier pages, eg 2851 when the patient is female and older than 13 include a page about 2852 "female" data (number of kids etc) 2853 2854 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable() 2855 """ 2856 #--------------------------------------------------------
2857 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
2858 """ 2859 Creates a new instance of NewPatientWizard 2860 @param parent - The parent widget 2861 @type parent - A wx.Window instance 2862 """ 2863 id_wiz = wx.NewId() 2864 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap() 2865 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY) 2866 self.__subtitle = subtitle 2867 self.__do_layout()
2868 #--------------------------------------------------------
2869 - def RunWizard(self, activate=False):
2870 """Create new patient. 2871 2872 activate, too, if told to do so (and patient successfully created) 2873 """ 2874 while True: 2875 2876 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details): 2877 return False 2878 2879 try: 2880 # retrieve DTD and create patient 2881 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD) 2882 except: 2883 _log.exception('cannot add new patient - missing identity fields') 2884 gmGuiHelpers.gm_show_error ( 2885 _('Cannot create new patient.\n' 2886 'Missing parts of the identity.' 2887 ), 2888 _('Adding new patient') 2889 ) 2890 continue 2891 2892 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2893 2894 try: 2895 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2896 except: 2897 _log.exception('cannot finalize new patient - missing address fields') 2898 gmGuiHelpers.gm_show_error ( 2899 _('Cannot add address for the new patient.\n' 2900 'You must either enter all of the address fields or\n' 2901 'none at all. The relevant fields are marked in yellow.\n' 2902 '\n' 2903 'You will need to add the address details in the\n' 2904 'demographics module.' 2905 ), 2906 _('Adding new patient') 2907 ) 2908 break 2909 2910 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 2911 2912 break 2913 2914 if activate: 2915 from Gnumed.wxpython import gmPatSearchWidgets 2916 gmPatSearchWidgets.set_active_patient(patient = ident) 2917 2918 return ident
2919 #-------------------------------------------------------- 2920 # internal helpers 2921 #--------------------------------------------------------
2922 - def __do_layout(self):
2923 """Arrange widgets. 2924 """ 2925 # Create the wizard pages 2926 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle ) 2927 self.FitToPage(self.basic_pat_details)
2928 #============================================================ 2929 #============================================================
2930 -class cBasicPatDetailsPageValidator(wx.PyValidator):
2931 """ 2932 This validator is used to ensure that the user has entered all 2933 the required conditional values in the page (eg., to properly 2934 create an address, all the related fields must be filled). 2935 """ 2936 #--------------------------------------------------------
2937 - def __init__(self, dtd):
2938 """ 2939 Validator initialization. 2940 @param dtd The object containing the data model. 2941 @type dtd A cFormDTD instance 2942 """ 2943 # initialize parent class 2944 wx.PyValidator.__init__(self) 2945 # validator's storage object 2946 self.form_DTD = dtd
2947 #--------------------------------------------------------
2948 - def Clone(self):
2949 """ 2950 Standard cloner. 2951 Note that every validator must implement the Clone() method. 2952 """ 2953 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
2954 #--------------------------------------------------------
2955 - def Validate(self, parent = None):
2956 """ 2957 Validate the contents of the given text control. 2958 """ 2959 _pnl_form = self.GetWindow().GetParent() 2960 2961 error = False 2962 2963 # name fields 2964 if _pnl_form.PRW_lastname.GetValue().strip() == '': 2965 error = True 2966 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2967 _pnl_form.PRW_lastname.SetBackgroundColour('pink') 2968 _pnl_form.PRW_lastname.Refresh() 2969 else: 2970 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2971 _pnl_form.PRW_lastname.Refresh() 2972 2973 if _pnl_form.PRW_firstname.GetValue().strip() == '': 2974 error = True 2975 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2976 _pnl_form.PRW_firstname.SetBackgroundColour('pink') 2977 _pnl_form.PRW_firstname.Refresh() 2978 else: 2979 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2980 _pnl_form.PRW_firstname.Refresh() 2981 2982 # gender 2983 if _pnl_form.PRW_gender.GetData() is None: 2984 error = True 2985 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2986 _pnl_form.PRW_gender.SetBackgroundColour('pink') 2987 _pnl_form.PRW_gender.Refresh() 2988 else: 2989 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2990 _pnl_form.PRW_gender.Refresh() 2991 2992 # dob validation 2993 if ( 2994 (_pnl_form.PRW_dob.GetValue().strip() == u'') 2995 or (not _pnl_form.PRW_dob.is_valid_timestamp()) 2996 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900) 2997 ): 2998 error = True 2999 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue() 3000 gmDispatcher.send(signal = 'statustext', msg = msg) 3001 _pnl_form.PRW_dob.SetBackgroundColour('pink') 3002 _pnl_form.PRW_dob.Refresh() 3003 else: 3004 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 3005 _pnl_form.PRW_dob.Refresh() 3006 3007 # address 3008 is_any_field_filled = False 3009 address_fields = ( 3010 _pnl_form.TTC_address_number, 3011 _pnl_form.PRW_zip_code, 3012 _pnl_form.PRW_street, 3013 _pnl_form.PRW_town 3014 ) 3015 for field in address_fields: 3016 if field.GetValue().strip() == u'': 3017 if is_any_field_filled: 3018 error = True 3019 msg = _('To properly create an address, all the related fields must be filled in.') 3020 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 3021 field.SetBackgroundColour('pink') 3022 field.SetFocus() 3023 field.Refresh() 3024 else: 3025 is_any_field_filled = True 3026 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 3027 field.Refresh() 3028 3029 address_fields = ( 3030 _pnl_form.PRW_state, 3031 _pnl_form.PRW_country 3032 ) 3033 for field in address_fields: 3034 if field.GetData() is None: 3035 if is_any_field_filled: 3036 error = True 3037 msg = _('To properly create an address, all the related fields must be filled in.') 3038 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 3039 field.SetBackgroundColour('pink') 3040 field.SetFocus() 3041 field.Refresh() 3042 else: 3043 is_any_field_filled = True 3044 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 3045 field.Refresh() 3046 3047 return (not error)
3048 #--------------------------------------------------------
3049 - def TransferToWindow(self):
3050 """ 3051 Transfer data from validator to window. 3052 The default implementation returns False, indicating that an error 3053 occurred. We simply return True, as we don't do any data transfer. 3054 """ 3055 _pnl_form = self.GetWindow().GetParent() 3056 # fill in controls with values from self.form_DTD 3057 _pnl_form.PRW_gender.SetData(self.form_DTD['gender']) 3058 _pnl_form.PRW_dob.SetText(self.form_DTD['dob']) 3059 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames']) 3060 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames']) 3061 _pnl_form.PRW_title.SetText(self.form_DTD['title']) 3062 _pnl_form.PRW_nick.SetText(self.form_DTD['nick']) 3063 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation']) 3064 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number']) 3065 _pnl_form.PRW_street.SetText(self.form_DTD['street']) 3066 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code']) 3067 _pnl_form.PRW_town.SetText(self.form_DTD['town']) 3068 _pnl_form.PRW_state.SetData(self.form_DTD['state']) 3069 _pnl_form.PRW_country.SetData(self.form_DTD['country']) 3070 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone']) 3071 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment']) 3072 return True # Prevent wxDialog from complaining
3073 #--------------------------------------------------------
3074 - def TransferFromWindow(self):
3075 """ 3076 Transfer data from window to validator. 3077 The default implementation returns False, indicating that an error 3078 occurred. We simply return True, as we don't do any data transfer. 3079 """ 3080 # FIXME: should be called automatically 3081 if not self.GetWindow().GetParent().Validate(): 3082 return False 3083 try: 3084 _pnl_form = self.GetWindow().GetParent() 3085 # fill in self.form_DTD with values from controls 3086 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData() 3087 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData() 3088 3089 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue() 3090 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue() 3091 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue() 3092 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue() 3093 3094 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue() 3095 3096 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue() 3097 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue() 3098 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue() 3099 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue() 3100 self.form_DTD['state'] = _pnl_form.PRW_state.GetData() 3101 self.form_DTD['country'] = _pnl_form.PRW_country.GetData() 3102 3103 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue() 3104 3105 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue() 3106 except: 3107 return False 3108 return True
3109 #============================================================ 3110 #class cFormDTD: 3111 # """ 3112 # Simple Data Transfer Dictionary class to make easy the trasfer of 3113 # data between the form (view) and the business logic. 3114 # 3115 # Maybe later consider turning this into a standard dict by 3116 # {}.fromkeys([key, key, ...], default) when it becomes clear that 3117 # we really don't need the added potential of a full-fledged class. 3118 # """ 3119 # def __init__(self, fields): 3120 # """ 3121 # Initialize the DTD with the supplied field names. 3122 # @param fields The names of the fields. 3123 # @type fields A TupleType instance. 3124 # """ 3125 # self.data = {} 3126 # for a_field in fields: 3127 # self.data[a_field] = '' 3128 # 3129 # def __getitem__(self, attribute): 3130 # """ 3131 # Retrieve the value of the given attribute (key) 3132 # @param attribute The attribute (key) to retrieve its value for. 3133 # @type attribute a StringType instance. 3134 # """ 3135 # if not self.data[attribute]: 3136 # return '' 3137 # return self.data[attribute] 3138 # 3139 # def __setitem__(self, attribute, value): 3140 # """ 3141 # Set the value of a given attribute (key). 3142 # @param attribute The attribute (key) to set its value for. 3143 # @type attribute a StringType instance. 3144 # @param avaluee The value to set. 3145 # @rtpe attribute a StringType instance. 3146 # """ 3147 # self.data[attribute] = value 3148 # 3149 # def __str__(self): 3150 # """ 3151 # Print string representation of the DTD object. 3152 # """ 3153 # return str(self.data) 3154 #============================================================ 3155 # patient demographics editing classes 3156 #============================================================
3157 -class cPersonDemographicsEditorNb(wx.Notebook):
3158 """Notebook displaying demographics editing pages: 3159 3160 - Identity 3161 - Contacts (addresses, phone numbers, etc) 3162 - Social Network (significant others, GP, etc) 3163 3164 Does NOT act on/listen to the current patient. 3165 """ 3166 #--------------------------------------------------------
3167 - def __init__(self, parent, id):
3168 3169 wx.Notebook.__init__ ( 3170 self, 3171 parent = parent, 3172 id = id, 3173 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 3174 name = self.__class__.__name__ 3175 ) 3176 3177 self.__identity = None 3178 self.__do_layout() 3179 self.SetSelection(0)
3180 #-------------------------------------------------------- 3181 # public API 3182 #--------------------------------------------------------
3183 - def refresh(self):
3184 """Populate fields in pages with data from model.""" 3185 for page_idx in range(self.GetPageCount()): 3186 page = self.GetPage(page_idx) 3187 page.identity = self.__identity 3188 3189 return True
3190 #-------------------------------------------------------- 3191 # internal API 3192 #--------------------------------------------------------
3193 - def __do_layout(self):
3194 """Build patient edition notebook pages.""" 3195 3196 # contacts page 3197 new_page = cPersonContactsManagerPnl(self, -1) 3198 new_page.identity = self.__identity 3199 self.AddPage ( 3200 page = new_page, 3201 text = _('Contacts'), 3202 select = True 3203 ) 3204 3205 # identity page 3206 new_page = cPersonIdentityManagerPnl(self, -1) 3207 new_page.identity = self.__identity 3208 self.AddPage ( 3209 page = new_page, 3210 text = _('Identity'), 3211 select = False 3212 ) 3213 3214 # social network page 3215 new_page = cPersonSocialNetworkManagerPnl(self, -1) 3216 new_page.identity = self.__identity 3217 self.AddPage ( 3218 page = new_page, 3219 text = _('Social Network'), 3220 select = False 3221 )
3222 #-------------------------------------------------------- 3223 # properties 3224 #--------------------------------------------------------
3225 - def _get_identity(self):
3226 return self.__identity
3227
3228 - def _set_identity(self, identity):
3229 self.__identity = identity
3230 3231 identity = property(_get_identity, _set_identity)
3232 #============================================================ 3233 #============================================================ 3234 # FIXME: support multiple occupations 3235 # FIXME: redo with wxGlade 3236
3237 -class cPatOccupationsPanel(wx.Panel):
3238 """Page containing patient occupations edition fields. 3239 """
3240 - def __init__(self, parent, id, ident=None):
3241 """ 3242 Creates a new instance of BasicPatDetailsPage 3243 @param parent - The parent widget 3244 @type parent - A wx.Window instance 3245 @param id - The widget id 3246 @type id - An integer 3247 """ 3248 wx.Panel.__init__(self, parent, id) 3249 self.__ident = ident 3250 self.__do_layout()
3251 #--------------------------------------------------------
3252 - def __do_layout(self):
3253 PNL_form = wx.Panel(self, -1) 3254 # occupation 3255 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 3256 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 3257 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient")) 3258 # known since 3259 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 3260 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 3261 3262 # layout input widgets 3263 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 3264 SZR_input.AddGrowableCol(1) 3265 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 3266 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 3267 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 3268 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 3269 PNL_form.SetSizerAndFit(SZR_input) 3270 3271 # layout page 3272 SZR_main = wx.BoxSizer(wx.VERTICAL) 3273 SZR_main.Add(PNL_form, 1, wx.EXPAND) 3274 self.SetSizer(SZR_main)
3275 #--------------------------------------------------------
3276 - def set_identity(self, identity):
3277 return self.refresh(identity=identity)
3278 #--------------------------------------------------------
3279 - def refresh(self, identity=None):
3280 if identity is not None: 3281 self.__ident = identity 3282 jobs = self.__ident.get_occupations() 3283 if len(jobs) > 0: 3284 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 3285 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 3286 return True
3287 #--------------------------------------------------------
3288 - def save(self):
3289 if self.PRW_occupation.IsModified(): 3290 new_job = self.PRW_occupation.GetValue().strip() 3291 jobs = self.__ident.get_occupations() 3292 for job in jobs: 3293 if job['l10n_occupation'] == new_job: 3294 continue 3295 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 3296 self.__ident.link_occupation(occupation = new_job) 3297 return True
3298 #============================================================
3299 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
3300 """Patient demographics plugin for main notebook. 3301 3302 Hosts another notebook with pages for Identity, Contacts, etc. 3303 3304 Acts on/listens to the currently active patient. 3305 """ 3306 #--------------------------------------------------------
3307 - def __init__(self, parent, id):
3308 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 3309 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 3310 self.__do_layout() 3311 self.__register_interests()
3312 #-------------------------------------------------------- 3313 # public API 3314 #-------------------------------------------------------- 3315 #-------------------------------------------------------- 3316 # internal helpers 3317 #--------------------------------------------------------
3318 - def __do_layout(self):
3319 """Arrange widgets.""" 3320 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 3321 3322 szr_main = wx.BoxSizer(wx.VERTICAL) 3323 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 3324 self.SetSizerAndFit(szr_main)
3325 #-------------------------------------------------------- 3326 # event handling 3327 #--------------------------------------------------------
3328 - def __register_interests(self):
3329 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 3330 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
3331 #--------------------------------------------------------
3332 - def _on_pre_patient_selection(self):
3333 self._schedule_data_reget()
3334 #--------------------------------------------------------
3335 - def _on_post_patient_selection(self):
3336 self._schedule_data_reget()
3337 #-------------------------------------------------------- 3338 # reget mixin API 3339 #--------------------------------------------------------
3340 - def _populate_with_data(self):
3341 """Populate fields in pages with data from model.""" 3342 pat = gmPerson.gmCurrentPatient() 3343 if pat.connected: 3344 self.__patient_notebook.identity = pat 3345 else: 3346 self.__patient_notebook.identity = None 3347 self.__patient_notebook.refresh() 3348 return True
3349 #============================================================ 3350 #def create_identity_from_dtd(dtd=None): 3351 # """ 3352 # Register a new patient, given the data supplied in the 3353 # Data Transfer Dictionary object. 3354 # 3355 # @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3356 # supplied data. 3357 # @type basic_details_DTD A cFormDTD instance. 3358 # """ 3359 # new_identity = gmPerson.create_identity ( 3360 # gender = dtd['gender'], 3361 # dob = dtd['dob'].get_pydt(), 3362 # lastnames = dtd['lastnames'], 3363 # firstnames = dtd['firstnames'] 3364 # ) 3365 # if new_identity is None: 3366 # _log.error('cannot create identity from %s' % str(dtd)) 3367 # return None 3368 # _log.debug('identity created: %s' % new_identity) 3369 # 3370 # if dtd['comment'] is not None: 3371 # if dtd['comment'].strip() != u'': 3372 # name = new_identity.get_active_name() 3373 # name['comment'] = dtd['comment'] 3374 # name.save_payload() 3375 # 3376 # return new_identity 3377 #============================================================ 3378 #def update_identity_from_dtd(identity, dtd=None): 3379 # """ 3380 # Update patient details with data supplied by 3381 # Data Transfer Dictionary object. 3382 # 3383 # @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3384 # supplied data. 3385 # @type basic_details_DTD A cFormDTD instance. 3386 # """ 3387 # # identity 3388 # if identity['gender'] != dtd['gender']: 3389 # identity['gender'] = dtd['gender'] 3390 # if identity['dob'] != dtd['dob'].get_pydt(): 3391 # identity['dob'] = dtd['dob'].get_pydt() 3392 # if len(dtd['title']) > 0 and identity['title'] != dtd['title']: 3393 # identity['title'] = dtd['title'] 3394 # # FIXME: error checking 3395 # # FIXME: we need a trigger to update the values of the 3396 # # view, identity['keys'], eg. lastnames and firstnames 3397 # # are not refreshed. 3398 # identity.save_payload() 3399 # 3400 # # names 3401 # # FIXME: proper handling of "active" 3402 # if identity['firstnames'] != dtd['firstnames'] or identity['lastnames'] != dtd['lastnames']: 3403 # new_name = identity.add_name(firstnames = dtd['firstnames'], lastnames = dtd['lastnames'], active = True) 3404 # # nickname 3405 # if len(dtd['nick']) > 0 and identity['preferred'] != dtd['nick']: 3406 # identity.set_nickname(nickname = dtd['nick']) 3407 # 3408 # return True 3409 #============================================================ 3410 #def link_contacts_from_dtd(identity, dtd=None): 3411 # """ 3412 # Update patient details with data supplied by 3413 # Data Transfer Dictionary object. 3414 # 3415 # @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3416 # supplied data. 3417 # @type basic_details_DTD A cFormDTD instance. 3418 # """ 3419 # lng = len ( 3420 # dtd['address_number'].strip() + 3421 # dtd['street'].strip() + 3422 # dtd['zip_code'].strip() + 3423 # dtd['town'].strip() + 3424 # dtd['state'].strip() + 3425 # dtd['country'].strip() 3426 # ) 3427 # # FIXME: improve error checking 3428 # if lng > 5: 3429 # # FIXME: support address type 3430 # success = identity.link_address ( 3431 # number = dtd['address_number'].strip(), 3432 # street = dtd['street'].strip(), 3433 # postcode = dtd['zip_code'].strip(), 3434 # urb = dtd['town'].strip(), 3435 # state = dtd['state'].strip(), 3436 # country = dtd['country'].strip() 3437 # ) 3438 # if not success: 3439 # gmDispatcher.send(signal='statustext', msg = _('Cannot add patient address.')) 3440 # else: 3441 # gmDispatcher.send(signal='statustext', msg = _('Cannot add patient address. Missing fields.')) 3442 # 3443 # if len(dtd['phone']) > 0: 3444 # identity.link_comm_channel ( 3445 # comm_medium = 'homephone', 3446 # url = dtd['phone'], 3447 # is_confidential = False 3448 # ) 3449 # 3450 # # FIXME: error checking 3451 ## identity.save_payload() 3452 # return True 3453 #============================================================ 3454 #def link_occupation_from_dtd(identity, dtd=None): 3455 # """ 3456 # Update patient details with data supplied by 3457 # Data Transfer Dictionary object. 3458 # 3459 # @param basic_details_DTD Data Transfer Dictionary encapsulating all the 3460 # supplied data. 3461 # @type basic_details_DTD A cFormDTD instance. 3462 # """ 3463 # identity.link_occupation(occupation = dtd['occupation']) 3464 # 3465 # return True 3466 #============================================================
3467 -class TestWizardPanel(wx.Panel):
3468 """ 3469 Utility class to test the new patient wizard. 3470 """ 3471 #--------------------------------------------------------
3472 - def __init__(self, parent, id):
3473 """ 3474 Create a new instance of TestPanel. 3475 @param parent The parent widget 3476 @type parent A wx.Window instance 3477 """ 3478 wx.Panel.__init__(self, parent, id) 3479 wizard = cNewPatientWizard(self) 3480 print wizard.RunWizard()
3481 #============================================================ 3482 if __name__ == "__main__": 3483 3484 #--------------------------------------------------------
3485 - def test_zipcode_prw():
3486 app = wx.PyWidgetTester(size = (200, 50)) 3487 pw = cZipcodePhraseWheel(app.frame, -1) 3488 app.frame.Show(True) 3489 app.MainLoop()
3490 #--------------------------------------------------------
3491 - def test_state_prw():
3492 app = wx.PyWidgetTester(size = (200, 50)) 3493 pw = cStateSelectionPhraseWheel(app.frame, -1) 3494 # pw.set_context(context = u'zip', val = u'04318') 3495 # pw.set_context(context = u'country', val = u'Deutschland') 3496 app.frame.Show(True) 3497 app.MainLoop()
3498 #--------------------------------------------------------
3499 - def test_urb_prw():
3500 app = wx.PyWidgetTester(size = (200, 50)) 3501 pw = cUrbPhraseWheel(app.frame, -1) 3502 app.frame.Show(True) 3503 pw.set_context(context = u'zip', val = u'04317') 3504 app.MainLoop()
3505 #--------------------------------------------------------
3506 - def test_suburb_prw():
3507 app = wx.PyWidgetTester(size = (200, 50)) 3508 pw = cSuburbPhraseWheel(app.frame, -1) 3509 app.frame.Show(True) 3510 app.MainLoop()
3511 #--------------------------------------------------------
3512 - def test_address_type_prw():
3513 app = wx.PyWidgetTester(size = (200, 50)) 3514 pw = cAddressTypePhraseWheel(app.frame, -1) 3515 app.frame.Show(True) 3516 app.MainLoop()
3517 #--------------------------------------------------------
3518 - def test_address_prw():
3519 app = wx.PyWidgetTester(size = (200, 50)) 3520 pw = cAddressPhraseWheel(app.frame, -1) 3521 app.frame.Show(True) 3522 app.MainLoop()
3523 #--------------------------------------------------------
3524 - def test_street_prw():
3525 app = wx.PyWidgetTester(size = (200, 50)) 3526 pw = cStreetPhraseWheel(app.frame, -1) 3527 # pw.set_context(context = u'zip', val = u'04318') 3528 app.frame.Show(True) 3529 app.MainLoop()
3530 #--------------------------------------------------------
3531 - def test_organizer_pnl():
3532 app = wx.PyWidgetTester(size = (600, 400)) 3533 app.SetWidget(cKOrganizerSchedulePnl) 3534 app.MainLoop()
3535 #--------------------------------------------------------
3536 - def test_person_names_pnl():
3537 app = wx.PyWidgetTester(size = (600, 400)) 3538 widget = cPersonNamesManagerPnl(app.frame, -1) 3539 widget.identity = activate_patient() 3540 app.frame.Show(True) 3541 app.MainLoop()
3542 #--------------------------------------------------------
3543 - def test_person_ids_pnl():
3544 app = wx.PyWidgetTester(size = (600, 400)) 3545 widget = cPersonIDsManagerPnl(app.frame, -1) 3546 widget.identity = activate_patient() 3547 app.frame.Show(True) 3548 app.MainLoop()
3549 #--------------------------------------------------------
3550 - def test_pat_ids_pnl():
3551 app = wx.PyWidgetTester(size = (600, 400)) 3552 widget = cPersonIdentityManagerPnl(app.frame, -1) 3553 widget.identity = activate_patient() 3554 app.frame.Show(True) 3555 app.MainLoop()
3556 #--------------------------------------------------------
3557 - def test_name_ea_pnl():
3558 app = wx.PyWidgetTester(size = (600, 400)) 3559 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name()) 3560 app.MainLoop()
3561 #--------------------------------------------------------
3562 - def test_address_ea_pnl():
3563 app = wx.PyWidgetTester(size = (600, 400)) 3564 app.SetWidget(cAddressEditAreaPnl, address = gmDemographicRecord.cAddress(aPK_obj = 1)) 3565 app.MainLoop()
3566 #--------------------------------------------------------
3567 - def test_person_adrs_pnl():
3568 app = wx.PyWidgetTester(size = (600, 400)) 3569 widget = cPersonAddressesManagerPnl(app.frame, -1) 3570 widget.identity = activate_patient() 3571 app.frame.Show(True) 3572 app.MainLoop()
3573 #--------------------------------------------------------
3574 - def test_person_comms_pnl():
3575 app = wx.PyWidgetTester(size = (600, 400)) 3576 widget = cPersonCommsManagerPnl(app.frame, -1) 3577 widget.identity = activate_patient() 3578 app.frame.Show(True) 3579 app.MainLoop()
3580 #--------------------------------------------------------
3581 - def test_pat_contacts_pnl():
3582 app = wx.PyWidgetTester(size = (600, 400)) 3583 widget = cPersonContactsManagerPnl(app.frame, -1) 3584 widget.identity = activate_patient() 3585 app.frame.Show(True) 3586 app.MainLoop()
3587 #--------------------------------------------------------
3588 - def test_cPersonDemographicsEditorNb():
3589 app = wx.PyWidgetTester(size = (600, 400)) 3590 widget = cPersonDemographicsEditorNb(app.frame, -1) 3591 widget.identity = activate_patient() 3592 widget.refresh() 3593 app.frame.Show(True) 3594 app.MainLoop()
3595 #--------------------------------------------------------
3596 - def activate_patient():
3597 patient = gmPerson.ask_for_patient() 3598 if patient is None: 3599 print "No patient. Exiting gracefully..." 3600 sys.exit(0) 3601 from Gnumed.wxpython import gmPatSearchWidgets 3602 gmPatSearchWidgets.set_active_patient(patient=patient) 3603 return patient
3604 #-------------------------------------------------------- 3605 if len(sys.argv) > 1 and sys.argv[1] == 'test': 3606 3607 gmI18N.activate_locale() 3608 gmI18N.install_domain(domain='gnumed') 3609 gmPG2.get_connection() 3610 3611 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields) 3612 3613 # app = wx.PyWidgetTester(size = (400, 300)) 3614 # app.SetWidget(cNotebookedPatEditionPanel, -1) 3615 # app.SetWidget(TestWizardPanel, -1) 3616 # app.frame.Show(True) 3617 # app.MainLoop() 3618 3619 # phrasewheels 3620 # test_zipcode_prw() 3621 # test_state_prw() 3622 # test_street_prw() 3623 # test_organizer_pnl() 3624 #test_address_type_prw() 3625 #test_suburb_prw() 3626 test_urb_prw() 3627 #test_address_prw() 3628 3629 # contacts related widgets 3630 #test_address_ea_pnl() 3631 #test_person_adrs_pnl() 3632 #test_person_comms_pnl() 3633 #test_pat_contacts_pnl() 3634 3635 # identity related widgets 3636 #test_person_names_pnl() 3637 #test_person_ids_pnl() 3638 #test_pat_ids_pnl() 3639 #test_name_ea_pnl() 3640 3641 #test_cPersonDemographicsEditorNb() 3642 3643 #============================================================ 3644