django-polymodels
django-polymodels copied to clipboard
Polymorphic models implementation for django.
################# django-polymodels #################
A django application that provides a simple way to retrieve models type casted
to their original ContentType
.
.. image:: https://github.com/charettes/django-polymodels/workflows/Test/badge.svg :target: https://github.com/charettes/django-polymodels/actions :alt: Build Status
.. image:: https://coveralls.io/repos/charettes/django-polymodels/badge.svg?branch=master&service=github :target: https://coveralls.io/github/charettes/django-polymodels?branch=master
Installation
pip install django-polymodels
Make sure 'django.contrib.contenttypes'
and 'polymodels'
are in
your INSTALLED_APPS
::
INSTALLED_APPS += ('django.contrib.contenttypes', 'polymodels')
Usage
Subclass PolymorphicModel
, an abstract model class.
::
from django.db import models
from polymodels.models import PolymorphicModel
class Animal(PolymorphicModel):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Mammal(Animal):
pass
class Dog(Mammal):
pass
class Reptile(Animal):
pass
class Snake(Reptile):
class Meta:
proxy = True
Objects are created the same way as usual and their associated ContentType
is saved automatically:
animal = Animal.objects.create(name='animal') mammal = Mammal.objects.create(name='mammal') reptile = Reptile.objects.create(name='reptile') snake = Snake.objects.create(name='snake')
To retreive type casted instances from the Animal.objects
manager you just
have to use the select_subclasses
method.
Animal.objects.select_subclasses() [<Animal: animal>, <Mammal: mammal>, <Reptile: reptile>, <Snake: snake>]
You can also retreive a subset of the subclasses by passing them as arguments to
select_subclass
.
Animal.objects.select_subclasses(Reptile) [<Reptile: reptile>, <Snake: snake>]
Or directly from subclasses managers.
Reptile.objects.select_subclasses(Snake) [<Snake: snake>]
Note that you can also retrieve original results by avoiding the
select_subclasses
call.
Animal.objects.all() [<Animal: animal>, <Animal: mammal>, <Animal: reptile>, <Animal: snake>]
It's also possible to select only instances of the model to which the
manager is attached by using the exclude_subclasses
method.
Mammal.objects.all() [<Mammal: mammal>]
Each instance of PolymorphicModel
has a type_cast
method that knows how
to convert itself to the correct ContentType
.
animal_snake = Animal.objects.get(pk=snake.pk) <Animal: snake> animal_snake.type_cast() <Snake: snake> animal_snake.type_cast(Reptile) <Reptile: snake>
If the PolymorphicModel.content_type
fields conflicts with one of your
existing fields you just have to subclass
polymodels.models.BasePolymorphicModel
and specify which field polymodels
should use instead by defining a CONTENT_TYPE_FIELD
attribute on your model.
This field must be a ForeignKey
to ContentType
.
::
from django.contrib.contenttypes.models import ContentType
from django.db import models
from polymodels.models import BasePolymorphicModel
class MyModel(BasePolymorphicModel):
CONTENT_TYPE_FIELD = 'polymorphic_ct'
polymorphic_ct = models.ForeignKey(ContentType)
How it works
Under the hood select_subclasses
calls seleted_related
to avoid
unnecessary queries and filter
if you pass some classes to it. On queryset
iteration, the fetched instanced are converted to their correct type by calling
BasePolymorphicModel.type_cast
. Note that those lookups are cached on class
creation to avoid computing them on every single query.
Note of the author
I'm aware there's already plenty of existing projects tackling the whole
model-inheritance-type-casting-thing such as django-polymorphic
. However
I wanted to implement this feature in a lightweight way: no
__metaclass__
or __init__
overrides while using django's public API as
much as possible. In the end, this was really just an extraction of
django-mutant
's own mecanism of handling this since I needed it as a
standalone app for another project.
.. _django-polymorphic: https://github.com/chrisglass/django_polymorphic .. _django-mutant: https://github.com/charettes/django-mutant
Contribute
If you happen to encounter a bug or would like to suggest a feature addition
please file an issue
_ or create a pull request
_ containing tests.
.. _file an issue: https://github.com/charettes/django-polymodels/issues .. _create a pull request: https://github.com/charettes/django-polymodels/pulls
Credits
- Inspired by a
post of Jeff Elmores
_.
.. _post of Jeff Elmores: http://jeffelmore.org/2010/11/11/automatic-downcasting-of-inherited-models-in-django/