Skip to content

Commit

Permalink
Merge branch 'release/0.6.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
pavlov99 committed Feb 8, 2015
2 parents 2e8af9f + f45d7d3 commit ca6ac75
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 81 deletions.
2 changes: 1 addition & 1 deletion jsonapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
""" JSON:API realization."""
__version = (0, 6, 1)
__version = (0, 6, 2)

__version__ = version = '.'.join(map(str, __version))
__project__ = PROJECT = __name__
6 changes: 4 additions & 2 deletions jsonapi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,21 @@ class Meta:

logger = logging.getLogger(__name__)

model_inspector = ModelInspector()
model_inspector.inspect()


class API(object):

""" API handler."""

CONTENT_TYPE = "application/vnd.api+json"
model_inspector = model_inspector

def __init__(self):
self._resources = []
self.base_url = None # base server url
self.api_url = None # api root url
self.model_inspector = ModelInspector()
self.model_inspector.inspect()

@property
def resource_map(self):
Expand Down
6 changes: 4 additions & 2 deletions jsonapi/model_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

class ModelInfo(object):

def __init__(self, fields_own=None, fields_to_one=None, fields_to_many=None,
auth_user_paths=None, is_user=None):
def __init__(self, name, fields_own=None, fields_to_one=None,
fields_to_many=None, auth_user_paths=None, is_user=None):
self.name = name
self.fields_own = fields_own or []
self.fields_to_one = fields_to_one or []
self.fields_to_many = fields_to_many or []
Expand Down Expand Up @@ -79,6 +80,7 @@ def inspect(self):

self.models = {
model: ModelInfo(
get_model_name(model),
fields_own=self._get_fields_own(model),
fields_to_one=self._get_fields_self_foreign_key(model),
fields_to_many=self._get_fields_others_foreign_key(model) +
Expand Down
26 changes: 15 additions & 11 deletions jsonapi/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def get(cls, request=None, **kwargs):
meta = {}
if cls.Meta.page_size is not None:
paginator = Paginator(queryset, cls.Meta.page_size)
page = int(kwargs.get('page', 1))
page = int(queryargs.get('page') or 1)
meta["count"] = paginator.count
meta["num_pages"] = paginator.num_pages
meta["page_size"] = cls.Meta.page_size
Expand All @@ -277,16 +277,20 @@ def get(cls, request=None, **kwargs):
meta["page_prev"] = objects.previous_page_number() \
if objects.has_previous() else None

data = [
cls.dump_document(
m,
fields_own=fields_own,
fields_to_one=model_info.fields_to_one,
# fields_to_many=model_info.fields_to_many
)
for m in objects
]
response = {cls.Meta.name_plural: data}
fields_include = set(queryargs.get("include", []))
fields_to_one = [f for f in model_info.fields_to_one
if f.name in fields_include]
fields_to_many = [f for f in model_info.fields_to_many
if f.name in fields_include]


response = cls.dump_documents(
cls,
objects,
fields_own=fields_own,
fields_to_one=fields_to_one,
fields_to_many=fields_to_many
)
if meta:
response["meta"] = meta
return response
Expand Down
70 changes: 63 additions & 7 deletions jsonapi/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ def dump_document(cls, model_instance, fields_own=None, fields_to_one=None,
fields_to_many = fields_to_many or []

document = {}
# apply rules for field serialization
for field in fields_own:
value = getattr(model_instance, field.name)

Expand All @@ -100,12 +99,11 @@ def dump_document(cls, model_instance, fields_own=None, fields_to_one=None,

document[field.name] = value

if fields_to_one or fields_to_many:
document["links"] = {}

for field in fields_to_one:
document["links"][field.name] = getattr(
model_instance, "{}_id".format(field.name))
for field in model_instance._meta.fields:
if field.rel:
document["links"] = document.get("links") or {}
document["links"][field.name] = getattr(
model_instance, "{}_id".format(field.name))

for field in fields_to_many:
document["links"][field.name] = list(
Expand All @@ -114,3 +112,61 @@ def dump_document(cls, model_instance, fields_own=None, fields_to_one=None,
)

return document

@classmethod
def dump_documents(cls, resource, model_instances, fields_own=None,
fields_to_one=None, fields_to_many=None):
data = {
resource.Meta.name_plural: [
cls.dump_document(
m,
fields_own=fields_own,
fields_to_one=fields_to_one,
fields_to_many=fields_to_many
)
for m in model_instances
]
}

model_info = resource.Meta.api.model_inspector.models[
resource.Meta.model]

if model_info.fields_to_one or fields_to_many:
data["links"] = {}
for field in model_info.fields_to_one:
linkname = "{}.{}".format(resource.Meta.name_plural, field.name)
data["links"].update({
linkname: resource.Meta.api.api_url + "/" + field.name +
"/{" + linkname + "}"
})

fields_to_one = fields_to_one or []
fields_to_many = fields_to_many or []

if fields_to_one or fields_to_many:
data["linked"] = {}

for field in fields_to_one:
related_model_info = resource.Meta.api.model_inspector.models[
field.related_model]
related_resource = cls.Meta.api.resource_map[related_model_info.name]
data["linked"][related_resource.Meta.name_plural] = [
related_resource.dump_document(
getattr(m, field.name),
related_model_info.fields_own
) for m in model_instances
]

for field in fields_to_many:
related_model_info = resource.Meta.api.model_inspector.models[
field.related_model]
related_resource = cls.Meta.api.resource_map[related_model_info.name]
data["linked"][related_resource.Meta.name_plural] = [
related_resource.dump_document(
x,
related_model_info.fields_own
) for m in model_instances
for x in getattr(getattr(m, field.name), "all").__call__()
]

return data
102 changes: 96 additions & 6 deletions tests/testapp/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ def test_content_type_validation(self):
response = self.client.get('/api', content_type='application/json')
self.assertEqual(response.status_code, 415)
self.assertEqual(
str(response.content), "Content-Type SHOULD be application/vnd.api+json")
str(response.content),
"Content-Type SHOULD be application/vnd.api+json")

@unittest.skipIf(django.VERSION[:2] == (1, 5),
"FIXME: For some reason does not work. Tested manually")
Expand All @@ -152,6 +153,28 @@ def test_api_url(self):


class TestApiClient(TestCase):
def test_resource_get_empty(self):
response = self.client.get(
'/api/author',
content_type='application/vnd.api+json'
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.content.decode('utf8'))
data_expected = {
"authors": []
}
self.assertEqual(data, data_expected)

def test_resource_own_fields_serialization(self):
mixer.blend('testapp.author')
response = self.client.get(
'/api/author',
content_type='application/vnd.api+json'
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.content.decode('utf8'))
self.assertEqual(len(data["authors"]), 1)

def test_create_model(self):
self.assertEqual(Author.objects.count(), 0)
# NOTE: send individual resource
Expand All @@ -177,7 +200,7 @@ def test_create_model(self):
}
self.assertEqual(response.status_code, 201)
self.assertEqual(author.name, "author")
data = json.loads(response.content)
data = json.loads(response.content.decode("utf-8"))
self.assertEqual(data, expected_data)
self.assertTrue(response.has_header("Location"))

Expand Down Expand Up @@ -214,7 +237,7 @@ def test_create_models(self):
} for author in authors]
}

