master 9d920eb91ef1
41 files
36.9 KB
9.7k tokens
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.

Copied to clipboard!