Opened 5 years ago

Last modified 5 years ago

#30844 closed New feature

Add after_db_init() hook method to model — at Version 4

Reported by: Robert Singer Owned by: nobody
Component: Database layer (models, ORM) Version: dev
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Robert Singer)

I've encountered a need in my personal projects, and when writing django-lifecycle, to hook into the moment when a model has been fully initialized from the database.

Overriding the model's init method does NOT work here because the select_related relationships have not yet been added in/cached in the model's FK fields. It would be useful to do things right after the model is fully loaded and initialized from the database. For example, if you have a "type" foreign key field, you may want to apply some polymorphic behavior based on the value of that field. Doing this in __init__ will cause a n+1 query explosion if you load multiple models and iterate over the QuerySet.

Current Problem

This will cause an n+1 issue when iterating a QuerySet:

class CatMixin(object):
    def greet(self):
         return "Meow"

class DogMixin(object):
    def greet(self):
         return "Woof"

class PolymorphicModel(models.Model):
   type = models.ForeignKey(PetType)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
       # another db query is triggered b/c select_related has populated the instance yet
       if self.type.name == 'cat':
           self.__class__ = CatMixin
       else:
           self.__class__ = DogMixin

The Fix

Add a call to obj.post_db_init() to this line:
https://github.com/django/django/blob/master/django/db/models/query.py#L91

Then this code will eliminate the n+1 problem (assuming select_related('type') is used):

class PolymorphicModel(models.Model):
   type = models.ForeignKey(PetType)

    def post_db_init(self,):
       if type.name == 'cat':
           self.__class__ = CatMixin
       else:
           self.__class__ = DogMixin

I realize there are other ways to achieve the behavior in this example -- I'm bringing up polymorphism as a general use case. In django-lifecycle, this hook would allow me to track a model instance's initial state across a foreign key relationship without causing users to experience the n+1 problem.

Change History (4)

comment:1 by Robert Singer, 5 years ago

Description: modified (diff)

comment:2 by Robert Singer, 5 years ago

Component: UncategorizedDatabase layer (models, ORM)

comment:3 by Robert Singer, 5 years ago

Type: UncategorizedNew feature

comment:4 by Robert Singer, 5 years ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top