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

Source Code for Module Gnumed.wxpython.gmDemographicsWidgets

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