Skip to content

Parsers, Relation Fields and more #97

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

Merged
merged 31 commits into from
Sep 22, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
96a6484
Skip read_only attribute fields when the resource is non-existent
jsenecal Sep 16, 2015
1aa6d39
Created JSONAPIRelatedField
jsenecal Sep 17, 2015
f9c4fdf
Created JSONAPIModelSerializer
jsenecal Sep 17, 2015
8caebbb
Renamed the classes so that they can replace their original equivalen…
jsenecal Sep 17, 2015
ea6c6bc
Thou shalt return something
jsenecal Sep 17, 2015
64ce376
Wrong exception class in except
jsenecal Sep 17, 2015
2202e26
Proper error messages
jsenecal Sep 17, 2015
5df8ee3
ResourceRelatedField & tests
Sep 17, 2015
d47d0af
Merge pull request #95 from ZEROFAIL/feature/parsers
jsenecal Sep 17, 2015
0fc8bb0
Merge branch 'develop' of github.com:django-json-api/django-rest-fram…
jsenecal Sep 17, 2015
b74c4e0
Getting rid of get_related_resource_type for relations.ResourceRelate…
jsenecal Sep 17, 2015
1fa74e7
Created a generic _relationship_ endpoint view
jsenecal Sep 18, 2015
a1f5095
Merge branch 'develop' of github.com:django-json-api/django-rest-fram…
Sep 18, 2015
ba985ef
Merge stashed changes
Sep 18, 2015
16d4fa5
Working on relationships views
jsenecal Sep 20, 2015
7979db0
WIP RelationshipView
Sep 21, 2015
dc64c20
Fixed issue with empty "ToOne" relationship being returned as empty l…
jsenecal Sep 21, 2015
081d3a2
Handling RelationshipView
jsenecal Sep 21, 2015
610469d
Better handling of various HTTP methods for RelationshipView
jsenecal Sep 21, 2015
854a8e0
More tests for RelationshipView
jsenecal Sep 21, 2015
20c7fb5
Merge branch 'develop' of github.com:django-json-api/django-rest-fram…
jsenecal Sep 22, 2015
d3e4243
Updated error message
jsenecal Sep 22, 2015
0d6230f
WIP support for links
jsenecal Sep 22, 2015
502cc43
Handle RelationshipViews
jsenecal Sep 22, 2015
ea65003
Fixed missed empty test
jsenecal Sep 22, 2015
2797b9d
Now supporting links for RelationshipView
jsenecal Sep 22, 2015
5f0c2b4
Compat fix for Django 1.6
jsenecal Sep 22, 2015
212c44a
Compat fix for Django 1.6
jsenecal Sep 22, 2015
6c5413c
Compat fix for DRF 3.1
jsenecal Sep 22, 2015
bc63e9f
Use six from Django instead
jsenecal Sep 22, 2015
066de0f
Rebased from develop
jsenecal Sep 22, 2015
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
13 changes: 13 additions & 0 deletions example/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,16 @@ class Entry(BaseModel):
def __str__(self):
return self.headline


@python_2_unicode_compatible
class Comment(BaseModel):
entry = models.ForeignKey(Entry)
body = models.TextField()
author = models.ForeignKey(
Author,
null=True,
blank=True
)

def __str__(self):
return self.body
131 changes: 131 additions & 0 deletions example/tests/test_relations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from __future__ import absolute_import

from django.utils import timezone

from rest_framework import serializers

from . import TestBase
from rest_framework_json_api.utils import format_relation_name
from example.models import Blog, Entry, Comment, Author
from rest_framework_json_api.relations import ResourceRelatedField


class TestResourceRelatedField(TestBase):

def setUp(self):
super(TestResourceRelatedField, self).setUp()
self.blog = Blog.objects.create(name='Some Blog', tagline="It's a blog")
self.entry = Entry.objects.create(
blog=self.blog,
headline='headline',
body_text='body_text',
pub_date=timezone.now(),
mod_date=timezone.now(),
n_comments=0,
n_pingbacks=0,
rating=3
)
for i in range(1,6):
name = 'some_author{}'.format(i)
self.entry.authors.add(
Author.objects.create(name=name, email='{}@example.org'.format(name))
)

