resolve settings in GeneratedField when migration are applied

Is it possible to use settings at “migration time” when using GeneratedField?
My model:

class Foo(model.Model):
    name = models.CharField(max_length=30)
    link = models.GeneratedField(
        expression=Concat(
            models.Value(settings.SOME_HOST),
            models.Value("/api/v2/"),
            "name"
        ),
        output_field=models.URLField(),
        db_persist=False,
    )

link used to be a property, but I want to migrate to GeneratedField.
Unfortunately, the code above creates the migration with the actual value of settings.SOME_HOST, and not with the settings name.

# settings.py
SOME_HOST = "https://test-api.example.com"

# 0001_foo.py
...
('link', models.GeneratedField(db_persist=False, expression=django.db.models.functions.text.Concat(models.Value('https://test-api.example.com'), models.Value('/api/v2/'), 'name'), output_field=models.URLField()))
...

This breaks my current development flow, since the value of settings.SOME_HOST comes from settings at the time and from the environment on which I run makemigrations, and not from the time the migrations are actually being applied on the target enviroment.

I don’t think you could do it in the model itself, because that’s going to be resolved during the makemigrations command.

But since you want this to be evaluated during migrate, I’m thinking you’ll need to create a custom migration, probably using one of the AlterField, RunPython, or RunSQL operation.

I’d suggest you create a specialized Expression subclass that defers the resolving of the setting value.

Something like

class SettingValue(models.Value):
    def __init__(self, setting_name):
        super().__init__(getattr(settings, setting_name))

And then use it as

expression=Concat(
    SettingValue("SOME_HOST"),
    models.Value("/api/v2/"),
    "name"
)

This will ensure the expression is deconstructed as SettingValue("SETTING_NAME") instead of picking up the makemigration time value.

Note that such an approach will prevent any changes in the referenced setting from generating a new migration but I think that’s something you are aware of.

2 Likes

That’s actually a very neat idea. I’ll explore it :slight_smile: