Changes between Initial Version and Version 2 of Ticket #34555


Ignore:
Timestamp:
May 10, 2023, 3:44:00 PM (14 months ago)
Author:
hottwaj
Comment:

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!

Legend:

Unmodified
Added
Removed
Modified
  • Ticket #34555

    • Property Summary Dynamic model fields without using a metaclassModelBase metaclass implementation prevents addition of model fields via __init_subclass__
    • Property Type New featureBug
  • Ticket #34555 – Description

    initial v2  
    1 I'd like to be able to write some abstract model "templates" that can be re-used/customised in various other django apps.  Some of these templates require ForeignKeys to other "templates" which are also abstract.  Obviously ForeignKeys to Abstract models are not possible
     1The 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). 
    22
    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.
     3My understanding is that currently, fields can be added to models outside of a class body by:
     4i) calling "model_cls.add_to_class(...)" after the definition of model_cls e.g. in module level code after class definition,
     5ii) using a class decorator to call .add_to_class as in point i), or
     6iii) using a metaclass to completely customise class creation
    47
    5 Example approach below (but note this does not work for reasons explained above):
     8Inheritance 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
     10It 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
     12Correctly 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
     14A 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.
    615
    716{{{#!python
     
    2433}}}
    2534
    26 Essentially what I'd like is some way of doing some extra work after BaseModel.__new__ is called without resorting to having to write a metaclass for BaseBookModel - I'm just adding some extra fields and a metaclass is complex for most mortals to read let alone write.  There does not seem to be an appropriate hook method that I can override to do this for every subclass of BaseBookModel
    27 
    2835Thanks for reading and appreciate any thoughts!
Back to Top