Skip to content

Allow type field on none polymorphic serializers #376

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 1 commit into from
Nov 28, 2017
Merged
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
9 changes: 9 additions & 0 deletions example/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
ArtProject,
Author,
AuthorBio,
AuthorType,
Blog,
Comment,
Company,
Expand All @@ -26,6 +27,13 @@ class Meta:
name = factory.LazyAttribute(lambda x: faker.name())


class AuthorTypeFactory(factory.django.DjangoModelFactory):
class Meta:
model = AuthorType

name = factory.LazyAttribute(lambda x: faker.name())


class AuthorFactory(factory.django.DjangoModelFactory):
class Meta:
model = Author
Expand All @@ -34,6 +42,7 @@ class Meta:
email = factory.LazyAttribute(lambda x: faker.email())

bio = factory.RelatedFactory('example.factories.AuthorBioFactory', 'author')
type = factory.SubFactory(AuthorTypeFactory)


class AuthorBioFactory(factory.django.DjangoModelFactory):
Expand Down
62 changes: 62 additions & 0 deletions example/migrations/0004_auto_20171011_0631.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-10-11 06:31
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('example', '0003_polymorphics'),
]

operations = [
migrations.CreateModel(
name='AuthorType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=50)),
],
options={
'ordering': ('id',),
},
),
migrations.AlterModelOptions(
name='author',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='authorbio',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='blog',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='comment',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='entry',
options={'ordering': ('id',)},
),
migrations.AlterModelOptions(
name='taggeditem',
options={'ordering': ('id',)},
),
migrations.AlterField(
model_name='entry',
name='authors',
field=models.ManyToManyField(related_name='entries', to='example.Author'),
),
migrations.AddField(
model_name='author',
name='type',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='example.AuthorType'),
),
]
12 changes: 12 additions & 0 deletions example/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,22 @@ class Meta:
ordering = ('id',)


@python_2_unicode_compatible
class AuthorType(BaseModel):
name = models.CharField(max_length=50)

def __str__(self):
return self.name

class Meta:
ordering = ('id',)


@python_2_unicode_compatible
class Author(BaseModel):
name = models.CharField(max_length=50)
email = models.EmailField()
type = models.ForeignKey(AuthorType, null=True)

def __str__(self):
return self.name
Expand Down
12 changes: 10 additions & 2 deletions example/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
ArtProject,
Author,
AuthorBio,
AuthorType,
Blog,
Comment,
Company,
Expand Down Expand Up @@ -101,6 +102,12 @@ class JSONAPIMeta:
included_resources = ['comments']


class AuthorTypeSerializer(serializers.ModelSerializer):
class Meta:
model = AuthorType
fields = ('name', )


class AuthorBioSerializer(serializers.ModelSerializer):
class Meta:
model = AuthorBio
Expand All @@ -109,12 +116,13 @@ class Meta:

class AuthorSerializer(serializers.ModelSerializer):
included_serializers = {
'bio': AuthorBioSerializer
'bio': AuthorBioSerializer,
'type': AuthorTypeSerializer
}

class Meta:
model = Author
fields = ('name', 'email', 'bio', 'entries')
fields = ('name', 'email', 'bio', 'entries', 'type')


class WriterSerializer(serializers.ModelSerializer):
Expand Down
2 changes: 2 additions & 0 deletions example/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
ArtProjectFactory,
AuthorBioFactory,
AuthorFactory,
AuthorTypeFactory,
BlogFactory,
CommentFactory,
CompanyFactory,
Expand All @@ -16,6 +17,7 @@
register(BlogFactory)
register(AuthorFactory)
register(AuthorBioFactory)
register(AuthorTypeFactory)
register(EntryFactory)
register(CommentFactory)
register(TaggedItemFactory)
Expand Down
29 changes: 29 additions & 0 deletions example/tests/test_model_viewsets.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
Expand Down Expand Up @@ -226,3 +227,31 @@ def test_key_in_post(self):
self.assertEqual(
get_user_model().objects.get(pk=self.miles.pk).email,
'miles@trumpet.org')


@pytest.mark.django_db
def test_patch_allow_field_type(author, author_type_factory, client):
"""
Verify that type field may be updated.
"""
author_type = author_type_factory()
url = reverse('author-detail', args=[author.id])

data = {
'data': {
'id': author.id,
'type': 'authors',
'relationships': {
'data': {
'id': author_type.id,
'type': 'author-type'
}
}
}
}

response = client.patch(url,
content_type='application/vnd.api+json',
data=dump_json(data))

assert response.status_code == 200
5 changes: 3 additions & 2 deletions example/tests/test_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ def test_query_count_include_author(self):
1. Primary resource COUNT query
2. Primary resource SELECT
3. Authors prefetched
3. Entries prefetched
4. Author types prefetched
5. Entries prefetched
"""
with self.assertNumQueries(4):
with self.assertNumQueries(5):
response = self.client.get('/comments?include=author&page_size=25')
self.assertEqual(len(response.data['results']), 25)
2 changes: 1 addition & 1 deletion example/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class CommentViewSet(ModelViewSet):
serializer_class = CommentSerializer
prefetch_for_includes = {
'__all__': [],
'author': ['author', 'author__bio', 'author__entries'],
'author': ['author', 'author__bio', 'author__entries', 'author__type'],
'entry': ['author', 'author__bio', 'author__entries']
}

Expand Down
11 changes: 8 additions & 3 deletions rest_framework_json_api/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rest_framework import parsers
from rest_framework.exceptions import ParseError

from . import exceptions, renderers, utils
from . import exceptions, renderers, serializers, utils


class JSONParser(parsers.JSONParser):
Expand Down Expand Up @@ -83,9 +83,10 @@ def parse(self, stream, media_type=None, parser_context=None):
raise ParseError('Received document does not contain primary data')

data = result.get('data')
view = parser_context['view']

from rest_framework_json_api.views import RelationshipView
if isinstance(parser_context['view'], RelationshipView):
if isinstance(view, RelationshipView):
# We skip parsing the object as JSONAPI Resource Identifier Object and not a regular
# Resource Object
if isinstance(data, list):
Expand Down Expand Up @@ -129,8 +130,12 @@ def parse(self, stream, media_type=None, parser_context=None):
raise ParseError("The resource identifier object must contain an 'id' member")

# Construct the return data
serializer_class = getattr(view, 'serializer_class', None)
parsed_data = {'id': data.get('id')} if 'id' in data else {}
parsed_data['type'] = data.get('type')
# `type` field needs to be allowed in none polymorphic serializers
if serializer_class is not None:
if issubclass(serializer_class, serializers.PolymorphicModelSerializer):
parsed_data['type'] = data.get('type')
parsed_data.update(self.parse_attributes(data))
parsed_data.update(self.parse_relationships(data))
parsed_data.update(self.parse_metadata(result))
Expand Down