Django inheritance and polymorphism with proxy models

2024/10/12 22:26:20

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):passdef do_something(self):passclass ModelA(MyModel):class Meta:proxy = Truedef __init__(self, *args, **kwargs):super().__init__(self, *args, **kwargs)self.item_type = TYPE_Adef do_something(self):return 'Hola'class ModelB(MyModel):class Meta:proxy = Truedef common_thing(self):passclass ModelB1(ModelB):class Meta:proxy = Truedef __init__(self, *args, **kwargs):super().__init__(self, *args, **kwargs)self.item_type = TYPE_B1def do_something(self):passclass ModelB2(ModelB):class Meta:proxy = Truedef __init__(self, *args, **kwargs):super().__init__(self, *args, **kwargs)self.item_type = TYPE_B2def 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?

Answer

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()

https://en.xdnf.cn/q/69604.html

Related Q&A

L suffix in long integer in Python 3.x

In Python 2.x there was a L suffix after long integer. As Python 3 treats all integers as long integer this has been removed. From Whats New In Python 3.0:The repr() of a long integer doesn’t include …

Custom Colormap

I want to plot a heatmap with a custom colormap similar to this one, although not exactly.Id like to have a colormap that goes like this. In the interval [-0.6, 0.6] the color is light grey. Above 0.6,…

Whats the point of @staticmethod in Python?

Ive developed this short test/example code, in order to understand better how static methods work in Python.class TestClass:def __init__(self, size):self.size = sizedef instance(self):print("regul…

logical or on list of pandas masks

I have a list of boolean masks obtained by applying different search criteria to a dataframe. Here is an example list containing 4 masks: mask_list = [mask1, mask2, mask3, mask4]I would like to find th…

How to view the implementation of pythons built-in functions in pycharm?

When I try to view the built-in function all() in PyCharm, I could just see "pass" in the function body. How to view the actual implementation so that I could know what exactly the built-in f…

How to gracefully fallback to `NaN` value while reading integers from a CSV with Pandas?

While using read_csv with Pandas, if i want a given column to be converted to a type, a malformed value will interrupt the whole operation, without an indication about the offending value.For example, …

Python - object layout

can somebody describe the following exception? What is the "object layout" and how it is defined? ThanksTraceback (most recent call last):File "test_gui.py", line 5, in <module…

Using Tor proxy with scrapy

I need help setting up Tor in Ubuntu and to use it within scrapy framework.I did some research and found out this guide:class RetryChangeProxyMiddleware(RetryMiddleware):def _retry(self, request, reaso…

Best practice for structuring module exceptions in Python3

Suppose I have a project with a folder structure like so./project__init__.pymain.py/__helpers__init__.pyhelpers.py...The module helpers.py defines some exception and contains some method that raises th…

How can you read a gzipped parquet file in Python

I need to open a gzipped file, that has a parquet file inside with some data. I am having so much trouble trying to print/read what is inside the file. I tried the following: with gzip.open("myFil…