Class defaults involving DB contents

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:

  1. Initialize the widgets for the fields in a constructor
  2. 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 datalists 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?

Oops. I should add a little more context, because I searched the forum and found some adjacently relevant content.

So the ideal solution to the example would be to have a Researcher model and use something like a ModelChoiceField (with one big caveat^). I have lobbied for us creating a Researcher model, but have thus far lost that debate each time. It’s never been a “priority”. So what we have is I think 2 fields in 2 different models that contain a CharField for researchers in different contexts: Animal Handler and Mass Spec Operator, so getting the union of all unique researchers currently involves a query of 2 models.

My understanding from the other forum threads is that DB queries don’t belong in the form field definition at all, though given my constraints, unless I do this in (or from) the view class instead and manually render the datalist element, I’m not sure how I could do it in an encapsulated and reusable fashion. In fact, my initial instinct was to do just that. I hadn’t created a custom widget before, but I thought putting this code there would be the best way to re-usably encapsulate that code. So I still may be missing something…

^ The caveat is that this is a load interface, so there could be novel input that would end up getting loaded. Perhaps there’s a way to allow novel input in a choice-like field that I’m just not familiar with yet.

We actually have a form instantiation that looks like this in the view:

action_form = forms.Form()

Yep, that’s the complete form.

After we create the instance, we populate the action_form.fields dict directly with field related data, in the view.

Consider the following session from the Django shell:

>>> from django import forms
>>> af = forms.Form()
>>> af.fields['a_field'] = forms.CharField(max_length=10, required=False)
>>> af.fields['b_field'] = forms.IntegerField()
>>> af.fields['c_field'] = forms.ModelChoiceField(User.objects.all())
>>> print(af.as_p())
<p>
    <label for="id_a_field">A field:</label>
    <input type="text" name="a_field" maxlength="10" id="id_a_field">
  </p>
  <p>
    <label for="id_b_field">B field:</label>
    <input type="number" name="b_field" required id="id_b_field">
  </p>
  <p>
    <label for="id_c_field">C field:</label>
    <select name="c_field" required id="id_c_field">
  <option value="" selected>---------</option>
  <option value="1">First User</option>
  <option value="2">Second User</option>
  <option value="3">Third User</option>
</select>
  </p>

(Disclaimer: I did do a little cleanup of the HTML produced by as_p to remove excess line feeds and redact the user names.)

This all works fine.

In fact, in our case, the form to be created is defined by a JSON data structure containing both the field types and the attributes to be supplied, which is then eval-ed to create the actual fields.

This appears to automatically generate a select list whose values are (what appear to be) record IDs. I assume that the visible options are the __str__ of those records. How is it that I can use this in my case, (where [to clarify] I don’t have a User(/Researcher) model and the loading form needs to be able to accept novel entries (not in the DB))?

What I was trying to highlight with that highly-contrived example is that you don’t need to define a form as a separate class.

You can create an instance of Form() in your view, and then add the fields you need by adding the entries to the form_instance.fields dict. You create those fields using the same classes as you would use in the Form class, except that since you’re creating them in your view, their attributes are completely dynamic.

So in the situation you’re referring to, instead of passing a queryset to a ModelChoiceField instance, you would create the choices to be used in a ChoiceField.