views:

31

answers:

2

I'm setting up a data model in django using multiple-table inheritance (MTI) like this:

class Metric(models.Model):
    account = models.ForeignKey(Account)
    date = models.DateField()
    value = models.FloatField()
    calculation_in_progress = models.BooleanField()
    type = models.CharField( max_length=20, choices= METRIC_TYPES ) # Appropriate?

    def calculate(self):
        # default calculation...

class WebMetric(Metric):
    url = models.URLField()

    def calculate(self):
        # web-specific calculation...

class TextMetric(Metric):
    text = models.TextField()

    def calculate(self):
        # text-specific calculation...

My instinct is to put a 'type' field in the base class as shown here, so I can tell which sub-class any Metric object belongs to. It would be a bit of a hassle to keep this up to date all the time, but possible. But do I need to do this? Is there some way that django handles this automatically?

When I call Metric.objects.all() every objects returned is an instance of Metric never the subclasses. So if I call .calculate() I never get the sub-class's behavior.

I could write a function on the base class that tests to see if I can cast it to any of the sub-types like:

def determine_subtype(self):
    try:
        self.webmetric
        return WebMetric
    except WebMetric.DoesNotExist:
        pass
    # Repeat for every sub-class

but this seems like a bunch of repetitious code. And it's also not something that can be included in a SELECT filter -- only works in python-space.

What's the best way to handle this?

A: 

But do I need to do this?

Never. Never. Never.

Is there some way that django handles this automatically?

Yes. It's called "polymorphism".

You never need to know the subclass. Never.

"What about my WebMetric.url and my TextMetric.text attributes?"

What will you do with these attributes? Define a method function that does something. Implement different versions in WebMetric (that uses url) and TextMetric (that uses text). That's proper polymorphism.


Please read this: http://docs.djangoproject.com/en/1.2/topics/db/models/#abstract-base-classes

Please make your superclass abstract.

Do NOT do this: http://docs.djangoproject.com/en/1.2/topics/db/models/#multi-table-inheritance

You want "single-table inheritance".

S.Lott
I didn't think that worked. If I call Metric.objects.all() will it actually return subclass objects? I thought it would only return Metric objects. See the third code snippet in http://docs.djangoproject.com/en/dev/topics/db/models/#multi-table-inheritance where they explicitly cast the object to its subclass.
Leopd
I think what S. Lott means is that you shouldn't do anything that is specific to a 'WebMetric' or 'TextMetric' on a 'Metric'. Define all the functions that you need on 'Metric', override them in the subclasses, and then just call them on 'Metric'. You probably have a problem of design if you need to specifically know what subclass an object is from. Manipulate EITHER 'Metric' and therefore only the common functionalities OR one of the subclasses. Please describe in more details what you are trying to do, then we can help you on the design !
sebpiq
S.Lott's idea is nice in theory but it just doesn't work in Django. I just checked. When I pull the objects from the database using Metric.objects, they come back as Metric objects, not as instances of the subclasses. So polymorphism doesn't actually work here. If I define a method in the base class and override it in the sub-classes, the sub-classes' method won't get called because I don't actually have an instance of the subclass.
Leopd
Avoid "Multi-Table Inheritance". Read this: http://docs.djangoproject.com/en/1.2/topics/db/models/#model-inheritance. Make your superclass abstract.
S.Lott
Why should I avoid MTI? Because polymorphism doesn't work? That's what I'm looking for an answer to. An abstract base isn't appropriate because very often I want the base class without the extra data. An abstract base class is the same in terms of pulling objects from the db - I still need to know what class they are in order to execute the right query to find them.
Leopd
+1  A: 

While it might offend some people's sensibilities, the only practical way to solve this problem is to put either a field or a method in the base class which says what kind of object each record really is. The problem with the method you describe is that it requires a separate database query for every type of subclass, for each object you're dealing with. This could get extremely slow when working with large querysets. A better way is to use a ForeignKey to the django Content Type class.

@Carl Meyer wrote a good solution here: http://stackoverflow.com/questions/929029/how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-name/929982#929982

Single Table Inheritance could help alleviate this issue, depending on how it gets implemented. But for now Django does not support it: http://stackoverflow.com/questions/241250/single-table-inheritance-in-django so it's not a helpful suggestion.

muudscope
Thanks! Using ContentType is nice and general.
Leopd