Using a dictionary in limit_choices_to

Hi, I’m trying to understand what happens when I pass a dictionary as the limit_choices_to argument in a ModelField. Based on a little digging around, it looks like it only supports a strict {field_name: required_value} usage, and for anything more complicated I would need to use a Q expression. Is that right? Is there any other behavior that you can get by passing a dictionary?

More specifically, I was wondering whether I could pass limit_choices_to={field_name: [allowed_values]}, but that doesn’t seem to work.

Looking at this line in the source (forms/models.py):

if not isinstance(complex_filter, Q):
    complex_filter = Q(**limit_choices_to)

It looks like the dictionary just gets passed and splatted directly to a Q expression. I hit a bit of a wall digging into the code for Q expressions, so I just tried experimenting, and indeed it seems that something else happens:

# desired outcome using Q-expressions
$ Q(abc='xyz') | Q(abc='def')
<Q: (OR: ('abc', 'xyz'), ('abc', 'def'))>

# alternate attempt that gives the wrong output
$ Q(**{'abc': ['xyz','def']})
<Q: (AND: ('abc', ['xyz', 'def']))>

It looks like the alternate attempt is checking for the literal value ['xyz','def'] in the abc field. Am I understanding that right?

Is there any other documentation that would explain the functionality of passing a dictionary to limits_choices_to?

That’s not quite accurate. If you look at the examples in the docs for limit_choices_to in combination with the source you’ve found, you’ll see that what you can pass as a key in the dictionary is any valid key you could pass in a filter expression.

So, for the example you posed, you could pass limit_choices_to={'field_name__in': [allowed_values]}. Or, you can even transit through field relationships as in limit_choices_to={'field_name__related_field__gt': some_value}. And, like a filter clause, you can have multiple keys for a compound logical AND expression.

When working in Django, it’s helpful to remember that in Python, these sets of statements are all identical in their effect:

SomeModel.objects.filter(field_1=3, field_2='Yes')

and

SomeModel.objects.filter(**{'field_1': 3, 'field_2': 'Yes'})

and

comparison = {'field_1': 3, 'field_2': 'Yes'}
SomeModel.objects.filter(**comparison)

Once I got my head fully wrapped around that, parts of Django made a lot more sense to me.

2 Likes

Wow, thank you Ken! Super helpful. Your explanation is actually much clearer than that documentation page. I’m pretty comfortable with python so I’m familiar with the different ways to pass kwargs. But I’m new to Django, so it was great to see the various options of what a filter expression will accept as the keys. If I want to learn more, this would be under field lookups, right?

That is correct. (Doc reference to lookup options)

1 Like