self.comment = Comment.objects.create(
entry=self.entry,
body='testing one two three',
author=Author.objects.first()
)

def test_data_in_correct_format_when_instantiated_with_blog_object(self):
serializer = BlogFKSerializer(instance={'blog': self.blog})

expected_data = {
'type': format_relation_name('Blog'),
'id': str(self.blog.id)
}

actual_data = serializer.data['blog']

self.assertEqual(actual_data, expected_data)

def test_data_in_correct_format_when_instantiated_with_entry_object(self):
serializer = EntryFKSerializer(instance={'entry': self.entry})

expected_data = {
'type': format_relation_name('Entry'),
'id': str(self.entry.id)
}

actual_data = serializer.data['entry']

self.assertEqual(actual_data, expected_data)

def test_deserialize_primitive_data_blog(self):
serializer = BlogFKSerializer(data={
'blog': {
'type': format_relation_name('Blog'),
'id': str(self.blog.id)
}
}
)

self.assertTrue(serializer.is_valid())
self.assertEqual(serializer.validated_data['blog'], self.blog)

def test_validation_fails_for_wrong_type(self):
serializer = BlogFKSerializer(data={
'blog': {
'type': 'Entries',
'id': str(self.blog.id)
}
}
)

self.assertFalse(serializer.is_valid())

def test_serialize_many_to_many_relation(self):
serializer = EntryModelSerializer(instance=self.entry)

type_string = format_relation_name('Author')
author_pks = Author.objects.values_list('pk', flat=True)
expected_data = [{'type': type_string, 'id': str(pk)} for pk in author_pks]

self.assertEqual(
serializer.data['authors'],
expected_data
)

def test_deserialize_many_to_many_relation(self):
type_string = format_relation_name('Author')
author_pks = Author.objects.values_list('pk', flat=True)
authors = [{'type': type_string, 'id': pk} for pk in author_pks]

serializer = EntryModelSerializer(data={'authors': authors, 'comment_set': []})

self.assertTrue(serializer.is_valid())
self.assertEqual(len(serializer.validated_data['authors']), Author.objects.count())
for author in serializer.validated_data['authors']:
self.assertIsInstance(author, Author)

def test_read_only(self):
serializer = EntryModelSerializer(data={'authors': [], 'comment_set': [{'type': 'Comments', 'id': 2}]})
serializer.is_valid(raise_exception=True)
self.assertNotIn('comment_set', serializer.validated_data)


class BlogFKSerializer(serializers.Serializer):
blog = ResourceRelatedField(queryset=Blog.objects)


class EntryFKSerializer(serializers.Serializer):
entry = ResourceRelatedField(queryset=Entry.objects)


class EntryModelSerializer(serializers.ModelSerializer):
authors = ResourceRelatedField(many=True, queryset=Author.objects)
comment_set = ResourceRelatedField(many=True, read_only=True)

class Meta:
model = Entry
fields = ('authors', 'comment_set')
73 changes: 73 additions & 0 deletions example/tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from django.test import TestCase
from django.utils import timezone

from rest_framework_json_api.utils import format_relation_name
from rest_framework_json_api.serializers import ResourceIdentifierObjectSerializer

from example.models import Blog, Entry, Author


class TestResourceIdentifierObjectSerializer(TestCase):
def setUp(self):
self.blog = Blog.objects.create(name='Some Blog', tagline="It's a blog")
self.entry = Entry.objects.create(
blog=self.blog,
headline='headline',
body_text='body_text',
pub_date=timezone.now(),
mod_date=timezone.now(),
n_comments=0,
n_pingbacks=0,
rating=3
)
for i in range(1,6):
name = 'some_author{}'.format(i)
self.entry.authors.add(
Author.objects.create(name=name, email='{}@example.org'.format(name))
)

