Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refresh SQLAlchemy object after creation in POST #647

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ Not yet released.
`future`_ library in resource serialization.
- :issue:`625`: adds schema metadata to root endpoint.
- :issue:`626`: allows the client to request case-insensitive sorting.
- :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 @@ -481,6 +481,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