1 """GNUmed medication/substances handling widgets.
2 """
3
4 __version__ = "$Revision: 1.33 $"
5 __author__ = "Karsten Hilbert <Karsten.Hilbert@gmx.net>"
6
7 import logging, sys, os.path, webbrowser
8
9
10 import wx, wx.grid
11
12
13 if __name__ == '__main__':
14 sys.path.insert(0, '../../')
15 from Gnumed.pycommon import gmDispatcher, gmCfg, gmShellAPI, gmTools, gmDateTime
16 from Gnumed.pycommon import gmMatchProvider, gmI18N, gmPrinting, gmCfg2
17 from Gnumed.business import gmPerson, gmATC, gmSurgery, gmMedication, gmForms
18 from Gnumed.wxpython import gmGuiHelpers, gmRegetMixin, gmAuthWidgets, gmEditArea, gmMacro
19 from Gnumed.wxpython import gmCfgWidgets, gmListWidgets, gmPhraseWheel, gmFormWidgets
20 from Gnumed.wxpython import gmAllergyWidgets
21
22
23 _log = logging.getLogger('gm.ui')
24 _log.info(__version__)
25
26
27
28
29
31
32 if parent is None:
33 parent = wx.GetApp().GetTopWindow()
34
35 def refresh(lctrl):
36 atcs = gmATC.get_reference_atcs()
37
38 items = [ [
39 a['atc'],
40 a['term'],
41 u'%s' % gmTools.coalesce(a['ddd'], u''),
42 gmTools.coalesce(a['unit'], u''),
43 gmTools.coalesce(a['administrative_route'], u''),
44 gmTools.coalesce(a['comment'], u''),
45 a['version'],
46 a['lang']
47 ] for a in atcs ]
48 lctrl.set_string_items(items)
49 lctrl.set_data(atcs)
50
51 gmListWidgets.get_choices_from_list (
52 parent = parent,
53 msg = _('\nThe ATC codes as known to GNUmed.\n'),
54 caption = _('Showing ATC codes.'),
55 columns = [ u'ATC', _('Term'), u'DDD', _('Unit'), _(u'Route'), _('Comment'), _('Version'), _('Language') ],
56 single_selection = True,
57 refresh_callback = refresh
58 )
59
60
61
63
64 dlg = wx.FileDialog (
65 parent = None,
66 message = _('Choose an ATC import config file'),
67 defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')),
68 defaultFile = '',
69 wildcard = "%s (*.conf)|*.conf|%s (*)|*" % (_('config files'), _('all files')),
70 style = wx.OPEN | wx.HIDE_READONLY | wx.FILE_MUST_EXIST
71 )
72
73 result = dlg.ShowModal()
74 if result == wx.ID_CANCEL:
75 return
76
77 cfg_file = dlg.GetPath()
78 dlg.Destroy()
79
80 conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing ATC reference data'))
81 if conn is None:
82 return False
83
84 wx.BeginBusyCursor()
85
86 if gmATC.atc_import(cfg_fname = cfg_file, conn = conn):
87 gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported ATC reference data.'))
88 else:
89 gmDispatcher.send(signal = 'statustext', msg = _('Importing ATC reference data failed.'), beep = True)
90
91 wx.EndBusyCursor()
92 return True
93
94
95
97
99
100 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
101
102 query = u"""
103
104 SELECT DISTINCT ON (label)
105 atc_code,
106 label
107 FROM (
108
109 SELECT
110 code as atc_code,
111 (code || ': ' || term || coalesce(' (' || ddd || unit || ')', ''))
112 AS label
113 FROM ref.atc
114 WHERE
115 term %(fragment_condition)s
116 OR
117 code %(fragment_condition)s
118
119 UNION ALL
120
121 SELECT
122 atc_code,
123 (atc_code || ': ' || description)
124 AS label
125 FROM ref.substance_in_brand
126 WHERE
127 description %(fragment_condition)s
128 OR
129 atc_code %(fragment_condition)s
130
131 UNION ALL
132
133 SELECT
134 atc_code,
135 (atc_code || ': ' || description || ' (' || preparation || ')')
136 AS label
137 FROM ref.branded_drug
138 WHERE
139 description %(fragment_condition)s
140 OR
141 atc_code %(fragment_condition)s
142
143 -- it would be nice to be able to include clin.vacc_indication but that's hard to do in SQL
144
145 ) AS candidates
146
147 ORDER BY label
148 LIMIT 50"""
149
150 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
151 mp.setThresholds(1, 2, 4)
152
153 self.SetToolTipString(_('Select an ATC (Anatomical-Therapeutic-Chemical) code.'))
154 self.matcher = mp
155 self.selection_only = True
156
157
158
159
161
162 if parent is None:
163 parent = wx.GetApp().GetTopWindow()
164
165
166 def delete(component):
167 gmMedication.delete_component_from_branded_drug (
168 brand = component['pk_brand'],
169 component = component['pk_substance_in_brand']
170 )
171 return True
172
173 def refresh(lctrl):
174 substs = gmMedication.get_substances_in_brands()
175 items = [ [
176 u'%s%s' % (s['brand'], gmTools.coalesce(s['atc_brand'], u'', u' (%s)')),
177 s['substance'],
178 gmTools.coalesce(s['atc_substance'], u''),
179 s['preparation'],
180 gmTools.coalesce(s['external_code_brand'], u'', u'%%s [%s]' % s['external_code_type_brand']),
181 s['pk_substance_in_brand']
182 ] for s in substs ]
183 lctrl.set_string_items(items)
184 lctrl.set_data(substs)
185
186 msg = _('\nThese are the substances in the drug brands known to GNUmed.\n')
187
188 gmListWidgets.get_choices_from_list (
189 parent = parent,
190 msg = msg,
191 caption = _('Showing drug brand components (substances).'),
192 columns = [_('Brand'), _('Substance'), u'ATC', _('Preparation'), _('Code'), u'#'],
193 single_selection = True,
194
195
196 delete_callback = delete,
197 refresh_callback = refresh
198 )
199
201
202 if parent is None:
203 parent = wx.GetApp().GetTopWindow()
204
205 def delete(brand):
206 if brand.is_vaccine:
207 gmGuiHelpers.gm_show_info (
208 aTitle = _('Deleting medication'),
209 aMessage = _(
210 'Cannot delete the medication\n'
211 '\n'
212 ' "%s" (%s)\n'
213 '\n'
214 'because it is a vaccine. Please delete it\n'
215 'from the vaccine management section !\n'
216 ) % (brand['description'], brand['preparation'])
217 )
218 return False
219 gmMedication.delete_branded_drug(brand = brand['pk'])
220 return True
221
222 def new():
223 drug_db = get_drug_database(parent = parent)
224
225 if drug_db is None:
226 return False
227
228 drug_db.import_drugs()
229
230 return True
231
232 def refresh(lctrl):
233 drugs = gmMedication.get_branded_drugs()
234 items = [ [
235 d['description'],
236 d['preparation'],
237 gmTools.coalesce(d['atc_code'], u''),
238 gmTools.coalesce(d['external_code'], u'', u'%%s [%s]' % d['external_code_type']),
239 d['pk']
240 ] for d in drugs ]
241 lctrl.set_string_items(items)
242 lctrl.set_data(drugs)
243
244 msg = _('\nThese are the drug brands known to GNUmed.\n')
245
246 gmListWidgets.get_choices_from_list (
247 parent = parent,
248 msg = msg,
249 caption = _('Showing branded drugs.'),
250 columns = [_('Name'), _('Preparation'), _('ATC'), _('Code'), u'#'],
251 single_selection = True,
252 refresh_callback = refresh,
253 new_callback = new,
254
255 delete_callback = delete
256 )
257
259
260 if parent is None:
261 parent = wx.GetApp().GetTopWindow()
262
263 def delete(substance):
264 gmMedication.delete_used_substance(substance = substance['pk'])
265 return True
266
267 def new():
268 drug_db = get_drug_database(parent = parent)
269
270 if drug_db is None:
271 return False
272
273 drug_db.import_drugs()
274
275 return True
276
277 def refresh(lctrl):
278 substs = gmMedication.get_substances_in_use()
279 items = [ [
280 s['description'],
281 gmTools.coalesce(s['atc_code'], u''),
282 s['pk']
283 ] for s in substs ]
284 lctrl.set_string_items(items)
285 lctrl.set_data(substs)
286
287 msg = _('\nThese are the substances currently or previously\nconsumed across all patients.\n')
288
289 gmListWidgets.get_choices_from_list (
290 parent = parent,
291 msg = msg,
292 caption = _('Showing consumed substances.'),
293 columns = [_('Name'), _('ATC'), u'#'],
294 single_selection = True,
295 refresh_callback = refresh,
296 new_callback = new,
297
298 delete_callback = delete
299 )
300
301
302
320
350
351
361
362
364
365 dbcfg = gmCfg.cCfgSQL()
366
367 ifap_cmd = dbcfg.get2 (
368 option = 'external.ifap-win.shell_command',
369 workplace = gmSurgery.gmCurrentPractice().active_workplace,
370 bias = 'workplace',
371 default = 'wine "C:\Ifapwin\WIAMDB.EXE"'
372 )
373 found, binary = gmShellAPI.detect_external_binary(ifap_cmd)
374 if not found:
375 gmDispatcher.send('statustext', msg = _('Cannot call IFAP via [%s].') % ifap_cmd)
376 return False
377 ifap_cmd = binary
378
379 if import_drugs:
380 transfer_file = os.path.expanduser(dbcfg.get2 (
381 option = 'external.ifap-win.transfer_file',
382 workplace = gmSurgery.gmCurrentPractice().active_workplace,
383 bias = 'workplace',
384 default = '~/.wine/drive_c/Ifapwin/ifap2gnumed.csv'
385 ))
386
387 try:
388 f = open(transfer_file, 'w+b').close()
389 except IOError:
390 _log.exception('Cannot create IFAP <-> GNUmed transfer file [%s]', transfer_file)
391 gmDispatcher.send('statustext', msg = _('Cannot create IFAP <-> GNUmed transfer file [%s].') % transfer_file)
392 return False
393
394 wx.BeginBusyCursor()
395 gmShellAPI.run_command_in_shell(command = ifap_cmd, blocking = import_drugs)
396 wx.EndBusyCursor()
397
398 if import_drugs:
399
400
401 try:
402 csv_file = open(transfer_file, 'rb')
403 except:
404 _log.exception('cannot access [%s]', fname)
405 csv_file = None
406
407 if csv_file is not None:
408 import csv
409 csv_lines = csv.DictReader (
410 csv_file,
411 fieldnames = u'PZN Handelsname Form Abpackungsmenge Einheit Preis1 Hersteller Preis2 rezeptpflichtig Festbetrag Packungszahl Packungsgr\xf6\xdfe'.split(),
412 delimiter = ';'
413 )
414 pat = gmPerson.gmCurrentPatient()
415 emr = pat.get_emr()
416
417 epi = emr.add_episode(episode_name = _('Current medication'))
418 for line in csv_lines:
419 narr = u'%sx %s %s %s (\u2258 %s %s) von %s (%s)' % (
420 line['Packungszahl'].strip(),
421 line['Handelsname'].strip(),
422 line['Form'].strip(),
423 line[u'Packungsgr\xf6\xdfe'].strip(),
424 line['Abpackungsmenge'].strip(),
425 line['Einheit'].strip(),
426 line['Hersteller'].strip(),
427 line['PZN'].strip()
428 )
429 emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi)
430 csv_file.close()
431
432 return True
433
434
435
436
438
440
441 query = u"""
442 SELECT schedule as sched, schedule
443 FROM clin.substance_intake
444 where schedule %(fragment_condition)s
445 ORDER BY sched
446 LIMIT 50"""
447
448 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
449 mp.setThresholds(1, 2, 4)
450 mp.word_separators = '[ \t=+&:@]+'
451 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
452 self.SetToolTipString(_('The schedule for taking this substance.'))
453 self.matcher = mp
454 self.selection_only = False
455
457
459
460 query = u"""
461 (
462 SELECT preparation as prep, preparation
463 FROM ref.branded_drug
464 where preparation %(fragment_condition)s
465 ) union (
466 SELECT preparation as prep, preparation
467 FROM clin.substance_intake
468 where preparation %(fragment_condition)s
469 )
470 order by prep
471 limit 30"""
472
473 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
474 mp.setThresholds(1, 2, 4)
475 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
476 self.SetToolTipString(_('The preparation (form) of the substance the patient is taking.'))
477 self.matcher = mp
478 self.selection_only = False
479
481
483
484 query = u"""
485 (
486 SELECT pk, (coalesce(atc_code || ': ', '') || description) as subst
487 FROM clin.consumed_substance
488 WHERE description %(fragment_condition)s
489 ) union (
490 SELECT NULL, (coalesce(atc_code || ': ', '') || description) as subst
491 FROM ref.substance_in_brand
492 WHERE description %(fragment_condition)s
493 ) union (
494 SELECT NULL, (atc || ': ' || term) as subst
495 FROM ref.v_atc
496 WHERE
497 is_group_code IS FALSE
498 AND
499 term %(fragment_condition)s
500 )
501 order by subst
502 limit 50"""
503
504 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
505 mp.setThresholds(1, 2, 4)
506 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
507 self.SetToolTipString(_('The INN / substance the patient is taking.'))
508 self.matcher = mp
509 self.selection_only = False
510
512
514
515 query = u"""
516 SELECT
517 pk,
518 (
519 description || ' (' || preparation || ')' || 'coalesce(' [' || atc_code || ']', '')
520 ) AS brand
521 FROM ref.branded_drug
522 WHERE description %(fragment_condition)s
523 ORDER BY brand
524 LIMIT 50"""
525
526 mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
527 mp.setThresholds(2, 3, 4)
528 gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
529 self.SetToolTipString(_('The brand name of the drug the patient is taking.'))
530 self.matcher = mp
531 self.selection_only = False
532
533
534 from Gnumed.wxGladeWidgets import wxgCurrentMedicationEAPnl
535
536 -class cCurrentMedicationEAPnl(wxgCurrentMedicationEAPnl.wxgCurrentMedicationEAPnl, gmEditArea.cGenericEditAreaMixin):
537
554
560
562 emr = gmPerson.gmCurrentPatient().get_emr()
563
564 state = emr.allergy_state
565 if state['last_confirmed'] is None:
566 confirmed = _('never')
567 else:
568 confirmed = state['last_confirmed'].strftime('%Y %B %d').decode(gmI18N.get_encoding())
569 msg = _(u'%s, last confirmed %s\n') % (state.state_string, confirmed)
570 msg += gmTools.coalesce(state['comment'], u'', _('Comment (%s): %%s\n') % state['modified_by'])
571 msg += u'\n'
572
573 for allergy in emr.get_allergies():
574 msg += u'%s (%s, %s): %s\n' % (
575 allergy['descriptor'],
576 allergy['l10n_type'],
577 gmTools.bool2subst(allergy['definite'], _('definite'), _('suspected'), u'?'),
578 gmTools.coalesce(allergy['reaction'], _('reaction not recorded'))
579 )
580
581 self._LBL_allergies.SetLabel(msg)
582
584
585 if self._PRW_brand.GetData() is None:
586 self._TCTRL_brand_ingredients.SetValue(u'')
587 if self.data is None:
588 return
589 if self.data['pk_brand'] is None:
590 return
591 self._PRW_brand.SetText(self.data['brand'], self.data['pk_brand'])
592
593 brand = gmMedication.cBrandedDrug(aPK_obj = self._PRW_brand.GetData())
594
595 if self.data is None:
596 self._PRW_preparation.SetText(brand['preparation'], None)
597 else:
598 self._PRW_preparation.SetText (
599 gmTools.coalesce(self.data['preparation'], brand['preparation']),
600 self.data['preparation']
601 )
602
603 comps = brand.components
604
605 if comps is None:
606 return
607
608 if len(comps) == 0:
609 return
610
611 comps = u' / '.join([ u'%s%s' % (c['description'], gmTools.coalesce(c['atc_code'], u'', u' (%s)')) for c in comps ])
612 self._TCTRL_brand_ingredients.SetValue(comps)
613
614
615
666
752
803
805 self._PRW_substance.SetText(u'', None)
806 self._PRW_strength.SetText(u'', None)
807
808 self._PRW_schedule.SetText(u'', None)
809 self._PRW_duration.SetText(u'', None)
810 self._PRW_aim.SetText(u'', None)
811 self._PRW_notes.SetText(u'', None)
812 self._PRW_episode.SetText(u'', None)
813
814 self._CHBOX_long_term.SetValue(False)
815 self._CHBOX_approved.SetValue(True)
816
817 self._DP_started.SetValue(gmDateTime.pydt_now_here())
818 self._DP_discontinued.SetValue(None)
819 self._PRW_discontinue_reason.SetValue(u'')
820
821 self.__refresh_brand_and_components()
822 self.__refresh_allergies()
823
824 self._PRW_substance.SetFocus()
825
827
828 self._PRW_substance.SetText(self.data['substance'], self.data['pk_substance'])
829 self._PRW_strength.SetText(gmTools.coalesce(self.data['strength'], u''), self.data['strength'])
830
831 if self.data['is_long_term']:
832 self._CHBOX_long_term.SetValue(True)
833 self._PRW_duration.Enable(False)
834 self._PRW_duration.SetText(gmTools.u_infinity, None)
835 self._BTN_discontinued_as_planned.Enable(False)
836 else:
837 self._CHBOX_long_term.SetValue(False)
838 self._PRW_duration.Enable(True)
839 self._BTN_discontinued_as_planned.Enable(True)
840 if self.data['duration'] is None:
841 self._PRW_duration.SetText(u'', None)
842 else:
843 self._PRW_duration.SetText(gmDateTime.format_interval(self.data['duration'], gmDateTime.acc_days), self.data['duration'])
844 self._PRW_aim.SetText(gmTools.coalesce(self.data['aim'], u''), self.data['aim'])
845 self._PRW_notes.SetText(gmTools.coalesce(self.data['notes'], u''), self.data['notes'])
846 self._PRW_episode.SetData(self.data['pk_episode'])
847 self._PRW_schedule.SetText(gmTools.coalesce(self.data['schedule'], u''), self.data['schedule'])
848
849 self._CHBOX_approved.SetValue(self.data['intake_is_approved_of'])
850
851 self._DP_started.SetValue(self.data['started'])
852 self._DP_discontinued.SetValue(self.data['discontinued'])
853 self._PRW_discontinue_reason.SetValue(gmTools.coalesce(self.data['discontinue_reason'], u''))
854
855 self.__refresh_brand_and_components()
856 self.__refresh_allergies()
857
858 self._PRW_substance.SetFocus()
859
861 self._refresh_as_new()
862
863
864
866 self.__refresh_brand_and_components()
867
869 if self._DP_discontinued.GetValue() is None:
870 self._PRW_discontinue_reason.Enable(False)
871 self._CHBOX_is_allergy.Enable(False)
872 else:
873 self._PRW_discontinue_reason.Enable(True)
874 self._CHBOX_is_allergy.Enable(True)
875
894
916
945
947 if self._CHBOX_long_term.IsChecked() is True:
948 self._PRW_duration.Enable(False)
949 self._BTN_discontinued_as_planned.Enable(False)
950 self._PRW_discontinue_reason.Enable(False)
951 self._CHBOX_is_allergy.Enable(False)
952 else:
953 self._PRW_duration.Enable(True)
954 self._BTN_discontinued_as_planned.Enable(True)
955 self._PRW_discontinue_reason.Enable(True)
956 self._CHBOX_is_allergy.Enable(True)
957
958 self.__refresh_allergies()
959
961 if self._CHBOX_is_allergy.IsChecked() is True:
962 val = self._PRW_discontinue_reason.GetValue().strip()
963 if not val.startswith(_('not tolerated:')):
964 self._PRW_discontinue_reason.SetValue(u'%s %s' % (_('not tolerated:'), val))
965
966 self.__refresh_allergies()
967
969 delete_it = gmGuiHelpers.gm_show_question (
970 aMessage = _(
971 'Do you really want to remove this substance intake ?\n'
972 '\n'
973 'It may be prudent to edit the details first so as to\n'
974 'leave behind some indication of why it was deleted.\n'
975 ),
976 aTitle = _('Deleting medication / substance intake')
977 )
978 if not delete_it:
979 return
980
981 gmMedication.delete_substance_intake(substance = substance)
982
992
993
994
1022
1024
1025 if parent is None:
1026 parent = wx.GetApp().GetTopWindow()
1027
1028
1029 dbcfg = gmCfg.cCfgSQL()
1030 option = u'form_templates.medication_list'
1031
1032 template = dbcfg.get2 (
1033 option = option,
1034 workplace = gmSurgery.gmCurrentPractice().active_workplace,
1035 bias = 'user'
1036 )
1037
1038 if template is None:
1039 template = configure_medication_list_template(parent = parent)
1040 if template is None:
1041 gmGuiHelpers.gm_show_error (
1042 aMessage = _('There is no medication list template configured.'),
1043 aTitle = _('Printing medication list')
1044 )
1045 return False
1046 else:
1047 try:
1048 name, ver = template.split(u' - ')
1049 except:
1050 _log.exception('problem splitting medication list template name [%s]', template)
1051 gmDispatcher.send(signal = 'statustext', msg = _('Problem loading medication list template.'), beep = True)
1052 return False
1053 template = gmForms.get_form_template(name_long = name, external_version = ver)
1054 if template is None:
1055 gmGuiHelpers.gm_show_error (
1056 aMessage = _('Cannot load medication list template [%s - %s]') % (name, ver),
1057 aTitle = _('Printing medication list')
1058 )
1059 return False
1060
1061
1062 try:
1063 meds_list = template.instantiate()
1064 except KeyError:
1065 _log.exception('cannot instantiate medication list template [%s]', template)
1066 gmGuiHelpers.gm_show_error (
1067 aMessage = _('Invalid medication list template [%s - %s (%s)]') % (name, ver, template['engine']),
1068 aTitle = _('Printing medication list')
1069 )
1070 return False
1071
1072 ph = gmMacro.gmPlaceholderHandler()
1073
1074 meds_list.substitute_placeholders(data_source = ph)
1075 pdf_name = meds_list.generate_output(cleanup = cleanup)
1076 if cleanup:
1077 meds_list.cleanup()
1078 if pdf_name is None:
1079 gmGuiHelpers.gm_show_error (
1080 aMessage = _('Error generating the medication list.'),
1081 aTitle = _('Printing medication list')
1082 )
1083 return False
1084
1085
1086 printed = gmPrinting.print_file_by_shellscript(filename = pdf_name, jobtype = 'medication_list')
1087 if not printed:
1088 gmGuiHelpers.gm_show_error (
1089 aMessage = _('Error printing the medication list.'),
1090 aTitle = _('Printing medication list')
1091 )
1092 return False
1093
1094 pat = gmPerson.gmCurrentPatient()
1095 emr = pat.get_emr()
1096 epi = emr.add_episode(episode_name = 'administration', is_open = False)
1097 emr.add_clin_narrative (
1098 soap_cat = None,
1099 note = _('medication list printed from template [%s - %s]') % (template['name_long'], template['external_version']),
1100 episode = epi
1101 )
1102
1103 return True
1104
1106 """A grid class for displaying current substance intake.
1107
1108 - does NOT listen to the currently active patient
1109 - thereby it can display any patient at any time
1110 """
1112
1113 wx.grid.Grid.__init__(self, *args, **kwargs)
1114
1115 self.__patient = None
1116 self.__row_data = {}
1117 self.__prev_row = None
1118 self.__prev_tooltip_row = None
1119 self.__prev_cell_0 = None
1120 self.__grouping_mode = u'episode'
1121 self.__filter_show_unapproved = False
1122 self.__filter_show_inactive = False
1123
1124 self.__grouping2col_labels = {
1125 u'episode': [
1126 _('Episode'),
1127 _('Substance'),
1128 _('Dose'),
1129 _('Schedule'),
1130 _('Started'),
1131 _('Duration'),
1132 _('Brand')
1133 ],
1134 u'brand': [
1135 _('Brand'),
1136 _('Schedule'),
1137 _('Substance'),
1138 _('Dose'),
1139 _('Started'),
1140 _('Duration'),
1141 _('Episode')
1142 ]
1143 }
1144
1145 self.__grouping2order_by_clauses = {
1146 u'episode': u'pk_health_issue nulls first, episode, substance, started',
1147 u'brand': u'brand nulls last, substance, started'
1148 }
1149
1150 self.__init_ui()
1151 self.__register_events()
1152
1153
1154
1156
1157 sel_block_top_left = self.GetSelectionBlockTopLeft()
1158 sel_block_bottom_right = self.GetSelectionBlockBottomRight()
1159 sel_cols = self.GetSelectedCols()
1160 sel_rows = self.GetSelectedRows()
1161
1162 selected_cells = []
1163
1164
1165 selected_cells += self.GetSelectedCells()
1166
1167
1168 selected_cells += list (
1169 (row, col)
1170 for row in sel_rows
1171 for col in xrange(self.GetNumberCols())
1172 )
1173
1174
1175 selected_cells += list (
1176 (row, col)
1177 for row in xrange(self.GetNumberRows())
1178 for col in sel_cols
1179 )
1180
1181
1182 for top_left, bottom_right in zip(self.GetSelectionBlockTopLeft(), self.GetSelectionBlockBottomRight()):
1183 selected_cells += [
1184 (row, col)
1185 for row in xrange(top_left[0], bottom_right[0] + 1)
1186 for col in xrange(top_left[1], bottom_right[1] + 1)
1187 ]
1188
1189 return set(selected_cells)
1190
1192 rows = {}
1193
1194 for row, col in self.get_selected_cells():
1195 rows[row] = True
1196
1197 return rows.keys()
1198
1201
1203
1204 self.empty_grid()
1205
1206 if self.__patient is None:
1207 return
1208
1209 emr = self.__patient.get_emr()
1210 meds = emr.get_current_substance_intake (
1211 order_by = self.__grouping2order_by_clauses[self.__grouping_mode],
1212 include_unapproved = self.__filter_show_unapproved,
1213 include_inactive = self.__filter_show_inactive
1214 )
1215 if not meds:
1216 return
1217
1218 self.BeginBatch()
1219
1220
1221 labels = self.__grouping2col_labels[self.__grouping_mode]
1222 if self.__filter_show_unapproved:
1223 self.AppendCols(numCols = len(labels) + 1)
1224 else:
1225 self.AppendCols(numCols = len(labels))
1226 for col_idx in range(len(labels)):
1227 self.SetColLabelValue(col_idx, labels[col_idx])
1228 if self.__filter_show_unapproved:
1229 self.SetColLabelValue(len(labels), u'OK?')
1230 self.SetColSize(len(labels), 40)
1231
1232 self.AppendRows(numRows = len(meds))
1233
1234
1235 for row_idx in range(len(meds)):
1236 med = meds[row_idx]
1237 self.__row_data[row_idx] = med
1238
1239 if med['is_currently_active'] is True:
1240 atcs = []
1241 if med['atc_substance'] is not None:
1242 atcs.append(med['atc_substance'])
1243
1244
1245
1246 allg = emr.is_allergic_to(atcs = tuple(atcs), inns = (med['substance'],))
1247 if allg not in [None, False]:
1248 attr = self.GetOrCreateCellAttr(row_idx, 0)
1249 if allg['type'] == u'allergy':
1250 attr.SetTextColour('red')
1251 else:
1252 attr.SetTextColour('yellow')
1253 self.SetRowAttr(row_idx, attr)
1254 else:
1255 attr = self.GetOrCreateCellAttr(row_idx, 0)
1256 attr.SetTextColour('grey')
1257 self.SetRowAttr(row_idx, attr)
1258
1259 if self.__grouping_mode == u'episode':
1260 if med['pk_episode'] is None:
1261 self.__prev_cell_0 = None
1262 self.SetCellValue(row_idx, 0, gmTools.u_diameter)
1263 else:
1264 if self.__prev_cell_0 != med['episode']:
1265 self.__prev_cell_0 = med['episode']
1266 self.SetCellValue(row_idx, 0, gmTools.coalesce(med['episode'], u''))
1267
1268 self.SetCellValue(row_idx, 1, med['substance'])
1269 self.SetCellValue(row_idx, 2, gmTools.coalesce(med['strength'], u''))
1270 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['schedule'], u''))
1271 self.SetCellValue(row_idx, 4, med['started'].strftime('%Y-%m-%d'))
1272
1273 if med['is_long_term']:
1274 self.SetCellValue(row_idx, 5, gmTools.u_infinity)
1275 else:
1276 if med['duration'] is None:
1277 self.SetCellValue(row_idx, 5, u'')
1278 else:
1279 self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days))
1280
1281 if med['pk_brand'] is None:
1282 self.SetCellValue(row_idx, 6, gmTools.coalesce(med['brand'], u''))
1283 else:
1284 if med['fake_brand']:
1285 self.SetCellValue(row_idx, 6, gmTools.coalesce(med['brand'], u'', _('%s (fake)')))
1286 else:
1287 self.SetCellValue(row_idx, 6, gmTools.coalesce(med['brand'], u''))
1288
1289 elif self.__grouping_mode == u'brand':
1290
1291 if med['pk_brand'] is None:
1292 self.__prev_cell_0 = None
1293 self.SetCellValue(row_idx, 0, gmTools.u_diameter)
1294 else:
1295 if self.__prev_cell_0 != med['brand']:
1296 self.__prev_cell_0 = med['brand']
1297 if med['fake_brand']:
1298 self.SetCellValue(row_idx, 0, gmTools.coalesce(med['brand'], u'', _('%s (fake)')))
1299 else:
1300 self.SetCellValue(row_idx, 0, gmTools.coalesce(med['brand'], u''))
1301
1302 self.SetCellValue(row_idx, 1, gmTools.coalesce(med['schedule'], u''))
1303 self.SetCellValue(row_idx, 2, med['substance'])
1304 self.SetCellValue(row_idx, 3, gmTools.coalesce(med['strength'], u''))
1305 self.SetCellValue(row_idx, 4, med['started'].strftime('%Y-%m-%d'))
1306
1307 if med['is_long_term']:
1308 self.SetCellValue(row_idx, 5, gmTools.u_infinity)
1309 else:
1310 if med['duration'] is None:
1311 self.SetCellValue(row_idx, 5, u'')
1312 else:
1313 self.SetCellValue(row_idx, 5, gmDateTime.format_interval(med['duration'], gmDateTime.acc_days))
1314
1315 if med['pk_episode'] is None:
1316 self.SetCellValue(row_idx, 6, u'')
1317 else:
1318 self.SetCellValue(row_idx, 6, gmTools.coalesce(med['episode'], u''))
1319
1320 else:
1321 raise ValueError('unknown grouping mode [%s]' % self.__grouping_mode)
1322
1323 if self.__filter_show_unapproved:
1324 self.SetCellValue (
1325 row_idx,
1326 len(labels),
1327 gmTools.bool2subst(med['intake_is_approved_of'], gmTools.u_checkmark_thin, u'', u'?')
1328 )
1329
1330
1331
1332 self.EndBatch()
1333
1335 self.BeginBatch()
1336 self.ClearGrid()
1337
1338
1339 if self.GetNumberRows() > 0:
1340 self.DeleteRows(pos = 0, numRows = self.GetNumberRows())
1341 if self.GetNumberCols() > 0:
1342 self.DeleteCols(pos = 0, numCols = self.GetNumberCols())
1343 self.EndBatch()
1344 self.__row_data = {}
1345 self.__prev_cell_0 = None
1346
1348
1349 if len(self.__row_data) == 0:
1350 return
1351
1352 sel_rows = self.get_selected_rows()
1353 if len(sel_rows) != 1:
1354 return
1355
1356 drug_db = get_drug_database()
1357 if drug_db is None:
1358 return
1359
1360 drug_db.show_info_on_substance(substance = self.get_selected_data()[0])
1361
1377
1390
1404
1407
1421
1423
1424 rows = self.get_selected_rows()
1425
1426 if len(rows) == 0:
1427 return
1428
1429 if len(rows) > 1:
1430 gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete more than one substance at once.'), beep = True)
1431 return
1432
1433 subst = self.get_selected_data()[0]
1434 delete_substance_intake(parent = self, substance = subst['pk_substance_intake'])
1435
1457
1462
1571
1572
1573
1575 self.CreateGrid(0, 1)
1576 self.EnableEditing(0)
1577 self.EnableDragGridSize(1)
1578 self.SetSelectionMode(wx.grid.Grid.wxGridSelectRows)
1579
1580 self.SetColLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTER)
1581
1582 self.SetRowLabelSize(0)
1583 self.SetRowLabelAlignment(horiz = wx.ALIGN_RIGHT, vert = wx.ALIGN_CENTRE)
1584
1585
1586
1588 return self.__patient
1589
1593
1594 patient = property(_get_patient, _set_patient)
1595
1597 return self.__grouping_mode
1598
1602
1603 grouping_mode = property(_get_grouping_mode, _set_grouping_mode)
1604
1606 return self.__filter_show_unapproved
1607
1611
1612 filter_show_unapproved = property(_get_filter_show_unapproved, _set_filter_show_unapproved)
1613
1615 return self.__filter_show_inactive
1616
1620
1621 filter_show_inactive = property(_get_filter_show_inactive, _set_filter_show_inactive)
1622
1623
1624
1626
1627 self.GetGridWindow().Bind(wx.EVT_MOTION, self.__on_mouse_over_cells)
1628
1629
1630
1631
1632 self.Bind(wx.grid.EVT_GRID_CELL_LEFT_DCLICK, self.__on_cell_left_dclicked)
1633
1635 """Calculate where the mouse is and set the tooltip dynamically."""
1636
1637
1638
1639 x, y = self.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653 row, col = self.XYToCell(x, y)
1654
1655 if row == self.__prev_tooltip_row:
1656 return
1657
1658 self.__prev_tooltip_row = row
1659
1660 try:
1661 evt.GetEventObject().SetToolTipString(self.get_row_tooltip(row = row))
1662 except KeyError:
1663 pass
1664
1669
1670 from Gnumed.wxGladeWidgets import wxgCurrentSubstancesPnl
1671
1672 -class cCurrentSubstancesPnl(wxgCurrentSubstancesPnl.wxgCurrentSubstancesPnl, gmRegetMixin.cRegetOnPaintMixin):
1673
1674 """Panel holding a grid with current substances. Used as notebook page."""
1675
1682
1683
1684
1693
1694
1695
1697 gmDispatcher.connect(signal = u'pre_patient_selection', receiver = self._on_pre_patient_selection)
1698 gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._schedule_data_reget)
1699 gmDispatcher.connect(signal = u'substance_intake_mod_db', receiver = self._schedule_data_reget)
1700
1701
1702
1704 wx.CallAfter(self.__on_pre_patient_selection)
1705
1707 self._grid_substances.patient = None
1708
1711
1714
1717
1720
1723
1726
1729
1732
1735
1738
1741
1744
1747
1748
1749
1750 if __name__ == '__main__':
1751
1752 if len(sys.argv) < 2:
1753 sys.exit()
1754
1755 if sys.argv[1] != 'test':
1756 sys.exit()
1757
1758 from Gnumed.pycommon import gmI18N
1759
1760 gmI18N.activate_locale()
1761 gmI18N.install_domain(domain = 'gnumed')
1762
1763
1764 app = wx.PyWidgetTester(size = (600, 600))
1765 app.SetWidget(cATCPhraseWheel, -1)
1766 app.MainLoop()
1767
1768
1769