Skip to content

Commit

Permalink
Merge 4913359 into e5751f7
Browse files Browse the repository at this point in the history
  • Loading branch information
jfinkels committed Jul 1, 2016
2 parents e5751f7 + 4913359 commit c449d11
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 27 deletions.
1 change: 1 addition & 0 deletions CHANGES
Expand Up @@ -29,6 +29,7 @@ Not yet released.
- :issue:`492`: support JSON API recommended "simple" filtering.
- :issue:`508`: flush the session before postprocessors, and commit after.
- :issue:`536`: adds support for single-table inheritance.
- :issue:`546`: adds support for joined table inheritance.

Version 1.0.0b1
---------------
Expand Down
16 changes: 8 additions & 8 deletions docs/databasesetup.rst
Expand Up @@ -74,14 +74,14 @@ relationship in the resource object.
Polymorphic models
------------------

Flask-Restless automatically handles polymorphic models. For single-table
inheritance, we have made some design choices we believe are reasonable.
Requests to create, update, or delete a resource must specify a ``type`` that
matches the collection name of the endpoint. This means you cannot request to
create a resource of the subclass type at the endpoint for the superclass type,
for example. On the other hand, requests to fetch a collection of objects that
have a subclass will yield a response that includes all resources of the
superclass and all resources of any subclass.
Flask-Restless automatically handles polymorphic models defined using either
single table or joined table inheritance. We have made some design choices we
believe are reasonable. Requests to create, update, or delete a resource must
specify a ``type`` that matches the collection name of the endpoint. This means
you cannot request to create a resource of the subclass type at the endpoint
for the superclass type, for example. On the other hand, requests to fetch a
collection of objects that have a subclass will yield a response that includes
all resources of the superclass and all resources of any subclass.

For example, consider a setup where there are employees and some employees are
managers::
Expand Down
7 changes: 5 additions & 2 deletions flask_restless/serialization/serializers.py
Expand Up @@ -297,9 +297,12 @@ def _dump(self, instance, only=None):
# Exclude column names that are blacklisted.
columns = (c for c in columns
if not c.startswith('__') and c not in COLUMN_BLACKLIST)
# Exclude column names that are foreign keys.
# Exclude column names that are foreign keys (unless the foreign
# key is the primary key for the model; this can happen in the
# joined table inheritance database configuration).
foreign_key_columns = foreign_keys(model)
columns = (c for c in columns if c not in foreign_key_columns)
columns = (c for c in columns if c not in foreign_key_columns or
c == primary_key_for(model))

# Create a dictionary mapping attribute name to attribute value for
# this particular instance.
Expand Down
130 changes: 113 additions & 17 deletions tests/test_polymorphism.py
Expand Up @@ -12,16 +12,18 @@
"""Unit tests for interacting with polymorphic models.
The tests in this module use models defined using `single table
inheritance`_.
inheritance`_ and `joined table inheritance`_.
.. _single table inheritance: http://docs.sqlalchemy.org/en/latest/orm/inheritance.html#single-table-inheritance
.. _joined table inheritance: http://docs.sqlalchemy.org/en/latest/orm/inheritance.html#joined-table-inheritance
"""
from operator import itemgetter

from sqlalchemy import Column
from sqlalchemy import Integer
from sqlalchemy import Enum
from sqlalchemy import ForeignKey
from sqlalchemy import Unicode

from flask_restless import DefaultSerializer
Expand All @@ -32,15 +34,12 @@
from .helpers import ManagerTestBase


class PolymorphismTestBase(ManagerTestBase):
"""Base class for tests of APIs created for polymorphic models
defined using single table inheritance.
"""
class SingleTableInheritanceSetupMixin(object):
"""Mixin for setting up single table inheritance in test cases."""

def setUp(self):
"""Creates polymorphic models using single table inheritance."""
super(PolymorphismTestBase, self).setUp()
super(SingleTableInheritanceSetupMixin, self).setUp()

class Employee(self.Base):
__tablename__ = 'employee'
Expand All @@ -64,11 +63,42 @@ class Manager(Employee):
self.Base.metadata.create_all()


class FetchingTestBase(PolymorphismTestBase):
class JoinedTableInheritanceSetupMixin(object):
"""Mixin for setting up joined table inheritance in test cases."""

def setUp(self):
"""Creates polymorphic models using joined table inheritance."""
super(JoinedTableInheritanceSetupMixin, self).setUp()

