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): 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) 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) 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(): 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 print "refreshing provider" 1005 print self.__identity 1006 print self.__identity['pk_primary_provider'] 1007 if self.__identity['pk_primary_provider'] is None: 1008 self._PRW_provider.SetText(value = u'', data = None) 1009 else: 1010 self._PRW_provider.SetData(data = self.__identity['pk_primary_provider'])
1011 #-------------------------------------------------------- 1012 # properties 1013 #--------------------------------------------------------
1014 - def _get_identity(self):
1015 return self.__identity
1016
1017 - def _set_identity(self, identity):
1018 self.__identity = identity 1019 self.refresh()
1020 1021 identity = property(_get_identity, _set_identity) 1022 #-------------------------------------------------------- 1023 # event handlers 1024 #--------------------------------------------------------
1025 - def _on_save_button_pressed(self, event):
1026 if self.__identity is not None: 1027 self.__identity['emergency_contact'] = self._TCTRL_er_contact.GetValue().strip() 1028 if self._TCTRL_person.person is not None: 1029 self.__identity['pk_emergency_contact'] = self._TCTRL_person.person.ID 1030 if self._PRW_provider.GetValue().strip == u'': 1031 self.__identity['pk_primary_provider'] = None 1032 else: 1033 self.__identity['pk_primary_provider'] = self._PRW_provider.GetData() 1034 1035 self.__identity.save() 1036 1037 event.Skip()
1038 #--------------------------------------------------------
1039 - def _on_remove_contact_button_pressed(self, event):
1040 event.Skip() 1041 1042 if self.__identity is None: 1043 return 1044 1045 self._TCTRL_person.person = None 1046 1047 self.__identity['pk_emergency_contact'] = None 1048 self.__identity.save()
1049 #--------------------------------------------------------
1050 - def _on_button_activate_contact_pressed(self, event):
1051 ident = self._TCTRL_person.person 1052 if ident is not None: 1053 from Gnumed.wxpython import gmPatSearchWidgets 1054 gmPatSearchWidgets.set_active_patient(patient = ident, forced_reload = False) 1055 1056 event.Skip()
1057 #============================================================ 1058 # new-patient widgets 1059 #============================================================
1060 -def create_new_person(parent=None, activate=False):
1061 1062 dbcfg = gmCfg.cCfgSQL() 1063 1064 def_region = dbcfg.get2 ( 1065 option = u'person.create.default_region', 1066 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1067 bias = u'user' 1068 ) 1069 def_country = None 1070 1071 if def_region is None: 1072 def_country = dbcfg.get2 ( 1073 option = u'person.create.default_country', 1074 workplace = gmSurgery.gmCurrentPractice().active_workplace, 1075 bias = u'user' 1076 ) 1077 else: 1078 countries = gmDemographicRecord.get_country_for_region(region = def_region) 1079 if len(countries) == 1: 1080 def_country = countries[0]['l10n_country'] 1081 1082 if parent is None: 1083 parent = wx.GetApp().GetTopWindow() 1084 1085 ea = cNewPatientEAPnl(parent = parent, id = -1, country = def_country, region = def_region) 1086 dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = True) 1087 dlg.SetTitle(_('Adding new person')) 1088 ea._PRW_lastname.SetFocus() 1089 result = dlg.ShowModal() 1090 pat = ea.data 1091 dlg.Destroy() 1092 1093 if result != wx.ID_OK: 1094 return False 1095 1096 _log.debug('created new person [%s]', pat.ID) 1097 1098 if activate: 1099 from Gnumed.wxpython import gmPatSearchWidgets 1100 gmPatSearchWidgets.set_active_patient(patient = pat) 1101 1102 gmDispatcher.send(signal = 'display_widget', name = 'gmNotebookedPatientEditionPlugin') 1103 1104 return True
1105 #============================================================ 1106 from Gnumed.wxGladeWidgets import wxgNewPatientEAPnl 1107
1108 -class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGenericEditAreaMixin):
1109
1110 - def __init__(self, *args, **kwargs):
1111 1112 try: 1113 self.default_region = kwargs['region'] 1114 del kwargs['region'] 1115 except KeyError: 1116 self.default_region = None 1117 1118 try: 1119 self.default_country = kwargs['country'] 1120 del kwargs['country'] 1121 except KeyError: 1122 self.default_country = None 1123 1124 wxgNewPatientEAPnl.wxgNewPatientEAPnl.__init__(self, *args, **kwargs) 1125 gmEditArea.cGenericEditAreaMixin.__init__(self) 1126 1127 self.mode = 'new' 1128 self.data = None 1129 self._address = None 1130 1131 self.__init_ui() 1132 self.__register_interests()
1133 #---------------------------------------------------------------- 1134 # internal helpers 1135 #----------------------------------------------------------------
1136 - def __init_ui(self):
1137 self._PRW_lastname.final_regex = '.+' 1138 self._PRW_firstnames.final_regex = '.+' 1139 self._PRW_address_searcher.selection_only = False 1140 1141 # don't do that or else it will turn <invalid> into <today> :-( 1142 # low = wx.DateTimeFromDMY(1,0,1900) 1143 # hi = wx.DateTime() 1144 # self._DP_dob.SetRange(low, hi.SetToCurrent()) 1145 #self._DP_dob.SetValue(None) 1146 1147 # only if we would support None on selection_only's: 1148 # self._PRW_external_id_type.selection_only = True 1149 1150 if self.default_country is not None: 1151 self._PRW_country.SetText(value = self.default_country) 1152 1153 if self.default_region is not None: 1154 self._PRW_region.SetText(value = self.default_region)
1155 #----------------------------------------------------------------
1156 - def __perhaps_invalidate_address_searcher(self, ctrl=None, field=None):
1157 1158 adr = self._PRW_address_searcher.get_address() 1159 if adr is None: 1160 return True 1161 1162 if ctrl.GetValue().strip() != adr[field]: 1163 wx.CallAfter(self._PRW_address_searcher.SetText, value = u'', data = None) 1164 return True 1165 1166 return False
1167 #----------------------------------------------------------------
1169 adr = self._PRW_address_searcher.get_address() 1170 if adr is None: 1171 return True 1172 1173 self._PRW_zip.SetText(value = adr['postcode'], data = adr['postcode']) 1174 1175 self._PRW_street.SetText(value = adr['street'], data = adr['street']) 1176 self._PRW_street.set_context(context = u'zip', val = adr['postcode']) 1177 1178 self._TCTRL_number.SetValue(adr['number']) 1179 1180 self._PRW_urb.SetText(value = adr['urb'], data = adr['urb']) 1181 self._PRW_urb.set_context(context = u'zip', val = adr['postcode']) 1182 1183 self._PRW_region.SetText(value = adr['l10n_state'], data = adr['code_state']) 1184 self._PRW_region.set_context(context = u'zip', val = adr['postcode']) 1185 1186 self._PRW_country.SetText(value = adr['l10n_country'], data = adr['code_country']) 1187 self._PRW_country.set_context(context = u'zip', val = adr['postcode'])
1188 #----------------------------------------------------------------
1189 - def __identity_valid_for_save(self):
1190 error = False 1191 1192 # name fields 1193 if self._PRW_lastname.GetValue().strip() == u'': 1194 error = True 1195 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 1196 self._PRW_lastname.display_as_valid(False) 1197 else: 1198 self._PRW_lastname.display_as_valid(True) 1199 1200 if self._PRW_firstnames.GetValue().strip() == '': 1201 error = True 1202 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 1203 self._PRW_firstnames.display_as_valid(False) 1204 else: 1205 self._PRW_firstnames.display_as_valid(True) 1206 1207 # gender 1208 if self._PRW_gender.GetData() is None: 1209 error = True 1210 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 1211 self._PRW_gender.display_as_valid(False) 1212 else: 1213 self._PRW_gender.display_as_valid(True) 1214 1215 # dob validation 1216 dob = self._DP_dob.GetValue(as_pydt = False, invalid_as_none = True) 1217 # 1) valid timestamp ? 1218 if self._DP_dob.is_valid_timestamp(allow_none = False): # properly colors the field 1219 # but year also usable ? 1220 msg = None 1221 if (dob.GetYear() < 1900): 1222 msg = _( 1223 'DOB: %s\n' 1224 '\n' 1225 'While this is a valid point in time Python does\n' 1226 'not know how to deal with it.\n' 1227 '\n' 1228 'We suggest using January 1st 1901 instead and adding\n' 1229 'the true date of birth to the patient comment.\n' 1230 '\n' 1231 'Sorry for the inconvenience %s' 1232 ) % (dob, gmTools.u_frowning_face) 1233 elif dob > gmDateTime.wx_now_here(wx = wx): 1234 msg = _( 1235 'DOB: %s\n' 1236 '\n' 1237 'Date of birth in the future !' 1238 ) % dob 1239 1240 if msg is not None: 1241 error = True 1242 gmGuiHelpers.gm_show_error ( 1243 msg, 1244 _('Registering new person') 1245 ) 1246 self._DP_dob.display_as_valid(False) 1247 self._DP_dob.SetFocus() 1248 # 2) invalid timestamp ? 1249 # Do we have to check for u'', ever ? 1250 else: 1251 allow_empty_dob = gmGuiHelpers.gm_show_question ( 1252 _( 1253 'Are you sure you want to register this person\n' 1254 'without a valid date of birth ?\n' 1255 '\n' 1256 'This can be useful for temporary staff members\n' 1257 'but will provoke nag screens if this person\n' 1258 'becomes a patient.\n' 1259 ), 1260 _('Registering new person') 1261 ) 1262 if allow_empty_dob: 1263 self._DP_dob.display_as_valid(True) 1264 else: 1265 error = True 1266 self._DP_dob.SetFocus() 1267 1268 # TOB validation if non-empty 1269 # if self._TCTRL_tob.GetValue().strip() != u'': 1270 1271 return (not error)
1272 #----------------------------------------------------------------
1273 - def __address_valid_for_save(self, empty_address_is_valid=False):
1274 1275 # existing address ? if so set other fields 1276 if self._PRW_address_searcher.GetData() is not None: 1277 wx.CallAfter(self.__set_fields_from_address_searcher) 1278 return True 1279 1280 # must either all contain something or none of them 1281 fields_to_fill = ( 1282 self._TCTRL_number, 1283 self._PRW_zip, 1284 self._PRW_street, 1285 self._PRW_urb, 1286 self._PRW_region, 1287 self._PRW_country 1288 ) 1289 no_of_filled_fields = 0 1290 1291 for field in fields_to_fill: 1292 if field.GetValue().strip() != u'': 1293 no_of_filled_fields += 1 1294 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1295 field.Refresh() 1296 1297 # empty address ? 1298 if no_of_filled_fields == 0: 1299 if empty_address_is_valid: 1300 return True 1301 else: 1302 return None 1303 1304 # incompletely filled address ? 1305 if no_of_filled_fields != len(fields_to_fill): 1306 for field in fields_to_fill: 1307 if field.GetValue().strip() == u'': 1308 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1309 field.SetFocus() 1310 field.Refresh() 1311 msg = _('To properly create an address, all the related fields must be filled in.') 1312 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1313 return False 1314 1315 # fields which must contain a selected item 1316 # FIXME: they must also contain an *acceptable combination* which 1317 # FIXME: can only be tested against the database itself ... 1318 strict_fields = ( 1319 self._PRW_region, 1320 self._PRW_country 1321 ) 1322 error = False 1323 for field in strict_fields: 1324 if field.GetData() is None: 1325 error = True 1326 field.SetBackgroundColour(gmPhraseWheel.color_prw_invalid) 1327 field.SetFocus() 1328 else: 1329 field.SetBackgroundColour(gmPhraseWheel.color_prw_valid) 1330 field.Refresh() 1331 1332 if error: 1333 msg = _('This field must contain an item selected from the dropdown list.') 1334 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1335 return False 1336 1337 return True
1338 #----------------------------------------------------------------
1339 - def __register_interests(self):
1340 1341 # identity 1342 self._PRW_firstnames.add_callback_on_lose_focus(self._on_leaving_firstname) 1343 1344 # address 1345 self._PRW_address_searcher.add_callback_on_lose_focus(self._on_leaving_adress_searcher) 1346 1347 # invalidate address searcher when any field edited 1348 self._PRW_street.add_callback_on_lose_focus(self._invalidate_address_searcher) 1349 wx.EVT_KILL_FOCUS(self._TCTRL_number, self._invalidate_address_searcher) 1350 self._PRW_urb.add_callback_on_lose_focus(self._invalidate_address_searcher) 1351 self._PRW_region.add_callback_on_lose_focus(self._invalidate_address_searcher) 1352 1353 self._PRW_zip.add_callback_on_lose_focus(self._on_leaving_zip) 1354 self._PRW_country.add_callback_on_lose_focus(self._on_leaving_country)
1355 #---------------------------------------------------------------- 1356 # event handlers 1357 #----------------------------------------------------------------
1358 - def _on_leaving_firstname(self):
1359 """Set the gender according to entered firstname. 1360 1361 Matches are fetched from existing records in backend. 1362 """ 1363 # only set if not already set so as to not 1364 # overwrite a change by the user 1365 if self._PRW_gender.GetData() is not None: 1366 return True 1367 1368 firstname = self._PRW_firstnames.GetValue().strip() 1369 if firstname == u'': 1370 return True 1371 1372 gender = gmPerson.map_firstnames2gender(firstnames = firstname) 1373 if gender is None: 1374 return True 1375 1376 wx.CallAfter(self._PRW_gender.SetData, gender) 1377 return True
1378 #----------------------------------------------------------------
1379 - def _on_leaving_zip(self):
1380 self.__perhaps_invalidate_address_searcher(self._PRW_zip, 'postcode') 1381 1382 zip_code = gmTools.none_if(self._PRW_zip.GetValue().strip(), u'') 1383 self._PRW_street.set_context(context = u'zip', val = zip_code) 1384 self._PRW_urb.set_context(context = u'zip', val = zip_code) 1385 self._PRW_region.set_context(context = u'zip', val = zip_code) 1386 self._PRW_country.set_context(context = u'zip', val = zip_code) 1387 1388 return True
1389 #----------------------------------------------------------------
1390 - def _on_leaving_country(self):
1391 self.__perhaps_invalidate_address_searcher(self._PRW_country, 'l10n_country') 1392 1393 country = gmTools.none_if(self._PRW_country.GetValue().strip(), u'') 1394 self._PRW_region.set_context(context = u'country', val = country) 1395 1396 return True
1397 #----------------------------------------------------------------
1398 - def _invalidate_address_searcher(self, *args, **kwargs):
1399 mapping = [ 1400 (self._PRW_street, 'street'), 1401 (self._TCTRL_number, 'number'), 1402 (self._PRW_urb, 'urb'), 1403 (self._PRW_region, 'l10n_state') 1404 ] 1405 1406 # loop through fields and invalidate address searcher if different 1407 for ctrl, field in mapping: 1408 if self.__perhaps_invalidate_address_searcher(ctrl, field): 1409 return True 1410 1411 return True
1412 #----------------------------------------------------------------
1414 adr = self._PRW_address_searcher.get_address() 1415 if adr is None: 1416 return True 1417 1418 wx.CallAfter(self.__set_fields_from_address_searcher) 1419 return True
1420 #---------------------------------------------------------------- 1421 # generic Edit Area mixin API 1422 #----------------------------------------------------------------
1423 - def _valid_for_save(self):
1424 return (self.__identity_valid_for_save() and self.__address_valid_for_save(empty_address_is_valid = True))
1425 #----------------------------------------------------------------
1426 - def _save_as_new(self):
1427 1428 # identity 1429 new_identity = gmPerson.create_identity ( 1430 gender = self._PRW_gender.GetData(), 1431 dob = self._DP_dob.get_pydt(), 1432 lastnames = self._PRW_lastname.GetValue().strip(), 1433 firstnames = self._PRW_firstnames.GetValue().strip() 1434 ) 1435 _log.debug('identity created: %s' % new_identity) 1436 1437 new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip()) 1438 new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u'')) 1439 #TOB 1440 new_identity.save() 1441 1442 name = new_identity.get_active_name() 1443 name['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'') 1444 name.save() 1445 1446 # address 1447 is_valid = self.__address_valid_for_save(empty_address_is_valid = False) 1448 if is_valid is True: 1449 # because we currently only check for non-emptiness 1450 # we must still deal with database errors 1451 try: 1452 new_identity.link_address ( 1453 number = self._TCTRL_number.GetValue().strip(), 1454 street = self._PRW_street.GetValue().strip(), 1455 postcode = self._PRW_zip.GetValue().strip(), 1456 urb = self._PRW_urb.GetValue().strip(), 1457 state = self._PRW_region.GetData(), 1458 country = self._PRW_country.GetData() 1459 ) 1460 except gmPG2.dbapi.InternalError: 1461 _log.debug('number: >>%s<<', self._TCTRL_number.GetValue().strip()) 1462 _log.debug('street: >>%s<<', self._PRW_street.GetValue().strip()) 1463 _log.debug('postcode: >>%s<<', self._PRW_zip.GetValue().strip()) 1464 _log.debug('urb: >>%s<<', self._PRW_urb.GetValue().strip()) 1465 _log.debug('state: >>%s<<', self._PRW_region.GetData().strip()) 1466 _log.debug('country: >>%s<<', self._PRW_country.GetData().strip()) 1467 _log.exception('cannot link address') 1468 gmGuiHelpers.gm_show_error ( 1469 aTitle = _('Saving address'), 1470 aMessage = _( 1471 'Cannot save this address.\n' 1472 '\n' 1473 'You will have to add it via the Demographics plugin.\n' 1474 ) 1475 ) 1476 elif is_valid is False: 1477 gmGuiHelpers.gm_show_error ( 1478 aTitle = _('Saving address'), 1479 aMessage = _( 1480 'Address not saved.\n' 1481 '\n' 1482 'You will have to add it via the Demographics plugin.\n' 1483 ) 1484 ) 1485 # else it is None which means empty address which we ignore 1486 1487 # phone 1488 new_identity.link_comm_channel ( 1489 comm_medium = u'homephone', 1490 url = gmTools.none_if(self._TCTRL_phone.GetValue().strip(), u''), 1491 is_confidential = False 1492 ) 1493 1494 # external ID 1495 pk_type = self._PRW_external_id_type.GetData() 1496 id_value = self._TCTRL_external_id_value.GetValue().strip() 1497 if (pk_type is not None) and (id_value != u''): 1498 new_identity.add_external_id(value = id_value, pk_type = pk_type) 1499 1500 # occupation 1501 new_identity.link_occupation ( 1502 occupation = gmTools.none_if(self._PRW_occupation.GetValue().strip(), u'') 1503 ) 1504 1505 self.data = new_identity 1506 return True
1507 #----------------------------------------------------------------
1508 - def _save_as_update(self):
1509 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1510 #----------------------------------------------------------------
1511 - def _refresh_as_new(self):
1512 # FIXME: button "empty out" 1513 return
1514 #----------------------------------------------------------------
1515 - def _refresh_from_existing(self):
1516 return # there is no forward button so nothing to do here
1517 #----------------------------------------------------------------
1519 raise NotImplementedError('[%s]: not expected to be used' % self.__class__.__name__)
1520 #============================================================ 1521 # new-patient wizard classes 1522 #============================================================
1523 -class cBasicPatDetailsPage(wx.wizard.WizardPageSimple):
1524 """ 1525 Wizard page for entering patient's basic demographic information 1526 """ 1527 1528 form_fields = ( 1529 'firstnames', 'lastnames', 'nick', 'dob', 'gender', 'title', 'occupation', 1530 'address_number', 'zip_code', 'street', 'town', 'state', 'country', 'phone', 'comment' 1531 ) 1532
1533 - def __init__(self, parent, title):
1534 """ 1535 Creates a new instance of BasicPatDetailsPage 1536 @param parent - The parent widget 1537 @type parent - A wx.Window instance 1538 @param tile - The title of the page 1539 @type title - A StringType instance 1540 """ 1541 wx.wizard.WizardPageSimple.__init__(self, parent) #, bitmap = gmGuiHelpers.gm_icon(_('oneperson')) 1542 self.__title = title 1543 self.__do_layout() 1544 self.__register_interests()
1545 #--------------------------------------------------------
1546 - def __do_layout(self):
1547 PNL_form = wx.Panel(self, -1) 1548 1549 # last name 1550 STT_lastname = wx.StaticText(PNL_form, -1, _('Last name')) 1551 STT_lastname.SetForegroundColour('red') 1552 self.PRW_lastname = cLastnamePhraseWheel(parent = PNL_form, id = -1) 1553 self.PRW_lastname.SetToolTipString(_('Required: lastname (family name)')) 1554 1555 # first name 1556 STT_firstname = wx.StaticText(PNL_form, -1, _('First name(s)')) 1557 STT_firstname.SetForegroundColour('red') 1558 self.PRW_firstname = cFirstnamePhraseWheel(parent = PNL_form, id = -1) 1559 self.PRW_firstname.SetToolTipString(_('Required: surname/given name/first name')) 1560 1561 # nickname 1562 STT_nick = wx.StaticText(PNL_form, -1, _('Nick name')) 1563 self.PRW_nick = cNicknamePhraseWheel(parent = PNL_form, id = -1) 1564 1565 # DOB 1566 STT_dob = wx.StaticText(PNL_form, -1, _('Date of birth')) 1567 STT_dob.SetForegroundColour('red') 1568 self.PRW_dob = gmDateTimeInput.cFuzzyTimestampInput(parent = PNL_form, id = -1) 1569 self.PRW_dob.SetToolTipString(_("Required: date of birth, if unknown or aliasing wanted then invent one")) 1570 1571 # gender 1572 STT_gender = wx.StaticText(PNL_form, -1, _('Gender')) 1573 STT_gender.SetForegroundColour('red') 1574 self.PRW_gender = cGenderSelectionPhraseWheel(parent = PNL_form, id=-1) 1575 self.PRW_gender.SetToolTipString(_("Required: gender of patient")) 1576 1577 # title 1578 STT_title = wx.StaticText(PNL_form, -1, _('Title')) 1579 self.PRW_title = cTitlePhraseWheel(parent = PNL_form, id = -1) 1580 1581 # zip code 1582 STT_zip_code = wx.StaticText(PNL_form, -1, _('Postal code')) 1583 STT_zip_code.SetForegroundColour('orange') 1584 self.PRW_zip_code = gmPersonContactWidgets.cZipcodePhraseWheel(parent = PNL_form, id = -1) 1585 self.PRW_zip_code.SetToolTipString(_("primary/home address: zip/postal code")) 1586 1587 # street 1588 STT_street = wx.StaticText(PNL_form, -1, _('Street')) 1589 STT_street.SetForegroundColour('orange') 1590 self.PRW_street = gmPersonContactWidgets.cStreetPhraseWheel(parent = PNL_form, id = -1) 1591 self.PRW_street.SetToolTipString(_("primary/home address: name of street")) 1592 1593 # address number 1594 STT_address_number = wx.StaticText(PNL_form, -1, _('Number')) 1595 STT_address_number.SetForegroundColour('orange') 1596 self.TTC_address_number = wx.TextCtrl(PNL_form, -1) 1597 self.TTC_address_number.SetToolTipString(_("primary/home address: address number")) 1598 1599 # town 1600 STT_town = wx.StaticText(PNL_form, -1, _('Place')) 1601 STT_town.SetForegroundColour('orange') 1602 self.PRW_town = gmPersonContactWidgets.cUrbPhraseWheel(parent = PNL_form, id = -1) 1603 self.PRW_town.SetToolTipString(_("primary/home address: city/town/village/dwelling/...")) 1604 1605 # state 1606 STT_state = wx.StaticText(PNL_form, -1, _('Region')) 1607 STT_state.SetForegroundColour('orange') 1608 self.PRW_state = gmPersonContactWidgets.cStateSelectionPhraseWheel(parent=PNL_form, id=-1) 1609 self.PRW_state.SetToolTipString(_("primary/home address: state/province/county/...")) 1610 1611 # country 1612 STT_country = wx.StaticText(PNL_form, -1, _('Country')) 1613 STT_country.SetForegroundColour('orange') 1614 self.PRW_country = gmPersonContactWidgets.cCountryPhraseWheel(parent = PNL_form, id = -1) 1615 self.PRW_country.SetToolTipString(_("primary/home address: country")) 1616 1617 # phone 1618 STT_phone = wx.StaticText(PNL_form, -1, _('Phone')) 1619 self.TTC_phone = wx.TextCtrl(PNL_form, -1) 1620 self.TTC_phone.SetToolTipString(_("phone number at home")) 1621 1622 # occupation 1623 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 1624 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 1625 1626 # comment 1627 STT_comment = wx.StaticText(PNL_form, -1, _('Comment')) 1628 self.TCTRL_comment = wx.TextCtrl(PNL_form, -1) 1629 self.TCTRL_comment.SetToolTipString(_('A comment on this patient.')) 1630 1631 # form main validator 1632 self.form_DTD = cFormDTD(fields = self.__class__.form_fields) 1633 PNL_form.SetValidator(cBasicPatDetailsPageValidator(dtd = self.form_DTD)) 1634 1635 # layout input widgets 1636 SZR_input = wx.FlexGridSizer(cols = 2, rows = 16, vgap = 4, hgap = 4) 1637 SZR_input.AddGrowableCol(1) 1638 SZR_input.Add(STT_lastname, 0, wx.SHAPED) 1639 SZR_input.Add(self.PRW_lastname, 1, wx.EXPAND) 1640 SZR_input.Add(STT_firstname, 0, wx.SHAPED) 1641 SZR_input.Add(self.PRW_firstname, 1, wx.EXPAND) 1642 SZR_input.Add(STT_nick, 0, wx.SHAPED) 1643 SZR_input.Add(self.PRW_nick, 1, wx.EXPAND) 1644 SZR_input.Add(STT_dob, 0, wx.SHAPED) 1645 SZR_input.Add(self.PRW_dob, 1, wx.EXPAND) 1646 SZR_input.Add(STT_gender, 0, wx.SHAPED) 1647 SZR_input.Add(self.PRW_gender, 1, wx.EXPAND) 1648 SZR_input.Add(STT_title, 0, wx.SHAPED) 1649 SZR_input.Add(self.PRW_title, 1, wx.EXPAND) 1650 SZR_input.Add(STT_zip_code, 0, wx.SHAPED) 1651 SZR_input.Add(self.PRW_zip_code, 1, wx.EXPAND) 1652 SZR_input.Add(STT_street, 0, wx.SHAPED) 1653 SZR_input.Add(self.PRW_street, 1, wx.EXPAND) 1654 SZR_input.Add(STT_address_number, 0, wx.SHAPED) 1655 SZR_input.Add(self.TTC_address_number, 1, wx.EXPAND) 1656 SZR_input.Add(STT_town, 0, wx.SHAPED) 1657 SZR_input.Add(self.PRW_town, 1, wx.EXPAND) 1658 SZR_input.Add(STT_state, 0, wx.SHAPED) 1659 SZR_input.Add(self.PRW_state, 1, wx.EXPAND) 1660 SZR_input.Add(STT_country, 0, wx.SHAPED) 1661 SZR_input.Add(self.PRW_country, 1, wx.EXPAND) 1662 SZR_input.Add(STT_phone, 0, wx.SHAPED) 1663 SZR_input.Add(self.TTC_phone, 1, wx.EXPAND) 1664 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 1665 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 1666 SZR_input.Add(STT_comment, 0, wx.SHAPED) 1667 SZR_input.Add(self.TCTRL_comment, 1, wx.EXPAND) 1668 1669 PNL_form.SetSizerAndFit(SZR_input) 1670 1671 # layout page 1672 SZR_main = gmGuiHelpers.makePageTitle(self, self.__title) 1673 SZR_main.Add(PNL_form, 1, wx.EXPAND)
1674 #-------------------------------------------------------- 1675 # event handling 1676 #--------------------------------------------------------
1677 - def __register_interests(self):
1678 self.PRW_firstname.add_callback_on_lose_focus(self.on_name_set) 1679 self.PRW_country.add_callback_on_selection(self.on_country_selected) 1680 self.PRW_zip_code.add_callback_on_lose_focus(self.on_zip_set)
1681 #--------------------------------------------------------
1682 - def on_country_selected(self, data):
1683 """Set the states according to entered country.""" 1684 self.PRW_state.set_context(context=u'country', val=data) 1685 return True
1686 #--------------------------------------------------------
1687 - def on_name_set(self):
1688 """Set the gender according to entered firstname. 1689 1690 Matches are fetched from existing records in backend. 1691 """ 1692 firstname = self.PRW_firstname.GetValue().strip() 1693 rows, idx = gmPG2.run_ro_queries(queries = [{ 1694 'cmd': u"select gender from dem.name_gender_map where name ilike %s", 1695 'args': [firstname] 1696 }]) 1697 if len(rows) == 0: 1698 return True 1699 wx.CallAfter(self.PRW_gender.SetData, rows[0][0]) 1700 return True
1701 #--------------------------------------------------------
1702 - def on_zip_set(self):
1703 """Set the street, town, state and country according to entered zip code.""" 1704 zip_code = self.PRW_zip_code.GetValue().strip() 1705 self.PRW_street.set_context(context=u'zip', val=zip_code) 1706 self.PRW_town.set_context(context=u'zip', val=zip_code) 1707 self.PRW_state.set_context(context=u'zip', val=zip_code) 1708 self.PRW_country.set_context(context=u'zip', val=zip_code) 1709 return True
1710 #============================================================
1711 -class cNewPatientWizard(wx.wizard.Wizard):
1712 """ 1713 Wizard to create a new patient. 1714 1715 TODO: 1716 - write pages for different "themes" of patient creation 1717 - make it configurable which pages are loaded 1718 - make available sets of pages that apply to a country 1719 - make loading of some pages depend upon values in earlier pages, eg 1720 when the patient is female and older than 13 include a page about 1721 "female" data (number of kids etc) 1722 1723 FIXME: use: wizard.FindWindowById(wx.ID_FORWARD).Disable() 1724 """ 1725 #--------------------------------------------------------
1726 - def __init__(self, parent, title = _('Register new person'), subtitle = _('Basic demographic details') ):
1727 """ 1728 Creates a new instance of NewPatientWizard 1729 @param parent - The parent widget 1730 @type parent - A wx.Window instance 1731 """ 1732 id_wiz = wx.NewId() 1733 wx.wizard.Wizard.__init__(self, parent, id_wiz, title) #images.getWizTest1Bitmap() 1734 self.SetExtraStyle(wx.WS_EX_VALIDATE_RECURSIVELY) 1735 self.__subtitle = subtitle 1736 self.__do_layout()
1737 #--------------------------------------------------------
1738 - def RunWizard(self, activate=False):
1739 """Create new patient. 1740 1741 activate, too, if told to do so (and patient successfully created) 1742 """ 1743 while True: 1744 1745 if not wx.wizard.Wizard.RunWizard(self, self.basic_pat_details): 1746 return False 1747 1748 try: 1749 # retrieve DTD and create patient 1750 ident = create_identity_from_dtd(dtd = self.basic_pat_details.form_DTD) 1751 except: 1752 _log.exception('cannot add new patient - missing identity fields') 1753 gmGuiHelpers.gm_show_error ( 1754 _('Cannot create new patient.\n' 1755 'Missing parts of the identity.' 1756 ), 1757 _('Adding new patient') 1758 ) 1759 continue 1760 1761 update_identity_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 1762 1763 try: 1764 link_contacts_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 1765 except: 1766 _log.exception('cannot finalize new patient - missing address fields') 1767 gmGuiHelpers.gm_show_error ( 1768 _('Cannot add address for the new patient.\n' 1769 'You must either enter all of the address fields or\n' 1770 'none at all. The relevant fields are marked in yellow.\n' 1771 '\n' 1772 'You will need to add the address details in the\n' 1773 'demographics module.' 1774 ), 1775 _('Adding new patient') 1776 ) 1777 break 1778 1779 link_occupation_from_dtd(identity = ident, dtd = self.basic_pat_details.form_DTD) 1780 1781 break 1782 1783 if activate: 1784 from Gnumed.wxpython import gmPatSearchWidgets 1785 gmPatSearchWidgets.set_active_patient(patient = ident) 1786 1787 return ident
1788 #-------------------------------------------------------- 1789 # internal helpers 1790 #--------------------------------------------------------
1791 - def __do_layout(self):
1792 """Arrange widgets. 1793 """ 1794 # Create the wizard pages 1795 self.basic_pat_details = cBasicPatDetailsPage(self, self.__subtitle ) 1796 self.FitToPage(self.basic_pat_details)
1797 #============================================================ 1798 #============================================================
1799 -class cBasicPatDetailsPageValidator(wx.PyValidator):
1800 """ 1801 This validator is used to ensure that the user has entered all 1802 the required conditional values in the page (eg., to properly 1803 create an address, all the related fields must be filled). 1804 """ 1805 #--------------------------------------------------------
1806 - def __init__(self, dtd):
1807 """ 1808 Validator initialization. 1809 @param dtd The object containing the data model. 1810 @type dtd A cFormDTD instance 1811 """ 1812 # initialize parent class 1813 wx.PyValidator.__init__(self) 1814 # validator's storage object 1815 self.form_DTD = dtd
1816 #--------------------------------------------------------
1817 - def Clone(self):
1818 """ 1819 Standard cloner. 1820 Note that every validator must implement the Clone() method. 1821 """ 1822 return cBasicPatDetailsPageValidator(dtd = self.form_DTD) # FIXME: probably need new instance of DTD ?
1823 #--------------------------------------------------------
1824 - def Validate(self, parent = None):
1825 """ 1826 Validate the contents of the given text control. 1827 """ 1828 _pnl_form = self.GetWindow().GetParent() 1829 1830 error = False 1831 1832 # name fields 1833 if _pnl_form.PRW_lastname.GetValue().strip() == '': 1834 error = True 1835 gmDispatcher.send(signal = 'statustext', msg = _('Must enter lastname.')) 1836 _pnl_form.PRW_lastname.SetBackgroundColour('pink') 1837 _pnl_form.PRW_lastname.Refresh() 1838 else: 1839 _pnl_form.PRW_lastname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1840 _pnl_form.PRW_lastname.Refresh() 1841 1842 if _pnl_form.PRW_firstname.GetValue().strip() == '': 1843 error = True 1844 gmDispatcher.send(signal = 'statustext', msg = _('Must enter first name.')) 1845 _pnl_form.PRW_firstname.SetBackgroundColour('pink') 1846 _pnl_form.PRW_firstname.Refresh() 1847 else: 1848 _pnl_form.PRW_firstname.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1849 _pnl_form.PRW_firstname.Refresh() 1850 1851 # gender 1852 if _pnl_form.PRW_gender.GetData() is None: 1853 error = True 1854 gmDispatcher.send(signal = 'statustext', msg = _('Must select gender.')) 1855 _pnl_form.PRW_gender.SetBackgroundColour('pink') 1856 _pnl_form.PRW_gender.Refresh() 1857 else: 1858 _pnl_form.PRW_gender.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1859 _pnl_form.PRW_gender.Refresh() 1860 1861 # dob validation 1862 if ( 1863 (_pnl_form.PRW_dob.GetValue().strip() == u'') 1864 or (not _pnl_form.PRW_dob.is_valid_timestamp()) 1865 or (_pnl_form.PRW_dob.GetData().timestamp.year < 1900) 1866 ): 1867 error = True 1868 msg = _('Cannot parse <%s> into proper timestamp.') % _pnl_form.PRW_dob.GetValue() 1869 gmDispatcher.send(signal = 'statustext', msg = msg) 1870 _pnl_form.PRW_dob.SetBackgroundColour('pink') 1871 _pnl_form.PRW_dob.Refresh() 1872 else: 1873 _pnl_form.PRW_dob.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1874 _pnl_form.PRW_dob.Refresh() 1875 1876 # address 1877 is_any_field_filled = False 1878 address_fields = ( 1879 _pnl_form.TTC_address_number, 1880 _pnl_form.PRW_zip_code, 1881 _pnl_form.PRW_street, 1882 _pnl_form.PRW_town 1883 ) 1884 for field in address_fields: 1885 if field.GetValue().strip() == u'': 1886 if is_any_field_filled: 1887 error = True 1888 msg = _('To properly create an address, all the related fields must be filled in.') 1889 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1890 field.SetBackgroundColour('pink') 1891 field.SetFocus() 1892 field.Refresh() 1893 else: 1894 is_any_field_filled = True 1895 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1896 field.Refresh() 1897 1898 address_fields = ( 1899 _pnl_form.PRW_state, 1900 _pnl_form.PRW_country 1901 ) 1902 for field in address_fields: 1903 if field.GetData() is None: 1904 if is_any_field_filled: 1905 error = True 1906 msg = _('To properly create an address, all the related fields must be filled in.') 1907 gmGuiHelpers.gm_show_error(msg, _('Required fields')) 1908 field.SetBackgroundColour('pink') 1909 field.SetFocus() 1910 field.Refresh() 1911 else: 1912 is_any_field_filled = True 1913 field.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) 1914 field.Refresh() 1915 1916 return (not error)
1917 #--------------------------------------------------------
1918 - def TransferToWindow(self):
1919 """ 1920 Transfer data from validator to window. 1921 The default implementation returns False, indicating that an error 1922 occurred. We simply return True, as we don't do any data transfer. 1923 """ 1924 _pnl_form = self.GetWindow().GetParent() 1925 # fill in controls with values from self.form_DTD 1926 _pnl_form.PRW_gender.SetData(self.form_DTD['gender']) 1927 _pnl_form.PRW_dob.SetText(self.form_DTD['dob']) 1928 _pnl_form.PRW_lastname.SetText(self.form_DTD['lastnames']) 1929 _pnl_form.PRW_firstname.SetText(self.form_DTD['firstnames']) 1930 _pnl_form.PRW_title.SetText(self.form_DTD['title']) 1931 _pnl_form.PRW_nick.SetText(self.form_DTD['nick']) 1932 _pnl_form.PRW_occupation.SetText(self.form_DTD['occupation']) 1933 _pnl_form.TTC_address_number.SetValue(self.form_DTD['address_number']) 1934 _pnl_form.PRW_street.SetText(self.form_DTD['street']) 1935 _pnl_form.PRW_zip_code.SetText(self.form_DTD['zip_code']) 1936 _pnl_form.PRW_town.SetText(self.form_DTD['town']) 1937 _pnl_form.PRW_state.SetData(self.form_DTD['state']) 1938 _pnl_form.PRW_country.SetData(self.form_DTD['country']) 1939 _pnl_form.TTC_phone.SetValue(self.form_DTD['phone']) 1940 _pnl_form.TCTRL_comment.SetValue(self.form_DTD['comment']) 1941 return True # Prevent wxDialog from complaining
1942 #--------------------------------------------------------
1943 - def TransferFromWindow(self):
1944 """ 1945 Transfer data from window to validator. 1946 The default implementation returns False, indicating that an error 1947 occurred. We simply return True, as we don't do any data transfer. 1948 """ 1949 # FIXME: should be called automatically 1950 if not self.GetWindow().GetParent().Validate(): 1951 return False 1952 try: 1953 _pnl_form = self.GetWindow().GetParent() 1954 # fill in self.form_DTD with values from controls 1955 self.form_DTD['gender'] = _pnl_form.PRW_gender.GetData() 1956 self.form_DTD['dob'] = _pnl_form.PRW_dob.GetData() 1957 1958 self.form_DTD['lastnames'] = _pnl_form.PRW_lastname.GetValue() 1959 self.form_DTD['firstnames'] = _pnl_form.PRW_firstname.GetValue() 1960 self.form_DTD['title'] = _pnl_form.PRW_title.GetValue() 1961 self.form_DTD['nick'] = _pnl_form.PRW_nick.GetValue() 1962 1963 self.form_DTD['occupation'] = _pnl_form.PRW_occupation.GetValue() 1964 1965 self.form_DTD['address_number'] = _pnl_form.TTC_address_number.GetValue() 1966 self.form_DTD['street'] = _pnl_form.PRW_street.GetValue() 1967 self.form_DTD['zip_code'] = _pnl_form.PRW_zip_code.GetValue() 1968 self.form_DTD['town'] = _pnl_form.PRW_town.GetValue() 1969 self.form_DTD['state'] = _pnl_form.PRW_state.GetData() 1970 self.form_DTD['country'] = _pnl_form.PRW_country.GetData() 1971 1972 self.form_DTD['phone'] = _pnl_form.TTC_phone.GetValue() 1973 1974 self.form_DTD['comment'] = _pnl_form.TCTRL_comment.GetValue() 1975 except: 1976 return False 1977 return True
1978 #============================================================ 1979 # patient demographics editing classes 1980 #============================================================
1981 -class cPersonDemographicsEditorNb(wx.Notebook):
1982 """Notebook displaying demographics editing pages: 1983 1984 - Identity 1985 - Contacts (addresses, phone numbers, etc) 1986 - Social Network (significant others, GP, etc) 1987 1988 Does NOT act on/listen to the current patient. 1989 """ 1990 #--------------------------------------------------------
1991 - def __init__(self, parent, id):
1992 1993 wx.Notebook.__init__ ( 1994 self, 1995 parent = parent, 1996 id = id, 1997 style = wx.NB_TOP | wx.NB_MULTILINE | wx.NO_BORDER, 1998 name = self.__class__.__name__ 1999 ) 2000 2001 self.__identity = None 2002 self.__do_layout() 2003 self.SetSelection(0)
2004 #-------------------------------------------------------- 2005 # public API 2006 #--------------------------------------------------------
2007 - def refresh(self):
2008 """Populate fields in pages with data from model.""" 2009 for page_idx in range(self.GetPageCount()): 2010 page = self.GetPage(page_idx) 2011 page.identity = self.__identity 2012 2013 return True
2014 #-------------------------------------------------------- 2015 # internal API 2016 #--------------------------------------------------------
2017 - def __do_layout(self):
2018 """Build patient edition notebook pages.""" 2019 2020 # contacts page 2021 new_page = gmPersonContactWidgets.cPersonContactsManagerPnl(self, -1) 2022 new_page.identity = self.__identity 2023 self.AddPage ( 2024 page = new_page, 2025 text = _('Contacts'), 2026 select = True 2027 ) 2028 2029 # identity page 2030 new_page = cPersonIdentityManagerPnl(self, -1) 2031 new_page.identity = self.__identity 2032 self.AddPage ( 2033 page = new_page, 2034 text = _('Identity'), 2035 select = False 2036 ) 2037 2038 # social network page 2039 new_page = cPersonSocialNetworkManagerPnl(self, -1) 2040 new_page.identity = self.__identity 2041 self.AddPage ( 2042 page = new_page, 2043 text = _('Social Network'), 2044 select = False 2045 )
2046 #-------------------------------------------------------- 2047 # properties 2048 #--------------------------------------------------------
2049 - def _get_identity(self):
2050 return self.__identity
2051
2052 - def _set_identity(self, identity):
2053 self.__identity = identity
2054 2055 identity = property(_get_identity, _set_identity)
2056 #============================================================ 2057 # old occupation widgets 2058 #============================================================ 2059 # FIXME: support multiple occupations 2060 # FIXME: redo with wxGlade 2061
2062 -class cPatOccupationsPanel(wx.Panel):
2063 """Page containing patient occupations edition fields. 2064 """
2065 - def __init__(self, parent, id, ident=None):
2066 """ 2067 Creates a new instance of BasicPatDetailsPage 2068 @param parent - The parent widget 2069 @type parent - A wx.Window instance 2070 @param id - The widget id 2071 @type id - An integer 2072 """ 2073 wx.Panel.__init__(self, parent, id) 2074 self.__ident = ident 2075 self.__do_layout()
2076 #--------------------------------------------------------
2077 - def __do_layout(self):
2078 PNL_form = wx.Panel(self, -1) 2079 # occupation 2080 STT_occupation = wx.StaticText(PNL_form, -1, _('Occupation')) 2081 self.PRW_occupation = cOccupationPhraseWheel(parent = PNL_form, id = -1) 2082 self.PRW_occupation.SetToolTipString(_("primary occupation of the patient")) 2083 # known since 2084 STT_occupation_updated = wx.StaticText(PNL_form, -1, _('Last updated')) 2085 self.TTC_occupation_updated = wx.TextCtrl(PNL_form, -1, style = wx.TE_READONLY) 2086 2087 # layout input widgets 2088 SZR_input = wx.FlexGridSizer(cols = 2, rows = 5, vgap = 4, hgap = 4) 2089 SZR_input.AddGrowableCol(1) 2090 SZR_input.Add(STT_occupation, 0, wx.SHAPED) 2091 SZR_input.Add(self.PRW_occupation, 1, wx.EXPAND) 2092 SZR_input.Add(STT_occupation_updated, 0, wx.SHAPED) 2093 SZR_input.Add(self.TTC_occupation_updated, 1, wx.EXPAND) 2094 PNL_form.SetSizerAndFit(SZR_input) 2095 2096 # layout page 2097 SZR_main = wx.BoxSizer(wx.VERTICAL) 2098 SZR_main.Add(PNL_form, 1, wx.EXPAND) 2099 self.SetSizer(SZR_main)
2100 #--------------------------------------------------------
2101 - def set_identity(self, identity):
2102 return self.refresh(identity=identity)
2103 #--------------------------------------------------------
2104 - def refresh(self, identity=None):
2105 if identity is not None: 2106 self.__ident = identity 2107 jobs = self.__ident.get_occupations() 2108 if len(jobs) > 0: 2109 self.PRW_occupation.SetText(jobs[0]['l10n_occupation']) 2110 self.TTC_occupation_updated.SetValue(jobs[0]['modified_when'].strftime('%m/%Y')) 2111 return True
2112 #--------------------------------------------------------
2113 - def save(self):
2114 if self.PRW_occupation.IsModified(): 2115 new_job = self.PRW_occupation.GetValue().strip() 2116 jobs = self.__ident.get_occupations() 2117 for job in jobs: 2118 if job['l10n_occupation'] == new_job: 2119 continue 2120 self.__ident.unlink_occupation(occupation = job['l10n_occupation']) 2121 self.__ident.link_occupation(occupation = new_job) 2122 return True
2123 #============================================================
2124 -class cNotebookedPatEditionPanel(wx.Panel, gmRegetMixin.cRegetOnPaintMixin):
2125 """Patient demographics plugin for main notebook. 2126 2127 Hosts another notebook with pages for Identity, Contacts, etc. 2128 2129 Acts on/listens to the currently active patient. 2130 """ 2131 #--------------------------------------------------------
2132 - def __init__(self, parent, id):
2133 wx.Panel.__init__ (self, parent = parent, id = id, style = wx.NO_BORDER) 2134 gmRegetMixin.cRegetOnPaintMixin.__init__(self) 2135 self.__do_layout() 2136 self.__register_interests()
2137 #-------------------------------------------------------- 2138 # public API 2139 #-------------------------------------------------------- 2140 #-------------------------------------------------------- 2141 # internal helpers 2142 #--------------------------------------------------------
2143 - def __do_layout(self):
2144 """Arrange widgets.""" 2145 self.__patient_notebook = cPersonDemographicsEditorNb(self, -1) 2146 2147 szr_main = wx.BoxSizer(wx.VERTICAL) 2148 szr_main.Add(self.__patient_notebook, 1, wx.EXPAND) 2149 self.SetSizerAndFit(szr_main)
2150 #-------------------------------------------------------- 2151 # event handling 2152 #--------------------------------------------------------
2153 - def __register_interests(self):
2154 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection) 2155 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
2156 #--------------------------------------------------------
2157 - def _on_pre_patient_selection(self):
2158 self._schedule_data_reget()
2159 #--------------------------------------------------------
2160 - def _on_post_patient_selection(self):
2161 self._schedule_data_reget()
2162 #-------------------------------------------------------- 2163 # reget mixin API 2164 #--------------------------------------------------------
2165 - def _populate_with_data(self):
2166 """Populate fields in pages with data from model.""" 2167 pat = gmPerson.gmCurrentPatient() 2168 if pat.connected: 2169 self.__patient_notebook.identity = pat 2170 else: 2171 self.__patient_notebook.identity = None 2172 self.__patient_notebook.refresh() 2173 return True
2174 #============================================================
2175 -class TestWizardPanel(wx.Panel):
2176 """ 2177 Utility class to test the new patient wizard. 2178 """ 2179 #--------------------------------------------------------
2180 - def __init__(self, parent, id):
2181 """ 2182 Create a new instance of TestPanel. 2183 @param parent The parent widget 2184 @type parent A wx.Window instance 2185 """ 2186 wx.Panel.__init__(self, parent, id) 2187 wizard = cNewPatientWizard(self) 2188 print wizard.RunWizard()
2189 #============================================================ 2190 if __name__ == "__main__": 2191 2192 #--------------------------------------------------------
2193 - def test_organizer_pnl():
2194 app = wx.PyWidgetTester(size = (600, 400)) 2195 app.SetWidget(cKOrganizerSchedulePnl) 2196 app.MainLoop()
2197 #--------------------------------------------------------
2198 - def test_person_names_pnl():
2199 app = wx.PyWidgetTester(size = (600, 400)) 2200 widget = cPersonNamesManagerPnl(app.frame, -1) 2201 widget.identity = activate_patient() 2202 app.frame.Show(True) 2203 app.MainLoop()
2204 #--------------------------------------------------------
2205 - def test_person_ids_pnl():
2206 app = wx.PyWidgetTester(size = (600, 400)) 2207 widget = cPersonIDsManagerPnl(app.frame, -1) 2208 widget.identity = activate_patient() 2209 app.frame.Show(True) 2210 app.MainLoop()
2211 #--------------------------------------------------------
2212 - def test_pat_ids_pnl():
2213 app = wx.PyWidgetTester(size = (600, 400)) 2214 widget = cPersonIdentityManagerPnl(app.frame, -1) 2215 widget.identity = activate_patient() 2216 app.frame.Show(True) 2217 app.MainLoop()
2218 #--------------------------------------------------------
2219 - def test_name_ea_pnl():
2220 app = wx.PyWidgetTester(size = (600, 400)) 2221 app.SetWidget(cNameGenderDOBEditAreaPnl, name = activate_patient().get_active_name()) 2222 app.MainLoop()
2223 #--------------------------------------------------------
2224 - def test_pat_contacts_pnl():
2225 app = wx.PyWidgetTester(size = (600, 400)) 2226 widget = cPersonContactsManagerPnl(app.frame, -1) 2227 widget.identity = activate_patient() 2228 app.frame.Show(True) 2229 app.MainLoop()
2230 #--------------------------------------------------------
2231 - def test_cPersonDemographicsEditorNb():
2232 app = wx.PyWidgetTester(size = (600, 400)) 2233 widget = cPersonDemographicsEditorNb(app.frame, -1) 2234 widget.identity = activate_patient() 2235 widget.refresh() 2236 app.frame.Show(True) 2237 app.MainLoop()
2238 #--------------------------------------------------------
2239 - def activate_patient():
2240 patient = gmPersonSearch.ask_for_patient() 2241 if patient is None: 2242 print "No patient. Exiting gracefully..." 2243 sys.exit(0) 2244 from Gnumed.wxpython import gmPatSearchWidgets 2245 gmPatSearchWidgets.set_active_patient(patient=patient) 2246 return patient
2247 #-------------------------------------------------------- 2248 if len(sys.argv) > 1 and sys.argv[1] == 'test': 2249 2250 gmI18N.activate_locale() 2251 gmI18N.install_domain(domain='gnumed') 2252 gmPG2.get_connection() 2253 2254 # a = cFormDTD(fields = cBasicPatDetailsPage.form_fields) 2255 2256 # app = wx.PyWidgetTester(size = (400, 300)) 2257 # app.SetWidget(cNotebookedPatEditionPanel, -1) 2258 # app.SetWidget(TestWizardPanel, -1) 2259 # app.frame.Show(True) 2260 # app.MainLoop() 2261 2262 # phrasewheels 2263 # test_zipcode_prw() 2264 # test_state_prw() 2265 # test_street_prw() 2266 # test_organizer_pnl() 2267 #test_address_type_prw() 2268 #test_suburb_prw() 2269 test_urb_prw() 2270 #test_address_prw() 2271 2272 # contacts related widgets 2273 #test_address_ea_pnl() 2274 #test_person_adrs_pnl() 2275 #test_person_comms_pnl() 2276 #test_pat_contacts_pnl() 2277 2278 # identity related widgets 2279 #test_person_names_pnl() 2280 #test_person_ids_pnl() 2281 #test_pat_ids_pnl() 2282 #test_name_ea_pnl() 2283 2284 #test_cPersonDemographicsEditorNb() 2285 2286 #============================================================ 2287