Skip to content

Commit d47d0af

Browse files
committed
Merge pull request #95 from ZEROFAIL/feature/parsers
ResourceRelatedField & tests
2 parents 2202e26 + 5df8ee3 commit d47d0af

File tree

4 files changed

+168
-0
lines changed

4 files changed

+168
-0
lines changed

example/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,13 @@ class Entry(BaseModel):
4848

4949
def __str__(self):
5050
return self.headline
51+
52+
53+
@python_2_unicode_compatible
54+
class Comment(BaseModel):
55+
entry = models.ForeignKey(Entry)
56+
body = models.TextField()
57+
author = models.ForeignKey(Author)
58+
59+
def __str__(self):
60+
return self.body

example/tests/test_relations.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
from __future__ import absolute_import
2+
3+
from django.utils import timezone
4+
5+
from rest_framework import serializers
6+
7+
from . import TestBase
8+
from rest_framework_json_api.utils import format_relation_name
9+
from example.models import Blog, Entry, Comment, Author
10+
from rest_framework_json_api.relations import ResourceRelatedField
11+
12+
13+
class TestResourceRelatedField(TestBase):
14+
15+
def setUp(self):
16+
super(TestResourceRelatedField, self).setUp()
17+
self.blog = Blog.objects.create(name='Some Blog', tagline="It's a blog")
18+
self.entry = Entry.objects.create(
19+
blog=self.blog,
20+
headline='headline',
21+
body_text='body_text',
22+
pub_date=timezone.now(),
23+
mod_date=timezone.now(),
24+
n_comments=0,
25+
n_pingbacks=0,
26+
rating=3
27+
)
28+
for i in range(1,6):
29+
name = 'some_author{}'.format(i)
30+
self.entry.authors.add(
31+
Author.objects.create(name=name, email='{}@example.org'.format(name))
32+
)
33+
34+
self.comment = Comment.objects.create(
35+
entry=self.entry,
36+
body='testing one two three',
37+
author=Author.objects.first()
38+
)
39+
40+
def test_data_in_correct_format_when_instantiated_with_blog_object(self):
41+
serializer = BlogFKSerializer(instance={'blog': self.blog})
42+
43+
expected_data = {
44+
'type': format_relation_name('Blog'),
45+
'id': str(self.blog.id)
46+
}
47+
48+
actual_data = serializer.data['blog']
49+
50+
self.assertEqual(actual_data, expected_data)
51+
52+
def test_data_in_correct_format_when_instantiated_with_entry_object(self):
53+
serializer = EntryFKSerializer(instance={'entry': self.entry})
54+
55+
expected_data = {
56+
'type': format_relation_name('Entry'),
57+
'id': str(self.entry.id)
58+
}
59+
60+
actual_data = serializer.data['entry']
61+
62+
self.assertEqual(actual_data, expected_data)
63+
64+
def test_deserialize_primitive_data_blog(self):
65+
serializer = BlogFKSerializer(data={
66+
'blog': {
67+
'type': format_relation_name('Blog'),
68+
'id': str(self.blog.id)
69+
}
70+
}
71+
)
72+
73+
self.assertTrue(serializer.is_valid())
74+
self.assertEqual(serializer.validated_data['blog'], self.blog)
75+
76+
def test_validation_fails_for_wrong_type(self):
77+
serializer = BlogFKSerializer(data={
78+
'blog': {
79+
'type': 'Entries',
80+
'id': str(self.blog.id)
81+
}
82+
}
83+
)
84+
85+
self.assertFalse(serializer.is_valid())
86+
87+
def test_serialize_many_to_many_relation(self):
88+
serializer = EntryModelSerializer(instance=self.entry)
89+
90+
type_string = format_relation_name('Author')
91+
author_pks = Author.objects.values_list('pk', flat=True)
92+
expected_data = [{'type': type_string, 'id': str(pk)} for pk in author_pks]
93+
94+
self.assertEqual(
95+
serializer.data['authors'],
96+
expected_data
97+
)
98+
99+
def test_deserialize_many_to_many_relation(self):
100+
type_string = format_relation_name('Author')
101+
author_pks = Author.objects.values_list('pk', flat=True)
102+
authors = [{'type': type_string, 'id': pk} for pk in author_pks]
103+
104+
serializer = EntryModelSerializer(data={'authors': authors, 'comment_set': []})
105+
106+
self.assertTrue(serializer.is_valid())
107+
self.assertEqual(len(serializer.validated_data['authors']), Author.objects.count())
108+
for author in serializer.validated_data['authors']:
109+
self.assertIsInstance(author, Author)
110+
111+
def test_read_only(self):
112+
serializer = EntryModelSerializer(data={'authors': [], 'comment_set': [{'type': 'Comments', 'id': 2}]})
113+
serializer.is_valid(raise_exception=True)
114+
self.assertNotIn('comment_set', serializer.validated_data)
115+
116+
117+
class BlogFKSerializer(serializers.Serializer):
118+
blog = ResourceRelatedField(queryset=Blog.objects)
119+
120+
121+
class EntryFKSerializer(serializers.Serializer):
122+
entry = ResourceRelatedField(queryset=Entry.objects)
123+
124+
125+
class EntryModelSerializer(serializers.ModelSerializer):
126+
authors = ResourceRelatedField(many=True, queryset=Author.objects)
127+
comment_set = ResourceRelatedField(many=True, read_only=True)
128+
129+
class Meta:
130+
model = Entry
131+
fields = ('authors', 'comment_set')

rest_framework_json_api/relations.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from rest_framework.exceptions import ValidationError
22
from rest_framework.relations import *
3+
from rest_framework_json_api.utils import format_relation_name, get_related_resource_type
34
from django.utils.translation import ugettext_lazy as _
45

56

@@ -35,3 +36,25 @@ def to_internal_value(self, data):
3536
self.fail('pk_does_not_exist', pk_value=data)
3637
except (TypeError, ValueError):
3738
self.fail('incorrect_pk_type', data_type=type(data).__name__)
39+
40+
41+
class ResourceRelatedField(PrimaryKeyRelatedField):
42+
default_error_messages = {
43+
'required': _('This field is required.'),
44+
'does_not_exist': _('Invalid pk "{pk_value}" - object does not exist.'),
45+
'incorrect_type': _('Incorrect type. Expected pk value, received {data_type}.'),
46+
'incorrect_relation_type': _('Incorrect relation type. Expected {relation_type}, received {received_type}.'),
47+
}
48+
49+
def to_internal_value(self, data):
50+
expected_relation_type = format_relation_name(get_related_resource_type(self))
51+
if data['type'] != expected_relation_type:
52+
self.fail('incorrect_relation_type', relation_type=expected_relation_type, received_type=data['type'])
53+
return super(ResourceRelatedField, self).to_internal_value(data['id'])
54+
55+
def to_representation(self, value):
56+
return {
57+
'type': format_relation_name(get_related_resource_type(self)),
58+
'id': str(value.pk)
59+
}
60+

rest_framework_json_api/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ def get_related_resource_type(relation):
179179
return format_relation_name(relation_model.__name__)
180180

181181

182+
def get_model_name_from_queryset(qs):
183+
return qs.model._meta.model_name
184+
185+
182186
def extract_attributes(fields, resource):
183187
data = OrderedDict()
184188
for field_name, field in six.iteritems(fields):

0 commit comments

Comments
 (0)