Passing an altered queryset to ModelChoiceField

Hi there again,

is it somehow possible to have a form with a ModelChoice field, but override its queryset by a filtered quersyset when instantiating the form?

Example:
If i have this in my forms.py:

class AddLockingAuthorisationForm(LockingAuthorisationForm):

    key = forms.IntegerField(widget=forms.HiddenInput, required=False, label="")

    room = forms.ModelChoiceField(queryset=Rooms.objects.all(),
                                  required=False,
                                  widget=forms.CheckboxSelectMultiple(attrs={
                                        'class': "fw-bold border border-1 border-black m-1 mb-2",
                                    }))

and what i have in mind is something like this:

            if 'mail_add' in request.POST:
                key = Keys.objects.get(pk=request.POST.get('mail_add')).pk
                room = Rooms.objects.exclude(lockingauthorisations__key_id=key)
                formdata = {'key': key, 'room': room}

…of course THIS doesn’t work, otherwise i would not post it here :stuck_out_tongue:

This is generally done by modifying the queryset attribute of that field in the __init__ method of the form.
e.g.

def __init__(self, *args, **kwargs):
  ...
  super().__init__(self, *args, **kwargs)
  ...
  self.fields['room'].queryset = your_altered_queryset
  ...

You could also alter that queryset in the view after it has been created but before it has been bound or rendered - precise mechanics depend upon whether you’re using a CBV or an FBV.

(There are a few previous topics in the forum discussing this issue along with more detailed examples.)

1 Like

Thanks again for this quick answer Ken, you really seeem to be kind of soul of this platform…
but if i follow your explanation, how can i pass a var to the filter of your altered_queryset?

Again, the precise mechanics are going to depend upon the view and how that form is being instantiated.

If you’re using an FBV, you can pass it directly. (You’ll also want to pop the value from kwargs in the __init__ method.)
If you’re using a CBV, you can override get_form.

See the threads below for some more examples and explanations:
(Not all are directly on point, but do, in general, illustrate the issue. There are more posts along these lines if you wish to search for them.)

I thank you very much for the input and will study the links! Considering the past of our talks, I am sure, I will find an answer :slight_smile:

Hmmm. I´m not sure wether this gets more complicated than it should be…

@KenWhitesell I´ve read the threads you kindly have linked but after all it seems to me, that solution must be much easier than all that…

Maybe I didn`t provide enough information on the topic in the beginning and that would be my bad.

I am talking about a form that is basically no model form and its data is not intended to be saved to database. The form is simply generated to provide an easy way for the user to enter data that will be afterwards automatically reformatted and opened in OS default email application for last checkup before sending.

So, therefore I am asking myself, wehter my form definition may be inappropriate for the task…

At the moment, I have this:

class AddLockingAuthorisationForm(LockingAuthorisationForm):

    key = forms.IntegerField(widget=forms.HiddenInput, required=False, label="")

    room = forms.ModelChoiceField(queryset=Rooms.objects.all(),
                                  required=False,
                                  widget=forms.CheckboxSelectMultiple(attrs={
                                        'class': "fw-bold border border-1 border-black m-1 mb-2",
                                    }))

but would like to instantiate the form with this:

key = Keys.objects.get(pk=request.POST.get('mail_add')).pk
room = Rooms.objects.exclude(lockingauthorisations__key_id=key)
formdata = {'key': key, 'room': room}
form = AddLockingAuthorisationForm(initial=formdata)

but this is not working at all. When i print out

Rooms.objects.exclude(lockingauthorisations__key_id=key)

i get the desired selection, but it is not accepted within form-rendering. I am using Django since April '23 and I am using Python since April '23. I have learned a lot so far and i am sure, my problem is completely silly to an experienced programmer but at the moment I am stuck…

I agree - I’m not sure we’re on the same page here.

You have:

This renders as a select field.

Side note: Since you’re using the CheckboxSelectMultiple widget, this field should be a ModelMultipleChoiceField. Otherwise you have a mismatch of types between the data type and the widget.

Right now, it’s going to show all Rooms as options.

My understanding is that you want that queryset to be modified to restrict the number of Rooms being shown as options.

If so, and if you want the filter to be determined at the time the form is instantiated, then yes - you need to do it in one of the ways I described earlier. There really isn’t an “easier” way - not that this is particularly difficult.

Side note #2: The initial parameter in the form instantiation identifies the initial values of a field, it does not serve to alter or set the options that can be selected in a choice-style field.

1 Like

Thanks again for clarification. All of Your assumptions…
AND side notes :see_no_evil: are completely correct,
I’m afraid :stuck_out_tongue:

(The lacking of ModelMultipleChoiceField will not lead to a problem in this speacial case, because the form-data is only used to generate an e-Mail and will not be saved to database, but nevertheless, you are right concerning programming :slight_smile: )

Concerning the initialparameter, you were describing a fact that i have simultaneously concluded myself…but this means your are also completely correct here :wink:

Thanks for taking care of this subject matter, so then I will have to deal with it in a way You have described/linked earlier :slight_smile:

FunFact: Changed ModelChoiceField to ModelMultipleChoiceField and all of a sudden my problems are gone :stuck_out_tongue: Now I get a preselected list with all rooms checked or unchecked according to my altered queries :rofl:

Ok, was thinking too complicated after all, after reading your linked posts I did it this way and it works fine:

views.py:

elif 'mail_add' in request.POST:
       key = Keys.objects.get(pk=request.POST.get('mail_add')).pk
       context['key_number'] = Keys.objects.get(pk=request.POST.get('mail_add'))
       formdata = {'recipient': 1, 'key': key, 'alter': 'ERTEILEN'}
       form = AddLockingAuthorisationForm(initial=formdata, key=request.POST.get('mail_add'))

forms.py:

class AddLockingAuthorisationForm(LockingAuthorisationForm):

    def __init__(self, key, *args, **kwargs):
        super(AddLockingAuthorisationForm, self).__init__(*args, **kwargs)
        self.fields['room'].queryset = self.fields['room'].queryset.exclude(lockingauthorisations__key_id=key)

Once more Thx for your support @KenWhitesell

1 Like