[Answered ]-Django: Setting initial vals of multiplechoicefield only works first time

2πŸ‘

βœ…

To understand what’s going on, consider the nature of the objects at play:

  1. ActiveSignalForm is a class. It is defined within a module, which means that when the module is loaded into the interpreter, the class object is constructed.

  2. ActiveSignalForm.choices is a class attribute. As such, it is available to any code that loads the ActiveSignalForm class, before even an instance of that class is created. In other words, you can access it as ActiveSignalForm.choices without first having to create an instance with ActiveSignalForm().

  3. In this particular case, the definition involves an assignment β€” ActiveSignalForm.choices will be set to the return value provided by calling forms.MultipleChoiceField().

  4. And finally, two of the calling arguments to ModelChoiceField involve comprehension expressions.

With all of this information, you start getting a picture of the chain of events:

  1. The module containing the class is loaded. This kicks-off the construction of the ActiveSignalForm class.

  2. The interpreter encounters an assignment expression to define ActiveSignalForm.choices.

  3. The right-hand side of the expression is evaluated, which involves calling the MultipleChoiceField() callable.

  4. In order to do this, the choices and initial calling arguments must be resolved, and this involves evaluating two comprehension expressions.

So you see, steps 1 through 4 are nested, which results in the behaviour you are seeing β€” the server is started, the module is loaded, the values are computed once and then never again.

The Django documentation contains this piece of important information about both initial and choices fields:

Instead of a constant, you can also pass any callable. The callable will be evaluated only when the unbound form is displayed, not when it is defined.

So it seems that in order to have these fields re-calculated every time a different ActiveSignalForm instance is created, we will need to define them as callables.

To accomplish this, I believe all you need to do is prefix your comprehension expressions with lambda:, in order to create a simple callable:

choices = lambda: ((sig.id, sig.signal)
                   for sig
                   in registry.objects.order_by('first_registered')),
initial = lambda: [sig.id
                   for sig
                   in registry.objects.order_by('first_registered')
                   if sig.archival_active],

What this does, is it splits step (4), above, into two parts.

4a. Now, when the class is defined, the only thing that needs to be evaluated when resolving the initial= and choices= arguments to MultipleChoiceField is a lambda statement. This creates a tiny function that, when called will evaluate the comprehension expressions within. This allows the nesting to resolve (i.e. step 4a returns a callable for step 3, which returns a result for assignment as a class property in step 2, which then allows the rest of the class definition to proceed in step 1).

4b. (Much) later, when Django is rendering the ActiveSignalForm.choices field, it encounters the callables we created in (4a) and executes them in order to get the appropriate iterables.

πŸ‘€BillyBBone

0πŸ‘

There’s no reason to have these list comprehensions. You are filtering on values from querysets, so you should use ModelMultipleChoiceField along with an actual queryset.

choices = forms.ModelMultipleChoiceField(
    label='Alter Archiver Registry',
    queryset=registry.objects.order_by('first_registered')),
    initial=registry.objects.order_by('first_registered').filter(archival_active=True)
)

Leave a comment