Skip to content

Commit 55e8def

Browse files
santiavenda2sliverc
authored andcommitted
Avoid exception in AutoPrefetchMixin when including a reverse one to one relation (#536)
1 parent 11e0edd commit 55e8def

File tree

9 files changed

+104
-8
lines changed

9 files changed

+104
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ any parts of the framework not mentioned in the documentation should generally b
2929
* Do not skip empty one-to-one relationships
3030
* Allow `HyperlinkRelatedField` to be used with [related urls](https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html?highlight=related%20links#related-urls)
3131
* Fixed hardcoded year 2018 in tests ([#539](https://github.com/django-json-api/django-rest-framework-json-api/issues/539))
32+
* Avoid exception in `AutoPrefetchMixin` when including a reverse one to one relation ([#537](https://github.com/django-json-api/django-rest-framework-json-api/issues/537))
3233

3334
## [2.6.0] - 2018-09-20
3435

example/factories.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
Entry,
1515
ProjectType,
1616
ResearchProject,
17-
TaggedItem
18-
)
17+
TaggedItem,
18+
AuthorBioMetadata)
1919

2020
faker = FakerFactory.create()
2121
faker.seed(983843)
@@ -53,6 +53,16 @@ class Meta:
5353
author = factory.SubFactory(AuthorFactory)
5454
body = factory.LazyAttribute(lambda x: faker.text())
5555

56+
metadata = factory.RelatedFactory('example.factories.AuthorBioMetadataFactory', 'bio')
57+
58+
59+
class AuthorBioMetadataFactory(factory.django.DjangoModelFactory):
60+
class Meta:
61+
model = AuthorBioMetadata
62+
63+
bio = factory.SubFactory(AuthorBioFactory)
64+
body = factory.LazyAttribute(lambda x: faker.text())
65+
5666

5767
class EntryFactory(factory.django.DjangoModelFactory):
5868
class Meta:
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Generated by Django 2.1.4 on 2018-12-28 07:52
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('example', '0005_auto_20180922_1508'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='AuthorBioMetadata',
16+
fields=[
17+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18+
('created_at', models.DateTimeField(auto_now_add=True)),
19+
('modified_at', models.DateTimeField(auto_now=True)),
20+
('body', models.TextField()),
21+
('bio', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='metadata', to='example.AuthorBio')),
22+
],
23+
options={
24+
'ordering': ('id',),
25+
},
26+
),
27+
migrations.AlterField(
28+
model_name='comment',
29+
name='author',
30+
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='example.Author'),
31+
),
32+
]

example/models.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,21 @@ class Meta:
8181
ordering = ('id',)
8282

8383

84+
@python_2_unicode_compatible
85+
class AuthorBioMetadata(BaseModel):
86+
"""
87+
Just a class to have a relation with author bio
88+
"""
89+
bio = models.OneToOneField(AuthorBio, related_name='metadata', on_delete=models.CASCADE)
90+
body = models.TextField()
91+
92+
def __str__(self):
93+
return self.bio.author.name
94+
95+
class Meta:
96+
ordering = ('id',)
97+
98+
8499
@python_2_unicode_compatible
85100
class Entry(BaseModel):
86101
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)

example/serializers.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
Project,
1717
ProjectType,
1818
ResearchProject,
19-
TaggedItem
20-
)
19+
TaggedItem,
20+
AuthorBioMetadata)
2121

2222

2323
class TaggedItemSerializer(serializers.ModelSerializer):
@@ -197,7 +197,17 @@ class Meta:
197197
class AuthorBioSerializer(serializers.ModelSerializer):
198198
class Meta:
199199
model = AuthorBio
200-
fields = ('author', 'body')
200+
fields = ('author', 'body', 'metadata')
201+
202+
included_serializers = {
203+
'metadata': 'example.serializers.AuthorBioMetadataSerializer',
204+
}
205+
206+
207+
class AuthorBioMetadataSerializer(serializers.ModelSerializer):
208+
class Meta:
209+
model = AuthorBioMetadata
210+
fields = ('body',)
201211

202212

203213
class AuthorSerializer(serializers.ModelSerializer):

example/tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
ArtProjectFactory,
77
AuthorBioFactory,
88
AuthorFactory,
9+
AuthorBioMetadataFactory,
910
AuthorTypeFactory,
1011
BlogFactory,
1112
CommentFactory,
@@ -18,6 +19,7 @@
1819
register(BlogFactory)
1920
register(AuthorFactory)
2021
register(AuthorBioFactory)
22+
register(AuthorBioMetadataFactory)
2123
register(AuthorTypeFactory)
2224
register(EntryFactory)
2325
register(CommentFactory)

example/tests/integration/test_includes.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,25 @@ def test_included_data_on_list(multiple_entries, client):
2020
assert comment_count == expected_comment_count, 'List comment count is incorrect'
2121

2222

23+
def test_included_data_on_list_with_one_to_one_relations(multiple_entries, client):
24+
response = client.get(reverse("entry-list"),
25+
data={'include': 'authors.bio.metadata', 'page[size]': 5})
26+
included = response.json().get('included')
27+
28+
assert len(response.json()['data']) == len(multiple_entries), (
29+
'Incorrect entry count'
30+
)
31+
expected_include_types = [
32+
'authorBioMetadata', 'authorBioMetadata',
33+
'authorBios', 'authorBios',
34+
'authors', 'authors'
35+
]
36+
include_types = [x.get('type') for x in included]
37+
assert include_types == expected_include_types, (
38+
'List included types are incorrect'
39+
)
40+
41+
2342
def test_default_included_data_on_detail(single_entry, client):
2443
return test_included_data_on_detail(single_entry=single_entry, client=client, query='')
2544

example/tests/test_views.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
2-
32
from datetime import datetime
3+
44
from django.test import RequestFactory
55
from django.utils import timezone
66
from rest_framework.exceptions import NotFound
@@ -337,7 +337,10 @@ def test_retrieve_related_single_reverse_lookup(self):
337337
'data': {
338338
'type': 'authorBios', 'id': str(self.author.bio.id),
339339
'relationships': {
340-
'author': {'data': {'type': 'authors', 'id': str(self.author.id)}}},
340+
'author': {'data': {'type': 'authors', 'id': str(self.author.id)}},
341+
'metadata': {'data': {'id': str(self.author.bio.metadata.id),
342+
'type': 'authorBioMetadata'}}
343+
},
341344
'attributes': {
342345
'body': str(self.author.bio.body)
343346
},

rest_framework_json_api/views.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ def get_queryset(self, *args, **kwargs):
9292
if level == levels[-1]:
9393
included_model = field
9494
else:
95-
model_field = field.field
95+
96+
if issubclass(field_class, ReverseOneToOneDescriptor):
97+
model_field = field.related.field
98+
else:
99+
model_field = field.field
96100

97101
if is_forward_relation:
98102
level_model = model_field.related_model

0 commit comments

Comments
 (0)