Search code examples
pythondjangodjango-models

Django Models - trying to get the "best of both worlds" when it comes to copying a model vs subclassing it


Let's say I have a base class (Django model) called Character that looks something like

class Character(models.Model):
    strength = models.blabla
    dex = models.blabla
    ...

I can set that up with its attributes and hook it up to Admin and everything is lovely.

Then I decide I want a NonPlayerCharacter model, which has everything the Character model has, plus a new field or two. I can think of at least two ways to do this, each with pros and cons.

Option 1:

# The basic obvious stupid-simple answer is just to copy the model and add the fields

class NonPlayerCharacter(models.Model):
    strength = models.blabla
    dex = models.blabla
    ...
    faction = models.blabla

Pros: Everything is kept very simple and easy to manage, and if the models need to diverge in the future, that's easy too.

Cons: Obvious code duplication and keeping universal changes in sync. If I change a stat or add a method on one, I've got to duplicate it on the other. If I add another type of Character, like Mob, well then it triples the upkeep to keep the necessary parts in sync.

Option 2:

# The next most obvious solution is to subclass Character

class NonPlayerCharacter(Character):
    faction = models.blabla

Pros: I don't have to worry about keeping my classes in sync if I add something or make a change. Each is only concerned with its own NEW attributes and methods.

Cons: Everywhere I query for Character will also, by default, pull every subclass of Character. I'm (somewhat) aware of how to filter that out on a query-by-query basis, but it sure would be nice if Character.objects just got me characters and left the rest to queries for that particular model/subclass.

Is there a handy method to get the best of both worlds without filtering Character each time, or am I just asking something unreasonable?


Solution

  • Typically you can make an abstract base model, like:

    class BaseCharacter(models.Model):
        strength = models.blabla
        dex = models.blabla
        …
    
        class Meta:
            abstract = True
    
    
    class Character(BaseCharacter):
        pass
    
    
    class NonPlayerCharacter(BaseCharacter):
        faction = models.blabla