3 | | Having used metaclasses in a similar situation before, but hoping to find a less complex approach, I thought I might be able to implement this by providing a __init_subclass__ method on my "template" model, with that method designed to set up the ForeignKey to a given concrete model, but this does not work. It seems that __init_subclass__ is called after ModelBase.__new__ collects fields that require "contribute_to_class", so the fields I add in __init_subclass__ are never "seen" by e.g. the migration builder. |
| 3 | My understanding is that currently, fields can be added to models outside of a class body by: |
| 4 | i) calling "model_cls.add_to_class(...)" after the definition of model_cls e.g. in module level code after class definition, |
| 5 | ii) using a class decorator to call .add_to_class as in point i), or |
| 6 | iii) using a metaclass to completely customise class creation |
5 | | Example approach below (but note this does not work for reasons explained above): |
| 8 | 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__ |
| 9 | |
| 10 | 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. |
| 11 | |
| 12 | 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. |
| 13 | |
| 14 | 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_subclass__is not collected by current implementation of ModelBase.__new__ so is not created in migrations and not available as might be expected in subclass Book. |