I am facing the problem described in this StackOverflow post (albeit being on Django 2.2 and not using django-solo)
I have a function used as a default for a field a
of model A
that accesses a different model Settings
, which provides default settings from the database. This access is obviously not using the historical model B
as it is part of the regular app code.
class A(Model):
a = DateTimeField(default=default_a, null=True, blank=True)
class Settings(Model):
setting = TextField()
value = IntegerField()
def default_a():
return Settings.object.get(setting="a").value
Later adding a column to Settings
causes errors in the previous migration that adds the default to A.a
as Django evaluates default_a
once to determine the default if it needs one. Accessing Settings
as part of this previous migration complains about the column missing from Settings
as Django tries to get the referenced settings object. Note that there exist no A
objects that actually need the default. The field a
is added in three migrations: 1) add field without default, 2) update all existing models in a data migration, 3) add the default. The function is nevertheless evaluated in this third migration.
The StackOverflow post has one answer suggesting to access the Settings
model only using the columns necessary with values
, which seems a bit like a hack to me. I tried to overwrite default_a
in the migration to provide a version working in the historical context, but this way Django thinks that the current models are different from the migration state so it wants to create a migration with the actual default when running makemigrations
.
Am I missing the proper way to do that or are callable defaults not supposed to do something like that?
Hi dfn-certling,
The upvoted answer on that SO question indicates that you could try to use .only
to limit which fields are being used in the SQL query that gets generated. What you have currently for default_a
would select all fields that exist on the Settings
model, including ones that wouldn’t have columns created yet because the migration hasn’t been run.
Can you try that and let me know if that resolves the issue?
-Tim
Hi Tim,
you’re right. I somehow missed that part and it indeed feels less hackish than the values
solution. .only
works as well. I think I was looking for some way to provide a historical version of the callable, but this is good enough for the use case.
Thanks
Keep in mind if you ever need to change that table or that column, this will likely break in the future.
That can well be the case, yes. I’m aware that things referenced by migrations have to be present, as long as the migration is. So for some scenarios that might come up, I’m willing to hold on to some old version until I can squash migrations.
Or do you see a better solution for this? One could do this in A.save()
with the drawback of — not tested but as far as I understand it from the documentation — simply being ignored in the migration. Thus, I could provide a one of solution but would have to think of it and not be reminded by some error if I forget to do so.
It seems like being able to provide a historical version would solve this, and part of it is possible with preserve_default = False
in AddField. But there seems to be no way to register the actual default — which automatically is recognized in makemigrations
— without Django executing it at least once.
One option to investigate is overriding the deconstruct
method on the model and pop off the default. Since the django default is at the application layer and not in the database, this could work:
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
kwargs.pop("default", None)
return name, path, args, kwargs
Yes, that works. Unfortunately it has sort of the same drawback as the save option. If I now create a new model with the historical class in a data migration, the field is set to None
in the absence of another value.
If that is ok, depends on the application. In my case objects with a = None
are kind of special and should only be created explicitly and consciously. Since the application is deployed in-house and not in large numbers with possibly different migration states, the risk of running into problems with future changes in the Settings model that can be addressed with a consistent migration state and squashing is preferable compared with the chance of inadvertently creating models with a = None
.
Another way could also be to introduce some state into default_a
so that you could return for example a static default when set in a migration context. This would again force a conscious decision about the migration time behavior. But adding features to the running code to address migration time issues seems rather hackish again.
But thanks, this seems like a solution for the general problem as well.