[med-svn] [gnumed-client] 01/10: Imported Upstream version 1.5.8+dfsg
Andreas Tille
tille at debian.org
Wed Dec 16 15:56:19 UTC 2015
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository gnumed-client.
commit d28963f95234074971ab819ad8ace2c7e0db062f
Author: Andreas Tille <tille at debian.org>
Date: Tue Nov 10 08:34:38 2015 +0100
Imported Upstream version 1.5.8+dfsg
---
client/CHANGELOG | 20 ++
client/business/gmClinicalRecord.py | 96 +++++-
client/business/gmEMRStructItems.py | 4 +-
client/business/gmPerson.py | 97 ++++--
client/business/gmStaff.py | 9 +-
client/doc/schema/gnumed-entire_schema.html | 2 +-
client/gm-from-vcs.bat | 21 +-
client/gnumed.py | 66 ++++-
client/pycommon/gmBackendListener.py | 4 +-
client/pycommon/gmBorg.py | 43 +--
client/pycommon/gmBusinessDBObject.py | 27 +-
client/pycommon/gmNull.py | 108 +++----
client/pycommon/gmPG2.py | 7 +-
client/pycommon/gmTools.py | 116 +++++++-
client/wxpython/gmGuiMain.py | 63 +++-
client/wxpython/gmListWidgets.py | 4 +-
client/wxpython/gmNarrativeWidgets.py | 9 +-
client/wxpython/gmPatOverviewWidgets.py | 66 +++--
client/wxpython/gmPersonCreationWidgets.py | 7 +-
client/wxpython/gmPregWidgets.py | 2 +-
client/wxpython/gmSubstanceMgmtWidgets.py | 442 ----------------------------
client/wxpython/gmTopPanel.py | 60 +++-
22 files changed, 586 insertions(+), 687 deletions(-)
diff --git a/client/CHANGELOG b/client/CHANGELOG
index 3f03667..afa8beb 100644
--- a/client/CHANGELOG
+++ b/client/CHANGELOG
@@ -6,6 +6,26 @@
# rel-1-5-patches
------------------------------------------------
+ 1.5.8
+
+FIX: SQL formatting when retrieving clinical narrative [thanks Marc]
+FIX: strange case of "curr_pat is None" in top panel [thanks Marc]
+
+ 1.5.7
+
+FIX: one more nonissue-problem tooltip exception in SOAP editor [thanks Marc]
+FIX: encounter change exception on patient change w/ multiple clients [thanks Marc]
+FIX: patient overview tooltip exception on patient change [thanks Marc]
+FIX: mysterious non-problem with missing "Gnumed." in import [thanks Basti]
+FIX: symlink creation on Windows
+
+IMPROVED: logging of payload changes in case of conflict
+IMPROVED: early startup logging
+IMPROVED: show low file location during startup
+IMPROVED: windows startup batch file
+IMPROVED: redirect wxPython log to python logging
+IMPROVED: set wxPython AssertMode appropriately
+
1.5.6
FIX: exception on removing temporary config file [thanks Vaibhav]
diff --git a/client/business/gmClinicalRecord.py b/client/business/gmClinicalRecord.py
index 97d5b68..013ffc5 100644
--- a/client/business/gmClinicalRecord.py
+++ b/client/business/gmClinicalRecord.py
@@ -138,10 +138,70 @@ class cClinicalRecord(object):
# messaging
#--------------------------------------------------------
def _register_interests(self):
- gmDispatcher.connect(signal = u'clin.encounter_mod_db', receiver = self.db_callback_encounter_mod_db)
+ #gmDispatcher.connect(signal = u'clin.encounter_mod_db', receiver = self.db_callback_encounter_mod_db)
+ gmDispatcher.connect(signal = u'gm_table_mod', receiver = self.db_modification_callback)
return True
#--------------------------------------------------------
+ def db_modification_callback(self, **kwds):
+
+ if kwds['table'] != u'clin.encounter':
+ return True
+ if self.current_encounter is None:
+ _log.debug('no local current-encounter, ignoring encounter modification signal')
+ return True
+ if int(kwds['pk_row']) <> self.current_encounter['pk_encounter']:
+ _log.debug('modified encounter [%s] != local encounter [%s], ignoring signal', kwds['pk_row'], self.current_encounter['pk_encounter'])
+ return True
+
+ # get the current encounter as an extra instance
+ # from the database to check for changes
+ curr_enc_in_db = gmEMRStructItems.cEncounter(aPK_obj = self.current_encounter['pk_encounter'])
+
+ # the encounter just retrieved and the active encounter
+ # have got the same transaction ID so there's no change
+ # in the database, there could be a local change in
+ # the active encounter but that doesn't matter because
+ # no one else can have written to the DB so far
+ if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
+ _log.debug('same XMIN, there really should not be any difference between DB and in-client instance of current encounter')
+ if self.current_encounter.is_modified():
+ _log.error('encounter modification signal from DB with same XMIN as in local in-client instance of encounter BUT local instance ALSO has .is_modified()=True')
+ _log.error('this hints at an error in .is_modified handling')
+ gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
+ return True
+
+ # there must have been a change to the active encounter
+ # committed to the database from elsewhere,
+ # we must fail propagating the change, however, if
+ # there are local changes pending
+ if self.current_encounter.is_modified():
+ gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
+ raise ValueError('unsaved changes in locally active encounter [%s], cannot switch to DB state of encounter [%s]' % (
+ self.current_encounter['pk_encounter'],
+ curr_enc_in_db['pk_encounter']
+ ))
+
+ # don't do this: same_payload() does not compare _all_ fields
+ # so we can get into a reality disconnect if we don't
+ # announce the mod
+# if self.current_encounter.same_payload(another_object = curr_enc_in_db):
+# _log.debug('clin.encounter_mod_db received but no change to active encounter payload')
+# return True
+
+ # there was a change in the database from elsewhere,
+ # locally, however, we don't have any pending changes,
+ # therefore we can propagate the remote change locally
+ # without losing anything
+ # this really should be the standard case
+ gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
+ _log.debug('active encounter modified remotely, no locally pending changes, reloading from DB and locally announcing the remote modification')
+ self.current_encounter.refetch_payload()
+ gmDispatcher.send(u'current_encounter_modified')
+
+ return True
+
+ #--------------------------------------------------------
def db_callback_encounter_mod_db(self, **kwds):
# get the current encounter as an extra instance
@@ -151,7 +211,8 @@ class cClinicalRecord(object):
# the encounter just retrieved and the active encounter
# have got the same transaction ID so there's no change
# in the database, there could be a local change in
- # the active encounter but that doesn't matter
+ # the active encounter but that doesn't matter because
+ # no one else can have written to the DB so far
# THIS DOES NOT WORK
# if curr_enc_in_db['xmin_encounter'] == self.current_encounter['xmin_encounter']:
# return True
@@ -161,8 +222,9 @@ class cClinicalRecord(object):
# we must fail propagating the change, however, if
# there are local changes
if self.current_encounter.is_modified():
+ gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
_log.error('current in client: %s', self.current_encounter)
- raise ValueError('unsaved changes in active encounter [%s], cannot switch to another one [%s]' % (
+ raise ValueError('unsaved changes in active encounter [%s], cannot switch [%s]' % (
self.current_encounter['pk_encounter'],
curr_enc_in_db['pk_encounter']
))
@@ -175,7 +237,8 @@ class cClinicalRecord(object):
# locally, however, we don't have any changes, therefore
# we can propagate the remote change locally without
# losing anything
- _log.debug('active encounter modified remotely, reloading and announcing the modification')
+ gmTools.compare_dict_likes(self.current_encounter.fields_as_dict(), curr_enc_in_db.fields_as_dict(), 'modified enc in client', 'enc loaded from DB')
+ _log.debug('active encounter modified remotely, reloading from DB and locally announcing the modification')
self.current_encounter.refetch_payload()
gmDispatcher.send(u'current_encounter_modified')
@@ -409,16 +472,17 @@ class cClinicalRecord(object):
_log.error(str(data))
return None
return data
+
#--------------------------------------------------------
def get_clin_narrative(self, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None):
"""Get SOAP notes pertinent to this encounter.
encounters
- - list of encounters whose narrative are to be retrieved
+ - list of encounters the narrative of which are to be retrieved
episodes
- - list of episodes whose narrative are to be retrieved
+ - list of episodes the narrative of which are to be retrieved
issues
- - list of health issues whose narrative are to be retrieved
+ - list of health issues the narrative of which are to be retrieved
soap_cats
- list of SOAP categories of the narrative to be retrieved
"""
@@ -435,7 +499,7 @@ class cClinicalRecord(object):
elif isinstance(issues[0], int):
args['issues'] = tuple(issues)
else:
- raise ValueError('<issues> must of of type int (=pk) or cHealthIssue, but 1st issue is: %s' % issues[0])
+ raise ValueError('<issues> must be list of type int (=pk) or cHealthIssue, but 1st issue is: %s' % issues[0])
if episodes is not None:
where_parts.append(u'pk_episode IN %(epis)s')
@@ -447,7 +511,7 @@ class cClinicalRecord(object):
elif isinstance(episodes[0], int):
args['epis'] = tuple(episodes)
else:
- raise ValueError('<episodes> must of of type int (=pk) or cEpisode, but 1st episode is: %s' % episodes[0])
+ raise ValueError('<episodes> must be list of type int (=pk) or cEpisode, but 1st episode is: %s' % episodes[0])
if encounters is not None:
where_parts.append(u'pk_encounter IN %(encs)s')
@@ -459,7 +523,7 @@ class cClinicalRecord(object):
elif isinstance(encounters[0], int):
args['encs'] = tuple(encounters)
else:
- raise ValueError('<encounters> must of of type int (=pk) or cEncounter, but 1st encounter is: %s' % encounters[0])
+ raise ValueError('<encounters> must be list of type int (=pk) or cEncounter, but 1st encounter is: %s' % encounters[0])
if soap_cats is not None:
where_parts.append(u'c_vn.soap_cat IN %(cats)s')
@@ -469,6 +533,10 @@ class cClinicalRecord(object):
args['cats'].append(None)
args['cats'] = tuple(args['cats'])
+ if providers is not None:
+ where_parts.append(u'c_vn.modified_by IN %(docs)s')
+ args['docs'] = tuple(providers)
+
cmd = u"""
SELECT
c_vn.*,
@@ -481,13 +549,8 @@ class cClinicalRecord(object):
""" % u' AND '.join(where_parts)
rows, idx = gmPG2.run_ro_queries(queries = [{'cmd': cmd, 'args': args}], get_col_idx = True)
+ return [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
- filtered_narrative = [ gmClinNarrative.cNarrative(row = {'pk_field': 'pk_narrative', 'idx': idx, 'data': row}) for row in rows ]
-
- if providers is not None:
- filtered_narrative = filter(lambda narr: narr['modified_by'] in providers, filtered_narrative)
-
- return filtered_narrative
#--------------------------------------------------------
def get_as_journal(self, since=None, until=None, encounters=None, episodes=None, issues=None, soap_cats=None, providers=None, order_by=None, time_range=None):
return gmClinNarrative.get_as_journal (
@@ -1698,6 +1761,7 @@ WHERE
_log.debug('switching of active encounter')
# fail if the currently active encounter has unsaved changes
if self.__encounter.is_modified():
+ gmTools.compare_dict_likes(self.__encounter, encounter, 'modified enc in client', 'enc to switch to')
_log.error('current in client: %s', self.__encounter)
raise ValueError('unsaved changes in active encounter [%s], cannot switch to another one [%s]' % (
self.__encounter['pk_encounter'],
diff --git a/client/business/gmEMRStructItems.py b/client/business/gmEMRStructItems.py
index 5538f6a..7cfd862 100644
--- a/client/business/gmEMRStructItems.py
+++ b/client/business/gmEMRStructItems.py
@@ -1625,7 +1625,7 @@ def episode2problem(episode=None, allow_closed=False):
class cEncounter(gmBusinessDBObject.cBusinessDBObject):
"""Represents one encounter."""
- _cmd_fetch_payload = u"select * from clin.v_pat_encounters where pk_encounter = %s"
+ _cmd_fetch_payload = u"SELECT * FROM clin.v_pat_encounters WHERE pk_encounter = %s"
_cmds_store_payload = [
u"""UPDATE clin.encounter SET
started = %(started)s,
@@ -1639,7 +1639,7 @@ class cEncounter(gmBusinessDBObject.cBusinessDBObject):
xmin = %(xmin_encounter)s
""",
# need to return all fields so we can survive in-place upgrades
- u"""select * from clin.v_pat_encounters where pk_encounter = %(pk_encounter)s"""
+ u"SELECT * FROM clin.v_pat_encounters WHERE pk_encounter = %(pk_encounter)s"
]
_updatable_fields = [
'started',
diff --git a/client/business/gmPerson.py b/client/business/gmPerson.py
index 32b318c..84ac92b 100644
--- a/client/business/gmPerson.py
+++ b/client/business/gmPerson.py
@@ -23,6 +23,7 @@ from xml.etree import ElementTree as etree
# GNUmed
if __name__ == '__main__':
+ logging.basicConfig(level = logging.DEBUG)
sys.path.insert(0, '../../')
from Gnumed.pycommon import gmExceptions
from Gnumed.pycommon import gmDispatcher
@@ -620,8 +621,15 @@ class cIdentity(gmBusinessDBObject.cBusinessDBObject):
active name.
@param nickname The preferred/nick/warrior name to set.
"""
- rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': u"select dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}])
- self.refetch_payload()
+ if self._payload[self._idx['preferred']] == nickname:
+ return True
+ rows, idx = gmPG2.run_rw_queries(queries = [{'cmd': u"SELECT dem.set_nickname(%s, %s)", 'args': [self.ID, nickname]}])
+ # setting nickname doesn't change dem.identity, so other fields
+ # of dem.v_basic_person do not get changed as a consequence of
+ # setting the nickname, hence locally setting nickname matches
+ # in-database reality
+ self._payload[self._idx['preferred']] = nickname
+ #self.refetch_payload()
return True
#--------------------------------------------------------
def get_tags(self, order_by=None):
@@ -1732,7 +1740,7 @@ class cPatient(cIdentity):
# return self.__emr
def get_emr(self, allow_user_interaction=True):
- _log.debug('accessing EMR (thread %s)', thread.get_ident())
+ _log.debug('accessing EMR for identity [%s] (thread %s)', self._payload[self._idx['pk_identity']], thread.get_ident())
if not self.__emr_access_lock.acquire(False):
got_lock = False
for idx in range(100):
@@ -1744,7 +1752,7 @@ class cPatient(cIdentity):
break
if not got_lock:
_log.error('still failed to acquire EMR access lock, aborting (thread %s)', thread.get_ident())
- raise AttributeError('cannot lock access to EMR')
+ raise AttributeError('cannot lock access to EMR for identity [%s]', self._payload[self._idx['pk_identity']])
# # maybe something slow is happening on the machine
# _log.debug('failed to acquire EMR access lock, sleeping for 500ms (thread %s)', thread.get_ident())
@@ -1754,14 +1762,14 @@ class cPatient(cIdentity):
# raise AttributeError('cannot lock access to EMR')
if self.__emr is None:
- _log.debug('pulling chart (thread %s)', thread.get_ident())
+ _log.debug('pulling chart for identity [%s] (thread %s)', self._payload[self._idx['pk_identity']], thread.get_ident())
#emr = _pull_chart(self._payload[self._idx['pk_identity']])
emr = _pull_chart(self)
if emr is None: # user aborted pulling chart
return None
self.__emr = emr
- _log.debug('returning EMR (thread %s)', thread.get_ident())
+ _log.debug('returning EMR for identity [%s] (thread %s)', self._payload[self._idx['pk_identity']], thread.get_ident())
self.__emr_access_lock.release()
return self.__emr
@@ -1823,7 +1831,7 @@ class gmCurrentPatient(gmBorg.cBorg):
"""
# make sure we do have a patient pointer
try:
- tmp = self.patient
+ self.patient
except AttributeError:
self.patient = gmNull.cNull()
self.__register_interests()
@@ -1847,7 +1855,7 @@ class gmCurrentPatient(gmBorg.cBorg):
if patient == -1:
_log.debug('explicitly unsetting current patient')
if not self.__run_callbacks_before_switching_away_from_patient():
- _log.debug('not unsetting current patient')
+ _log.error('not unsetting current patient, at least one pre-change callback failed')
return None
self.__send_pre_unselection_notification()
self.patient.cleanup()
@@ -1868,10 +1876,10 @@ class gmCurrentPatient(gmBorg.cBorg):
return None
# user wants different patient
- _log.debug('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity'])
+ _log.info('patient change [%s] -> [%s] requested', self.patient['pk_identity'], patient['pk_identity'])
if not self.__run_callbacks_before_switching_away_from_patient():
- _log.debug('not changing current patient')
+ _log.error('not changing current patient, at least one pre-change callback failed')
return None
# everything seems swell
@@ -1890,12 +1898,30 @@ class gmCurrentPatient(gmBorg.cBorg):
return None
#--------------------------------------------------------
def __register_interests(self):
- gmDispatcher.connect(signal = u'dem.identity_mod_db', receiver = self._on_identity_change)
- gmDispatcher.connect(signal = u'dem.names_mod_db', receiver = self._on_identity_change)
+ gmDispatcher.connect(signal = u'gm_table_mod', receiver = self._on_database_signal)
+
#--------------------------------------------------------
- def _on_identity_change(self):
- """Listen for patient *data* change."""
+ def _on_database_signal(self, **kwds):
+ # we don't have a patient: don't process signals
+ if isinstance(self.patient, gmNull.cNull):
+ return True
+
+ # we only care about identity and name changes
+ if kwds['table'] not in [u'dem.identity', u'dem.names']:
+ return True
+
+ # signal is not about our patient: ignore signal
+ if int(kwds['pk_identity']) != self.patient.ID:
+ return True
+
+ if kwds['table'] == u'dem.identity':
+ # we don't care about newly INSERTed or DELETEd patients
+ if kwds['operation'] != 'UPDATE':
+ return True
+
self.patient.refetch_payload()
+ return True
+
#--------------------------------------------------------
# external API
#--------------------------------------------------------
@@ -1909,14 +1935,13 @@ class gmCurrentPatient(gmBorg.cBorg):
raise TypeError(u'callback [%s] not callable' % callback)
self.__callbacks_before_switching_away_from_patient.append(callback)
+
#--------------------------------------------------------
def _get_connected(self):
return (not isinstance(self.patient, gmNull.cNull))
- def _set_connected(self):
- raise AttributeError(u'invalid to set <connected> state')
+ connected = property(_get_connected, lambda x:x)
- connected = property(_get_connected, _set_connected)
#--------------------------------------------------------
def _get_locked(self):
return (self.__lock_depth > 0)
@@ -1927,18 +1952,20 @@ class gmCurrentPatient(gmBorg.cBorg):
gmDispatcher.send(signal = 'patient_locked', sender = self.__class__.__name__)
else:
if self.__lock_depth == 0:
- _log.error('lock/unlock imbalance, trying to refcount lock depth below 0')
+ _log.error('lock/unlock imbalance, tried to refcount lock depth below 0')
return
else:
self.__lock_depth = self.__lock_depth - 1
gmDispatcher.send(signal = 'patient_unlocked', sender = self.__class__.__name__)
locked = property(_get_locked, _set_locked)
+
#--------------------------------------------------------
def force_unlock(self):
_log.info('forced patient unlock at lock depth [%s]' % self.__lock_depth)
self.__lock_depth = 0
gmDispatcher.send(signal = 'patient_unlocked', sender = self.__class__.__name__)
+
#--------------------------------------------------------
# patient change handling
#--------------------------------------------------------
@@ -1957,10 +1984,11 @@ class gmCurrentPatient(gmBorg.cBorg):
return False
if not successful:
- _log.debug('callback [%s] returned False', call_back)
+ _log.error('callback [%s] returned False', call_back)
return False
return True
+
#--------------------------------------------------------
def __send_pre_unselection_notification(self):
"""Sends signal when current patient is about to be unset.
@@ -1973,6 +2001,7 @@ class gmCurrentPatient(gmBorg.cBorg):
'pk_identity': self.patient['pk_identity']
}
gmDispatcher.send(**kwargs)
+
#--------------------------------------------------------
def __send_unselection_notification(self):
"""Sends signal when the previously active patient has
@@ -1987,6 +2016,7 @@ class gmCurrentPatient(gmBorg.cBorg):
'sender': self.__class__.__name__
}
gmDispatcher.send(**kwargs)
+
#--------------------------------------------------------
def __send_selection_notification(self):
"""Sends signal when another patient has actually been made active."""
@@ -1996,14 +2026,24 @@ class gmCurrentPatient(gmBorg.cBorg):
'pk_identity': self.patient['pk_identity']
}
gmDispatcher.send(**kwargs)
+
#--------------------------------------------------------
# __getattr__ handling
#--------------------------------------------------------
def __getattr__(self, attribute):
+ # override __getattr__ here, not __getattribute__ because
+ # the former is used _after_ ordinary attribute lookup
+ # failed while the latter is applied _before_ ordinary
+ # lookup (and is easy to drive into infinite recursion),
+ # this is also why subsequent access to self.patient
+ # simply returns the .patient member value :-)
if attribute == 'patient':
raise AttributeError
- if not isinstance(self.patient, gmNull.cNull):
- return getattr(self.patient, attribute)
+ if isinstance(self.patient, gmNull.cNull):
+ _log.error("[%s]: cannot getattr(%s, '%s'), patient attribute not connected to a patient", self, self.patient, attribute)
+ raise AttributeError("[%s]: cannot getattr(%s, '%s'), patient attribute not connected to a patient" % (self, self.patient, attribute))
+ return getattr(self.patient, attribute)
+
#--------------------------------------------------------
# __get/setitem__ handling
#--------------------------------------------------------
@@ -2011,9 +2051,11 @@ class gmCurrentPatient(gmBorg.cBorg):
"""Return any attribute if known how to retrieve it by proxy.
"""
return self.patient[attribute]
+
#--------------------------------------------------------
def __setitem__(self, attribute, value):
self.patient[attribute] = value
+
#============================================================
# match providers
#============================================================
@@ -2060,14 +2102,16 @@ INSERT INTO dem.names (
) VALUES (
currval('dem.identity_pk_seq'), coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx')
) RETURNING id_identity"""
+# cmd2 = u"select dem.add_name(currval('dem.identity_pk_seq')::integer, coalesce(%s, 'xxxDEFAULTxxx'), coalesce(%s, 'xxxDEFAULTxxx'), True)"
rows, idx = gmPG2.run_rw_queries (
queries = [
{'cmd': cmd1, 'args': [gender, dob]},
{'cmd': cmd2, 'args': [lastnames, firstnames]}
+ #{'cmd': cmd2, 'args': [firstnames, lastnames]}
],
return_data = True
)
- ident = cIdentity(aPK_obj=rows[0][0])
+ ident = cIdentity(aPK_obj = rows[0][0])
gmHooks.run_hook_script(hook = u'post_person_creation')
return ident
@@ -2376,6 +2420,12 @@ if __name__ == '__main__':
def test_vcf():
person = cIdentity(aPK_obj = 12)
print person.export_as_vcard()
+
+ #--------------------------------------------------------
+ def test_current_patient():
+ pat = gmCurrentPatient()
+ print "pat.get_emr()", pat.get_emr()
+
#--------------------------------------------------------
#test_dto_person()
#test_identity()
@@ -2391,6 +2441,7 @@ if __name__ == '__main__':
#print "\n\nRetrieving communication media enum (id, description): %s" % comms
#test_export_area()
#test_ext_id()
- test_vcf()
+ #test_vcf()
+ test_current_patient()
#============================================================
diff --git a/client/business/gmStaff.py b/client/business/gmStaff.py
index 802bd12..bf5391e 100644
--- a/client/business/gmStaff.py
+++ b/client/business/gmStaff.py
@@ -244,6 +244,7 @@ def deactivate_staff(conn=None, pk_staff=None):
#============================================================
def set_current_provider_to_logged_on_user():
gmCurrentProvider(provider = cStaff())
+
#============================================================
class gmCurrentProvider(gmBorg.cBorg):
"""Staff member Borg to hold currently logged on provider.
@@ -271,15 +272,15 @@ class gmCurrentProvider(gmBorg.cBorg):
if not isinstance(provider, cStaff):
raise ValueError, 'cannot set logged on provider to [%s], must be either None or cStaff instance' % str(provider)
- # same ID, no change needed
- if self.provider['pk_staff'] == provider['pk_staff']:
- return None
-
# first invocation
if isinstance(self.provider, gmNull.cNull):
self.provider = provider
return None
+ # same ID, no change needed
+ if self.provider['pk_staff'] == provider['pk_staff']:
+ return None
+
# user wants different provider
raise ValueError, 'provider change [%s] -> [%s] not yet supported' % (self.provider['pk_staff'], provider['pk_staff'])
diff --git a/client/doc/schema/gnumed-entire_schema.html b/client/doc/schema/gnumed-entire_schema.html
index 3e7c538..ac003b1 100644
--- a/client/doc/schema/gnumed-entire_schema.html
+++ b/client/doc/schema/gnumed-entire_schema.html
@@ -112,7 +112,7 @@
<body>
<!-- Primary Index -->
- <p><br><br>Dumped on 2015-07-08</p>
+ <p><br><br>Dumped on 2015-10-11</p>
<h1><a name="index">Index of database - gnumed_v20</a></h1>
<ul>
diff --git a/client/gm-from-vcs.bat b/client/gm-from-vcs.bat
index e84baec..215e867 100755
--- a/client/gm-from-vcs.bat
+++ b/client/gm-from-vcs.bat
@@ -1,4 +1,19 @@
-mklink /J ..\Gnumed ..\client
-set PYTHONPATH=..\;%PYTHONPATH%
-Python gnumed.py --log-file=gm-from-vcs.log --conf-file=gm-from-vcs.conf --debug
+REM # GNUmed tarball startup batch file
+
+REM # normally we would like to use a link but Python
+REM # on Windows seems to have problems importing
+REM # modules from directory links
+REM mklink /J ..\Gnumed ..\client
+
+REM # hence we use xcopy: http://commandwindows.com/xcopy.htm
+REM # but need to remove old link first (if any)
+fsutil reparsepoint delete ..\Gnumed
+REM # if it still exists it shouldn't be a link, so remove the directory now
+rmdir ..\Gnumed /s
+xcopy ..\client ..\Gnumed /E /I /F /H /O /Y
+
+set PYTHONPATH=..;%PYTHONPATH%
+
+REM echo Log file: ./gm-from-vcs.log
+Python gnumed.py --log-file=gm-from-vcs.log --conf-file=gm-from-vcs.conf --local-import --debug
diff --git a/client/gnumed.py b/client/gnumed.py
index f75f658..1f077e1 100644
--- a/client/gnumed.py
+++ b/client/gnumed.py
@@ -86,10 +86,11 @@ against. Please run GNUmed as a non-root user.
sys.exit(1)
#----------------------------------------------------------
-current_client_version = u'1.5.6'
+current_client_version = u'1.5.8'
current_client_branch = u'1.5'
_log = None
+_pre_log_buffer = []
_cfg = None
_old_sig_term = None
_known_short_options = u'h?V'
@@ -167,28 +168,55 @@ Cannot run GNUmed without any of them.
#==========================================================
# convenience functions
#==========================================================
+def _symlink_windows(source, link_name):
+ import ctypes
+ csl = ctypes.windll.kernel32.CreateSymbolicLinkW
+ csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32)
+ csl.restype = ctypes.c_ubyte
+ if os.path.isdir(source):
+ flags = 1
+ else:
+ flags = 0
+ ret_code = csl(link_name, source.replace('/', '\\'), flags)
+ if ret_code == 0:
+ raise ctypes.WinError()
+ return ret_code
+
+#==========================================================
def setup_python_path():
if not u'--local-import' in sys.argv:
+ _pre_log_buffer.append('running against systemwide install')
return
- local_python_base_dir = os.path.dirname (
+ local_python_import_dir = os.path.dirname (
os.path.abspath(os.path.join(sys.argv[0], '..'))
)
- print "Running from local source tree (%s) ..." % local_python_base_dir
+ print "Running from local source tree (%s) ..." % local_python_import_dir
+ _pre_log_buffer.append("running from local source tree: %s" % local_python_import_dir)
# does the path exist at all, physically ?
# (*broken* links are reported as False)
- link_name = os.path.join(local_python_base_dir, 'Gnumed')
- if not os.path.exists(link_name):
- real_dir = os.path.join(local_python_base_dir, 'client')
- print "Creating module import symlink ..."
+ link_name = os.path.join(local_python_import_dir, 'Gnumed')
+ if os.path.exists(link_name):
+ _pre_log_buffer.append('local module import dir symlink exists: %s' % link_name)
+ else:
+ real_dir = os.path.join(local_python_import_dir, 'client')
+ print "Creating local module import symlink ..."
print ' real dir:', real_dir
print ' link:', link_name
- os.symlink(real_dir, link_name)
+ try:
+ os.symlink(real_dir, link_name)
+ except AttributeError:
+ _pre_log_buffer.append('Windows does not have os.symlink(), resorting to ctypes')
+ result = _symlink_windows(real_dir, link_name)
+ _pre_log_buffer.append('ctypes.windll.kernel32.CreateSymbolicLinkW() exit code: %s', result)
+ _pre_log_buffer.append('created local module import dir symlink: link [%s] => dir [%s]' % (link_name, real_dir))
print "Adjusting PYTHONPATH ..."
- sys.path.insert(0, local_python_base_dir)
+ sys.path.insert(0, local_python_import_dir)
+ _pre_log_buffer.append('sys.path with local module import base dir prepended: %s' % sys.path)
+
#==========================================================
def setup_local_repo_path():
@@ -252,10 +280,13 @@ def setup_fault_handler(target=None):
import faulthandler
except ImportError:
print "Faulthandler not available ..."
+ _pre_log_buffer.append('<faulthandler> not available')
return
if target is None:
faulthandler.enable()
+ _pre_log_buffer.append('<faulthandler> enabled, target = [console]: %s (%s)' % (faulthandler, faulthandler.__version__))
return
+ _pre_log_buffer.append('<faulthandler> enabled, target = [%s]: %s (%s)' % (target, faulthandler, faulthandler.__version__))
faulthandler.enable(file = target)
#==========================================================
@@ -265,6 +296,7 @@ def setup_logging():
except ImportError:
sys.exit(import_error_sermon % '\n '.join(sys.path))
+ print "Log file: %s" % _gmLog2._logfile.name
setup_fault_handler(target = _gmLog2._logfile)
global gmLog2
@@ -272,27 +304,37 @@ def setup_logging():
global _log
_log = logging.getLogger('gm.launcher')
+
#==========================================================
def log_startup_info():
+ global _pre_log_buffer
+ if len(_pre_log_buffer) > 0:
+ _log.info('early startup log buffer:')
+ for line in _pre_log_buffer:
+ _log.info(u' ' + line)
+ del _pre_log_buffer
_log.info(u'GNUmed client version [%s] on branch [%s]', current_client_version, current_client_branch)
_log.info(u'Platform: %s', platform.uname())
- _log.info(u'Python %s on %s (%s)', sys.version, sys.platform, os.name)
+ _log.info((u'Python %s on %s (%s)' % (sys.version, sys.platform, os.name)).replace(u'\n', u'<\\n>'))
try:
import lsb_release
- _log.info(u'%s' % lsb_release.get_distro_information())
+ _log.info(u'lsb_release: %s', lsb_release.get_distro_information())
except ImportError:
pass
+ _log.info('os.getcwd(): [%s]', os.getcwd())
_log.info('process environment:')
for key, val in os.environ.items():
_log.info(u' %s: %s' % (
(u'${%s}' % key).rjust(30),
unicode(val, encoding = sys.getfilesystemencoding(), errors = 'replace')
))
+
#==========================================================
def setup_console_exception_handler():
from Gnumed.pycommon.gmTools import handle_uncaught_exception_console
sys.excepthook = handle_uncaught_exception_console
+
#==========================================================
def setup_cli():
from Gnumed.pycommon import gmCfg2
@@ -368,10 +410,12 @@ def handle_sig_term(signum, frame):
sys.exit(signal.SIGTERM)
else:
_old_sig_term(signum, frame)
+
#----------------------------------------------------------
def setup_signal_handlers():
global _old_sig_term
old_sig_term = signal.signal(signal.SIGTERM, handle_sig_term)
+
#==========================================================
def setup_locale():
gmI18N.activate_locale()
diff --git a/client/pycommon/gmBackendListener.py b/client/pycommon/gmBackendListener.py
index ca9fac5..665f029 100644
--- a/client/pycommon/gmBackendListener.py
+++ b/client/pycommon/gmBackendListener.py
@@ -57,7 +57,7 @@ class gmBackendListener(gmBorg.cBorg):
self._conn = conn
self.backend_pid = self._conn.get_backend_pid()
- _log.debug('connection has backend PID [%s]', self.backend_pid)
+ _log.debug('notification listener connection has backend PID [%s]', self.backend_pid)
self._conn.set_isolation_level(0) # autocommit mode = psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
self._cursor = self._conn.cursor()
try:
@@ -217,7 +217,7 @@ class gmBackendListener(gmBorg.cBorg):
self.__notifications_received += 1
if self.debug:
print notification
- _log.debug('#%s: %s', self.__notifications_received, notification)
+ _log.debug('#%s: %s (first param is PID of sending backend)', self.__notifications_received, notification)
# decode payload
payload = notification.payload.split(u'::')
operation = None
diff --git a/client/pycommon/gmBorg.py b/client/pycommon/gmBorg.py
index 37132c2..76864b2 100644
--- a/client/pycommon/gmBorg.py
+++ b/client/pycommon/gmBorg.py
@@ -1,10 +1,8 @@
#===================================================
# Thanks to Python Patterns !
# ---------------------------
-# $Id: gmBorg.py,v 1.7 2009-05-08 07:58:35 ncq Exp $
-__version__ = "$Revision: 1.7 $"
__author__ = "Karsten.Hilbert at gmx.net"
-__license__ = "GPL"
+__license__ = "GPL v2 or later"
#===================================================
class cBorg(object):
@@ -12,7 +10,7 @@ class cBorg(object):
- mixin this class with your class' ancestors to borg it
- - there may be many instances of this - PER CHILD CLASS - but they all share state
+ - there may be many _instances_ of this - PER CHILD CLASS - but they all share _state_
"""
_instances = {}
@@ -22,6 +20,7 @@ class cBorg(object):
#cBorg._instances[cls] = object.__new__(cls, *args, **kargs)
cBorg._instances[cls] = object.__new__(cls)
return cBorg._instances[cls]
+
#===================================================
if __name__ == '__main__':
@@ -57,39 +56,3 @@ if __name__ == '__main__':
print c3.x
#===================================================
-# $Log: gmBorg.py,v $
-# Revision 1.7 2009-05-08 07:58:35 ncq
-# - __new__ doesn't take args anymore
-#
-# Revision 1.6 2008/05/21 13:57:57 ncq
-# - remove old borg
-#
-# Revision 1.5 2007/10/23 21:23:30 ncq
-# - cleanup
-#
-# Revision 1.4 2007/09/24 22:05:23 ncq
-# - improved docs
-#
-# Revision 1.3 2007/05/11 14:14:59 ncq
-# - make borg per-sublcass
-#
-# Revision 1.2 2007/05/07 12:30:05 ncq
-# - make cBorg an object child so properties work on it
-#
-# Revision 1.1 2004/02/25 09:30:13 ncq
-# - moved here from python-common
-#
-# Revision 1.3 2003/12/29 16:21:51 uid66147
-# - spelling fix
-#
-# Revision 1.2 2003/11/17 10:56:35 sjtan
-#
-# synced and commiting.
-#
-# Revision 1.1 2003/10/23 06:02:38 sjtan
-#
-# manual edit areas modelled after r.terry's specs.
-#
-# Revision 1.1 2003/04/02 16:07:55 ncq
-# - first version
-#
diff --git a/client/pycommon/gmBusinessDBObject.py b/client/pycommon/gmBusinessDBObject.py
index 7aadbde..19589f9 100644
--- a/client/pycommon/gmBusinessDBObject.py
+++ b/client/pycommon/gmBusinessDBObject.py
@@ -141,7 +141,9 @@ if __name__ == '__main__':
from Gnumed.pycommon import gmExceptions
from Gnumed.pycommon import gmPG2
from Gnumed.pycommon.gmDateTime import pydt_strftime
-from Gnumed.pycommon.gmTools import tex_escape_string, xetex_escape_string
+from Gnumed.pycommon.gmTools import tex_escape_string, xetex_escape_string, compare_dict_likes
+from Gnumed.pycommon.gmTools import xetex_escape_string
+from Gnumed.pycommon.gmTools import compare_dict_likes
_log = logging.getLogger('gm.db')
@@ -483,7 +485,7 @@ def delete_xxx(pk_XXX=None):
#--------------------------------------------------------
def fields_as_dict(self, date_format='%Y %b %d %H:%M', none_string=u'', escape_style=None, bool_strings=None):
if bool_strings is None:
- bools = {True: u'true', False: u'false'}
+ bools = {True: u'True', False: u'False'}
else:
bools = {True: bool_strings[0], False: bool_strings[1]}
data = {}
@@ -501,6 +503,9 @@ def delete_xxx(pk_XXX=None):
continue
if isinstance(val, datetime.datetime):
+ if date_format is None:
+ data[field] = val
+ continue
data[field] = pydt_strftime(val, format = date_format, encoding = 'utf8')
if escape_style in [u'latex', u'tex']:
data[field] = tex_escape_string(data[field])
@@ -534,10 +539,11 @@ def delete_xxx(pk_XXX=None):
"""Fetch field values from backend.
"""
if self._is_modified:
+ compare_dict_likes(self.original_payload, self.fields_as_dict(date_format = None, none_string = None), u'original payload', u'modified payload')
if ignore_changes:
_log.critical('[%s:%s]: loosing payload changes' % (self.__class__.__name__, self.pk_obj))
- _log.debug('original: %s' % self.original_payload)
- _log.debug('modified: %s' % self._payload)
+ #_log.debug('original: %s' % self.original_payload)
+ #_log.debug('modified: %s' % self._payload)
else:
_log.critical('[%s:%s]: cannot reload, payload changed' % (self.__class__.__name__, self.pk_obj))
return False
@@ -604,10 +610,11 @@ def delete_xxx(pk_XXX=None):
if len(rows) == 0:
return (False, (u'cannot update row', _('[%s:%s]: row not updated (nothing returned), row in use ?') % (self.__class__.__name__, self.pk_obj)))
- # update cached values from should-be-first-and-only result
- # row of last query,
+ # update cached values from should-be-first-and-only
+ # result row of last query,
# update all fields returned such that computed
- # columns see their new values
+ # columns see their new values (given they are
+ # returned by the query)
row = rows[0]
for key in idx:
try:
@@ -621,10 +628,14 @@ def delete_xxx(pk_XXX=None):
_log.error(args)
raise
+ # only at conn.commit() time will data actually
+ # get committed (and thusly trigger based notifications
+ # be sent out), so reset the local modification flag
+ # right before that
+ self._is_modified = False
conn.commit()
close_conn()
- self._is_modified = False
# update to new "original" payload
self.original_payload = {}
for field in self._idx.keys():
diff --git a/client/pycommon/gmNull.py b/client/pycommon/gmNull.py
index 1ac21e9..2bc48a3 100644
--- a/client/pycommon/gmNull.py
+++ b/client/pycommon/gmNull.py
@@ -35,19 +35,19 @@ combinations of these words: Null, object, design and pattern.
Dinu C. Gherman,
August 2001
-For modifications see CVS changelog below.
-
Karsten Hilbert
July 2004
"""
#==============================================================
-# $Source: /home/ncq/Projekte/cvs2git/vcs-mirror/gnumed/gnumed/client/pycommon/gmNull.py,v $
-__version__ = "$Revision: 1.6 $"
-__author__ = "Dinu C. Gherman"
+__author__ = "Dinu C. Gherman, Karsten Hilbert"
__license__ = "GPL v2 or later (details at http://www.gnu.org)"
+import logging
+
+_log = logging.getLogger('cNull')
+
#==============================================================
-class cNull:
+class cNull(object):
"""A class for implementing Null objects.
This class ignores all parameters passed when constructing or
@@ -61,69 +61,67 @@ class cNull:
on the environment and, hence, these special methods are not
provided here.
"""
- _warn = 0
-
# object constructing
-
def __init__(self, *args, **kwargs):
"Ignore parameters."
- try:
- cNull._warn = kwargs['warn']
- except KeyError:
- pass
- return None
+ _log.debug(u'args: %s', args)
+ _log.debug(u'kwargs: %s', kwargs)
# object calling
-
def __call__(self, *args, **kwargs):
"Ignore method calls."
- if cNull._warn:
- print "cNull.__call__()"
+ _log.debug(u'args: %s', args)
+ _log.debug(u'kwargs: %s', kwargs)
return self
# attribute handling
-
- def __getattr__(self, mname):
+ def __getattr__(self, attribute):
"Ignore attribute requests."
- if cNull._warn:
- print "cNull.__getattr__()"
+ _log.debug(u'%s.%s', self, attribute)
return self
- def __setattr__(self, name, value):
+ def __setattr__(self, attribute, value):
"Ignore attribute setting."
- if cNull._warn:
- print "cNull.__setattr__()"
+ _log.debug(u'%s.%s = %s', self, attribute, value)
return self
- def __delattr__(self, name):
+ def __delattr__(self, attribute):
"Ignore deleting attributes."
- if cNull._warn:
- print "cNull.__delattr__()"
+ _log.debug(u'%s.%s', self, attribute)
return self
- # misc.
+ # item handling
+ def __getitem__(self, item):
+ "Ignore item requests."
+ _log.debug(u'%s[%s]', self, item)
+ return self
+
+ def __setitem__(self, item, value):
+ "Ignore item setting."
+ _log.debug(u'%s[%s] = %s', self, item, value)
+ return self
+ def __delitem__(self, item):
+ "Ignore deleting items."
+ _log.debug(u'%s[%s]', self, item)
+ return self
+
+ # misc.
def __repr__(self):
"Return a string representation."
- if cNull._warn:
- print "cNull.__repr__()"
return "<cNull instance @ %s>" % id(self)
def __str__(self):
"Convert to a string and return it."
- if cNull._warn:
- print "cNull.__str__()"
- return "cNull instance"
+ return 'cNull instance'
def __nonzero__(self):
- if cNull._warn:
- print "cNull.__nonzero__()"
+ _log.debug(u'returns 0')
return 0
-
+
def __len__(self):
- if cNull._warn:
- print "cNull.__len__()"
- return 0
+ _log.debug(u'0')
+ return 0
#==============================================================
def test():
@@ -133,7 +131,7 @@ def test():
n = cNull()
n = cNull('value')
- n = cNull('value', param='value', warn=1)
+ n = cNull('value', param='value')
n()
n('value')
@@ -154,6 +152,10 @@ def test():
n.attr1 = 'value'
n.attr1.attr2 = 'value'
+ n['1']
+ n['2'] = '123'
+ del n['3']
+
del n.attr1
del n.attr1.attr2.attr3
@@ -167,29 +169,7 @@ def test():
print "Null object == 1"
else:
print "Null object != 1"
-#--------------------------------------------------------------
-if __name__ == '__main__':
- test()
#==============================================================
-# $Log: gmNull.py,v $
-# Revision 1.6 2005-06-28 14:12:55 cfmoro
-# Integration in space fixes
-#
-# Revision 1.5 2004/12/22 08:40:01 ncq
-# - make output more obvious
-#
-# Revision 1.4 2004/11/24 15:49:11 ncq
-# - use 0/1 not False/True so we can run on older pythons
-#
-# Revision 1.3 2004/08/20 08:38:47 ncq
-# - robustify while working on allowing inactive patient after search
-#
-# Revision 1.2 2004/07/21 07:51:47 ncq
-# - tabified
-# - __nonzero__ added
-# - if keyword argument 'warn' is True: warn on use of Null class
-#
-# Revision 1.1 2004/07/06 00:08:31 ncq
-# - null design pattern from python cookbook
-#
+if __name__ == '__main__':
+ test()
diff --git a/client/pycommon/gmPG2.py b/client/pycommon/gmPG2.py
index da71465..4e9317b 100644
--- a/client/pycommon/gmPG2.py
+++ b/client/pycommon/gmPG2.py
@@ -353,7 +353,7 @@ where
curs.execute(cmd, args)
rows = curs.fetchall()
if len(rows) > 0:
- result = rows[0][0]
+ result = rows[0]['name']
_log.debug(u'[%s] maps to [%s]', timezone, result)
except:
_log.exception(u'cannot expand timezone abbreviation [%s]', timezone)
@@ -1603,6 +1603,7 @@ def get_raw_connection(dsn=None, verbose=False, readonly=True):
try:
conn = dbapi.connect(dsn=dsn, connection_factory=psycopg2.extras.DictConnection)
+ #conn = dbapi.connect(dsn=dsn, cursor_factory=psycopg2.extras.RealDictCursor)
except dbapi.OperationalError, e:
t, v, tb = sys.exc_info()
@@ -1930,7 +1931,7 @@ def _log_PG_settings(curs=None):
_log.error(u'cannot log PG settings (>>>show all<<< did not return rows)')
return False
for setting in settings:
- _log.debug(u'PG option [%s]: %s', setting[0], setting[1])
+ _log.debug(u'PG option [%s]: %s', setting['name'], setting['setting'])
try:
curs.execute(u'select pg_available_extensions()')
@@ -1942,7 +1943,7 @@ def _log_PG_settings(curs=None):
_log.error(u'no PG extensions available')
return False
for ext in extensions:
- _log.debug(u'PG extension: %s', ext[0])
+ _log.debug(u'PG extension: %s', ext['pg_available_extensions'])
return True
#========================================================================
diff --git a/client/pycommon/gmTools.py b/client/pycommon/gmTools.py
index 263330a..de090ee 100644
--- a/client/pycommon/gmTools.py
+++ b/client/pycommon/gmTools.py
@@ -6,13 +6,21 @@ __author__ = "K. Hilbert <Karsten.Hilbert at gmx.net>"
__license__ = "GPL v2 or later (details at http://www.gnu.org)"
# std libs
-import re as regex, sys, os, os.path, csv, tempfile, logging, hashlib
+import sys
+import os
+import os.path
+import csv
+import tempfile
+import logging
+import hashlib
import platform
import subprocess
import decimal
import getpass
-import cPickle, zlib
+import re as regex
import xml.sax.saxutils as xml_tools
+# old:
+import cPickle, zlib
# GNUmed libs
@@ -95,6 +103,12 @@ u_kanji_yen = u'\u5186' # Yen kanji
u_replacement_character = u'\ufffd'
u_link_symbol = u'\u1f517'
+_kB = 1024
+_MB = 1024 * _kB
+_GB = 1024 * _MB
+_TB = 1024 * _GB
+_PB = 1024 * _TB
+
#===========================================================================
def handle_uncaught_exception_console(t, v, tb):
@@ -105,6 +119,7 @@ def handle_uncaught_exception_console(t, v, tb):
print "`========================================================"
_log.critical('unhandled exception caught', exc_info = (t,v,tb))
sys.__excepthook__(t,v,tb)
+
#===========================================================================
# path level operations
#---------------------------------------------------------------------------
@@ -358,6 +373,7 @@ class gmPaths(gmBorg.cBorg):
return self.__tmp_dir
tmp_dir = property(_get_tmp_dir, _set_tmp_dir)
+
#===========================================================================
# file related tools
#---------------------------------------------------------------------------
@@ -396,6 +412,7 @@ def gpg_decrypt_file(filename=None, passphrase=None):
return None
return filename_decrypted
+
#---------------------------------------------------------------------------
def file2md5(filename=None, return_hex=True):
blocksize = 2**10 * 128 # 128k, since md5 uses 128 byte blocks
@@ -415,6 +432,26 @@ def file2md5(filename=None, return_hex=True):
if return_hex:
return md5.hexdigest()
return md5.digest()
+
+#---------------------------------------------------------------------------
+def file2chunked_md5(filename=None, chunk_size=500*_MB):
+ _log.debug('chunked_md5(%s, chunk_size=%s bytes)', filename, chunk_size)
+ md5_concat = u''
+ f = open(filename, 'rb')
+ while True:
+ md5 = hashlib.md5()
+ data = f.read(chunk_size)
+ if not data:
+ break
+ md5.update(data)
+ md5_concat += md5.hexdigest()
+ f.close()
+ md5 = hashlib.md5()
+ md5.update(md5_concat)
+ hex_digest = md5.hexdigest()
+ _log.debug('md5("%s"): %s', md5_concat, hex_digest)
+ return hex_digest
+
#---------------------------------------------------------------------------
def unicode2charset_encoder(unicode_csv_data, encoding='utf-8'):
for line in unicode_csv_data:
@@ -498,6 +535,7 @@ def get_unique_filename(prefix=None, suffix=None, tmp_dir=None):
f.close()
return filename
+
#===========================================================================
def import_module_from_directory(module_path=None, module_name=None, always_remove_path=False):
"""Import a module from any location."""
@@ -529,15 +567,10 @@ def import_module_from_directory(module_path=None, module_name=None, always_remo
sys.path.remove(module_path)
return module
+
#===========================================================================
# text related tools
#---------------------------------------------------------------------------
-_kB = 1024
-_MB = 1024 * _kB
-_GB = 1024 * _MB
-_TB = 1024 * _GB
-_PB = 1024 * _TB
-#---------------------------------------------------------------------------
def size2str(size=0, template=u'%s'):
if size == 1:
return template % _('1 Byte')
@@ -856,6 +889,44 @@ def html_escape_string(text=None):
return "".join(__html_escape_table.get(char, char) for char in text)
#---------------------------------------------------------------------------
+#---------------------------------------------------------------------------
+def compare_dict_likes(d1, d2, title1=None, title2=None):
+ _log.info('comparing dict-likes: %s[%s] vs %s[%s]', coalesce(title1, u'', u'"%s" '), type(d1), coalesce(title2, u'', u'"%s" '), type(d2))
+ k1 = frozenset(d1)
+ k2 = frozenset(d2)
+ different = False
+ if len(k1) != len(k2):
+ _log.info('different number of keys: %s vs %s', len(k1), len(k2))
+ different = True
+ for key in k1:
+ if key in k2:
+ if type(d1[key]) != type(d2[key]):
+ _log.info(u'%25.25s: type(dict1) = %s = >>>%s<<<' % (key, type(d1[key]), d1[key]))
+ _log.info(u'%25.25s type(dict2) = %s = >>>%s<<<' % (u'', type(d2[key]), d2[key]))
+ different = True
+ continue
+ if d1[key] == d2[key]:
+ _log.info(u'%25.25s: both = >>>%s<<<' % (key, d1[key]))
+ else:
+ _log.info(u'%25.25s: dict1 = >>>%s<<<' % (key, d1[key]))
+ _log.info(u'%25.25s dict2 = >>>%s<<<' % (u'', d2[key]))
+ different = True
+ else:
+ _log.info(u'%25.25s: %50.50s | <MISSING>' % (key, u'>>>%s<<<' % d1[key]))
+ different = True
+ for key in k2:
+ if key in k1:
+ continue
+ _log.info(u'%25.25s: %50.50s | %.50s' % (key, u'<MISSING>', u'>>>%s<<<' % d2[key]))
+ different = True
+ if different:
+ _log.info('dict-likes appear to be different from each other')
+ return False
+ _log.info('dict-likes appear equal to each other')
+ return True
+
+#---------------------------------------------------------------------------
+#---------------------------------------------------------------------------
def prompted_input(prompt=None, default=None):
"""Obtains entry from standard input.
@@ -1200,7 +1271,8 @@ second line\n
print wrap(test, 7, u' ', u' ')
#-----------------------------------------------------------------------
def test_md5():
- print '%s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
+ print 'md5 %s: %s' % (sys.argv[2], file2md5(sys.argv[2]))
+ print 'chunked md5 %s: %s' % (sys.argv[2], file2chunked_md5(sys.argv[2]))
#-----------------------------------------------------------------------
def test_unicode():
print u_link_symbol * 10
@@ -1259,6 +1331,27 @@ second line\n
def test_dir_is_empty():
print sys.argv[2], 'empty:', dir_is_empty(sys.argv[2])
#-----------------------------------------------------------------------
+ def test_compare_dicts():
+ d1 = {}
+ d2 = {}
+ d1[1] = 1
+ d1[2] = 2
+ d1[3] = 3
+ # 4
+ d1[5] = 5
+
+ d2[1] = 1
+ d2[2] = None
+ # 3
+ d2[4] = 4
+
+ compare_dict_likes(d1, d2)
+
+ d1 = {1: 1, 2: 2}
+ d2 = {1: 1, 2: 2}
+
+ compare_dict_likes(d1, d2, 'same1', 'same2')
+ #-----------------------------------------------------------------------
#test_coalesce()
#test_capitalize()
#test_import_module()
@@ -1273,13 +1366,14 @@ second line\n
#test_input2decimal()
#test_input2int()
#test_unwrap()
- #test_md5()
+ test_md5()
#test_unicode()
#test_xml_escape()
#test_gpg_decrypt()
#test_strip_trailing_empty_lines()
#test_fname_stem()
- test_tex_escape()
+ #test_tex_escape()
#test_dir_is_empty()
+ #test_compare_dicts()
#===========================================================================
diff --git a/client/wxpython/gmGuiMain.py b/client/wxpython/gmGuiMain.py
index a65455a..4578730 100644
--- a/client/wxpython/gmGuiMain.py
+++ b/client/wxpython/gmGuiMain.py
@@ -141,6 +141,17 @@ _scripting_listener = None
_original_wxEndBusyCursor = None
#==============================================================================
+class cLog_wx2gm(wx.PyLog):
+ # redirect wx.LogXXX() calls to python logging log
+ def DoLogTextAtLevel(self, level, msg):
+ _log.log(level, msg)
+
+__wxlog = cLog_wx2gm()
+_log.info('redirecting wx.Log to [%s]', __wxlog)
+wx.Log_SetActiveTarget(__wxlog)
+#wx.LogDebug('test message')
+
+#==============================================================================
class gmTopLevelFrame(wx.Frame):
"""GNUmed client's main windows frame.
@@ -237,6 +248,7 @@ class gmTopLevelFrame(wx.Frame):
_log.error('cannot switch font from [%s] (%s) to [%s]', font.GetNativeFontInfoUserDesc(), font.GetNativeFontInfoDesc(), font_face)
return
+
#----------------------------------------------
def __set_GUI_size(self):
"""Try to get previous window size from backend."""
@@ -988,18 +1000,51 @@ class gmTopLevelFrame(wx.Frame):
wx.EVT_END_SESSION(self, self._on_end_session)
gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
- gmDispatcher.connect(signal = u'dem.names_mod_db', receiver = self._on_pat_name_changed)
- gmDispatcher.connect(signal = u'dem.identity_mod_db', receiver = self._on_pat_name_changed)
- gmDispatcher.connect(signal = u'dem.praxis_branch_mod_db', receiver = self._on_pat_name_changed)
gmDispatcher.connect(signal = u'statustext', receiver = self._on_set_statustext)
gmDispatcher.connect(signal = u'request_user_attention', receiver = self._on_request_user_attention)
- # FIXME: xxxxxxx signal
- gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
gmDispatcher.connect(signal = u'register_pre_exit_callback', receiver = self._register_pre_exit_callback)
gmDispatcher.connect(signal = u'plugin_loaded', receiver = self._on_plugin_loaded)
+ gmDispatcher.connect(signal = u'db_maintenance_warning', receiver = self._on_db_maintenance_warning)
+ gmDispatcher.connect(signal = u'gm_table_mod', receiver = self._on_database_signal)
+
+ # FIXME: xxxxxxx signal
+
gmPerson.gmCurrentPatient().register_before_switching_from_patient_callback(callback = self._before_switching_from_patient_callback)
+
#----------------------------------------------
+ def _on_database_signal(self, **kwds):
+
+ if kwds['table'] == u'dem.praxis_branch':
+ if kwds['operation'] != u'UPDATE':
+ return True
+ branch = gmPraxis.gmCurrentPraxisBranch()
+ if branch['pk_praxis_branch'] != kwds['pk_row']:
+ return True
+ self.__update_window_title()
+ return True
+
+ if kwds['table'] == u'dem.names':
+ pat = gmPerson.gmCurrentPatient()
+ if pat.connected:
+ if pat.ID != kwds['pk_identity']:
+ return True
+ self.__update_window_title()
+ return True
+
+ if kwds['table'] == u'dem.identity':
+ if kwds['operation'] != u'UPDATE':
+ return True
+ pat = gmPerson.gmCurrentPatient()
+ if pat.connected:
+ if pat.ID != kwds['pk_identity']:
+ return True
+ self.__update_window_title()
+ return True
+
+ return True
+
+ #-----------------------------------------------
def _on_plugin_loaded(self, plugin_name=None, class_name=None, menu_name=None, menu_item_name=None, menu_help_string=None):
_log.debug('registering plugin with menu system')
@@ -1130,9 +1175,6 @@ class gmTopLevelFrame(wx.Frame):
gmHooks.run_hook_script(hook = u'request_user_attention')
#-----------------------------------------------
- def _on_pat_name_changed(self):
- self.__update_window_title()
- #-----------------------------------------------
def _on_post_patient_selection(self, **kwargs):
self.__update_window_title()
gmDispatcher.send(signal = 'statustext', msg = u'')
@@ -3142,6 +3184,11 @@ class gmApp(wx.App):
def OnInit(self):
+ if _cfg.get(option = 'debug'):
+ self.SetAssertMode(wx.PYAPP_ASSERT_LOG)
+ else:
+ self.SetAssertMode(wx.PYAPP_ASSERT_SUPPRESS)
+
self.__starting_up = True
gmExceptionHandlingWidgets.install_wx_exception_handler()
diff --git a/client/wxpython/gmListWidgets.py b/client/wxpython/gmListWidgets.py
index 29ed1f5..8ee6a3f 100644
--- a/client/wxpython/gmListWidgets.py
+++ b/client/wxpython/gmListWidgets.py
@@ -1041,7 +1041,7 @@ A discontinuous selection may depend on your holding down a platform-dependent m
if self.debug is not None:
_log.debug('[round %s] <%s>.GetItemCount() before DeleteAllItems(): %s (thread [%s])', tries, self.debug, self.GetItemCount(), thread.get_ident())
if not self.DeleteAllItems():
- _log.debug('<%s>.DeleteAllItems() failed', self.debug)
+ _log.error('<%s>.DeleteAllItems() failed', self.debug)
item_count = self.GetItemCount()
if item_count == 0:
return True
@@ -1070,7 +1070,7 @@ A discontinuous selection may depend on your holding down a platform-dependent m
topmost_visible = self.TopItem
if not self.remove_items_safely(max_tries = 3):
- _log.debug(", continuing and hoping for the best")
+ _log.error("cannot remove items (?), continuing and hoping for the best")
if items is None:
self.data = None
diff --git a/client/wxpython/gmNarrativeWidgets.py b/client/wxpython/gmNarrativeWidgets.py
index ac6f83a..2494064 100644
--- a/client/wxpython/gmNarrativeWidgets.py
+++ b/client/wxpython/gmNarrativeWidgets.py
@@ -1105,11 +1105,10 @@ class cSoapPluginPnl(wxgSoapPluginPnl.wxgSoapPluginPnl, gmRegetMixin.cRegetOnPai
with_rfe_aoe = True
)
- tmp = emr.active_encounter.format_soap (
- soap_cats = 'soapu',
- emr = emr,
- issues = [ problem['pk_health_issue'] ],
- )
+ if problem['pk_health_issue'] is None:
+ tmp = emr.active_encounter.format_soap(soap_cats = 'soapu', emr = emr)
+ else:
+ tmp = emr.active_encounter.format_soap(soap_cats = 'soapu', emr = emr, issues = [problem['pk_health_issue']])
if len(tmp) > 0:
soap += _('Current encounter:') + u'\n'
soap += u'\n'.join(tmp) + u'\n'
diff --git a/client/wxpython/gmPatOverviewWidgets.py b/client/wxpython/gmPatOverviewWidgets.py
index 3ac783c..2140e84 100644
--- a/client/wxpython/gmPatOverviewWidgets.py
+++ b/client/wxpython/gmPatOverviewWidgets.py
@@ -123,34 +123,17 @@ class cPatientOverviewPnl(wxgPatientOverviewPnl.wxgPatientOverviewPnl, gmRegetMi
gmDispatcher.connect(signal = u'pre_patient_unselection', receiver = self._on_pre_patient_unselection)
gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
+ # generic database change signal
+ gmDispatcher.connect(signal = u'gm_table_mod', receiver = self._on_database_signal)
+
# database change signals
- gmDispatcher.connect(signal = u'dem.identity_mod_db', receiver = self._on_post_patient_selection)
- gmDispatcher.connect(signal = u'dem.names_mod_db', receiver = self._on_post_patient_selection)
- gmDispatcher.connect(signal = u'dem.comm_channel_mod_db', receiver = self._on_post_patient_selection)
- gmDispatcher.connect(signal = u'dem.job_mod_db', receiver = self._on_post_patient_selection)
# no signal for external IDs yet
# no signal for address yet
- #gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified)
- #gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched)
-
- gmDispatcher.connect(signal = u'clin.episode_mod_db', receiver = self._on_episode_issue_mod_db)
- gmDispatcher.connect(signal = u'clin.health_issue_mod_db', receiver = self._on_episode_issue_mod_db)
-
- gmDispatcher.connect(signal = u'clin.substance_intake_mod_db', receiver = self._on_post_patient_selection)
-
- gmDispatcher.connect(signal = u'clin.hospital_stay_mod_db', receiver = self._on_post_patient_selection)
- gmDispatcher.connect(signal = u'clin.family_history_mod_db', receiver = self._on_post_patient_selection)
- gmDispatcher.connect(signal = u'clin.procedure_mod_db', receiver = self._on_post_patient_selection)
- gmDispatcher.connect(signal = u'clin.vaccination_mod_db', receiver = self._on_post_patient_selection)
- #gmDispatcher.connect(signal = u'clin.external_care_mod_db', receiver = self._on_post_patient_selection)
+ ##gmDispatcher.connect(signal = u'current_encounter_modified', receiver = self._on_current_encounter_modified)
+ ##gmDispatcher.connect(signal = u'current_encounter_switched', receiver = self._on_current_encounter_switched)
- gmDispatcher.connect(signal = u'dem.message_inbox_mod_db', receiver = self._on_post_patient_selection)
- gmDispatcher.connect(signal = u'clin.test_result_mod_db', receiver = self._on_post_patient_selection)
+ # doesn't have pk_identity:
gmDispatcher.connect(signal = u'clin.reviewed_test_results_mod_db', receiver = self._on_post_patient_selection)
- gmDispatcher.connect(signal = u'blobs.doc_med_mod_db', receiver = self._on_post_patient_selection)
-
- # generic signal
- gmDispatcher.connect(signal = u'gm_table_mod', receiver = self._on_post_patient_selection)
# synchronous signals
# self.__pat.register_before_switching_from_patient_callback(callback = self._before_switching_from_patient_callback)
@@ -170,8 +153,41 @@ class cPatientOverviewPnl(wxgPatientOverviewPnl.wxgPatientOverviewPnl, gmRegetMi
def _on_post_patient_selection(self):
self._schedule_data_reget()
#--------------------------------------------------------
- def _on_episode_issue_mod_db(self):
- self._schedule_data_reget()
+ def _on_database_signal(self, **kwds):
+
+ pat = gmPerson.gmCurrentPatient()
+ if not pat.connected:
+ # probably not needed:
+ #self._schedule_data_reget()
+ return True
+
+ if kwds['pk_identity'] != pat.ID:
+ return True
+
+ if kwds['table'] == u'dem.identity':
+ if kwds['operation'] != u'UPDATE':
+ return True
+
+ if kwds['table'] in [
+ u'dem.identity',
+ u'dem.names',
+ u'dem.lnk_identity2comm',
+ u'dem.lnk_job2person',
+ u'clin.substance_intake',
+ u'clin.hospital_stay',
+ u'clin.procedure',
+ u'clin.vaccination',
+ u'clin.family_history',
+ u'clin.test_result',
+ u'blobs.doc_med',
+ u'dem.message_inbox',
+ u'clin.episode',
+ u'clin.health_issue'
+ ]:
+ self._schedule_data_reget()
+ return True
+
+ return True
#-----------------------------------------------------
# reget-on-paint mixin API
#-----------------------------------------------------
diff --git a/client/wxpython/gmPersonCreationWidgets.py b/client/wxpython/gmPersonCreationWidgets.py
index dd4f3ad..3ded688 100644
--- a/client/wxpython/gmPersonCreationWidgets.py
+++ b/client/wxpython/gmPersonCreationWidgets.py
@@ -474,20 +474,23 @@ class cNewPatientEAPnl(wxgNewPatientEAPnl.wxgNewPatientEAPnl, gmEditArea.cGeneri
lastnames = self._PRW_lastname.GetValue().strip(),
firstnames = self._PRW_firstnames.GetValue().strip()
)
- _log.debug('identity created: %s' % new_identity)
+ _log.info('identity created: %s' % new_identity)
new_identity['dob_is_estimated'] = self._CHBOX_estimated_dob.GetValue()
val = self._TCTRL_tob.GetValue().strip()
if val != u'':
new_identity['tob'] = pydt.time(int(val[:2]), int(val[3:5]))
new_identity['title'] = gmTools.none_if(self._PRW_title.GetValue().strip())
- new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
prov = self._PRW_primary_provider.GetData()
if prov is not None:
new_identity['pk_primary_provider'] = prov
new_identity['comment'] = gmTools.none_if(self._TCTRL_comment.GetValue().strip(), u'')
new_identity.save()
+ _log.info('new identity updated: %s' % new_identity)
+
+ new_identity.set_nickname(nickname = gmTools.none_if(self._PRW_nickname.GetValue().strip(), u''))
+ _log.info('nickname set on new identity: %s' % new_identity)
# address
# if we reach this the address cannot be completely empty
diff --git a/client/wxpython/gmPregWidgets.py b/client/wxpython/gmPregWidgets.py
index eca1f25..a7a77e6 100644
--- a/client/wxpython/gmPregWidgets.py
+++ b/client/wxpython/gmPregWidgets.py
@@ -42,7 +42,7 @@ def calculate_edc(parent=None, patient=None):
patient.emr.EDC = edc
#====================================================================
-from wxGladeWidgets import wxgEdcCalculatorDlg
+from Gnumed.wxGladeWidgets import wxgEdcCalculatorDlg
class cEdcCalculatorDlg(wxgEdcCalculatorDlg.wxgEdcCalculatorDlg):
diff --git a/client/wxpython/gmSubstanceMgmtWidgets.py b/client/wxpython/gmSubstanceMgmtWidgets.py
deleted file mode 100644
index 6dc81f5..0000000
--- a/client/wxpython/gmSubstanceMgmtWidgets.py
+++ /dev/null
@@ -1,442 +0,0 @@
-# -*- coding: utf-8 -*-
-
-#from __future__ import print_function
-
-__doc__ = """GNUmed drug / substance reference widgets."""
-
-#================================================================
-__author__ = "Karsten Hilbert <Karsten.Hilbert at gmx.net>"
-__license__ = "GPL v2 or later"
-
-import logging
-import sys
-import os.path
-#import io
-#import csv
-#import decimal
-#import datetime as pydt
-
-
-import wx
-
-
-if __name__ == '__main__':
- sys.path.insert(0, '../../')
- from Gnumed.pycommon import gmI18N
- gmI18N.activate_locale()
- gmI18N.install_domain(domain = 'gnumed')
-
-from Gnumed.pycommon import gmDispatcher
-from Gnumed.pycommon import gmCfg
-from Gnumed.pycommon import gmShellAPI
-from Gnumed.pycommon import gmTools
-#from Gnumed.pycommon import gmDateTime
-from Gnumed.pycommon import gmMatchProvider
-#from Gnumed.pycommon import gmI18N
-#from Gnumed.pycommon import gmPrinting
-#from Gnumed.pycommon import gmCfg2
-#from Gnumed.pycommon import gmNetworkTools
-
-from Gnumed.business import gmPerson
-from Gnumed.business import gmATC
-from Gnumed.business import gmPraxis
-from Gnumed.business import gmMedication
-#from Gnumed.business import gmForms
-#from Gnumed.business import gmStaff
-#from Gnumed.business import gmDocuments
-#from Gnumed.business import gmLOINC
-#from Gnumed.business import gmClinicalRecord
-#from Gnumed.business import gmClinicalCalculator
-#from Gnumed.business import gmPathLab
-
-from Gnumed.wxpython import gmGuiHelpers
-#from Gnumed.wxpython import gmRegetMixin
-from Gnumed.wxpython import gmAuthWidgets
-from Gnumed.wxpython import gmEditArea
-#from Gnumed.wxpython import gmMacro
-from Gnumed.wxpython import gmCfgWidgets
-from Gnumed.wxpython import gmListWidgets
-from Gnumed.wxpython import gmPhraseWheel
-
-
-_log = logging.getLogger('gm.ui')
-
-#============================================================
-# generic drug database access
-#------------------------------------------------------------
-def configure_drug_data_source(parent=None):
- gmCfgWidgets.configure_string_from_list_option (
- parent = parent,
- message = _(
- '\n'
- 'Please select the default drug data source from the list below.\n'
- '\n'
- 'Note that to actually use it you need to have the database installed, too.'
- ),
- option = 'external.drug_data.default_source',
- bias = 'user',
- default_value = None,
- choices = gmMedication.drug_data_source_interfaces.keys(),
- columns = [_('Drug data source')],
- data = gmMedication.drug_data_source_interfaces.keys(),
- caption = _('Configuring default drug data source')
- )
-
-#============================================================
-def get_drug_database(parent=None, patient=None):
- dbcfg = gmCfg.cCfgSQL()
-
- # load from option
- default_db = dbcfg.get2 (
- option = 'external.drug_data.default_source',
- workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
- bias = 'workplace'
- )
-
- # not configured -> try to configure
- if default_db is None:
- gmDispatcher.send('statustext', msg = _('No default drug database configured.'), beep = True)
- configure_drug_data_source(parent = parent)
- default_db = dbcfg.get2 (
- option = 'external.drug_data.default_source',
- workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
- bias = 'workplace'
- )
- # still not configured -> return
- if default_db is None:
- gmGuiHelpers.gm_show_error (
- aMessage = _('There is no default drug database configured.'),
- aTitle = _('Jumping to drug database')
- )
- return None
-
- # now it MUST be configured (either newly or previously)
- # but also *validly* ?
- try:
- drug_db = gmMedication.drug_data_source_interfaces[default_db]()
- except KeyError:
- # not valid
- _log.error('faulty default drug data source configuration: %s', default_db)
- # try to configure
- configure_drug_data_source(parent = parent)
- default_db = dbcfg.get2 (
- option = 'external.drug_data.default_source',
- workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
- bias = 'workplace'
- )
- # deconfigured or aborted (and thusly still misconfigured) ?
- try:
- drug_db = gmMedication.drug_data_source_interfaces[default_db]()
- except KeyError:
- _log.error('still faulty default drug data source configuration: %s', default_db)
- return None
-
- if patient is not None:
- drug_db.patient = pat
-
- return drug_db
-
-#============================================================
-def jump_to_drug_database(patient=None):
- drug_db = get_drug_database(patient = patient)
- if drug_db is None:
- return
- drug_db.switch_to_frontend(blocking = False)
-
-#============================================================
-def jump_to_ifap_deprecated(import_drugs=False, emr=None):
-
- if import_drugs and (emr is None):
- gmDispatcher.send('statustext', msg = _('Cannot import drugs from IFAP into chart without chart.'))
- return False
-
- dbcfg = gmCfg.cCfgSQL()
-
- ifap_cmd = dbcfg.get2 (
- option = 'external.ifap-win.shell_command',
- workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
- bias = 'workplace',
- default = 'wine "C:\Ifapwin\WIAMDB.EXE"'
- )
- found, binary = gmShellAPI.detect_external_binary(ifap_cmd)
- if not found:
- gmDispatcher.send('statustext', msg = _('Cannot call IFAP via [%s].') % ifap_cmd)
- return False
- ifap_cmd = binary
-
- if import_drugs:
- transfer_file = os.path.expanduser(dbcfg.get2 (
- option = 'external.ifap-win.transfer_file',
- workplace = gmPraxis.gmCurrentPraxisBranch().active_workplace,
- bias = 'workplace',
- default = '~/.wine/drive_c/Ifapwin/ifap2gnumed.csv'
- ))
- # file must exist for Ifap to write into it
- try:
- f = io.open(transfer_file, mode = 'wt').close()
- except IOError:
- _log.exception('Cannot create IFAP <-> GNUmed transfer file [%s]', transfer_file)
- gmDispatcher.send('statustext', msg = _('Cannot create IFAP <-> GNUmed transfer file [%s].') % transfer_file)
- return False
-
- wx.BeginBusyCursor()
- gmShellAPI.run_command_in_shell(command = ifap_cmd, blocking = import_drugs)
- wx.EndBusyCursor()
-
- if import_drugs:
- # COMMENT: this file must exist PRIOR to invoking IFAP
- # COMMENT: or else IFAP will not write data into it ...
- try:
- csv_file = io.open(transfer_file, mode = 'rt', encoding = 'latin1') # FIXME: encoding unknown
- except:
- _log.exception('cannot access [%s]', fname)
- csv_file = None
-
- if csv_file is not None:
- import csv
- csv_lines = csv.DictReader (
- csv_file,
- fieldnames = u'PZN Handelsname Form Abpackungsmenge Einheit Preis1 Hersteller Preis2 rezeptpflichtig Festbetrag Packungszahl Packungsgr\xf6\xdfe'.split(),
- delimiter = ';'
- )
- # dummy episode for now
- epi = emr.add_episode(episode_name = _('Current medication'))
- for line in csv_lines:
- narr = u'%sx %s %s %s (\u2258 %s %s) von %s (%s)' % (
- line['Packungszahl'].strip(),
- line['Handelsname'].strip(),
- line['Form'].strip(),
- line[u'Packungsgr\xf6\xdfe'].strip(),
- line['Abpackungsmenge'].strip(),
- line['Einheit'].strip(),
- line['Hersteller'].strip(),
- line['PZN'].strip()
- )
- emr.add_clin_narrative(note = narr, soap_cat = 's', episode = epi)
- csv_file.close()
-
- return True
-
-#============================================================
-# ATC related widgets
-#------------------------------------------------------------
-def browse_atc_reference_deprecated(parent=None):
-
- if parent is None:
- parent = wx.GetApp().GetTopWindow()
- #------------------------------------------------------------
- def refresh(lctrl):
- atcs = gmATC.get_reference_atcs()
-
- items = [ [
- a['atc'],
- a['term'],
- gmTools.coalesce(a['unit'], u''),
- gmTools.coalesce(a['administrative_route'], u''),
- gmTools.coalesce(a['comment'], u''),
- a['version'],
- a['lang']
- ] for a in atcs ]
- lctrl.set_string_items(items)
- lctrl.set_data(atcs)
- #------------------------------------------------------------
- gmListWidgets.get_choices_from_list (
- parent = parent,
- msg = _('\nThe ATC codes as known to GNUmed.\n'),
- caption = _('Showing ATC codes.'),
- columns = [ u'ATC', _('Term'), _('Unit'), _(u'Route'), _('Comment'), _('Version'), _('Language') ],
- single_selection = True,
- refresh_callback = refresh
- )
-
-#============================================================
-def update_atc_reference_data():
-
- dlg = wx.FileDialog (
- parent = None,
- message = _('Choose an ATC import config file'),
- defaultDir = os.path.expanduser(os.path.join('~', 'gnumed')),
- defaultFile = '',
- wildcard = "%s (*.conf)|*.conf|%s (*)|*" % (_('config files'), _('all files')),
- style = wx.OPEN | wx.FILE_MUST_EXIST
- )
-
- result = dlg.ShowModal()
- if result == wx.ID_CANCEL:
- return
-
- cfg_file = dlg.GetPath()
- dlg.Destroy()
-
- conn = gmAuthWidgets.get_dbowner_connection(procedure = _('importing ATC reference data'))
- if conn is None:
- return False
-
- wx.BeginBusyCursor()
-
- if gmATC.atc_import(cfg_fname = cfg_file, conn = conn):
- gmDispatcher.send(signal = 'statustext', msg = _('Successfully imported ATC reference data.'))
- else:
- gmDispatcher.send(signal = 'statustext', msg = _('Importing ATC reference data failed.'), beep = True)
-
- wx.EndBusyCursor()
- return True
-
-#============================================================
-class cATCPhraseWheel(gmPhraseWheel.cPhraseWheel):
-
- def __init__(self, *args, **kwargs):
-
- gmPhraseWheel.cPhraseWheel.__init__(self, *args, **kwargs)
- query = u"""
-
- SELECT DISTINCT ON (label)
- atc_code,
- label
- FROM (
-
- SELECT
- code as atc_code,
- (code || ': ' || term)
- AS label
- FROM ref.atc
- WHERE
- term %(fragment_condition)s
- OR
- code %(fragment_condition)s
-
- UNION ALL
-
- SELECT
- atc_code,
- (atc_code || ': ' || description)
- AS label
- FROM ref.consumable_substance
- WHERE
- description %(fragment_condition)s
- OR
- atc_code %(fragment_condition)s
-
- UNION ALL
-
- SELECT
- atc_code,
- (atc_code || ': ' || description || ' (' || preparation || ')')
- AS label
- FROM ref.branded_drug
- WHERE
- description %(fragment_condition)s
- OR
- atc_code %(fragment_condition)s
-
- -- it would be nice to be able to include clin.vacc_indication but that's hard to do in SQL
-
- ) AS candidates
- WHERE atc_code IS NOT NULL
- ORDER BY label
- LIMIT 50"""
-
- mp = gmMatchProvider.cMatchProvider_SQL2(queries = query)
- mp.setThresholds(1, 2, 4)
-# mp.word_separators = '[ \t=+&:@]+'
- self.SetToolTipString(_('Select an ATC (Anatomical-Therapeutic-Chemical) code.'))
- self.matcher = mp
- self.selection_only = True
-
-#============================================================
-# consumable substances widgets
-#------------------------------------------------------------
-def edit_consumable_substance(parent=None, substance=None, single_entry=False):
-
- if substance is not None:
- if substance.is_in_use_by_patients:
- gmDispatcher.send(signal = 'statustext', msg = _('Cannot edit this substance. It is in use.'), beep = True)
- return False
-
- ea = cConsumableSubstanceEAPnl(parent = parent, id = -1)
- ea.data = substance
- ea.mode = gmTools.coalesce(substance, 'new', 'edit')
- dlg = gmEditArea.cGenericEditAreaDlg2(parent = parent, id = -1, edit_area = ea, single_entry = single_entry)
- dlg.SetTitle(gmTools.coalesce(substance, _('Adding new consumable substance'), _('Editing consumable substance')))
- if dlg.ShowModal() == wx.ID_OK:
- dlg.Destroy()
- return True
- dlg.Destroy()
- return False
-
-#------------------------------------------------------------
-def manage_consumable_substances(parent=None):
-
- if parent is None:
- parent = wx.GetApp().GetTopWindow()
- #------------------------------------------------------------
- def add_from_db(substance):
- drug_db = get_drug_database(parent = parent)
- if drug_db is None:
- return False
- drug_db.import_drugs()
- return True
- #------------------------------------------------------------
- def edit(substance=None):
- return edit_consumable_substance(parent = parent, substance = substance, single_entry = (substance is not None))
- #------------------------------------------------------------
- def delete(substance):
- if substance.is_in_use_by_patients:
- gmDispatcher.send(signal = 'statustext', msg = _('Cannot delete this substance. It is in use.'), beep = True)
- return False
-
- return gmMedication.delete_consumable_substance(substance = substance['pk'])
- #------------------------------------------------------------
- def refresh(lctrl):
- substs = gmMedication.get_consumable_substances(order_by = 'description')
- items = [ [
- s['description'],
- s['amount'],
- s['unit'],
- gmTools.coalesce(s['atc_code'], u''),
- s['pk']
- ] for s in substs ]
- lctrl.set_string_items(items)
- lctrl.set_data(substs)
- #------------------------------------------------------------
- msg = _('\nThese are the consumable substances registered with GNUmed.\n')
-
- gmListWidgets.get_choices_from_list (
- parent = parent,
- msg = msg,
- caption = _('Showing consumable substances.'),
- columns = [_('Substance'), _('Amount'), _('Unit'), 'ATC', u'#'],
- single_selection = True,
- new_callback = edit,
- edit_callback = edit,
- delete_callback = delete,
- refresh_callback = refresh,
- left_extra_button = (_('Import'), _('Import consumable substances from a drug database.'), add_from_db)
- )
-
-
-#============================================================
-# main
-#------------------------------------------------------------
-if __name__ == '__main__':
-
- if len(sys.argv) < 2:
- sys.exit()
-
- if sys.argv[1] != 'test':
- sys.exit()
-
- from Gnumed.business import gmPersonSearch
-
- pat = gmPersonSearch.ask_for_patient()
- if pat is None:
- sys.exit()
- gmPerson.set_active_patient(patient = pat)
-
- #----------------------------------------
- app = wx.PyWidgetTester(size = (600, 300))
-# #app.SetWidget(cATCPhraseWheel, -1)
- #app.SetWidget(cSubstancePhraseWheel, -1)
- app.SetWidget(cBrandOrSubstancePhraseWheel, -1)
- app.MainLoop()
- #manage_substance_intakes()
diff --git a/client/wxpython/gmTopPanel.py b/client/wxpython/gmTopPanel.py
index 827fcfd..eca0e6b 100644
--- a/client/wxpython/gmTopPanel.py
+++ b/client/wxpython/gmTopPanel.py
@@ -71,13 +71,10 @@ class cTopPnl(wxgTopPnl.wxgTopPnl):
# client internal signals
gmDispatcher.connect(signal = u'post_patient_selection', receiver = self._on_post_patient_selection)
- gmDispatcher.connect(signal = u'clin.allgergy_mod_db', receiver = self._on_allergies_change)
- gmDispatcher.connect(signal = u'clin.allergy_state_mod_db', receiver = self._on_allergies_change)
- gmDispatcher.connect(signal = u'dem.names_mod_db', receiver = self._on_name_identity_change)
- gmDispatcher.connect(signal = u'dem.identity_mod_db', receiver = self._on_name_identity_change)
- gmDispatcher.connect(signal = u'dem.identity_tag_mod_db', receiver = self._on_tag_change)
-
gmDispatcher.connect(signal = u'focus_patient_search', receiver = self._on_focus_patient_search)
+
+ gmDispatcher.connect(signal = u'gm_table_mod', receiver = self._on_database_signal)
+
#----------------------------------------------
# event handling
#----------------------------------------------
@@ -89,19 +86,47 @@ class cTopPnl(wxgTopPnl.wxgTopPnl):
dlg.ShowModal()
return
#----------------------------------------------
- def _on_tag_change(self):
- self.__update_tags()
- #----------------------------------------------
- def _on_name_identity_change(self):
- self.__update_age_label()
+ def _on_database_signal(self, **kwds):
+
+ if kwds['table'] not in [u'dem.identity', u'dem.names', u'dem.identity_tag', u'clin.allergy', u'clin.allergy_state']:
+ return True
+
+ if self.curr_pat.connected:
+ # signal is not about our patient: ignore signal
+ if int(kwds['pk_identity']) != self.curr_pat.ID:
+ return True
+
+ if kwds['table'] == u'dem.identity':
+ # we don't care about newly INSERTed or DELETEd patients
+ if kwds['operation'] != 'UPDATE':
+ return True
+ self.__update_age_label()
+ return True
+
+ if kwds['table'] == u'dem.names':
+ self.__update_age_label()
+ return True
+
+ if kwds['table'] == u'dem.identity_tag':
+ self.__update_tags()
+ return True
+
+ if kwds['table'] == u'clin.allergy':
+ self.__update_allergies()
+ return True
+
+ if kwds['table'] == u'clin.allergy_state':
+ self.__update_allergies()
+ return True
+
+ return True
+
#----------------------------------------------
def _on_post_patient_selection(self, **kwargs):
self.__update_age_label()
self.__update_allergies()
self.__update_tags()
- #-------------------------------------------------------
- def _on_allergies_change(self, **kwargs):
- self.__update_allergies()
+
#-------------------------------------------------------
def _on_focus_patient_search(self, **kwargs):
wx.CallAfter(self._TCTRL_patient_selector.SetFocus)
@@ -199,6 +224,13 @@ class cTopPnl(wxgTopPnl.wxgTopPnl):
#-------------------------------------------------------
def __update_allergies(self, **kwargs):
+ if not self.curr_pat.connected:
+ self._LBL_allergies.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))
+ self._TCTRL_allergies.SetForegroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))
+ self._TCTRL_allergies.SetValue(u'')
+ self._TCTRL_allergies.SetToolTipString(u'')
+ return
+
show_red = True
emr = self.curr_pat.get_emr()
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/gnumed-client.git
More information about the debian-med-commit
mailing list