Opened 5 years ago
Last modified 5 years ago
#30844 closed New feature
Add after_db_init() hook method to model — at Initial Version
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
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 do it when iterating over a QuerySet
.
Causes 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) if 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.