def test_data_in_correct_format_when_instantiated_with_blog_object(self):
serializer = ResourceIdentifierObjectSerializer(instance=self.blog)

expected_data = {'type': format_relation_name('Blog'), 'id': str(self.blog.id)}

assert serializer.data == expected_data

def test_data_in_correct_format_when_instantiated_with_entry_object(self):
serializer = ResourceIdentifierObjectSerializer(instance=self.entry)

expected_data = {'type': format_relation_name('Entry'), 'id': str(self.entry.id)}

assert serializer.data == expected_data

def test_deserialize_primitive_data_blog(self):
initial_data = {
'type': format_relation_name('Blog'),
'id': str(self.blog.id)
}
serializer = ResourceIdentifierObjectSerializer(data=initial_data, model_class=Blog)

self.assertTrue(serializer.is_valid(), msg=serializer.errors)
assert serializer.validated_data == self.blog

def test_data_in_correct_format_when_instantiated_with_queryset(self):
qs = Author.objects.all()
serializer = ResourceIdentifierObjectSerializer(instance=qs, many=True)

type_string = format_relation_name('Author')
author_pks = Author.objects.values_list('pk', flat=True)
expected_data = [{'type': type_string, 'id': str(pk)} for pk in author_pks]

assert serializer.data == expected_data

def test_deserialize_many(self):
type_string = format_relation_name('Author')
author_pks = Author.objects.values_list('pk', flat=True)
initial_data = [{'type': type_string, 'id': str(pk)} for pk in author_pks]

serializer = ResourceIdentifierObjectSerializer(data=initial_data, model_class=Author, many=True)

self.assertTrue(serializer.is_valid(), msg=serializer.errors)

print(serializer.data)

170 changes: 170 additions & 0 deletions example/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import json

from django.utils import timezone
from rest_framework.reverse import reverse

from rest_framework.test import APITestCase

from rest_framework_json_api.utils import format_relation_name
from example.models import Blog, Entry, Comment, Author


class TestRelationshipView(APITestCase):
def setUp(self):
self.author = Author.objects.create(name='Super powerful superhero', email='[email protected]')
self.blog = Blog.objects.create(name='Some Blog', tagline="It's a blog")
self.other_blog = Blog.objects.create(name='Other blog', tagline="It's another blog")
self.first_entry = Entry.objects.create(
blog=self.blog,
headline='headline one',
body_text='body_text two',
pub_date=timezone.now(),
mod_date=timezone.now(),
n_comments=0,
n_pingbacks=0,
rating=3
)
self.second_entry = Entry.objects.create(
blog=self.blog,
headline='headline two',
body_text='body_text one',
pub_date=timezone.now(),
mod_date=timezone.now(),
n_comments=0,
n_pingbacks=0,
rating=1
)
self.first_comment = Comment.objects.create(entry=self.first_entry, body="This entry is cool", author=None)
self.second_comment = Comment.objects.create(
entry=self.second_entry,
body="This entry is not cool",
author=self.author
)

def test_get_entry_relationship_blog(self):
url = reverse('entry-relationships', kwargs={'pk': self.first_entry.id, 'related_field': 'blog'})
response = self.client.get(url)
expected_data = {'type': format_relation_name('Blog'), 'id': str(self.first_entry.blog.id)}

assert response.data == expected_data

def test_get_entry_relationship_invalid_field(self):
response = self.client.get('/entries/{}/relationships/invalid_field'.format(self.first_entry.id))

assert response.status_code == 404

def test_get_blog_relationship_entry_set(self):
response = self.client.get('/blogs/{}/relationships/entry_set'.format(self.blog.id))
expected_data = [{'type': format_relation_name('Entry'), 'id': str(self.first_entry.id)},
{'type': format_relation_name('Entry'), 'id': str(self.second_entry.id)}]

assert response.data == expected_data

def test_put_entry_relationship_blog_returns_405(self):
url = '/entries/{}/relationships/blog'.format(self.first_entry.id)
response = self.client.put(url, data={})
assert response.status_code == 405

