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 sys, os, codecs, re as regex, 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, gmPersonSearch 
  21  from Gnumed.wxpython import gmPhraseWheel, gmGuiHelpers, gmDateTimeInput 
  22  from Gnumed.wxpython import gmRegetMixin, gmDataMiningWidgets, gmListWidgets, gmEditArea 
  23  from Gnumed.wxpython import gmAuthWidgets, gmPersonContactWidgets 
  24   
  25   
  26  # constant defs 
  27  _log = logging.getLogger('gm.ui') 
  28   
  29   
  30  try: 
  31          _('dummy-no-need-to-translate-but-make-epydoc-happy') 
  32  except NameError: 
  33          _ = lambda x:x 
  34   
  35  #============================================================ 
  36  #============================================================ 
37 -class cKOrganizerSchedulePnl(gmDataMiningWidgets.cPatientListingPnl):
38
39 - def __init__(self, *args, **kwargs):
40 41 kwargs['message'] = _("Today's KOrganizer appointments ...") 42 kwargs['button_defs'] = [ 43 {'label': _('Reload'), 'tooltip': _('Reload appointments from KOrganizer')}, 44 {'label': u''}, 45 {'label': u''}, 46 {'label': u''}, 47 {'label': u'KOrganizer', 'tooltip': _('Launch KOrganizer')} 48 ] 49 gmDataMiningWidgets.cPatientListingPnl.__init__(self, *args, **kwargs) 50 51 self.fname = os.path.expanduser(os.path.join('~', '.gnumed', 'tmp', 'korganizer2gnumed.csv')) 52 self.reload_cmd = 'konsolekalendar --view --export-type csv --export-file %s' % self.fname
53 54 #--------------------------------------------------------
55 - def _on_BTN_1_pressed(self, event):
56 """Reload appointments from KOrganizer.""" 57 self.reload_appointments()
58 #--------------------------------------------------------
59 - def _on_BTN_5_pressed(self, event):
60 """Reload appointments from KOrganizer.""" 61 found, cmd = gmShellAPI.detect_external_binary(binary = 'korganizer') 62 63 if not found: 64 gmDispatcher.send(signal = 'statustext', msg = _('KOrganizer is not installed.'), beep = True) 65 return 66 67 gmShellAPI.run_command_in_shell(command = cmd, blocking = False)
68 #--------------------------------------------------------
69 - def reload_appointments(self):
70 try: os.remove(self.fname) 71 except OSError: pass 72 gmShellAPI.run_command_in_shell(command=self.reload_cmd, blocking=True) 73 try: 74 csv_file = codecs.open(self.fname , mode = 'rU', encoding = 'utf8', errors = 'replace') 75 except IOError: 76 gmDispatcher.send(signal = u'statustext', msg = _('Cannot access KOrganizer transfer file [%s]') % self.fname, beep = True) 77 return 78 79 csv_lines = gmTools.unicode_csv_reader ( 80 csv_file, 81 delimiter = ',' 82 ) 83 # start_date, start_time, end_date, end_time, title (patient), ort, comment, UID 84 self._LCTRL_items.set_columns ([ 85 _('Place'), 86 _('Start'), 87 u'', 88 u'', 89 _('Patient'), 90 _('Comment') 91 ]) 92 items = [] 93 data = [] 94 for line in csv_lines: 95 items.append([line[5], line[0], line[1], line[3], line[4], line[6]]) 96 data.append([line[4], line[7]]) 97 98 self._LCTRL_items.set_string_items(items = items) 99 self._LCTRL_items.set_column_widths() 100 self._LCTRL_items.set_data(data = data) 101 self._LCTRL_items.patient_key = 0
102 #-------------------------------------------------------- 103 # notebook plugins API 104 #--------------------------------------------------------
105 - def repopulate_ui(self):
106 self.reload_appointments()
107 #============================================================ 108 # occupation related widgets / functions 109 #============================================================
110 -def edit_occupation():
111 112 pat = gmPerson.gmCurrentPatient() 113 curr_jobs = pat.get_occupations() 114 if len(curr_jobs) > 0: 115 old_job = curr_jobs[0]['l10n_occupation'] 116 update = curr_jobs[0]['modified_when'].strftime('%m/%Y') 117 else: 118 old_job = u'' 119 update = u'' 120 121 msg = _( 122 'Please enter the primary occupation of the patient.\n' 123 '\n' 124 'Currently recorded:\n' 125 '\n' 126 ' %s (last updated %s)' 127 ) % (old_job, update) 128 129 new_job = wx.GetTextFromUser ( 130 message = msg, 131 caption = _('Editing primary occupation'), 132 default_value = old_job, 133 parent = None 134 ) 135 if new_job.strip() == u'': 136 return 137 138 for job in curr_jobs: 139 # unlink all but the new job 140 if job['l10n_occupation'] != new_job: 141 pat.unlink_occupation(occupation = job['l10n_occupation']) 142 # and link the new one 143 pat.link_occupation(occupation = new_job)
144 145 #------------------------------------------------------------
146 -class cOccupationPhraseWheel(gmPhraseWheel.cPhraseWheel):
147
148 - def __init__(self, *args, **kwargs):
149 query = u"select distinct name, _(name) from dem.occupation where _(name) %(fragment_condition)s" 150 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 151 mp.setThresholds(1, 3, 5) 152 gmPhraseWheel.cPhraseWheel.__init__ ( 153 self, 154 *args, 155 **kwargs 156 ) 157 self.SetToolTipString(_("Type or select an occupation.")) 158 self.capitalisation_mode = gmTools.CAPS_FIRST 159 self.matcher = mp
160 161 #============================================================ 162 # identity widgets / functions 163 #============================================================
164 -def disable_identity(identity=None):
165 # ask user for assurance 166 go_ahead = gmGuiHelpers.gm_show_question ( 167 _('Are you sure you really, positively want\n' 168 'to disable the following person ?\n' 169 '\n' 170 ' %s %s %s\n' 171 ' born %s\n' 172 '\n' 173 '%s\n' 174 ) % ( 175 identity['firstnames'], 176 identity['lastnames'], 177 identity['gender'], 178 identity['dob'], 179 gmTools.bool2subst ( 180 identity.is_patient, 181 _('This patient DID receive care.'), 182 _('This person did NOT receive care.') 183 ) 184 ), 185 _('Disabling person') 186 ) 187 if not go_ahead: 188 return True 189 190 # get admin connection 191 conn = gmAuthWidgets.get_dbowner_connection ( 192 procedure = _('Disabling patient') 193 ) 194 # - user cancelled 195 if conn is False: 196 return True 197 # - error 198 if conn is None: 199 return False 200 201 # now disable patient 202 gmPG2.run_rw_queries(queries = [{'cmd': u"update dem.identity set deleted=True where pk=%s", 'args': [identity['pk_identity']]}]) 203 204 return True
205 206 #------------------------------------------------------------ 207 # phrasewheels 208 #------------------------------------------------------------
209 -class cLastnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
210
211 - def __init__(self, *args, **kwargs):
212 query = u"select distinct lastnames, lastnames from dem.names where lastnames %(fragment_condition)s order by lastnames limit 25" 213 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 214 mp.setThresholds(3, 5, 9) 215 gmPhraseWheel.cPhraseWheel.__init__ ( 216 self, 217 *args, 218 **kwargs 219 ) 220 self.SetToolTipString(_("Type or select a last name (family name/surname).")) 221 self.capitalisation_mode = gmTools.CAPS_NAMES 222 self.matcher = mp
223 #------------------------------------------------------------
224 -class cFirstnamePhraseWheel(gmPhraseWheel.cPhraseWheel):
225
226 - def __init__(self, *args, **kwargs):
227 query = u""" 228 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 229 union 230 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 231 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 232 mp.setThresholds(3, 5, 9) 233 gmPhraseWheel.cPhraseWheel.__init__ ( 234 self, 235 *args, 236 **kwargs 237 ) 238 self.SetToolTipString(_("Type or select a first name (forename/Christian name/given name).")) 239 self.capitalisation_mode = gmTools.CAPS_NAMES 240 self.matcher = mp
241 #------------------------------------------------------------
242 -class cNicknamePhraseWheel(gmPhraseWheel.cPhraseWheel):
243
244 - def __init__(self, *args, **kwargs):
245 query = u""" 246 (select distinct preferred, preferred from dem.names where preferred %(fragment_condition)s order by preferred limit 20) 247 union 248 (select distinct firstnames, firstnames from dem.names where firstnames %(fragment_condition)s order by firstnames limit 20) 249 union 250 (select distinct name, name from dem.name_gender_map where name %(fragment_condition)s order by name limit 20)""" 251 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 252 mp.setThresholds(3, 5, 9) 253 gmPhraseWheel.cPhraseWheel.__init__ ( 254 self, 255 *args, 256 **kwargs 257 ) 258 self.SetToolTipString(_("Type or select an alias (nick name, preferred name, call name, warrior name, artist name).")) 259 # nicknames CAN start with lower case ! 260 #self.capitalisation_mode = gmTools.CAPS_NAMES 261 self.matcher = mp
262 #------------------------------------------------------------
263 -class cTitlePhraseWheel(gmPhraseWheel.cPhraseWheel):
264
265 - def __init__(self, *args, **kwargs):
266 query = u"select distinct title, title from dem.identity where title %(fragment_condition)s" 267 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 268 mp.setThresholds(1, 3, 9) 269 gmPhraseWheel.cPhraseWheel.__init__ ( 270 self, 271 *args, 272 **kwargs 273 ) 274 self.SetToolTipString(_("Type or select a title. Note that the title applies to the person, not to a particular name !")) 275 self.matcher = mp
276 #------------------------------------------------------------
277 -class cGenderSelectionPhraseWheel(gmPhraseWheel.cPhraseWheel):
278 """Let user select a gender.""" 279 280 _gender_map = None 281
282 - def __init__(self, *args, **kwargs):
283 284 if cGenderSelectionPhraseWheel._gender_map is None: 285 cmd = u""" 286 select tag, l10n_label, sort_weight 287 from dem.v_gender_labels 288 order by sort_weight desc""" 289 rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd}], get_col_idx=True) 290 cGenderSelectionPhraseWheel._gender_map = {} 291 for gender in rows: 292 cGenderSelectionPhraseWheel._gender_map[gender[idx['tag']]] = { 293 'data': gender[idx['tag']], 294 'label': gender[idx['l10n_label']], 295 'weight': gender[idx['sort_weight']] 296 } 297 298 mp = gmMatchProvider.cMatchProvider_FixedList(aSeq = cGenderSelectionPhraseWheel._gender_map.values()) 299 mp.setThresholds(1, 1, 3) 300 301 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 302 self.selection_only = True 303 self.matcher = mp 304 self.picklist_delay = 50
305 #------------------------------------------------------------
306 -class cExternalIDTypePhraseWheel(gmPhraseWheel.cPhraseWheel):
307
308 - def __init__(self, *args, **kwargs):
309 query = u""" 310 select distinct pk, (name || coalesce(' (%s ' || issuer || ')', '')) as label 311 from dem.enum_ext_id_types 312 where name %%(fragment_condition)s 313 order by label limit 25""" % _('issued by') 314 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 315 mp.setThresholds(1, 3, 5) 316 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 317 self.SetToolTipString(_("Enter or select a type for the external ID.")) 318 self.matcher = mp
319 #------------------------------------------------------------
320 -class cExternalIDIssuerPhraseWheel(gmPhraseWheel.cPhraseWheel):
321
322 - def __init__(self, *args, **kwargs):
323 query = u""" 324 select distinct issuer, issuer 325 from dem.enum_ext_id_types 326 where issuer %(fragment_condition)s 327 order by issuer limit 25""" 328 mp = gmMatchProvider.cMatchProvider_SQL2(queries=query) 329 mp.setThresholds(1, 3, 5) 330 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs) 331 self.SetToolTipString(_("Type or select an ID issuer.")) 332 self.capitalisation_mode = gmTools.CAPS_FIRST 333 self.matcher = mp
334 #------------------------------------------------------------ 335 # edit areas 336 #------------------------------------------------------------ 337 from Gnumed.wxGladeWidgets import wxgExternalIDEditAreaPnl 338
339 -class cExternalIDEditAreaPnl(wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl):
340 """An edit area for editing/creating external IDs. 341 342 Does NOT act on/listen to the current patient. 343 """
344 - def __init__(self, *args, **kwargs):
345 346 try: 347 self.ext_id = kwargs['external_id'] 348 del kwargs['external_id'] 349 except: 350 self.ext_id = None 351 352 wxgExternalIDEditAreaPnl.wxgExternalIDEditAreaPnl.__init__(self, *args, **kwargs) 353 354 self.identity = None 355 356 self.__register_events() 357 358 self.refresh()
359 #-------------------------------------------------------- 360 # external API 361 #--------------------------------------------------------
362 - def refresh(self, ext_id=None):
363 if ext_id is not None: 364 self.ext_id = ext_id 365 366 if self.ext_id is not None: 367 self._PRW_type.SetText(value = self.ext_id['name'], data = self.ext_id['pk_type']) 368 self._TCTRL_value.SetValue(self.ext_id['value']) 369 self._PRW_issuer.SetText(self.ext_id['issuer']) 370 self._TCTRL_comment.SetValue(gmTools.coalesce(self.ext_id['comment'], u''))
371 # FIXME: clear fields 372 # else: 373 # pass 374 #--------------------------------------------------------
375 - def save(self):
376 377 if not self.__valid_for_save(): 378 return False 379 380 # strip out " (issued by ...)" added by phrasewheel 381 type = regex.split(' \(%s .+\)$' % _('issued by'), self._PRW_type.GetValue().strip(), 1)[0] 382 383 # add new external ID 384 if self.ext_id is None: 385 self.identity.add_external_id ( 386 type_name = type, 387 value = self._TCTRL_value.GetValue().strip(), 388 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''), 389 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 390 ) 391 # edit old external ID 392 else: 393 self.identity.update_external_id ( 394 pk_id = self.ext_id['pk_id'], 395 type = type, 396 value = self._TCTRL_value.GetValue().strip(), 397 issuer = gmTools.none_if(self._PRW_issuer.GetValue().strip(), u''), 398 comment = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 399 ) 400 401 return True
402 #-------------------------------------------------------- 403 # internal helpers 404 #--------------------------------------------------------
405 - def __register_events(self):
406 self._PRW_type.add_callback_on_lose_focus(self._on_type_set)
407 #--------------------------------------------------------
408 - def _on_type_set(self):
409 """Set the issuer according to the selected type. 410 411 Matches are fetched from existing records in backend. 412 """ 413 pk_curr_type = self._PRW_type.GetData() 414 if pk_curr_type is None: 415 return True 416 rows, idx = gmPG2.run_ro_queries(queries = [{ 417 'cmd': u"select issuer from dem.enum_ext_id_types where pk = %s", 418 'args': [pk_curr_type] 419 }]) 420 if len(rows) == 0: 421 return True 422 wx.CallAfter(self._PRW_issuer.SetText, rows[0][0]) 423 return True
424 #--------------------------------------------------------
425 - def __valid_for_save(self):
426 427 no_errors = True 428 429 # do not test .GetData() because adding external IDs 430 # will create types if necessary 431 # if self._PRW_type.GetData() is None: 432 if self._PRW_type.GetValue().strip() == u'': 433 self._PRW_type.SetBackgroundColour('pink') 434 self._PRW_type.SetFocus() 435 self._PRW_type.Refresh() 436 else: 437 self._PRW_type.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 438 self._PRW_type.Refresh() 439 440 if self._TCTRL_value.GetValue().strip() == u'': 441 self._TCTRL_value.SetBackgroundColour('pink') 442 self._TCTRL_value.SetFocus() 443 self._TCTRL_value.Refresh() 444 no_errors = False 445 else: 446 self._TCTRL_value.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 447 self._TCTRL_value.Refresh() 448 449 return no_errors
450 #------------------------------------------------------------ 451 from Gnumed.wxGladeWidgets import wxgIdentityEAPnl 452
453 -class cIdentityEAPnl(wxgIdentityEAPnl.wxgIdentityEAPnl, gmEditArea.cGenericEditAreaMixin):
454 """An edit area for editing/creating title/gender/dob/dod etc.""" 455
456 - def __init__(self, *args, **kwargs):
457 458 try: 459 data = kwargs['identity'] 460 del kwargs['identity'] 461 except KeyError: 462 data = None 463 464 wxgIdentityEAPnl.wxgIdentityEAPnl.__init__(self, *args, **kwargs) 465 gmEditArea.cGenericEditAreaMixin.__init__(self) 466 467 self.mode = 'new' 468 self.data = data 469 if data is not None: 470 self.mode = 'edit'
471 472 # self.__init_ui() 473 #---------------------------------------------------------------- 474 # def __init_ui(self): 475 # # adjust phrasewheels etc 476 #---------------------------------------------------------------- 477 # generic Edit Area mixin API 478 #----------------------------------------------------------------
479 - def _valid_for_save(self):
480 481 has_error = False 482 483 if self._PRW_gender.GetData() is None: 484 self._PRW_gender.SetFocus() 485 has_error = True 486 487 if not self._PRW_dob.is_valid_timestamp(): 488 val = self._PRW_dob.GetValue().strip() 489 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 490 self._PRW_dob.SetBackgroundColour('pink') 491 self._PRW_dob.Refresh() 492 self._PRW_dob.SetFocus() 493 has_error = True 494 else: 495 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 496 self._PRW_dob.Refresh() 497 498 if not self._DP_dod.is_valid_timestamp(allow_none = True, invalid_as_none = True): 499 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 500 self._DP_dod.SetFocus() 501 has_error = True 502 503 return (has_error is False)
504 #----------------------------------------------------------------
505 - def _save_as_new(self):
506 # not intended to be used 507 return False
508 #----------------------------------------------------------------
509 - def _save_as_update(self):
510 511 self.data['gender'] = self._PRW_gender.GetData() 512 513 if self._PRW_dob.GetValue().strip() == u'': 514 self.data['dob'] = None 515 else: 516 self.data['dob'] = self._PRW_dob.GetData().get_pydt() 517 518 self.data['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 519 self.data['deceased'] = self._DP_dod.GetValue(as_pydt = True, invalid_as_none = True) 520 self.data['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 521 522 self.data.save() 523 return True
524 #----------------------------------------------------------------
525 - def _refresh_as_new(self):
526 pass
527 #----------------------------------------------------------------
528 - def _refresh_from_existing(self):
529 530 self._LBL_info.SetLabel(u'ID: #%s' % ( 531 self.data.ID 532 # FIXME: add 'deleted' status 533 )) 534 self._PRW_dob.SetText ( 535 value = self.data.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()), 536 data = self.data['dob'] 537 ) 538 self._DP_dod.SetValue(self.data['deceased']) 539 self._PRW_gender.SetData(self.data['gender']) 540 #self._PRW_ethnicity.SetValue() 541 self._PRW_title.SetText(gmTools.coalesce(self.data['title'], u'')) 542 self._TCTRL_comment.SetValue(gmTools.coalesce(self.data['comment'], u''))
543 #----------------------------------------------------------------
545 pass
546 547 #------------------------------------------------------------ 548 from Gnumed.wxGladeWidgets import wxgNameGenderDOBEditAreaPnl 549
550 -class cNameGenderDOBEditAreaPnl(wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl):
551 """An edit area for editing/creating name/gender/dob. 552 553 Does NOT act on/listen to the current patient. 554 """
555 - def __init__(self, *args, **kwargs):
556 557 self.__name = kwargs['name'] 558 del kwargs['name'] 559 self.__identity = gmPerson.cIdentity(aPK_obj = self.__name['pk_identity']) 560 561 wxgNameGenderDOBEditAreaPnl.wxgNameGenderDOBEditAreaPnl.__init__(self, *args, **kwargs) 562 563 self.__register_interests() 564 self.refresh()
565 #-------------------------------------------------------- 566 # external API 567 #--------------------------------------------------------
568 - def refresh(self):
569 if self.__name is None: 570 return 571 572 self._PRW_title.SetText(gmTools.coalesce(self.__name['title'], u'')) 573 self._PRW_firstname.SetText(self.__name['firstnames']) 574 self._PRW_lastname.SetText(self.__name['lastnames']) 575 self._PRW_nick.SetText(gmTools.coalesce(self.__name['preferred'], u'')) 576 self._PRW_dob.SetText ( 577 value = self.__identity.get_formatted_dob(format = '%Y-%m-%d %H:%M', encoding = gmI18N.get_encoding()), 578 data = self.__identity['dob'] 579 ) 580 self._PRW_gender.SetData(self.__name['gender']) 581 self._CHBOX_active.SetValue(self.__name['active_name']) 582 self._DP_dod.SetValue(self.__identity['deceased']) 583 self._TCTRL_comment.SetValue(gmTools.coalesce(self.__name['comment'], u''))
584 # FIXME: clear fields 585 # else: 586 # pass 587 #--------------------------------------------------------
588 - def save(self):
589 590 if not self.__valid_for_save(): 591 return False 592 593 self.__identity['gender'] = self._PRW_gender.GetData() 594 if self._PRW_dob.GetValue().strip() == u'': 595 self.__identity['dob'] = None 596 else: 597 self.__identity['dob'] = self._PRW_dob.GetData().get_pydt() 598 self.__identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip(), u'') 599 self.__identity['deceased'] = self._DP_dod.GetValue(as_pydt = True, invalid_as_none = True) 600 self.__identity.save_payload() 601 602 active = self._CHBOX_active.GetValue() 603 first = self._PRW_firstname.GetValue().strip() 604 last = self._PRW_lastname.GetValue().strip() 605 old_nick = self.__name['preferred'] 606 607 # is it a new name ? 608 old_name = self.__name['firstnames'] + self.__name['lastnames'] 609 if (first + last) != old_name: 610 self.__name = self.__identity.add_name(first, last, active) 611 612 self.__name['active_name'] = active 613 self.__name['preferred'] = gmTools.none_if(self._PRW_nick.GetValue().strip(), u'') 614 self.__name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 615 616 self.__name.save_payload() 617 618 return True
619 #-------------------------------------------------------- 620 # event handling 621 #--------------------------------------------------------
622 - def __register_interests(self):
623 self._PRW_firstname.add_callback_on_lose_focus(self._on_name_set)
624 #--------------------------------------------------------
625 - def _on_name_set(self):
626 """Set the gender according to entered firstname. 627 628 Matches are fetched from existing records in backend. 629 """ 630 firstname = self._PRW_firstname.GetValue().strip() 631 if firstname == u'': 632 return True 633 rows, idx = gmPG2.run_ro_queries(queries = [{ 634 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 635 'args': [firstname] 636 }]) 637 if len(rows) == 0: 638 return True 639 wx.CallAfter(self._PRW_gender.SetData, rows[0][0]) 640 return True
641 #-------------------------------------------------------- 642 # internal helpers 643 #--------------------------------------------------------
644 - def __valid_for_save(self):
645 646 has_error = False 647 648 if self._PRW_gender.GetData() is None: 649 self._PRW_gender.SetBackgroundColour('pink') 650 self._PRW_gender.Refresh() 651 self._PRW_gender.SetFocus() 652 has_error = True 653 else: 654 self._PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 655 self._PRW_gender.Refresh() 656 657 if not self._PRW_dob.is_valid_timestamp(): 658 val = self._PRW_dob.GetValue().strip() 659 gmDispatcher.send(signal = u'statustext', msg = _('Cannot parse <%s> into proper timestamp.') % val) 660 self._PRW_dob.SetBackgroundColour('pink') 661 self._PRW_dob.Refresh() 662 self._PRW_dob.SetFocus() 663 has_error = True 664 else: 665 self._PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 666 self._PRW_dob.Refresh() 667 668 if not self._DP_dod.is_valid_timestamp(allow_none = True, invalid_as_none = True): 669 gmDispatcher.send(signal = u'statustext', msg = _('Invalid date of death.')) 670 self._DP_dod.SetBackgroundColour('pink') 671 self._DP_dod.Refresh() 672 self._DP_dod.SetFocus() 673 has_error = True 674 else: 675 self._DP_dod.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 676 self._DP_dod.Refresh() 677 678 if self._PRW_lastname.GetValue().strip() == u'': 679 self._PRW_lastname.SetBackgroundColour('pink') 680 self._PRW_lastname.Refresh() 681 self._PRW_lastname.SetFocus() 682 has_error = True 683 else: 684 self._PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 685 self._PRW_lastname.Refresh() 686 687 if self._PRW_firstname.GetValue().strip() == u'': 688 self._PRW_firstname.SetBackgroundColour('pink') 689 self._PRW_firstname.Refresh() 690 self._PRW_firstname.SetFocus() 691 has_error = True 692 else: 693 self._PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 694 self._PRW_firstname.Refresh() 695 696 return (has_error is False)
697 #------------------------------------------------------------ 698 # list manager 699 #------------------------------------------------------------
700 -class cPersonNamesManagerPnl(gmListWidgets.cGenericListManagerPnl):
701 """A list for managing a person's names. 702 703 Does NOT act on/listen to the current patient. 704 """
705 - def __init__(self, *args, **kwargs):
706 707 try: 708 self.__identity = kwargs['identity'] 709 del kwargs['identity'] 710 except KeyError: 711 self.__identity = None 712 713 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 714 715 self.new_callback = self._add_name 716 self.edit_callback = self._edit_name 717 self.delete_callback = self._del_name 718 self.refresh_callback = self.refresh 719 720 self.__init_ui() 721 self.refresh()
722 #-------------------------------------------------------- 723 # external API 724 #--------------------------------------------------------
725 - def refresh(self, *args, **kwargs):
726 if self.__identity is None: 727 self._LCTRL_items.set_string_items() 728 return 729 730 names = self.__identity.get_names() 731 self._LCTRL_items.set_string_items ( 732 items = [ [ 733 gmTools.bool2str(n['active_name'], 'X', ''), 734 n['lastnames'], 735 n['firstnames'], 736 gmTools.coalesce(n['preferred'], u''), 737 gmTools.coalesce(n['comment'], u'') 738 ] for n in names ] 739 ) 740 self._LCTRL_items.set_column_widths() 741 self._LCTRL_items.set_data(data = names)
742 #-------------------------------------------------------- 743 # internal helpers 744 #--------------------------------------------------------
745 - def __init_ui(self):
746 self._LCTRL_items.set_columns(columns = [ 747 _('Active'), 748 _('Lastname'), 749 _('Firstname(s)'), 750 _('Preferred Name'), 751 _('Comment') 752 ])
753 #--------------------------------------------------------
754 - def _add_name(self):
755 ea = cNameGenderDOBEditAreaPnl(self, -1, name = self.__identity.get_active_name()) 756 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 757 dlg.SetTitle(_('Adding new name')) 758 if dlg.ShowModal() == wx.ID_OK: 759 dlg.Destroy() 760 return True 761 dlg.Destroy() 762 return False
763 #--------------------------------------------------------
764 - def _edit_name(self, name):
765 ea = cNameGenderDOBEditAreaPnl(self, -1, name = name) 766 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 767 dlg.SetTitle(_('Editing name')) 768 if dlg.ShowModal() == wx.ID_OK: 769 dlg.Destroy() 770 return True 771 dlg.Destroy() 772 return False
773 #--------------------------------------------------------
774 - def _del_name(self, name):
775 776 if len(self.__identity.get_names()) == 1: 777 gmDispatcher.send(signal = u'statustext', msg = _('Cannot delete the only name of a person.'), beep = True) 778 return False 779 780 go_ahead = gmGuiHelpers.gm_show_question ( 781 _( 'It is often advisable to keep old names around and\n' 782 'just create a new "currently active" name.\n' 783 '\n' 784 'This allows finding the patient by both the old\n' 785 'and the new name (think before/after marriage).\n' 786 '\n' 787 'Do you still want to really delete\n' 788 "this name from the patient ?" 789 ), 790 _('Deleting name') 791 ) 792 if not go_ahead: 793 return False 794 795 self.__identity.delete_name(name = name) 796 return True
797 #-------------------------------------------------------- 798 # properties 799 #--------------------------------------------------------
800 - def _get_identity(self):
801 return self.__identity
802
803 - def _set_identity(self, identity):
804 self.__identity = identity 805 self.refresh()
806 807 identity = property(_get_identity, _set_identity)
808 #------------------------------------------------------------
809 -class cPersonIDsManagerPnl(gmListWidgets.cGenericListManagerPnl):
810 """A list for managing a person's external IDs. 811 812 Does NOT act on/listen to the current patient. 813 """
814 - def __init__(self, *args, **kwargs):
815 816 try: 817 self.__identity = kwargs['identity'] 818 del kwargs['identity'] 819 except KeyError: 820 self.__identity = None 821 822 gmListWidgets.cGenericListManagerPnl.__init__(self, *args, **kwargs) 823 824 self.new_callback = self._add_id 825 self.edit_callback = self._edit_id 826 self.delete_callback = self._del_id 827 self.refresh_callback = self.refresh 828 829 self.__init_ui() 830 self.refresh()
831 #-------------------------------------------------------- 832 # external API 833 #--------------------------------------------------------
834 - def refresh(self, *args, **kwargs):
835 if self.__identity is None: 836 self._LCTRL_items.set_string_items() 837 return 838 839 ids = self.__identity.get_external_ids() 840 self._LCTRL_items.set_string_items ( 841 items = [ [ 842 i['name'], 843 i['value'], 844 gmTools.coalesce(i['issuer'], u''), 845 gmTools.coalesce(i['comment'], u'') 846 ] for i in ids 847 ] 848 ) 849 self._LCTRL_items.set_column_widths() 850 self._LCTRL_items.set_data(data = ids)
851 #-------------------------------------------------------- 852 # internal helpers 853 #--------------------------------------------------------
854 - def __init_ui(self):
855 self._LCTRL_items.set_columns(columns = [ 856 _('ID type'), 857 _('Value'), 858 _('Issuer'), 859 _('Comment') 860 ])
861 #--------------------------------------------------------
862 - def _add_id(self):
863 ea = cExternalIDEditAreaPnl(self, -1) 864 ea.identity = self.__identity 865 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 866 dlg.SetTitle(_('Adding new external ID')) 867 if dlg.ShowModal() == wx.ID_OK: 868 dlg.Destroy() 869 return True 870 dlg.Destroy() 871 return False
872 #--------------------------------------------------------
873 - def _edit_id(self, ext_id):
874 ea = cExternalIDEditAreaPnl(self, -1, external_id = ext_id) 875 ea.identity = self.__identity 876 dlg = gmEditArea.cGenericEditAreaDlg(self, -1, edit_area = ea) 877 dlg.SetTitle(_('Editing external ID')) 878 if dlg.ShowModal() == wx.ID_OK: 879 dlg.Destroy() 880 return True 881 dlg.Destroy() 882 return False
883 #--------------------------------------------------------
884 - def _del_id(self, ext_id):
885 go_ahead = gmGuiHelpers.gm_show_question ( 886 _( 'Do you really want to delete this\n' 887 'external ID from the patient ?'), 888 _('Deleting external ID') 889 ) 890 if not go_ahead: 891 return False 892 self.__identity.delete_external_id(pk_ext_id = ext_id['pk_id']) 893 return True
894 #-------------------------------------------------------- 895 # properties 896 #--------------------------------------------------------
897 - def _get_identity(self):
898 return self.__identity
899
900 - def _set_identity(self, identity):
901 self.__identity = identity 902 self.refresh()
903 904 identity = property(_get_identity, _set_identity)
905 #------------------------------------------------------------ 906 # integrated panels 907 #------------------------------------------------------------ 908 from Gnumed.wxGladeWidgets import wxgPersonIdentityManagerPnl 909
910 -class cPersonIdentityManagerPnl(wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl):
911 """A panel for editing identity data for a person. 912 913 - provides access to: 914 - name 915 - external IDs 916 917 Does NOT act on/listen to the current patient. 918 """
919 - def __init__(self, *args, **kwargs):
920 921 wxgPersonIdentityManagerPnl.wxgPersonIdentityManagerPnl.__init__(self, *args, **kwargs) 922 923 self.__identity = None 924 self.refresh()
925 #-------------------------------------------------------- 926 # external API 927 #--------------------------------------------------------
928 - def refresh(self):
929 self._PNL_names.identity = self.__identity 930 self._PNL_ids.identity = self.__identity 931 # this is an Edit Area: 932 self._PNL_identity.mode = 'new' 933 self._PNL_identity.data = self.__identity 934 if self.__identity is not None: 935 self._PNL_identity.mode = 'edit'
936 #-------------------------------------------------------- 937 # properties 938 #--------------------------------------------------------
939 - def _get_identity(self):
940 return self.__identity
941
942 - def _set_identity(self, identity):
943 self.__identity = identity 944 self.refresh()
945 946 identity = property(_get_identity, _set_identity) 947 #-------------------------------------------------------- 948 # event handlers 949 #--------------------------------------------------------
951 if not self._PNL_identity.save(): 952 gmDispatcher.send(signal = 'statustext', msg = _('Cannot save identity. Incomplete information.'), beep = True)
953 #--------------------------------------------------------
954 - def _on_reload_identity_button_pressed(self, event):
955 self._PNL_identity.refresh()
956 957 #============================================================ 958 from Gnumed.wxGladeWidgets import wxgPersonSocialNetworkManagerPnl 959
960 -class cPersonSocialNetworkManagerPnl(wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl):
961 - def __init__(self, *args, **kwargs):
962 963 wxgPersonSocialNetworkManagerPnl.wxgPersonSocialNetworkManagerPnl.__init__(self, *args, **kwargs) 964 965 self.__identity = None 966 self._PRW_provider.selection_only = False 967 self.refresh()
968 #-------------------------------------------------------- 969 # external API 970 #--------------------------------------------------------
971 - def refresh(self):
972 973 tt = _("Link another person in this database as the emergency contact.") 974 975 if self.__identity is None: 976 self._TCTRL_er_contact.SetValue(u'') 977 self._TCTRL_person.person = None 978 self._TCTRL_person.SetToolTipString(tt) 979 980 self._PRW_provider.SetText(value = u'', data = None) 981 return 982 983 self._TCTRL_er_contact.SetValue(gmTools.coalesce(self.__identity['emergency_contact'], u'')) 984 if self.__identity['pk_emergency_contact'] is not None: 985 ident = gmPerson.cIdentity(aPK_obj = self.__identity['pk_emergency_contact']) 986 self._TCTRL_person.person = ident 987 tt = u'%s\n\n%s\n\n%s' % ( 988 tt, 989 ident['description_gender'], 990 u'\n'.join([ 991 u'%s: %s%s' % ( 992 c['l10n_comm_type'], 993 c['url'], 994 gmTools.bool2subst(c['is_confidential'], _(' (confidential !)'), u'', u'') 995 ) 996 for c in ident.get_comm_channels() 997 ]) 998 ) 999 else: 1000 self._TCTRL_person.person = None 1001 1002 self._TCTRL_person.SetToolTipString(tt) 1003 1004 if self.__identity['pk_primary_provider'] is None: 1005 self._PRW_provider.SetText(value = u'', data = None) 1006 else: 1007 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1008 #-------------------------------------------------------- 1009 # properties 1010 #--------------------------------------------------------
1011 - def _get_identity(self):
1012 return self.__identity
1013
1014 - def _set_identity(self, identity):
1015 self.__identity = identity 1016 self.refresh()
1017 1018 identity = property(_get_identity, _set_identity) 1019 #-------------------------------------------------------- 1020 # event handlers 1021 #--------------------------------------------------------
1022 - def _on_save_button_pressed(self, event):
1023 if self.__identity is not None: 1024 self.__identity['emergency_contact'] = self._TCTRL_er_contact.GetValue().strip() 1025 if self._TCTRL_person.person is not None: 1026 self.__identity['pk_emergency_contact'] = self._TCTRL_person.person.ID 1027 if self._PRW_provider.GetValue().strip == u'': 1028 self.__identity['pk_primary_provider'] = None 1029 else: 1030 self.__identity['pk_primary_provider'] = self._PRW_provider.GetData() 1031 1032 self.__identity.save() 1033 1034 event.Skip()
1035 #--------------------------------------------------------
1036 - def _on_remove_contact_button_pressed(self, event):
1037 event.Skip() 1038 1039 if self.__identity is None: 1040 return 1041 1042 self._TCTRL_person.person = None 1043 1044 self.__identity['pk_emergency_contact'] = None 1045 self.__identity.save()
1046 #--------------------------------------------------------
1047 - def _on_button_activate_contact_pressed(self, event):
1048 ident = self._TCTRL_person.person 1049 if ident is not None: 1050 from Gnumed.wxpython import gmPatSearchWidgets 1051 gmPatSearchWidgets.set_active_patient(patient = ident, forced_reload = False) 1052 1053 event.Skip()
1054 #============================================================ 1055 # new-patient widgets 1056 #============================================================
1057 -def create_new_person(parent=None, activate=False):
1058 1059 dbcfg = gmCfg.cCfgSQL() 1060 1061 def_region = dbcfg.get2 ( 1062 option = u'person.create.default_region', 1063 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1064 bias = u'user' 1065 ) 1066 def_country = None 1067 1068 if def_region is None: 1069 def_country = dbcfg.get2 ( 1070 option = u'person.create.default_country', 1071 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1072 bias = u'user' 1073 ) 1074 else: 1075 countries = gmDemographicRecord.get_country_for_region(region = def_region) 1076 if len(countries) == 1: 1077 def_country = countries[0]['l10n_country'] 1078 1079 if parent is None: 1080 parent = wx.GetApp().GetTopWindow() 1081 1082 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region) 1083 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 1084 dlg.SetTitle(_('Adding new person')) 1085 ea._PRW_lastname.SetFocus() 1086 result = dlg.ShowModal() 1087 pat = ea.data 1088 dlg.Destroy() 1089 1090 if result != wx.ID_OK: 1091 return False 1092 1093 _log.debug('created new person [%s]', pat.ID) 1094 1095 if activate: 1096 from Gnumed.wxpython import gmPatSearchWidgets 1097 gmPatSearchWidgets.set_active_patient(patient = pat) 1098 1099 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 1100 1101 return True
1102 #============================================================ 1103 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 1104
1105 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
1106
1107 - def __init__(self, *args, **kwargs):
1108 1109 try: 1110 self.default_region = kwargs['region'] 1111 del kwargs['region'] 1112 except KeyError: 1113 self.default_region = None 1114 1115 try: 1116 self.default_country = kwargs['country'] 1117 del kwargs['country'] 1118 except KeyError: 1119 self.default_country = None 1120 1121 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 1122 gmEditArea.cGenericEditAreaMixin.__init__(self) 1123 1124 self.mode = 'new' 1125 self.data = None 1126 self._address = None 1127 1128 self.__init_ui() 1129 self.__register_interests()
1130 #---------------------------------------------------------------- 1131 # internal helpers 1132 #----------------------------------------------------------------
1133 - def __init_ui(self):
1134 self._PRW_lastname.final_regex = '.+' 1135 self._PRW_firstnames.final_regex = '.+' 1136 self._PRW_address_searcher.selection_only = False 1137 1138 # don't do that or else it will turn <invalid> into <today> :-( 1139 # low = wx.DateTimeFromDMY(1,0,1900) 1140 # hi = wx.DateTime() 1141 # self._DP_dob.SetRange(low, hi.SetToCurrent()) 1142 #self._DP_dob.SetValue(None) 1143 1144 # only if we would support None on selection_only's: 1145 # self._PRW_external_id_type.selection_only = True 1146 1147 if self.default_country is not None: 1148 self._PRW_country.SetText(value = self.default_country) 1149 1150 if self.default_region is not None: 1151 self._PRW_region.SetText(value = self.default_region)
1152 #----------------------------------------------------------------
1153 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
1154 1155 adr = self._PRW_address_searcher.get_address() 1156 if adr is None: 1157 return True 1158 1159 if ctrl.GetValue().strip() != adr[field]: 1160 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None) 1161 return True 1162 1163 return False
1164 #----------------------------------------------------------------
1166 adr = self._PRW_address_searcher.get_address() 1167 if adr is None: 1168 return True 1169 1170 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 1171 1172 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 1173 self._PRW_street.set_context(context = u'zip', val = adr['postcode']) 1174 1175 self._TCTRL_number.SetValue(adr['number']) 1176 1177 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 1178 self._PRW_urb.set_context(context = u'zip', val = adr['postcode']) 1179 1180 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state']) 1181 self._PRW_region.set_context(context = u'zip', val = adr['postcode']) 1182 1183 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 1184 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
1185 #----------------------------------------------------------------
1186 - def __identity_valid_for_save(self):
1187 error = False 1188 1189 # name fields 1190 if self._PRW_lastname.GetValue().strip() == u'': 1191 error = True 1192 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 1193 self._PRW_lastname.display_as_valid(False) 1194 else: 1195 self._PRW_lastname.display_as_valid(True) 1196 1197 if self._PRW_firstnames.GetValue().strip() == '': 1198 error = True 1199 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 1200 self._PRW_firstnames.display_as_valid(False) 1201 else: 1202 self._PRW_firstnames.display_as_valid(True) 1203 1204 # gender 1205 if self._PRW_gender.GetData() is None: 1206 error = True 1207 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 1208 self._PRW_gender.display_as_valid(False) 1209 else: 1210 self._PRW_gender.display_as_valid(True) 1211 1212 # dob validation 1213 dob = self._DP_dob.GetValue(as_pydt = False, invalid_as_none = True) 1214 # 1) valid timestamp ? 1215 if self._DP_dob.is_valid_timestamp(allow_none = False): # properly colors the field 1216 # but year also usable ? 1217 msg = None 1218 if (dob.GetYear() < 1900): 1219 msg = _( 1220 'DOB: %s\n' 1221 '\n' 1222 'While this is a valid point in time Python does\n' 1223 'not know how to deal with it.\n' 1224 '\n' 1225 'We suggest using January 1st 1901 instead and adding\n' 1226 'the true date of birth to the patient comment.\n' 1227 '\n' 1228 'Sorry for the inconvenience %s' 1229 ) % (dob, gmTools.u_frowning_face) 1230 elif dob > gmDateTime.wx_now_here(wx = wx): 1231 msg = _( 1232 'DOB: %s\n' 1233 '\n' 1234 'Date of birth in the future !' 1235 ) % dob 1236 1237 if msg is not None: 1238 error = True 1239 gmGuiHelpers.gm_show_error ( 1240 msg, 1241 _('Registering new person') 1242 ) 1243 self._DP_dob.display_as_valid(False) 1244 self._DP_dob.SetFocus() 1245 # 2) invalid timestamp ? 1246 # Do we have to check for u'', ever ? 1247 else: 1248 allow_empty_dob = gmGuiHelpers.gm_show_question ( 1249 _( 1250 'Are you sure you want to register this person\n' 1251 'without a valid date of birth ?\n' 1252 '\n' 1253 'This can be useful for temporary staff members\n' 1254 'but will provoke nag screens if this person\n' 1255 'becomes a patient.\n' 1256 ), 1257 _('Registering new person') 1258 ) 1259 if allow_empty_dob: 1260 self._DP_dob.display_as_valid(True) 1261 else: 1262 error = True 1263 self._DP_dob.SetFocus() 1264 1265 # TOB validation if non-empty 1266 # if self._TCTRL_tob.GetValue().strip() != u'': 1267 1268 return (not error)
1269 #----------------------------------------------------------------
1270 - def __address_valid_for_save(self, empty_address_is_valid=False):
1271 1272 # existing address ? if so set other fields 1273 if self._PRW_address_searcher.GetData() is not None: 1274 wx.CallAfter(self.__set_fields_from_address_searcher) 1275 return True 1276 1277 # must either all contain something or none of them 1278 fields_to_fill = ( 1279 self._TCTRL_number, 1280 self._PRW_zip, 1281 self._PRW_street, 1282 self._PRW_urb, 1283 self._PRW_region, 1284 self._PRW_country 1285 ) 1286 no_of_filled_fields = 0 1287 1288 for field in fields_to_fill: 1289 if field.GetValue().strip() != u'': 1290 no_of_filled_fields += 1 1291 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1292 field.Refresh() 1293 1294 # empty address ? 1295 if no_of_filled_fields == 0: 1296 if empty_address_is_valid: 1297 return True 1298 else: 1299 return None 1300 1301 # incompletely filled address ? 1302 if no_of_filled_fields != len(fields_to_fill): 1303 for field in fields_to_fill: 1304 if field.GetValue().strip() == u'': 1305 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1306 field.SetFocus() 1307 field.Refresh() 1308 msg = _('To properly create an address, all the related fields must be filled in.') 1309 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1310 return False 1311 1312 # fields which must contain a selected item 1313 # FIXME: they must also contain an *acceptable combination* which 1314 # FIXME: can only be tested against the database itself ... 1315 strict_fields = ( 1316 self._PRW_region, 1317 self._PRW_country 1318 ) 1319 error = False 1320 for field in strict_fields: 1321 if field.GetData() is None: 1322 error = True 1323 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1324 field.SetFocus() 1325 else: 1326 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1327 field.Refresh() 1328 1329 if error: 1330 msg = _('This field must contain an item selected from the dropdown list.') 1331 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1332 return False 1333 1334 return True
1335 #----------------------------------------------------------------
1336 - def __register_interests(self):
1337 1338 # identity 1339 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 1340 1341 # address 1342 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 1343 1344 # invalidate address searcher when any field edited 1345 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 1346 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher) 1347 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 1348 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 1349 1350 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 1351 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
1352 #---------------------------------------------------------------- 1353 # event handlers 1354 #----------------------------------------------------------------
1355 - def _on_leaving_firstname(self):
1356 """Set the gender according to entered firstname. 1357 1358 Matches are fetched from existing records in backend. 1359 """ 1360 # only set if not already set so as to not 1361 # overwrite a change by the user 1362 if self._PRW_gender.GetData() is not None: 1363 return True 1364 1365 firstname = self._PRW_firstnames.GetValue().strip() 1366 if firstname == u'': 1367 return True 1368 1369 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 1370 if gender is None: 1371 return True 1372 1373 wx.CallAfter(self._PRW_gender.SetData, gender) 1374 return True
1375 #----------------------------------------------------------------
1376 - def _on_leaving_zip(self):
1377 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 1378 1379 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'') 1380 self._PRW_street.set_context(context = u'zip', val = zip_code) 1381 self._PRW_urb.set_context(context = u'zip', val = zip_code) 1382 self._PRW_region.set_context(context = u'zip', val = zip_code) 1383 self._PRW_country.set_context(context = u'zip', val = zip_code) 1384 1385 return True
1386 #----------------------------------------------------------------
1387 - def _on_leaving_country(self):
1388 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 1389 1390 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'') 1391 self._PRW_region.set_context(context = u'country', val = country) 1392 1393 return True
1394 #----------------------------------------------------------------
1395 - def _invalidate_address_searcher(self, *args, **kwargs):
1396 mapping = [ 1397 (self._PRW_street, 'street'), 1398 (self._TCTRL_number, 'number'), 1399 (self._PRW_urb, 'urb'), 1400 (self._PRW_region, 'l10n_state') 1401 ] 1402 1403 # loop through fields and invalidate address searcher if different 1404 for ctrl, field in mapping: 1405 if self.__perhaps_invalidate_address_searcher(ctrl, field): 1406 return True 1407 1408 return True
1409 #----------------------------------------------------------------
1411 adr = self._PRW_address_searcher.get_address() 1412 if adr is None: 1413 return True 1414 1415 wx.CallAfter(self.__set_fields_from_address_searcher) 1416 return True
1417 #---------------------------------------------------------------- 1418 # generic Edit Area mixin API 1419 #----------------------------------------------------------------
1420 - def _valid_for_save(self):
1421 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
1422 #----------------------------------------------------------------
1423 - def _save_as_new(self):
1424 1425 # identity 1426 new_identity = gmPerson.create_identity ( 1427 gender = self._PRW_gender.GetData(), 1428 dob = self._DP_dob.get_pydt(), 1429 lastnames = self._PRW_lastname.GetValue().strip(), 1430 firstnames = self._PRW_firstnames.GetValue().strip() 1431 ) 1432 _log.debug('identity created: %s' % new_identity) 1433 1434 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 1435 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u'')) 1436 #TOB 1437 new_identity.save() 1438 1439 name = new_identity.get_active_name() 1440 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1441 name.save() 1442 1443 # address 1444 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 1445 if is_valid is True: 1446 # because we currently only check for non-emptiness 1447 # we must still deal with database errors 1448 try: 1449 new_identity.link_address ( 1450 number = self._TCTRL_number.GetValue().strip(), 1451 street = self._PRW_street.GetValue().strip(), 1452 postcode = self._PRW_zip.GetValue().strip(), 1453 urb = self._PRW_urb.GetValue().strip(), 1454 state = self._PRW_region.GetData(), 1455 country = self._PRW_country.GetData() 1456 ) 1457 except gmPG2.dbapi.InternalError: 1458 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 1459 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 1460 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 1461 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 1462 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip()) 1463 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 1464 _log.exception('cannot link address') 1465 gmGuiHelpers.gm_show_error ( 1466 aTitle = _('Saving address'), 1467 aMessage = _( 1468 'Cannot save this address.\n' 1469 '\n' 1470 'You will have to add it via the Demographics plugin.\n' 1471 ) 1472 ) 1473 elif is_valid is False: 1474 gmGuiHelpers.gm_show_error ( 1475 aTitle = _('Saving address'), 1476 aMessage = _( 1477 'Address not saved.\n' 1478 '\n' 1479 'You will have to add it via the Demographics plugin.\n' 1480 ) 1481 ) 1482 # else it is None which means empty address which we ignore 1483 1484 # phone 1485 new_identity.link_comm_channel ( 1486 comm_medium = u'homephone', 1487 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''), 1488 is_confidential = False 1489 ) 1490 1491 # external ID 1492 pk_type = self._PRW_external_id_type.GetData() 1493 id_value = self._TCTRL_external_id_value.GetValue().strip() 1494 if (pk_type is not None) and (id_value != u''): 1495 new_identity.add_external_id(value = id_value, pk_type = pk_type) 1496 1497 # occupation 1498 new_identity.link_occupation ( 1499 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'') 1500 ) 1501 1502 self.data = new_identity 1503 return True
1504 #----------------------------------------------------------------
1505 - def _save_as_update(self):
1506 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1507 #----------------------------------------------------------------
1508 - def _refresh_as_new(self):
1509 # FIXME: button "empty out" 1510 return
1511 #----------------------------------------------------------------
1512 - def _refresh_from_existing(self):
1513 return # there is no forward button so nothing to do here
1514 #----------------------------------------------------------------
1516 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1517 1518 #============================================================ 1519 # patient demographics editing classes 1520 #============================================================
1521 -class cPersonDemographicsEditorNb(wx.Notebook):
1522 """Notebook displaying demographics editing pages: 1523 1524 - Identity 1525 - Contacts (addresses, phone numbers, etc) 1526 - Social network (significant others, GP, etc) 1527 1528 Does NOT act on/listen to the current patient. 1529 """ 1530 #--------------------------------------------------------
1531 - def __init__(self, parent, id):
1532 1533 wx.Notebook.__init__ ( 1534 self, 1535 parent = parent, 1536 id = id, 1537 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 1538 name = self.__class__.__name__ 1539 ) 1540 1541 self.__identity = None 1542 self.__do_layout() 1543 self.SetSelection(0)
1544 #-------------------------------------------------------- 1545 # public API 1546 #--------------------------------------------------------
1547 - def refresh(self):
1548 """Populate fields in pages with data from model.""" 1549 for page_idx in range(self.GetPageCount()): 1550 page = self.GetPage(page_idx) 1551 page.identity = self.__identity 1552 1553 return True
1554 #-------------------------------------------------------- 1555 # internal API 1556 #--------------------------------------------------------
1557 - def __do_layout(self):
1558 """Build patient edition notebook pages.""" 1559 1560 # contacts page 1561 new_page = gmPersonContactWidgets.cPersonContactsManagerPnl(self, -1) 1562 new_page.identity = self.__identity 1563 self.AddPage ( 1564 page = new_page, 1565 text = _('Contacts'), 1566 select = True 1567 ) 1568 1569 # identity page 1570 new_page = cPersonIdentityManagerPnl(self, -1) 1571 new_page.identity = self.__identity 1572 self.AddPage ( 1573 page = new_page, 1574 text = _('Identity'), 1575 select = False 1576 ) 1577 1578 # social network page 1579 new_page = cPersonSocialNetworkManagerPnl(self, -1) 1580 new_page.identity = self.__identity 1581 self.AddPage ( 1582 page = new_page, 1583 text = _('Social network'), 1584 select = False 1585 )
1586 #-------------------------------------------------------- 1587 # properties 1588 #--------------------------------------------------------
1589 - def _get_identity(self):
1590 return self.__identity
1591
1592 - def _set_identity(self, identity):
1593 self.__identity = identity
1594 1595 identity = property(_get_identity, _set_identity)
1596 #============================================================ 1597 # old occupation widgets 1598 #============================================================ 1599 # FIXME: support multiple occupations 1600 # FIXME: redo with wxGlade 1601
1602 -class cPatOccupationsPanel(wx.Panel):
1603 """Page containing patient occupations edition fields. 1604 """
1605 - def __init__(self, parent, id, ident=None):
1606 """ 1607 Creates a new instance of BasicPatDetailsPage 1608 @param parent - The parent widget 1609 @type parent - A wx.Window instance 1610 @param id - The widget id 1611 @type id - An integer 1612 """ 1613 wx.Panel.__init__(self, parent, id) 1614 self.__ident = ident 1615 self.__do_layout()
1616 #--------------------------------------------------------
1617 - def __do_layout(self):
1618 PNL_form = wx.Panel(self, -1) 1619 # occupation 1620 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 1621 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 1622 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient")) 1623 # known since 1624 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 1625 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 1626 1627 # layout input widgets 1628 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 1629 SZR_input.AddGrowableCol(1) 1630 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 1631 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 1632 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 1633 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 1634 PNL_form.SetSizerAndFit(SZR_input) 1635 1636 # layout page 1637 SZR_main = wx.BoxSizer(wx.VERTICAL) 1638 SZR_main.Add(PNL_form, 1, wx.EXPAND) 1639 self.SetSizer(SZR_main)
1640 #--------------------------------------------------------
1641 - def set_identity(self, identity):
1642 return self.refresh(identity=identity)
1643 #--------------------------------------------------------
1644 - def refresh(self, identity=None):
1645 if identity is not None: 1646 self.__ident = identity 1647 jobs = self.__ident.get_occupations() 1648 if len(jobs) > 0: 1649 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 1650 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 1651 return True
1652 #--------------------------------------------------------
1653 - def save(self):
1654 if self.PRW_occupation.IsModified(): 1655 new_job = self.PRW_occupation.GetValue().strip() 1656 jobs = self.__ident.get_occupations() 1657 for job in jobs: 1658 if job['l10n_occupation'] == new_job: 1659 continue 1660 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 1661 self.__ident.link_occupation(occupation = new_job) 1662 return True
1663 #============================================================
1664 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
1665 """Patient demographics plugin for main notebook. 1666 1667 Hosts another notebook with pages for Identity, Contacts, etc. 1668 1669 Acts on/listens to the currently active patient. 1670 """ 1671 #--------------------------------------------------------
1672 - def __init__(self, parent, id):
1673 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 1674 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 1675 self.__do_layout() 1676 self.__register_interests()
1677 #-------------------------------------------------------- 1678 # public API 1679 #-------------------------------------------------------- 1680 #-------------------------------------------------------- 1681 # internal helpers 1682 #--------------------------------------------------------
1683 - def __do_layout(self):
1684 """Arrange widgets.""" 1685 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 1686 1687 szr_main = wx.BoxSizer(wx.VERTICAL) 1688 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 1689 self.SetSizerAndFit(szr_main)
1690 #-------------------------------------------------------- 1691 # event handling 1692 #--------------------------------------------------------
1693 - def __register_interests(self):
1694 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 1695 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
1696 #--------------------------------------------------------
1697 - def _on_pre_patient_selection(self):
1698 self._schedule_data_reget()
1699 #--------------------------------------------------------
1700 - def _on_post_patient_selection(self):
1701 self._schedule_data_reget()
1702 #-------------------------------------------------------- 1703 # reget mixin API 1704 #--------------------------------------------------------
1705 - def _populate_with_data(self):
1706 """Populate fields in pages with data from model.""" 1707 pat = gmPerson.gmCurrentPatient() 1708 if pat.connected: 1709 self.__patient_notebook.identity = pat 1710 else: 1711 self.__patient_notebook.identity = None 1712 self.__patient_notebook.refresh() 1713 return True
1714 1715 1716 #============================================================ 1717 #============================================================ 1718 #============================================================ 1719 #============================================================ 1720 # outdated, delete soon: 1721 # new-patient wizard classes 1722 #============================================================
1723 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
1724 """ 1725 Wizard page for entering patient's basic demographic information 1726 """ 1727 1728 form_fields = ( 1729 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation', 1730 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment' 1731 ) 1732
1733 - def __init__(self, parent, title):
1734 """ 1735 Creates a new instance of BasicPatDetailsPage 1736 @param parent - The parent widget 1737 @type parent - A wx.Window instance 1738 @param tile - The title of the page 1739 @type title - A StringType instance 1740 """ 1741 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson')) 1742 self.__title = title 1743 self.__do_layout() 1744 self.__register_interests()
1745 #--------------------------------------------------------
1746 - def __do_layout(self):
1747 PNL_form = wx.Panel(self, -1) 1748 1749 # last name 1750 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name')) 1751 STT_lastname.SetForegroundColour('red') 1752 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1) 1753 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)')) 1754 1755 # first name 1756 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)')) 1757 STT_firstname.SetForegroundColour('red') 1758 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1) 1759 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name')) 1760 1761 # nickname 1762 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name')) 1763 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1) 1764 1765 # DOB 1766 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth')) 1767 STT_dob.SetForegroundColour('red') 1768 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1) 1769 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one")) 1770 1771 # gender 1772 STT_gender = wx.StaticText(PNL_form, -1, _('Gender')) 1773 STT_gender.SetForegroundColour('red') 1774 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1) 1775 self.PRW_gender.SetToolTipString(_("Required: gender of patient")) 1776 1777 # title 1778 STT_title = wx.StaticText(PNL_form, -1, _('Title')) 1779 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1) 1780 1781 # zip code 1782 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code')) 1783 STT_zip_code.SetForegroundColour('orange') 1784 self.PRW_zip_code = gmPersonContactWidgets.cZipcodePhraseWheel(parent = PNL_form, id = -1) 1785 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code")) 1786 1787 # street 1788 STT_street = wx.StaticText(PNL_form, -1, _('Street')) 1789 STT_street.SetForegroundColour('orange') 1790 self.PRW_street = gmPersonContactWidgets.cStreetPhraseWheel(parent = PNL_form, id = -1) 1791 self.PRW_street.SetToolTipString(_("primary/home address: name of street")) 1792 1793 # address number 1794 STT_address_number = wx.StaticText(PNL_form, -1, _('Number')) 1795 STT_address_number.SetForegroundColour('orange') 1796 self.TTC_address_number = wx.TextCtrl(PNL_form, -1) 1797 self.TTC_address_number.SetToolTipString(_("primary/home address: address number")) 1798 1799 # town 1800 STT_town = wx.StaticText(PNL_form, -1, _('Place')) 1801 STT_town.SetForegroundColour('orange') 1802 self.PRW_town = gmPersonContactWidgets.cUrbPhraseWheel(parent = PNL_form, id = -1) 1803 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/...")) 1804 1805 # state 1806 STT_state = wx.StaticText(PNL_form, -1, _('Region')) 1807 STT_state.SetForegroundColour('orange') 1808 self.PRW_state = gmPersonContactWidgets.cStateSelectionPhraseWheel(parent=PNL_form, id=-1) 1809 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/...")) 1810 1811 # country 1812 STT_country = wx.StaticText(PNL_form, -1, _('Country')) 1813 STT_country.SetForegroundColour('orange') 1814 self.PRW_country = gmPersonContactWidgets.cCountryPhraseWheel(parent = PNL_form, id = -1) 1815 self.PRW_country.SetToolTipString(_("primary/home address: country")) 1816 1817 # phone 1818 STT_phone = wx.StaticText(PNL_form, -1, _('Phone')) 1819 self.TTC_phone = wx.TextCtrl(PNL_form, -1) 1820 self.TTC_phone.SetToolTipString(_("phone number at home")) 1821 1822 # occupation 1823 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 1824 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 1825 1826 # comment 1827 STT_comment = wx.StaticText(PNL_form, -1, _('Comment')) 1828 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1) 1829 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.')) 1830 1831 # form main validator 1832 self.form_DTD = cFormDTD(fields = self.__class__.form_fields) 1833 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD)) 1834 1835 # layout input widgets 1836 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4) 1837 SZR_input.AddGrowableCol(1) 1838 SZR_input.Add(STT_lastname, 0, wx.SHAPED) 1839 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND) 1840 SZR_input.Add(STT_firstname, 0, wx.SHAPED) 1841 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND) 1842 SZR_input.Add(STT_nick, 0, wx.SHAPED) 1843 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND) 1844 SZR_input.Add(STT_dob, 0, wx.SHAPED) 1845 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND) 1846 SZR_input.Add(STT_gender, 0, wx.SHAPED) 1847 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND) 1848 SZR_input.Add(STT_title, 0, wx.SHAPED) 1849 SZR_input.Add(self.PRW_title, 1, wx.EXPAND) 1850 SZR_input.Add(STT_zip_code, 0, wx.SHAPED) 1851 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND) 1852 SZR_input.Add(STT_street, 0, wx.SHAPED) 1853 SZR_input.Add(self.PRW_street, 1, wx.EXPAND) 1854 SZR_input.Add(STT_address_number, 0, wx.SHAPED) 1855 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND) 1856 SZR_input.Add(STT_town, 0, wx.SHAPED) 1857 SZR_input.Add(self.PRW_town, 1, wx.EXPAND) 1858 SZR_input.Add(STT_state, 0, wx.SHAPED) 1859 SZR_input.Add(self.PRW_state, 1, wx.EXPAND) 1860 SZR_input.Add(STT_country, 0, wx.SHAPED) 1861 SZR_input.Add(self.PRW_country, 1, wx.EXPAND) 1862 SZR_input.Add(STT_phone, 0, wx.SHAPED) 1863 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND) 1864 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 1865 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 1866 SZR_input.Add(STT_comment, 0, wx.SHAPED) 1867 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND) 1868 1869 PNL_form.SetSizerAndFit(SZR_input) 1870 1871 # layout page 1872 SZR_main = makePageTitle(self, self.__title) 1873 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1874 #-------------------------------------------------------- 1875 # event handling 1876 #--------------------------------------------------------
1877 - def __register_interests(self):
1878 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set) 1879 self.PRW_country.add_callback_on_selection(self.on_country_selected) 1880 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
1881 #--------------------------------------------------------
1882 - def on_country_selected(self, data):
1883 """Set the states according to entered country.""" 1884 self.PRW_state.set_context(context=u'country', val=data) 1885 return True
1886 #--------------------------------------------------------
1887 - def on_name_set(self):
1888 """Set the gender according to entered firstname. 1889 1890 Matches are fetched from existing records in backend. 1891 """ 1892 firstname = self.PRW_firstname.GetValue().strip() 1893 rows, idx = gmPG2.run_ro_queries(queries = [{ 1894 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 1895 'args': [firstname] 1896 }]) 1897 if len(rows) == 0: 1898 return True 1899 wx.CallAfter(self.PRW_gender.SetData, rows[0][0]) 1900 return True
1901 #--------------------------------------------------------
1902 - def on_zip_set(self):
1903 """Set the street, town, state and country according to entered zip code.""" 1904 zip_code = self.PRW_zip_code.GetValue().strip() 1905 self.PRW_street.set_context(context=u'zip', val=zip_code) 1906 self.PRW_town.set_context(context=u'zip', val=zip_code) 1907 self.PRW_state.set_context(context=u'zip', val=zip_code) 1908 self.PRW_country.set_context(context=u'zip', val=zip_code) 1909 return True
1910 #============================================================
1911 -def makePageTitle(wizPg, title):
1912 """ 1913 Utility function to create the main sizer of a wizard's page. 1914 1915 @param wizPg The wizard page widget 1916 @type wizPg A wx.WizardPageSimple instance 1917 @param title The wizard page's descriptive title 1918 @type title A StringType instance 1919 """ 1920 sizer = wx.BoxSizer(wx.VERTICAL) 1921 wizPg.SetSizer(sizer) 1922 title = wx.StaticText(wizPg, -1, title) 1923 title.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)) 1924 sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 2) 1925 sizer.Add(wx.StaticLine(wizPg, -1), 0, wx.EXPAND|wx.ALL, 2) 1926 return sizer
1927 #============================================================
1928 -class cNewPatientWizard(wx.wizard.Wizard):
1929 """ 1930 Wizard to create a new patient. 1931 1932 TODO: 1933 - write pages for different "themes" of patient creation 1934 - make it configurable which pages are loaded 1935 - make available sets of pages that apply to a country 1936 - make loading of some pages depend upon values in earlier pages, eg 1937 when the patient is female and older than 13 include a page about 1938 "female" data (number of kids etc) 1939 1940 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable() 1941 """ 1942 #--------------------------------------------------------
1943 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
1944 """ 1945 Creates a new instance of NewPatientWizard 1946 @param parent - The parent widget 1947 @type parent - A wx.Window instance 1948 """ 1949 id_wiz = wx.NewId() 1950 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap() 1951 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY) 1952 self.__subtitle = subtitle 1953 self.__do_layout()
1954 #--------------------------------------------------------
1955 - def RunWizard(self, activate=False):
1956 """Create new patient. 1957 1958 activate, too, if told to do so (and patient successfully created) 1959 """ 1960 while True: 1961 1962 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details): 1963 return False 1964 1965 try: 1966 # retrieve DTD and create patient 1967 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD) 1968 except: 1969 _log.exception('cannot add new patient - missing identity fields') 1970 gmGuiHelpers.gm_show_error ( 1971 _('Cannot create new patient.\n' 1972 'Missing parts of the identity.' 1973 ), 1974 _('Adding new patient') 1975 ) 1976 continue 1977 1978 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 1979 1980 try: 1981 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 1982 except: 1983 _log.exception('cannot finalize new patient - missing address fields') 1984 gmGuiHelpers.gm_show_error ( 1985 _('Cannot add address for the new patient.\n' 1986 'You must either enter all of the address fields or\n' 1987 'none at all. The relevant fields are marked in yellow.\n' 1988 '\n' 1989 'You will need to add the address details in the\n' 1990 'demographics module.' 1991 ), 1992 _('Adding new patient') 1993 ) 1994 break 1995 1996 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 1997 1998 break 1999 2000 if activate: 2001 from Gnumed.wxpython import gmPatSearchWidgets 2002 gmPatSearchWidgets.set_active_patient(patient = ident) 2003 2004 return ident
2005 #-------------------------------------------------------- 2006 # internal helpers 2007 #--------------------------------------------------------
2008 - def __do_layout(self):
2009 """Arrange widgets. 2010 """ 2011 # Create the wizard pages 2012 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle ) 2013 self.FitToPage(self.basic_pat_details)
2014 #============================================================ 2015 #============================================================
2016 -class cBasicPatDetailsPageValidator(wx.PyValidator):
2017 """ 2018 This validator is used to ensure that the user has entered all 2019 the required conditional values in the page (eg., to properly 2020 create an address, all the related fields must be filled). 2021 """ 2022 #--------------------------------------------------------
2023 - def __init__(self, dtd):
2024 """ 2025 Validator initialization. 2026 @param dtd The object containing the data model. 2027 @type dtd A cFormDTD instance 2028 """ 2029 # initialize parent class 2030 wx.PyValidator.__init__(self) 2031 # validator's storage object 2032 self.form_DTD = dtd
2033 #--------------------------------------------------------
2034 - def Clone(self):
2035 """ 2036 Standard cloner. 2037 Note that every validator must implement the Clone() method. 2038 """ 2039 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
2040 #--------------------------------------------------------
2041 - def Validate(self, parent = None):
2042 """ 2043 Validate the contents of the given text control. 2044 """ 2045 _pnl_form = self.GetWindow().GetParent() 2046 2047 error = False 2048 2049 # name fields 2050 if _pnl_form.PRW_lastname.GetValue().strip() == '': 2051 error = True 2052 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 2053 _pnl_form.PRW_lastname.SetBackgroundColour('pink') 2054 _pnl_form.PRW_lastname.Refresh() 2055 else: 2056 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2057 _pnl_form.PRW_lastname.Refresh() 2058 2059 if _pnl_form.PRW_firstname.GetValue().strip() == '': 2060 error = True 2061 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 2062 _pnl_form.PRW_firstname.SetBackgroundColour('pink') 2063 _pnl_form.PRW_firstname.Refresh() 2064 else: 2065 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2066 _pnl_form.PRW_firstname.Refresh() 2067 2068 # gender 2069 if _pnl_form.PRW_gender.GetData() is None: 2070 error = True 2071 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 2072 _pnl_form.PRW_gender.SetBackgroundColour('pink') 2073 _pnl_form.PRW_gender.Refresh() 2074 else: 2075 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2076 _pnl_form.PRW_gender.Refresh() 2077 2078 # dob validation 2079 if ( 2080 (_pnl_form.PRW_dob.GetValue().strip() == u'') 2081 or (not _pnl_form.PRW_dob.is_valid_timestamp()) 2082 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900) 2083 ): 2084 error = True 2085 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue() 2086 gmDispatcher.send(signal = 'statustext', msg = msg) 2087 _pnl_form.PRW_dob.SetBackgroundColour('pink') 2088 _pnl_form.PRW_dob.Refresh() 2089 else: 2090 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2091 _pnl_form.PRW_dob.Refresh() 2092 2093 # address 2094 is_any_field_filled = False 2095 address_fields = ( 2096 _pnl_form.TTC_address_number, 2097 _pnl_form.PRW_zip_code, 2098 _pnl_form.PRW_street, 2099 _pnl_form.PRW_town 2100 ) 2101 for field in address_fields: 2102 if field.GetValue().strip() == u'': 2103 if is_any_field_filled: 2104 error = True 2105 msg = _('To properly create an address, all the related fields must be filled in.') 2106 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2107 field.SetBackgroundColour('pink') 2108 field.SetFocus() 2109 field.Refresh() 2110 else: 2111 is_any_field_filled = True 2112 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2113 field.Refresh() 2114 2115 address_fields = ( 2116 _pnl_form.PRW_state, 2117 _pnl_form.PRW_country 2118 ) 2119 for field in address_fields: 2120 if field.GetData() is None: 2121 if is_any_field_filled: 2122 error = True 2123 msg = _('To properly create an address, all the related fields must be filled in.') 2124 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 2125 field.SetBackgroundColour('pink') 2126 field.SetFocus() 2127 field.Refresh() 2128 else: 2129 is_any_field_filled = True 2130 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 2131 field.Refresh() 2132 2133 return (not error)
2134 #--------------------------------------------------------
2135 - def TransferToWindow(self):
2136 """ 2137 Transfer data from validator to window. 2138 The default implementation returns False, indicating that an error 2139 occurred. We simply return True, as we don't do any data transfer. 2140 """ 2141 _pnl_form = self.GetWindow().GetParent() 2142 # fill in controls with values from self.form_DTD 2143 _pnl_form.PRW_gender.SetData(self.form_DTD['gender']) 2144 _pnl_form.PRW_dob.SetText(self.form_DTD['dob']) 2145 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames']) 2146 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames']) 2147 _pnl_form.PRW_title.SetText(self.form_DTD['title']) 2148 _pnl_form.PRW_nick.SetText(self.form_DTD['nick']) 2149 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation']) 2150 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number']) 2151 _pnl_form.PRW_street.SetText(self.form_DTD['street']) 2152 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code']) 2153 _pnl_form.PRW_town.SetText(self.form_DTD['town']) 2154 _pnl_form.PRW_state.SetData(self.form_DTD['state']) 2155 _pnl_form.PRW_country.SetData(self.form_DTD['country']) 2156 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone']) 2157 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment']) 2158 return True # Prevent wxDialog from complaining
2159 #--------------------------------------------------------
2160 - def TransferFromWindow(self):
2161 """ 2162 Transfer data from window to validator. 2163 The default implementation returns False, indicating that an error 2164 occurred. We simply return True, as we don't do any data transfer. 2165 """ 2166 # FIXME: should be called automatically 2167 if not self.GetWindow().GetParent().Validate(): 2168 return False 2169 try: 2170 _pnl_form = self.GetWindow().GetParent() 2171 # fill in self.form_DTD with values from controls 2172 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData() 2173 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData() 2174 2175 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue() 2176 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue() 2177 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue() 2178 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue() 2179 2180 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue() 2181 2182 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue() 2183 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue() 2184 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue() 2185 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue() 2186 self.form_DTD['state'] = _pnl_form.PRW_state.GetData() 2187 self.form_DTD['country'] = _pnl_form.PRW_country.GetData() 2188 2189 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue() 2190 2191 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue() 2192 except: 2193 return False 2194 return True
2195 #============================================================
2196 -class TestWizardPanel(wx.Panel):
2197 """ 2198 Utility class to test the new patient wizard. 2199 """ 2200 #--------------------------------------------------------
2201 - def __init__(self, parent, id):
2202 """ 2203 Create a new instance of TestPanel. 2204 @param parent The parent widget 2205 @type parent A wx.Window instance 2206 """ 2207 wx.Panel.__init__(self, parent, id) 2208 wizard = cNewPatientWizard(self) 2209 print wizard.RunWizard()
2210 #============================================================ 2211 if __name__ == "__main__": 2212 2213 #--------------------------------------------------------
2214 - def test_organizer_pnl():
2215 app = wx.PyWidgetTester(size = (600, 400)) 2216 app.SetWidget(cKOrganizerSchedulePnl) 2217 app.MainLoop()
2218 #--------------------------------------------------------
2219 - def test_person_names_pnl():
2220 app = wx.PyWidgetTester(size = (600, 400)) 2221 widget = cPersonNamesManagerPnl(app.frame, -1) 2222 widget.identity = activate_patient() 2223 app.frame.Show(True) 2224 app.MainLoop()
2225 #--------------------------------------------------------
2226 - def test_person_ids_pnl():
2227 app = wx.PyWidgetTester(size = (600, 400)) 2228 widget = cPersonIDsManagerPnl(app.frame, -1) 2229 widget.identity = activate_patient() 2230 app.frame.Show(True) 2231 app.MainLoop()
2232 #--------------------------------------------------------
2233 - def test_pat_ids_pnl():
2234 app = wx.PyWidgetTester(size = (600, 400)) 2235 widget = cPersonIdentityManagerPnl(app.frame, -1) 2236 widget.identity = activate_patient() 2237 app.frame.Show(True) 2238 app.MainLoop()
2239 #--------------------------------------------------------
2240 - def test_name_ea_pnl():
2241 app = wx.PyWidgetTester(size = (600, 400)) 2242 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name()) 2243 app.MainLoop()
2244 #--------------------------------------------------------
2245 - def test_pat_contacts_pnl():
2246 app = wx.PyWidgetTester(size = (600, 400)) 2247 widget = cPersonContactsManagerPnl(app.frame, -1) 2248 widget.identity = activate_patient() 2249 app.frame.Show(True) 2250 app.MainLoop()
2251 #--------------------------------------------------------
2252 - def test_cPersonDemographicsEditorNb():
2253 app = wx.PyWidgetTester(size = (600, 400)) 2254 widget = cPersonDemographicsEditorNb(app.frame, -1) 2255 widget.identity = activate_patient() 2256 widget.refresh() 2257 app.frame.Show(True) 2258 app.MainLoop()
2259 #--------------------------------------------------------
2260 - def activate_patient():
2261 patient = gmPersonSearch.ask_for_patient() 2262 if patient is None: 2263 print "No patient. Exiting gracefully..." 2264 sys.exit(0) 2265 from Gnumed.wxpython import gmPatSearchWidgets 2266 gmPatSearchWidgets.set_active_patient(patient=patient) 2267 return patient
2268 #-------------------------------------------------------- 2269 if len(sys.argv) > 1 and sys.argv[1] == 'test': 2270 2271 gmI18N.activate_locale() 2272 gmI18N.install_domain(domain='gnumed') 2273 gmPG2.get_connection() 2274 2275 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields) 2276 2277 # app = wx.PyWidgetTester(size = (400, 300)) 2278 # app.SetWidget(cNotebookedPatEditionPanel, -1) 2279 # app.SetWidget(TestWizardPanel, -1) 2280 # app.frame.Show(True) 2281 # app.MainLoop() 2282 2283 # phrasewheels 2284 # test_zipcode_prw() 2285 # test_state_prw() 2286 # test_street_prw() 2287 # test_organizer_pnl() 2288 #test_address_type_prw() 2289 #test_suburb_prw() 2290 test_urb_prw() 2291 #test_address_prw() 2292 2293 # contacts related widgets 2294 #test_address_ea_pnl() 2295 #test_person_adrs_pnl() 2296 #test_person_comms_pnl() 2297 #test_pat_contacts_pnl() 2298 2299 # identity related widgets 2300 #test_person_names_pnl() 2301 #test_person_ids_pnl() 2302 #test_pat_ids_pnl() 2303 #test_name_ea_pnl() 2304 2305 #test_cPersonDemographicsEditorNb() 2306 2307 #============================================================ 2308