wkrueger
wkrueger

Reputation: 1363

Django: dynamically/programmatically setting model fields

I am attempting to create tables with fields based on metadata declared on other classes. My current approach is a bit like this:

metadata = { ... }
class CustomModel(MyBaseModel):
    field1 = CharField(...)
    field2 = CharField(...)
    ...

for key, info in metadata.items():
    setattr(CustomModel, key, fieldFrom(info))

What currently happens is that the table is created and the fields declared in class included in the migration.

BUT the fields included through setattr are not getting included in the migration, even tough they correctly appear in the class when inspecting with the debugger. Is there any magic that only works for fields declared “in-place”? How could I dynamically set those fields?

EDIT: The models are still static. The gist here is that when I make changes to source metadata, those changes would propagate to (i.e.) several models and/or fields. Without this, I'd have to exhaustively add fields to several models manually.

EDIT2:

I'm gonna hand an example that is not really my current case but also applies. Imagine I had an openAPI (swagger) file somewhere inside my project and I wanted to dynamically create tables based on its definitions key. This should be no big deal, right?

This swagger.json file would be static. Whenever I made changes to it, I'd run makemigrations and it would add the necessary changes to my DB.

(Now please, don't come with "but you shouldn't be creating data from a swagger.json", this is not the focus of my question -- and this is even a fictional example. I didn't ask for architecture advice, thank you!)

Upvotes: 0

Views: 1277

Answers (1)

wkrueger
wkrueger

Reputation: 1363

Django models use the "metaclasses" feature of python.

Short explanation of what I understood of metaclasses:

  • When you declare a class, this somewhat "desugars" to the invocation of a callable. Which will usually be type("ClassName", (extends.list,), props), type being the "root metaclass";
  • When you set a metaclass, you (sort of) replace type() with something else;
  • Theres also extra magic around since some extra magic fields like __module__ are set;
  • You set a new metaclass by writing class X(metaclass=Y):;
  • You can override the declaration of a class by setting __new__ on its metaclass; __new__ should return the remapped class (not to be confused with its instance)

For the current sample, we get an inheritance chain of something like:

class MyModel(models.Model)
class Model(metaclass=BaseModel) #django's
class BaseModel(type)            #django's

BaseModel overrides __new__ and performs logic on class declaration. You can see the result by inspecting MyModel, which will have a _meta field with tons of metadata.

When I had used setattr, the model metadata didnt get updated since the base class only does that on init.

The fix: When looking into BaseModel, I found the add_to_class method which does the trick. Currently working. Might have issues? I hope not.

MyModel.add_to_class('my_field_name', CharField(...))

Upvotes: 2

Related Questions