Skip to content

Commit e078d4b

Browse files
committed
Refresh SQLAlchemy object after creation in POST
1 parent 2a878e9 commit e078d4b

File tree

4 files changed

+83
-4
lines changed

4 files changed

+83
-4
lines changed

CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ Not yet released.
4848
- :issue:`599`: fixes `unicode` bug using :func:`!urlparse.urljoin` with the
4949
`future`_ library in resource serialization.
5050
- :issue:`625`: adds schema metadata to root endpoint.
51+
- :issue:`630`: correctly respond with timezone-naive :class:`datetime`
52+
attributes on :http:method:`post` requests when the database doesn't support
53+
timezones, even if the request included a timezone-aware :class:`datetime`
54+
attribute.
5155

5256
.. _future: http://python-future.org/
5357

flask_restless/views/resources.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,19 @@ def post(self):
478478
except self.validation_exceptions as exception:
479479
return self._handle_validation_exception(exception)
480480
only = self.sparse_fields.get(self.collection_name)
481+
# Refresh the instance's attributes/relationships from the database.
482+
#
483+
# One place where we need this is when the request has a
484+
# timezone-aware datetime attribute. In this case, the
485+
# deserialized SQLAlchemy object actually seems to have the
486+
# timezone-aware datetime attribute regardless of whether the
487+
# underlying database has support for it. When added to the
488+
# database however, the timezone is (silently) dropped if the
489+
# database does not support it. By refreshing the object, we
490+
# force the attribute to be reloaded from the database, thereby
491+
# reloading a timezone-naive attribute into the SQLAlchemy
492+
# object.
493+
self.session.refresh(instance)
481494
# Get the dictionary representation of the new instance as it
482495
# appears in the database.
483496
try:

tests/test_creating.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
2121
"""
2222
from __future__ import division
23+
from datetime import timedelta
24+
from datetime import timezone
2325
from datetime import datetime
2426

2527
import dateutil
@@ -77,7 +79,7 @@ class Person(self.Base):
7779
id = Column(Integer, primary_key=True)
7880
age = Column(Integer)
7981
name = Column(Unicode, unique=True)
80-
birth_datetime = Column(DateTime, nullable=True)
82+
birth_datetime = Column(DateTime(timezone=False), nullable=True)
8183
bedtime = Column(Time)
8284
hangtime = Column(Interval)
8385
articles = relationship('Article')
@@ -885,6 +887,35 @@ def test_special_field_names(self):
885887
assert article['type'] == 'article'
886888
assert article['attributes']['type'] == u'fluff'
887889

890+
def test_timezone_aware_datetime(self):
891+
"""A timezone dropped by the DB should be reflected in the response.
892+
893+
For more information, see GitHub issue #630.
894+
895+
"""
896+
# Request to create a person with a timezone-aware datetime attribute.
897+
tz = timezone(timedelta(hours=-5))
898+
now = datetime.now(tz=tz)
899+
data = {
900+
'data': {
901+
'type': 'person',
902+
'attributes': {
903+
'birth_datetime': now.isoformat()
904+
}
905+
}
906+
}
907+
response = self.app.post('/api/person', data=dumps(data))
908+
self.assertEqual(response.status_code, 201)
909+
document = loads(response.data)
910+
person = document['data']
911+
birth_datetime = person['attributes']['birth_datetime']
912+
birth_datetime = dateutil.parser.parse(birth_datetime)
913+
# Our request had a timezone-aware attribute, but the database
914+
# is timezone-naive so we expect that the database created a
915+
# timezone-naive object. The returned resource should reflect
916+
# that timezone-naive object.
917+
self.assertEqual(birth_datetime, now.replace(tzinfo=None))
918+
888919

889920
class TestProcessors(ManagerTestBase):
890921
"""Tests for pre- and postprocessors."""

tests/test_updating.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
from __future__ import division
2222

2323
from datetime import datetime
24-
from unittest2 import skip
24+
from datetime import timedelta
25+
from datetime import timezone
2526

2627
try:
2728
from flask_sqlalchemy import SQLAlchemy
@@ -37,7 +38,6 @@
3738
from sqlalchemy import Integer
3839
from sqlalchemy import Time
3940
from sqlalchemy import Unicode
40-
from sqlalchemy.ext.associationproxy import association_proxy
4141
from sqlalchemy.ext.hybrid import hybrid_property
4242
from sqlalchemy.orm import backref
4343
from sqlalchemy.orm import relationship
@@ -82,7 +82,7 @@ class Person(self.Base):
8282
name = Column(Unicode, unique=True)
8383
bedtime = Column(Time)
8484
date_created = Column(Date)
85-
birth_datetime = Column(DateTime)
85+
birth_datetime = Column(DateTime(timezone=False))
8686

8787
def foo(self):
8888
return u'foo'
@@ -961,6 +961,37 @@ def test_integer_id_error_message(self):
961961
check_sole_error(response, 409, ['"id" element', 'resource object',
962962
'must be a JSON string'])
963963

964+
def test_timezone_aware_datetime(self):
965+
"""Test that timezone information is correctly dropped.
966+
967+
For more information, see GitHub issue #630.
968+
969+
"""
970+
# Create a person with a timezone-naive datetime attribute.
971+
now = datetime.now()
972+
person = self.Person(id=1, birth_datetime=now)
973+
self.session.add(person)
974+
self.session.commit()
975+
# Request to update the attribute with a timezone-aware datetime.
976+
tz = timezone(timedelta(hours=-5))
977+
later = datetime.now(tz=tz)
978+
data = {
979+
'data': {
980+
'id': '1',
981+
'type': 'person',
982+
'attributes': {
983+
'birth_datetime': later.isoformat()
984+
}
985+
}
986+
}
987+
response = self.app.patch('/api/person/1', data=dumps(data))
988+
self.assertEqual(response.status_code, 204)
989+
# Our request had a timezone-aware attribute, but the database
990+
# is timezone-naive so we expect that the database created a
991+
# timezone-naive object. The returned resource should reflect
992+
# that timezone-naive object.
993+
self.assertEqual(person.birth_datetime, later.replace(tzinfo=None))
994+
964995

965996
class TestProcessors(ManagerTestBase):
966997
"""Tests for pre- and postprocessors."""

0 commit comments

Comments
 (0)