Skip to content
This repository was archived by the owner on Jun 6, 2024. It is now read-only.
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
5 changes: 5 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[run]
branch=True
source=
marshmallow_jsonapi
tests
35 changes: 35 additions & 0 deletions marshmallow_jsonapi/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,41 @@ class Meta:
"""
pass

def __init__(self, *args, **kwargs):
if kwargs.get('self_url', None):
raise TypeError('Use `self_view` instead of `self_url` '
'using the Flask extension.')
if kwargs.get('self_url_kwargs', None):
raise TypeError('Use `self_view_kwargs` instead of '
'`self_url_kwargs` when using the Flask extension.')
if kwargs.get('related_url', None):
raise TypeError('Use `related_view` instead of `related_url` '
'using the Flask extension.')
if kwargs.get('related_url_kwargs', None):
raise TypeError('Use `related_view_kwargs` instead of '
'`related_url_kwargs` when using the Flask extension.')

self_view = kwargs.pop('self_view', None)
self_view_kwargs = kwargs.pop('self_view_kwargs', dict())
related_view = kwargs.pop('related_view', None)
related_view_kwargs = kwargs.pop('related_view_kwargs', dict())

if self_view_kwargs and not self_view:
raise ValueError('Must specify `self_view` keyword when '
'`self_view_kwargs` is specified.')

if related_view_kwargs and not related_view:
raise ValueError('Must specify `related_view` keyword when '
'`related_view_kwargs` is specified.')

if self_view:
kwargs['self_url'] = flask.url_for(self_view, **self_view_kwargs)
if related_view:
kwargs['related_url'] = flask.url_for(related_view,
**related_view_kwargs)

super(Schema, self).__init__(*args, **kwargs)

def generate_url(self, view_name, **kwargs):
"""Generate URL with any kwargs interpolated."""
return flask.url_for(view_name, **kwargs) if view_name else None
Expand Down
32 changes: 26 additions & 6 deletions marshmallow_jsonapi/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class Meta:

def __init__(self, *args, **kwargs):
self.include_data = kwargs.pop('include_data', ())
self.resource_identifier = kwargs.pop('resource_identifier', False)
self.override_self_url = kwargs.pop('self_url', None)
self.override_related_url = kwargs.pop('related_url', None)
super(Schema, self).__init__(*args, **kwargs)
if self.include_data:
self.check_relations(self.include_data)
Expand Down Expand Up @@ -364,13 +367,19 @@ def format_item(self, item):
ret['relationships'] = self.dict_class()
ret['relationships'][self.inflect(field_name)] = value
else:
# Resource identifier objects don't get attributes.
if self.resource_identifier:
continue

if 'attributes' not in ret:
ret['attributes'] = self.dict_class()
ret['attributes'][self.inflect(field_name)] = value

links = self.get_resource_links(item)
if links:
ret['links'] = links
# Resource identifier objects don't get links
if not self.resource_identifier:
links = self.get_resource_links(item)
if links:
ret['links'] = links
return ret

def format_items(self, data, many):
Expand All @@ -391,10 +400,21 @@ def get_top_level_links(self, data, many):
if self.opts.self_url_many:
self_link = self.generate_url(self.opts.self_url_many)
else:
if self.opts.self_url:
if self.opts.self_url and data is not None:
self_link = data.get('links', {}).get('self', None)

return {'self': self_link}
if self.override_self_url is not None:
self_link = self.override_self_url

links = dict()

if self_link is not None:
links['self'] = self_link

if self.override_related_url:
links['related'] = self.override_related_url

return links

def get_resource_links(self, item):
"""Hook for adding links to a resource object."""
Expand All @@ -412,7 +432,7 @@ def wrap_response(self, data, many):
# may only be included if there is data in the ret
if many or data:
top_level_links = self.get_top_level_links(data, many)
if top_level_links['self']:
if top_level_links:
ret['links'] = top_level_links
return ret

Expand Down
7 changes: 6 additions & 1 deletion tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ def get_top_level_links(self, data, many):
self_link = '/authors/'
else:
self_link = '/authors/{}'.format(data['id'])
return {'self': self_link}
links = {'self': self_link}

schema_links = super(AuthorSchema, self).get_top_level_links(data, many)
links.update(schema_links)

return links

class Meta:
type_ = 'people'
Expand Down
31 changes: 31 additions & 0 deletions tests/test_flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,37 @@ def test_self_link_many(self, app, posts):
assert 'links' in data['data'][0]
assert data['data'][0]['links']['self'] == '/posts/{}/'.format(posts[0].id)

@pytest.mark.parametrize('kwargs, exc, msg', [
(dict(self_url='foo'), TypeError, 'self_view'),
(dict(self_url_kwargs='foo'), TypeError, 'self_view_kwargs'),
(dict(related_url='foo'), TypeError, 'related_view'),
(dict(related_url_kwargs='foo'), TypeError, 'related_view_kwargs'),
(dict(self_view_kwargs='foo'), ValueError, 'self_view'),
(dict(related_view_kwargs='foo'), ValueError, 'related_view'),
])
def test_schema_view_override_exceptions(self, kwargs, exc, msg):
with pytest.raises(exc) as exc_info:
self.PostFlaskSchema(**kwargs)

assert msg in exc_info.value.args[0]

def test_view_override(self, app):
# Here we use "wrong" views just to check output.

schema = self.PostFlaskSchema(self_view='posts')
assert schema.override_self_url == '/posts/'

schema = self.PostFlaskSchema(
self_view='author_detail', self_view_kwargs={'author_id': 1})
assert schema.override_self_url == '/authors/1'

schema = self.PostFlaskSchema(related_view='posts')
assert schema.override_related_url == '/posts/'

schema = self.PostFlaskSchema(
related_view='author_detail', related_view_kwargs={'author_id': 1})
assert schema.override_related_url == '/authors/1'

def test_schema_with_empty_relationship(self, app, post_with_null_author):
data = unpack(self.PostAuthorFlaskSchema().dump(post_with_null_author))
assert 'relationships' not in data
Expand Down
49 changes: 49 additions & 0 deletions tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,34 @@ def test_dump_empty_list(self):
assert 'links' in data
assert data['links']['self'] == '/authors/'

def test_dump_single_resource_identifer(self, author):
data = unpack(AuthorSchema(resource_identifier=True).dump(author))

assert data == {
'data': {'id': str(author.id), 'type': 'people'},
'links': {'self': '/authors/' + str(author.id)}
}

def test_dump_many_resource_identifer(self, authors):
schema = AuthorSchema(many=True, resource_identifier=True)
data = unpack(schema.dump(authors))

assert set(data.keys()) == {'data', 'links'}
assert data['links'] == {'self': '/authors/'}
for i, author in enumerate(authors):
assert data['data'][i] == {'id': str(authors[i].id), 'type': 'people'}

def test_dump_none_resource_identifier(self):
data = unpack(AuthorSchema(resource_identifier=True).dump(None))

assert 'data' in data
assert data['data'] is None
assert 'links' not in data

def test_dump_empty_list_resource_identifier(self):
data = unpack(AuthorSchema(many=True, resource_identifier=True).dump([]))
assert data == {'data': [], 'links': {'self': '/authors/'}}


class TestCompoundDocuments:

Expand Down Expand Up @@ -724,3 +752,24 @@ class Meta:

assert assert_relationship_error('author', errors['errors'])
assert assert_relationship_error('comments', errors['errors'])


class TestOverrideUrls(object):
def test_self_url(self, author):
url = '/articles/1/author'
schema = AuthorSchema(self_url=url)
data = unpack(schema.dump(author))

assert data['links']['self'] == url

def test_related_url(self, author):
self_url = '/articles/1/relationships/author'
related_url = '/articles/1/author'
schema = AuthorSchema(
resource_identifier=True,
self_url=self_url,
related_url=related_url)
data = unpack(schema.dump(author))

assert data['links']['self'] == self_url
assert data['links']['related'] == related_url