def test_patch_invalid_entry_relationship_blog_returns_400(self):
url = '/entries/{}/relationships/blog'.format(self.first_entry.id)
response = self.client.patch(url,
data=json.dumps({'data': {'invalid': ''}}),
content_type='application/vnd.api+json')
assert response.status_code == 400

def test_get_empty_to_one_relationship(self):
url = '/comments/{}/relationships/author'.format(self.first_entry.id)
response = self.client.get(url)
expected_data = None

assert response.data == expected_data

def test_get_to_many_relationship_self_link(self):
url = '/authors/{}/relationships/comment_set'.format(self.author.id)

response = self.client.get(url)
expected_data = {
'links': {'self': 'http://testserver/authors/1/relationships/comment_set'},
'data': [{'id': str(self.second_comment.id), 'type': format_relation_name('Comment')}]
}
assert json.loads(response.content.decode('utf-8')) == expected_data

def test_patch_to_one_relationship(self):
url = '/entries/{}/relationships/blog'.format(self.first_entry.id)
request_data = {
'data': {'type': format_relation_name('Blog'), 'id': str(self.other_blog.id)}
}
response = self.client.patch(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
assert response.status_code == 200, response.content.decode()

response = self.client.get(url)
assert response.data == request_data['data']

def test_patch_to_many_relationship(self):
url = '/blogs/{}/relationships/entry_set'.format(self.first_entry.id)
request_data = {
'data': [{'type': format_relation_name('Entry'), 'id': str(self.first_entry.id)}, ]
}
response = self.client.patch(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
assert response.status_code == 200, response.content.decode()

response = self.client.get(url)
assert response.data == request_data['data']

def test_post_to_one_relationship_should_fail(self):
url = '/entries/{}/relationships/blog'.format(self.first_entry.id)
request_data = {
'data': {'type': format_relation_name('Blog'), 'id': str(self.other_blog.id)}
}
response = self.client.post(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
assert response.status_code == 405, response.content.decode()

def test_post_to_many_relationship_with_no_change(self):
url = '/entries/{}/relationships/comment_set'.format(self.first_entry.id)
request_data = {
'data': [{'type': format_relation_name('Comment'), 'id': str(self.first_comment.id)}, ]
}
response = self.client.post(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
assert response.status_code == 204, response.content.decode()

def test_post_to_many_relationship_with_change(self):
url = '/entries/{}/relationships/comment_set'.format(self.first_entry.id)
request_data = {
'data': [{'type': format_relation_name('Comment'), 'id': str(self.second_comment.id)}, ]
}
response = self.client.post(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
assert response.status_code == 200, response.content.decode()

assert request_data['data'][0] in response.data

def test_delete_to_one_relationship_should_fail(self):
url = '/entries/{}/relationships/blog'.format(self.first_entry.id)
request_data = {
'data': {'type': format_relation_name('Blog'), 'id': str(self.other_blog.id)}
}
response = self.client.delete(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
assert response.status_code == 405, response.content.decode()

def test_delete_to_many_relationship_with_no_change(self):
url = '/entries/{}/relationships/comment_set'.format(self.first_entry.id)
request_data = {
'data': [{'type': format_relation_name('Comment'), 'id': str(self.second_comment.id)}, ]
}
response = self.client.delete(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
assert response.status_code == 204, response.content.decode()

def test_delete_one_to_many_relationship_with_not_null_constraint(self):
url = '/entries/{}/relationships/comment_set'.format(self.first_entry.id)
request_data = {
'data': [{'type': format_relation_name('Comment'), 'id': str(self.first_comment.id)}, ]
}
response = self.client.delete(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
assert response.status_code == 409, response.content.decode()

def test_delete_to_many_relationship_with_change(self):
url = '/authors/{}/relationships/comment_set'.format(self.author.id)
request_data = {
'data': [{'type': format_relation_name('Comment'), 'id': str(self.second_comment.id)}, ]
}
response = self.client.delete(url, data=json.dumps(request_data), content_type='application/vnd.api+json')
assert response.status_code == 200, response.content.decode()
Loading