Coder Social home page Coder Social logo

django-polymodels's Introduction

django-polymodels

A django application that provides a simple way to retrieve models type casted to their original ContentType.

Build Status https://coveralls.io/repos/charettes/django-polymodels/badge.svg?branch=master&service=github

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.

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.

Credits

django-polymodels's People

Contributors

allisson avatar charettes avatar filipweidemann avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-polymodels's Issues

Can't install from source without django already installed

~/django-polymodels$ pip install -e .
Obtaining file:///home/gavin/django-polymodels
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/home/gavin/django-polymodels/setup.py", line 4, in <module>
        from polymodels import __version__
      File "polymodels/__init__.py", line 3, in <module>
        from django.utils.version import get_version
    ImportError: No module named django.utils.version

select_subclasses doesn't interact well with .dates()

class Base(PolymorphicModel):
    date = models.DateField()

class Child1(Base):
    pass

Child1.objects.select_subclasses(Child1).dates('date', 'month')

Throws this error:

Traceback (most recent call last):
  File "/usr/lib/python3.4/code.py", line 90, in runcode
    exec(code, self.locals)
  File "<console>", line 1, in <module>
  File "django/db/models/query.py", line 234, in __repr__
    data = list(self[:REPR_OUTPUT_SIZE + 1])
  File "django/db/models/query.py", line 258, in __iter__
    self._fetch_all()
  File "django/db/models/query.py", line 1074, in _fetch_all
    self._result_cache = list(self.iterator())
  File "polymodels/managers.py", line 52, in iterator
    yield obj.type_cast()
AttributeError: 'datetime.date' object has no attribute 'type_cast'

I'm using select_subclasses as a way to add a filter on the content type as suggested in #21.

Multiple Inheritance breaks when creating model instances out of Django admin widget

Good morning,

I recently discovered a weird bug when dealing with multiple layers of inheritance on PolymorphicModels.

Let's consider the following structure:

---
app1.models
---

class LayoutProvider(PolymorphicModel):
    def __str__(self):
        return "Totally abstract base LayoutProvider that should never be instanciated."


class DefaultLayoutProvider(LayoutProvider):
    def __str__(self):
        return "Default Layout Provider"


class WidgetLayoutProvider(DefaultLayoutProvider):
    def __str__(self):
        return "Widget Layout Provider"

class PriviledgedLayoutProvider(WidgetLayoutProvider):
    def __str__(self):
        return "Priviledged Layout Provider"

and

---
app2.models
---

class Configuration(models.Model):
    layout_provider = models.ForeignKey(DefaultLayoutProvider, null=True, blank=True, related_name="config", on_delete=models.SET_NULL)

Now, whenever I create a LayoutProvider out of the native admin widget inside the Configuration model, I essentially create one with the type DefaultLayoutProvider, since this is the one referenced. But when I set the content_type to PriviledgedLayoutProvider and try to type_cast the created instance, I get this error:

providers.models.DefaultLayoutProvider.widgetlayoutprovider.RelatedObjectDoesNotExist: DefaultLayoutProvider has no widgetlayoutprovider.

This can be resolved by creating the PriviledgedLayoutProvider directly inside the correct admin widget, but I just don't understand why the type_cast doesn't work in that case. I thought the content_type is used for casting the instance to their correct identity, so why is it breaking there?

I am trying to find the root of this issue by myself as well, but I would really appreciate it if someone could come up with an easy explanation to why this is happening.

My current idea is that it has something to do with this line:

return attrgetter('.'.join(self.attrs))

But I might be wrong.

Thanks for taking the time to read this.

PolymorphicTypeField doesn't play well with migrations

Even if it's deconstructible it breaks the auto-detector as it uses Field.clone() instead of Field.deconstruct() (charettes/django-mutant#44)

Two things should be done here:

  • Convert the PolymorphicTypeField.validate_polymorphic_type assertion to a check
  • Investigate if the limit_choices_to override can be used in migrations as it relies on the subclasses_lookup attribute.