data = json.loads(response.content)
data = json.loads(response.content.decode("utf-8"))
self.assertEqual(data, expected_data)
self.assertTrue(response.has_header("Location"))

Expand Down Expand Up @@ -258,7 +281,7 @@ def test_update_models(self):
self.assertEqual(author.name, "author")

def test_update_model_missing_ids(self):
author = mixer.blend("testapp.author")
mixer.blend("testapp.author")
response = self.client.put(
'/api/author',
{
Expand All @@ -270,7 +293,9 @@ def test_update_model_missing_ids(self):
HTTP_ACCEPT='application/vnd.api+json'
)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.content, "Request SHOULD have resource ids")
self.assertEqual(
response.content.decode("utf-8"),
"Request SHOULD have resource ids")

def test_delete_model(self):
author = mixer.blend("testapp.author")
Expand Down Expand Up @@ -299,4 +324,69 @@ def test_delete_model_missing_ids(self):
HTTP_ACCEPT='application/vnd.api+json'
)
self.assertEqual(response.status_code, 400)
self.assertEqual(response.content, "Request SHOULD have resource ids")
self.assertEqual(
response.content.decode("utf-8"),
"Request SHOULD have resource ids")

def test_get_top_level_links(self):
post = mixer.blend("testapp.post")
response = self.client.get(
'/api/post',
content_type='application/vnd.api+json',
HTTP_ACCEPT='application/vnd.api+json'
)
self.assertEqual(response.status_code, 200)
expected_data = {
"posts": [{
"id": post.id,
"title": post.title,
"links": {
"user": post.user_id,
"author": post.author_id,
}
}],
"links": {
"posts.author": "http://testserver/api/author/{posts.author}",
"posts.user": "http://testserver/api/user/{posts.user}",
}
}

self.maxDiff = None
data = json.loads(response.content.decode("utf-8"))
self.assertEqual(data, expected_data)

def test_get_include(self):
post = mixer.blend("testapp.post")
response = self.client.get(
'/api/post?include=author',
content_type='application/vnd.api+json',
HTTP_ACCEPT='application/vnd.api+json'
)
data = json.loads(response.content.decode("utf-8"))["linked"]
expected_data = {
"authors": [{
"id": post.author_id,
"name": post.author.name
}]
}
self.assertEqual(data, expected_data)

def test_get_include_many(self):
comment = mixer.blend("testapp.comment")
post = comment.post
response = self.client.get(
'/api/post?include=comments',
content_type='application/vnd.api+json',
HTTP_ACCEPT='application/vnd.api+json'
)
data = json.loads(response.content.decode("utf-8"))["linked"]
expected_data = {
"comments": [{
"id": comment.id,
"links": {
"author": comment.author_id,
"post": comment.post_id,
},
}]
}
self.assertEqual(data, expected_data)
52 changes: 0 additions & 52 deletions tests/testapp/tests/test_resource.py

This file was deleted.

0 comments on commit ca6ac75

Please sign in to comment.