# HG changeset patch # User Sylvain Thénault # Date 1450794910 -3600 # Node ID 75d752e6daf7e6c38a357988182e2ec43f752ded # Parent 63d860a14a170d47502e153362c2abb9c0d574fe [server] improve TZDatetime support by depending on logilab-database 1.15.0 including https://www.logilab.org/ticket/1485893, we can now keep the tzinfo attribute on datetime objects for TZDatetime attributes, so one knows that this is a tz-aware datetime. To easily make it work with backends that have no tz support, we keep converting tz-aware datetime objects into utc naive datetime objects before sending them to the database. diff -r 63d860a14a17 -r 75d752e6daf7 __pkginfo__.py --- a/__pkginfo__.py Fri Dec 18 09:23:23 2015 +0100 +++ b/__pkginfo__.py Tue Dec 22 15:35:10 2015 +0100 @@ -49,8 +49,9 @@ 'lxml': '', # XXX graphviz # server dependencies - 'logilab-database': '>= 1.13.0', + 'logilab-database': '>= 1.14.0', 'passlib': '', + 'pytz': '', 'Markdown': '' } diff -r 63d860a14a17 -r 75d752e6daf7 cubicweb.spec --- a/cubicweb.spec Fri Dec 18 09:23:23 2015 +0100 +++ b/cubicweb.spec Tue Dec 22 15:35:10 2015 +0100 @@ -25,11 +25,12 @@ Requires: %{python}-logilab-mtconverter >= 0.8.0 Requires: %{python}-rql >= 0.31.2 Requires: %{python}-yams >= 0.41.1 -Requires: %{python}-logilab-database >= 1.13.0 +Requires: %{python}-logilab-database >= 1.14.0 Requires: %{python}-passlib Requires: %{python}-lxml Requires: %{python}-twisted-web Requires: %{python}-markdown +Requires: %{python}-tz # the schema view uses `dot'; at least on el5, png output requires graphviz-gd Requires: graphviz-gd Requires: gettext diff -r 63d860a14a17 -r 75d752e6daf7 debian/control --- a/debian/control Fri Dec 18 09:23:23 2015 +0100 +++ b/debian/control Tue Dec 22 15:35:10 2015 +0100 @@ -16,6 +16,7 @@ python-unittest2 | python (>= 2.7), python-logilab-mtconverter, python-markdown, + python-tz, python-rql, python-yams (>= 0.41.1), python-lxml, @@ -53,7 +54,7 @@ ${python:Depends}, cubicweb-common (= ${source:Version}), cubicweb-ctl (= ${source:Version}), - python-logilab-database (>= 1.13.0), + python-logilab-database (>= 1.14.0), cubicweb-postgresql-support | cubicweb-mysql-support | python-pysqlite2, diff -r 63d860a14a17 -r 75d752e6daf7 hooks/metadata.py --- a/hooks/metadata.py Fri Dec 18 09:23:23 2015 +0100 +++ b/hooks/metadata.py Tue Dec 22 15:35:10 2015 +0100 @@ -22,6 +22,8 @@ from datetime import datetime from base64 import b64encode +from pytz import utc + from cubicweb.predicates import is_instance from cubicweb.server import hook from cubicweb.server.edition import EditedEntity @@ -41,7 +43,7 @@ events = ('before_add_entity',) def __call__(self): - timestamp = datetime.utcnow() + timestamp = datetime.now(utc) edited = self.entity.cw_edited if not edited.get('creation_date'): edited['creation_date'] = timestamp @@ -64,7 +66,7 @@ # XXX to be really clean, we should turn off modification_date update # explicitly on each command where we do not want that behaviour. if not self._cw.vreg.config.repairing: - self.entity.cw_edited.setdefault('modification_date', datetime.utcnow()) + self.entity.cw_edited.setdefault('modification_date', datetime.now(utc)) class SetCreatorOp(hook.DataOperationMixIn, hook.Operation): diff -r 63d860a14a17 -r 75d752e6daf7 hooks/test/unittest_hooks.py --- a/hooks/test/unittest_hooks.py Fri Dec 18 09:23:23 2015 +0100 +++ b/hooks/test/unittest_hooks.py Tue Dec 22 15:35:10 2015 +0100 @@ -26,6 +26,7 @@ from six import text_type +from pytz import utc from cubicweb import ValidationError, AuthenticationError, BadConnectionId from cubicweb.devtools.testlib import CubicWebTC @@ -115,7 +116,7 @@ def test_metadata_creation_modification_date(self): with self.admin_access.repo_cnx() as cnx: - _now = datetime.utcnow() + _now = datetime.now(utc) entity = cnx.create_entity('Workflow', name=u'wf1') self.assertEqual((entity.creation_date - _now).seconds, 0) self.assertEqual((entity.modification_date - _now).seconds, 0) diff -r 63d860a14a17 -r 75d752e6daf7 server/sqlutils.py --- a/server/sqlutils.py Fri Dec 18 09:23:23 2015 +0100 +++ b/server/sqlutils.py Tue Dec 22 15:35:10 2015 +0100 @@ -30,6 +30,8 @@ from six import string_types, text_type from six.moves import filter +from pytz import utc + from logilab import database as db, common as lgc from logilab.common.shellutils import ProgressBar, DummyProgressBar from logilab.common.deprecation import deprecated @@ -503,6 +505,8 @@ row[cellindex] = strptime(value, '%Y-%m-%d %H:%M:%S') except Exception: row[cellindex] = strptime(value, '%Y-%m-%d') + if vtype == 'TZDatetime': + row[cellindex] = row[cellindex].replace(tzinfo=utc) if vtype == 'Time' and isinstance(value, text_type): found_date = True try: diff -r 63d860a14a17 -r 75d752e6daf7 server/test/unittest_postgres.py --- a/server/test/unittest_postgres.py Fri Dec 18 09:23:23 2015 +0100 +++ b/server/test/unittest_postgres.py Tue Dec 22 15:35:10 2015 +0100 @@ -127,20 +127,22 @@ 'WHERE X has_text "cubicweb"').rows, [[c1.eid,], [c3.eid,], [c2.eid,]]) - def test_tz_datetime(self): with self.admin_access.repo_cnx() as cnx: - cnx.execute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s", - {'date': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))}) + bob = cnx.create_entity('Personne', nom=u'bob', + tzdatenaiss=datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))) datenaiss = cnx.execute("Any XD WHERE X nom 'bob', X tzdatenaiss XD")[0][0] - self.assertEqual(datenaiss.tzinfo, None) + self.assertIsNotNone(datenaiss.tzinfo) self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0)) cnx.commit() - cnx.execute("INSERT Personne X: X nom 'boby', X tzdatenaiss %(date)s", - {'date': datetime(1977, 6, 7, 2, 0)}) + cnx.create_entity('Personne', nom=u'boby', + tzdatenaiss=datetime(1977, 6, 7, 2, 0)) datenaiss = cnx.execute("Any XD WHERE X nom 'boby', X tzdatenaiss XD")[0][0] - self.assertEqual(datenaiss.tzinfo, None) + self.assertIsNotNone(datenaiss.tzinfo) self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 2, 0)) + rset = cnx.execute("Any X WHERE X tzdatenaiss %(d)s", + {'d': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))}) + self.assertEqual(rset.rows, [[bob.eid]]) def test_constraint_validationerror(self): with self.admin_access.repo_cnx() as cnx: diff -r 63d860a14a17 -r 75d752e6daf7 server/test/unittest_querier.py --- a/server/test/unittest_querier.py Fri Dec 18 09:23:23 2015 +0100 +++ b/server/test/unittest_querier.py Tue Dec 22 15:35:10 2015 +0100 @@ -21,6 +21,8 @@ from datetime import date, datetime, timedelta, tzinfo +import pytz + from six import PY2, integer_types, binary_type, text_type from logilab.common.testlib import TestCase, unittest_main from rql import BadRQLQuery, RQLSyntaxError @@ -855,6 +857,7 @@ self.assertIsInstance(rset[0][0], datetime) rset = self.qexecute('Any MAX(D) WHERE X is Personne, X tzdatenaiss D') self.assertIsInstance(rset[0][0], datetime) + self.assertEqual(rset[0][0].tzinfo, pytz.utc) def test_today(self): self.qexecute("INSERT Tag X: X name 'bidule', X creation_date TODAY") @@ -1398,7 +1401,7 @@ self.qexecute("INSERT Personne X: X nom 'bob', X tzdatenaiss %(date)s", {'date': datetime(1977, 6, 7, 2, 0, tzinfo=FixedOffset(1))}) datenaiss = self.qexecute("Any XD WHERE X nom 'bob', X tzdatenaiss XD")[0][0] - self.assertEqual(datenaiss.tzinfo, None) + self.assertIsNotNone(datenaiss.tzinfo) self.assertEqual(datenaiss.utctimetuple()[:5], (1977, 6, 7, 1, 0)) def test_tz_datetime_cache_nonregr(self): diff -r 63d860a14a17 -r 75d752e6daf7 sobjects/cwxmlparser.py --- a/sobjects/cwxmlparser.py Fri Dec 18 09:23:23 2015 +0100 +++ b/sobjects/cwxmlparser.py Tue Dec 22 15:35:10 2015 +0100 @@ -37,6 +37,7 @@ from six import text_type from six.moves.urllib.parse import urlparse, urlunparse, parse_qs, urlencode +import pytz from logilab.common.date import todate, totime from logilab.common.textutils import splitstrip, text_to_dict from logilab.common.decorators import classproperty @@ -65,7 +66,11 @@ # XXX handle timezone, though this will be enough as TZDatetime are # serialized without time zone by default (UTC time). See # cw.web.views.xmlrss.SERIALIZERS. -DEFAULT_CONVERTERS['TZDatetime'] = convert_datetime +def convert_tzdatetime(ustr): + date = convert_datetime(ustr) + date = date.replace(tzinfo=pytz.utc) + return date +DEFAULT_CONVERTERS['TZDatetime'] = convert_tzdatetime def convert_time(ustr): return totime(datetime.strptime(ustr, '%H:%M:%S')) DEFAULT_CONVERTERS['Time'] = convert_time diff -r 63d860a14a17 -r 75d752e6daf7 sobjects/test/unittest_cwxmlparser.py --- a/sobjects/test/unittest_cwxmlparser.py Fri Dec 18 09:23:23 2015 +0100 +++ b/sobjects/test/unittest_cwxmlparser.py Tue Dec 22 15:35:10 2015 +0100 @@ -20,6 +20,7 @@ from six.moves.urllib.parse import urlsplit, parse_qsl +import pytz from cubicweb.devtools.testlib import CubicWebTC from cubicweb.sobjects.cwxmlparser import CWEntityXMLParser @@ -215,8 +216,8 @@ with self.admin_access.web_request() as req: user = req.execute('CWUser X WHERE X login "sthenault"').get_entity(0, 0) - self.assertEqual(user.creation_date, datetime(2010, 1, 22, 10, 27, 59)) - self.assertEqual(user.modification_date, datetime(2011, 1, 25, 14, 14, 6)) + self.assertEqual(user.creation_date, datetime(2010, 1, 22, 10, 27, 59, tzinfo=pytz.utc)) + self.assertEqual(user.modification_date, datetime(2011, 1, 25, 14, 14, 6, tzinfo=pytz.utc)) self.assertEqual(user.cwuri, 'http://pouet.org/5') self.assertEqual(user.cw_source[0].name, 'myfeed') self.assertEqual(user.absolute_url(), 'http://pouet.org/5') @@ -300,8 +301,8 @@ with self.repo.internal_cnx() as cnx: stats = dfsource.pull_data(cnx, force=True, raise_on_error=True) user = cnx.execute('CWUser X WHERE X login "sthenault"').get_entity(0, 0) - self.assertEqual(user.creation_date, datetime(2010, 1, 22, 10, 27, 59)) - self.assertEqual(user.modification_date, datetime(2011, 1, 25, 14, 14, 6)) + self.assertEqual(user.creation_date, datetime(2010, 1, 22, 10, 27, 59, tzinfo=pytz.utc)) + self.assertEqual(user.modification_date, datetime(2011, 1, 25, 14, 14, 6, tzinfo=pytz.utc)) self.assertEqual(user.cwuri, 'http://pouet.org/5') self.assertEqual(user.cw_source[0].name, 'myfeed')