class Employee(self.Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
type = Column(Enum('employee', 'manager'), nullable=False)
name = Column(Unicode)
__mapper_args__ = {
'polymorphic_on': type,
'polymorphic_identity': 'employee'
}

# This model inherits directly from the `Employee` class, so
# there is only one table being used.
class Manager(Employee):
__tablename__ = 'manager'
id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
__mapper_args__ = {
'polymorphic_identity': 'manager'
}

self.Employee = Employee
self.Manager = Manager
self.Base.metadata.create_all()


class FetchingTestMixinBase(object):
"""Base class for test cases for fetching resources."""

def setUp(self):
super(FetchingTestBase, self).setUp()
super(FetchingTestMixinBase, self).setUp()

# Create the APIs for the Employee and Manager.
self.apimanager = self.manager
Expand All @@ -83,7 +113,7 @@ def setUp(self):
self.session.commit()


class TestFetchCollection(FetchingTestBase):
class FetchCollectionTestMixin(FetchingTestMixinBase):
"""Tests for fetching a collection of resources defined using single
table inheritance.
Expand Down Expand Up @@ -155,7 +185,7 @@ def serialize(self, instance, *args, **kw):
assert employees[1]['attributes']['baz'] == 'xyzzy'


class TestFetchResource(FetchingTestBase):
class FetchResourceTestMixin(FetchingTestMixinBase):
"""Tests for fetching a single resource defined using single table
inheritance.
Expand Down Expand Up @@ -202,14 +232,14 @@ def test_subclass_at_superclass(self):
assert response.status_code == 404


class TestCreating(PolymorphismTestBase):
class CreatingTestMixin(object):
"""Tests for APIs created for polymorphic models defined using
single table inheritance.
"""

def setUp(self):
super(TestCreating, self).setUp()
super(CreatingTestMixin, self).setUp()
self.manager.create_api(self.Employee, methods=['POST'])
self.manager.create_api(self.Manager, methods=['POST'])

Expand Down Expand Up @@ -278,11 +308,11 @@ def test_superclass_at_subclass(self):
'type', 'manager', 'employee'])


class TestDeleting(PolymorphismTestBase):
class DeletingTestMixin(object):
"""Tests for deleting resources."""

def setUp(self):
super(TestDeleting, self).setUp()
super(DeletingTestMixin, self).setUp()

# Create the APIs for the Employee and Manager.
self.manager.create_api(self.Employee, methods=['DELETE'])
Expand Down Expand Up @@ -339,11 +369,11 @@ def test_superclass_at_subclass(self):
assert self.session.query(self.Employee).all() == self.all_employees


class TestUpdating(PolymorphismTestBase):
class UpdatingTestMixin(object):
"""Tests for updating resources."""

def setUp(self):
super(TestUpdating, self).setUp()
super(UpdatingTestMixin, self).setUp()

# Create the APIs for the Employee and Manager.
self.manager.create_api(self.Employee, methods=['PATCH'])
Expand Down Expand Up @@ -433,3 +463,69 @@ def test_superclass_at_subclass(self):
response = self.app.patch('/api/manager/1', data=dumps(data))
check_sole_error(response, 404, ['No resource found', 'type',
'manager', 'ID', '1'])


class TestFetchCollectionSingle(FetchCollectionTestMixin,
SingleTableInheritanceSetupMixin,
ManagerTestBase):
"""Tests for fetching a collection of resources defined using single
table inheritance.
"""


class TestFetchCollectionJoined(FetchCollectionTestMixin,
JoinedTableInheritanceSetupMixin,
ManagerTestBase):
"""Tests for fetching a collection of resources defined using joined
table inheritance.
"""


class TestFetchResourceSingle(FetchResourceTestMixin,
SingleTableInheritanceSetupMixin,
ManagerTestBase):
"""Tests for fetching a single resource defined using single table
inheritance.
"""


class TestFetchResourceJoined(FetchResourceTestMixin,
JoinedTableInheritanceSetupMixin,
ManagerTestBase):
"""Tests for fetching a single resource defined using joined table
inheritance.
"""


class TestCreatingSingle(CreatingTestMixin, SingleTableInheritanceSetupMixin,
ManagerTestBase):
"""Tests for creating a resource defined using single table inheritance."""


class TestCreatingJoined(CreatingTestMixin, JoinedTableInheritanceSetupMixin,
ManagerTestBase):
"""Tests for creating a resource defined using joined table inheritance."""


class TestDeletingSingle(DeletingTestMixin, SingleTableInheritanceSetupMixin,
ManagerTestBase):
"""Tests for deleting a resource defined using single table inheritance."""


class TestDeletingJoined(DeletingTestMixin, JoinedTableInheritanceSetupMixin,
ManagerTestBase):
"""Tests for deleting a resource defined using joined table inheritance."""


class TestUpdatingSingle(UpdatingTestMixin, SingleTableInheritanceSetupMixin,
ManagerTestBase):
"""Tests for updating a resource defined using single table inheritance."""


class TestUpdatingJoined(UpdatingTestMixin, JoinedTableInheritanceSetupMixin,
ManagerTestBase):
"""Tests for updating a resource defined using joined table inheritance."""

0 comments on commit c449d11

Please sign in to comment.