Opened 6 years ago
Last modified 6 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.