0

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.

odigity
  • 7,568
  • 4
  • 37
  • 51

1 Answers1

1

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
Maxim Danilov
  • 2,472
  • 1
  • 3
  • 8
  • A great answer - thanks. Admittedly, querysets and managers are two areas I'm still weak on. My studies so far have been focused more on model definitions and migration-related challenges. – odigity Aug 29 '22 at 21:42
  • 1
    you are welcome. i have a talks on django con EU 2022 and on django con US 2022. We can talk there – Maxim Danilov Aug 29 '22 at 21:49
  • Only feature I still long for is a QS method that can undo a previous call to defer() - like undefer(). That way I have the option of making the default behavior the deferring of large fields, but have a QS method that undoes the defer when you want the full model. – odigity Aug 29 '22 at 22:38
  • 1
    undefer = defer(None,) more here: django.db.models.query.py row 1223 – Maxim Danilov Aug 29 '22 at 22:51
  • That's perfect, thanks! PS-I checked, and it is indeed mentioned in my version of the docs, I must have missed it: https://docs.djangoproject.com/en/3.2/ref/models/querysets/#defer – odigity Sep 01 '22 at 14:03