1

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!)

7
  • 1
    Dynamically changing models is tricky, since schema sync doesn't happen automatically. Under normal workflow you would modify the model class, run makemigrations command and then migrate. If you want to dynamically change model fields, you would need to find a way to dynamically generate and apply migrations as well. This feels dangerous and fragile. You might want to consider just how badly do you want to do this. Commented Oct 18, 2019 at 15:27
  • The models are still "static". It is just that they are generated from other code. When I change the source metadata -- through code, the source metadata IS static. -- those changes would propagate to several models. Then I'd run makemigrations and migrate. Commented Oct 18, 2019 at 16:19
  • Maybe you should have a look at docs.djangoproject.com/en/2.2/topics/db/models/… if you have several fields which are common across several models, it would make sense to group them in abstract models. Commented Oct 18, 2019 at 16:33
  • 1
    Habe a look at contribute_to_class: docs.djangoproject.com/en/2.2/_modules/django/db/models/fields Commented Oct 18, 2019 at 17:56
  • 1
    perhaps this would help you? pypi.org/project/django-dynamic-model Commented Oct 18, 2019 at 20:43

1 Answer 1

3

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(...))
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.