Home > database >  Elegantly spread single Django model across multiple tables?
Elegantly spread single Django model across multiple tables?

Time:08-30

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:

  • When you create a Person, the other two objects are automatically created and linked for you.
  • You can access profile and preference fields directly on Person. (person.some_field)

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.

CodePudding user response:

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:

  • content type is always the same
  • you don't have problem with generic objects
  • you work with the same ._meta, .app_label and .model_name
  • you don't have any new model, which you are dont need.
  • you use idea of django querysets

cons:

  • not found
  • Related