Django-filter: Filtering by Foreign Key Model's Attribute

Created on 9 Jan 2019  ·  4Comments  ·  Source: carltongibson/django-filter

Hi there!

I was working on an issue at work and wondering what the most idiomatic way of tackling it is. We have multiple ModelMultipleChoiceFilter filter fields for foreign keys of a model. I'm trying to figure out how to filter by a unique attribute on the foreign keys while using a CheckboxSelectMultiple widget, without exposing primary keys in the URLs.

Here's an example that (more or less) shows how I have it set up right now.

# app/models.py
from django.db import models

class Foo(models.Model):
    prop = models.CharField(max_length=55, unique=True)

    def __str__(self):
        return self.name


class Bar(models.Model):
    foo = models.ForeignKey(Foo, related_name="bars")
# app/filters.py
import django_filters
from app.models import Bar, Foo

class BarFilter(django_filters.FilterSet):
    foo = django_filters.ModelMultipleChoiceFilter(
        queryset=Foo.objects.all(),
        widget=CheckboxSelectMultiple(),
        label="Foo",
        label_suffix="",
    )

    class Meta:
        model = bar
        fields = ['foo']

This works totally fine in the view. The filters work and filter correctly with the right widgets. However, the url has the primary key in it, so in this example /?foo=1, whereas I want it to read /?foo=<prop value>, to prevent exposing my pk as well as make urls more readable.

I tried modifying fields like this, because elsewhere in Django it tends to work:

# app/filters.py
...snip...
    class Meta:
        model = bar
        fields = ['foo__prop']

This makes the url respond to /?foo=<prop value> as desired, but now it doesn't use CheckboxSelectMultiple for the form. I tried making the attribute into foo__prop as well, but the form rendering was still incorrect.

I have a feeling what I am trying to do is supported considering its almost working, but I can't seem to figure out quite how to get it. If this is outside the current supported scope, I can write my own form to handle this. Thank you very much for any help and for the awesome project!

Most helpful comment

Hi, thank you for taking the time to reply!

You've set me up on the right track! I got a chance to implement this yesterday, and using the field_name argument worked!

For anyone in the future who might read this issue, here's approximately how the code came out:

# app/filters.py
...snip
class BarFilter(django_filters.FilterSet):
    foo = django_filters.ModelMultipleChoiceFilter(
        queryset=Foo.objects.all(),
        field_name="foo__prop", # This lets us keep the url as "/?foo=<value>
        to_field_name="prop",
        widget=CheckboxSelectMultiple(),
        label="Foo",
        label_suffix="",
    )

    class Meta:
        model = bar
        fields = ['foo']

You can find a reference to this in the documentation @carltongibson mentioned up thread. Specifically, see the section on ModelMultipleChoice filters.

Anyways, thank you so much for taking the time to help me out!

All 4 comments

Hiya. Nice issue. Strictly a usage question, but so well put it’s a pleasure to read.

Did you look at the field_name argument to filters?
https://django-filter.readthedocs.io/en/master/ref/filters.html

This would allow you to use foo_prop as the target for your foo filter.

I’d imagine that’d just work... maybe you’d need to set choices on the widget or such (but that should be handled...)

Hi, thank you for taking the time to reply!

You've set me up on the right track! I got a chance to implement this yesterday, and using the field_name argument worked!

For anyone in the future who might read this issue, here's approximately how the code came out:

# app/filters.py
...snip
class BarFilter(django_filters.FilterSet):
    foo = django_filters.ModelMultipleChoiceFilter(
        queryset=Foo.objects.all(),
        field_name="foo__prop", # This lets us keep the url as "/?foo=<value>
        to_field_name="prop",
        widget=CheckboxSelectMultiple(),
        label="Foo",
        label_suffix="",
    )

    class Meta:
        model = bar
        fields = ['foo']

You can find a reference to this in the documentation @carltongibson mentioned up thread. Specifically, see the section on ModelMultipleChoice filters.

Anyways, thank you so much for taking the time to help me out!

Super. Glad you solved it. Well done!

Great job ! Thank you !

Was this page helpful?
0 / 5 - 0 ratings