Skip to content

Commit

Permalink
Allow type field on none polymorphic serializers (#376)
Browse files Browse the repository at this point in the history
  • Loading branch information
sliverc authored and mblayman committed Nov 28, 2017
1 parent a953a03 commit 9fa2e23
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 8 deletions.
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,
'[email protected]')


@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

0 comments on commit 9fa2e23

Please sign in to comment.