South migrations

Hey There

I'm trying to use this in an existing project as I like the approach.
I have a possible problem when trying to create a migration

When I run

manage.py schemamigration spa --auto

I get

     ? The field 'Venue.content_type' does not have a default specified, yet is NOT NULL
     ? Since you are adding this field, you MUST specify a default                      
     ? value to use for existing rows. Would you like to:                               
     ?  1. Quit now, and add a default to the field in models.py                        
     ?  2. Specify a one-off value to use for existing columns now                      
     ? Please select a choice:                                                          

Some guidance on what to do here would be appreciated.

Incorrect migration behavior

Hi @charettes
It seems that PolymorphicManager of django-polymodels does not work in migrations.

class PolymorphicManager(models.Manager.from_queryset(PolymorphicQuerySet)):

I came across this when I create data migration and the code like following

MyProxyModel = apps.get_model('app', 'Model')
MyProxyModel.objects.all()

returned me all objects, without filtering on content type.

How I see there is use_in_migrations option for model managers in Django.
Should that option be present in PolymorphicManager?

Explicitly state usage of MTI

Hi there!
Reading the following sentence:

Subclass PolymorphicModel, an abstract model class.

One can mistakenly believe that this package uses Django's Abstract base class approach to inheritance, when in fact it uses Multi-table inheritance.

I think it's important to explicitly state in the readme that this package uses (or only supports) MTI to implement inheritance.

Thanks,
Simon.

field with text type does not have pk

In[2]: from mutant.models import FieldDefinition
In[3]: FieldDefinition.objects.select_subclasses()
Out[3]: [<MultiPointFieldDefinition: MultiPointFieldDefinition object>, <BigIntegerFieldDefinition: BigIntegerFieldDefinition object>, <FloatFieldDefinition: FloatFieldDefinition object>, <BooleanFieldDefinition: BooleanFieldDefinition object>, <TextFieldDefinition: TextFieldDefinition object>]
In[4]: FieldDefinition.objects.select_subclasses()[0].pk
No pk
In[5]: FieldDefinition.objects.select_subclasses()[0]
Out[5]: <TextFieldDefinition: TextFieldDefinition object>
In[6]: FieldDefinition.objects.select_subclasses()[1].pk
But here it is!
Out[6]: 59
In[7]: FieldDefinition.objects.select_subclasses()[1]
Out[7]: <BooleanFieldDefinition: BooleanFieldDefinition object>

Renaming polymodel migrations in django 1.8

Hello!

i am using django-polymodels and I needed to rename a model which inherits from another model which inherits from PolymorphicModel. I renamed the model and ran makemigrations which properly detected the rename and made RenameModel migrations. However, when executing this, I got a warning saying that the content type of my old name is stale and needs to be deleted. When answering 'yes' to that, all model instances are deleted by cascading.

As I am not using content types myself, I figured it is related to the models being a polymodel since django-polymodels uses content types. I tried to circumvent this in many ways, for example by creating a new model to copy over all data in a data migration and then deleting the old model, but I also ran into problems with polymodels in the django 1.8 migrations. When loading models like so:

CombinedProduct = apps.get_model("product", "CombinedProduct")

and creating instances like CombinedProduct.create(...) or CombinedProduct(), I get an error saying that content type id null constraint failed. So it seems impossible to create new instances of models based on polymodels through the method advised in migrations.

Are these issues just me, or are they issues in the polymodels library? If not, how do I do proper rename migrations with django-polymodels?

Calling `.type_cast` sometimes raises `RelatedObjectDoesNotExist`

Sometimes, until the Django application is restarted, calling .type_cast on a base model raises RelatedObjectDoesNotExist.

Here's the stack trace:

