jazzband / django-polymorphic

Improved Django model inheritance with automatic downcasting
https://django-polymorphic.readthedocs.io
Other
1.66k stars 280 forks source link

Using non-polymorphic mixins breaks _base_manager #534

Open CelestialGuru opened 1 year ago

CelestialGuru commented 1 year ago

Here are three polymorphic models, each with different mixins:

class ModelMixin(models.Model):
    class Meta:
        abstract = True

    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)

class PolymorphicMixin(PolymorphicModel):
    class Meta:
        abstract = True

    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)

class Foo(PolymorphicModel):
    pass

class Bar(PolymorphicMixin, PolymorphicModel):
    pass

class Baz(ModelMixin, PolymorphicModel):
    pass

Both Foo and Bar have a base_manager of PolymorphicManager:

print(type(Foo._base_manager))  # <class 'polymorphic.managers.PolymorphicManager'>
print(type(Bar._base_manager))  # <class 'polymorphic.managers.PolymorphicManager'>

However, for Baz, its base_manager is not PolymorphicManager:

print(type(Baz._base_manager))  # <class 'django.db.models.manager.Manager'>

The reason I would use a non-polymorphic mixin is because I want to use it on multiple models, polymorphic and non-polymorphic alike. The only solution I've found is to duplicate my model mixin code, just swapping out models.Model with PolymorphicModel. I'd rather have only one mixin, do you know if this is possible?

WalnussPower commented 1 year ago

Does your mixin class need to inherit from models.Model?

CelestialGuru commented 1 year ago

Only insofar that I would like features available for both non-polymorphic and polymorphic models. I strive to keep my code DRY and currently I have to define the mixin twice, once for polymorphic and non-polymorphic models. The following is an attempt to keep things DRY, but it does not work:

class BaseMixin:
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)

class ModelMixin(BaseMixin, models.Model):
    class Meta:
        abstract = True

class PolymorphicMixin(BaseMixin, PolymorphicModel):
    class Meta:
        abstract = True

If you you check the fields defined on these models you'll get:

print(ModelMixin._meta.get_fields())  # (<django.db.models.fields.BigAutoField: id>,)
print(PolymorphicMixin._meta.get_fields())  # (<django.db.models.fields.BigAutoField: id>,)

As you can see created_at and modified_at are not on these models so this approach doesn't work.

If PolymorphicModel could handle Model mixins, then everything would be fine. Create a single mixin (it would inhert Model), use it on Model and PolymorphicModel classes alike and enjoy DRY code.