From ce2aad456c7577899cb14876741470aa37496b8b Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Fri, 4 Feb 2022 22:13:41 +0100 Subject: [PATCH 1/4] Avoid UnknownTimeZoneError when creating patient from Sample Traceback: Traceback (innermost last): Module ZServer.ZPublisher.Publish, line 144, in publish Module ZPublisher.mapply, line 85, in mapply Module ZServer.ZPublisher.Publish, line 44, in call_object Module senaite.app.listing.view, line 223, in __call__ Module senaite.app.listing.ajax, line 109, in handle_subpath Module senaite.core.decorators, line 20, in decorator Module senaite.app.listing.decorators, line 63, in wrapper Module senaite.app.listing.decorators, line 50, in wrapper Module senaite.app.listing.decorators, line 100, in wrapper Module senaite.app.listing.ajax, line 432, in ajax_folderitems Module senaite.app.listing.decorators, line 88, in wrapper Module senaite.app.listing.ajax, line 313, in get_folderitems Module senaite.app.listing.view, line 937, in folderitems Module senaite.patient.browser.patientfolder, line 116, in folderitem Module bika.lims.api, line 444, in get_url Module bika.lims.api, line 268, in is_brain Module ZODB.Connection, line 795, in setstate Module ZODB.serialize, line 633, in setGhostState Module ZODB.serialize, line 626, in getState Module pytz, line 307, in _p Module pytz.tzinfo, line 539, in unpickler Module pytz, line 188, in timezone UnknownTimeZoneError: (UnknownTimeZoneError('GMT+1',), , ('GMT+1',)) This happens when a `DateTime` object was converted with `asdatetime`, which resulted in a static timezone like this: `datetime.datetime(1980, 2, 25, 0, 0, tzinfo=)` --- src/senaite/patient/content/patient.py | 52 ++++++++++++++++++-------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/src/senaite/patient/content/patient.py b/src/senaite/patient/content/patient.py index 6ab0821..a16d0a4 100644 --- a/src/senaite/patient/content/patient.py +++ b/src/senaite/patient/content/patient.py @@ -20,13 +20,15 @@ from six import string_types +from AccessControl import ClassSecurityInfo from bika.lims import api from bika.lims.api.mail import is_valid_email_address -from DateTime import DateTime from plone.autoform import directives from plone.dexterity.content import Container from plone.supermodel import model from plone.supermodel.directives import fieldset +from Products.CMFCore import permissions +from senaite.core.api import dtime from senaite.core.schema import DatetimeField from senaite.core.z3cform.widgets.datetimewidget import DatetimeWidget from senaite.patient import api as patient_api @@ -219,6 +221,26 @@ class Patient(Container): """ _catalogs = [PATIENT_CATALOG] + security = ClassSecurityInfo() + + @security.private + def accessor(self, fieldname): + """Return the field accessor for the fieldname + """ + schema = api.get_schema(self) + if fieldname not in schema: + return None + return schema[fieldname].get + + @security.private + def mutator(self, fieldname): + """Return the field mutator for the fieldname + """ + schema = api.get_schema(self) + if fieldname not in schema: + return None + return schema[fieldname].set + def Title(self): fullname = self.get_fullname() return fullname.encode("utf8") @@ -297,22 +319,20 @@ def set_gender(self, value): value = k self.gender = value + @security.protected(permissions.View) def get_birthdate(self): - if not self.birthdate: - return None - return self.birthdate + """Returns the birthday with the field accessor + """ + accessor = self.accessor("birthdate") + return accessor(self) + @security.protected(permissions.ModifyPortalContent) def set_birthdate(self, value): - """Set birthdate field - - Tries to convert value to datetime before set. + """Set birthdate by the field accessor """ - if isinstance(value, DateTime): - # convert DateTime -> datetime - value = value.asdatetime() - elif isinstance(value, string_types): - # convert string to datetime - dt = api.to_date(value, None) - if dt is not None: - value = dt.asdatetime() - self.birthdate = value + dt = dtime.to_dt(value) + if dtime.is_date(dt): + # strip off timezone to avoid UnknownTimeZoneError + value = dtime.to_dt(dt.strftime("%Y-%m-%d")) + mutator = self.mutator("birthdate") + return mutator(self, value) From 60ee23c80e5860147159da6efcf3fb21e9f7d1fa Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Fri, 4 Feb 2022 22:21:47 +0100 Subject: [PATCH 2/4] Changelog updated --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 3062f5b..6a9a274 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog 1.1.0 (unreleased) ------------------ +- #29 Avoid UnknownTimeZoneError when creating patient from Sample - #28 Remember age/dob selection on context - #27 Fix cannot create samples with empty Patient ID From 709c31c106e5243df02c57d212765b30f9c22465 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sat, 5 Feb 2022 22:34:11 +0100 Subject: [PATCH 3/4] Rely on datetime field setter to convert birthdate --- src/senaite/patient/browser/patientfolder.py | 3 ++- src/senaite/patient/catalog/configure.zcml | 1 + src/senaite/patient/catalog/indexers.py | 12 +++++++-- .../patient/catalog/patient_catalog.py | 1 + src/senaite/patient/content/patient.py | 4 --- src/senaite/patient/upgrade/v01_01_000.py | 26 +++++++++++++++++++ 6 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/senaite/patient/browser/patientfolder.py b/src/senaite/patient/browser/patientfolder.py index 7f367f7..0973ebc 100644 --- a/src/senaite/patient/browser/patientfolder.py +++ b/src/senaite/patient/browser/patientfolder.py @@ -79,7 +79,8 @@ def __init__(self, context, request): ("gender", { "title": _("Gender"), }), ("birthdate", { - "title": _("Birthdate"), }), + "title": _("Birthdate"), + "index": "patient_birthdate"}), )) self.review_states = [ diff --git a/src/senaite/patient/catalog/configure.zcml b/src/senaite/patient/catalog/configure.zcml index bd576f0..03ce71d 100644 --- a/src/senaite/patient/catalog/configure.zcml +++ b/src/senaite/patient/catalog/configure.zcml @@ -9,6 +9,7 @@ + diff --git a/src/senaite/patient/catalog/indexers.py b/src/senaite/patient/catalog/indexers.py index 384aca7..8bd81dd 100644 --- a/src/senaite/patient/catalog/indexers.py +++ b/src/senaite/patient/catalog/indexers.py @@ -46,7 +46,7 @@ def patient_mrn(instance): @indexer(IPatient) def patient_fullname(instance): - """Index client fullname + """Index fullname """ fullname = instance.get_fullname() return fullname @@ -54,12 +54,20 @@ def patient_fullname(instance): @indexer(IPatient) def patient_email(instance): - """Index client email + """Index email """ email = instance.get_email() return email +@indexer(IPatient) +def patient_birthdate(instance): + """Index birthdate + """ + birthdate = instance.get_birthdate() + return birthdate + + @indexer(IPatient) def patient_searchable_text(instance): """Index for searchable text queries diff --git a/src/senaite/patient/catalog/patient_catalog.py b/src/senaite/patient/catalog/patient_catalog.py index ae530e6..a4067c7 100644 --- a/src/senaite/patient/catalog/patient_catalog.py +++ b/src/senaite/patient/catalog/patient_catalog.py @@ -34,6 +34,7 @@ ("patient_mrn", "", "FieldIndex"), ("patient_email", "", "FieldIndex"), ("patient_fullname", "", "FieldIndex"), + ("patient_birthdate", "", "DateIndex"), ("patient_searchable_text", "", "ZCTextIndex"), ] diff --git a/src/senaite/patient/content/patient.py b/src/senaite/patient/content/patient.py index a16d0a4..6d9d9c0 100644 --- a/src/senaite/patient/content/patient.py +++ b/src/senaite/patient/content/patient.py @@ -330,9 +330,5 @@ def get_birthdate(self): def set_birthdate(self, value): """Set birthdate by the field accessor """ - dt = dtime.to_dt(value) - if dtime.is_date(dt): - # strip off timezone to avoid UnknownTimeZoneError - value = dtime.to_dt(dt.strftime("%Y-%m-%d")) mutator = self.mutator("birthdate") return mutator(self, value) diff --git a/src/senaite/patient/upgrade/v01_01_000.py b/src/senaite/patient/upgrade/v01_01_000.py index 506d74c..2b0158a 100644 --- a/src/senaite/patient/upgrade/v01_01_000.py +++ b/src/senaite/patient/upgrade/v01_01_000.py @@ -18,10 +18,13 @@ # Copyright 2020-2022 by it's authors. # Some rights reserved, see README and LICENSE. +from bika.lims import api from senaite.core.upgrade import upgradestep from senaite.core.upgrade.utils import UpgradeUtils from senaite.patient import logger +from senaite.patient.config import PATIENT_CATALOG from senaite.patient.config import PRODUCT_NAME +from senaite.patient.setuphandlers import setup_catalogs version = "1.1.0" @@ -43,5 +46,28 @@ def upgrade(tool): # -------- ADD YOUR STUFF BELOW -------- + # add dateindex for birthdates + setup_catalogs(portal) + + # migrate birthdates w/o time but with valid timezone + migrate_birthdates(portal) + logger.info("{0} upgraded to version {1}".format(PRODUCT_NAME, version)) return True + + +def migrate_birthdates(portal): + """Migrate all birthdates from patients to be timezone aware + """ + logger.info("Migrate patient birthdate timezones ...") + catalog = api.get_tool(PATIENT_CATALOG) + results = catalog({"portal_type": "Patient"}) + for brain in results: + patient = api.get_object(brain) + birthdate = patient.birthdate + if birthdate: + # clean existing time and timezone + date = birthdate.strftime("%Y-%m-%d") + patient.set_birthdate(date) + + logger.info("Migrate patient birthdate timezones [DONE]") From 8f459c1a74be79c02f9cf1977e127e9ca1ec5443 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Sat, 5 Feb 2022 23:00:23 +0100 Subject: [PATCH 4/4] Append system timezone instead of UTC --- src/senaite/patient/upgrade/v01_01_000.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/senaite/patient/upgrade/v01_01_000.py b/src/senaite/patient/upgrade/v01_01_000.py index 2b0158a..0c24bea 100644 --- a/src/senaite/patient/upgrade/v01_01_000.py +++ b/src/senaite/patient/upgrade/v01_01_000.py @@ -19,6 +19,7 @@ # Some rights reserved, see README and LICENSE. from bika.lims import api +from senaite.core.api import dtime from senaite.core.upgrade import upgradestep from senaite.core.upgrade.utils import UpgradeUtils from senaite.patient import logger @@ -62,12 +63,17 @@ def migrate_birthdates(portal): logger.info("Migrate patient birthdate timezones ...") catalog = api.get_tool(PATIENT_CATALOG) results = catalog({"portal_type": "Patient"}) + timezone = dtime.get_os_timezone() for brain in results: patient = api.get_object(brain) birthdate = patient.birthdate if birthdate: # clean existing time and timezone date = birthdate.strftime("%Y-%m-%d") + # append current OS timezone if possible + if timezone: + date = date + " %s" % timezone patient.set_birthdate(date) + patient.reindexObject() logger.info("Migrate patient birthdate timezones [DONE]")