Excellent, thanks for confirming! Thinking about it, I can probably post an anonymised explanation here just in case it helps other people.
A common constraint one might put into a Django app is to limit the values that a user can select in a form to a set of choices that are determined at runtime from another table in the database -- that is, from another model's objects. Normally a foreign key is the right solution, but sometimes you just want to have a simple constraint on user input rather than something at the database level.
So, some example code. We have these two models:
class Foo(models.Model):
name = models.CharField(max_length=255)
class Bar(models.Model):
option = models.CharField(max_length=255)
...and we want to create a Django Form
that allows someone to edit a Bar
object and to only be able to set its option
to a value that is present as a name
for a Foo
object that exists at runtime.
A natural Django-esque manner to do this is to use a ChoiceField
:
class BarForm(forms.Form):
option = forms.ChoiceField(choices=[foo.name for foo in Foo.objects.all()])
This will work fine if you already have the Foo
object set up and populated when you add the form to your code. But if you're starting with a fresh database, and want to run a migration to get it all set up, you have a problem. When Django starts up in order to run your migrations, it imports all of your code as a first step. Part of the code that it imports is the definition of the BarForm
. But that depends on the existence of the Foo
model in the database; so at import time, before the migrations have been run, it will run the Foo.objects.all()
, which means that it will try to connect to the DB and read in the appropriate values -- which will fail because it hasn't got to running the migrations yet so there is no table for Foo
in the DB.
The solution is to defer the getting of the list of Foo
objects until it's actually needed -- that is, until you create an instance of the form, rather than when you define it. Django has a nice way to do this; the choices
parameter for ChoiceField
can either be an iterable object like a list, or it can be a function that returns an iterable object. So if we change the form definition to
class BarForm(forms.Form):
option = forms.ChoiceField(choices=lambda: [foo.name for foo in Foo.objects.all()])
...then the database query will not run at import time, so the form can be imported and the migrations can be run.
This also has an extra added bonus; because in the original code the choices were read from the database at import time, if you added a new Foo
object you would need to reload the website in order to pick it up in your form. Deferring the database query to form creation time means that this doesn't happen -- each time you create a new instance of the form, the database is checked and a free set of choices is read in.