Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django inheritance and polymorphism with proxy models

I'm working on a Django project that I did not start and I am facing a problem of inheritance.
I have a big model (simplified in the example) called MyModel that is supposed to represents different kind of items.

All the instance objects of MyModel should have the same fields but the methods behaviours varies a lot depending on the item type.

Up to this moment this has been designed using a single MyModel field called item_type.
Then methods defined in MyModel check for this field and perform different logic using multiple if:

def example_method(self):
    if self.item_type == TYPE_A:
        do_this()
    if self.item_type == TYPE_B1:
        do_that()

Even more, some of the sub-types have many things in common, so let's say the subtypes B and C represents a 1st level of inheritance. Then these types have sub-types being for example B1, B2, C1, C2 (better explained in the example code below).

I would say that's not the best approach to perform polymorphism.

Now I want to change these models to use real inheritance.

Since all submodels have the same fields I think multi-table inheritance is not necessary. I was thinking to use proxy models because only their behaviour should change depending on their types.

This a pseudo-solution I came up to:

ITEM_TYPE_CHOICES = (
    (TYPE_A, _('Type A')),
    (TYPE_B1, _('Type B1')),
    (TYPE_B2, _('Type B2')),
    (TYPE_C1, _('Type C1')),
    (TYPE_C2, _('Type C2')))


class MyModel(models.Model):
    item_type = models.CharField(max_length=12, choices=ITEM_TYPE_CHOICES)

    def common_thing(self):
        pass

    def do_something(self):
        pass


class ModelA(MyModel):
    class Meta:
        proxy = True

    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)
        self.item_type = TYPE_A

    def do_something(self):
        return 'Hola'


class ModelB(MyModel):
    class Meta:
        proxy = True

    def common_thing(self):
        pass

class ModelB1(ModelB):
    class Meta:
        proxy = True

    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)
        self.item_type = TYPE_B1

    def do_something(self):
        pass


class ModelB2(ModelB):
    class Meta:
        proxy = True

    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)
        self.item_type = TYPE_B2

    def do_something(self):
        pass

This might work if we already know the type of the object we are working on.
Let's say we want to instantiate a MyModel object of type C1 then we could simply instantiate a ModelC1 and the item_type would be set up correctly.

The problem is how to get the correct proxy model from the generic MyModel instances?

The most common case is when we get a queryset result: MyModel.objects.all(), all these objects are instances of MyModel and they don't know anything about the proxies.

I've seen around different solution like django-polymorphic but as I've understood that relies on multi-table inheritance, isn't it?

Several SO answers and custom solutions I've seen:

  • https://stackoverflow.com/a/7526676/1191416
  • Polymorphism in Django
  • http://anthony-tresontani.github.io/Python/2012/09/11/django-polymorphism/
  • https://github.com/craigds/django-typed-models
  • Creating instances of Django proxy models from their base class

but none of them convinced me 100%..

Considering this might be a common scenario did anyone came up with a better solution?

like image 465
Leonardo Avatar asked May 25 '18 08:05

Leonardo


People also ask

What is proxy model inheritance in Django?

The main Usage of a proxy model is to override the main functionality of existing Model. It is a type of model inheritance without creating a new table in Database. It always query on original model with overridden methods or managers.

Can we inherit models in Django?

Model Inheritance in Django works almost identically to the way normal class inheritance works in python. In this article we will revolve around how to create abstract base class in Django Models. Abstract Base Class are useful when you want to put some common information into a number of other models.

What is polymorphic model in Django?

Polymorphism is the ability of an object to take on many forms. Common examples of polymorphic objects include event streams, different types of users, and products in an e-commerce website. A polymorphic model is used when a single entity requires different functionality or information.

What is proxy inheritance?

Proxy model is a type of Django Model Inheritance, which you can extends from the base. class and you can add your own properties except fields. You can create, delete and. update instances of the proxy model and all the data will be saved as if you were using. the original (non-proxied) model.


2 Answers

I have few experience with model proxies so I can't tell if this would properly work (without bearking anything I mean) nor how complicated this might be, but you could use an item_type:ProxyClass mapping and override your model's queryset (or provide a second manager with custom queryset etc) that actually lookup this mapping and instanciates the correct proxy model.

BTW you may want at django.models.base.Model.from_db, which (from a very quick glance at the source code) seems to be the method called by QuerySet.populate() to instanciate models. Just overriding this method might possibly be enough to solve the problem - but here again it might also breaks something...

like image 103
bruno desthuilliers Avatar answered Oct 28 '22 13:10

bruno desthuilliers


When you use django-polymorphic in your base model, you'll get this casting behavior for free:

class MyModel(PolymorphicModel):
    pass

Each model that extends from it (proxy model or concrete model), will be casted back to that model when you do a MyModel.objects.all()

like image 20
vdboor Avatar answered Oct 28 '22 15:10

vdboor