Opened 18 months ago
Last modified 4 weeks ago
#34555 new Bug
ModelBase metaclass implementation prevents addition of model fields via __init_subclass__ — at Version 2
Reported by: | hottwaj | Owned by: | nobody |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 4.2 |
Severity: | Normal | Keywords: | ModelBase init_subclass |
Cc: | Carlton Gibson | Triage Stage: | Someday/Maybe |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
The current implementation of ModelBase.new prevents addition of model fields via python's init_subclass method (described in PEP 487 and provided since python 3.6).
My understanding is that currently, fields can be added to models outside of a class body by:
i) calling "model_cls.add_to_class(...)" after the definition of model_cls e.g. in module level code after class definition,
ii) using a class decorator to call .add_to_class as in point i), or
iii) using a metaclass to completely customise class creation
Inheritance and init_subclass should in theory provide a relatively straightforward way to customise class creation without using a metaclass (option iii above), as described/encouraged in PEP 487, but Django does not currently support this for Field attributes of subclasses of Model because the ModelBase metaclass does not correctly pickup Fields added to a class during the execution of init_subclass
It seems that init_subclass is called after ModelBase.new collects Fields that require calling of their "contribute_to_class" method, so ModelBase does not do appropriate bookkeeping on fields added in init_subclass and such Fields are then ultimately not "seen" by e.g. the migration builder.
Correctly collecting Fields added by init_subclass in ModelBase.new would allow for easier customisation of model fields outside of a model class body and provide an implementation that works with init_subclass in a way that matches (rather than contrary to) behaviour supported elsewhere in python.
A simple example that currently does not work, but I believe ought to work, is provided below. In this example, the "author" attribute added in BaseBookModel.init_subclassis not collected by current implementation of ModelBase.new so is not created in migrations and not available as might be expected in subclass Book.
from django.db.models import Model, ForeignKey, CASCADE, CharField class BaseBookModel(Model): class Meta: abstract = True def __init_subclass__(cls, author_model_cls: Type[Model], **kwargs,): super().__init_subclass__(**kwargs) author = ForeignKey(author_model_cls, on_delete = CASCADE) cls.add_to_class('author', author) class Author(Model): name = CharField(max_len = 256, unique = True) class Book(BaseBookModel, author_model_cls = Author): pass
Thanks for reading and appreciate any thoughts!
Change History (2)
comment:1 by , 18 months ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
comment:2 by , 18 months ago
Description: | modified (diff) |
---|---|
Resolution: | invalid |
Status: | closed → new |
Summary: | Dynamic model fields without using a metaclass → ModelBase metaclass implementation prevents addition of model fields via __init_subclass__ |
Type: | New feature → Bug |
Thanks for taking time to review. I guess I did not phrase the issue very well - I am not looking for help implementing something, but rather think there is an issue with the way the ModelBase metaclass interacts with init_subclass which prevents model fields being added dynamically via the latter.
I have re-written the issue to clarify and would appreciate if you could take a second look. Thanks!
Thanks for this ticket, however, it seems that you're rather asking for help in your implementation. Trac is not an appropriate channel for this. You can start a discussion on the DevelopersMailingList or on the Django Forum, where you'll reach a wider audience and see what other think.