Traceback (most recent call last):
  File "/usr/local/lib/python3.7/dist-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.7/dist-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/local/lib/python3.7/dist-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.7/dist-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/usr/local/lib/python3.7/dist-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/dist-packages/rest_framework/views.py", line 505, in dispatch
    response = self.handle_exception(exc)
  File "/usr/local/lib/python3.7/dist-packages/rest_framework/views.py", line 465, in handle_exception
    self.raise_uncaught_exception(exc)
  File "/usr/local/lib/python3.7/dist-packages/rest_framework/views.py", line 476, in raise_uncaught_exception
    raise exc
  File "/usr/local/lib/python3.7/dist-packages/rest_framework/views.py", line 502, in dispatch
    response = handler(request, *args, **kwargs)
  File "/opt/app/webpublisher/views.py", line 181, in get
    "fields": layout.fetch(code, current_lang=current_lang),
  File "/opt/app/core_storage/models.py", line 527, in fetch
    layout_element.type_cast().render(lysis, output, current_lang=current_lang)
  File "/usr/local/lib/python3.7/dist-packages/polymodels/models.py", line 112, in type_cast
    return accessor(self, with_prefetched_objects)
  File "/usr/local/lib/python3.7/dist-packages/polymodels/models.py", line 33, in __call__
    casted = self.attrgetter(obj)
  File "/usr/local/lib/python3.7/dist-packages/django/db/models/fields/related_descriptors.py", line 415, in __get__
    self.related.get_accessor_name()
core_storage.models.BaseLayoutElement.titlelayoutelement.RelatedObjectDoesNotExist: BaseLayoutElement has no titlelayoutelement.

If there is anything else needed to investigate the issue, let me know please.
Thank you.

django error specifies a many-to-many relation through model 'login.Freelancer_groups', which has not been installed.

I'm getting the following errors

login.Freelancer.groups: (fields.E331) Field specifies a many-to-many relation through model 'login.Freelancer_groups', which has not been installed.
login.Freelancer.user_permissions: (fields.E331) Field specifies a many-to-many relation through model 'login.Freelancer_user_permissions', which has not been installed.
message_board.Post.author: (fields.E301) Field defines a relation with the model 'auth.User', which has been swapped out.
HINT: Update the relation to point at 'settings.AUTH_USER_MODEL'.

Here is models.py

from django.contrib.auth.tests.custom_user import CustomUserManager
from django.db import models
from django.utils import timezone
from django.utils.http import urlquote
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from crm import settings

class FreelancerManager(BaseUserManager):
def create_user(self, name, skills, password=None):
if not name:
raise ValueError('Users must have a unique name ')

    user = self.model(
        name=self.name,
        skills=skills,
    )
    user.set_password(password)
    user.save(using=self._db)
    return user

def create_superuser(self, name, skills, password):
    """
    Creates and saves a superuser with the given email, date of
    birth and password.
    """
    user = self.create_user(
        name,
        password=password,
        skills=skills,
    )
    user.is_admin = True
    user.save(using=self._db)
    return user

class Freelancer(AbstractBaseUser, PermissionsMixin):
name = models.CharField(verbose_name='name',
max_length=20,
unique=True, )

field_of_interest = models.CharField(max_length=200)
skills = models.TextField()
experience = models.TextField()

is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)

objects = FreelancerManager()

USERNAME_FIELD = 'name'
REQUIRED_FIELDS = ['skills']

class Meta:
    db_table = 'auth_user'
    verbose_name = _('user')
    verbose_name_plural = _('users')

def get_absolute_url(self):
    return "/users/%s/" % urlquote(self.name)

def get_short_name(self):
    return self.name

def get_full_name(self):
    return self.name

def __str__(self):  # __unicode__ on Python 2
    return self.name

def has_perm(self, perm, obj=None):
    # "Does the user have a specific permission?"
    # # Simplest possible answer: Yes, always
    return True

def has_module_perms(self, applabel):
    # "Does the user have permissions to view the app `app_label`?"
    # Simplest possible answer: Yes, always
    return True

@property
def is_staff(self):
      return self.is_admin

Abstract polymorphic type definition to allow non-contenttype scenarios

One should be able to define the type of an object based on another kind of field.

