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