Repository: apirobot/django-rest-polymorphic
Branch: master
Commit: 9d920eb91ef1
Files: 41
Total size: 36.9 KB
Directory structure:
gitextract_sxcds196/
├── .editorconfig
├── .gitignore
├── .travis.yml
├── AUTHORS.rst
├── HISTORY.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── codecov.yml
├── example/
│ ├── example/
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── manage.py
│ └── projects/
│ ├── __init__.py
│ ├── management/
│ │ ├── __init__.py
│ │ └── commands/
│ │ ├── __init__.py
│ │ └── testdata.py
│ ├── migrations/
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── serializers.py
│ ├── urls.py
│ └── views.py
├── manage.py
├── pytest.ini
├── requirements/
│ ├── codestyle.txt
│ ├── packaging.txt
│ └── testing.txt
├── requirements.txt
├── rest_polymorphic/
│ ├── __init__.py
│ ├── __version__.py
│ └── serializers.py
├── setup.py
├── tests/
│ ├── __init__.py
│ ├── models.py
│ ├── serializers.py
│ ├── settings.py
│ └── test_serializers.py
└── tox.ini
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{py,rst,ini}]
indent_style = space
indent_size = 4
[*.{html,css,scss,json,yml}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
================================================
FILE: .gitignore
================================================
*.py[cod]
__pycache__
# C extensions
*.so
# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
nosetests.xml
htmlcov
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Pycharm/Intellij
.idea
# Complexity
output/*.html
output/*/index.html
# Sphinx
docs/_build
# DB
db.sqlite3
================================================
FILE: .travis.yml
================================================
sudo: false
language: python
cache: pip
matrix:
fast_finish: true
include:
- python: 3.6
env: TOXENV=py36-django22-drf38
- python: 3.6
env: TOXENV=py36-django22-drf39
- python: 3.6
env: TOXENV=py36-django22-drf310
- python: 3.6
env: TOXENV=py36-django22-drf311
- python: 3.6
env: TOXENV=py36-django30-drf310
- python: 3.6
env: TOXENV=py36-django30-drf311
- python: 3.6
env: TOXENV=py36-djangomaster-drfmaster
- python: 3.7
env: TOXENV=py37-django22-drf38
dist: xenial
sudo: required
- python: 3.7
env: TOXENV=py37-django22-drf39
dist: xenial
sudo: required
- python: 3.7
env: TOXENV=py37-django22-drf310
dist: xenial
sudo: required
- python: 3.7
env: TOXENV=py37-django22-drf311
dist: xenial
sudo: required
- python: 3.7
env: TOXENV=py37-django30-drf310
dist: xenial
sudo: required
- python: 3.7
env: TOXENV=py37-django30-drf311
dist: xenial
sudo: required
- python: 3.7
env: TOXENV=py37-djangomaster-drfmaster
dist: xenial
sudo: required
- python: 3.8
env: TOXENV=py38-django22-drf38
dist: xenial
sudo: required
- python: 3.8
env: TOXENV=py38-django22-drf39
dist: xenial
sudo: required
- python: 3.8
env: TOXENV=py38-django22-drf310
dist: xenial
sudo: required
- python: 3.8
env: TOXENV=py38-django22-drf311
dist: xenial
sudo: required
- python: 3.8
env: TOXENV=py38-django30-drf310
dist: xenial
sudo: required
- python: 3.8
env: TOXENV=py38-django30-drf311
dist: xenial
sudo: required
- python: 3.8
env: TOXENV=py38-djangomaster-drfmaster
dist: xenial
sudo: required
allow_failures:
- env: TOXENV=py36-djangomaster-drfmaster
- env: TOXENV=py37-djangomaster-drfmaster
- env: TOXENV=py38-djangomaster-drfmaster
install:
- travis_retry pip install -U tox-travis
script:
- tox
after_success:
- travis_retry pip install -U codecov
- codecov
================================================
FILE: AUTHORS.rst
================================================
=======
Credits
=======
Development Lead
----------------
* Denis Orehovsky <denis.orehovsky@gmail.com>
Contributors
------------
* Jeff Hackshaw <jeffrey.hackshaw@gmail.com>
* TFranzel
* Ignacio Losiggio <iglosiggio@gmail.com>
================================================
FILE: HISTORY.rst
================================================
.. :changelog:
History
-------
0.1.10 (2022-07-17)
++++++++++++++++++
* Allow partial updates without resourcetype.
0.1.9 (2020-04-02)
++++++++++++++++++
* Fix validation error and update versions.
0.1.8 (2018-10-11)
++++++++++++++++++
* Add Python 3.7 and Django 2.1 support.
0.1.7 (2018-06-06)
++++++++++++++++++
0.1.6 (2018-06-03)
++++++++++++++++++
* Add Django REST Framework 3.8 support.
0.1.5 (2018-03-29)
++++++++++++++++++
* Fix validation for nested serializers.
0.1.4 (2018-02-10)
++++++++++++++++++
* Fix serializer instantiation.
0.1.3 (2018-02-09)
++++++++++++++++++
* Add Python 2.7 support.
0.1.2 (2018-01-30)
++++++++++++++++++
* Add Django 2.0 support.
* Drop Django 1.10 support.
0.1.1 (2017-12-08)
++++++++++++++++++
* Add example project.
0.1.0 (2017-12-07)
++++++++++++++++++
* First release on PyPI.
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017, Denis Orehovsky
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
include AUTHORS.rst
include HISTORY.rst
include LICENSE
include README.rst
recursive-include rest_polymorphic *.html *.png *.gif *js *.css *jpg *jpeg *svg *py
================================================
FILE: README.rst
================================================
.. image:: https://travis-ci.org/apirobot/django-rest-polymorphic.svg?branch=master
:target: https://travis-ci.org/apirobot/django-rest-polymorphic
.. image:: https://codecov.io/gh/apirobot/django-rest-polymorphic/branch/master/graph/badge.svg
:target: https://codecov.io/gh/apirobot/django-rest-polymorphic
.. image:: https://badge.fury.io/py/django-rest-polymorphic.svg
:target: https://badge.fury.io/py/django-rest-polymorphic
=======================
Django REST Polymorphic
=======================
Polymorphic serializers for Django REST Framework.
Overview
--------
``django-rest-polymorphic`` allows you to easily define serializers for your inherited models that you have created using ``django-polymorphic`` library.
Installation
------------
Install using ``pip``:
.. code-block:: bash
$ pip install django-rest-polymorphic
Usage
-----
Define your polymorphic models:
.. code-block:: python
# models.py
from django.db import models
from polymorphic.models import PolymorphicModel
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)
Define serializers for each polymorphic model the way you did it when you used ``django-rest-framework``:
.. code-block:: python
# serializers.py
from rest_framework import serializers
from .models import Project, ArtProject, ResearchProject
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('topic', )
class ArtProjectSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ArtProject
fields = ('topic', 'artist', 'url')
extra_kwargs = {
'url': {'view_name': 'project-detail', 'lookup_field': 'pk'},
}
class ResearchProjectSerializer(serializers.ModelSerializer):
class Meta:
model = ResearchProject
fields = ('topic', 'supervisor')
Note that if you extend ``HyperlinkedModelSerializer`` instead of ``ModelSerializer`` you need to define ``extra_kwargs`` to direct the URL to the appropriate view for your polymorphic serializer.
Then you have to create a polymorphic serializer that serves as a mapper between models and serializers which you have defined above:
.. code-block:: python
# serializers.py
from rest_polymorphic.serializers import PolymorphicSerializer
class ProjectPolymorphicSerializer(PolymorphicSerializer):
model_serializer_mapping = {
Project: ProjectSerializer,
ArtProject: ArtProjectSerializer,
ResearchProject: ResearchProjectSerializer
}
Create viewset with serializer_class equals to your polymorphic serializer:
.. code-block:: python
# views.py
from rest_framework import viewsets
from .models import Project
from .serializers import ProjectPolymorphicSerializer
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectPolymorphicSerializer
Test it:
.. code-block:: bash
$ http GET "http://localhost:8000/projects/"
.. code-block:: http
HTTP/1.0 200 OK
Content-Length: 227
Content-Type: application/json
[
{
"resourcetype": "Project",
"topic": "John's gathering"
},
{
"artist": "T. Turner",
"resourcetype": "ArtProject",
"topic": "Sculpting with Tim",
"url": "http://localhost:8000/projects/2/"
},
{
"resourcetype": "ResearchProject",
"supervisor": "Dr. Winter",
"topic": "Swallow Aerodynamics"
}
]
.. code-block:: bash
$ http POST "http://localhost:8000/projects/" resourcetype="ArtProject" topic="Guernica" artist="Picasso"
.. code-block:: http
HTTP/1.0 201 Created
Content-Length: 67
Content-Type: application/json
{
"artist": "Picasso",
"resourcetype": "ArtProject",
"topic": "Guernica",
"url": "http://localhost:8000/projects/4/"
}
Customize resource type
-----------------------
As you can see from the example above, in order to specify the type of your polymorphic model, you need to send a request with resource type field. The value of resource type should be the name of the model.
If you want to change the resource type field name from ``resourcetype`` to something else, you should override ``resource_type_field_name`` attribute:
.. code-block:: python
class ProjectPolymorphicSerializer(PolymorphicSerializer):
resource_type_field_name = 'projecttype'
...
If you want to change the behavior of resource type, you should override ``to_resource_type`` method:
.. code-block:: python
class ProjectPolymorphicSerializer(PolymorphicSerializer):
...
def to_resource_type(self, model_or_instance):
return model_or_instance._meta.object_name.lower()
Now, the request for creating new object will look like this:
.. code-block:: bash
$ http POST "http://localhost:8000/projects/" projecttype="artproject" topic="Guernica" artist="Picasso"
================================================
FILE: codecov.yml
================================================
coverage:
precision: 2
round: down
range: 70...100
status:
project: true
patch: true
changes: false
comment:
layout: "header, diff"
behavior: default
ignore:
- "rest_polymorphic/__version__.py"
================================================
FILE: example/example/__init__.py
================================================
================================================
FILE: example/example/asgi.py
================================================
"""
ASGI config for example project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings')
application = get_asgi_application()
================================================
FILE: example/example/settings.py
================================================
"""
Django settings for example project.
Generated by 'django-admin startproject' using Django 3.0.5.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.0/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'i=m896-(xjn#dz*kx=m8aa^7y!le5od3$(y-2doym!c%or=e64'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'projects',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'example.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates'), ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'example.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
================================================
FILE: example/example/urls.py
================================================
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('projects.urls')),
]
================================================
FILE: example/example/wsgi.py
================================================
"""
WSGI config for example project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings')
application = get_wsgi_application()
================================================
FILE: example/manage.py
================================================
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
================================================
FILE: example/projects/__init__.py
================================================
================================================
FILE: example/projects/management/__init__.py
================================================
================================================
FILE: example/projects/management/commands/__init__.py
================================================
================================================
FILE: example/projects/management/commands/testdata.py
================================================
from django.core.management.base import BaseCommand
from projects.models import Project, ArtProject, ResearchProject
class Command(BaseCommand):
help = "Generates test data"
def handle(self, *args, **options):
Project.objects.all().delete()
Project.objects.create(topic="Project title #1")
ArtProject.objects.create(
topic="Art project title #1",
artist="T. Artist"
)
ResearchProject.objects.create(
topic="Research project title #1",
supervisor="Dr. Research"
)
================================================
FILE: example/projects/migrations/0001_initial.py
================================================
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-12-08 12:52
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
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='projects.Project')),
('artist', models.CharField(max_length=30)),
],
options={
'abstract': False,
},
bases=('projects.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='projects.Project')),
('supervisor', models.CharField(max_length=30)),
],
options={
'abstract': False,
},
bases=('projects.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_projects.project_set+', to='contenttypes.ContentType'),
),
]
================================================
FILE: example/projects/migrations/__init__.py
================================================
================================================
FILE: example/projects/models.py
================================================
from django.db import models
from polymorphic.models import PolymorphicModel
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)
================================================
FILE: example/projects/serializers.py
================================================
from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer
from .models import Project, ArtProject, ResearchProject
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('topic', )
class ArtProjectSerializer(serializers.ModelSerializer):
class Meta:
model = ArtProject
fields = ('topic', 'artist', 'url')
extra_kwargs = {
'url': {'view_name': 'project-detail', 'lookup_field': 'pk'},
}
class ResearchProjectSerializer(serializers.ModelSerializer):
class Meta:
model = ResearchProject
fields = ('topic', 'supervisor')
class ProjectPolymorphicSerializer(PolymorphicSerializer):
model_serializer_mapping = {
Project: ProjectSerializer,
ArtProject: ArtProjectSerializer,
ResearchProject: ResearchProjectSerializer
}
================================================
FILE: example/projects/urls.py
================================================
from rest_framework.routers import DefaultRouter
from .views import ProjectViewSet
router = DefaultRouter()
router.register(r'projects', ProjectViewSet)
urlpatterns = router.urls
================================================
FILE: example/projects/views.py
================================================
from rest_framework import viewsets
from .models import Project
from .serializers import ProjectPolymorphicSerializer
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectPolymorphicSerializer
================================================
FILE: manage.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
================================================
FILE: pytest.ini
================================================
[pytest]
DJANGO_SETTINGS_MODULE=tests.settings
================================================
FILE: requirements/codestyle.txt
================================================
flake8==3.7.9
================================================
FILE: requirements/packaging.txt
================================================
# Twine for secured PyPI uploads.
twine==3.1.1
================================================
FILE: requirements/testing.txt
================================================
pytest==5.4.1
pytest-django==3.9.0
pytest-mock==3.0.0
pytest-cov==2.8.1
================================================
FILE: requirements.txt
================================================
-e .
-r requirements/codestyle.txt
-r requirements/packaging.txt
-r requirements/testing.txt
================================================
FILE: rest_polymorphic/__init__.py
================================================
================================================
FILE: rest_polymorphic/__version__.py
================================================
__version__ = '0.1.10'
__title__ = 'django-rest-polymorphic'
__description__ = 'Polymorphic serializers for Django REST Framework.'
__url__ = 'https://github.com/denisorehovsky/django-rest-polymorphic'
__author__ = 'Denis Orehovsky'
__author_email__ = 'denis.orehovsky@gmail.com'
__license__ = 'MIT License'
================================================
FILE: rest_polymorphic/serializers.py
================================================
from collections.abc import Mapping
from six import string_types
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from rest_framework import serializers
from rest_framework.fields import empty
class PolymorphicSerializer(serializers.Serializer):
model_serializer_mapping = None
resource_type_field_name = 'resourcetype'
def __new__(cls, *args, **kwargs):
if cls.model_serializer_mapping is None:
raise ImproperlyConfigured(
'`{cls}` is missing a '
'`{cls}.model_serializer_mapping` attribute'.format(
cls=cls.__name__
)
)
if not isinstance(cls.resource_type_field_name, string_types):
raise ImproperlyConfigured(
'`{cls}.resource_type_field_name` must be a string'.format(
cls=cls.__name__
)
)
return super(PolymorphicSerializer, cls).__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
super(PolymorphicSerializer, self).__init__(*args, **kwargs)
model_serializer_mapping = self.model_serializer_mapping
self.model_serializer_mapping = {}
self.resource_type_model_mapping = {}
for model, serializer in model_serializer_mapping.items():
resource_type = self.to_resource_type(model)
if callable(serializer):
serializer = serializer(*args, **kwargs)
serializer.parent = self
self.resource_type_model_mapping[resource_type] = model
self.model_serializer_mapping[model] = serializer
# ----------
# Public API
def to_resource_type(self, model_or_instance):
return model_or_instance._meta.object_name
def to_representation(self, instance):
if isinstance(instance, Mapping):
resource_type = self._get_resource_type_from_mapping(instance)
serializer = self._get_serializer_from_resource_type(resource_type)
else:
resource_type = self.to_resource_type(instance)
serializer = self._get_serializer_from_model_or_instance(instance)
ret = serializer.to_representation(instance)
ret[self.resource_type_field_name] = resource_type
return ret
def to_internal_value(self, data):
if self.partial and self.instance:
resource_type = self.to_resource_type(self.instance)
serializer = self._get_serializer_from_model_or_instance(self.instance)
else:
resource_type = self._get_resource_type_from_mapping(data)
serializer = self._get_serializer_from_resource_type(resource_type)
ret = serializer.to_internal_value(data)
ret[self.resource_type_field_name] = resource_type
return ret
def create(self, validated_data):
resource_type = validated_data.pop(self.resource_type_field_name)
serializer = self._get_serializer_from_resource_type(resource_type)
return serializer.create(validated_data)
def update(self, instance, validated_data):
resource_type = validated_data.pop(self.resource_type_field_name)
serializer = self._get_serializer_from_resource_type(resource_type)
return serializer.update(instance, validated_data)
def is_valid(self, *args, **kwargs):
valid = super(PolymorphicSerializer, self).is_valid(*args, **kwargs)
try:
if self.partial and self.instance:
resource_type = self.to_resource_type(self.instance)
serializer = self._get_serializer_from_model_or_instance(self.instance)
else:
resource_type = self._get_resource_type_from_mapping(self.initial_data)
serializer = self._get_serializer_from_resource_type(resource_type)
except serializers.ValidationError:
child_valid = False
else:
child_valid = serializer.is_valid(*args, **kwargs)
self._errors.update(serializer.errors)
return valid and child_valid
def run_validation(self, data=empty):
if self.partial and self.instance:
resource_type = self.to_resource_type(self.instance)
serializer = self._get_serializer_from_model_or_instance(self.instance)
else:
resource_type = self._get_resource_type_from_mapping(data)
serializer = self._get_serializer_from_resource_type(resource_type)
validated_data = serializer.run_validation(data)
validated_data[self.resource_type_field_name] = resource_type
return validated_data
# --------------
# Implementation
def _to_model(self, model_or_instance):
return (model_or_instance.__class__
if isinstance(model_or_instance, models.Model)
else model_or_instance)
def _get_resource_type_from_mapping(self, mapping):
try:
return mapping[self.resource_type_field_name]
except KeyError:
raise serializers.ValidationError({
self.resource_type_field_name: 'This field is required',
})
def _get_serializer_from_model_or_instance(self, model_or_instance):
model = self._to_model(model_or_instance)
for klass in model.mro():
if klass in self.model_serializer_mapping:
return self.model_serializer_mapping[klass]
raise KeyError(
'`{cls}.model_serializer_mapping` is missing '
'a corresponding serializer for `{model}` model'.format(
cls=self.__class__.__name__,
model=model.__name__
)
)
def _get_serializer_from_resource_type(self, resource_type):
try:
model = self.resource_type_model_mapping[resource_type]
except KeyError:
raise serializers.ValidationError({
self.resource_type_field_name: 'Invalid {0}'.format(
self.resource_type_field_name
)
})
return self._get_serializer_from_model_or_instance(model)
================================================
FILE: setup.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import io
import os
import sys
from shutil import rmtree
from setuptools import setup, Command
here = os.path.abspath(os.path.dirname(__file__))
# Long description
with io.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
readme = '\n' + f.read()
about = {}
# Get meta-data from __version__.py
with open(os.path.join(here, 'rest_polymorphic', '__version__.py')) as f:
exec(f.read(), about)
class UploadCommand(Command):
"""Support setup.py upload."""
description = 'Build and publish the package.'
user_options = []
@staticmethod
def status(s):
"""Prints things in bold."""
print('\033[1m{0}\033[0m'.format(s))
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
try:
self.status('Removing previous builds…')
rmtree(os.path.join(here, 'dist'))
except OSError:
pass
self.status('Building Source and Wheel (universal) distribution…')
os.system('{0} setup.py sdist bdist_wheel --universal'.format(
sys.executable
))
self.status('Uploading the package to PyPi via Twine…')
os.system('twine upload dist/*')
sys.exit()
setup(
name=about['__title__'],
version=about['__version__'],
description=about['__description__'],
long_description=readme,
author=about['__author__'],
author_email=about['__author_email__'],
url=about['__url__'],
license=about['__license__'],
packages=[
'rest_polymorphic',
],
install_requires=[
'django',
'djangorestframework',
'django-polymorphic',
'six',
],
classifiers=[
# Trove classifiers
# Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
'License :: OSI Approved :: MIT License',
'Intended Audience :: Developers',
'Framework :: Django',
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.0',
'Programming Language :: Python',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy'
],
cmdclass={
'upload': UploadCommand,
},
)
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/models.py
================================================
from django.db import models
from polymorphic.models import PolymorphicModel
class BlogBase(PolymorphicModel):
name = models.CharField(max_length=10)
slug = models.SlugField(max_length=255, unique=True)
class BlogOne(BlogBase):
info = models.CharField(max_length=10)
class BlogTwo(BlogBase):
pass
class BlogThree(BlogBase):
info = models.CharField(max_length=255)
about = models.CharField(max_length=255)
class Meta:
unique_together = (('info', 'about'),)
================================================
FILE: tests/serializers.py
================================================
from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer
from tests.models import BlogBase, BlogOne, BlogTwo, BlogThree
class BlogBaseSerializer(serializers.ModelSerializer):
class Meta:
model = BlogBase
fields = ('name', 'slug')
class BlogOneSerializer(serializers.ModelSerializer):
class Meta:
model = BlogOne
fields = ('name', 'slug', 'info')
class BlogTwoSerializer(serializers.ModelSerializer):
class Meta:
model = BlogTwo
fields = ('name', 'slug')
class BlogThreeSerializer(serializers.ModelSerializer):
class Meta:
model = BlogThree
fields = ('name', 'slug', 'info', 'about')
class BlogPolymorphicSerializer(PolymorphicSerializer):
model_serializer_mapping = {
BlogBase: BlogBaseSerializer,
BlogOne: BlogOneSerializer,
BlogTwo: BlogTwoSerializer,
BlogThree: BlogThreeSerializer
}
================================================
FILE: tests/settings.py
================================================
# -*- coding: utf-8
from __future__ import absolute_import, unicode_literals
import django
from django.core import management
DEBUG = True
USE_TZ = True
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '55555555555555555555555555555555555555555555555555'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sites',
'rest_polymorphic',
'tests',
]
SITE_ID = 1
if django.VERSION >= (1, 10):
MIDDLEWARE = ()
else:
MIDDLEWARE_CLASSES = ()
django.setup()
management.call_command('migrate')
================================================
FILE: tests/test_serializers.py
================================================
from django.core.exceptions import ImproperlyConfigured
import pytest
from rest_polymorphic.serializers import PolymorphicSerializer
from tests.models import BlogBase, BlogOne, BlogTwo
from tests.serializers import BlogPolymorphicSerializer
pytestmark = pytest.mark.django_db
class TestPolymorphicSerializer:
def test_model_serializer_mapping_is_none(self):
class EmptyPolymorphicSerializer(PolymorphicSerializer):
pass
with pytest.raises(ImproperlyConfigured) as excinfo:
EmptyPolymorphicSerializer()
assert str(excinfo.value) == (
'`EmptyPolymorphicSerializer` is missing a '
'`EmptyPolymorphicSerializer.model_serializer_mapping` attribute'
)
def test_resource_type_field_name_is_not_string(self, mocker):
class NotStringPolymorphicSerializer(PolymorphicSerializer):
model_serializer_mapping = mocker.MagicMock
resource_type_field_name = 1
with pytest.raises(ImproperlyConfigured) as excinfo:
NotStringPolymorphicSerializer()
assert str(excinfo.value) == (
'`NotStringPolymorphicSerializer.resource_type_field_name` must '
'be a string'
)
def test_each_serializer_has_context(self, mocker):
context = mocker.MagicMock()
serializer = BlogPolymorphicSerializer(context=context)
for inner_serializer in serializer.model_serializer_mapping.values():
assert inner_serializer.context == context
def test_serialize(self):
instance = BlogBase.objects.create(name='blog', slug='blog')
serializer = BlogPolymorphicSerializer(instance)
assert serializer.data == {
'name': 'blog',
'slug': 'blog',
'resourcetype': 'BlogBase',
}
def test_deserialize(self):
data = {
'name': 'blog',
'slug': 'blog',
'resourcetype': 'BlogBase',
}
serializers = BlogPolymorphicSerializer(data=data)
assert serializers.is_valid()
assert serializers.data == data
def test_deserialize_with_invalid_resourcetype(self):
data = {
'name': 'blog',
'resourcetype': 'Invalid',
}
serializers = BlogPolymorphicSerializer(data=data)
assert not serializers.is_valid()
def test_create(self):
data = [
{
'name': 'a',
'slug': 'a',
'resourcetype': 'BlogBase'
},
{
'name': 'b',
'slug': 'b',
'info': 'info',
'resourcetype': 'BlogOne'
},
{
'name': 'c',
'slug': 'c',
'resourcetype': 'BlogTwo'
},
]
serializer = BlogPolymorphicSerializer(data=data, many=True)
assert serializer.is_valid()
instances = serializer.save()
assert len(instances) == 3
assert [item.name for item in instances] == ['a', 'b', 'c']
assert BlogBase.objects.count() == 3
assert BlogBase.objects.instance_of(BlogOne).count() == 1
assert BlogBase.objects.instance_of(BlogTwo).count() == 1
assert serializer.data == data
def test_update(self):
instance = BlogBase.objects.create(name='blog', slug='blog')
data = {
'name': 'new-blog',
'slug': 'blog',
'resourcetype': 'BlogBase'
}
serializer = BlogPolymorphicSerializer(instance, data=data)
assert serializer.is_valid()
serializer.save()
assert instance.name == 'new-blog'
assert instance.slug == 'blog'
def test_partial_update(self):
instance = BlogBase.objects.create(name='blog', slug='blog')
data = {
'name': 'new-blog',
'resourcetype': 'BlogBase'
}
serializer = BlogPolymorphicSerializer(
instance, data=data, partial=True
)
assert serializer.is_valid()
serializer.save()
assert instance.name == 'new-blog'
assert instance.slug == 'blog'
def test_partial_update_without_resourcetype(self):
instance = BlogBase.objects.create(name='blog', slug='blog')
data = {'name': 'new-blog'}
serializer = BlogPolymorphicSerializer(
instance, data=data, partial=True
)
assert serializer.is_valid()
serializer.save()
assert instance.name == 'new-blog'
assert instance.slug == 'blog'
def test_object_validators_are_applied(self):
data = {
'name': 'test-blog',
'slug': 'test-blog-slug',
'info': 'test-blog-info',
'about': 'test-blog-about',
'resourcetype': 'BlogThree'
}
serializer = BlogPolymorphicSerializer(data=data)
assert serializer.is_valid()
serializer.save()
data['slug'] = 'test-blog-slug-new'
duplicate = BlogPolymorphicSerializer(data=data)
assert not duplicate.is_valid()
assert 'non_field_errors' in duplicate.errors
err = duplicate.errors['non_field_errors']
assert err == ['The fields info, about must make a unique set.']
================================================
FILE: tox.ini
================================================
[tox]
envlist =
py{35,36,37,38}-django{22}-drf{38,39,310,311},
py{36,37,38}-django{30}-drf{310,311},
py{36,37,38}-djangomaster-drfmaster,
flake8
[testenv]
passenv = CI TRAVIS TRAVIS_*
basepython =
py35: python3.5
py36: python3.6
py37: python3.7
py38: python3.8
deps =
django22: django>=2.2,<3.0
django30: django>=3.0,<3.1
djangomaster: git+git://github.com/django/django.git
drf38: djangorestframework>=3.8,<3.9
drf39: djangorestframework>=3.9,<3.10
drf310: djangorestframework>=3.10,<3.11
drf311: djangorestframework==3.11,<3.12
drfmaster: git+git://github.com/encode/django-rest-framework.git
-rrequirements/testing.txt
commands =
py.test --capture=no --cov-report term-missing --cov-report html --cov=rest_polymorphic tests/
[testenv:flake8]
basepython = python3
deps =
-rrequirements/codestyle.txt
-rrequirements/testing.txt
commands =
flake8 rest_polymorphic/ tests/ setup.py
gitextract_sxcds196/ ├── .editorconfig ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── codecov.yml ├── example/ │ ├── example/ │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── manage.py │ └── projects/ │ ├── __init__.py │ ├── management/ │ │ ├── __init__.py │ │ └── commands/ │ │ ├── __init__.py │ │ └── testdata.py │ ├── migrations/ │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── urls.py │ └── views.py ├── manage.py ├── pytest.ini ├── requirements/ │ ├── codestyle.txt │ ├── packaging.txt │ └── testing.txt ├── requirements.txt ├── rest_polymorphic/ │ ├── __init__.py │ ├── __version__.py │ └── serializers.py ├── setup.py ├── tests/ │ ├── __init__.py │ ├── models.py │ ├── serializers.py │ ├── settings.py │ └── test_serializers.py └── tox.ini
Condensed preview — 41 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (41K chars).
[
{
"path": ".editorconfig",
"chars": 331,
"preview": "# http://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_w..."
},
{
"path": ".gitignore",
"chars": 436,
"preview": "*.py[cod]\n__pycache__\n\n# C extensions\n*.so\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nbin\nvar\nsdist\ndevelop-eggs..."
},
{
"path": ".travis.yml",
"chars": 2149,
"preview": "sudo: false\nlanguage: python\ncache: pip\n\nmatrix:\n fast_finish: true\n include:\n - python: 3.6\n env: TOXENV=py36..."
},
{
"path": "AUTHORS.rst",
"chars": 232,
"preview": "=======\nCredits\n=======\n\nDevelopment Lead\n----------------\n\n* Denis Orehovsky <denis.orehovsky@gmail.com>\n\nContributors..."
},
{
"path": "HISTORY.rst",
"chars": 845,
"preview": ".. :changelog:\n\nHistory\n-------\n\n0.1.10 (2022-07-17)\n++++++++++++++++++\n\n* Allow partial updates without resourcetype...."
},
{
"path": "LICENSE",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2017, Denis Orehovsky\n\nPermission is hereby granted, free of charge, to any person obtaining..."
},
{
"path": "MANIFEST.in",
"chars": 159,
"preview": "include AUTHORS.rst\ninclude HISTORY.rst\ninclude LICENSE\ninclude README.rst\nrecursive-include rest_polymorphic *.html *.p..."
},
{
"path": "README.rst",
"chars": 5371,
"preview": ".. image:: https://travis-ci.org/apirobot/django-rest-polymorphic.svg?branch=master\n :target: https://travis-ci.org/a..."
},
{
"path": "codecov.yml",
"chars": 223,
"preview": "coverage:\n precision: 2\n round: down\n range: 70...100\n\n status:\n project: true\n patch: true\n changes: false..."
},
{
"path": "example/example/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "example/example/asgi.py",
"chars": 391,
"preview": "\"\"\"\nASGI config for example project.\n\nIt exposes the ASGI callable as a module-level variable named ``application``.\n\nFo..."
},
{
"path": "example/example/settings.py",
"chars": 3167,
"preview": "\"\"\"\nDjango settings for example project.\n\nGenerated by 'django-admin startproject' using Django 3.0.5.\n\nFor more informa..."
},
{
"path": "example/example/urls.py",
"chars": 167,
"preview": "from django.contrib import admin\nfrom django.urls import path, include\n\nurlpatterns = [\n path('admin/', admin.site.ur..."
},
{
"path": "example/example/wsgi.py",
"chars": 391,
"preview": "\"\"\"\nWSGI config for example project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFo..."
},
{
"path": "example/manage.py",
"chars": 627,
"preview": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\nimport os\nimport sys\n\n\ndef main():..."
},
{
"path": "example/projects/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "example/projects/management/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "example/projects/management/commands/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "example/projects/management/commands/testdata.py",
"chars": 571,
"preview": "from django.core.management.base import BaseCommand\n\nfrom projects.models import Project, ArtProject, ResearchProject..."
},
{
"path": "example/projects/migrations/0001_initial.py",
"chars": 1983,
"preview": "# -*- coding: utf-8 -*-\n# Generated by Django 1.11 on 2017-12-08 12:52\nfrom __future__ import unicode_literals\n\nfrom dja..."
},
{
"path": "example/projects/migrations/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "example/projects/models.py",
"chars": 314,
"preview": "from django.db import models\n\nfrom polymorphic.models import PolymorphicModel\n\n\nclass Project(PolymorphicModel):\n top..."
},
{
"path": "example/projects/serializers.py",
"chars": 923,
"preview": "from rest_framework import serializers\n\nfrom rest_polymorphic.serializers import PolymorphicSerializer\n\nfrom .models imp..."
},
{
"path": "example/projects/urls.py",
"chars": 182,
"preview": "from rest_framework.routers import DefaultRouter\n\nfrom .views import ProjectViewSet\n\nrouter = DefaultRouter()\nrouter.reg..."
},
{
"path": "example/projects/views.py",
"chars": 255,
"preview": "from rest_framework import viewsets\n\nfrom .models import Project\nfrom .serializers import ProjectPolymorphicSerializer..."
},
{
"path": "manage.py",
"chars": 329,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom __future__ import unicode_literals, absolute_import\n\nimport os\nimport..."
},
{
"path": "pytest.ini",
"chars": 47,
"preview": "[pytest]\nDJANGO_SETTINGS_MODULE=tests.settings\n"
},
{
"path": "requirements/codestyle.txt",
"chars": 14,
"preview": "flake8==3.7.9\n"
},
{
"path": "requirements/packaging.txt",
"chars": 47,
"preview": "# Twine for secured PyPI uploads.\ntwine==3.1.1\n"
},
{
"path": "requirements/testing.txt",
"chars": 72,
"preview": "pytest==5.4.1\npytest-django==3.9.0\npytest-mock==3.0.0\npytest-cov==2.8.1\n"
},
{
"path": "requirements.txt",
"chars": 93,
"preview": "-e .\n-r requirements/codestyle.txt\n-r requirements/packaging.txt\n-r requirements/testing.txt\n"
},
{
"path": "rest_polymorphic/__init__.py",
"chars": 1,
"preview": "\n"
},
{
"path": "rest_polymorphic/__version__.py",
"chars": 308,
"preview": "__version__ = '0.1.10'\n__title__ = 'django-rest-polymorphic'\n__description__ = 'Polymorphic serializers for Django REST..."
},
{
"path": "rest_polymorphic/serializers.py",
"chars": 6173,
"preview": "from collections.abc import Mapping\nfrom six import string_types\n\nfrom django.core.exceptions import ImproperlyConfigure..."
},
{
"path": "setup.py",
"chars": 2469,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\nimport io\nimport os\nimport sys\nfrom shutil import rmtree\n\nfrom setuptools..."
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/models.py",
"chars": 502,
"preview": "from django.db import models\n\nfrom polymorphic.models import PolymorphicModel\n\n\nclass BlogBase(PolymorphicModel):\n na..."
},
{
"path": "tests/serializers.py",
"chars": 968,
"preview": "from rest_framework import serializers\n\nfrom rest_polymorphic.serializers import PolymorphicSerializer\n\nfrom tests.model..."
},
{
"path": "tests/settings.py",
"chars": 704,
"preview": "# -*- coding: utf-8\nfrom __future__ import absolute_import, unicode_literals\n\nimport django\nfrom django.core import mana..."
},
{
"path": "tests/test_serializers.py",
"chars": 5327,
"preview": "from django.core.exceptions import ImproperlyConfigured\n\nimport pytest\n\nfrom rest_polymorphic.serializers import Polymor..."
},
{
"path": "tox.ini",
"chars": 971,
"preview": "[tox]\nenvlist =\n py{35,36,37,38}-django{22}-drf{38,39,310,311},\n py{36,37,38}-django{30}-drf{310,311},\n py{36,3..."
}
]
About this extraction
This page contains the full source code of the apirobot/django-rest-polymorphic GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 41 files (36.9 KB), approximately 9.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.