[med-svn] [Git][med-team/gnumed-server][upstream] New upstream version 22.12
Andreas Tille
gitlab at salsa.debian.org
Wed Mar 25 14:18:13 GMT 2020
Andreas Tille pushed to branch upstream at Debian Med / gnumed-server
Commits:
d6ac831e by Andreas Tille at 2020-03-25T15:09:08+01:00
New upstream version 22.12
- - - - -
12 changed files:
- server/bootstrap/bootstrap_gm_db_system.py
- server/doc/schema/gnumed-entire_schema.html
- server/gm-backup.sh
- server/gm-backup_data.sh
- server/gm-fingerprint_db.py
- server/pycommon/gmBusinessDBObject.py
- server/pycommon/gmDateTime.py
- server/pycommon/gmMimeMagic.py
- server/pycommon/gmPG2.py
- server/pycommon/gmShellAPI.py
- server/pycommon/gmTools.py
- server/sql/v21-v22/fixups/v22-release_notes-fixup.sql
Changes:
=====================================
server/bootstrap/bootstrap_gm_db_system.py
=====================================
@@ -1,6 +1,4 @@
#!/usr/bin/python3
-##!/usr/bin/env python
-##!/usr/bin/python2.7-dbg
__doc__="""GNUmed schema installation.
=====================================
server/doc/schema/gnumed-entire_schema.html
=====================================
@@ -112,7 +112,7 @@
<body>
<!-- Primary Index -->
- <p><br><br>Dumped on 2020-02-29</p>
+ <p><br><br>Dumped on 2020-03-25</p>
<h1><a name="index">Index of database - gnumed_v22</a></h1>
<ul>
=====================================
server/gm-backup.sh
=====================================
@@ -100,7 +100,11 @@ fi
OUR_VER=$(echo "${GM_DATABASE}" | cut -f 2 -d v)
SQL_COMPARE_VERSIONS="SELECT EXISTS (SELECT 1 FROM pg_database WHERE datname LIKE 'gnumed_v%' AND substring(datname from 9 for 3)::integer > '${OUR_VER}');"
HAS_HIGHER_VER=$(sudo --user=postgres psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG_HOST_ARG} ${_PG_PORT_ARG} --command="${SQL_COMPARE_VERSIONS}")
-#HAS_HIGHER_VER=`sudo --user=postgres psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG_HOST_ARG} ${_PG_PORT_ARG} --command="${SQL_COMPARE_VERSIONS}"`
+RESULT="$?"
+if test "${RESULT}" != "0" ; then
+ echo "Cannot sanity check database version (${RESULT}). Aborting."
+ exit ${RESULT}
+fi
if test "${HAS_HIGHER_VER}" = "t" ; then
echo "Backing up database ${GM_DATABASE}."
echo ""
@@ -142,10 +146,22 @@ echo "backup: ${TS}" > ${TS_FILE}
BACKUP_DATA_DIR="${BACKUP_BASENAME}.dir"
# database
pg_dump --verbose --format=directory --compress=0 --column-inserts --clean --if-exists --serializable-deferrable ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username="${GM_DBO}" -f "${BACKUP_DATA_DIR}" "${GM_DATABASE}" 2> /dev/null
+RESULT="$?"
+if test "${RESULT}" != "0" ; then
+ echo "Cannot dump database content into [${BACKUP_DATA_DIR}] (${RESULT}). Aborting."
+ exit ${RESULT}
+fi
# roles
ROLES_FILE="${BACKUP_BASENAME}-roles.sql"
# -r -> -g for older versions
-ROLES=`psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username="${GM_DBO}" --command="select gm.get_users('${GM_DATABASE}');"`
+#ROLES=`psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username="${GM_DBO}" --command="select gm.get_users('${GM_DATABASE}');"`
+ROLES=$(psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username="${GM_DBO}" --command="select gm.get_users('${GM_DATABASE}');")
+#"
+RESULT="$?"
+if test "${RESULT}" != "0" ; then
+ echo "Cannot list database roles (${RESULT}). Aborting."
+ exit ${RESULT}
+fi
{
echo "-- -----------------------------------------------------"
echo "-- Below find a list of database roles which were in use"
@@ -166,6 +182,11 @@ ROLES=`psql --no-psqlrc --no-align --tuples-only --dbname="${GM_DATABASE}" ${_PG
echo "-- -----------------------------------------------------"
} > "${ROLES_FILE}" 2> /dev/null
sudo --user=postgres pg_dumpall --verbose --roles-only ${_PG_HOST_ARG} ${_PG_PORT_ARG} --username=postgres >> "${ROLES_FILE}" 2> /dev/null
+RESULT="$?"
+if test "${RESULT}" != "0" ; then
+ echo "Cannot dump database roles into [${ROLES_FILE}] (${RESULT}). Aborting."
+ exit ${RESULT}
+fi
# create tar archive
=====================================
server/gm-backup_data.sh
=====================================
@@ -18,7 +18,7 @@ exit 1
#
# To restore the data-only dump do this:
#
-# 1) $> python gmDBPruningDMLGenerator.py <data only dump>
+# 1) $> python3 gmDBPruningDMLGenerator.py <data only dump>
# 2) $> psql -d gnumed_vX -U gm-dbo -f <data only dump>-prune_tables.sql
# 3) $> psql -d gnumed_vX -U gm-dbo -f <data only dump>
#
@@ -28,7 +28,7 @@ exit 1
# To speed things up you can replace step 1) with:
#
# 1a) $> cut -f -5 -d " " <data only dump> | grep -E "^(SET)|(INSERT)" > tmp.sql
-# 1b) $> python gmDBPruningDMLGenerator.py tmp.sql
+# 1b) $> python3 gmDBPruningDMLGenerator.py tmp.sql
#
# and use that in step 2):
#
=====================================
server/gm-fingerprint_db.py
=====================================
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#==============================================================
# This script can be used to fingerprint a GNUmed database.
=====================================
server/pycommon/gmBusinessDBObject.py
=====================================
@@ -22,7 +22,7 @@ One way is to pass a "primary key equivalent" object into
__init__(). Refetch_payload() will then pull the data from
the backend. Another way would be to fetch the data outside
the instance and pass it in via the <row> argument. In that
-case the instance will not initially connect to the databse
+case the instance will not initially connect to the database
which may offer a great boost to performance.
Values API
@@ -326,7 +326,7 @@ def manage_xxx()
self._ext_cache = {} # the cache for extended method's results
self._is_modified = False
- # check descendants
+ # sanity check child implementions
self.__class__._cmd_fetch_payload
self.__class__._cmds_store_payload
self.__class__._updatable_fields
@@ -347,7 +347,7 @@ def manage_xxx()
* the primary key WHERE condition must be
a simple column
- a dictionary of values
- * the primary key where condition must be a
+ * the primary key WHERE condition must be a
subselect consuming the dict and producing
the single-value primary key
"""
@@ -448,6 +448,7 @@ def manage_xxx()
self._ext_cache[attribute] = getter()
return self._ext_cache[attribute]
+
#--------------------------------------------------------
def __setitem__(self, attribute, value):
@@ -676,8 +677,10 @@ def manage_xxx()
if len(rows) == 0:
_log.error('[%s:%s]: no such instance' % (self.__class__.__name__, self.pk_obj))
return False
+
if len(rows) > 1:
raise AssertionError('[%s:%s]: %s instances !' % (self.__class__.__name__, self.pk_obj, len(rows)))
+
self._payload = rows[0]
return True
=====================================
server/pycommon/gmDateTime.py
=====================================
@@ -373,38 +373,10 @@ def pydt_strftime(dt=None, format='%Y %b %d %H:%M.%S', accuracy=None, none_str=
try:
return dt.strftime(format)
+
except ValueError:
_log.exception()
return 'strftime() error'
- #_log.exception('Python cannot strftime() this <datetime>, trying ourselves')
-
-# if isinstance(dt, pyDT.date):
-# accuracy = acc_days
-#
-# if accuracy == acc_days:
-# return '%04d-%02d-%02d' % (
-# dt.year,
-# dt.month,
-# dt.day
-# )
-#
-# if accuracy == acc_minutes:
-# return '%04d-%02d-%02d %02d:%02d' % (
-# dt.year,
-# dt.month,
-# dt.day,
-# dt.hour,
-# dt.minute
-# )
-#
-# return '%04d-%02d-%02d %02d:%02d:%02d' % (
-# dt.year,
-# dt.month,
-# dt.day,
-# dt.hour,
-# dt.minute,
-# dt.second
-# )
#---------------------------------------------------------------------------
def pydt_add(dt, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, seconds=0, milliseconds=0, microseconds=0):
@@ -752,24 +724,36 @@ def format_pregnancy_months(age):
#---------------------------------------------------------------------------
def is_leap_year(year):
+ if year < 1582: # no leap years before Gregorian Reform
+ _log.debug('%s: before Gregorian Reform', year)
+ return False
+
# year is multiple of 4 ?
div, remainder = divmod(year, 4)
- # no -> not a leap year
+ # * NOT divisible by 4
+ # -> common year
if remainder > 0:
return False
# year is a multiple of 100 ?
div, remainder = divmod(year, 100)
- # no -> IS a leap year
+ # * divisible by 4
+ # * NOT divisible by 100
+ # -> leap year
if remainder > 0:
return True
# year is a multiple of 400 ?
div, remainder = divmod(year, 400)
- # yes -> IS a leap year
+ # * divisible by 4
+ # * divisible by 100, so, perhaps not leaping ?
+ # * but ALSO divisible by 400
+ # -> leap year
if remainder == 0:
return True
+ # all others
+ # -> common year
return False
#---------------------------------------------------------------------------
@@ -2406,8 +2390,13 @@ if __name__ == '__main__':
print (pydt_strftime(dt, accuracy = acc_seconds))
#-------------------------------------------------
def test_is_leap_year():
- for year in range(1995, 2017):
- print (year, "leaps:", is_leap_year(year))
+ for idx in range(120):
+ year = 1993 + idx
+ tmp, offset = divmod(idx, 4)
+ if is_leap_year(year):
+ print (offset+1, '--', year, 'leaps')
+ else:
+ print (offset+1, '--', year)
#-------------------------------------------------
def test_get_date_of_weekday_in_week_of_date():
@@ -2445,6 +2434,6 @@ if __name__ == '__main__':
#test_str2pydt()
#test_pydt_strftime()
#test_calculate_apparent_age()
- #test_is_leap_year()
+ test_is_leap_year()
#===========================================================================
=====================================
server/pycommon/gmMimeMagic.py
=====================================
@@ -5,7 +5,7 @@
(C)opyright 2000 Jason Petrone <jp at demonseed.net>
All Rights Reserved
- Command Line Usage: running as `python magic.py file` will print
+ Command Line Usage: running as `python3 magic.py file` will print
a description of what 'file' is.
Module Usage:
=====================================
server/pycommon/gmPG2.py
=====================================
@@ -54,6 +54,7 @@ except ImportError:
raise
import psycopg2.errorcodes as sql_error_codes
+import psycopg2.sql as psysql
PG_ERROR_EXCEPTION = dbapi.Error
@@ -261,6 +262,25 @@ WHERE
AND
indisprimary
"""
+
+SQL_get_primary_key_name = """
+SELECT
+ is_kcu.column_name,
+ is_kcu.ordinal_position
+FROM
+ information_schema.key_column_usage AS is_kcu
+ LEFT JOIN information_schema.table_constraints AS is_tc ON is_tc.constraint_name = is_kcu.constraint_name
+WHERE
+ -- constrain to current database
+ is_tc.table_catalog = current_database()
+ AND
+ is_tc.table_schema = %(schema)s
+ AND
+ is_tc.table_name = %(table)s
+ AND
+ is_tc.constraint_type = 'PRIMARY KEY';
+"""
+
# =======================================================================
# login API
# =======================================================================
@@ -1364,7 +1384,72 @@ def file2bytea_overlay(query=None, args=None, filename=None, conn=None, md5_quer
return False
#------------------------------------------------------------------------
-#---------------------------------------------------------------------------
+def read_all_rows_of_table(schema=None, table=None):
+ if schema is None:
+ schema = prompted_input(prompt = 'schema for table to dump', default = None)
+ if schema is None:
+ _log.debug('aborted by user (no schema entered)')
+ return None
+
+ if table is None:
+ table = prompted_input(prompt = 'table to dump (in schema %s.)' % schema, default = None)
+ if table is None:
+ _log.debug('aborted by user (no table entered)')
+ return None
+
+ _log.debug('dumping <%s.%s>', schema, table)
+ conn = get_connection(readonly=True, verbose = False, pooled = True, connection_name = 'read_all_rows_of_table')
+ # get pk column name
+ rows, idx = run_ro_queries(link_obj = conn, queries = [{'cmd': SQL_get_primary_key_name, 'args': {'schema': schema, 'table': table}}])
+ if rows:
+ _log.debug('primary key def: %s', rows)
+ if len(rows) > 1:
+ _log.error('cannot handle multi-column primary key')
+ return False
+
+ pk_name = rows[0][0]
+ else:
+ _log.debug('cannot determine primary key, asking user')
+ pk_name = prompted_input(prompt = 'primary key name for %s.%s' % (schema, table), default = None)
+ if pk_name is None:
+ _log.debug('aborted by user (no primary key name entered)')
+ return None
+
+ # get PK values
+ qualified_table = '%s.%s' % (schema, table)
+ qualified_pk_name = '%s.%s.%s' % (schema, table, pk_name)
+ cmd = psysql.SQL('SELECT {schema_table_pk} FROM {schema_table} ORDER BY 1'.format (
+ schema_table_pk = qualified_pk_name,
+ schema_table = qualified_table
+ ))
+ rows, idx = run_ro_queries(link_obj = conn, queries = [{'cmd': cmd}])
+ if not rows:
+ _log.debug('no rows to dump')
+ return True
+
+ # dump table rows
+ _log.debug('dumping %s rows', len(rows))
+ cmd = psysql.SQL('SELECT * FROM {schema_table} WHERE {schema_table_pk} = %(pk_val)s'.format (
+ schema_table = qualified_table,
+ schema_table_pk = qualified_pk_name
+ ))
+ found_errors = False
+ idx = 0
+ for row in rows:
+ idx += 1
+ args = {'pk_val': row[0]}
+ _log.debug('dumping row #%s with pk [%s]', idx, row[0])
+ try:
+ run_ro_queries(link_obj = conn, queries = [{'cmd': cmd, 'args': args}])
+ except dbapi.InternalError:
+ found_errors = True
+ _log.exception('error dumping row')
+ print('ERROR: cannot dump row %s of %s with pk %s = %s', idx, len(rows), qualified_pk_name, rows[0])
+
+ return found_errors is False
+
+#------------------------------------------------------------------------
+#------------------------------------------------------------------------
def run_sql_script(sql_script, conn=None):
if conn is None:
=====================================
server/pycommon/gmShellAPI.py
=====================================
@@ -38,6 +38,7 @@ def is_cmd_in_path(cmd=None):
_log.debug('command not found in PATH')
return (False, None)
+
#===========================================================================
def is_executable_by_wine(cmd=None):
@@ -297,6 +298,7 @@ def run_process(cmd_line=None, timeout=None, encoding='utf8', input_data=None, a
except (subprocess.TimeoutExpired, FileNotFoundError):
_log.exception('there was a problem running external process')
return False, -1, ''
+
_log.info('exit code [%s]', proc_result.returncode)
if verbose:
_log_output(logging.DEBUG, stdout = proc_result.stdout, stderr = proc_result.stderr)
@@ -306,6 +308,7 @@ def run_process(cmd_line=None, timeout=None, encoding='utf8', input_data=None, a
if not verbose:
_log_output(logging.ERROR, stdout = proc_result.stdout, stderr = proc_result.stderr)
return False, proc_result.returncode, ''
+
return True, proc_result.returncode, proc_result.stdout
#===========================================================================
=====================================
server/pycommon/gmTools.py
=====================================
@@ -557,24 +557,23 @@ class gmPaths(gmBorg.cBorg):
def recode_file(source_file=None, target_file=None, source_encoding='utf8', target_encoding=None, base_dir=None, error_mode='replace'):
if target_encoding is None:
return source_file
+
if target_encoding == source_encoding:
return source_file
+
if target_file is None:
target_file = get_unique_filename (
prefix = '%s-%s_%s-' % (fname_stem(source_file), source_encoding, target_encoding),
suffix = fname_extension(source_file, '.txt'),
tmp_dir = base_dir
)
-
_log.debug('[%s] -> [%s] (%s -> %s)', source_encoding, target_encoding, source_file, target_file)
-
in_file = io.open(source_file, mode = 'rt', encoding = source_encoding)
out_file = io.open(target_file, mode = 'wt', encoding = target_encoding, errors = error_mode)
for line in in_file:
out_file.write(line)
out_file.close()
in_file.close()
-
return target_file
#---------------------------------------------------------------------------
=====================================
server/sql/v21-v22/fixups/v22-release_notes-fixup.sql
=====================================
@@ -17,78 +17,25 @@ 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.8.0 (database v22.11)',
- 'GNUmed 1.8.0 Release Notes:
+ 'Release Notes for GNUmed 1.8.1 (database v22.12)',
+ 'GNUmed 1.8.1 Release Notes:
- 1.8.0
+ 1.8.1
-NEW: port to wxPython 4 (wxPhoenix)
-NEW: port to Python 3
-NEW: port bootstrapper to Python 3
-NEW: EMR tree: toggle episode status from context menu
-NEW: EMR tree: show/edit clinical items from below encounters
-NEW: ReST formatting in $free_text::::$ placeholder
-NEW: hook "after_waiting_list_modified"
-NEW: test results tab showing most-recent in test panel
-NEW: local documents cache
-NEW: systemd-tmpfiles config file
-NEW: emailing of export area content as encrypted zip file
-NEW: local directory entries in export area
-NEW: $praxis_scan2pay$ support
-NEW: $bill_scan2pay$ support
-NEW: status bar history/visual bell
-NEW: dicomize images/PDF into DICOM study
-NEW: [Abort] client from exception dialog
-NEW: edit clinical item from EMR list journal
-NEW: dist: add PortableApp XML skeleton
-NEW: placeholder: $most_recent_test_results$
-NEW: tool: check_mimetypes_in_archive
+NEW: tool: read_all_rows_of_table
-IMPROVED: symbolic link creation on Windows
-IMPROVED: Orthanc connection handling
-IMPROVED: DICOM plugin UI
-IMPROVED: EMR export as TimeLine
-IMPROVED: captions of all list and edit area dialogs
-IMPROVED: test type edit area workflow
-IMPROVED: CLI EMR export tool
-IMPROVED: form disposal dialog
-IMPROVED: date/timestamp picker functionality
-IMPROVED: better duplicate person detection
-IMPROVED: document tree details view usage
-IMPROVED: test results panels links w/ documents
-IMPROVED: console encoding errors behaviour [thanks INADA Naoki]
-IMPROVED: ADR URL handling
-IMPROVED: age sort mode in document tree
-IMPROVED: age/DOB tooltip
-IMPROVED: data revisions display
-IMPROVED: EMR list journal formatting
-IMPROVED: lab/plotting: support better gnuplot scripts
+FIX: bills: failure to generate bill PDFs [thanks Marc]
+FIX: bills: failure to edit bill item date [thanks Marc]
+FIX: bills: failure to edit billable [thanks Marc]
+FIX: export area: fails to load when gm-burn.sh not found [thanks Marc]
+FIX: demographics: failure to edit type of address [thanks Marc]
+FIX: forms: failure to archive generated forms [thanks Marc]
+FIX: demographics: faulty display of patient addresses [thanks Marc]
-FIX: [Save] functionality of Export Area
-FIX: document tree sorting / document insertion
-FIX: inability to delete inbox message w/o receiver
-FIX: "lastname, firstname" based patient search under Python 3
-FIX: billing: invoice ID generation [thanks Marc]
-FIX: export area: saving document part entries
-FIX: lab: grid display row tooltips
-FIX: lists: context menu CSV export
-FIX: EMR/tree: selection of pseudo issue node
-FIX: documents/new: error handling of unreadable parts
-FIX: PG access: rewrite connection pool
-FIX: y2038 exception in DST detection
+ 22.12
- 22.11
-
-FIX: i18n.set_curr/force_curr_lang(), again [thanks lucian]
-
- 22.10
-
-IMPROVED: database fixup script
-
- 22.9
-
-FIX: clin.v_candidate_diagnoses: missing coalesce()
+IMPROVED: robustify backup script
');
-- --------------------------------------------------------------
-select gm.log_script_insertion('v22-release_notes-fixup.sql', '22.11');
+select gm.log_script_insertion('v22-release_notes-fixup.sql', '22.12');
View it on GitLab: https://salsa.debian.org/med-team/gnumed-server/-/commit/d6ac831e8277ea5fcc5683c80797fbb9e2c95987
--
View it on GitLab: https://salsa.debian.org/med-team/gnumed-server/-/commit/d6ac831e8277ea5fcc5683c80797fbb9e2c95987
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20200325/f3dc0f27/attachment-0001.html>
More information about the debian-med-commit
mailing list