15

I'm currently working on a model in Django involving one model that can have a variety of different traits depending on what kind of object it is. So, let's say we have a model called Mammal, which can either be an Elephant or a Dolphin (with their own traits "tusk_length" and "flipper_length" respectively).

Basic OOP principles shout "polymorphism", and I'm inclined to agree. But, as I'm new to Django, I first want to know whether or not it is the best way to do so in Django. I've heard of plenty of examples of and some people giving their preferences toward singular giant models

I've already tried using GenericForeignKeys as described here: How can I restrict Django's GenericForeignKey to a list of models?. While this solution works beautifully, I don't like the inability to filter, and that the relationship is only one way. That is, while you can get a Dolphin from a Mammal object, you can't get the Mammal object from the Dolphin.

And so, here are my two choices:

Choice A:

from django.db import models
class Mammal(models.Model):
    hair_length = models.IntegerField()
    tusk_length = models.IntegerField()
    flipper_length = models.IntegerField()
    animal_type = models.CharField(max_length = 15, choices = ["Elephant", "Dolphin"]

Choice B:

from django.db import models
class Mammal(models.Model):
    hair_length = models.IntegerField()

class Elephant(Mammal):
    tusk_length = models.IntegerField()

class Dolphin(Mammal):
    flipper_length = models.IntegerField()

Choice B, from what I understand, has the advantage of nicer code when querying and listing all Elephants or Dolphins. However, I've noticed it's not as straightforward to get all of the Elephants from a list of Mammals (is there a query for this?) without putting animal_type in the class, with default being dependent on the class.

This leads to another problem I see with polymorphism, which won't come up in this example above or my application, but is worth mentioning is that it would be difficult to edit a Dolphin object into an Elephant without deleting the Dolphin entirely.

Overall, is there any general preference, or any big reason I shouldn't use polymorphism?

Community
  • 1
  • 1
limasxgoesto0
  • 4,555
  • 8
  • 31
  • 38

2 Answers2

11

My recommendation, in general with database design, is to avoid inheritance. It complicates both the access and updates.

In Django, try using an abstract class for your base model. That means a db table will not be created for it. Its fields/columns will be auto-created in its child models. The benefit is: code reuse in Django/Python code and a simple, flat design in the database. The penalty is: it's more work to manage/query a mixed collection of child models.

See an example here: Django Patterns: Model Inheritance

Alternatively, you could change the concept of "Mammal" to "MammalTraits." And include a MammalTraits object inside each specific mammal class. In code, that is composition (has-a). In the db, that will be expressed as a foreign key.

Bill Paetzke
  • 13,332
  • 6
  • 44
  • 46
  • Now, a main problem I see with using an abstract class (in my case, not in general) is there's an already existing and populated Mammal table in production. Changing it to "MammalTraits" could work, but I want to enforce a one-to-one relationship between one MammalTrait and one of the Dolphin/Elephant objects. This is of course simple in theory, but I would have to enforce there being only one Dolphin OR one Elephant, which I suppose isn't too difficult (overwriting the save() method for validation?). – limasxgoesto0 May 01 '13 at 16:57
  • Hm. I do see your point about the multiple child models though. Filtering all mammals by dolphin would be a bit of a hassle... – limasxgoesto0 May 01 '13 at 18:11
  • 1
    Revisited this - and I actually ended up using an abstract class as a base model for something new I was doing. So conclusion for me is to use one of the two things you suggest here for a new model, but if it's an existing model that needs to become polymorphic with existing live data, it might be safer to just saturate it with all of the fields you need. – limasxgoesto0 May 09 '13 at 22:53
2

We ended up going with a large table with a lot of usually-empty columns. Our reasoning was that (in this case) our Mammal table was all we'd be querying over, and there was no (intuitive) way to filter out by certain types of Mammals besides manually checking whether they had a "dolphin" or "elephant" object, which then threw an error if they didn't. Even looking for the type of an object returned from a query that was definitely an Elephant still returned "Mammal". It would be hard to extend any Pythonic workarounds to writing pure SQL, which one of our data guys does regularly.

limasxgoesto0
  • 4,555
  • 8
  • 31
  • 38