Skip to content

Commit

Permalink
Merge e078d4b into 2a878e9
Browse files Browse the repository at this point in the history
  • Loading branch information
jfinkels committed Mar 25, 2017
2 parents 2a878e9 + e078d4b commit db8caf2
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Not yet released.
- :issue:`599`: fixes `unicode` bug using :func:`!urlparse.urljoin` with the
`future`_ library in resource serialization.
- :issue:`625`: adds schema metadata to root endpoint.
- :issue:`630`: correctly respond with timezone-naive :class:`datetime`
attributes on :http:method:`post` requests when the database doesn't support
timezones, even if the request included a timezone-aware :class:`datetime`
attribute.

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

Expand Down
13 changes: 13 additions & 0 deletions flask_restless/views/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,19 @@ def post(self):
except self.validation_exceptions as exception:
return self._handle_validation_exception(exception)
only = self.sparse_fields.get(self.collection_name)
# Refresh the instance's attributes/relationships from the database.
#
# One place where we need this is when the request has a
# timezone-aware datetime attribute. In this case, the
# deserialized SQLAlchemy object actually seems to have the
# timezone-aware datetime attribute regardless of whether the
# underlying database has support for it. When added to the
# database however, the timezone is (silently) dropped if the
# database does not support it. By refreshing the object, we
# force the attribute to be reloaded from the database, thereby
# reloading a timezone-naive attribute into the SQLAlchemy
# object.
self.session.refresh(instance)
# Get the dictionary representation of the new instance as it
# appears in the database.
try:
Expand Down
33 changes: 32 additions & 1 deletion tests/test_creating.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"""
from __future__ import division
from datetime import timedelta
from datetime import timezone
from datetime import datetime

import dateutil
Expand Down Expand Up @@ -77,7 +79,7 @@ class Person(self.Base):
id = Column(Integer, primary_key=True)
age = Column(Integer)
name = Column(Unicode, unique=True)
birth_datetime = Column(DateTime, nullable=True)
birth_datetime = Column(DateTime(timezone=False), nullable=True)
bedtime = Column(Time)
hangtime = Column(Interval)
articles = relationship('Article')
Expand Down Expand Up @@ -885,6 +887,35 @@ def test_special_field_names(self):
assert article['type'] == 'article'
assert article['attributes']['type'] == u'fluff'

def test_timezone_aware_datetime(self):
"""A timezone dropped by the DB should be reflected in the response.
For more information, see GitHub issue #630.
"""
# Request to create a person with a timezone-aware datetime attribute.
tz = timezone(timedelta(hours=-5))
now = datetime.now(tz=tz)
data = {
'data': {
'type': 'person',
'attributes': {
'birth_datetime': now.isoformat()
}
}
}
response = self.app.post('/api/person', data=dumps(data))
self.assertEqual(response.status_code, 201)
document = loads(response.data)
person = document['data']
birth_datetime = person['attributes']['birth_datetime']
birth_datetime = dateutil.parser.parse(birth_datetime)
# Our request had a timezone-aware attribute, but the database
# is timezone-naive so we expect that the database created a
# timezone-naive object. The returned resource should reflect
# that timezone-naive object.
self.assertEqual(birth_datetime, now.replace(tzinfo=None))


class TestProcessors(ManagerTestBase):
"""Tests for pre- and postprocessors."""
Expand Down
37 changes: 34 additions & 3 deletions tests/test_updating.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
from __future__ import division

from datetime import datetime
from unittest2 import skip
from datetime import timedelta
from datetime import timezone

try:
from flask_sqlalchemy import SQLAlchemy
Expand All @@ -37,7 +38,6 @@
from sqlalchemy import Integer
from sqlalchemy import Time
from sqlalchemy import Unicode
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import backref
from sqlalchemy.orm import relationship
Expand Down Expand Up @@ -82,7 +82,7 @@ class Person(self.Base):
name = Column(Unicode, unique=True)
bedtime = Column(Time)
date_created = Column(Date)
birth_datetime = Column(DateTime)
birth_datetime = Column(DateTime(timezone=False))

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

def test_timezone_aware_datetime(self):
"""Test that timezone information is correctly dropped.
For more information, see GitHub issue #630.
"""
# Create a person with a timezone-naive datetime attribute.
now = datetime.now()
person = self.Person(id=1, birth_datetime=now)
self.session.add(person)
self.session.commit()
# Request to update the attribute with a timezone-aware datetime.
tz = timezone(timedelta(hours=-5))
later = datetime.now(tz=tz)
data = {
'data': {
'id': '1',
'type': 'person',
'attributes': {
'birth_datetime': later.isoformat()
}
}
}
response = self.app.patch('/api/person/1', data=dumps(data))
self.assertEqual(response.status_code, 204)
# Our request had a timezone-aware attribute, but the database
# is timezone-naive so we expect that the database created a
# timezone-naive object. The returned resource should reflect
# that timezone-naive object.
self.assertEqual(person.birth_datetime, later.replace(tzinfo=None))


class TestProcessors(ManagerTestBase):
"""Tests for pre- and postprocessors."""
Expand Down

0 comments on commit db8caf2

Please sign in to comment.