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
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
@@ -7,6 +7,7 @@
ArtProject,
Author,
AuthorBio,
AuthorType,
Blog,
Comment,
Company,
@@ -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
@@ -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):
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
@@ -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
12 changes: 10 additions & 2 deletions example/serializers.py
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
ArtProject,
Author,
AuthorBio,
AuthorType,
Blog,
Comment,
Company,
@@ -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
@@ -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):
2 changes: 2 additions & 0 deletions example/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
ArtProjectFactory,
AuthorBioFactory,
AuthorFactory,
AuthorTypeFactory,
BlogFactory,
CommentFactory,
CompanyFactory,
@@ -16,6 +17,7 @@
register(BlogFactory)
register(AuthorFactory)
register(AuthorBioFactory)
register(AuthorTypeFactory)
register(EntryFactory)
register(CommentFactory)
register(TaggedItemFactory)
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
@@ -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
@@ -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
@@ -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']
}

11 changes: 8 additions & 3 deletions rest_framework_json_api/parsers.py
Original file line number Diff line number Diff line change
@@ -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):
@@ -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):
@@ -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))