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