I have a general best-practices question about using database queries for class attributes. I’ve been bitten a couple times by this (including yesterday). I was making a form class (not for a model) where I wanted to set a placeholder attribute and I wanted a custom widget that used a datalist
element. And I wanted both of those to be populated by the results of database queries. Everything worked fine until I ran a migration check. The migration code evaluates the class before the DB is set up, so it complains about the queries, saying that the model/table doesn’t exist.
I understand the problem and the reasoning for it, and I considered 2 options for resolving it:
- Initialize the widgets for the fields in a constructor
- Put the class definition inside a factory method
I’m not terribly enamored with either option, (is there one I’m not thinking of?), but I went with the factory option. I also had to edit my View class, to deal with the setting of the form_class
attribute I’d created by overriding super().get_form_class()
.
I’d tried the constructor option first, but ran into a problem with some other code that I didn’t bother to dig into, and elected to take the easier path of using the factory option. I may go back and switch to the constructor option, TBH, but I just wanted to get to a PR before COB, and deal with it in a separate branch.
Anyway, I’m just wondering about best practices (so that the code is easy to follow for others). The reason I’m using the DB for the datalist
s in the form is to mitigate nomenclature issues that we’re not strict about on the database level, so there are no static lists to use instead of the DB queries.
Only half of my team are fluent in python, so I feel like using the constructor for the widget initialization may be the way to go (I think they’re unfamiliar with factories), but (other than the unrelated issue I ran into with that strategy), are there any gotchas related to adding a widget to the fields in the constructor (e.g. using self._meta.fields["operator"].widget = ...
) as opposed to defining it in the form’s class attributes? I suspect there may be a state issue if this is stored at the class level - is it? Or do the field details get stored in the instance?
I couldn’t find much on class level DB queries in the docs, and what I read on stack about the migration gotcha WRT class level DB queries says that that gotcha is completely undocumented, though I’d like to know if there’s some documentation I’m just not finding on that topic…
Here’s an example of the factory code I ended up with:
def create_BuildSubmissionForm() -> Type[Form]:
"""This class works around a problem that raises an exception when migrations are created or checked and there exist
database calls at the class level. See: https://stackoverflow.com/a/56878154"""
class BuildSubmissionForm(Form):
...
operator = CharField(
required=False,
widget=AutoCompleteTextInput( # My custom widget
"operators_datalist",
get_researchers(), # The DB query
datalist_manual=False,
attrs={
"placeholder": "John Doe",
"autocomplete": "off", # This prevents Safari from trying to insert contacts, which messes up my autocomplete HTML
},
),
)
...
return BuildSubmissionForm
Is this the only safe option?