How to filter the choices in forms.Select widget?

I have a model form, where I want to filter the choices in the forms.Select widget:

class ControlMemberGroupLineForm(forms.ModelForm):
	
	
	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self.queryset = Group.objects.filter(is_inactive=False)
	
	class Meta:
		model = Member_Group
		
		fields = (
			'group',
			'member',
			)
			
		error_messages = {
			'group': {
				'required': 'Please add at least one Group to this member'
				}
			}

		widgets = {
			'group': forms.Select(),
			#'group': forms.Select(choices=[('yes', 'y'),('no','n')]),
		}

Overriding the __init__ in this way seems to achieve nothing, I get the full set of (distinct) objects from Group. Changing the widgets attributes to comment out the first line and uncomment the second (with the choices) also makes no difference (ie I don’t get a choice of ‘yes’/‘no’).

I found a way that does change the choices, removing group from the various sections of the Meta class and then adding it in as a directly entered field (non-model?).

    group = forms.ModelChoiceField(queryset=Group.objects.filter(is_inactive=False), widget=forms.Select())

In this case I get the select choices I expect, but the option selected in the select are not populated (just defaults to first in the list), and generally seems to mess up the view with various errors when the form is posted – group is a many to many choice, so there is a fairly complicated view dealing with formsets.

Is there a way to populate the select widget with the filtered Group records, while defining the widget in the Meta?

Thanks

1 Like

Please post the Member_Group model. It may also help to see the view in which this form is to be used.

class Member_Group(models.Model):
	id = ShortUUIDField(primary_key=True, db_index=True, max_length=22)
	member = models.ForeignKey(Member, null=True, on_delete=models.CASCADE)
	group = models.ForeignKey(Group, verbose_name=_('Group'), null=True, on_delete=models.CASCADE)
	
	createdate = models.DateTimeField(auto_now_add=True)
	moddate = models.DateTimeField(auto_now=True)
	
	
	class Meta:
		constraints = [
			models.UniqueConstraint(fields=['member', 'group'], name='member_groups duplicate prevention')
		]

Starting from this:

class ControlMemberGroupLineForm(forms.ModelForm):
	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self.queryset = Group.objects.filter(is_inactive=False)
	
	class Meta:
		model = Member_Group		
		fields = ('group','member',)
			
		error_messages = {
			'group': {'required': 'Please add at least one Group to this member'}
		}

		widgets = {
			'group': forms.Select(),
			#'group': forms.Select(choices=[('yes', 'y'),('no','n')]),
		}

You’ve got the right basic idea for the commented out version, except that since group is a ForeignKey field, the choices y and n are not going to be valid selections - it must be the primary key from the Group model.

You do want to set the choices parameter, how you want to do it is described at Form fields | Django documentation | Django.

e.g.

def __init__(self, *args, **kwargs):
	super().__init__(*args, **kwargs)
	self.fields['group'].queryset = Group.objects.filter(is_inactive=False)

This also means you can remove the widgets dict from your Meta class

Disclaimer: I’m partially winging this, this may not all be 100% correct as written.

1 Like

Thanks, that was exactly right:

self.fields['group'].queryset = Group.objects.filter(is_inactive=False)

My previous (commented out) example, having [('yes','y')('no','n')]as the choices was just an attempt to debug, ie, to see what result I got (no change, as it turns out, just received the unfiltered Group list).