Skip to content

DRAFT: Polymorphism post and patch support #240

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

Closed
wants to merge 9 commits into from
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pip-delete-this-directory.txt

# Tox
.tox/
.cache/
.python-version

# VirtualEnv
.venv/
59 changes: 58 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ class LineItemViewSet(viewsets.ModelViewSet):

### RelationshipView
`rest_framework_json_api.views.RelationshipView` is used to build
relationship views (see the
relationship views (see the
[JSON API spec](http://jsonapi.org/format/#fetching-relationships)).
The `self` link on a relationship object should point to the corresponding
relationship view.
Expand Down Expand Up @@ -423,6 +423,63 @@ field_name_mapping = {
```


### Working with polymorphic resources

#### Extraction of the polymorphic type

This package can defer the resolution of the type of polymorphic models instances to retrieve the appropriate type.
However, most models are not polymorphic and for performance reasons this is only done if the underlying model is a subclass of a polymorphic model.

Polymorphic ancestors must be defined on settings like this:

```python
JSON_API_POLYMORPHIC_ANCESTORS = (
'polymorphic.models.PolymorphicModel',
)
```

#### Writing polymorphic resources

A polymorphic endpoint can be setup if associated with a polymorphic serializer.
A polymorphic serializer take care of (de)serializing the correct instances types and can be defined like this:

```python
class ProjectSerializer(serializers.PolymorphicModelSerializer):
polymorphic_serializers = [ArtProjectSerializer, ResearchProjectSerializer]

class Meta:
model = models.Project
```

It must inherit from `serializers.PolymorphicModelSerializer` and define the `polymorphic_serializers` list.
This attribute defines the accepted resource types.


Polymorphic relations can also be handled with `relations.PolymorphicResourceRelatedField` like this:

```python
class CompanySerializer(serializers.ModelSerializer):
current_project = relations.PolymorphicResourceRelatedField(
ProjectSerializer, queryset=models.Project.objects.all())
future_projects = relations.PolymorphicResourceRelatedField(
ProjectSerializer, queryset=models.Project.objects.all(), many=True)

class Meta:
model = models.Company
```

They must be explicitely declared with the `polymorphic_serializer` (first positional argument) correctly defined.
It must be a subclass of `serializers.PolymorphicModelSerializer`.

<div class="warning">
<strong>Note:</strong>
Polymorphic resources are not compatible with
<code class="docutils literal">
<span class="pre">resource_name</span>
</code>
defined on the view.
</div>

### Meta

You may add metadata to the rendered json in two different ways: `meta_fields` and `get_root_meta`.
Expand Down
45 changes: 39 additions & 6 deletions example/factories/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,23 @@

import factory
from faker import Factory as FakerFactory
from example.models import Blog, Author, AuthorBio, Entry, Comment
from example import models


faker = FakerFactory.create()
faker.seed(983843)


class BlogFactory(factory.django.DjangoModelFactory):
class Meta:
model = Blog
model = models.Blog

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


class AuthorFactory(factory.django.DjangoModelFactory):
class Meta:
model = Author
model = models.Author

name = factory.LazyAttribute(lambda x: faker.name())
email = factory.LazyAttribute(lambda x: faker.email())
Expand All @@ -25,15 +27,15 @@ class Meta:

class AuthorBioFactory(factory.django.DjangoModelFactory):
class Meta:
model = AuthorBio
model = models.AuthorBio

author = factory.SubFactory(AuthorFactory)
body = factory.LazyAttribute(lambda x: faker.text())


class EntryFactory(factory.django.DjangoModelFactory):
class Meta:
model = Entry
model = models.Entry

headline = factory.LazyAttribute(lambda x: faker.sentence(nb_words=4))
body_text = factory.LazyAttribute(lambda x: faker.text())
Expand All @@ -52,9 +54,40 @@ def authors(self, create, extracted, **kwargs):

class CommentFactory(factory.django.DjangoModelFactory):
class Meta:
model = Comment
model = models.Comment

entry = factory.SubFactory(EntryFactory)
body = factory.LazyAttribute(lambda x: faker.text())
author = factory.SubFactory(AuthorFactory)


class ArtProjectFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.ArtProject

topic = factory.LazyAttribute(lambda x: faker.catch_phrase())
artist = factory.LazyAttribute(lambda x: faker.name())


class ResearchProjectFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.ResearchProject

topic = factory.LazyAttribute(lambda x: faker.catch_phrase())
supervisor = factory.LazyAttribute(lambda x: faker.name())


class CompanyFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Company

name = factory.LazyAttribute(lambda x: faker.company())
current_project = factory.SubFactory(ArtProjectFactory)

@factory.post_generation
def future_projects(self, create, extracted, **kwargs):
if not create:
return
if extracted:
for project in extracted:
self.future_projects.add(project)
80 changes: 80 additions & 0 deletions example/migrations/0002_auto_20160513_0857.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-05-13 08:57
from __future__ import unicode_literals
from distutils.version import LooseVersion

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


class Migration(migrations.Migration):

# TODO: Must be removed as soon as Django 1.7 support is dropped
if django.get_version() < LooseVersion('1.8'):
dependencies = [
('contenttypes', '0001_initial'),
('example', '0001_initial'),
]
else:
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('example', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='Company',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name='Project',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('topic', models.CharField(max_length=30)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='ArtProject',
fields=[
('project_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='example.Project')),
('artist', models.CharField(max_length=30)),
],
options={
'abstract': False,
},
bases=('example.project',),
),
migrations.CreateModel(
name='ResearchProject',
fields=[
('project_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='example.Project')),
('supervisor', models.CharField(max_length=30)),
],
options={
'abstract': False,
},
bases=('example.project',),
),
migrations.AddField(
model_name='project',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_example.project_set+', to='contenttypes.ContentType'),
),
migrations.AddField(
model_name='company',
name='current_project',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='companies', to='example.Project'),
),
migrations.AddField(
model_name='company',
name='future_projects',
field=models.ManyToManyField(to='example.Project'),
),
]
22 changes: 22 additions & 0 deletions example/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from polymorphic.models import PolymorphicModel


class BaseModel(models.Model):
Expand Down Expand Up @@ -72,3 +73,24 @@ class Comment(BaseModel):
def __str__(self):
return self.body


class Project(PolymorphicModel):
topic = models.CharField(max_length=30)


class ArtProject(Project):
artist = models.CharField(max_length=30)


class ResearchProject(Project):
supervisor = models.CharField(max_length=30)


@python_2_unicode_compatible
class Company(models.Model):
name = models.CharField(max_length=100)
current_project = models.ForeignKey(Project, related_name='companies')
future_projects = models.ManyToManyField(Project)

def __str__(self):
return self.name
Loading