-
Notifications
You must be signed in to change notification settings - Fork 300
Support polymorphic models #211
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
Changes from 33 commits
dd4d4ec
8c73d95
681b5aa
96cbab0
5c63425
fddb06b
22829d1
d565334
e840438
19b0238
0ddf5ca
b8bf612
8fd4617
275793c
a26df13
2278976
8563b65
4aaeac2
030f6c8
ca23885
ae759e5
37c5ae6
f36821b
4eec4aa
bc12e0f
6b4f45b
36f3b6a
05cdb51
c1afe35
8ff5465
35c90d4
c5599c0
8d94efb
89ad607
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,8 @@ pip-delete-this-directory.txt | |
|
||
# Tox | ||
.tox/ | ||
.cache/ | ||
.python-version | ||
|
||
# VirtualEnv | ||
.venv/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
Adam Wróbel <https://adamwrobel.com> | ||
Christian Zosel <https://zosel.ch> | ||
Greg Aker <greg@gregaker.net> | ||
Jamie Bliss <astronouth7303@gmail.com> | ||
Jerel Unruh <mail@unruhdesigns.com> | ||
Léo S. <leo@naeka.fr> | ||
Matt Layman <http://www.mattlayman.com> | ||
Oliver Sauder <os@esite.ch> | ||
Yaniv Peer <yanivpeer@gmail.com> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
v2.3.0 | ||
|
||
* Added support for polymorphic models via `django-polymorphic` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, so actually this is only tested against I'm not sure how to deal with it, maybe an explicit warning, I'm not convinced to add more tests with more libraries... Any thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My sense is that we should make the documentation clear that |
||
* When `JSON_API_FORMAT_KEYS` is False (the default) do not translate request | ||
attributes and relations to snake\_case format. This conversion was unexpected | ||
and there was no way to turn it off. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -425,6 +425,56 @@ field_name_mapping = { | |
``` | ||
|
||
|
||
### Working with polymorphic resources | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This jumps straight into how to use polymorphic resources. While that is appropriate for a Usage page, if this is the only place in the documentation that describes polymorphic resources, I think an introductory paragraph providing context and why a user would need this would be beneficial. Also, are there going to be related packages that are needed for this work? If so, can we link to them? I'm thinking about |
||
Polymorphic resources (as backed by [django-polymorphic](https://django-polymorphic.readthedocs.io/en/stable/)) | ||
allow you to easily use specialized subclasses without have to have special | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can drop "easily" and replace "have to have" with "requiring." |
||
endpoints to expose the specialized versions. For example, if you had a | ||
`Project` that could be either an `ArtProject` or a `ResearchProject`, you can | ||
have both kinds at the same URL. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we could put a new paragraph here that includes comments about polymorphic libraries (and remove the "as backed by" parenthetical in the previous paragraph). How does the following sound?
|
||
#### Writing polymorphic resources | ||
|
||
A polymorphic endpoint can be set up if associated with a polymorphic serializer. | ||
A polymorphic serializer takes 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 explicitly declared with the `polymorphic_serializer` (first positional argument) correctly defined. | ||
It must be a subclass of `serializers.PolymorphicModelSerializer`. | ||
|
||
<div class="warning"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sigh... the downside of using Markdown for Sphinx, no directive support. Nothing to do here, just lamenting. 😢 |
||
<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`. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# -*- coding: utf-8 -*- | ||
# Generated by Django 1.11.1 on 2017-05-17 14:49 | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('contenttypes', '0002_remove_content_type_name'), | ||
('example', '0002_taggeditem'), | ||
] | ||
|
||
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.AlterField( | ||
model_name='comment', | ||
name='entry', | ||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='example.Entry'), | ||
), | ||
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'), | ||
), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
from django.contrib.contenttypes.fields import GenericRelation | ||
from django.db import models | ||
from django.utils.encoding import python_2_unicode_compatible | ||
from polymorphic.models import PolymorphicModel | ||
|
||
|
||
class BaseModel(models.Model): | ||
|
@@ -86,3 +87,25 @@ class Comment(BaseModel): | |
|
||
def __str__(self): | ||
return self.body | ||
|
||
|
||
class Project(PolymorphicModel): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice use of the |
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,12 @@ | ||
from datetime import datetime | ||
|
||
import rest_framework | ||
from rest_framework_json_api import serializers, relations | ||
from example.models import Blog, Entry, Author, AuthorBio, Comment, TaggedItem | ||
from packaging import version | ||
from example.models import ( | ||
Blog, Entry, Author, AuthorBio, Comment, TaggedItem, Project, ArtProject, ResearchProject, | ||
Company, | ||
) | ||
|
||
|
||
class TaggedItemSerializer(serializers.ModelSerializer): | ||
|
@@ -115,3 +121,40 @@ class Meta: | |
model = Comment | ||
exclude = ('created_at', 'modified_at',) | ||
# fields = ('entry', 'body', 'author',) | ||
|
||
|
||
class ArtProjectSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = ArtProject | ||
exclude = ('polymorphic_ctype',) | ||
|
||
|
||
class ResearchProjectSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = ResearchProject | ||
exclude = ('polymorphic_ctype',) | ||
|
||
|
||
class ProjectSerializer(serializers.PolymorphicModelSerializer): | ||
polymorphic_serializers = [ArtProjectSerializer, ResearchProjectSerializer] | ||
|
||
class Meta: | ||
model = Project | ||
exclude = ('polymorphic_ctype',) | ||
|
||
|
||
class CompanySerializer(serializers.ModelSerializer): | ||
current_project = relations.PolymorphicResourceRelatedField( | ||
ProjectSerializer, queryset=Project.objects.all()) | ||
future_projects = relations.PolymorphicResourceRelatedField( | ||
ProjectSerializer, queryset=Project.objects.all(), many=True) | ||
|
||
included_serializers = { | ||
'current_project': ProjectSerializer, | ||
'future_projects': ProjectSerializer, | ||
} | ||
|
||
class Meta: | ||
model = Company | ||
if version.parse(rest_framework.VERSION) >= version.parse('3.3'): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting... I had no idea it was possible to use an |
||
fields = '__all__' |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ | |
'django.contrib.auth', | ||
'django.contrib.admin', | ||
'rest_framework', | ||
'polymorphic', | ||
'example', | ||
] | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good! Thanks.