Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you access a subbed-classed model from within the super-class model in the Django ORM?

Lets say I have model inheritance set up in the way defined below.

class ArticleBase(models.Model):
    title = models.CharField()
    author = models.CharField()

class Review(ArticleBase):
    rating = models.IntegerField()

class News(ArticleBase):
    source = models.CharField()

If I need a list of all articles regardless of type (in this case both Reviews and News) ordered by title, I can run a query on ArticleBase. Is there an easy way once I have an ArticleBase record to determine if it relates to a Review or a News record without querying both models to see which has the foreign key of the record I am on?

like image 896
Jason Christa Avatar asked Nov 23 '25 22:11

Jason Christa


1 Answers

I am assuming that all ArticleBase instances are instances of ArticleBase subclasses.

One solution is to store the subclass name in ArticleBase and some methods that return the subclass or subclass object based on that information. As multi-table inheritance defines a property on the parent instance to access a child instance, this is all pretty straight forward.

from django.db import models

class ArticleBase(models.Model):
    title = models.CharField()
    author = models.CharField()
    # Store the actual class name.
    class_name = models.CharField()

    # Define save to make sure class_name is set.
    def save(self, *args, **kwargs):
        self.class_name = self.__class__.__name__
        super(ArticleBase, self).save(*args, **kwargs)

    # Multi-table inheritance defines an attribute to fetch the child
    # from a parent instance given the lower case subclass name.
    def get_child(self):
        return getattr(self, self.class_name.lower())

    # If indeed you really need the class.
    def get_child_class(self):
        return self.get_child().__class__

    # Check the type against a subclass name or a subclass.
    # For instance, 'if article.child_is(News):'
    # or 'if article.child_is("News"):'.
    def child_is(self, cls):
        if isinstance(cls, basestring):
            return cls.lower() == self.class_name.lower()
        else:
            return self.get_child_class()  == cls

class Review(ArticleBase):
    rating = models.IntegerField()

class News(ArticleBase):
    source = models.CharField()

This is by no means the only way to go about this. It is, however, a pretty simple and straight forward solution. The excellent contrib contenttypes app and the generic module which leverages it offer a wealth of ways to do this, well, generically.

It could be useful to have the following in ArticleBase:

def __unicode__(self)
    return self.get_child().__unicode__()

In that case, be aware that failure to define __unicode__ in the subclasses, or calling __unicode__ on an instance of ArticleBase (one that has not been subclassed) would lead to an infinite recursion. Thus the admonition below re sanity checking (for instance, preventing just such an instantiation of ArticleBase directly).

Disclaimer:

This code is untested, I'm sure I've got a typo or two in there, but the basic concept should be sound. Production level code should probably have some sanity checking to intercept usage errors.

like image 178
Wayne Werner Avatar answered Nov 26 '25 13:11

Wayne Werner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!