[med-svn] [gnumed-server] 01/06: Imported Upstream version 20.8

Andreas Tille tille at debian.org
Wed Dec 16 15:43:26 UTC 2015


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository gnumed-server.

commit 324056f7a32aa22556a630add3fa2ca54dbce543
Author: Andreas Tille <tille at debian.org>
Date:   Tue Nov 10 08:10:59 2015 +0100

    Imported Upstream version 20.8
---
 server/doc/schema/gnumed-entire_schema.html        |   2 +-
 server/pycommon/gmBackendListener.py               |   4 +-
 server/pycommon/gmBorg.py                          |  43 +-------
 server/pycommon/gmBusinessDBObject.py              |  27 +++--
 server/pycommon/gmNull.py                          | 108 ++++++++-----------
 server/pycommon/gmPG2.py                           |   7 +-
 server/pycommon/gmTools.py                         | 116 +++++++++++++++++++--
 .../v19-v20/dynamic/v20-release_notes-dynamic.sql  |  15 ++-
 8 files changed, 185 insertions(+), 137 deletions(-)

diff --git a/server/doc/schema/gnumed-entire_schema.html b/server/doc/schema/gnumed-entire_schema.html
index 3e7c538..ac003b1 100644
--- a/server/doc/schema/gnumed-entire_schema.html
+++ b/server/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/server/pycommon/gmBackendListener.py b/server/pycommon/gmBackendListener.py
index ca9fac5..665f029 100644
--- a/server/pycommon/gmBackendListener.py
+++ b/server/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/server/pycommon/gmBorg.py b/server/pycommon/gmBorg.py
index 37132c2..76864b2 100644
--- a/server/pycommon/gmBorg.py
+++ b/server/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/server/pycommon/gmBusinessDBObject.py b/server/pycommon/gmBusinessDBObject.py
index 7aadbde..19589f9 100644
--- a/server/pycommon/gmBusinessDBObject.py
+++ b/server/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/server/pycommon/gmNull.py b/server/pycommon/gmNull.py
index 1ac21e9..2bc48a3 100644
--- a/server/pycommon/gmNull.py
+++ b/server/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/server/pycommon/gmPG2.py b/server/pycommon/gmPG2.py
index da71465..4e9317b 100644
--- a/server/pycommon/gmPG2.py
+++ b/server/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/server/pycommon/gmTools.py b/server/pycommon/gmTools.py
index 263330a..de090ee 100644
--- a/server/pycommon/gmTools.py
+++ b/server/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/server/sql/v19-v20/dynamic/v20-release_notes-dynamic.sql b/server/sql/v19-v20/dynamic/v20-release_notes-dynamic.sql
index 92ae3d2..96b5817 100644
--- a/server/sql/v19-v20/dynamic/v20-release_notes-dynamic.sql
+++ b/server/sql/v19-v20/dynamic/v20-release_notes-dynamic.sql
@@ -17,19 +17,18 @@ INSERT INTO dem.message_inbox (
 ) VALUES (
 	(select pk from dem.staff where db_user = 'any-doc'),
 	(select pk_type from dem.v_inbox_item_type where type = 'memo' and category = 'administrative'),
-	'Release Notes for GNUmed 1.5.6 (database v20.6)',
-	'GNUmed 1.5.6 Release Notes:
+	'Release Notes for GNUmed 1.5.8 (database v20.8)',
+	'GNUmed 1.5.8 Release Notes:
 
-	1.5.6
+	1.5.8
 
-FIX: exception on removing temporary config file [thanks Vaibhav]
-FIX: exception on importing duplicate file into export area
-FIX: exception on merging patients under wxPython 3 [thanks max]
+FIX: SQL formatting when retrieving clinical narrative [thanks Marc]
+FIX: strange case of "curr_pat is None" in top panel [thanks Marc]
 
-	20.6
+	20.8
 
 no changes
 ');
 
 -- --------------------------------------------------------------
-select gm.log_script_insertion('v20-release_notes-dynamic.sql', '20.6');
+select gm.log_script_insertion('v20-release_notes-dynamic.sql', '20.8');

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/gnumed-server.git



More information about the debian-med-commit mailing list