views:

261

answers:

3

Hi folks,

I think this is a bit tricky, at least for me. :)

So I have 4 models Person, Singer, Bassist and Ninja.

Singer, Bassist and Ninja inherit from Person.


The problem is that each Person can be any of its subclasses.

e.g. A person can be a Singer and a Ninja. Another Person can be a Bassist and a Ninja. Another one can be all three.

How should I organise my models?


Help would be much appreciated!

+10  A: 

Multiple inheritance doesn't work well with databases (and your Django models do need to map down to a database in the end), and inheritance is often a bad way to model "roles" (because people's roles do change). I would have Singer, Bassist and Ninja as "roles", not as subclasses of Person, and connect them via foreign keys:

class Singer(models.Model):
    person = models.ForeignKey('Person')
    # ...

class Person(models.Model):
    # ...
Alex Martelli
@Alex: instead of the roles together through multiple inheritance, would there be another way to be able to merge them together? I feel uneasy about using FKs to pair up the roles to the Person class, as the Person's information depends entirely on who they are. Maybe I should just give this some more thought, thank you very much Alex!!
RadiantHex
This is the way to go, unless "singer" is nothing more than a descriptor.... in which case, simply adding `is_singer = models.BooleanField()` to the `Person` model will make for a simplified model and quicker queries
Daniel
these are roles, not subclasses. Subclasses in traditional class-based _single inheritance_ taxonomies are mutually exclusive. For example, Dog, Cat, and Bunny can all inherit from Mammal, but there is no animal that is simultaneously a Dog *and* a Bunny. Language that support multiple inheritance are more forgiving about such things, but in this case I agree with Alex that using inheritance would be a modeling mistake, even if it easily worked.
Steven A. Lowe
@Radiant, what's the problem with using `thisperson.singer_set.all()` and the like?
Alex Martelli
@Daniel thanks for that. Btw a Signer has more fields and method overriding, and the way the field data is generated entirely depends on it. What about merging different Person models together some way? What if I have a Master and Slave Person models merged together through a ManyToMany field?
RadiantHex
@Alex: problem is that I have probably used a wrong example, as in reality the subclasses are not roles, but actually determine their shape and form. A Signer and a Ninja would just have a name in common, all the rest is entirely dependent on what they are.
RadiantHex
@Radiant, looks like a Singer and a Ninja may have in common a _model_ and an _identity_ -- that's way too little to pay the enormous price of foisting inheritance onto a relational substrate, much less when compounded by the huge further complication of *multiple* inheritance (!). Just give both models a name and a personID property, forget the person model, and -- if you need it for advanced business logic -- an *application-level* Person class (*not* a Model!) which deals with db fetches and stores for 1+ actual model instances as needed, plus, the behavior parts (Strategy DP suggested).
Alex Martelli
@Alex: thank you very much Alex, your answers are always incredibly informative and helpful! You made my day.
RadiantHex
+2  A: 

I agree about the roles solution, as depicted by Alex. What you have is not different subclasses of persons. You have different roles a person can have.

But I hear you say: "hey, the ninja can have a property "numberOfStars", while a singer can have a property "highestNote". Same as for the interface: a ninja can have the method throwStar() and disappear(), while a singer can have sing() and getWasted(), and the bass player can have goFunky() and slapPop()

What you have here is a case where your data model needs a very loose schema. So loose that, in fact, you have no schema at all. If the singer decides to take the bass and improvise a tune, that's fine. If he wants to act as a ninja, and you call throwStar, it will return an error, because he has no stars, but you could in principle, assign stars to a singer and make him throw stars.

What you are venturing in is the world of ontologies, rather than schemas. You have a resource, which is "something" and this something can be some type, have some properties, etc. Presence of some properties can infer the type, or presence of some type can infer other types. You cannot describe this information easily with the simple django data model. What you would need is a context-aware, inferenced graph store, such as AllegroGraph, or implement your hacked up solution using rdflib.

Stefano Borini
You are mixing multiple issues here. Singers and ninjas may require different attributes, but in no way does that imply that you suddenly have a completely open-ended dynamic schema. The obvious and simple way to represent knowledge that is peculiar to a type of person is to have a singer table that holds facts about singers, a corresponding ninja table, and so on, in addition to the person table. All tables would have a PK of person_id, with FKs as appropriate.
Marcelo Cantos
+1  A: 

In principle you can do something like the following:

class Role(models.Model):       
     ......

class Ninja(Role):
     .......

class Person(models.Model):
      roles = models.ManyToManyField(Role)

But then you run in to the problem that Person.roles.objects.all() can only give you instances of Role. So you need a method to casts each instance of Role to the a suitable subclass such as Ninja or Pirate. Here is a link to a thread that discusses this problem.

http://groups.google.com/group/django-users/browse_thread/thread/f4241bc16455f92d/7268c3f7bca6b046

So in short Alex and Stefano have given more useful answers than me.

niels