djangodjango-modelsmulti-table-inheritance

Elegantly spread single Django model across multiple tables?


There's plenty of document and discussion for having multiple models share fields in a parent class/table using "concrete" or "multi-table" inheritance:

class Place(models.Model):
    ...

class Restaurant(Place):
    ...

However, I can't find much on the inverse use-case: splitting the fields of a single model across multiple tables to save the cost of loading wide columns except when you actually need them.

Consider the following scenario:

class Person_Profile(models.Model):
    ...lots of fields...

class Person_Preferences(models.Model):
    ...lots of fields...

class Person(Person_Profile, Person_Preferences):
    ...small set of core fields...

What works:

The only thing I'm missing is how to elegantly control when Django loads the fields from the parent table, since currently, loading a Person with p = Person.objects.first() results in all three tables being joined and selected by default.

UPDATE

I went looking for how Django let's you express selecting a subset of fields and arrived at the QuerySet methods defer() and only(), along with the suggestion there about using an unmanaged model as a facade to conveniently load a subset of fields:

class Person(models.Model):
    name = models.CharField(max_length=30)
    pref1 = models.BooleanField(default=False)
    profile1 = models.CharField(max_length=30, null=True)

class SkinnyPerson(models.Model):
    name = models.CharField(max_length=30)

    class Meta:
        managed = False
        db_table = 'myapp_person'

Haven't reach a conclusion yet, but this method has a lot of appeal right now.


Solution

  • Splitted model - This is a good solution only if you don't understand the DataManagers/Queryset idea.

    The splitting fields on the models is good approach, if you use Meta.abstract = True. if you create two real сlasses only to add together all fields in child class - you loose many time with joins on every ask in DB.

    more here: https://docs.djangoproject.com/en/4.1/topics/db/models/#abstract-base-classes

    In QuerySet you can define many methods. After that you can chain methods together.

    class Person_Profile(models.Model):
        Meta:
            abstract = True
    
        ...lots of fields...
    
    class Person_Preferences(models.Model):
        Meta:
            abstract = True
    
        ...lots of fields...
    
    class Person(Person_Profile, Person_Preferences):
        ...small set of core fields...
        objects = PersonQueryset.as_manager()  # in old django PersonDataManager()
    

    How might PersonDataManager or PersonQuerySet look like:

    class PersonQuerySet(QuerySet):
    
        def onlySpecialFields(self, *args, **kwargs):
            return self.only(*my_special_only_list)
    
        def deferSpecialFields(self, *args, **kwargs):
            return self.defer(*my_special_defer_list)
    
        def skinnyPersons(self, *args, **kwargs):
            return self.only('name')
    

    How you can use it:

    SkinnyPersons_List = Person.objects.filter(name=something).skinnyPersons()
    # after that
    FirstSkinnyPerson = SkinnyPersons_List.first()
    
    # somethere in code
    LastSkinnyPerson = Person.objects.skinnyPersons().last()
    

    Pros:

    cons: