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 )
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 , 5 years ago
Description: | modified (diff) |
---|
comment:2 by , 5 years ago
Component: | Uncategorized → Database layer (models, ORM) |
---|
comment:3 by , 5 years ago
Type: | Uncategorized → New feature |
---|
comment:4 by , 5 years ago
Description: | modified (diff) |
---|