For example:

  1. The first two characters of a string primary key might identify the type of the object;
  2. An additional field (say tableoid in the case of PostgreSQL inheritance) could identify the type of the object.

TypeError: unhashable type: 'SubclassAccessors' after upgrade to django 3.2b1

After upgrading to Django 3.2, the manage.py call raises an exception.
I'm not sure django-polymodels is the problem, but it's in the log.

Traceback
$ ./manage.py
Traceback (most recent call last):
  File "./manage.py", line 8, in <module>
    execute_from_command_line(sys.argv)
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/django/core/management/__init__.py", line 395, in execute
    django.setup()
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/django/apps/registry.py", line 114, in populate
    app_config.import_models()
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/django/apps/config.py", line 301, in import_models
    self.models_module = import_module(models_module_name)
  File "/home/user/.pyenv/versions/3.6.6/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/polymodels/models.py", line 99, in <module>
    class BasePolymorphicModel(models.Model):
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/django/db/models/base.py", line 161, in __new__
    new_class.add_to_class(obj_name, obj)
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/django/db/models/base.py", line 326, in add_to_class
    value.contribute_to_class(cls, name)
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/polymodels/models.py", line 58, in contribute_to_class
    class_prepared.connect(self.class_prepared_receiver, weak=False)
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/django/dispatch/dispatcher.py", line 96, in connect
    if not func_accepts_kwargs(receiver):
  File "/home/user/.pyenv/versions/3.6.6/envs/project/lib/python3.6/site-packages/django/utils/inspect.py", line 45, in func_accepts_kwargs
    p for p in _get_signature(func).parameters.values()
TypeError: unhashable type: 'SubclassAccessors'

May relate to django/django#13151

Problem with Django 1.8 migrations

I create a very simple models.py with polymodels (shown below). Running a makemigrations (initial - first time run) with Django 1.8 results in no migration being generated.

If you create a proxy model of MainModel then the proxy model will get generated in the migration but MainModel never gets an initial migration created.

Not sure if this is an issue with polymodels or django-migrations, but only happens with polymodels.

# -------
from django.db import models
from polymodels.models import PolymorphicModel


class Base1(PolymorphicModel):
    base_name = models.CharField(max_length=60)


class MainModel(Base1):
    main_name = models.CharField(max_length=60)

m2m relation through a polymodel cannot use `string` definiton

I am testing using django master and 1.8, and latest polymodels (1.2.3). The Organization model has a m2m relation to a polymodel based model Subscription:

This won't work:

class Organization(models.Model)
    subscriptions = models.ManyToManyField('billing.Plan',
                                           related_name='subscribes',
                                           through='billing.Subscription')

Throwing error:

ERRORS:
organization.Organization.subscriptions: (fields.E331) Field specifies a many-to-many relation through model 'billing.Subscription', which has not been installed.

But when i changed to:

class Organization(models.Model)
    subscriptions = models.ManyToManyField('billing.Plan',
                                           related_name='subscribes',
                                           through=Subscription)

It works. makemigrations creates the schema migrations

Before the the Subscription using PolymorphicModel (i've used regular models.Model before), which as i can remember, didn't throw any error when either using 'string based' method when specifying m2m through, or the import method one

Thank you

When querying for subclasses, put a where clause on content_type_id?

I have these models:

class Base(PolymorphicModel):
    date = models.DateField()

    class Meta:
        unique_together = [
            ('content_type', 'date')
        ]

class Child1(Base):
    pass

class Child2(Base):
    pass

I did the query Child1.objects.order_by('date'), expecting that the index on (content_type_id, date) would be used. This is not the case, because the content_type_id is not filtered to Child1's content type (where clause + order by must be a prefix of the index for it to be used). The query Child1.objects.filter(content_type=ContentType.objects.get_for_model(Child1)).order_by('date') is much more efficient, because now the index can be used.

Is this something that polymodels could do automatically? I don't think it will negatively effect anything even if there's not an index that could be used.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.