Polymorphic relations between models

class Order(models.Model):
    date = models.DateField(auto_now_add=True)
    cost = models.IntegerField()
    user = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=150)
    last_name = models.CharField(max_length=150)
    email = models.EmailField(unique=True)
    phone = models.CharField(max_length=20)
    products = models.ManyToManyField(Product, through="OrderProduct")
    
    class Meta:
        abstract = True

    
class HouseOrder(Order):

    country = models.CharField(max_length=120, default='')
    city = models.CharField(max_length=120, default='') 
    street_name = models.CharField(max_length=120, default='')
    postcode = models.CharField(max_length=20, default='')  
    additional_address_details = models.CharField(max_length=500, default='')  
    phone = models.CharField(max_length=20)
    

class TakeawayOrder(Order):

    warehouse = models.OneToOneField(Warehouse, on_delete=models.CASCADE)

class OrderProduct(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    count = models.SmallIntegerField()

Order is an abstract model from which HouseOrder and TakeAwayOrder inherit, but I have created a many-to-many relationship to Product via the intermediate table OrderProduct, I want a reference to Order in OrderProduct to mean that either HouseOrder or TakeAwayOrder can be an Order, is there any way I can do this and would it be correct? Or is it still better to create separate intermediate tables for the successors?

Not in any way that I would consider worth doing.

This is one of those situations where you’re probably better off using multi-table inheritance instead of an abstract base class.

1 Like

Taks, how to solve the problem with many-to-many relationship in to Product via intermediate table? Just for each model write its own relationship and create its own intermediate model (also inheriting intermediate models from the main one)? It seems to me that in such a case abstract classes would be more useful (for the main Order model should not exist as a separate entity, for it will not be used anywhere) or Welcome to django-polymorphic’s documentation! — django-polymorphic 3.1 documentation. And what do you think about generic relationships in OrderProduct to all Order subtypes, or would that be redundant?

Yes it should. It’s the “target” of the many-to-many relationship with Product.

Bad idea.

Yes, generic relationships exist, but they are a lot less useful from a relational model perspective than most people typically think. Yes, they do serve a purpose, but only properly in those cases where there is no practical alternative.

I mean, I think it would be better to use an abstract parent Order to inherit the fields and not create a separate entity in the database (I will remove the relationships from it and move them to the child models), wouldn’t that be more correct? And could you tell me why generic relationships would be inappropriate?

No it wouldn’t. The situation you’re describing is effectively the prototypal case for multi-table inheritance.

See my reply at What are the drawbacks/risks with using Generic Relations? - #2 by KenWhitesell

Why? I don’t understand🥺

I guess I have to turn this question around.

Given that Generic Foreign Keys are an anti-pattern both from the perspective of normalization of the tables and from a data-integrity perspective, what’s your alternative?

Abstract models are a syntactical convenience. They don’t actually represent anything within the database, and so do not exist in the relational model.

The multi-table inheritance describes, at the database layer, the type of specialization or variance that you’re describing.

You have Product with a many-to-many relationship with Order. You have two subtypes of Order, but they’re still orders. The key here is that the relationship of Product to Order is with Order, and not just with (say) HouseOrder.

I’m happy to discuss this further, but I think I’d need to understand where your reluctance is coming from. What do you find uncomfortable with this solution?

Why create an unnecessary entity “Order” if it is not used anywhere, and I will remove its many-to-many relationship with Product, i.e. Order will only be used to avoid repeating itself in fields and directly show in the code that business logic implies that HouseOrder and TakeAwaYOrder have a common purpose. That’s why I think an abstract class would be more appropriate

I’m happy to discuss this further, but I think I’d need to understand where your reluctance is coming from. What do you find uncomfortable with this solution?

But it is going to be used. It expresses the common attributes of a HouseOrder and a TakeAwayOrder.

It allows you to manage that Order as the independent entity that it is, while allowing for the extensions to be separated based on their unique attributes.

If it’s an issue that you’re concerned about the extra table being created, that’s a misplaced concern. The purpose and the design goals of a relational database are to facilitate normalized table structures. An appropriate data model is going to tend toward more tables, not fewer.

Obviously, it’s your system - you can design it as you see fit. But if I were designing this, it’s a no-brainer. This is such an obvious solution to me that I can’t even think of a different solution worth considering.

As I understand it, I further operate on the child models, and the parent model just rolls around in the models? I didn’t understand the relationship of subtypes to parents and how they work together in multi-table inheritance.
Text from doc:

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)


class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)
All of the fields of Place will also be available in Restaurant, although the data will reside in a different database table. So these are both possible:

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter.
If you have a Place that is also a Restaurant, you can get from the Place object to the Restaurant object by using the lowercase version of the model name:

>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>

As I understand it, when I create Place and Restaraunt objects that have the same name and address fields, they are automatically linked or become one object?

I don’t understand the work with child and parent models. For example, I have a case where I need to create either an instance of the first child model or the second one, and then access them as needed. How would you implement it?

You would create one object, the child (in this case, Restaurant). You create the Restaurant with all the fields for both Restaurant and Place. Django creates the object, but the data is stored in two different tables.

You have multiple options based upon what it is that you are needing to do.

If you want to select from all objects, you can write your query on the parent class. If you need to check whether a child exists (or which child), you can either use a try / except on the reference or use the hasattr function to verify the existence of the child instance.

If you want to only select from one of the child classes, you can write your query on that child class.

1 Like

I got it and have already started using it, thank you very much. But after all these django quirks I get the impression that there is too much not obvious magic behind the hood, or is there just no point in my thinking and I should leave it to the django developers?

There’s an amazing amount of “magic under the hood” - that’s why I think Django is so great. The entire ORM is an incredible amount of work.

The purpose of any framework, library, language, etc, is to hide the ugliness of what’s really happening under the covers.

We use TCP libraries because we don’t want to handle raw network I/O.
We use Python because we don’t want to write code in X64 assembler. (At least I don’t)
We use the Python psycopg library because we don’t want to handle network-layer communication with a database server.
And we use Django because we don’t want to write SQL queries and code to create Python objects from the result sets. (And for many other reasons as well.)

Django provides features for you to use. If they’re documented, then they’re considered appropriate for you to use - and generally will make your life easier when used in the appropriate circumstances. And every single feature will seem like magic - until you reach the point where you understand what’s really happening.

So, should you give up on thinking? Absolutely not. But, a deep understanding is not necessary for use - and if you can understand something well enough to use it, then there’s no reason not to. That deeper understanding may (or may not!) come over time. (I’ve only been using Django for a bit more than 10 years now, and there are still parts that feel like magic to me.)

1 Like